More States

OK so with Impact State out the way, and the bugs giving me a bit of a break, I figured I’d continue implementing some states that existed in the third person, but are yet to be implemented in the course, to accelerate my personal process a bit

My next step was to debate between implementing a blocking state or a jumping state first… In the end, I went for a Jumping State first (since it’s the boring one). However, it’s not working as we speak, and I have no clue as of why. Here’s what I tried doing:

  1. Implement a ‘PlayerJumpingState.cs’ script from scratch:
using RPG.States.Player;
using UnityEngine;

public class PlayerJumpingState : PlayerBaseState
{
    private readonly int JumpHash = Animator.StringToHash("Jump");
    private Vector3 momentum;

    public PlayerJumpingState(PlayerStateMachine stateMachine) : base(stateMachine) {}

    public override void Enter()
    {
        stateMachine.ForceReceiver.Jump(stateMachine.JumpForce);
        momentum = stateMachine.CharacterController.velocity;
        momentum.y = 0;
        stateMachine.Animator.CrossFadeInFixedTime(JumpHash, stateMachine.CrossFadeDuration);
    }

    public override void Tick(float deltaTime)
    {
        Move(momentum, deltaTime);

        if (stateMachine.CharacterController.velocity.y <= 0) 
        {
            stateMachine.SwitchState(new PlayerFallingState(stateMachine));
            return;
        }

        FaceTarget(momentum, deltaTime);
    }

    public override void Exit() {}

}

  1. Implement a ‘PlayerFallingState.cs’, from scratch as well:
using RPG.States.Player;
using UnityEngine;

public class PlayerFallingState : PlayerBaseState
{
    private readonly int FallHash = Animator.StringToHash("Fall");
    private Vector3 momentum;

    public PlayerFallingState(PlayerStateMachine stateMachine) : base(stateMachine) {}

    public override void Enter()
    {
        momentum = stateMachine.CharacterController.velocity;
        momentum.y = 0;

        stateMachine.Animator.CrossFadeInFixedTime(FallHash, stateMachine.CrossFadeDuration);
    }

    public override void Tick(float deltaTime)
    {
        Move(momentum, deltaTime);
        if (stateMachine.CharacterController.isGrounded) SetLocomotionState();
        FaceTarget(momentum, deltaTime);
    }

    public override void Exit() {}
}

  1. Implement the ‘Jump’ function in ‘ForceReceiver.cs’:
public void Jump(float jumpForce) 
        {
            verticalVelocity += jumpForce;
        }
  1. Modify the Jumping state in ‘PlayerTestState.cs’ (what was the point of this state, apart from initial testing, again…?! Somehow it works though, so I assumed that’s where the Jump magic happens):
private void InputReader_HandleJumpEvent()
        {
            Debug.Log($"I get up, and nothing gets me down");
            // stateMachine.SwitchState(new PlayerTestState(stateMachine));
            stateMachine.SwitchState(new PlayerJumpingState(stateMachine));
        }
  1. Handle the Jumping button in ‘InputReader.cs’:
public void OnJump(InputAction.CallbackContext context) 
    {
        if (!context.performed) return;
        JumpEvent?.Invoke();
    }
  1. Name them properly in the Animation tab, so I named the Jump animation “Jump” (no tags) and the Falling Animation “Fall” (no tags either)

How do I move forward to get Jumping and Falling to work?

