Merging the Third Person Controller Course with the RPG Course

yup, that was the problem… If one day I decide to unlock my attack out of combat mode though, will this system have any issues I need to be aware of? (when it comes to ranged attacks, this will be something to think of)

I’ll find where it was and try replicate it in my player and keep it commented out, JUST IN CASE :slight_smile:

You’ll just add the same check in PlayerFreelookState before entering PlayerAttackingState, just like you did in PlayerTargetingState. It really is that easy.

OK so let me get this straight, because this one was a whacko job. The condition must be met in the Attack State introduced in ‘tick()’, right? I introduced it in the attack switch in ‘HandleAttackButtonPressed()’ as well (I really need to change that to ‘HandleInteractButtonPressed()’ instead, and set that to “E”…), and it was as good as pointless, but it worked when I added the condition to the state switch in’Tick()’

I can delete the condition check from the switch that happens in ‘HandleAttackButtonPress’, right? Apart from that, what’s the next step on this roadmap? :stuck_out_tongue:

        void HandleAttackButtonPressed()
        {
            if (stateMachine.Targeter.HasTargets)
            {
                if (!stateMachine.CooldownTokenManager.HasCooldown("Attack"))
                {
                    stateMachine.SwitchState(new PlayerAttackingState(stateMachine, 0));
                }
                return;
            }

            if (stateMachine.PickupFinder.HasTargets)
            {
                InputReader_HandlePickupEvent();
                if (stateMachine.PickupFinder.CurrentTarget) return;
            }

            if (stateMachine.ConversantFinder.HasTargets)
            {
                InputReader_HandleDialogueEvent();
                if (stateMachine.ConversantFinder.CurrentTarget) return;
            }
            stateMachine.SwitchState(new PlayerAttackingState(stateMachine, 0));
        }

that didn’t work either… It’s all good though, as long as the one in ‘Tick()’ works. I don’t think they make a big difference :slight_smile:

and it still baffles me why we have two switches to the attack in ‘HandleAttackButtonPressed()’, and I’m guessing that’s why it’s not working (the switch ignores the check)…

Anyway, day 1 of asking Brian what the next topic will be :stuck_out_tongue:

https://community.gamedev.tv/t/more-states/242327/17?u=edc237

Brian already told us what the roadmap looks like. I believe that jumping is next.

My impatience got me to work on these solo… I’m probably trying ranged next, mainly because mine is a little more complex than the tutorials… :skull:

I just need help with my construction system above anything else first

Hey, just a heads up for lesson 38
anyone trying to get the TargetingBlendTree switch, your enemy will slide instead of walk forward if you move your player away from the enemy.

In EnemyChaseState.cs
I added

public readonly int TargetingForwardSpeedHash = Animator.StringToHash("TargetingForwardSpeed");

and switched from FreeLookSpeedHash to TargetingForwardSpeedHash in the tick method.
Now the enemies will move in targeting mode right from the beginning (and not run / target based on distance, which is the next step perhaps), but it does look better than the slide.

My heart skipped a HUGE beat reading this. I got incredibly excited… Development was on hold for a bit because of this :sweat_smile:!

oh I thought I was the one going crazy because his enemy was sliding as he was patrolling… :sweat_smile: - @Brian_Trotter please address this for us, since it turns out it’s not just my fault :smiley:

In my case at least, he slides when he’s patrolling, but walks like normal again when I attack him and get him into combat mode, and he starts pursuing me.

If it helps, here is my ‘EnemyPatrolState.cs’ script:

using RPG.Control;
using UnityEngine;

namespace RPG.States.Enemies 
{
    public class EnemyPatrolState : EnemyBaseState 
    {
        private const string NextPatrolPointIndexKey = "NextPatrolPointIndex";

        public EnemyPatrolState(EnemyStateMachine stateMachine) : base(stateMachine) {}

        private float movementSpeed = 0.5f;
        private float acceptanceRadius = 2f;
        private float dwellTime = 2f;
        private Vector3 targetPatrolPoint;

