Merging the Third Person Controller Course with the RPG Course

        public void AddForce(Vector3 force, bool triggerKnockbackEvent = false)
        {
            impact += force;
            if (agent) agent.enabled = false;
            if(triggerKnockbackEvent) OnForceApplied?.Invoke(force);
            forceActive = true;
        }

We need to know f the force is active so we only invoke that OnForceCompleted once. Without it, the force never depletes and of course OnForceCompleted never invokes.

I have no idea how I missed that, but itā€™s fixed (for now, still testing it). Thank you again Brian for saving me :slight_smile:

By the way, considering the current state of the project, suppose I want to open it from another project name, computer or something else, what do I need to check for to get it to work? I just gave that a go (because I wanted to test something else), but that did not go as expectedā€¦ I canā€™t even get my controllers to work when the game runs for some reason (not on my main project, but on a copy I tried making out of it)

I imported all the necessary libraries for that (otherwise it wonā€™t run)

You could zip up the project folder (but remove the Library, obj and Temp folders from the zip) and unzip it in a new directory. (It should be that simple). Then you just add the project from disk in the Unity Hub.

Using source control, itā€™s even easier, you just clone the project using your git client, then add the project from disk in the Unity Hub.

SOOā€¦ Go to the unity project location, delete the temporary, library and object files and then unzip it wherever I want to take itā€¦ Got it!

Quick question by the way, for the randomization part of the script, when assigning ā€˜forceAmountā€™, did you mean ā€˜force.sqrMagnitudeā€™ on the Right Hand Side? Placing ā€˜forceAmountā€™ gives errors (for obvious reasonsā€¦)

Apart from that, the randomization isnā€™t working against the player, only the enemyā€¦ Iā€™m guessing it has to do with the mathematical formula, Iā€™m still trying to understand it :slight_smile:

  • ZIP the project
  • Remove the Library, obj, and temp folders from the zip
  • Go to a new directory
  • Unzip the project in that new directory

Yes, I think I might have typed the changes on that one in by hand rather than pasting from the repo.

1 Like

welp, will go investigate the randomizer a bit and see why my enemy canā€™t knock me back :slight_smile:

Not sure what went wrong, but my player literally never goes into impact state, but the enemy does so 100% of the timeā€¦ Any idea what went wrong?

Letā€™s see your Start() and HandleImpact in the PlayerStateMachineā€¦

void Start()
        {
            MainCameraTransform = Camera.main.transform;

            // The following check ensures that if we kill an enemy, save the game, quit and then return later, he is indeed dead
            // (the reason it's here is because 'RestoreState()' happens between 'Awake()' and 'Start()', so to counter for the delay, we do it in start too)
            if (Health.IsDead()) SwitchState(new PlayerDeathState(this));
            else SwitchState(new PlayerFreeLookState(this));

            Health.onDie.AddListener(() => {
                SwitchState(new PlayerDeathState(this));
            });
            Health.onResurrection.AddListener(() => {
                SwitchState(new PlayerFreeLookState(this));
            });

            ForceReceiver.OnForceApplied += HandleForceApplied;
        }

        private void HandleForceApplied(Vector3 force)
        {
            if (Health.IsDead()) return;
            SwitchState(new PlayerImpactState(this));
        }

I removed the randomizer in ā€˜HandleForceApplied()ā€™ for the moment, to see what went wrongā€¦

Seeā€¦ youā€™re asking why it didnā€™t randomize, and the two things I wanted to see were if the method was subscribed to and what your randomize code looked like.

The code I provided was merely an example anyways.

lol ironically enough, even without the randomizer my player was not going into impact state for some reasonā€¦

Anyway, here is my tuned attempt with the randomizer, for both the player and the enemy state machine:

PlayerStateMachine:

        private void HandleForceApplied(Vector3 force)
        {
            if (Health.IsDead()) return;
            float forceAmount = Random.Range(0f, force.sqrMagnitude);
            if (forceAmount > Random.Range(0f, SkillStore.GetSkillLevel(Skill.Attack))) // I created a getter for the SkillStore...
{
            SwitchState(new PlayerImpactState(this));
            }
        }

EnemyStateMachine:

    private void HandleForceApplied(Vector3 force) 
    {
        if (Health.IsDead()) return;
        float forceAmount = Random.Range(0f, force.sqrMagnitude);
        if (forceAmount > Random.Range(0f, BaseStats.GetLevel())) {
        SwitchState(new EnemyImpactState(this));
        }
    }

Whatā€™s concerning me though, is that the impact state never gets entered for the player even without a randomizer, albeit the code is asking it to do soā€¦

Edit: Through code debugging, the player enters the Impact state, but the animation is not playing for the player for some reasonā€¦ I checked the hash, all seems wellā€¦

Edit 2: Entering and Exiting the Impact state work perfectly fineā€¦ now Iā€™m going nuts about why in the world is the animation not playing!

Edit 3: LOOOOOOOOOOOOOOOOL I had ZERO Hit force applied to my first attack with the 1H-Sword, NO WONDER IT WASNā€™T WORKING!

the NavMeshAgent bug is back, but with a brand new error set (I checked the bake settings, all is good on that part):

