Hello, I am trying to make a fence and have an ai patrol it, currently when the ai detects an enemy, it makes to a shooting distance and then attacks, this is fine. The issue I seem to be having though is that when it detects an enemy it attempts to move around the fence even if the enemy is close, rather than shoot it through the fence which I am wanting it to do. Is there a way to make the ai not want to move around the fence which has an navmesh obstacle attached. Many thanks for the help.
I'm guessing you are checking the navMeshAgent.remainingDistance to determine whether or not you will stop and fire. If you are able to shoot through fences, then you should do an actual distance comparison between the unit and the target(Vector3.Distance), as well as a line of sight check (To check for actual, non-fence obstacles that would block a bullet). If both of those pass, stop and fire - otherwise, navigate around the fence so that you can get closer.
Make sure your fence is low enough or does not collide with the raycast used for the player visibility test. Patrolling, navigating and shooting the target on sight is already a complex AI. I don't know how your AI is currently implemented but you might be interested in Behaviour Tree, its a technique that simplifies the design of AI. I would suggest to have a look at Panda BT, it's a script-based Behaviour Tree engine: http://www.pandabehaviour.com I'm the author of this package, if you have any question, I'd be glad to help.
We can't really give you much technical advice without seeing what code you already have set up that's making that reaction. Can you please post it using the Code tags?
I'm using a mixture of setDestination and stoppingDistance to make the ai move, I have moved the collider down already so that the raycast can see over it, however the ai seems to still want to move around the carved object. This is the code I'm using: Code (CSharp): using UnityEngine; using System.Collections; public class AI_Sight : MonoBehaviour { private NavMeshAgent agent; private AI_Stats myAI_Stats; private AI_Attack myAI_Attack; private float height = 1f; public float sightDist; public float approachSpeed = 2f; RaycastHit hit; void Start() { agent = GetComponent<NavMeshAgent>(); myAI_Stats = GetComponent<AI_Stats>(); myAI_Attack = GetComponent<AI_Attack>(); } void Update() { if (Physics.Raycast(transform.position + Vector3.up * height, transform.forward, out hit, sightDist)) { Attack(); } if (Physics.Raycast(transform.position + Vector3.up * height, Quaternion.AngleAxis(5, transform.up) * transform.forward, out hit, sightDist)) { Attack(); } if (Physics.Raycast(transform.position + Vector3.up * height, Quaternion.AngleAxis(-5, transform.up) * transform.forward, out hit, sightDist)) { Attack(); } if (Physics.Raycast(transform.position + Vector3.up * height, Quaternion.AngleAxis(10, transform.up) * transform.forward, out hit, sightDist)) { Attack(); } if (Physics.Raycast(transform.position + Vector3.up * height, Quaternion.AngleAxis(-10, transform.up) * transform.forward, out hit, sightDist)) { Attack(); } if (Physics.Raycast(transform.position + Vector3.up * height, Quaternion.AngleAxis(15, transform.up) * transform.forward, out hit, sightDist)) { Attack(); } if (Physics.Raycast(transform.position + Vector3.up * height, Quaternion.AngleAxis(-15, transform.up) * transform.forward, out hit, sightDist)) { Attack(); } if (Physics.Raycast(transform.position + Vector3.up * height, Quaternion.AngleAxis(20, transform.up) * transform.forward, out hit, sightDist)) { Attack(); } } void Attack() { if (hit.transform.gameObject.tag != transform.gameObject.tag) { if (hit.transform.gameObject.tag == "Raider") { agent.transform.LookAt(hit.collider.gameObject.transform.localPosition); agent.SetDestination(hit.collider.gameObject.transform.localPosition); agent.stoppingDistance = (myAI_Attack.attackDist - 0.5f); agent.speed = approachSpeed; } if (hit.transform.gameObject.tag == "RaiderLeader") { agent.transform.LookAt(hit.collider.gameObject.transform.localPosition); agent.SetDestination(hit.collider.gameObject.transform.localPosition); agent.stoppingDistance = (myAI_Attack.attackDist - 0.5f); agent.speed = approachSpeed; } } } }
stoppingDistance is still going to be a distance along the movement path; if you want the attack to be "line of sight" (shooting) rather than "distance along movement path" (which would be more appropriate for melee) you can't rely on that to ensure the attacker is going to stop. If you want line of sight distance and not distance along movement path you're looking at a simple "distance" check (Vector3.Distance(..) etc.)
As @LeftyRighty suggested you would need to implement a IsPlayerVisible method. Also your AI logic would be much easier to manage as a Behaviour Tree. If you are wondering what a behaviour tree looks like, your description above translated into a BT script would be: Code (CSharp): // Do the first successfull action: Attack, Chase or Patrol tree "Root" fallback tree "Attack" tree "ChasePlayer" tree "Patrol" // Attack the player if within attack range. tree "Attack" while IsPlayerWithinAttackRange sequence AimAt_Player Shoot // Chase the player if visible. tree "ChasePlayer" while IsPlayerVisible sequence MoveTo_PlayerPosition // Patrol along a given path made of waypoints. tree "Patrol" while sequence // stop Patroling when the player is visible or attackable not IsPlayerVisible not IsPlayerWithinAttackRange sequence repeat MoveTo_NextPatrolWaypoint For inspiration, the Panda BT package contains an example named "Shooter" that demonstrates behaviours such as: Patrolling, Chasing, Shooting, Moving To Cover, ... This technique might look a bit unusual, but it will save you a lot of troubles and debugging time once you'll get a grip on it. If you have any question, please ask.
I've edited my sight to this: Code (CSharp): agent.transform.LookAt(hit.collider.gameObject.transform.localPosition); if (Vector3.Distance(gameObject.transform.position, hit.collider.gameObject.transform.localPosition) <= (myAI_Attack.attackDist - 0.5f)) { agent.destination = gameObject.transform.position; } else agent.destination = (hit.collider.gameObject.transform.localPosition); agent.speed = approachSpeed ; This seems to allow my ai to look over obstacles whilst also moving to enemies, does this seem like it will create any issues? Also I have been wanting to make an AI behavior tree but haven't find many good examples to work off from online that explains it well, any tips or advice on how it works? How to implement the scripts I current have into it? so on... Thanks a lot for the help.
First, you have to split your script into simple tasks such as: SetDestination_*, MoveToDestination, IsPlayerVisible, IsPlayerWithinAttackRange. Once you have defined these tasks you can use them as building blocks to build up the behaviour tree of your AI. Let's say you want to implement the following tasks: SetDestination_PlayerPosition, MoveToDestination, IsPlayerVisiblie. Then your MonoBehaviour script will become: Code (CSharp): using UnityEngine; using Panda; // We are using Panda BT. public class UnitAI : MonoBehaviour { private NavMeshAgent agent; public Vector3 destination; public Transform player; public float fieldOfView = 45.0f; public float viewRange = 30.0f; [Task] // <-- Marks a method as a task usable from a BT Script. void SetDestination_PlayerPosition() { destination = player.position; Task.current.Succeed(); } [Task] void MoveTo_Destination() { if( Task.current.isStarting ) { // The task has just started, set the destination and make the unit move. agent.destination = destination; agent.Resume(); } if( agent.remainingDistance < 0.5f) { // We have reached the destination, we are done with this task. Task.current.Succeed(); } } [Task] bool IsPlayerVisible() { // Get some information about the player relative location. Vector3 displacemementToPlayer = player.position - this.transform.position; Vector3 playerDirection = displacemementToPlayer.normalized; float distanceToPlayer = displacemementToPlayer.magnitude; float angle = Vector3.Angle(this.transform.forward, playerDirection); // Test whether the player is withing FoV and view range. bool isVisible = (angle < fieldOfView) && (distanceToPlayer < viewRange); // TODO: Use raycasting to test whether player is occluded by walls or obstacle. return isVisible; } } In general, implementing a task in C# consists of indicating when the taks is completed ( either in success or in failure). The task method is called once per frame while it is running. Then we can used these tasks from a BT script to define, for example the ChasePlayer sub-tree: Code (CSharp): // Chase the player if visible. tree "ChasePlayer" while IsPlayerVisible sequence SetDestination_PlayerPosition MoveTo_Destination BT scripts are executed through the PandaBehaviour component, which you attached on a GameObject you want to drive by a behaviour tree. Does this make sens to you, or is there something that is not clear enough? Just tell me, I'll do my best to clarify.
Panda BT is a very simple language, it contains only 10 keywords: tree, sequence, fallback, parallel, race, random, repeat, while, not, mute (yep, that's all of them.) Despite the few number of keywords, a lot can be done using them, it's really powerful. In fact I don't know any behaviour that could not be described with them (If you know any, please let me know!). If you want to have a grasp about these keywords (what they do and how they can be used), you can play a bit with this demo: http://www.pandabehaviour.com/?page_id=236 Or course, you can always check the documentation: http://www.pandabehaviour.com/?page_id=23#Structural_nodes