- Home /
Making a compass point towards the closest enemy
We have been making a pirate game, and to make it easier for the player to find the enemies, we plan to have a part of a compass point towards the closest enemy. To do that, we have placed a small cube (designated "Pointer") to the player's ship, as well as a compass on the canvas. However, we have bot been able to make the code calculate and choose which enemy in an array is closest. Here is the code in C#:
using UnityEngine;
using System.Collections;
public class Compass_Script : MonoBehaviour
{
public GameObject compassUnder;
public GameObject compassOver;
public GameObject playerShip;
public GameObject pointer;
public GameObject enemyShip;
public GameObject camera;
public float angle;
public float offset = 180;
private GameObject[] enemies;
public float[] distanceToTarget;
public GameObject target;
public Vector3[] dis;
public float[] a;
// Use this for initialization
void Start()
{
enemies = GameObject.FindGameObjectsWithTag("enemy");
enemyShip = enemies[1];
}
// Update is called once per frame
void Update()
{
targeting();
compassUnder.transform.localEulerAngles = new Vector3(0.0f, 0.0f, +playerShip.transform.localEulerAngles.y - camera.transform.localEulerAngles.y - 180);
pointer.transform.LookAt(enemyShip.transform.position);
if (Vector3.Dot(pointer.transform.forward, camera.transform.right) > 0.0f)
{
angle = Vector3.Angle(camera.transform.forward, pointer.transform.forward) * (-1.0f);
}
else
{
angle = Vector3.Angle(camera.transform.forward, pointer.transform.forward);
}
compassOver.transform.localEulerAngles = new Vector3(0.0f, 0.0f, (angle - compassUnder.transform.localEulerAngles.z));
}
private void targeting()
{
for(int i = 0; i >= enemies.Length;)
{
distanceToTarget[i] = Vector3.Distance(GameObject.Find("pointer").transform.position, enemies[i].transform.position);
//Vector3.Distance(GameObject.Find("Pointer").transform.position, enemies[i].transform.position).GetHashCode;
i++;
}
}
private void rulerDis()
{
for(int i = 0; i >= distanceToTarget[i];)
{
for(int j = 0; j >= distanceToTarget[j];)
{
a[j] = (distanceToTarget[j] - distanceToTarget[j+1]);
}
if(a[i] < a[i+1])
{
enemyShip = enemies[i];
Debug.Log("enemy is " + i);
}
}/*catch()
{
Debug.Log("Failed to compare");
}*/
}
private void worked()
{
Debug.Log("it worked");
}
}
The important thing for us is to make the pointer choose the closest enemy to face, and change if another enemy comes closer.
We thank your for your help and interest.
Just skimmed through your code. Line 50: (i >= enemies.Length) seems a little bit odd to me!
Answer by sidestepper · Jun 10, 2015 at 08:54 AM
Assuming the issue is not wanting to iterate every enemy and run a distance formula every frame, which I can totally understand as unwanted. Here's a couple ideas to limit/restrict your calculating, but regardless of how you do it, an easy speed benefit could be gained by running a distance squared check instead of a distance check as it should be considerably faster since it doesn't require a square root operation.
1) Easiest: If there is a maximum range for the compass - you could add a large sphere collider around the player with a radius of max compass range. Set this collider to a trigger and add a behaviour that adds and removes enemies from a list as they enter/exit the trigger. Then you can just iterate over all enemies currently managed in the list rather than all enemies active in the scene.
2) Difficult: You may have to setup some space partition, this is generally the way to find the locations of(Or narrow scope of) a large set of objects in space. Normally this is done through a series of nodes that a half-space test can be run on to determine how to navigate the tree until you get to the smallest region available, then run your checks on entities in that region. HOWEVER, you might be able to fake this by adding a series of box colliders across your playing field of equal sizes, fitted perfectly like a checkerboard. Make each one a trigger, then for both the player and all enemies have a property pointing to which region they are currently in(And regions should have a list of their connected regions, ie. neighbors). As enemies and the player move around, you can capture this region and reassign it. Then when you need to find the nearest enemy, iterate over each one, but skip to the next if its region doesn't match the player's or one of the player's neighboring regions(You will need to check neighbors as well, otherwise this won't work if the player is right on the edge of a region with an enemy, then moves into the next region before the enemy does).
Hope this helps!
Answer by Baste · Jun 10, 2015 at 08:16 AM
you're not actually calling the rulerDis method anywhere, which is the one meant to find the closest ship.
It's also a weird-ass algorithm for finding the closest ship. The targeting method:
for(int i = 0; i >= enemies.Length;) {
distanceToTarget[i] = Vector3.Distance(GameObject.Find("pointer").transform.position, enemies[i].transform.position);
}
Is able to do the whole job:
float closestDistance = Mathf.Infinity;
GameObject closestEnemy = null;
Vector3 myPos = GameObject.Find("pointer").transform.position;
foreach(GameObject enemy in enemies) {
Vector3 distance = Vector3.Distance(enemy.transform.position, myPos);
if(distance < closestDistance) {
closestEnemy = enemy;
closestDistance = distance;
}
}
An important point here - GameObject.Find is slow-ish. If you do it inside the for-loop over all of the enemies, you're searching for the same information every time. That's not neccessary.
Answer by Elisvaldo · Jun 10, 2015 at 09:20 AM
Let me just start by saying I don't have enough information on how exactly is your game design but I'm assuming that you have a large area and the objective of the compass is to not make the player wander the map looking for the last guy to kill.
I would reconsider if you really want the kind of implementation you are asking a solution for.
In your approach, when facing two enemies, the compass point will be jittering from one enemy to another, in case they are constantly changing from being the closest or not. Is this a main issue? No. But I do think that is a reflex of bad game design. You won't provide a smooth experience to the user. They will see by the jittering which logic you used to implement the compass. Your game will be more of a machine interaction than an experience.
Also, by having the compass constantly point to the nearest enemy, you are taking the player by the hand and the game turns into "just go where the compass is pointing".
So what I would suggest?
Imagine that the camera lets the player to see everything around him at 20m radius. I would then create a sphere/circle collider around the player with a larger value (let's say 100m), which would be the compass range. When an enemy OnTriggerEnters that collider, I would add him to a Queue (or List). I would then implement the compass to point towards the first enemy on the list. When the enemy OnTriggerExits, then I'd remove him from the list.
This actually creates another problem: when two enemies are visible, the compass might point towards the one which is actually further, and give the player the sense that one enemy is more important than the other.
However, if you have already an enemy on view, then the compass has done its job.
When the enemies/enemy are within the camera view, then there is no need for the compass: the player can actually see where they are. Therefore, I would disable the compass somehow (e.g. change the alpha, remove it completely, remove the compass point, make compass blink) as it is completely unnecessary by the time.
Once again, this answer was made with some assumptions about your game design.
I'd even say that the best option would be not using the compass to point the nearest enemy (compasses are good to point a single thing whose position doesn't change often). If that is okay with you, then put some multiple arrows on-screen pointing to all enemies in range. Simple and informative. This is the approach you can see most games take when facing a problem similar to yours.
Answer by JHM007 · Jun 11, 2015 at 09:33 AM
Thank you all for the interest and feedback. I am pleased to say that we have gotten it to work, thanks to you.
Have a nice day.