        public override void Enter()
        {
            if (stateMachine.PatrolPath == null) 
            {
                stateMachine.SwitchState(new EnemyIdleState(stateMachine));
                return;
            }

            int index;
            // Check blackboard for key, set index if key is set
            if (stateMachine.Blackboard.ContainsKey(NextPatrolPointIndexKey)) 
            {
                index = stateMachine.Blackboard.GetValueAsInt(NextPatrolPointIndexKey);
            }

            else 
            {
                // The first time we enter a Patrol state, the index will not be set in the blackboard
                // So we get it from the PatrolPath's GetNearestIndex
                // We will also be resetting the index if the enemy goes into 'EnemyChasingState.cs'
                index = stateMachine.PatrolPath.GetNearestIndex(stateMachine.transform.position);
            }

            // Set our goal
            targetPatrolPoint = stateMachine.PatrolPath.GetWaypoint(index);
            PatrolPoint patrolPoint = stateMachine.PatrolPath.GetPatrolPoint(index);

            if (patrolPoint) // If the current index has a PatrolPoint attached, then we can adjust the settings from the default
            {
                movementSpeed = stateMachine.MovementSpeed * patrolPoint.SpeedModifier;
                acceptanceRadius = patrolPoint.AcceptanceRadius;
                dwellTime = patrolPoint.DwellTime;
            }

            else // if not, calculate the movementSpeed as a percentage of the stateMachine's movement speed
            {
                movementSpeed *= stateMachine.MovementSpeed;
            }

            // Squaring the acceptanceRadius (to save calculation time when we use 'SqrMagnitude')
            acceptanceRadius *= acceptanceRadius;
            // next waypoint index setup
            stateMachine.Blackboard[NextPatrolPointIndexKey] = stateMachine.PatrolPath.GetNextIndex(index);
            // Since the waypoint won't move, set the destination here on the Agent
            stateMachine.Agent.SetDestination(targetPatrolPoint);
            // Set the animation
            stateMachine.Animator.CrossFadeInFixedTime(FreeLookBlendTreeHash, stateMachine.CrossFadeDuration);

        }

        public override void Tick(float deltaTime)
        {
            if (IsInChaseRange() && IsAggro()) // if you want to consider the optional aggregation, add "&& IsAggro()" to the if statement of this line
            {

                // if you want the vision system to work, uncomment the if statement below:

                // if (CanSeePlayer()) {
                // Clearing key to ensure that at the end of the battle, the enemy finds the nearest waypoint
                stateMachine.Blackboard.Remove(NextPatrolPointIndexKey);
                stateMachine.SwitchState(new EnemyChasingState(stateMachine));
                return;
                // }
            }

            if (IsInAcceptanceRange()) 
            {
                // Once we're close enough to the waypoint, we head to a Dwell state
                stateMachine.SwitchState(new EnemyDwellState(stateMachine, dwellTime));
                return;
            }

            // Chasing Code
            Vector3 lastPosition = stateMachine.transform.position;
            MoveToWayPoint(deltaTime);
            Vector3 deltaMovement = lastPosition - stateMachine.transform.position;
            float deltaMagnitude = deltaMovement.magnitude;
            float grossSpeed = deltaMagnitude / deltaTime;

            stateMachine.Animator.SetFloat(FreeLookSpeedHash, grossSpeed / stateMachine.MovementSpeed, stateMachine.AnimatorDampTime, deltaTime);
            if (deltaMagnitude > 0) 
            {
                FaceTarget(stateMachine.transform.position - deltaMovement, deltaTime);
            }
            else 
            {
                FaceTarget(targetPatrolPoint, deltaTime);
            }

        }

        private bool IsInAcceptanceRange() 
        {
            return (stateMachine.transform.position - targetPatrolPoint).sqrMagnitude < acceptanceRadius;
        }

        public override void Exit()
        {
            stateMachine.Agent.ResetPath();
            stateMachine.Agent.velocity = Vector3.zero;
        }

        void MoveToWayPoint(float deltaTime) 
        {
            Vector3 direction = stateMachine.Agent.desiredVelocity.normalized;
            Move(direction * movementSpeed, deltaTime);
            stateMachine.Agent.velocity = stateMachine.CharacterController.velocity;
            stateMachine.Agent.nextPosition = stateMachine.transform.position;
        }
    }
}

Well we didn’t really touch anything to do with Patrolling state in either of these lectures.

