loodakrawa / SpriterDotNet

A pure C# Spriter implementation
zlib License
220 stars 75 forks source link

Regarding the creation of bounding boxes, and collision boxes. #111

Open CharlesLindberghMcGill opened 4 years ago

CharlesLindberghMcGill commented 4 years ago

The following question feels insufficiently answered as well as insufficiently documented: https://github.com/loodakrawa/SpriterDotNet/issues/80

What do we pass to the width and height as arguments provided we're talking about collision boxes?

I don't know how to get the width and height information for the object box. In Spriter Pro itself it also only provides a scale. What is the size the boxes are scaled from? Maybe I am not well informed enough about how Spriter Pro works and that's why I am so confused.

image

I do know how to get this information for textures. That is simply the actual size of the image.

The following code is how I managed to get the information from the sprites that are being drawn - as you can see I can just multiply the scale of the animation to the size of the textures - however, I don't know where to find the 'size' of the box.

 foreach (var info in Animator.DrawInfos)
            {
                var scale = info.Scale;
                var pos = new Vector2(info.Position.X, info.Position.Y);
                var size = new Vector2(info.Drawable.Width, info.Drawable.Height) * scale;

                var center = Vector2.Zero;// size * 0.5f;

                HitBoxes[info] = new[]
                {
                    (pos - center).RotateAround(pos, info.Rotation),
                    (pos + new Vector2(size.X, 0) - center).RotateAround(pos, info.Rotation),
                    (pos + new Vector2(size.X, size.Y) - center).RotateAround(pos, info.Rotation),
                    (pos + new Vector2(0, size.Y) - center).RotateAround(pos, info.Rotation)
                };
            }
CharlesLindberghMcGill commented 4 years ago

Okay so I've just looked through the .scml information and it does provide an actual width and height there. How could we access this information from this library? I would then only need to apply the animator.scale to it and use the GetBoundingBox function.

<obj_info name="bathitbox" type="box" w="145.833" h="483.333" pivot_x="0" pivot_y="0"/>

image

If I could get this information the "w" and "h" as well as the "name" I could do all of this using the provided GetBoundingBox function, as opposed to the one I made myself.

Something like Survivor.Animator.FrameData.BoxData.Get("bathitbox"); would be absolutely amazing to have.

CharlesLindberghMcGill commented 4 years ago

I'm back. I've done a little bit of digging and I've managed to retrieve the object information that I was wondering about above. The only issue is that in this information the 'id' is set to 0. So I am not sure how to (cross reference, is that the word?) the object data retrieved by name, with the information within the frame data.

            var objectData = Animator.Entity.ObjectInfos.First(d => d.Name.Equals("bathitbox"));

            // objectData.Id is 0 here, so the following line does not work.
            var objectFrameData = Animator.FrameData.BoxData[objectData.Id];
            var objectScaled = new Vector2(objectData.Width, objectData.Height) * Animator.Scale;
            var box = Animator.GetBoundingBox(objectFrameData, objectScaled.X, objectScaled.Y);

            var path = new[]
            {
                box.Point1,
                box.Point2,
                box.Point3,
                box.Point4
            };

         engineBatch.DrawShape(path, Color.Red);
CharlesLindberghMcGill commented 4 years ago

Okay, so currently the hacky workaround is to add a name field to "SpriterObject"


    public class SpriterObject : SpriterSpatial
    {
        [XmlAttribute("animation")]
        public int AnimationId;

        [XmlAttribute("entity")]
        public int EntityId;

        [XmlAttribute("folder")]
        public int FolderId;

        [XmlAttribute("file")]
        public int FileId;

        [XmlAttribute("pivot_x")]
        public float PivotX;

        [XmlAttribute("pivot_y")]
        public float PivotY;

        [XmlAttribute("t")]
        public float T;

        **public string Name;**

        public SpriterObject()
        {
            PivotX = float.NaN;
            PivotY = float.NaN;
        }
    }

Then within the "AddSpatialData" function set the name.

           case SpriterObjectType.Box:
                    FrameData.BoxData[timeline.ObjectId] = info;
                    **FrameData.BoxData[timeline.ObjectId].Name = timeline.Name;**
                    break;

An example code would then be:

            if(Animator.FrameData.BoxData.Any(d => d.Value.Name.Equals("bathitbox")))
            {
                var boxData = Animator.Entity.ObjectInfos.FirstOrDefault(d => d.Name.Equals("bathitbox"));
                var frameData = Animator.FrameData.BoxData.FirstOrDefault(d => d.Value.Name.Equals("bathitbox"));

                var collision = Animator.GetBoundingBox(
                    frameData.Value,
                    boxData.Width,
                    boxData.Height
                );

                engineBatch.DrawShape(
                    new []{collision.Point1, collision.Point2, collision.Point3, collision.Point4}, Color.Red, 1f);
            }

I don't understand why there's no other way to cross reference the frame data with the object data. The ids are always set to zero for some reason.

CharlesLindberghMcGill commented 4 years ago

Updated example code to map all boxes by their name.

FrameDataCalculator.cs

        protected virtual void AddSpatialData(SpriterObject info, SpriterTimeline timeline, Spriter spriter, float deltaTime)
        {
            info.TimelineName = timeline.Name;

Entity.cs

        public Dictionary<string, Box> ObjectBoxMap { get; } = new Dictionary<string, Box>();

        protected virtual void OnAnimationChanged(string animation)
        {
            ObjectInfoMap.Clear();
            ObjectBoxMap.Clear();

            foreach (var obj in Animator.CurrentAnimation.Entity.ObjectInfos)
                ObjectInfoMap[obj.Name] = obj;
        }
            var deltaTime = gameTime.ElapsedGameTime.Ticks / (float)TimeSpan.TicksPerMillisecond;
            Animator.Update(deltaTime);

            foreach (var info in Animator.FrameData.BoxData)
            {
                ObjectBoxMap[info.Value.TimelineName] = Animator.GetBoundingBox(
                    info.Value,
                    ObjectInfoMap[info.Value.TimelineName].Width,
                    ObjectInfoMap[info.Value.TimelineName].Height
                );
            }