Topic: Unique Hitboxes per state

I'd like to make it so the hitboxes for the character change if they are "Jumping" and "Crouching", differing from the "Standing" default hitbox state.

Any suggestions on how to do this? I imagine I'd need to create additional hitBoxes arrays (hitBoxesStand, hitBoxesJump, hitBoxesCrouch). Then somewhere I'd change the current hitBoxes to be one of those three states depending on the instance.

The reason I'm wanting to do this is for more control of the hitboxes in the game per character. The way UFE does it by default is very... 3D fighter like, where the hitboxes are linked to the bone instead of being based off a root or origin position. Also, with the existing method you can't make say the feet of a jump state be projectile invincible like it is in Street Fighter, since it just holds the default standing hitbox state to all states in the game.

Eitherway, any suggestions on this are appreciated. I'll use this thread to flesh out any findings I have.

Share

Thumbs up +1 Thumbs down

Re: Unique Hitboxes per state

Related but not exactly the end goal, I've been able to add/remove hit, body, and throw colliders during a move's frames. Made in combination with the Invincible body parts feature you can make a character less or more vulnerable. If people are interested in having such a feature let me know and I can try posting up the script changes that are necessary (it's a lot of changes).

Next step for me is to try and get the Original Post's goal: Change hitboxes per state change instead of just in move assets.

Share

Thumbs up Thumbs down

3 (edited by MrPonton 2017-02-25 16:02:59)

Re: Unique Hitboxes per state

Okay, this honestly was easier than I thought it would be. Easier than that last post of mine in this thread.

You'll need to touch the following files: CharacterEditorWindow.cs, ControlsScript.cs, HitBoxesScript.cs, MoveEditorWindow.cs

HitBoxesScript.cs

The crux of this is we're going to store additional HitBox[] arrays, that we will then use the existing hitBoxes[] array as a vessel that contains whatever the current state's hit boxes are. This will make it a lot easier to not have to touch core functionality while switching it in and out. In the HitBoxesScript class go ahead and add a HitBox[] for each of the states: Standing, Crouching, Jumping Straight, Jumping Forward, Jumping Backward, and Down.

Look for the following line of code:

public class HitBoxesScript : MonoBehaviour {
    public HitBox[] hitBoxes;

and add below it the following:

    public HitBox[] jumpHitBoxes;
    public HitBox[] jumpForwardHitBoxes;
    public HitBox[] jumpBackHitBoxes;
    public HitBox[] standHitBoxes;
    public HitBox[] crouchHitBoxes;
    public HitBox[] downHitBoxes;

ControlsScript.cs

Now, let's get these arrays to render in-game shall we? In the DoFixedUpdate() I imagine you'll want one of the first things to be checking what the current state is and setting the character's hit boxes to that state. I personally put my code above the '// Character colliders based on collision mass and body colliders" since that's the first area that utilizes the hit boxes:

Look for the following line of code:

        // Character colliders based on collision mass and body colliders

and add above it the following:

        switch (currentState) {
            case PossibleStates.Stand:
                myHitBoxesScript.hitBoxes = myHitBoxesScript.standHitBoxes;
                break;
            case PossibleStates.BackJump:
                myHitBoxesScript.hitBoxes = myHitBoxesScript.jumpBackHitBoxes;
                break;
            case PossibleStates.ForwardJump:
                myHitBoxesScript.hitBoxes = myHitBoxesScript.jumpForwardHitBoxes;
                break;
            case PossibleStates.StraightJump:
                myHitBoxesScript.hitBoxes = myHitBoxesScript.jumpHitBoxes;
                break;
            case PossibleStates.Crouch:
                myHitBoxesScript.hitBoxes = myHitBoxesScript.crouchHitBoxes;
                break;
            case PossibleStates.Down:
                myHitBoxesScript.hitBoxes = myHitBoxesScript.downHitBoxes;
                break;
            default:
                myHitBoxesScript.hitBoxes = myHitBoxesScript.standHitBoxes;
                break;
        }

At this point the game should work fine and render whatever hit boxes you have in a state's hit box array. However, I think unless you're hardcore programmer, you might want to use the existing UFE editor tools to create those hit box arrays right? Yeah, i thought so, so let's move on to modifying the editors to utilize these state changes.

CharacterEditorWindow.cs

First we want to declare a new property enum for the possible states. So at the top of the CharacterEditorWindow class add a PossibleStates property:

Look for the following line of code:

    private GameObject selectedPrefab;

and add below it:

    private PossibleStates possibleStates;

Next let's make it possible to set that property in the editor. It seems like a good idea that I'd want to set this property after selecting the Character Prefab to modify that way it's like "I want this character, with this hit boxes state.". So find where we select the Prefab and add a line that displays the enum property for selection only when we aren't actively previewing a character:

Find the following line of code:

selectedPrefabIndex = EditorGUILayout.Popup("Prefab Selection:", selectedPrefabIndex, prefabSelect);

and add the following below it:

if (!characterPreviewToggle)
    possibleStates = (PossibleStates)EditorGUILayout.EnumPopup("State: ", possibleStates, enumStyle);

And there you go, now you can set the state you'll want to preview in the editor. Next let's actually load the currently set state's hit boxes when previewing the character. We're going to need to know and have basic move animations set up for the character as I'm basically borrowing the animation preview code from the Move Editor. Inside the PreviewCharacter() alter the method to accept two parameters: float animFrame, int castingFrame. Then, within the if (character == null) check add the code to set the animation, the hit boxes, and begin playback of the animation, also take note that the end of the method has moved the hitboxesscript instationation which we now do in the if check:

Look for the following line of code:

    public void PreviewCharacter(){

and replace with the following:

    public void PreviewCharacter(float animFrame, int castingFrame){

Look for the following line of code:

   if (character == null){

and replace the lines inside the if check with the following:

            character = (GameObject) PrefabUtility.InstantiatePrefab(selectedPrefab);
            character.transform.position = new Vector3(-2,0,0);
            Animator animator = character.GetComponent<Animator>();
            hitBoxesScript = character.GetComponent<HitBoxesScript>();
            if (animator == null) animator = (Animator)character.AddComponent(typeof(Animator));
            if (animator.runtimeAnimatorController == null)
                animator.runtimeAnimatorController = (RuntimeAnimatorController)Resources.Load("MC_Controller");
            BasicMoveInfo move = characterInfo.moves[0].basicMoves.idle;
            switch (possibleStates) {
                case PossibleStates.Stand:
                    move = characterInfo.moves[0].basicMoves.idle;
                    hitBoxesScript.hitBoxes = hitBoxesScript.standHitBoxes;
                    break;
                case PossibleStates.BackJump:
                    move = characterInfo.moves[0].basicMoves.jumpBack.clip1 != null
                        ? characterInfo.moves[0].basicMoves.jumpBack
                        : characterInfo.moves[0].basicMoves.jumpStraight;
                    hitBoxesScript.hitBoxes = hitBoxesScript.jumpBackHitBoxes;
                    break;                    
                case PossibleStates.ForwardJump:
                    move = characterInfo.moves[0].basicMoves.jumpForward.clip1 != null
                        ? characterInfo.moves[0].basicMoves.jumpForward
                        : characterInfo.moves[0].basicMoves.jumpStraight;
                    hitBoxesScript.hitBoxes = hitBoxesScript.jumpForwardHitBoxes;
                    break;
                case PossibleStates.StraightJump:
                    move = characterInfo.moves[0].basicMoves.jumpStraight;
                    hitBoxesScript.hitBoxes = hitBoxesScript.jumpHitBoxes;
                    break;
                case PossibleStates.Crouch:
                    move = characterInfo.moves[0].basicMoves.crouching;
                    hitBoxesScript.hitBoxes = hitBoxesScript.crouchHitBoxes;
                    break;
                case PossibleStates.Down:
                    move = characterInfo.moves[0].basicMoves.standUp;
                    hitBoxesScript.hitBoxes = hitBoxesScript.downHitBoxes;
                    break;
                default:
                    move = characterInfo.moves[0].basicMoves.idle;
                    hitBoxesScript.hitBoxes = hitBoxesScript.standHitBoxes;
                    break;
            }
            float animTime = (((float)(1f - castingFrame) / move.clip1.frameRate * move.animationSpeed));
            move.clip1.SampleAnimation(character, animTime);
            hitBoxesScript.UpdateRenderer();

Now find the following two lines below where you just pasted code:

 hitBoxesScript = character.GetComponent<HitBoxesScript>();
hitBoxesScript.UpdateRenderer();

Delete those lines.

Now, let's tell the editor to call that modified PreviewCharacter() method using the new parameters. Find where we Open the character and pass in the values you want for the animation states. I used 1 and 1:

Look for the following line of code:

                       PreviewCharacter()

Change it to the following:

                       PreviewCharacter(1f, 1);

So now, whenever you load the character preview it will load the hit boxes for whatever state you currently have selected.

Next we want to save the hit boxes we just created to the proper state's array. So find where we Apply the changes, add a switch statement to set whatever the current vessel'd hit boxes are to the desired state's array. Then leave the existing code to save the entire character prefab.

Look for the following line of code:

                                        if (StyledButton("Apply Changes")){

and add the following below it, contained within the if statement:

                                            switch (possibleStates) {
                                                case PossibleStates.Stand:
                                                    hitBoxesScript.standHitBoxes = hitBoxesScript.hitBoxes;
                                                    break;
                                                case PossibleStates.StraightJump:
                                                    hitBoxesScript.jumpHitBoxes = hitBoxesScript.hitBoxes;
                                                    break;
                                                case PossibleStates.ForwardJump:
                                                    hitBoxesScript.jumpForwardHitBoxes = hitBoxesScript.hitBoxes;
                                                    break;
                                                case PossibleStates.BackJump:
                                                    hitBoxesScript.jumpBackHitBoxes = hitBoxesScript.hitBoxes;
                                                    break;
                                                case PossibleStates.Crouch:
                                                    hitBoxesScript.crouchHitBoxes = hitBoxesScript.hitBoxes;
                                                    break;
                                                case PossibleStates.Down:
                                                    hitBoxesScript.downHitBoxes = hitBoxesScript.hitBoxes;
                                                    break;
                                                default:
                                                    break;
                                            }

With that, you can now set and save these hit box state arrays. So now in-game you'll see your hit boxes change per state.

There is one other area of issue: Move editing. Since the HitBoxesScript's hitBoxes[] is being used as a vessel, whenever you go to a move it will currently load that vessel's hit boxes. I.E., whatever the last state you opened in the character editor window will be what hit boxes render at a base when previewing a move. Let's fix that by giving the MoveEditorWindow a possible states control.

MoveEditorWindow.cs

Create a new property in the class for an enumerator of PossibleStates:

Look for the following line of code:

    private GameObject projectilePrefab;

and add the following line below it:

    private PossibleStates possibleStates;

Now, let's make it so we can set the hitboxes to render for preview. Find the SubGroupTitle("Preview") area, and like before, set the selector after the Character Prefab line:

Find the following line of code:

GameObject newCharacterPrefab = (GameObject) EditorGUILayout.ObjectField("Character Prefab:", moveInfo.characterPrefab, typeof(UnityEngine.GameObject), true);

and add the following below it:

possibleStates = (PossibleStates)EditorGUILayout.EnumPopup("Hit Boxes State: ", possibleStates, enumStyle);

The nice thing about this instance is that since it's just a read-only and doesn't have any effect on the move, you can live update the state as you see fit. Then the hitboxes will render appropriately. But before we can do that we also need to tell it to load those hit boxes based on this current state you just made it possible to set.

Create a new method in the class that updates the hit boxes vessel array to be whatever the current state you selected is. It doesn't matter where in the code this is since it's a new method you're creating. I named mine UpdateHitBoxesState() placing it right after the Update() method:

    void UpdateHitBoxesState() {
        if (characterPrefab != null) {
            HitBoxesScript hitBoxesScript = characterPrefab.GetComponent<HitBoxesScript>();
            switch (possibleStates) {
                case PossibleStates.Stand:
                    hitBoxesScript.hitBoxes = hitBoxesScript.standHitBoxes;
                    break;
                case PossibleStates.BackJump:
                    hitBoxesScript.hitBoxes = hitBoxesScript.jumpBackHitBoxes;
                    break;
                case PossibleStates.ForwardJump:
                    hitBoxesScript.hitBoxes = hitBoxesScript.jumpForwardHitBoxes;
                    break;
                case PossibleStates.StraightJump:
                    hitBoxesScript.hitBoxes = hitBoxesScript.jumpHitBoxes;
                    break;
                case PossibleStates.Crouch:
                    hitBoxesScript.hitBoxes = hitBoxesScript.crouchHitBoxes;
                    break;
                case PossibleStates.Down:
                    hitBoxesScript.hitBoxes = hitBoxesScript.downHitBoxes;
                    break;
                default:
                    hitBoxesScript.hitBoxes = hitBoxesScript.standHitBoxes;
                    break;
            }
        }
    }

Now we need to call that method whenever we tell the system to load the preview.

So find the if check for loading hit boxes, and add the call to the method at the front after grabbing the hitBoxesScript but before updating the renderer:

Locate the following line of code:

           HitBoxesScript hitBoxesScript = targetChar.GetComponent<HitBoxesScript>();

and add the following below it:

            UpdateHitBoxesState();

There you go. Now you can freely load the hit boxes per state in the UFE editors. Hope this helps others as much as it has helped me in my game. It will benefit anyone who uses Rectangles for sure since previously you were stuck with the size and positioning of the rectangles as they were in the character's idle animation, which made it annoying for crouching and jumping animations causing awkward hits to happen.

Share

Thumbs up +2 Thumbs down

Re: Unique Hitboxes per state

Updated tutorial to give better instructions.

Share

Thumbs up Thumbs down