Define “not working”. Be very descriptive. What did you expect to happen, what did happen? For example:

  • Does “I get up, and nothing gets me down” fire in the PlayerFreeLookState() when you press the space bar (or whatever key you assigned to Jump?
  • Does the character enter the Jumping state (you can check that with a Debug in JumpingState.Enter
  • Does the character transition from the Jumping State to the Falling State when the CharacterController begin a negative descent?
  • Does the character transition back to the Locomotion state when the CharacterController is grounded again.
  • Does the proper animation perform?

All of these things can be checked with Debugs in the enter states as well as debugs in the if statements that determine the switch. Each test above is in order, so once the code has failed one of those tests, it needs must be handled before moving on to the next test.

Good morning to you too :stuck_out_tongue:

Yes it does (btw that’s in the ‘PlayerTestState.cs’, not the ‘PlayerFreeLookState.cs’)

nope, he doesn’t… although I did give off the command to do so

haven’t gotten that far :sweat_smile:

again, didn’t get that far

take a wild guess :stuck_out_tongue: (didn’t get that far either :sweat_smile:)

PlayerTestState exists to get started, but way back when we renamed it to PlayerFreeLookState… which is the state you want to implement Jumping in. Since we never use PlayerTestState in the game proper, it doesn’t make much sense to implement Jumping there.

Try subscribing in PlayerFreeLookState (don’t forget to unsubscribe).

lol I totally forgot that even exists… Yup, works all good now :slight_smile:

I’ll give the blocking state a run now and keep you updated on the next comment

but the face target is a complete mess… It works, but I decided to eliminate it because it doesn’t make much sense to me (and before I forget, can I delete ‘PlayerTestState.cs’?)

OK so the general concept of the blocking state works, but I have a problem and (probably) a request (something I’ll probably be testing as we speak):


[PROBLEM]

  1. I went to ‘Health.TakeDamage()’ and tried implementing this line of code at the top of the function (along with the changes I did to ‘health.cs’ for this):
// first, declare the variable:
private bool isInvulnerable;

// the 'IsInvulnerable()' function:
public void SetInvulnerable(bool isInvulnerable) 
{
this.isInvulnerable = isInvulnerable;
}

// in 'Health.TakeDamage()':
if (isInvulnerable) return; // sometimes it returns true and still the player takes the damage and impact...

However, the return against ‘isInvulnerable’ does not work. In other words, I still get hit although my block state is on. Animation plays with no issues, but I still take damage…

and this is my current ‘PlayerBlockingState.cs’ script:

using RPG.States.Player;
using UnityEngine;

public class PlayerBlockingState : PlayerBaseState
{
    private readonly int BlockHash = Animator.StringToHash("Block");

    public PlayerBlockingState(PlayerStateMachine stateMachine) : base(stateMachine) {}

    public override void Enter()
    {
        stateMachine.Health.SetInvulnerable(true);
        stateMachine.Animator.CrossFadeInFixedTime(BlockHash, stateMachine.CrossFadeDuration);
    }

    public override void Tick(float deltaTime)
    {
        Move(deltaTime);

        // If the player wasn't blocking, return to targeting:
        if (!stateMachine.InputReader.IsBlocking) 
        {
            stateMachine.SwitchState(new PlayerTargetingState(stateMachine));
            return;
        }

        if (stateMachine.Targeter.CurrentTarget == null) 
        {
            stateMachine.SwitchState(new PlayerFreeLookState(stateMachine));
            return;
        }
    }

    public override void Exit()
    {
        stateMachine.Health.SetInvulnerable(false);
    }

}

[REQUEST]

As for my request, I wanted to split the way that blocking happens. So:

a. if I’m holding a 2-handed weapon, I want to use a specific block animation.
b. if I’m holding a shield, I want to use a different animation block, and with a randomness factor, I might even play a whole other third animation that reflects on the enemy and strikes him instead
c. If I don’t have a 2-handed weapon or a shield in hand, I don’t want my player to be capable of entering the blocking state to begin with

Any ideas how to go around this? (As soon as we fix the “can’t block” issue, I’m giving this a solo try before turning it into a discussion, although it sounds quite easy tbh… Just get a reference to the equipment slots and the current weapon config, and then use that to check whether ‘isTwoHanded’ or the shield slot is not null, right? RIGHT?)

I can write a tutorial for everybody or I can stop what I’m doing and just work on yours…

In terms of the blocking itself, it shouldn’t be any different from Nathan’s.

It can’t be true and get past return, that’s not how an if statement works…

Out of curiosity, are the animations you’re using for the enemy’s attack Explosive animations? Do they still have the Hit Animation events? That’s the only way I could see your blocking and still taking damage…

In terms of the Request… You really should have the tools to get these things done…
I would put a field in the WeaponConfig with the name of the Block animation for that weapon, same with the shield…
You can query the Weapon to see if it’s 2 handed, and if it’s not, you can check to see if a shield is equipped, and if neither is true, ignore the block.

nope, they’re Kevin Iglesias’ (for now. I might order Explosives’ if necessary down the line, or when he has another discount…)

Yes, they have the ‘TryHit()’ animations on. Fun fact, I recently deleted all the mesh colliders on my weapons (they were troubling the mounting system)

already am about to start trying, but first let me get the basic blocking to work first :slight_smile:

not to take you by surprise, but… that’s what’s happening in my code :sweat_smile:

:zipper_mouth_face:

I tried to change the function to a boolean and consider it in ‘TryHit()’, but the enemy just won’t get hit no matter what if I do so…

// in 'Health.cs':
private bool isInvulnerable = false;

    public bool SetInvulnerable(bool isInvulnerable)
    {
        this.isInvulnerable = isInvulnerable;
        if (isInvulnerable) return true;
        else return false;
    }

// in 'Fighter.cs':
    void TryHit(int slot)
    {

        if (target.SetInvulnerable(true)) return; // placing this line here means the enemy literally NEVER EVER gets hit again, no matter what happens!
// ... the rest of the function

}

OK so… I managed to get the basic blocking state to work. It’s not the most efficient way, and tbh it’s kinda funky, but if it works, it works. Here’s what I did:

  1. Ignore all the code I placed in the end in my last response

  2. I added a few new functions in ‘Health.cs’:

    private bool isInvulnerable = true;

    public void SetInvulnerable(bool isInvulnerable)
    {
        this.isInvulnerable = isInvulnerable;
    }

    public bool GetInvulnerable() 
    {
        Debug.Log($"IsInvulnerable State: {isInvulnerable}"); // just for testing purposes...
        return isInvulnerable;
    }

    public float GetPlayerHealth()
    {
        // so I can access the players' health component, through the 'Player' tag:
        return GameObject.FindWithTag("Player").GetComponent<Health>().GetHealthPoints();
    }
  1. In ‘Fighter.TryHit()’, I tuned the end of the function a bit, so we can still play the animations but ignore the damage if the block state (FOR THE PLAYER) is on:
// ... The rest of the 'TryHit()' function is right above this new 'if' statement:

// my new code block ------------------------------
if (other.tag == "Player") 
                    {
                        var playerHealth = other.GetComponent<Health>();
                        var vulnerability = playerHealth.GetInvulnerable();
                        if (vulnerability == true) return;
                    }
// end of my new code block ------------------------------

                    if (otherHealth.IsDead()) return;   // can't hit a dead enemy
                    otherHealth.TakeDamage(gameObject, damage, currentWeaponConfig.GetSkill());
                    TryApplyHitForce(other, transform.position);

However, I must say that ‘TryApplyForce()’ is attached to the damage, so that means I can’t simply just apply some force to the player to push him back a few steps… A way of saying “no damage dealt, but I need to move a few steps back…” (time to fix that too)

If you have any suggestions on how to make this better, I’m all ears. For now though, this works I guess :slight_smile: (later down the line, if I want advanced enemy AI, I just might do the same for the enemies as well…)

This one sentence really made all the difference for me

This will always return true…

This is the way

It looks like you’re getting a PlayerHealth and later getting an otherHealth… If other.tag==player, these will be the same. But this isn’t where we want to be testing for invulnerable anyways.
You really want to test this in Health

        public void TakeDamage(GameObject instigator, float damage)
        {
            if (isInvulnerable) return;
           //rest of method

Fighter shouldn’t even be concerned with whether or not the character is invulnerable, it just says "Hey, Health, I hit you THIS hard. Health then says, “Um, actually…” and declines the damage.

Then your knockback problem goes away, because Fighter.TryHit() will still try the knockback.

[if my questions sound silly, without going too deep into details, please understand my family is around me and I’m having an incredibly hard time focusing…]

I kept that code…

so do I eliminate the if statement, or just the first two lines of code, inside the ‘if’ statement? :sweat_smile:

every single time I place this in health, I get absolutely no sort of damage applied. Literally just the strike animation and impact play well, but absolutely no sort of damage is being dealt!

We can ignore that effect for now though. I honestly would’ve wanted a little bit of moving backwards, but it won’t break my game without it. As long as impact works when damage is applied (it’s still not randomized), and blocking works, then I’m not complaining :slight_smile:

My last question though, would this function be alright if I also want to implement an ‘EnemyBlockingState.cs’, where the enemy can block attacks as well? (It’s something I was thinking of as well…)


I’ve also been quietly trying to get the right hash for the block animations during combat (the request I had), but my approach relies on the type of weapon in hand rather than a string (because there’s a shield that has to be dealt with). Here’s what I tried doing:

// in 'PlayerBlockingState.cs'

    public string GetBlockHashName() 
    {
        string description = "";
        var playerFighter = GameObject.FindWithTag("Player").GetComponent<Fighter>();
        
        if (playerFighter.GetCurrentShieldConfig()) description = "ShieldBlock";
        if (playerFighter.GetCurrentWeaponConfig().IsTwoHanded) description = "2HLongBlock";
        if (playerFighter.GetCurrentWeaponConfig() != null && !playerFighter.GetCurrentWeaponConfig().IsTwoHanded) description = "1HBlock";

        return description;
    }

This function entirely replaces the “BlockHash” variable I had setup earlier (I wanted to just give them names and call it a day, but because there’s shields to consider as well, I was forced to create a function), and aims to call the right animation based on 3 things:

  1. Do you have a shield? Call the shield defence animation - HIGH PRIORITY
  2. If not, do you have a weapon? Call that weapon animation (if you got no shield) - LEAST PRIORITY
  3. Do you have a 2H Weapon? Call the 2H Defence Animation - TOP PRIORITY

What’s the problem here? The shield defence animation never gets called, it’s always the sword defence animation… It gets called under a different sequence of execution, but not this one

Anyway, I won’t throw anymore issues into this mess for now until these two are clean (although we can safely skip over the first one, unless there’s a “pushback a few steps when you’re hit” mechanism that won’t break my brain)

        if (playerFighter.GetCurrentShieldConfig()) description = "ShieldBlock";
        else if (playerFighter.GetCurrentWeaponConfig().IsTwoHanded) description = "2HLongBlock";
        else if (playerFighter.GetCurrentWeaponConfig() != null)  description = "1HBlock";

tried that before, no avail :sweat_smile: (now it’s the exact opposite. Everyone triggers the shield defence animation)

I’m guessing impact happens less and less the higher the level gets…?

^^^

OK so… I noticed something. Remember when we were creating the shield and we had that bug of an empty shield? Yup, that shield still “invisibly” exists. In other words, everytime we unequip the shield, we do it visually, but it’s never permanently gone. All we got is a “base shield” that acts as a placeholder, giving off the idea that it’s an empty hand, but it’s not (the base shield is invisibly sitting there). This is how I dealt with it, in ‘fighter.cs’ (back then):

        public void EquipShield (ShieldConfig shield) {
            DestroyOffHandItem();
            currentShield = shield;
            currentEquippedShield.value = AttachShield(shield);
        }

        private Shield AttachShield(ShieldConfig shield) {
            return shield.SpawnEquipableItem(rightHandTransform, leftHandTransform);
        }

        private void DestroyOffHandItem() {
            Shield offHandWeapon = leftHandTransform.GetComponentInChildren<Shield>();
            if (offHandWeapon == null) return;
            Destroy(offHandWeapon.gameObject);
        }

(what’s the point of ‘DestroyOffHandItem()’…?! Do we set the parent to null? Any additional steps we need to take to fix this…?!)

I noticed the issue because placing debuggers in this function (check below), everytime I play it after taking that shield off tells me I have a shield on (although I don’t). Once you put the shield on for the first time, the problem starts, and there’s no way back:

    public string GetBlockHashName()
    {
        string description = "";
        var playerFighter = GameObject.FindWithTag("Player").GetComponent<Fighter>();
        
        if (playerFighter.GetCurrentShieldConfig()) {
            Debug.Log("You have a shield in-hand");
            description = "ShieldBlock";
        }
        
        if (playerFighter.GetCurrentWeaponConfig().IsTwoHanded) {
        Debug.Log("You have a two-handed weapon");
        description = "2HLongBlock";
        }

        if (playerFighter.GetCurrentWeaponConfig() != null && !playerFighter.GetCurrentWeaponConfig().IsTwoHanded && playerFighter.GetBaseShield())
        {
            Debug.Log("You have a weapon, single-handed, and no shield, so 1-Handed Block");
            description = "1HBlock";
        }
        return description;
    }

The question now is, how do we put this into consideration when swapping between the animations? (and why does wielding weapons call the debug in ‘EquipShield()’?)

Seems to me we’re not null-checking the shield here… but this should be as simple as EquipShield(null),

        public void EquipShield (ShieldConfig shield) {
            DestroyOffHandItem();
            currentShield = shield;
            if(currentShield) currentEquippedShield.value = AttachShield(shield);
            else currentEquippedShield.value=null;
        }

and… that did solve a major and unforeseen problem, but it wasn’t the final bug solution yet (without a small tweak). You’d be surprised if I told you what was causing this major headache though (P.S: I solved it, at 2:29 AM… the usual time for me to solve major bugs :stuck_out_tongue: - my mind was racing so I couldn’t sleep (it’s a common thing for me…)). Here is the bug:

// in 'Fighter.cs':
    private void UpdateWeapon() {

        var weapon = equipment.GetItemInSlot(EquipLocation.Weapon) as WeaponConfig;
        var shield = equipment.GetItemInSlot(EquipLocation.Shield) as ShieldConfig;

        /* if (weapon == null) EquipWeapon(defaultWeapon);
        else EquipWeapon(weapon); */

        EquipWeapon(weapon != null ? weapon : defaultWeapon);
        // EquipShield(shield != null ? shield : baseShield); // THIS LINE
        EquipShield(shield != null ? shield : null);

    }

Where I wrote “THIS LINE” is where the bug was. As you may have noticed, if the shield was null, we were not equipping a “null” on. Instead, we were putting on the invisible “base” shield (the fictional solution we made to hide shields when 2h weapons were activated). Replacing it with a real null (i.e: make the hand empty) solves the problem :slight_smile:

and this is the final Block Hash Getter:

    public string GetBlockHashName()
    {
        string description = "";
        var playerFighter = GameObject.FindGameObjectWithTag("Player").GetComponent<Fighter>();

        if (playerFighter.GetCurrentWeaponConfig() != null && !playerFighter.GetCurrentWeaponConfig().IsTwoHanded && playerFighter.GetCurrentShieldConfig() == null)
        {
            Debug.Log("You have a weapon, single-handed, and no shield, so 1-Handed Block");
            description = "1HBlock";
            return description;
        }

        if (playerFighter.GetCurrentShieldConfig()) {
        Debug.Log("You have a shield in-hand");
        description = "ShieldBlock";
        }
        
        if (playerFighter.GetCurrentWeaponConfig().IsTwoHanded) {
        Debug.Log("You have a two-handed weapon");
        description = "2HLongBlock";
        }


        return description;
    }

I can’t find a single hand-cross animation to display a block event for hands-free weapons, so I’m skipping that for now (until I need it)

ANYWAY, I will need help with the next state, the dodging one because even in the Third Person Course I got that completely wrong, and it never worked for me… I’ll go watch the lectures again in the morning and keep you updated on where I get stuck :slight_smile: (I’ll do my best to minimize the requests as we speak)

JUST WAIT and I’ll do the dodging state up proper, but I have a couple sections to wrap up (throttling, then jumping, falling, then dodge, then a blocking state that will be much more realistic (because even blocking doesn’t mean you take no damage or have no consequences)

1 Like

alas, I see a roadmap :stuck_out_tongue: (Jumping, Falling and Blocking should be very similar to mine though, right? RIGHT? RIGHT?! - I don’t want to re-write the entire thing) :slight_smile: - hoping to see Throttling states soon though

Anyway if that’s the case, then I’ll go work on other major systems (honestly was kinda thinking of swimming states)

Swimming states I have no plans on doing, not on the roadmap, and once I’m done with what’s on the map, the saving system overhaul is next, and that will be a major task.

oweeee we aren’t seeing ranged attacks anytime soon :stuck_out_tongue: - I’ll try swimming alone and see what I can come up with :slight_smile: (it’s literally just an outward raycast, and animations to match)

Privacy & Terms