"SetDestination" can only be called on an active agent that has been placed on a NavMesh.
UnityEngine.AI.NavMeshAgent:set_destination (UnityEngine.Vector3)
RPG.States.Enemies.EnemyChasingState:MoveToPlayer (single) (at Assets/Project Backup/Scripts/State Machines/Enemy/EnemyChasingState.cs:64)
RPG.States.Enemies.EnemyChasingState:Tick (single) (at Assets/Project Backup/Scripts/State Machines/Enemy/EnemyChasingState.cs:33)
RPG.States.StateMachine:Update () (at Assets/Project Backup/Scripts/State Machines/StateMachine.cs:18)

"ResetPath" can only be called on an active agent that has been placed on a NavMesh.
UnityEngine.StackTraceUtility:ExtractStackTrace ()
RPG.States.Enemies.EnemyChasingState:Exit () (at Assets/Project Backup/Scripts/State Machines/Enemy/EnemyChasingState.cs:53)
RPG.States.StateMachine:SwitchState (RPG.States.State) (at Assets/Project Backup/Scripts/State Machines/StateMachine.cs:11)
RPG.States.Enemies.EnemyChasingState:Tick (single) (at Assets/Project Backup/Scripts/State Machines/Enemy/EnemyChasingState.cs:28)
RPG.States.StateMachine:Update () (at Assets/Project Backup/Scripts/State Machines/StateMachine.cs:18)

This time, itā€™s all coming from the ā€˜EnemyChasingState.csā€™ - Iā€™ll try figure this one out before you see it :slight_smile:

almost forgot, hereā€™s the ā€˜EnemyChasingState.csā€™:

using UnityEngine;

namespace RPG.States.Enemies
{
    public class EnemyChasingState : EnemyBaseState
    {
        public EnemyChasingState(EnemyStateMachine stateMachine) : base(stateMachine) {}

        private float attackingRange;

        public override void Enter()
        {
            stateMachine.Animator.CrossFadeInFixedTime(FreeLookBlendTreeHash, stateMachine.CrossFadeDuration);
            attackingRange = stateMachine.Fighter.GetAttackingRange();
            attackingRange *= attackingRange;
        }

        public override void Tick(float deltaTime)
        {
            if (!IsInChaseRange()) 
            {
                stateMachine.SwitchState(new EnemyIdleState(stateMachine));
                return;
            }

            if (IsInAttackingRange()) 
            {
                stateMachine.SwitchState(new EnemyAttackingState(stateMachine));
                return;
            }

            Vector3 lastPosition = stateMachine.transform.position;
            MoveToPlayer(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(stateMachine.Player.transform.position, deltaTime);
            }

        }

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

        private bool IsInAttackingRange() 
        {
            return Vector3.SqrMagnitude(stateMachine.Player.transform.position - stateMachine.transform.position) <= attackingRange;
        }

        public void MoveToPlayer(float deltaTime) 
        {
            stateMachine.Agent.destination = stateMachine.Player.transform.position;
            Vector3 desiredVelocity = stateMachine.Agent.desiredVelocity.normalized;
            Move(desiredVelocity * stateMachine.MovementSpeed, deltaTime);
            stateMachine.Agent.velocity = stateMachine.CharacterController.velocity;
            stateMachine.Agent.nextPosition = stateMachine.transform.position;
        }
    }
}

Iā€™m genuinely confused as to how the enemy could be following the patrol path or chasing the player if itā€™s not on the NavMeshā€¦
Once again, are these red errors stacking up really fast or yellow warnings that donā€™t repeat until the next time?

red errors that stack up really fast (and he wonā€™t move beyond a specific differenceā€¦ so if heā€™s x meters away from the player, heā€™ll get stuck on his navmesh if he canā€™t find a way back. Iā€™m being extra careful with my Patrol points as well so that he doesnā€™t go nuts)

and heā€™s on a blue, nicely baked NavMesh by the way

Try this:

        public void MoveToPlayer(float deltaTime) 
        {
            if(!stateMachine.Agent.enabled) stateMachine.enabled=true;
            stateMachine.Agent.destination = stateMachine.Player.transform.position;
            Vector3 desiredVelocity = stateMachine.Agent.desiredVelocity.normalized;
            Move(desiredVelocity * stateMachine.MovementSpeed, deltaTime);
            stateMachine.Agent.velocity = stateMachine.CharacterController.velocity;
            stateMachine.Agent.nextPosition = stateMachine.transform.position;
        }

that was an easy and quick fix, and everything works well nowā€¦ (the red errors still pop up, but now he can actually chase me around the terrain with no issues, so all is good for the moment). Thank you again :slight_smile:

hello again Brian. For Tutorial number 37, regarding the timers, is this section purposely left empty, or is something missing?

That aside, there are only two scripts, the ā€˜CooldownTokenManager.csā€™ and the ā€˜EventPool.csā€™, right? Do those go on the player and enemies, orā€¦ where do they go?

Itā€™s not that itā€™s left empty, itā€™s that both ideas were conquered with the one section. Eventually, Iā€™m going to flesh out the whole thing and break down step by step how I got to the final script, but I ran out of time to work on writing this, so, realizing that where we are right now is sufficient for enemy throttling (the rest is almost all about eliminating Update and allowing UI to easily react to the timers). Iā€™ll eventually go back and finish the rest. For now, the finished classes are in place.

The CooldownTokenManager will go on both the Player and the Enemies and and will need to be added to the [field:SerializeField] like the rest of the components (weā€™ll cover that in the next section). The EventPool is not a MonoBehaviour, itā€™s a class thatā€™s used (three times!) in the CooldownTokenManager.

for now, while I can just do that right now, I think Iā€™ll wait for the next tutorial so as not to confuse things up

Privacy & Terms