- Home /
How to detect - as an enemy - if a fellow enemy next to you is in combat with the player?
Heya! At the moment I'm trying to optimize our game's enemy group-AI. The scenario is as follows:
The goal as a player is to pull an enemy from the group with a gun, and hope that the other enemies are smart enough to follow the enemy's lead, except the most northern enemy.
The game uses Unity's native navigation system, so as soon as the enemy takes damage (refers to the gameobject's Health-script), the enemy's boolean "inCombat" turns true and sets its destination towards the player (NavMeshAgent.SetDestination()).
I've tried to calculate each enemy's distance from each other with Vector3.Distance(). So far they are able to find each other's distance as a float in the world, but I'm still trying to find a way to figure out how the enemies could detect each other's inCombat-status.
TLDR: So far I'm able to pull one enemy at the time. I want the enemy to be able to detect if a close-by enemy is in combat. If it is, set inCombat to true and set destination towards the player. Question: How to do that?
public class EnemyMovement : MonoBehaviour
{
Transform player; // Reference to the player's position.
PlayerHealth playerHealth; // Reference to the player's health.
EnemyHealth enemyHealth; // Reference to this enemy's health.
NavMeshAgent nav; // Reference to the nav mesh agent.
float speed = 5f; // speed-variable used for rotating the enemy's rotation when close to the player
Animator anim; // Reference to the Animator.
Vector3 enemyInitialPos; // Enemy's initial position. Used for calculating enemy's max fighting distance (=How far can the enemy walk from its initial position, until it goes out of combat?)
float maxEnemyFightingRadius = 20f; //Enemy's maximum fighting distance, until out of combat.
float enemyFightingRadius; //Enemy's current fighting radius from its initial position.
Transform enemy; // used for calculating enemy's position.
public bool inCombat; //handles enemy's falling in and out of combat
public float aggroRange = 7f; //enemy's aggro range.
void Awake ()
{
// Set up the references.
player = GameObject.FindGameObjectWithTag ("Player").transform;
enemy = GameObject.FindGameObjectWithTag("Enemy").transform;
playerHealth = player.GetComponent <PlayerHealth> ();
enemyHealth = GetComponent <EnemyHealth> ();
nav = GetComponent <NavMeshAgent> ();
enemyHealth = GetComponent<EnemyHealth> ();
anim = GetComponent <Animator> ();
//enemy's position right now
enemyInitialPos = transform.position;
}
void Start ()
{
inCombat = false;
nav.speed = 3;
nav.stoppingDistance = 2f;
}
void Update ()
{
//calculates enemy's distance from another enemy
float enemyDistance = Vector3.Distance(enemy.position, enemyInitialPos);
Debug.Log (enemyDistance);
//calculates enemy's distance from its initial position to where it's right now
enemyFightingRadius = Vector3.Distance (transform.position, enemyInitialPos);
//variables for rotating the enemy towards the player, when close
float distance = Vector3.Distance (player.transform.position, transform.position);
Quaternion targetRotation = Quaternion.LookRotation (player.transform.position - transform.position);
//Just a way to rotate the enemy's rotation towards the player, when the player gets close to the enemy, since the NavMeshAgent rotates too slow.
if (distance < 2.5f && enemyHealth.currentHealth > 0) {
transform.rotation = Quaternion.Slerp (transform.rotation, targetRotation, speed * Time.deltaTime);
}
//-----------ENEMY'S MOVEMENT TOWARDS THE PLAYER----------
// If the enemy and the player have health left...
if (enemyHealth.currentHealth > 0 && playerHealth.currentHealth > 0) {
// ... set the destination of the nav mesh agent to the player.
//If the player goes too close to the enemy, the enemy becomes angry.
if (distance < aggroRange && inCombat == false) {
inCombat = true;
}
//If a nearby enemy is in combat, set inCombat to true and set its destination towards the player.
// if (enemyDistance < 6f/* && ...*/) { //obviously just checking the enemyDistance won't work, since a tightly grouped pack will go to combat instantaneously, and that's not what we want.
// inCombat = true;
// }
//Handles the animator-state
if (inCombat) {
anim.SetBool ("inCombat", true); //Running animation
} else if (inCombat == false) {
anim.SetBool ("inCombat", false); //Idle animation
}
//Handles the agent's movement towards the player
if (inCombat) {
nav.SetDestination (player.position);
}
if (enemyFightingRadius > maxEnemyFightingRadius) {
nav.Stop ();
enemyInitialPos = transform.position; // Recalculates the enemy's initial position.
inCombat = false;
}
if (inCombat == true) {
nav.Resume ();
}
}
// Otherwise...
else {
// ... disable the nav mesh agent.
nav.enabled = false;
}
}
}
Answer by ande92 · Mar 19, 2015 at 12:20 PM
I found a relatively viable solution. I use Physics.OverlapSphere() on the enemy while it's in combat, went through the array with while-loop, and initiated a "War Shout-like"-method on each enemy detected, which in turn put a nearby outOfCombat-enemy IN combat.
if (inCombat && enemyFightingRadius < maxEnemyFightingRadius) { // "enemyFightingRadius < maxEnemyFightingRadius" is essentially the enemy's max fighting range it can travel to, until returning to its initial position
Collider[] enemies = Physics.OverlapSphere (transform.position, shoutRadius, enemyMask);
int i = 0;
while (i < enemies.Length) {
enemies [i].SendMessage ("ToArmsMen");
i++;
}
}
The ToArmsMen() is just a method containing a "inCombat = true" variable.
Answer by AlwaysSunny · Mar 18, 2015 at 03:31 PM
The common way to go about this involves managerial oversight. When I first encountered the need for "group" decisions, I wanted a script which could respond to "events" involving two or more agents. This wound up being a flexible system that worked pretty well. I could ensure only X enemies ever engaged the player simultaneously, for instance, or get enemies to work in concert.
In your scenario, this might be overkill. You might be able to get away with having a "sensor" trigger on your agents (or a call to Physics.CheckSphere). A script on the agent polls the sensor to detect the status of nearby agents and react accordingly.
Thanks for the reply!
The Physics.CheckSphere() seems to be effective finding out different objects entering the sphere, but is there a way to ignore the gameobject that's calling it? Since I'm attaching the script - which calls the CheckSphere() - to the enemy, I don't need information that it found itself inside the sphere. (I guess it doesn't really matter in the end, but still...) I only need information if OTHER enemies are entering it. (I'm using a layer$$anonymous$$ask which only affects enemies though.)
Also, since I'm still new to C# and program$$anonymous$$g in general, I can't seem to find a way to actually detect other gameobject's combat status (=bool inCombat)? The actual bool is public so I can access it inside other scripts, but I have only 1 Enemy$$anonymous$$ovement-script that all the enemies share.
The main goal is still to do the following:
If the enemy is out of combat, call Physics.CheckSphere()
If the enemy detects another enemy inside the sphere WITH inCombat = true, change its inCombat to true.
So the actual detection of other gameobject's boolean status - while sharing the same script - seems to be difficult.
Any example code would be greatly appreciated!
Your answer
