Enemies moving around Obstacles

I don’t know, probably the same thing as all your other walk animations

Repeat after me: (I believe I said this once before)
The only way to tell if something is blocking the shooter from shooting a target is a raycast.
The only way to tell if something is blocking the shooter from shooting a target is a raycast.
The only way to tell if something is blocking the shooter from shooting a target is a raycast.

Yep, they cost a bit in expense, but you’re only going to be doing the shot when you think you CAN do a shot… so… if you’re IN EnemyAttackState(), there’s no real reason to raycast, just in Chasing State.

Even in Chasing state, you only need to Raycast AFTER you’ve determined that

  • Any shooting cooldown has expired (no point in raycasting if you can’t shoot yet, right?)
  • Any Range calculation has succeeded (no point in raycasting if you’re too far a way, right?).

the variables are all over the place for this one (i.e: They’re all declared AFTER that code block), hence why I’m still thinking this one through. I’ll probably end up just re-writing and re-naming the variables

while (Bahaa.IsAlive()) 
{
Debug.Log($"The only way to tell if something is blocking the shooter from shooting a target is a raycast");
}

There. It’s on autopilot :stuck_out_tongue_winking_eye:

true, hence why it’s done after the ‘IsAggrevated()’ checker. The ‘IsAggrevated()’ checks for both cooldown and the existence of a LastAttacker. Combine that with the chase range, and you got a decent blocker for any unwanted calculations

