Search Unity

2D Hitboxes/Hurtboxes Based on Frame

Discussion in '2D' started by boxhallowed, Mar 24, 2016.

Thread Status:
Not open for further replies.
  1. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Well, I've done some research on this, but none of the solutions were satisfactory.

    We have a complex multi-character 2D fighting game, we need a way to store and generate hurtboxes/hitboxes for each frame of animation. We've ran into multiple barriers, including the inability to GET the actual frame of animation the animator is currently on. If I could accurately get the frame of animation each time, I could just manually create an array points (boxes) for each frame. Even the most accurate time based division to get the current frame is always a frame off eventually, this is not a solution.

    Creating sub-box objects is also not an option, as some frames may have many, or few hit/hurt boxes.

    Keeping this in mind, for a 2D game, what is the most efficient and accurate way to generate hand drawn hit/hurt boxes for each and every frame of animation across multiple animations and characters?

    EDIT: GO HERE FOR FREE ASSET THAT DOES THIS
    https://forum.unity.com/threads/released-free-flat-fighter-2d-alpha.519168/
     
    Last edited: Mar 6, 2018
  2. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Do the sprite animations yourself, and use the same system to swap out colliders.
     
  3. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    We did that, but it left us without the hugely powerful animation system. I was really hoping to have that at our disposal. Is there really no other way?
     
  4. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    If you mean the states and transitions, you could have your system handle sprite/collider changes only, and then possibly interface with the state system via scripts (adding scripts to the state nodes, and using onenter/onexit functions to trigger changes in your system).

    Really you just need a frames-per-second and an array of sprites & colliders, right? Any other fancy animation with curves and such can still be done with the animator. There's also animation events that you can use to trigger changes if you prefer.

    I suppose you COULD try using those animator state scripts to drive ONLY your collider animations, but if you want to guarantee perfectly synced sprite-to-collider animations, I would keep them using the same system.

    I'm just spitballing ideas here, I haven't personally implemented a system like this, but those are definitely some things I would try first.
     
  5. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Yeah, it is sounding like you are probably right, and I just need to go back to my old class array of sprites and sprite data. I really, REALLY was trying to avoid that since I can't use all the fancy animator features like exit times and such. Something that baffles the hell out of me, you can clearly see in the animator that the frame number is there. The system already has built in info about which frame the animation is on.

    Unity Devs: Why don't you allow us to access this via script?
     
  6. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    Yeah, this approach is definitely more work on your part, and very much re-inventing the wheel. You could in theory implement your own exit time as well.

    A kind of messy solution (tedious to change), but potentially more straightforward for you would be to do something like this:
    http://answers.unity3d.com/questions/372252/how-can-i-do-frame-by-frame-hitbox-control-for-a-2.html

    Where you have a script dedicated to swapping colliders, and then each frame in your animation fire an anim event call to "change hit box" etc.

    I personally don't trust those events to be 100% reliable, but perhaps in practice they are.
     
  7. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Yeah I am going to have to go the custom route, each character has around 108-140 animations with an average of 20 frames each. Some have up to 100 frames for a single attack.
     
  8. Massiano

    Massiano

    Joined:
    Dec 16, 2013
    Posts:
    4
    Question needs more context in my opinion.

    If you cant resolve the frame the animation is in, accurately, no solution suggestions for how to store the hitboxes really helps right?
    what was your best attempt at that? if feel this is of general interest and should be tried to be solved first.

    Do you have an existing system to edit hurt/hitboxes? initially i would probably do this (and have done but with very simple hitboxes)
    make hitBox Subobjects( proper game Objects ) with polygonColliders and enable these colliders as necessary.
    the "enabled" boolean can be animated itself (or in code if the array solution is successful) and should thus be easy to sync with your animation frames, if you do the in animations as well.
     
  9. Massiano

    Massiano

    Joined:
    Dec 16, 2013
    Posts:
    4
    To resolve the frames can you identify why something along the lines of

    frame = Mathf.Floor( durationInFrames * animationState.normalizedTime);

    doesnt work ? does it over- undershoot and why? does adding +0.5f inside the floor help ? what was the best solution you tried?
    You could animate a script variable in an animation itself? use the curves (next to dope sheets at the bottom left of the animation tab ) to get a step-function
     
  10. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Yes, I have developed a tool so I can generate hit/hurt boxes manually. It allows me to easily generate an array of boxes visually, I then copy each of those over by hand into the class that has an array of hit and hurt boxes. I then swap out the class for each frame so that the information is generated per frame.

    Code (CSharp):
    1. frame = Mathf.Floor( durationInFrames * animationState.normalizedTime);
    This is always the cited solution to this problem, and it does not work. This is not nearly accurate enough for a fighting game. It's always dropping or picking up a frame. Now you might say, "What's so bad about that?" Let's say you have a frame dependent attack, and depending on the charge level the attack happens at a different time in the animation. With a system like Game Maker, you could just get the frame, add, and find the appropriate frame to initiate. Not so with this. If the frame is off the animation will loop until it finally "hits".

    This is not an accurate way of getting the frame, nor it is a solution. Since the animator MUST know what frame it is on in the array of frames, WHY DON'T THEY EXPOSE IT TO THE CODE?

    It is something that has driven me insane with Unreal, Torque, and a variety of other engines that purport to have 2D support. If we can not programatically GET and SET the frame, then the engine is nearly useless for any time of frame dependent game.

    So the best solution here is going to be creating an array of a class, and that array would be called animation. That class would have:

    sprite (for animation)
    class hitbox array (hitbox class would have a top-left and bottom-right vectors for making a box, additionally it would have a damage modifier float)
    class hurtbox array (same as hitbox)

    This is the basic class, you would then create an array for each animation of this class. So if you had attack1, walk, run, ect, you would need one for each. Then you would have a function that divides out by your framerate and switches to the next one in the series.

    I am going to test this out. If it works, I will post the code so other people don't have to fight with this.
     
  11. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Alright, here is what I worked out. It animates pretty smoothly with around 100 animations loaded for 4 characters. You can have as many hit and hurt boxes as you would like. You load each animation as a frame. I provided an example picture.

    Could one of you be kind enough to include another function that would draw a green box for the hurt and a red box for the hit box that works with both perspective and ortho cameras? I am using a perspective camera to get the advantage of easy parallax scrolling and depth effects. GUI will not work for this, I am also trying to avoid canvases as generating this many objects could be detrimental.

    I've included an example function called DrawBox, and that would be the perfect spot to do so.

    With this script you can have infinite hit and hurtboxes, albeit square.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class FrameAnimate : MonoBehaviour {
    6.  
    7.     public int animationFramesPerSecond = 15;
    8.     public Animations[] animationList;
    9.  
    10.     [HideInInspector]
    11.     public Animations currentAnimation;
    12.     int currentAnimationFrame = 0;
    13.     float frameCurrenTime = 0;
    14.     float frameEndTime = 0;
    15.  
    16.     //useful for figuring out whether to do a check or not
    17.     [HideInInspector]
    18.     public bool hasHurtBoxThisFrame = true;
    19.     [HideInInspector]
    20.     public bool hasHitBoxThisFrame = true;
    21.  
    22.     [System.Serializable]
    23.     public class Animations
    24.     {
    25.         public string animationName;
    26.         public FrameInfo[] frames;
    27.     }
    28.     [System.Serializable]
    29.     public class FrameInfo
    30.     {
    31.         public Sprite frameSprite;
    32.         public HitBox[] HitBoxes;
    33.         public HurtBox[] HurtBoxes;
    34.     }
    35.     [System.Serializable]
    36.     public class HitBox {
    37.         public Rect hitBoxDimensions;
    38.     }
    39.     [System.Serializable]
    40.     public class HurtBox
    41.     {
    42.         public Rect hurtBoxDimensions;
    43.     }
    44.  
    45.     // Use this for initialization
    46.     void Start () {
    47.         SetAnimation("Stand");
    48.     }
    49.    
    50.     // Update is called once per frame
    51.     void Update () {
    52.         AnimateFrames();
    53.         DrawBoxes();
    54.     }
    55.  
    56.     void AnimateFrames()
    57.     {
    58.         frameCurrenTime = Time.time * 1000;
    59.  
    60.         if(frameCurrenTime >= frameEndTime)
    61.         {
    62.             currentAnimationFrame++;
    63.             frameEndTime = frameCurrenTime + Mathf.CeilToInt((1f / animationFramesPerSecond) * 1000);
    64.         }
    65.  
    66.        if(currentAnimationFrame > currentAnimation.frames.Length - 1)
    67.         {
    68.             currentAnimationFrame = 0;
    69.         }
    70.  
    71.         this.GetComponent<SpriteRenderer>().sprite = currentAnimation.frames[currentAnimationFrame].frameSprite;
    72.     }
    73.  
    74.     public bool SetAnimation(string animationName, int startFrame = 0)
    75.     {
    76.         //Is our current animation the same as the one we are setting?
    77.         if (currentAnimation.animationName.ToLower() == animationName.ToLower())
    78.             return true;
    79.  
    80.         for(int i = 0; i < animationList.Length; i++)
    81.         {
    82.             if(animationList[i].animationName.ToLower() == animationName.ToLower())
    83.             {
    84.                 if(startFrame > animationList[i].frames.Length - 1)
    85.                     startFrame = animationList[i].frames.Length - 1;
    86.                 if(startFrame < 0)
    87.                 {
    88.                     startFrame = 0;
    89.                 }
    90.  
    91.                 currentAnimationFrame = startFrame;
    92.                 currentAnimation = animationList[i];
    93.  
    94.                 return true;
    95.             }
    96.         }
    97.  
    98.         Debug.Log("Error, animation not registered.");
    99.         return false;
    100.     }
    101.  
    102.     void DrawBoxes()
    103.     {
    104.         Vector2 mePosition = new Vector2(this.transform.position.x, this.transform.position.y);
    105.  
    106.         if (currentAnimation.frames[currentAnimationFrame].HurtBoxes.Length != 0)
    107.         {
    108.             //hurtboxes
    109.             for (int i = 0; i < currentAnimation.frames[currentAnimationFrame].HurtBoxes.Length; i++)
    110.             {
    111.                 Rect hurtBoxPosition = new Rect(currentAnimation.frames[currentAnimationFrame].HurtBoxes[i].hurtBoxDimensions.position + mePosition, currentAnimation.frames[currentAnimationFrame].HurtBoxes[i].hurtBoxDimensions.size);
    112.             }
    113.         } else
    114.         {
    115.             hasHurtBoxThisFrame = false;
    116.         }
    117.  
    118.         if (currentAnimation.frames[currentAnimationFrame].HitBoxes.Length != 0)
    119.         {
    120.             //hitboxes
    121.             for (int i = 0; i < currentAnimation.frames[currentAnimationFrame].HitBoxes.Length; i++)
    122.             {
    123.                 Rect hitBoxPosition = new Rect(currentAnimation.frames[currentAnimationFrame].HitBoxes[i].hitBoxDimensions.position + mePosition, currentAnimation.frames[currentAnimationFrame].HitBoxes[i].hitBoxDimensions.size);
    124.            
    125.             }
    126.         }
    127.         else
    128.         {
    129.             hasHitBoxThisFrame = false;
    130.         }
    131.     }
    132.  
    133.  
    134.     //end
    135. }
    136.  
    Update: Added a function set to the animation by name.
     

    Attached Files:

    Last edited: Mar 25, 2016
  12. LiterallyJeff

    LiterallyJeff

    Joined:
    Jan 21, 2015
    Posts:
    2,807
    I edited your class to create two reusable sprites to represent the hit/hurt boxes each frame.

    I have absolutely no idea if this will work for you. Without using GUI or Canvas, your options are pretty limited.

    Honestly a reusable prefab box would work just as well.

    Code (CSharp):
    1. public class FrameAnimate : MonoBehaviour
    2. {
    3.     public int animationFramesPerSecond = 15;
    4.     public Animations[] animationList;
    5.  
    6.     public Color hitBoxColor = Color.green; // customize in inspector with lower alpha, etc.
    7.     public Color hurtBoxColor = Color.red;
    8.     public int pixelsPerUnit; // use whatever your other sprites use
    9.     SpriteRenderer hurtBox;
    10.     SpriteRenderer hitBox;
    11.  
    12.     [HideInInspector]
    13.     public Animations currentAnimation;
    14.     int currentAnimationFrame = 0;
    15.     float frameCurrenTime = 0;
    16.     float frameEndTime = 0;
    17.     SpriteRenderer frameRenderer;
    18.  
    19.     //useful for figuring out whether to do a check or not
    20.     [HideInInspector]
    21.     public bool hasHurtBoxThisFrame = true;
    22.     [HideInInspector]
    23.     public bool hasHitBoxThisFrame = true;
    24.  
    25.     [System.Serializable]
    26.     public class Animations
    27.     {
    28.         public string animationName;
    29.         public FrameInfo[] frames;
    30.     }
    31.     [System.Serializable]
    32.     public class FrameInfo
    33.     {
    34.         public Sprite frameSprite;
    35.         public HitBox[] HitBoxes;
    36.         public HurtBox[] HurtBoxes;
    37.     }
    38.     [System.Serializable]
    39.     public class HitBox
    40.     {
    41.         public Rect hitBoxDimensions;
    42.     }
    43.     [System.Serializable]
    44.     public class HurtBox
    45.     {
    46.         public Rect hurtBoxDimensions;
    47.     }
    48.  
    49.     private void Awake()
    50.     {
    51.         frameRenderer = GetComponent<SpriteRenderer>();
    52.  
    53.         hurtBox = new GameObject("hurtBox", typeof(SpriteRenderer)).GetComponent<SpriteRenderer>();
    54.         hurtBox.sprite = CreateBoxSprite();
    55.         hurtBox.color = Color.clear;
    56.  
    57.         hitBox = new GameObject("hitBox", typeof(SpriteRenderer)).GetComponent<SpriteRenderer>();
    58.         hitBox.sprite = CreateBoxSprite();
    59.         hitBox.color = Color.clear;
    60.     }
    61.  
    62.     private Sprite CreateBoxSprite()
    63.     {
    64.         // create 1x1 white pixel
    65.         Texture2D tex = new Texture2D(1, 1);
    66.         tex.SetPixel(0, 0, Color.white);
    67.         tex.Apply();
    68.  
    69.         // create a new sprite with the texture applied
    70.         Sprite sprite = new Sprite();
    71.         sprite = Sprite.Create(tex, new Rect(0, 0, 1, 1), new Vector2(0.5f, 0.5f), this.pixelsPerUnit);
    72.  
    73.         return sprite;
    74.     }
    75.  
    76.     // Use this for initialization
    77.     void Start()
    78.     {
    79.         SetAnimation("Stand");
    80.     }
    81.  
    82.     // Update is called once per frame
    83.     void Update()
    84.     {
    85.         AnimateFrames();
    86.         DrawBoxes();
    87.     }
    88.  
    89.     void AnimateFrames()
    90.     {
    91.         frameCurrenTime = Time.time * 1000;
    92.  
    93.         if (frameCurrenTime >= frameEndTime)
    94.         {
    95.             currentAnimationFrame++;
    96.             frameEndTime = frameCurrenTime + Mathf.CeilToInt((1f / animationFramesPerSecond) * 1000);
    97.         }
    98.  
    99.         if (currentAnimationFrame > currentAnimation.frames.Length - 1)
    100.         {
    101.             currentAnimationFrame = 0;
    102.         }
    103.  
    104.         frameRenderer.sprite = currentAnimation.frames[currentAnimationFrame].frameSprite;
    105.     }
    106.  
    107.     public bool SetAnimation(string animationName, int startFrame = 0)
    108.     {
    109.         //Is our current animation the same as the one we are setting?
    110.         if (currentAnimation.animationName.ToLower() == animationName.ToLower())
    111.             return true;
    112.  
    113.         for (int i = 0; i < animationList.Length; i++)
    114.         {
    115.             if (animationList[i].animationName.ToLower() == animationName.ToLower())
    116.             {
    117.                 if (startFrame > animationList[i].frames.Length - 1)
    118.                     startFrame = animationList[i].frames.Length - 1;
    119.                 if (startFrame < 0)
    120.                 {
    121.                     startFrame = 0;
    122.                 }
    123.  
    124.                 currentAnimationFrame = startFrame;
    125.                 currentAnimation = animationList[i];
    126.  
    127.                 return true;
    128.             }
    129.         }
    130.  
    131.         Debug.Log("Error, animation not registered.");
    132.         return false;
    133.     }
    134.  
    135.     void DrawBoxes()
    136.     {
    137.         Vector2 mePosition = transform.position;
    138.         FrameInfo currentFrame = currentAnimation.frames[currentAnimationFrame];
    139.         HurtBox[] hurtBoxes = currentFrame.HurtBoxes;
    140.         HitBox[] hitBoxes = currentFrame.HitBoxes;
    141.  
    142.         if (hurtBoxes.Length != 0)
    143.         {
    144.             //hurtboxes
    145.             for (int i = 0; i < hurtBoxes.Length; i++)
    146.             {
    147.                 Rect hurtBoxRect = new Rect(hurtBoxes[i].hurtBoxDimensions.position + mePosition, hurtBoxes[i].hurtBoxDimensions.size);
    148.  
    149.                 this.hurtBox.transform.position = new Vector2(hurtBoxRect.x, hurtBoxRect.y);
    150.                 this.hurtBox.transform.localScale = new Vector2(hurtBoxRect.width, hurtBoxRect.height);
    151.                 this.hurtBox.color = this.hurtBoxColor;
    152.             }
    153.         }
    154.         else
    155.         {
    156.             hasHurtBoxThisFrame = false;
    157.             this.hurtBox.color = Color.clear;
    158.         }
    159.  
    160.         if (hitBoxes.Length != 0)
    161.         {
    162.             //hitboxes
    163.             for (int i = 0; i < hitBoxes.Length; i++)
    164.             {
    165.                 Rect hitBoxRect = new Rect(hitBoxes[i].hitBoxDimensions.position + mePosition, hitBoxes[i].hitBoxDimensions.size);
    166.  
    167.                 this.hitBox.transform.position = new Vector2(hitBoxRect.x, hitBoxRect.y);
    168.                 this.hitBox.transform.localScale = new Vector2(hitBoxRect.width, hitBoxRect.height);
    169.                 this.hitBox.color = this.hitBoxColor;
    170.             }
    171.         }
    172.         else
    173.         {
    174.             hasHitBoxThisFrame = false;
    175.             this.hitBox.color = Color.clear;
    176.         }
    177.     }
    178.  
    179.  
    180.     //end
    181. }
     
    Last edited: Mar 26, 2016
  13. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    This didn't work, it only generates the last of the hit and hurt boxes in the array. I instead made two objects, called hit and hurtboxobject, then I instantiate them in the loop and store them in an arraylist.

    When the next new frame comes along, I wipe the array and start over. Tada, performance stable object based hit and hurt boxes!

    https://www.facebook.com/supersmash...850452852302/1737681106469234/?type=3&theater
     
    Last edited: Mar 26, 2016
    NioFox and LiterallyJeff like this.
  14. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    I will probably open source this when it's done, I think more people need access to something like this. I do have a new question though. If you look at the provided screenshot you can see we have a problem. It is messy as all hell when working with nested classes in an array.

    I've read some documentation on extending the Unity editor, but I need more information. Specifically, how can I:

    - Re-organize this so it's easier to work with.
    - Be able to see the boxes as I add and adjust them per frame in the Unity editor.

    Is this even possible?

    If you all need the project itself to poke around with, I can do that in a bit. I need to swap out our sprites for Kenny's, I can't distribute the team's work.
     

    Attached Files:

  15. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    The thinking in this thread is way overkill and ignores Unity's strengths.

    Hitboxes in Unity's 2D system are the most trivial things imaginable. Merely animate the root of the sprite in dope sheet. This means you get to animate child transforms and scale them, for hitboxes. You can also animate the values of a script on the sprite such as toggling when the hitbox should register the collision.

    All per-frame. Very clean and easy to do, and within a single Unity Animation Clip.

    This has trivial authoring and trivial code needed. I would recommend that native approach (I've tested it and it works fine).
     
  16. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Would you care to provide an example. There are allot of people having problems with exactly what you suggested.

    HitBox2D can't be trusted, they do not register multiple collisions accurately. This is a fighting game where up to four people can be on the stage at any given time. Multiple strikes with the same hitbox and on the same hurtbox is going to be a thing. We are working with around 108 animations per character, each animation has it's own set of PNG frames and can be rapidly deformed between frames. It is not a sprite sheet. There can also be one or more hit/hurt boxes per frame.

    This is also ignoring the need for being able to accurately set the frame of animation. As an example, we have a secondary attack that needs to start in 3 different places based upon the last attack. We can't simply divide frametime 0-1 to find the frame, because it isn't accurate enough. We need it to be exactly on that frame. We also need to get the EXACT frame the animation is on every time. Shielding is a perfect example of needing very accurate frametime.

    I even went so far as to put animation events on every single frame to feed back the frame information. After so many animations it started skipping. Even if this worked it did not give us the ability to set frames anyhow, which is core to the game.

    I would be elated if you had a solution that met the following criteria without advanced hackery, and so would many others. I've spent a few months now rolling over thread after thread of people having the exact same problem, with very little in the way of a solution that fit a Smash style game.

    - Multiple hit/hurt boxes per frame, or none at all sometimes.
    - Accurately getting and setting the animation frame, every time. Dividing out animation playtime does not work, this has been confirmed not only by every single programmer on our team with multiple engines, but has been echoed by the forum post on the subject.
    - Not messing around with tons of collision layers.

    After months of messing around, this is the only solution that has worked, and worked very well. We don't need to toggle hitboxes in this case because they just simply do not exist on the frames where we don't put them.

    I am not alone when I say I would like to see a solution that accomplishes this without all of the hackery involved here.

    Link to video of it working.
    https://drive.google.com/file/d/0B653I8dXeSNFaE1hTlNIeTlyc0k/view?usp=sharing
     
  17. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    It's simple, you use OnTriggerEnter for the child hitboxes. They detect colliders all the time, there is no on/off hackery. Instead you filter if it should be processed* by animating the state of a boolean on a script either on the hitbox itself, or on the parent sprite.

    This way, you don't encounter issues turning things on or off, and if you chose to, you could simply do a rotated box check in maths using the same child animated parts. I don't see where the problem is, regardless of multiple 1-1000 enemies or whatever. It seems like a logic problem.

    Also relying on OnTrigger messaging is at the mercy of your FixedUpdate rate - but I assume you've factored that in?


    * This is dreadfully important for a large number of your problems like knowing when the hitbox should be active or not, and timing, and of course multiple moves. You don't want to enable/disable, you want it enabled all the time, devouring all and anything it encounters. But when you do stuff with it is decided by the bool on the script.

    Will eliminate issues Box2D side. If you're still having issues, roll a rotated rect collision class and do the same thing with it.
     
  18. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    This has multiple problems, as the number of hit/hurt boxes can wildy fluctuate. For example, one attack has 6 hitboxes and 5 hurtboxes. Others have one 1. It also doesn't solve our issue of setting and getting exact frames. I know it's allot to ask, but if you could provide example code emulating what I am saying, and the video above, that would help clear it up. I feel like we are talking about two different games.
     
  19. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    It seems you might not realise that a single animation clip can animate a hierarchy of stuff, which includes scaling child hitboxes and animating script properties such as a boolean on an individual hitbox. You simply have say 8 hitboxes and most of them are dormant, only active during the precise dopesheet frames. These are scaled, rotated etc *when they are needed for that frame* within the clip. Not before. You don't use inspectors or complex arrays or any of that. When a given hitbox is activated or not, is determined within the clip, including the state of when it's allowed to send information to you main class.

    I don't have time to go into our repo, pull out our fighting game and deliver the code, so I'm hoping it's enough for you to investigate.

    I'm basing my reference off various street fighter games over the years, if that's any indication of where I am coming from. (SF4's boxes don't even rotate but ours do):

    Link
     
  20. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    While it sounds interesting, I think I am going to stay the course until I see a working solution. I feel if I flesh this out and give it editor access to easily setting up the hitbox array, this would be a guaranteed way to get absolute frame control. This isn't just about moving hitboxes around, it has to do with 100% accurate set and get frame control. I'm probably just not understanding you, but if you decide to publish a solution, there are many people searching for an answer that would be quite thankful.

    I do appreciate the extra thinking though, and it will be something I definitely tinker around with later.

    In the mean time, does anyone have an answer to my question about editor control for making these class arrays easier to manage? Should I start a separate thread on this?
     
  21. NioFox

    NioFox

    Joined:
    Dec 28, 2012
    Posts:
    65
  22. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    NioFox likes this.
  23. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Alright, so far our new system is working like a dream. If everything works out and we get this alpha done, I may go ahead and make a tutorial on creating a simple multi-character 2D Smash style game in Unity.

    https://www.facebook.com/supersmashponiesofficial

    I just posted a video of the hitboxer in action, and it is working really well. It also doesn't use a ton of resources or take all that long to setup.
     
    renman3000 likes this.
  24. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    Hey there, I'm making a stick figure beat-em-up game and my team is also having similar issues, are you willing to share your updated scripts?

    We're also beginners with unity, could you briefly explain what's needed in addition to the script? Can this work with polygon colliders instead of box colliders for greater hit accuracy?
     
    Last edited: Mar 28, 2016
  25. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Well, as explained above we are not using colliders really, I mean we kind of are, but not exactly. If you are new to programming, this can be a really kind of touchy and tricky thing, but I will try and explain what we are doing. It's also important to note that we keep everything, including controller input, in fixed update. If you have things mixed between fixed and regular update, it seems to misscount the animation frames. Maybe everything can be kept in update, I dunno.

    Disclaimer: The only reason we are using such a temperamental system is because Smash games are so unbelievably complicated. Hippo's method probably would work better if your game is a standard 1v1 fighting game. Though I wish they would elaborate more on it.

    This is the code so far. Here's basically how it works. We have two objects (because I am lazy). Each one has a sprite that is 32x32 so we can see it. Those objects are then dropped in as references as hitbox/hurtboxobjectreference.

    We have a HitBox and HurtBox class, we then have a class called FrameInfo that makes arrays of the HitBox and HurtBox class. FrameInfo is the individual frame information. In this way we can have infinite hit/hurtboxes per sprite. Finally we have the Animations class which is an array of frames.

    The publicly exposed variable is animationList which is an array of Animations. It's a heirchy so that we could have infinite animations with infinite frames with infinite hit/hurt boxes. All of which could have been avoided had get/set animation frame been reflected into the scripting language.

    Here is a picture of it in the editor with some frame info.



    This script loops through those hit and hurtbox arrays on each frame tick, and instantiates those objects. It places them based upon the relative info you fed it in the array through position and size (scale).

    The easiest way to do this - because it all has to be done by hand - is to place the single animation frame on the stage, child a hitbox to it, and move/size the hitbox until it's right, then write down the info.

    Yes, this has to be done for every single frame of animation, and you want to test it every single time. It is tedious at best.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6.  
    7. public class FrameAnimate : MonoBehaviour {
    8.  
    9.     List<GameObject> hurtBoxObjectArray = new List<GameObject>();
    10.     List<GameObject> hitBoxObjectArray = new List<GameObject>();
    11.  
    12.     [Header("References")]
    13.     //public Canvas debugGUIReference;
    14.     public GameObject hurtBoxObjectReference;
    15.     public GameObject hitBoxObjectReference;
    16.  
    17.     [Header("Animation Settings")]
    18.     public bool debugAnimation = true;
    19.     public string startAnimation = "Jab";
    20.     public int animationFramesPerSecond = 15;
    21.     int currentAnimationFrame = 0; //hide later
    22.  
    23.     [Header("Animation Array")]
    24.     public Animations[] animationList;
    25.  
    26.     [HideInInspector]
    27.     public Animations currentAnimation;
    28.  
    29.     [HideInInspector]
    30.     public int xFacing = 1;
    31.  
    32.     [HideInInspector]
    33.     public float frameCurrentTime = 0;
    34.     [HideInInspector]
    35.     public float frameEndTime = 0;
    36.  
    37.     [System.Serializable]
    38.     public class Animations
    39.     {
    40.         public string animationName;
    41.         public FrameInfo[] frames;
    42.     }
    43.     [System.Serializable]
    44.     public class FrameInfo
    45.     {
    46.         public Sprite frameSprite;
    47.         public HitBox[] HitBoxes;
    48.         public HurtBox[] HurtBoxes;
    49.     }
    50.     [System.Serializable]
    51.     public class HitBox {
    52.         public Rect hitBoxDimensions;
    53.  
    54.         [Range(0.0f, 1.0f)]
    55.         public float damageAmount;
    56.     }
    57.     [System.Serializable]
    58.     public class HurtBox
    59.     {
    60.         public Rect hurtBoxDimensions;
    61.     }
    62.  
    63.     void Start () {
    64.         SetAnimation(startAnimation);
    65.     }
    66.  
    67.     void FixedUpdate () {
    68.         frameCurrentTime = Time.fixedTime * 1000;
    69.  
    70.         AnimateFrames();
    71.  
    72.         //debugGUIReference.transform.FindChild("txt_frameinfo").GetComponent<Text>().text = "Frame: " + GetFrame().ToString() + "/" + (NumberOfFrames() - 1).ToString();
    73.         //debugGUIReference.transform.FindChild("txt_animationname").GetComponent<Text>().text = "Animation Name: " + currentAnimation.animationName;
    74.     }
    75.  
    76.     void AnimateFrames()
    77.     {
    78.         if(frameCurrentTime >= frameEndTime)
    79.         {
    80.             currentAnimationFrame++;
    81.             frameEndTime = frameCurrentTime + Mathf.CeilToInt((1f / animationFramesPerSecond) * 1000);
    82.         } else
    83.         {
    84.             return; //make sure we are only removing and generating new boxes once per
    85.         }
    86.  
    87.        if(currentAnimationFrame > currentAnimation.frames.Length - 1)
    88.         {
    89.             currentAnimationFrame = 0;
    90.         }
    91.  
    92.         this.GetComponent<SpriteRenderer>().sprite = currentAnimation.frames[currentAnimationFrame].frameSprite;
    93.  
    94.         CleanUpBoxes();
    95.         GenerateBoxes();
    96.     }
    97.  
    98.     public void SetFrame(int frame)
    99.     {
    100.         if (frame > currentAnimation.frames.Length - 1)
    101.             frame = currentAnimation.frames.Length - 1;
    102.         if (frame < 0)
    103.         {
    104.             frame = 0;
    105.         }
    106.  
    107.         currentAnimationFrame = frame;
    108.         frameEndTime = frameCurrentTime + Mathf.CeilToInt((1f / animationFramesPerSecond) * 1000);
    109.         this.GetComponent<SpriteRenderer>().sprite = currentAnimation.frames[frame].frameSprite;
    110.     }
    111.  
    112.     public int GetFrame()
    113.     {
    114.         return currentAnimationFrame;
    115.     }
    116.  
    117.     int NumberOfFrames()
    118.     {
    119.         return currentAnimation.frames.Length;
    120.     }
    121.  
    122.     public Animations GetAnimationInfo(string animationName) //returns current animation if error
    123.     {
    124.         for (int i = 0; i < animationList.Length; i++)
    125.         {
    126.             if (animationList[i].animationName.ToLower() == animationName.ToLower())
    127.             {
    128.                 return animationList[i];
    129.             }
    130.         }
    131.  
    132.         return currentAnimation;
    133.     }
    134.  
    135.     public bool SetAnimation(string animationName, int startFrame = 0)
    136.     {
    137.         //Is our current animation the same as the one we are setting?
    138.         if (currentAnimation.animationName.ToLower() == animationName.ToLower())
    139.             return true;
    140.  
    141.         for(int i = 0; i < animationList.Length; i++)
    142.         {
    143.             if(animationList[i].animationName.ToLower() == animationName.ToLower())
    144.             {
    145.                 if(startFrame > animationList[i].frames.Length - 1)
    146.                     startFrame = animationList[i].frames.Length - 1;
    147.                 if(startFrame < 0)
    148.                 {
    149.                     startFrame = 0;
    150.                 }
    151.  
    152.                 currentAnimationFrame = startFrame;
    153.                 currentAnimation = animationList[i];
    154.  
    155.                 //You need this to make sure we are restarting the first frame of animation timer.
    156.                 frameEndTime = frameCurrentTime + Mathf.CeilToInt((1f / animationFramesPerSecond) * 1000);
    157.                 this.GetComponent<SpriteRenderer>().sprite = currentAnimation.frames[startFrame].frameSprite;
    158.  
    159.                 CleanUpBoxes();
    160.                 GenerateBoxes();
    161.  
    162.                 return true;
    163.             }
    164.         }
    165.  
    166.         Debug.Log("Error, animation not registered.");
    167.         return false;
    168.     }
    169.  
    170.     void GenerateBoxes() //generated once per frame
    171.     {
    172.         Vector2 mePosition = new Vector2(this.transform.position.x, this.transform.position.y);
    173.  
    174.         if (currentAnimation.frames[currentAnimationFrame].HurtBoxes.Length != 0)
    175.         {
    176.  
    177.             //hurtboxes
    178.             for (int i = 0; i < currentAnimation.frames[currentAnimationFrame].HurtBoxes.Length; i++)
    179.             {
    180.                 Rect hurtBoxPosition = new Rect(new Vector2(currentAnimation.frames[currentAnimationFrame].HurtBoxes[i].hurtBoxDimensions.position.x * xFacing, currentAnimation.frames[currentAnimationFrame].HurtBoxes[i].hurtBoxDimensions.position.y) + mePosition, currentAnimation.frames[currentAnimationFrame].HurtBoxes[i].hurtBoxDimensions.size);
    181.  
    182.                 GameObject hurtBoxTemp = Instantiate(hurtBoxObjectReference) as GameObject;
    183.                 hurtBoxTemp.GetComponent<HurtBoxObject>().owner = this.transform.gameObject;
    184.                 hurtBoxTemp.transform.position = new Vector3(hurtBoxPosition.x, hurtBoxPosition.y, this.transform.position.z);
    185.                 hurtBoxTemp.transform.localScale = new Vector3(hurtBoxPosition.width, hurtBoxPosition.height, 0);
    186.                 hurtBoxTemp.transform.parent = this.transform;
    187.                 hurtBoxTemp.GetComponent<HurtBoxObject>().isDebug = debugAnimation;
    188.                 hurtBoxTemp.GetComponent<SpriteRenderer>().sortingLayerName = "foreground";
    189.  
    190.                 hurtBoxObjectArray.Add(hurtBoxTemp);
    191.             }
    192.         }
    193.  
    194.         if (currentAnimation.frames[currentAnimationFrame].HitBoxes.Length != 0)
    195.         {
    196.             //hitboxes
    197.             for (int i = 0; i < currentAnimation.frames[currentAnimationFrame].HitBoxes.Length; i++)
    198.             {
    199.                 Rect hitBoxPosition = new Rect(new Vector2(currentAnimation.frames[currentAnimationFrame].HitBoxes[i].hitBoxDimensions.position.x * xFacing, currentAnimation.frames[currentAnimationFrame].HitBoxes[i].hitBoxDimensions.position.y) + mePosition, currentAnimation.frames[currentAnimationFrame].HitBoxes[i].hitBoxDimensions.size);
    200.  
    201.                 GameObject hitBoxTemp = Instantiate(hitBoxObjectReference) as GameObject;
    202.                 hitBoxTemp.GetComponent<HitBoxObject>().owner = this.transform.gameObject;
    203.                 hitBoxTemp.transform.position = new Vector3(hitBoxPosition.x, hitBoxPosition.y, this.transform.position.z);
    204.                 hitBoxTemp.transform.localScale = new Vector3(hitBoxPosition.width, hitBoxPosition.height, 0);
    205.                 hitBoxTemp.transform.parent = this.transform;
    206.                 hitBoxTemp.GetComponent<HitBoxObject>().isDebug = debugAnimation;
    207.                 hitBoxTemp.GetComponent<HitBoxObject>().damageAmount = currentAnimation.frames[currentAnimationFrame].HitBoxes[i].damageAmount;
    208.                 hitBoxTemp.GetComponent<SpriteRenderer>().sortingLayerName = "foreground";
    209.  
    210.                 hitBoxObjectArray.Add(hitBoxTemp);
    211.             }
    212.         }
    213.      
    214.     }
    215.  
    216.     void CleanUpBoxes()
    217.     {
    218.         //find all boxes created by this pone and delete them
    219.         foreach(GameObject hb in hurtBoxObjectArray)
    220.         {
    221.             Destroy(hb);
    222.         }
    223.         hurtBoxObjectArray.Clear();
    224.  
    225.         foreach(GameObject hb in hitBoxObjectArray)
    226.         {
    227.             Destroy(hb);
    228.         }
    229.     }
    230.  
    231.  
    232.     //end
    233. }
    234.  
    The meat of how this script works is through AnimateFrames(), which simply does some math and comes up with the next time the next frame should fire. This is where things get wonky.

    It isn't perfect. It does not give each and every frame exactly the amount of time it should have. This is why your character script utilizing FrameAnimate should also be on fixedTime as well as your controller script. If you use Update() for anything having to do with frame checks, you will double play animations, miss combos, ect.

    I can't even guarantee this script will be stable or work. This was an off-the-cuff solution for what seemed to be our most annoying problem.

    Edit: Regarding your question on polygons. We settled for rectangles because we can overlap them across the frame and achieve similar results. Your results may vary.
     
    renman3000 likes this.
  26. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    Thanks a lot! Our game is beat 'em up style, so there is a similar need for hitting multiple enemies and/or multiple hitboxes. Would you be willing to post the hitbox, hurtbox, and frameInfo classes?
     
  27. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Hey there. All the classes are already there. That's what this stuff is:

    Code (CSharp):
    1. [System.Serializable]
    2.     public class Animations
    3.     {
    4.         public string animationName;
    5.         public FrameInfo[] frames;
    6.     }
    7.     [System.Serializable]
    8.     public class FrameInfo
    9.     {
    10.         public Sprite frameSprite;
    11.         public HitBox[] HitBoxes;
    12.         public HurtBox[] HurtBoxes;
    13.     }
    14.     [System.Serializable]
    15.     public class HitBox {
    16.         public Rect hitBoxDimensions;
    17.  
    18.         [Range(0.0f, 1.0f)]
    19.         public float damageAmount;
    20.     }
    21.     [System.Serializable]
    22.     public class HurtBox
    23.     {
    24.         public Rect hurtBoxDimensions;
    25.     }
    As far as the hitbox and hurtbox prefabs, it should take you more than a minute to make those, and drag them into the reference spots in the inspector.

    If you look here, you will see that you need to make two scripts HitBoxObject and HurtBoxObject, and attach those to the prefabs you just made. You will also see that I have a couple of variables on each like isDebug and damageAmount. The isDebug on those scripts just says whether or not to make them visible, you can use that or not.

    Code (CSharp):
    1. void GenerateBoxes() //generated once per frame
    2.     {
    3.         Vector2 mePosition = new Vector2(this.transform.position.x, this.transform.position.y);
    4.  
    5.         if (currentAnimation.frames[currentAnimationFrame].HurtBoxes.Length != 0)
    6.         {
    7.  
    8.             //hurtboxes
    9.             for (int i = 0; i < currentAnimation.frames[currentAnimationFrame].HurtBoxes.Length; i++)
    10.             {
    11.                 Rect hurtBoxPosition = new Rect(new Vector2(currentAnimation.frames[currentAnimationFrame].HurtBoxes[i].hurtBoxDimensions.position.x * xFacing, currentAnimation.frames[currentAnimationFrame].HurtBoxes[i].hurtBoxDimensions.position.y) + mePosition, currentAnimation.frames[currentAnimationFrame].HurtBoxes[i].hurtBoxDimensions.size);
    12.  
    13.                 GameObject hurtBoxTemp = Instantiate(hurtBoxObjectReference) as GameObject;
    14.                 hurtBoxTemp.GetComponent<HurtBoxObject>().owner = this.transform.gameObject;
    15.                 hurtBoxTemp.transform.position = new Vector3(hurtBoxPosition.x, hurtBoxPosition.y, this.transform.position.z);
    16.                 hurtBoxTemp.transform.localScale = new Vector3(hurtBoxPosition.width, hurtBoxPosition.height, 0);
    17.                 hurtBoxTemp.transform.parent = this.transform;
    18.                 hurtBoxTemp.GetComponent<HurtBoxObject>().isDebug = debugAnimation;
    19.                 hurtBoxTemp.GetComponent<SpriteRenderer>().sortingLayerName = "foreground";
    20.  
    21.                 hurtBoxObjectArray.Add(hurtBoxTemp);
    22.             }
    23.         }
    24.  
    25.         if (currentAnimation.frames[currentAnimationFrame].HitBoxes.Length != 0)
    26.         {
    27.             //hitboxes
    28.             for (int i = 0; i < currentAnimation.frames[currentAnimationFrame].HitBoxes.Length; i++)
    29.             {
    30.                 Rect hitBoxPosition = new Rect(new Vector2(currentAnimation.frames[currentAnimationFrame].HitBoxes[i].hitBoxDimensions.position.x * xFacing, currentAnimation.frames[currentAnimationFrame].HitBoxes[i].hitBoxDimensions.position.y) + mePosition, currentAnimation.frames[currentAnimationFrame].HitBoxes[i].hitBoxDimensions.size);
    31.  
    32.                 GameObject hitBoxTemp = Instantiate(hitBoxObjectReference) as GameObject;
    33.                 hitBoxTemp.GetComponent<HitBoxObject>().owner = this.transform.gameObject;
    34.                 hitBoxTemp.transform.position = new Vector3(hitBoxPosition.x, hitBoxPosition.y, this.transform.position.z);
    35.                 hitBoxTemp.transform.localScale = new Vector3(hitBoxPosition.width, hitBoxPosition.height, 0);
    36.                 hitBoxTemp.transform.parent = this.transform;
    37.                 hitBoxTemp.GetComponent<HitBoxObject>().isDebug = debugAnimation;
    38.                 hitBoxTemp.GetComponent<HitBoxObject>().damageAmount = currentAnimation.frames[currentAnimationFrame].HitBoxes[i].damageAmount;
    39.                 hitBoxTemp.GetComponent<SpriteRenderer>().sortingLayerName = "foreground";
    40.  
    41.                 hitBoxObjectArray.Add(hitBoxTemp);
    42.             }
    43.         }
    44.        
    45.     }
     
    JoeStrout likes this.
  28. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    For some reason the animations array isn't accepting any anim files when I try to drag and drop them to it, any idea why that is?
     
  29. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    (see attached)
    It's not going to. You are bypassing the animation system, you don't get to use it. You need to actually look at that script and understand what it is doing, if you don't understand what this solution is doing, you are going to have a bad time.

    Take this crouch animation for example. You have to add each sprite to the array, the array size is the exact size of the number of frames for that animation. Each frame has an array of hit and hurtboxes it generates.

    Again, I must say that this is not an easy method to use. I am almost exclusively using FixedUpdate set to a time of .3333. Everything I do is calculated in Time.fixedTime. My framerate is limited to 30fps. It takes quite a bit to get consistent frame-stable results. So be prepared to tinker. There is no drag and drop solution.

    I am thinking about adding some more functionality to the script to allow me to turn off looping as well, so that I can have extra frames to make sure when an animation is over, that is does not double play. So far I haven't needed it since my tweaks, but we will see.

    We are going this far out of our way because we are trying to emulate a console experience on the PC, including dedicated controller ports. It has been one helluva task.

    Edit: To further clarify why you can't use animation clips and you are forced to setup each frame, is because Animation.clip does not supply the frame count, current frame, or allow you to set the frame. It only supplies time length. This is why we are manually animating the thing.
     

    Attached Files:

  30. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    OH! You're hardcoding the information into the array in the inspector? I think I get it now.
     
  31. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Yes, each and every sprite along with the placement/scale of the hit/hurtbox objects relative to the character parent.
     
  32. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    Well that's easy enough, time consuming, yes, but hey, I'm a college student, got plenty of that. :p

    Doesn't take any more time than it does making this stick animations.

    Edit: What I'm saying is, well done Britain, you just made my life significantly easier. Many thanks!
     
  33. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    No problem, if you get stuck give a shout. I am probably going to do a tutorial series or something on this. There are many people out there getting stuck on things like the camera.
     
  34. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    Desperately, I tried that one post that somebody suggested with the hitbox manager, it really didn't work out.
     
  35. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Haha, you too? I researched for MONTHS on this. I bought like 3 Unity classes on Udemy and even took 2 on Lynda. I tried technique after technique trying to avoid what I am doing right now. All they need to do is expose setting and getting the animation frame!
     
  36. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    Ok, how did you do your canvas and hitbox construction?
     
  37. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    The canvas is not needed, that's why it is commented out. If you need it for debugging then you need to uncomment these two lines in FixedUpdate and add those components with those names to a canvas.

    Code (CSharp):
    1. //debugGUIReference.transform.FindChild("txt_frameinfo").GetComponent<Text>().text = "Frame: " + GetFrame().ToString() + "/" + (NumberOfFrames() - 1).ToString();
    2.         //debugGUIReference.transform.FindChild("txt_animationname").GetComponent<Text>().text = "Animation Name: " + currentAnimation.animationName;
    As far making the HitBox object and the HurtBox objects, as I explained you need to make two prefabs for your hit and hurt boxes, and then make two scripts. One called HurtBox and one called HitBox, and assign them to their respective prefabs. Then you assign those prefabs to the right place in the inspector of FrameAnimate.

    The HitBox and HurtBox prefab will each have a sprite renderer as well of a square. I have provided a picture. Of the hurtbox as an example.

    Take a square sprite, I use 32px x 32px. Drag it on the stage to make game object, attach a script called "HurtBox" I provided it on the right. Drag object to folder to make prefab. Drag that over to the inspector of the FrameAnimate script on your player into the HurtBoxObjectReference variable section.

    Do the same for the HitBox, except give it an extra variable "public float damageAmount".
     

    Attached Files:

  38. jastium

    jastium

    Joined:
    Aug 14, 2014
    Posts:
    2
    Three questions since this looks really interesting.

    1. Are you able to have complex shapes/rotations on your hitboxes?
    2. Is there a way for you to play the animation in edit mode?
    3. Would a system like this work well if you were designing a top down RPG with different equippable weapons that had the same attack pattern but different hitbox sizes? E.g. a spinning whirlwind attack that was faster, but had smaller hitboxes, than the same version of the attack when using a halberd (but both attacks follow the same motion). Now that I type it out, I guess that really would be two completely different animations under your system. Having 4 directions for your character to face (as opposed to 2 in a platformer) also complicates things :\

    Slight followup. Are you concerned with the # of GetComponent calls that you seem to be making every frame?
     
    Last edited: Mar 30, 2016
  39. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    1. I am working on this right now, we need it for traversing hills. Since Smash game are unlike most fighting games that simply have flat surfaces, we need to shove in some trig to rotate the hitboxes around the root of the character. We plan to use rectangles for everything. If we need a complex shape, we are just going to stack rectangles.

    2. No, each frame is drug out individually, we attach the hit/hurtbox objects, copy the values, and plug them in. Then we test it in game by playing the animation at 1fps. Very time consuming, not recommended for anyone's development but our own. Also, for a top down you probably would not need a system like this unless it was some very complex movement. I believe even a game like Secret of Evermore just forward cast the attack box. I don't think there was any complex hitbox array there.

    3. So far we have not experienced any negative side effects. We are doing many, many not-so-recommended things to hack this together. Right now we care less about performance and more about just getting something working. We will then go back and refactor with performance in mind. We are at the stage of just assembling the attacks.
     
    jastium likes this.
  40. jastium

    jastium

    Joined:
    Aug 14, 2014
    Posts:
    2
    Thanks for the answers. Best of luck in your endeavor, a smash style game and the laying out of hitboxes is pretty interesting stuff.
     
  41. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    No problem, if you get stuck let me know. I will help any way I can.
     
  42. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    Do you have a box vs box detection script yet?
     
  43. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Not yet, I am trying to decide how I want to deal with that. I am sure whatever I do is going to involve bounds and iterating through through players. I am probably going to create a notifier system.
     
  44. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    If box colliders dynamically change with the size of the hurtbox prefab, could you just add them to the hurtbox/hitbox prefab set to isTrigger and write a script around that, firing a call to a damage script on onTriggerEnter?
     
  45. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    There are two ways of doing it.

    1. Yes, it will re size with the prefab because it is being scaled, but you want your enemies/character detecting the hitboxes, not the other way around.

    2. There is a possibility that the hurtbox or hitbox generated won't exists on the overlap frame, you might be null exceptions. We may go the route of iteration and then report the find to the owner and receiver. We haven't decided yet. It may be a non issue since we are running the entire game on fixedTime and FixedUpdate to insure everything is synced with the framer.
     
  46. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    I told the HurtBox script to check for OnTriggerEnter and register the hit if it was a hitbox and pull the right info. Then I put a script on the barrel that loops through its hurtboxes looking for a damaged hurtbox.

    The result is this HIT/HURT box video on our Facebook.

    https://www.facebook.com/supersmashponiesofficial/
     
  47. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    Do you have separate recovery animations setup and scripted yet?
     
  48. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    I'm not sure what you're asking. We have hurt->recover, stunned->recover, ect already finished but they are not wired into our state machine yet. For stuns, we simply check if they have moved into a state - IE: Landing - and then check if they are between certain frames in that state to apply IsStunned (as an example).

    None of the state machine is stored on the FrameAnimation side. Let me give you an example, let's say I want to get if we are landing.

    I might script:

    • First check and see if we are in a state we can land.
    • Then check if we are in an animation that normally would permit us to land. IE: Not hurt or floored.The best way to check this it with frame.currentAnimation.animationName, which returns a string name of the current animation. "frame" is a reference to the FrameAnimate component. So basically, if we are say, falling, then we know we can transition to landing.
    • Check to see if we are in a suitable animation/state and check if we hit the ground.
    • If we hit the ground set the state to landing, and set the animation to "Land" or whatever you named your land animation. You should probably be using a custom boolean state machine as well. IsLanding would be a good name for this state.
    • Now, do a check with something like:
    Code (CSharp):
    1. if (frame.currentAnimation.animationName == "Land" && IsLanding)
    2.         {
    3.             if (frame.GetFrame() == frame.NumberOfFrames() - 1)
    4.             {
    5.                 IsLanding = false;
    6.                 IsStunned = false;
    7.             }
    8.             else
    9.             {
    10.                 IsLanding = true;
    11.                 animation = "Land";
    12.                 IsStunned = true;
    13.             }
    14.         }
    As you can see, during the time we are in the land animation, we are now stunned. You might notice that instead of frame.SetAnimation, I used animation="". I set the animation at the end of the stack with SetAnimation, I use a string variable throughout the top of the stack to allow it to change as it progresses through the state machine.
     
  49. boxhallowed

    boxhallowed

    Joined:
    Mar 31, 2015
    Posts:
    513
    Alright, to help with understanding how this works, I am putting together and example project. Give me a day or so.
     
  50. ngrimesdev

    ngrimesdev

    Joined:
    Mar 22, 2016
    Posts:
    15
    Sorry, I was referring to recovery frames after an attack. Startup -> active -> recovery. This is good info though.
     
Thread Status:
Not open for further replies.