My patrol state is the same as it was. Little bit of sliding (import settings / speed mismatch), but not like the one in the error which I mentioned earlier, where the walking animation wasn’t playing at all and the enemy was moving in idle (same happens in the git project).

I had that issue for a while to be honest, even in my swimming state (I developed my own swimming state for my player because, well… why not? And it’s kinda fun to be honest, but I implemented a trick that introduces a random, occasional bug that’s been bothering me for a while (the price I’m paying for building that thing… :sweat_smile:)). From experience working on that, I found that squaring the value that calls it (the second parameter in ‘Animator.SetFloat(…)’) gets the number closer to what the threshold has, usually eliminating the problem…

What a very silly mistake! Thanks for catching that!

1 Like

There’s always a little sliding happening, even with the added calculations to determine the true speed of the character. Ideally, you need to know the exact speed that any given animation moves per second, and that will vary from animation to animation. It’s relatively easy to tune the movement speed for one animation, it gets a bit more complex when you have to calculate movement speeds for 12 of them (and that’s just three weapons)…

I may explore in a future lecture the concept of storing the speeds needed to calculate movement in the WeaponConfig (this is the most logical place for them as Freelook and Targeting animation speeds vary most based on the current weapon) in a future tutorial.

1 Like

lol mine isn’t a “little sliding”… mine is DEFAULT SLIDING, and if you’re lucky, you’ll see him walk :man_shrugging: (OK seriously, where do I start fine-tuning to fix that? The thresholds or the code…?!)

On a serious note, please have a look at this. I’m not sure if the error is there or not, but I think it’s a good starting point:

and please check your messages. I almost forgot I asked a question about Synty’s materials, and color changing :slight_smile:

Edit: I removed the asterisk from this line in ‘EnemyPatrolState.Enter()’, and that fixed the problem:

movementSpeed *= stateMachine.MovementSpeed; // just delete the asterisk sign (the '*' sign)

Apart from the occasional hiccups where he plays no animations and just slides. This is usually fixed by exiting the game scene to the main menu and then coming back later

now we can have a look at this, until bixarrio comes back online :slight_smile:

question. Why don’t we add a bit of camera vibration when we unleash an attack? The value of how strong that attack would be would rely on how much damage we have done, and for now we can make it for swords-only (for ranged weapons, that would be an explosion for specific explosive arrows). It’s basically something that strongly indicates that we did a strong attack. Considering the name of the next section as “Polish”, I figured I’d ask about that :slight_smile:

And how do you stabilize a freeLook camera when you’re moving at high speed?

I’m not planning a specific lecture on camera shake, as this has been covered in a lot of other areas. I’ll give you a hint, though… Cinemachine Impulse Listener | Cinemachine | 2.9.7

1 Like

Hello again. OK so… there are a few things that I am struggling to fix:

  1. The enemy won’t patrol for some reason. I made a copy off my working enemy, but this one is stubborn and just won’t patrol. Nothing changed, but I can’t get a patrol path from the scene itself on his prefab (I’m guessing that’s the problem), and unsurprisingly, he won’t move… Is there any other steps I missed out on to fix this? (and when he respawns, he’s… standing airborne to say the least :sweat_smile: - he’s basically not taking any type of gravity). He won’t attack, patrol, get impacted on hits, or play any death animations… Something is completely off here

  2. It’s an Out-of-context problem. I’ll leave it at the very end (but I do need your help addressing it)

  3. For now, it’s perfectly fine that the arrow won’t hit anything beyond a few centimeters, right?

  4. We shall address the experience gain in the future as well, right?

I followed the tutorial exactly as is, but these 4 issues still exist… I’m guessing at least 1,3 and 4 (I will address 2 later. It’s a shame to have a brilliant system like a quiver and leave it out to be honest) will be addressed soon, right?

I also have to mention that although the lecture did not mention adding the ‘Shoot()’ event (or at least I didn’t catch it), I placed it on the animation myself


  1. I had a Quiver System that I introduced in the past, and I was trying to integrate it into my game. Frankly speaking, I have a bit of a funny approach to this one, and I’d like some help on it

