mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Adds debug visualizer for hitboxes
Summary: Adds debug visualizer to render Collider and CollidableComponents. You can choose their color in the entity defition even. Tweaked how HitboxComponent stores its AABB, for performance. (Forgive me, I lost control!) Fixed hitbox calculation that was shifting them down and to the right. Collidables still seem to prefer going by the sprite size instead of looking for a HitboxComponent. That will need to be fixed later. As will the offset of the player sprite. Test Plan: Hit F4 and caress the table. Reviewers: #ss14_developers, volundr- Reviewed By: #ss14_developers, volundr- Projects: #space_station_14 Differential Revision: http://phab.nexisonline.net/D40
This commit is contained in:
@@ -12,6 +12,8 @@ namespace CGO
|
||||
{
|
||||
public class CollidableComponent : Component, ICollidable
|
||||
{
|
||||
public Color DebugColor { get; set; }
|
||||
|
||||
private bool collisionEnabled = true;
|
||||
private RectangleF currentAABB;
|
||||
protected bool isHardCollidable = true;
|
||||
@@ -24,6 +26,7 @@ namespace CGO
|
||||
public CollidableComponent()
|
||||
{
|
||||
Family = ComponentFamily.Collidable;
|
||||
DebugColor = Color.Red;
|
||||
tweakAABB = new Vector4D(0,0,0,0);
|
||||
}
|
||||
|
||||
@@ -176,6 +179,11 @@ namespace CGO
|
||||
case "TweakAABBleft":
|
||||
tweakAABB.W = parameter.GetValue<float>();
|
||||
break;
|
||||
case "DebugColor":
|
||||
var color = ColorTranslator.FromHtml(parameter.GetValue<string>());
|
||||
if (!color.IsEmpty)
|
||||
DebugColor = color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,70 +11,44 @@ namespace CGO
|
||||
{
|
||||
public class ColliderComponent : Component
|
||||
{
|
||||
private RectangleF currentAABB
|
||||
public Color DebugColor { get; set; }
|
||||
|
||||
private RectangleF AABB
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Owner.HasComponent(ComponentFamily.Hitbox))
|
||||
{
|
||||
return Owner.GetComponent<HitboxComponent>(ComponentFamily.Hitbox).AABB;
|
||||
}
|
||||
if (Owner.HasComponent(ComponentFamily.Renderable))
|
||||
{
|
||||
else if (Owner.HasComponent(ComponentFamily.Renderable))
|
||||
return Owner.GetComponent<IRenderableComponent>(ComponentFamily.Renderable).AverageAABB;
|
||||
}
|
||||
return RectangleF.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// X - Top | Y - Right | Z - Bottom | W - Left
|
||||
/// </summary>
|
||||
private Vector4D tweakAABB;
|
||||
|
||||
public ColliderComponent()
|
||||
{
|
||||
Family = ComponentFamily.Collider;
|
||||
tweakAABB = new Vector4D(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private Vector4D TweakAABB
|
||||
{
|
||||
get { return tweakAABB; }
|
||||
set { tweakAABB = value; }
|
||||
}
|
||||
|
||||
public RectangleF OffsetAABB
|
||||
{
|
||||
get
|
||||
{
|
||||
// Return tweaked AABB
|
||||
var currAABB = currentAABB;
|
||||
if (currAABB != null)
|
||||
return
|
||||
new RectangleF(
|
||||
currAABB.Left +
|
||||
Owner.GetComponent<TransformComponent>(ComponentFamily.Transform).Position.X -
|
||||
(currAABB.Width / 2) + tweakAABB.W,
|
||||
currAABB.Top +
|
||||
Owner.GetComponent<TransformComponent>(ComponentFamily.Transform).Position.Y -
|
||||
(currAABB.Height / 2) + tweakAABB.X,
|
||||
currAABB.Width - (tweakAABB.W - tweakAABB.Y),
|
||||
currAABB.Height - (tweakAABB.X - tweakAABB.Z));
|
||||
else
|
||||
return RectangleF.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetParameter(ComponentParameter parameter)
|
||||
public ColliderComponent()
|
||||
{
|
||||
base.SetParameter(parameter);
|
||||
Family = ComponentFamily.Collider;
|
||||
DebugColor = Color.Lime;
|
||||
}
|
||||
|
||||
switch (parameter.MemberName)
|
||||
public RectangleF WorldAABB
|
||||
{
|
||||
get
|
||||
{
|
||||
case "TweakAABB":
|
||||
TweakAABB = parameter.GetValue<Vector4D>();
|
||||
break;
|
||||
// Return tweaked AABB
|
||||
var aabb = AABB;
|
||||
var trans = Owner.GetComponent<TransformComponent>(ComponentFamily.Transform);
|
||||
if (trans == null)
|
||||
return aabb;
|
||||
else if (aabb != null)
|
||||
return new RectangleF(
|
||||
aabb.Left + trans.X,
|
||||
aabb.Top + trans.Y,
|
||||
aabb.Width,
|
||||
aabb.Height);
|
||||
else
|
||||
return RectangleF.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,5 +56,15 @@ namespace CGO
|
||||
{
|
||||
return IoCManager.Resolve<ICollisionManager>().TryCollide(Owner, offset, bump);
|
||||
}
|
||||
|
||||
public override void SetParameter(ComponentParameter parameter) {
|
||||
switch (parameter.MemberName) {
|
||||
case "DebugColor":
|
||||
var color = ColorTranslator.FromHtml(parameter.GetValue<string>());
|
||||
if (!color.IsEmpty)
|
||||
DebugColor = color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,47 +6,55 @@ using System.Text;
|
||||
using GameObject;
|
||||
using SS13_Shared.GO;
|
||||
using SS13_Shared.GO.Component.Hitbox;
|
||||
using ClientInterfaces.GOC;
|
||||
|
||||
namespace CGO
|
||||
{
|
||||
public class HitboxComponent : Component
|
||||
{
|
||||
public SizeF Size;
|
||||
public PointF Offset;
|
||||
public PointF UpperLeft
|
||||
{
|
||||
get
|
||||
{
|
||||
return new PointF(Offset.X - Size.Width / 2, Offset.Y - Size.Height / 2);
|
||||
namespace CGO {
|
||||
public class HitboxComponent : Component {
|
||||
|
||||
public RectangleF AABB { get; set; }
|
||||
public SizeF Size {
|
||||
get {
|
||||
return AABB.Size;
|
||||
}
|
||||
set {
|
||||
AABB = new RectangleF(
|
||||
AABB.Left + (AABB.Width - value.Width),
|
||||
AABB.Top + (AABB.Height - value.Height),
|
||||
value.Width,
|
||||
value.Height
|
||||
);
|
||||
}
|
||||
}
|
||||
public RectangleF AABB
|
||||
{
|
||||
get
|
||||
{
|
||||
return new RectangleF(UpperLeft, Size);
|
||||
public PointF Offset {
|
||||
get {
|
||||
return new PointF(AABB.Left + AABB.Width / 2f, AABB.Top + AABB.Height / 2f);
|
||||
}
|
||||
set {
|
||||
AABB = new RectangleF(
|
||||
value.X - AABB.Width / 2f,
|
||||
value.Y - AABB.Height / 2f,
|
||||
AABB.Width,
|
||||
AABB.Height
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public HitboxComponent()
|
||||
{
|
||||
|
||||
public HitboxComponent() {
|
||||
Family = ComponentFamily.Hitbox;
|
||||
Size = new SizeF();
|
||||
Offset = new PointF();
|
||||
}
|
||||
|
||||
public override Type StateType
|
||||
{
|
||||
get
|
||||
{
|
||||
public override Type StateType {
|
||||
get {
|
||||
return typeof(HitboxComponentState);
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleComponentState(dynamic state)
|
||||
{
|
||||
Size = state.Size;
|
||||
Offset = state.Offset;
|
||||
public override void HandleComponentState(dynamic state) {
|
||||
AABB = state.AABB;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace ClientServices.Collision
|
||||
var collider = (ColliderComponent)entity.GetComponent(ComponentFamily.Collider);
|
||||
if (collider == null) return false;
|
||||
|
||||
var ColliderAABB = collider.OffsetAABB;
|
||||
var ColliderAABB = collider.WorldAABB;
|
||||
if(offset.Length > 0)
|
||||
ColliderAABB.Offset(offset.X, offset.Y);
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace ClientServices.Collision
|
||||
};
|
||||
|
||||
//Get the buckets that correspond to the collider's points.
|
||||
List<CollidableBucket> buckets = points.Select(GetBucket).Distinct().ToList();
|
||||
List<CollidableBucket> buckets = points.Select(GetBucket).Distinct().ToList(); // PERF: ToList() is absolutely unnecessary here.
|
||||
|
||||
//Get all of the points
|
||||
var cpoints = new List<CollidablePoint>();
|
||||
@@ -123,7 +123,7 @@ namespace ClientServices.Collision
|
||||
}
|
||||
|
||||
//Expand points to distinct AABBs
|
||||
List<CollidableAABB> aabBs = (cpoints.Select(cp => cp.ParentAABB)).Distinct().ToList();
|
||||
List<CollidableAABB> aabBs = (cpoints.Select(cp => cp.ParentAABB)).Distinct().ToList(); // PERF: ToList() and Distinct() are absolutely unnecessary here.
|
||||
|
||||
//try all of the AABBs against the target rect.
|
||||
bool collided = false;
|
||||
|
||||
@@ -113,6 +113,7 @@ namespace ClientServices.State.States
|
||||
private RenderImage shadowIntermediate;
|
||||
private ShadowMapResolver shadowMapResolver;
|
||||
private bool debugWallOccluders = false;
|
||||
private bool debugHitboxes = false;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -627,6 +628,40 @@ namespace ClientServices.State.States
|
||||
{
|
||||
if(debugWallOccluders)
|
||||
_occluderDebugTarget.Blit(0,0,_occluderDebugTarget.Width, _occluderDebugTarget.Height, Color.White, BlitterSizeMode.Crop);
|
||||
|
||||
if (debugHitboxes) {
|
||||
var corner = ClientWindowData.Singleton.ScreenOrigin;
|
||||
|
||||
var colliders =
|
||||
_entityManager.ComponentManager.GetComponents(ComponentFamily.Collider)
|
||||
.OfType<ColliderComponent>()
|
||||
.Select(c => new { Color = c.DebugColor, AABB = c.WorldAABB })
|
||||
.Where(c => !c.AABB.IsEmpty && c.AABB.IntersectsWith(ClientWindowData.Singleton.ViewPort));
|
||||
|
||||
var collidables =
|
||||
_entityManager.ComponentManager.GetComponents(ComponentFamily.Collidable)
|
||||
.OfType<CollidableComponent>()
|
||||
.Select(c => new { Color = c.DebugColor, AABB = c.AABB })
|
||||
.Where(c => !c.AABB.IsEmpty && c.AABB.IntersectsWith(ClientWindowData.Singleton.ViewPort));
|
||||
|
||||
var destAbo = _baseTarget.DestinationBlend;
|
||||
var srcAbo = _baseTarget.SourceBlend;
|
||||
_baseTarget.DestinationBlend = AlphaBlendOperation.InverseSourceAlpha;
|
||||
_baseTarget.SourceBlend = AlphaBlendOperation.SourceAlpha;
|
||||
|
||||
foreach (var hitbox in colliders.Concat(collidables)) {
|
||||
_baseTarget.FilledRectangle(
|
||||
hitbox.AABB.Left - corner.X, hitbox.AABB.Top - corner.Y,
|
||||
hitbox.AABB.Width, hitbox.AABB.Height, Color.FromArgb(64, hitbox.Color));
|
||||
_baseTarget.Rectangle(
|
||||
hitbox.AABB.Left - corner.X, hitbox.AABB.Top - corner.Y,
|
||||
hitbox.AABB.Width, hitbox.AABB.Height, Color.FromArgb(128, hitbox.Color));
|
||||
}
|
||||
|
||||
_baseTarget.DestinationBlend = destAbo;
|
||||
_baseTarget.SourceBlend = srcAbo;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void FormResize()
|
||||
@@ -662,6 +697,10 @@ namespace ClientServices.State.States
|
||||
{
|
||||
ToggleOccluderDebug();
|
||||
}
|
||||
if (e.Key == KeyboardKeys.F4)
|
||||
{
|
||||
debugHitboxes = !debugHitboxes;
|
||||
}
|
||||
if (e.Key == KeyboardKeys.F5)
|
||||
{
|
||||
PlayerManager.SendVerb("save", 0);
|
||||
|
||||
@@ -11,19 +11,17 @@ namespace SGO
|
||||
{
|
||||
public class HitboxComponent : Component
|
||||
{
|
||||
public SizeF Size;
|
||||
public PointF Offset;
|
||||
public RectangleF AABB { get; set; }
|
||||
|
||||
public HitboxComponent()
|
||||
{
|
||||
Family = ComponentFamily.Hitbox;
|
||||
Size = new SizeF();
|
||||
Offset = new PointF();
|
||||
AABB = new RectangleF();
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new HitboxComponentState(Size, Offset);
|
||||
return new HitboxComponentState(AABB);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,16 +34,20 @@ namespace SGO
|
||||
switch (parameter.MemberName)
|
||||
{
|
||||
case "SizeX":
|
||||
Size.Width = parameter.GetValue<float>();
|
||||
var width = parameter.GetValue<float>();
|
||||
AABB = new RectangleF(AABB.Left + (AABB.Width - width) / 2f, AABB.Top, width, AABB.Height);
|
||||
break;
|
||||
case "SizeY":
|
||||
Size.Height = parameter.GetValue<float>();
|
||||
var height = parameter.GetValue<float>();
|
||||
AABB = new RectangleF(AABB.Left, AABB.Top + (AABB.Height - height) / 2f, AABB.Width, height);
|
||||
break;
|
||||
case "OffsetX":
|
||||
Offset.X = parameter.GetValue<float>();
|
||||
var x = parameter.GetValue<float>();
|
||||
AABB = new RectangleF(x - AABB.Width / 2f, AABB.Top, AABB.Width, AABB.Height);
|
||||
break;
|
||||
case "OffsetY":
|
||||
Offset.Y = parameter.GetValue<float>();
|
||||
var y = parameter.GetValue<float>();
|
||||
AABB = new RectangleF(AABB.Left, y - AABB.Height / 2f, AABB.Width, AABB.Height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,11 @@
|
||||
<Component name="PlayerActionComp"></Component>
|
||||
<Component name="EntityStatsComp"></Component>
|
||||
<Component name="ParticleSystemComponent"></Component>
|
||||
<Component name="HitboxComponent"></Component>
|
||||
<Component name="HitboxComponent">
|
||||
<Parameters>
|
||||
<Parameter name="DebugColor" type="string" value="Red" />
|
||||
</Parameters>
|
||||
</Component>
|
||||
</Components>
|
||||
</EntityTemplate>
|
||||
</EntityTemplates>
|
||||
@@ -19,7 +19,12 @@
|
||||
</Parameters>
|
||||
</Component>
|
||||
<Component name="ClickableComponent"></Component>
|
||||
<Component name="CollidableComponent"></Component>
|
||||
<Component name="HitboxComponent"></Component>
|
||||
<Component name="CollidableComponent">
|
||||
<Parameters>
|
||||
<Parameter name="DebugColor" type="string" value="Blue" />
|
||||
</Parameters>
|
||||
</Component>
|
||||
</Components>
|
||||
</EntityTemplate>
|
||||
</EntityTemplates>
|
||||
@@ -14,14 +14,12 @@ namespace SS13_Shared.GO.Component.Hitbox
|
||||
[Serializable]
|
||||
public class HitboxComponentState : ComponentState
|
||||
{
|
||||
public SizeF Size;
|
||||
public PointF Offset;
|
||||
public RectangleF AABB;
|
||||
|
||||
public HitboxComponentState(SizeF size, PointF offset)
|
||||
public HitboxComponentState(RectangleF aabb)
|
||||
:base(ComponentFamily.Hitbox)
|
||||
{
|
||||
Size = size;
|
||||
Offset = offset;
|
||||
AABB = aabb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
<Component name="BasicInteractableComponent"></Component>
|
||||
<Component name="BasicMoverComponent"></Component>
|
||||
<Component name="WorktopComponent"></Component>
|
||||
<Component name="HitboxComponent">
|
||||
<Parameters>
|
||||
<Parameter name="SizeX" type="float" value="64" />
|
||||
<Parameter name="SizeY" type="float" value="47" />
|
||||
<Parameter name="OffsetY" type="float" value="8" />
|
||||
</Parameters>
|
||||
</Component>
|
||||
<Component name="CollidableComponent"></Component>
|
||||
</Components>
|
||||
</EntityTemplate>
|
||||
|
||||
Reference in New Issue
Block a user