Enemy Detection and Firing
Learn how to detect enemies and shoot projectiles at them. A continuation of the tower defense tutorial series.
Learn how to make turrets detect enemies and shoot projectiles at them. A continuation of the tower defense tutorial series.
Setting Up Your Turret GameObject
Go ahead and get started by creating a Sprite in your hierarchy.
This should create a new GameObject with the Transform and Sprite Renderer components
Don't worry too much about changing the position; we're going to be setting that programmatically when the player tries to place a turret on the map. Do, however, add some cool art to the Sprite property of your renderer.
If your art is scattered across multiple files, you may want to consider using our sprite sheet maker tool to pack the images into a single sprite sheet.
Attach a MonoBehavior script and call it Turret.cs.
Attach a Sphere Collider too. Make sure the Is Trigger property is checked. Don't worry about changing values for Center and Radius as we'll be changing these programmatically.
Go ahead and create a prefab by dragging the turret from your scene to a directory in the project tab. Having our turrets as prefabs will give the benefit of being able to instantiate them from code. We can even create multiple prefabs which will allow for different turret types, each with their own stats, special abilities, and artwork.
Writing the Scripts
Open up the Turret.cs attached to your prefab. It should look something like this:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class Turret : MonoBehaviour
{
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
Let's add some fields and properties. Keep in mind the public fields will be accessible in Unity's inspector panel. damage, range, and rateOfFire are all meant to adjusted there on a 0-10 scale. The associated properties AttackDelay and DetectionRadius scale those values to based on the size of the map. DetectionRadius also takes responsibility for changing the size of the attached sphere collider.
// Configurable
public float accuracyError = 2.0f;
public int damage = 10;
public GameObject projectileType;
public int range = 5;
public int rateOfFire = 5;
// Constants
private const float MinAttackDelay = 0.1f;
private const float MaxAttackDelay = 2f;
// Internal
private List myTargets;
private float nextDamageEvent;
private ObjectManager objectManager;
private static readonly object syncRoot = new object ();
// Properties
private float AttackDelay
{
get
{
int inverted = rateOfFire;
if (rateOfFire == 0)
{
return float.MaxValue;
}
else if (rateOfFire < 5)
{
inverted = rateOfFire + 2 * (5 - rateOfFire);
}
else if (rateOfFire > 5)
{
inverted = rateOfFire - 2 * (rateOfFire - 5);
}
return (((float)inverted - 1f) / (10f - 1f)) * (MaxAttackDelay - MinAttackDelay) + .1f;
}
}
public float DetectionRadius
{
get
{
float minRange = Mathf.Min(objectManager.Map.nodeSize.x, objectManager.Map.nodeSize.y) * 1.5f;
float maxRange = minRange * 4f;
float detectionRadius = (((float)range - 1f) / (10f - 1f)) * (maxRange - minRange) + minRange;
detectionRadius = detectionRadius / transform.localScale.x;
return detectionRadius;
}
set
{
float minRange = Mathf.Min(objectManager.Map.nodeSize.x, objectManager.Map.nodeSize.y) * 1.5f;
float maxRange = minRange * 4f;
float detectionRadius = (((float)value - 1f) / (10f - 1f)) * (maxRange - minRange) + minRange;
detectionRadius = detectionRadius / transform.localScale.x;
SphereCollider collider = transform.GetComponent ();
collider.radius = detectionRadius;
}
}
Here we initialize some of those private fields. objectManager is just a singleton we are using to help maintain game state. Matt talks more in-depth about it in some of the earlier videos from the Tower Defense Tutorial Video Series.
// Runs when entity is Instantiated
void Awake()
{
objectManager = ObjectManager.GetInstance();
objectManager.AddEntity(this);
}
// Use this for initialization
void Start ()
{
DetectionRadius = range;
myTargets = new List();
}
These two methods track when enemies enter and exit the attached sphere collider. At any given time, myTargets should now reflect all enemies inside the sphere, the turret's detectable area.
void OnTriggerEnter (Collider other)
{
if (other.gameObject.tag == "enemy") {
myTargets.Add (other.GetComponent());
}
}
void OnTriggerExit (Collider other)
{
lock (syncRoot) {
if (other != null &&
myTargets.Select (t => t!= null && t.gameObject).Contains(other.gameObject)) {
myTargets.Remove (other.GetComponent());
}
}
}
Killing Dudes with Projectiles
In order to create a projectile, create a new GameObject starting with as a sphere. I ended up adding a Mesh Renderer and a Line Renderer to get it to look like a bullet. Attach a MonoBehavior script called Projectile.cs. Go ahead and make a prefab from this object the same way you did for turrets.
Nothing too crazy going on in this script. It requires a target enemy (and associated location) and just homes in on it until it "hits". We're not doing any collision detection here, but rather checking distance between the projectile and its target. Once the projectile reaches the target, it destroys itself and damages the enemy by subtracting from its health.
using UnityEngine;
using System.Collections;
public class Projectile : MonoBehaviour
{
// Configurable
public float range;
public float speed;
public EnemyBase target;
public Vector3 targetPosition;
public int Damage { get; set; }
// Internal
private float distance;
// Runs when entity is Instantiated
void Awake ()
{
distance = 0;
}
// Update is called once per frame
void Update ()
{
Vector3 moveVector = new Vector3 (transform.position.x - targetPosition.x,
transform.position.y - targetPosition.y,
transform.position.z - targetPosition.z).normalized;
// update the position
transform.position = new Vector3 (transform.position.x - moveVector.x * speed * Time.deltaTime,
transform.position.y - moveVector.y * speed * Time.deltaTime,
transform.position.z - moveVector.z * speed * Time.deltaTime);
distance += Time.deltaTime * speed;
if (distance > range ||
Vector3.Distance (transform.position, new Vector3 (targetPosition.x, targetPosition.y, targetPosition.z)) < 1)
{
Destroy (gameObject);
if (target != null)
{
target.Damage (Damage);
}
}
}
}
Now that projectiles are good to go, drag that new projectile prefab onto the projectileType field (in the inspector when you've got a turret selected). Next, you'll need the following two methods to make the turret "fire" projectiles. All we're doing is setting up a loop where the turret instantiates new projectiles targeted at a random enemy within range.
void Fire (EnemyBase myTarget)
{
var targetPosition = myTarget.transform.position;
var aimError = Random.Range (-accuracyError, accuracyError);
var aimPoint = new Vector3 (targetPosition.x + aimError, targetPosition.y + aimError, targetPosition.z + aimError);
nextDamageEvent = Time.time + AttackDelay;
GameObject projectileObject = Instantiate (projectileType, transform.position, Quaternion.LookRotation (targetPosition)) as GameObject;
Projectile projectile = projectileObject.GetComponent ();
projectile.Damage = damage;
projectile.target = myTarget;
projectile.targetPosition = aimPoint;
}
// Update is called once per frame
void Update ()
{
lock(syncRoot)
{
if (myTargets.Any())
{
EnemyBase myTarget = myTargets.ElementAt(Random.Range(0, myTargets.Count));
if (myTarget != null) {
if (Time.time >= nextDamageEvent)
{
Fire(myTarget);
}
}
else
{
nextDamageEvent = Time.time + AttackDelay;
myTargets.Remove(myTarget);
}
}
}
}
To fill in some of the gaps like placing turrets on the map and spawning enemies, I encourage you to go take a look at some of the earlier videos from the Tower Defense Tutorial Video Series.
Recommended posts
We have similar articles. Keep reading!
Deploying Unity Games to Android
Step-by-step instructions to learn how to deploy an existing Unity game to your Android device or get a game ready for releasing to the Play Store.
How to set up Visual Studio Code for Unity
All of the Visual Studio Code Extensions you'll need to get started using it with Unity3D.
Unity Animator State Machine Tutorial
Technical talk from Matt Bauer about Unity animator state machines in Nauticus Act III. Simplify Unity state machines into manageable sub-components.
Object Pooling
Learn how to create a simple object pooler in Unity3D in our series about building a 3D shooter on rails.