So, to begin with, I managed to get my player to lose a single arrow per hit, which means ultimately he will run out of arrows, using my own Quiver system, as follows (this is what my ‘Shoot()’ function looks like after implementing it):

void Shoot() {

        // In very simple terms, this function is the 'Hit()' function for 
        // Ranged (Projectile) Weapons, to be called in 'Projectile.cs' (for simplicity in naming's sake)
        // Hit();

        // RPG to Third Person Conversion change:
        if (!currentWeaponConfig.HasProjectile()) return;

        if (TryGetComponent(out ITargetProvider targetProvider)) 
        {
            float damage = GetDamage();
            GameObject targetObject = targetProvider.GetTarget();

            if (targetObject != null) 
            {
                // use the 'LaunchProjectile' function with 5 arguments (as we do have a target)
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, targetObject.GetComponent<Health>(), gameObject, damage);
            }
            else 
            {
                // use the 'LaunchProjectile' function with 4 arguments (as we don't have a target)
                currentWeaponConfig.LaunchProjectile(rightHandTransform, leftHandTransform, gameObject, damage);
            }

                // (Arrow Deduction here) If you have enough ammo, fire and use the ammo
                if (currentWeaponConfig.GetAmmunitionItem())
                {
                    GetComponent<Quiver>().SpendAmmo(1);
                }
            }
        }

However, I also wanted him to be able to take the bow off his hands (like I did before) if you’re out of ammo, and you attempt to attack something or someone, so I introduced these 2 functions:

    void CheckAmmoQuantity() 
    {
        // If the weapon demands ammo (ranged weapon), but you don't have enough ammo, unequip the weapon and go fight with your hands
        if (currentWeaponConfig.GetAmmunitionItem() && !GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem()))
        {
            UnequipRangedWeapon();
        }
    }

the function above is assigned as an animation event, played only at the start of the bow-firing animation

and this:

    private void UnequipRangedWeapon()
    {
            if (!inventory.HasSpaceFor(currentWeaponConfig)) return;
            GetComponent<Fighter>().GetComponent<Inventory>().AddToFirstEmptySlot(currentWeaponConfig, 1, true);
            GetComponent<Fighter>().GetComponent<Equipment>().RemoveItem(EquipLocation.Weapon);
    }

This function unwields the ranged weapon basically

However, the last step I want to do is to avoid playing the ‘Bow’ animation entirely if the player doesn’t have any ammo on him, and the ‘CheckAmmoQuantity()’ function turns out as true (no clue why I didn’t make it a boolean tbh…), and instead punch whoever is ahead of him (you’re out of ammo, and you have nothing in your hand by default)


[UNNECESSARY, BUT YOU MIGHT FIND THIS HELPFUL]

To help out though, I also found this function I once created, but I don’t recall what it does:

bool CheckForProjectileAndSufficientAmmo() {

        // Step 1: If the weapon doesn't have a projectile (swords or basically any melee weapons), you're good
        // Step 2: If the weapon doesn't use Ammunition (maybe an infinity staff or some sort of infinite projectile), you're good
        // Step 3: If you have enough ammo for the right weapon you're holding, you're good
        // Step 4: If you have ammo, but for the wrong weapon, then unequip your weapon and rush to hand-to-hand combat (you're NOT good)

        // Step 1:
        if (!currentWeaponConfig.HasProjectile()) {
            return true;
            }
        // Step 2:
        if (currentWeaponConfig.GetAmmunitionItem() == null) {
            return true;
        }
        // Step 3:
        if (GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem())) {
            return true;
        }
        // Step 4:
        if (currentWeaponConfig.GetAmmunitionItem() && !GetComponent<Quiver>().HasSufficientAmmunition(currentWeaponConfig.GetAmmunitionItem())) {
            // If you want to keep pointing at your player without attacking them because of insufficient/wrong ammo, delete 'UnequipRangedWeapon()' below:
            UnequipRangedWeapon();
            return false;
        }

        // Anything else is just false:
        return false;

    }

Yes, assigning something from the hierarchy to a prefab is not supposed to work. I thought you and Brian had addressed this in the respawn topic.

I have a Respawn Manager in my own scene… that’s what makes things harder, because these enemies are never on the scene, they are just instantiated

Privacy & Terms