that too. They’re both in consideration here:

        if (!IsInChaseRange() && !IsAggrevated())
        {
            stateMachine.TriggerOnPlayerOutOfChaseRange();
            stateMachine.SwitchState(new EnemyIdleState(stateMachine));
            return;
        }

        Vector3 lastPosition = stateMachine.transform.position;

        if (IsInAttackingRange())
        {
            // (TEST - 27/5/2024): Solution to ensure the enemy can move around Obstacles, if his enemy (Player/another NPC) is behind a wall of some sort:
            if (stateMachine.LastAttacker != null)
            {
                if (stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Ranged || stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Magic)
                {
                    Debug.Log($"{stateMachine.gameObject.name} is wielding a ranged weapon, and LastAttacker is {stateMachine.LastAttacker.gameObject.name}");
                    if (!HasLineOfSight())
                    {
                        Debug.Log($"{stateMachine.gameObject.name} has no line of sight of {stateMachine.LastAttacker.gameObject.name}");
                        while (!HasLineOfSight() && CanMoveTo(stateMachine.LastAttacker.transform.position))
                        {
                            Debug.Log($"{stateMachine.gameObject.name} is attempting to move to {stateMachine.LastAttacker.gameObject.name}");
                            // Find a way to get the animator to play the correct animation here, along with the proper facing direction
                            MoveToTarget(deltaTime);
                            return;
                        }
                           // not sure if here is the best spot, but when you have a line of sight to your last attacker, make the script holder face their target
                    }
                }
            }
// ... rest of my code

that’s the start of my ‘EnemyChasingState.Tick()’

However, because it’s a while loop that can go on forever, I ensured to do it once only and then return, and repeat the cycle until you find someone. Without that ‘return’, you’ll be restarting Unity in no time

My current problem is, I don’t know how to get the correct animation working, combined with facing the correct direction (for the direction part, I’ll steal some ‘FaceMovementDirection()’ code from ‘PlayerStateMachine.cs’ series), but for animations, this one is trouble-some because sometimes whatever I write works, and sometimes it doesn’t… It’s really random, and I’m a little confused here

I solved it. Here’s the final if statement that goes into ‘EnemyChasingState.Tick()’, which ended up working for my project’s special needs, if anyone wants to have a look:

            // Solution to ensure the enemy can move around Obstacles, if his enemy (Player/another NPC) is behind a wall of some sort:
            if (stateMachine.LastAttacker != null)
            {
                if (stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Ranged || stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Magic)
                {
                    Debug.Log($"{stateMachine.gameObject.name} is wielding a ranged weapon, and LastAttacker is {stateMachine.LastAttacker.gameObject.name}");
                    if (!HasLineOfSight())
                    {
                        // Play the movement animation towards your goal:
                        stateMachine.Animator.SetFloat(FreeLookSpeedHash, 1f, stateMachine.AnimatorDampTime, deltaTime);
                        Debug.Log($"{stateMachine.gameObject.name} has no line of sight of {stateMachine.LastAttacker.gameObject.name}");
                        // the direction changing and movement itself should happen consistently as we are moving, so they're done inside the following while loop:
                        while (!HasLineOfSight() && CanMoveTo(stateMachine.LastAttacker.transform.position))
                        {
                            Debug.Log($"{stateMachine.gameObject.name} is attempting to move to {stateMachine.LastAttacker.gameObject.name}");
                            Vector3 movementDirection = stateMachine.Agent.desiredVelocity.normalized;
                            FaceMovementDirection(movementDirection, deltaTime);
                            MoveToTarget(deltaTime);
                            return;
                        }
                    }
                }
            }

Mind you though, I cheated my way through the ‘SetFloat()’ function, as I used a float value of 1f instead of getting the grossSpeed, but the grossSpeed was giving me NaNs and essentially blocking the animation from playing, so I purposely took a shortcut just to call it a day

The logic is as follows:

  1. If you’re moving, you constantly need to update the direction and move to your target, hence why they’re taken care of in the innermost part of the while loop
  2. The animation is set before the innermost while loop to ensure you don’t end up constantly calling the variable (I think it was glitching for me when it was innermost or something)

Why is there a while loop? There shouldn’t be a while loop. It’s only running once, it should be if instead of while

1 Like

true, I fixed it. Thank you for bringing this up @bixarrio :smiley:

You can remove HasLineOfSight() from that if, too, because you checked it already.

I just left it there because it’s easier for me to read when reviewing my code in the future (I find it easier to read stuff in horizontal straight lines). Since we need to be in that condition anyway I don’t think it’ll hurt the code this much (if there’s any pitfalls though, I’ll delete it)

If HasLineOfSight() does a raycast it will because now you’re doing the same raycast twice.

Valid point, alright I deleted it :slight_smile:

The NPCs do struggle a little with very rough angles, and I will probably return to debugging this later

didn’t fix that yet… :confused:

still pending…

Hi @Bahaa,

I’m afraid I’m a bit confused. In your initial post, I read

However, in your last comment, I read

still pending…

If you still need help with a problem, it would be great if you could briefly explain what the problem is. Otherwise, people might feel lost. :slight_smile:

However, if your problem is solved, please mark an answer as the solution, so this thread will close itself automatically.

hey Nina. I thought the problem was solved, but in the end I noticed a very specific issue, after I moved on, but I didn’t have the time to get back to it just yet. Kindly leave the topic open, I’ll get back to it soon :smiley:

As soon as my project is back to a stable point, I’ll briefly mention exactly what the problem I have is (although getting to see it happen is a little hard, as there’s no specific pattern being followed)

If you are still working on the problem, we won’t close this topic, of course. If it does get closed for some reason, just message a moderator. We can reopen it for you if you still need it. :slight_smile:

1 Like

that’s amazing, than you Nina! :smiley:

This might serve better as a Talk topic. If you’d like, I can convert it to a Talk and we won’t have to worry about it being open ended in our Ask category.

fair enough. I’ll turn this into a talk, and when I’m ready to tackle the navigation issues behind this one, I’ll tag you here :smiley:

For now, let’s get back to the new Pickup System

OK so, as @Brian_Trotter probably checks what’s up with object pooling, I’m on a logical error bug-fixing spree right now. Instead of moving forward with new ideas, I figured I’d make sure every old idea I implemented works first.

Essentially, what my problem was, is that sometimes my NPCs get stuck between the terrain, maybe there’s something large they don’t know how to patrol their way around or what not, and they essentially just get stuck playing the movement animation in that position, even with NavMeshObstacles installed on that terrain.

What I want to do, is find a way (maybe a Coroutine that checks their position at the time) to get them to move around obstacles if they’re stuck, so they get to act natural again

Here’s my current ‘EnemyChasingState.cs’, if it helps:

using UnityEngine;
using RPG.Skills;
using UnityEngine.AI;
using System.Collections;

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

        private float attackingRange;

        public override void Enter()
        {
            Debug.Log($"{stateMachine.gameObject.name} has entered chasing state");
            stateMachine.Animator.CrossFadeInFixedTime(FreeLookBlendTreeHash, stateMachine.CrossFadeDuration);
            attackingRange = stateMachine.Fighter.GetAttackingRange();
            attackingRange *= attackingRange;
        }

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

            Vector3 lastPosition = stateMachine.transform.position;

            if (IsInAttackingRange())
            {
                if (stateMachine.LastAttacker != null)
                {
                    if (stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Ranged || stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Magic || stateMachine.Fighter.GetCurrentWeaponConfig().GetSkill() == Skill.Attack)
                    {
                        Debug.Log($"{stateMachine.gameObject.name} is wielding a weapon, and LastAttacker is {stateMachine.LastAttacker.gameObject.name}");
                        if (!HasLineOfSight())
                        {
                            stateMachine.Animator.SetFloat(FreeLookSpeedHash, 1f, stateMachine.AnimatorDampTime, deltaTime);
                            Debug.Log($"{stateMachine.gameObject.name} has no line of sight of {stateMachine.LastAttacker.gameObject.name}");

                            if (CanMoveTo(stateMachine.LastAttacker.transform.position))
                            {
                                Debug.Log($"{stateMachine.gameObject.name} is attempting to move to {stateMachine.LastAttacker.gameObject.name}");
                                Vector3 movementDirection = stateMachine.Agent.desiredVelocity.normalized;
                                FaceMovementDirection(movementDirection, deltaTime);
                                MoveToTarget(deltaTime);
                                return;
                            }
                        }
                    }
                }

                if (!stateMachine.CooldownTokenManager.HasCooldown("Attack"))
                {
                    Debug.Log($"{stateMachine.gameObject.name} is exiting chase state to attack state");
                    stateMachine.SwitchState(new EnemyAttackingState(stateMachine));
                    return;
                }
                else
                {
                    Move(deltaTime);
                    stateMachine.Animator.SetFloat(FreeLookSpeedHash, 0);
                    if (stateMachine.LastAttacker == null)
                    {
                        stateMachine.SwitchState(new EnemyIdleState(stateMachine));
                    }
                    else
                    {
                        FaceTarget(stateMachine.LastAttacker.transform.position, deltaTime);
                    }
                    return;
                }
            }
            else
            {
                MoveToTarget(deltaTime);
            }

            UpdateMovementAnimation(deltaTime, lastPosition);
        }

        public override void Exit()
        {
            if (!stateMachine.Agent.enabled)
            {
                stateMachine.Agent.enabled = true;
                stateMachine.Agent.ResetPath();
                stateMachine.Agent.velocity = Vector3.zero;
                stateMachine.Agent.enabled = false;
            }

            else
            {
                stateMachine.Agent.ResetPath();
                stateMachine.Agent.velocity = Vector3.zero;
            }
        }

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

        private void MoveToTarget(float deltaTime)
        {
            if (!stateMachine.Agent.enabled) stateMachine.Agent.enabled = true;

            stateMachine.Agent.destination = stateMachine.LastAttacker != null ? stateMachine.LastAttacker.transform.position : stateMachine.Player.transform.position;

            Vector3 desiredVelocity = stateMachine.Agent.desiredVelocity.normalized;
            
            stateMachine.Agent.velocity = desiredVelocity;
            stateMachine.Agent.nextPosition = stateMachine.transform.position;
            Move(desiredVelocity * stateMachine.MovementSpeed, deltaTime);            
        }

        private bool HasLineOfSight()
        {
            Debug.Log($"HasLineOfSight has been called");
            Ray ray = new Ray(stateMachine.transform.position + Vector3.up * 1.6f, stateMachine.LastAttacker.transform.position - stateMachine.transform.position);
            if (Physics.Raycast(ray, out RaycastHit hit, Vector3.Distance(stateMachine.transform.position, stateMachine.LastAttacker.transform.position), stateMachine.LayerMask))
            {
                return false;
            }
            return true;
        }

        private bool CanMoveTo(Vector3 destination)
        {
            var path = new NavMeshPath();
            var hasPath = NavMesh.CalculatePath(stateMachine.transform.position, destination, NavMesh.AllAreas, path);
            if (!hasPath) return false;
            if (path.status != NavMeshPathStatus.PathComplete) return false;
            if (GetPathLength(path) > stateMachine.maxNavPathLength) return false;
            return true;
        }

        private float GetPathLength(NavMeshPath path)
        {
            float total = 0;
            if (path.corners.Length < 2) return total;

            for (int i = 0; i < path.corners.Length - 1; i++)
            {
                total += Vector3.Distance(path.corners[i], path.corners[i + 1]);
            }
            return total;
        }

        private void UpdateMovementAnimation(float deltaTime, Vector3 lastPosition)
        {
            Vector3 deltaMovement = lastPosition - stateMachine.transform.position;
            float deltaMagnitude = deltaMovement.magnitude;

            if (deltaMagnitude > 0)
            {
                // if the game is not paused:
                FaceTarget(stateMachine.transform.position - deltaMovement, deltaTime);
                float grossSpeed = deltaMagnitude / deltaTime;
                stateMachine.Animator.SetFloat(FreeLookSpeedHash, grossSpeed / stateMachine.MovementSpeed, stateMachine.AnimatorDampTime, deltaTime);
            }

            else
            {
                // if the game is paused:
                FaceTarget(stateMachine.Player.transform.position, deltaTime);
                stateMachine.Animator.SetFloat(FreeLookSpeedHash, 1f);
            }
        }

        private void FaceMovementDirection(Vector3 forward, float deltaTime) 
        {
            if (forward == Vector3.zero) return;
            Quaternion desiredRotation = Quaternion.LookRotation(forward, Vector3.up);
            stateMachine.transform.rotation = Quaternion.Slerp(stateMachine.transform.rotation, desiredRotation, stateMachine.FreeLookRotationSpeed * deltaTime);
        }
    }
}

This script succeeds to a great extent when it comes to obstacle avoidance, but it doesn’t know how to find it’s way around obstacles if it gets stuck

Privacy & Terms