- Home /
How to get an AI enemy character to stop moving and freeze animation when the FPS camera is looking at it?
Hi, I'm trying to make enemies that are like the Weeping Angels from Doctor Who or the COD Nuketown Mannequins where they only move and animate when the FPS camera is on them (example: https://youtu.be/JnUPdKQohd0?t=103). Also would like to have them do the opposite, move/animate when the camera IS on them when the variable "ReverseOption" is set to true.
I've been stuck on my current code for a while and it seems like something is overriding my code to set the speed and animator to zero. It's managing to make the animations perform correctly but only when ReverseOption is set to false.
My enemy character object is a humanoid rigidbody that uses a navmesh, a modified version of the standard asset ThirdPersonCharacter.cs, PublicThirdPersonCharacter.cs where some methods are public and a custom script for an opposite of the Weeping Angel effect, LaughingDevilControl.cs
My modified ThirdPersonCharacter.cs script (where UpdateAnimator is set to public and there's a new method DeadStop() intended to stop all motion/animation):
using System;
using UnityEngine;
//namespace UnityStandardAssets.Characters.ThirdPerson
namespace UnityStandardAssets.Characters.PublicThirdPerson
{
[RequireComponent(typeof (UnityEngine.AI.NavMeshAgent))]
//[RequireComponent(typeof(ThirdPersonCharacter))]
[RequireComponent(typeof (PublicThirdPersonCharacter))]
public class LaughingDevilControl : MonoBehaviour
{
public UnityEngine.AI.NavMeshAgent agent { get; private set; } // the navmesh agent required for the path finding
//public ThirdPersonCharacter character { get; private set; } // the character we are controlling
public PublicThirdPersonCharacter character { get; private set; } // the character we are controlling
public Transform target; // target to aim for
//For detecting if within sight of player
public GameObject RayTargetObject;
bool InSight = false;
float fov = 60.0f;
private RaycastHit hit;
int outLayer = 0;
private Animator m_Animator;
private float OGSpeed;
public bool ReverseOption;
private void Start()
{
// get the components on the object we need ( should not be null due to require component so no need to check )
agent = GetComponentInChildren<UnityEngine.AI.NavMeshAgent>();
//character = GetComponent<ThirdPersonCharacter>();//oof
character = GetComponent<PublicThirdPersonCharacter>();//oof
agent.updateRotation = false;
agent.updatePosition = true;
m_Animator = gameObject.GetComponent<Animator>();
OGSpeed = m_Animator.speed;
}
bool LineOfSight(GameObject RayTargetObject)
{
/*if (Vector3.Angle(RayTargetObject.transform.position - transform.position, transform.forward) <= fov &&
Physics.Linecast(transform.position, RayTargetObject.transform.position, out hit) &&
hit.collider.transform == RayTargetObject.transform)
{*/
if (Vector3.Angle(transform.position - RayTargetObject.transform.position, RayTargetObject.transform.forward) <= fov &&
Physics.Linecast(RayTargetObject.transform.position, transform.position, out hit) &&
hit.collider.transform == transform)
{
//UnityEngine.Debug.Log("Enemy in sight of player");
return true;
}
//UnityEngine.Debug.Log("Enemy out of sight of player");
return false;
}
private void Update()
{
if (InSight != LineOfSight(RayTargetObject))
{
InSight = LineOfSight(RayTargetObject);
UnityEngine.Debug.Log("Enemy in sight of player::: " + InSight);
}
else
{
UnityEngine.Debug.Log("Enemy in sight of player:: " + InSight);
}
if (InSight == ReverseOption)
{
//m_Animator.speed = OGSpeed;
//m_Animator.speed = 1;
//gameObject.m_MoveSpeedMultiplier = 1;
if (target != null)
agent.SetDestination(target.position);
if (agent.remainingDistance > agent.stoppingDistance)
character.Move(agent.desiredVelocity, false, false);
else
character.Move(Vector3.zero, false, false);
}
else
{
character.Move(Vector3.zero, false, false);
character.UpdateAnimator(Vector3.zero);
character.DeadStop();
}
}
public void SetTarget(Transform target)
{
this.target = target;
}
}
}
My LaughingDevilControl.cs script, for making the character only move and animate when the camera is on them, or to do the opposite if ReverseOption is set to true:
using System; using UnityEngine;
//namespace UnityStandardAssets.Characters.ThirdPerson
namespace UnityStandardAssets.Characters.PublicThirdPerson
{
[RequireComponent(typeof (UnityEngine.AI.NavMeshAgent))]
//[RequireComponent(typeof(ThirdPersonCharacter))]
[RequireComponent(typeof (PublicThirdPersonCharacter))]
public class LaughingDevilControl : MonoBehaviour
{
public UnityEngine.AI.NavMeshAgent agent { get; private set; } // the navmesh agent required for the path finding
//public ThirdPersonCharacter character { get; private set; } // the character we are controlling
public PublicThirdPersonCharacter character { get; private set; } // the character we are controlling
public Transform target; // target to aim for, FirstPersonCharacter (transform)
//For detecting if within sight of player
public GameObject RayTargetObject; //FPS_Controller
bool InSight = false;
float fov = 60.0f;
private RaycastHit hit;
int outLayer = 0;
//for animation
private Animator m_Animator;
private float OGSpeed; //original speed
public bool ReverseOption; //if set to true, will act like Weeping Angels, where they will not move/animate if the camera is on them
private void Start()
{
// get the components on the object we need ( should not be null due to require component so no need to check )
agent = GetComponentInChildren<UnityEngine.AI.NavMeshAgent>();
//character = GetComponent<ThirdPersonCharacter>();//oof
character = GetComponent<PublicThirdPersonCharacter>();//oof
agent.updateRotation = false;
agent.updatePosition = true;
m_Animator = gameObject.GetComponent<Animator>();
OGSpeed = m_Animator.speed;
}
bool LineOfSight(GameObject RayTargetObject) //appears to be working correctly based off of debug printouts
{
/*if (Vector3.Angle(RayTargetObject.transform.position - transform.position, transform.forward) <= fov &&
Physics.Linecast(transform.position, RayTargetObject.transform.position, out hit) &&
hit.collider.transform == RayTargetObject.transform)
{*/
if (Vector3.Angle(transform.position - RayTargetObject.transform.position, RayTargetObject.transform.forward) <= fov &&
Physics.Linecast(RayTargetObject.transform.position, transform.position, out hit) &&
hit.collider.transform == transform)
{
//UnityEngine.Debug.Log("Enemy in sight of player");
return true;
}
//UnityEngine.Debug.Log("Enemy out of sight of player");
return false;
}
private void Update()
{
if (InSight != LineOfSight(RayTargetObject))
{
InSight = LineOfSight(RayTargetObject);
UnityEngine.Debug.Log("Enemy in sight of player::: " + InSight);
}
else
{
UnityEngine.Debug.Log("Enemy in sight of player:: " + InSight);
}
if (InSight == ReverseOption)
{
//these lines are overridden somewhere in PublicThirdPersonCharacter.cs
//m_Animator.speed = OGSpeed;
//m_Animator.speed = 1;
if (target != null)
agent.SetDestination(target.position);
if (agent.remainingDistance > agent.stoppingDistance)
character.Move(agent.desiredVelocity, false, false);
else
character.Move(Vector3.zero, false, false);
}
else
{
character.Move(Vector3.zero, false, false);
character.UpdateAnimator(Vector3.zero);
character.DeadStop();
}
}
public void SetTarget(Transform target)
{
this.target = target;
}
}
}
Answer by oStaiko · Sep 21, 2020 at 10:53 PM
For starters, Unity comes with multiple built-in solutions to see if an object is in view of a camera. What is probably the easiest to use is Renderer.isVisible, which will get you the quick boolean that you use, without having to manually check everything. Theres also a few Unity methods (like Update and Start) that are OnBecameVisible() and OnBecameInvisible(), however these only work if the script is on the same object as the renderer - if you're using multiple renderers per entity, consider creating an array of them and testing isVisible instead.
As for the issue with your animations not playing, I can't find where you are trying to get them to play in the first place? On line 90 of this first script you call
character.UpdateAnimator(Vector3.zero);
which I assume makes the animator stop, however I don't see you setting the value to start the animation again - it's always being set to 0. Unless there's another script that's setting this value, that might be your issue. As far as I can see, nothing else seems to be logically wrong with the script.
To clarify, the script I have for LineOfSight is working correctly and without the need for me to attach a renderer and the animations are changing correctly, the problem is the movement isn't affected by it. I'm trying to find out why the movement is still happening despite all of my scripts trying to tell it to stop.
Something is happening within the AI that restarts the animation without needing to manually restart it.
I tried changing the "agent.updatePosition" variable which has part of the desired effect but is really janky. Like the enemy will freeze in place with an animation frozen from far away, but all of the sudden show up very close to me with several skipped frames of animation. It seems to be related to how the Nav$$anonymous$$eshAgent works, like the Nav$$anonymous$$eshAgent is moving towards me the whole time the actual enemy is frozen and then updates once its visible (or invisible) again.
EDIT: Tried putting in a $$anonymous$$esh Renderer and using that for visibility instead. For some reason the enemy jumps behind me whenever I enter the stopping distance of the Nav $$anonymous$$esh Agent
I cant help much from what I see here, but if you want you can send me a zip and I can look through it for you?
Here's a link to the .zip character is just ahead of the FPS controller character when you start
https://drive.google.com/file/d/13fQABiNsZ$$anonymous$$RI1stPllFDHAgVxwvK0fc6/view?usp=sharing
Your answer
Follow this Question
Related Questions
How do I implement A* pathfinding to my 2d game, without tiles? 4 Answers
Looping Between Waypoints 1 Answer
Enemy chase script // No longer attacking 0 Answers
AI pathfinding waypoints 1 Answer