I am currently creating a series of videos to show how to make an automated turret in unity. It is going to be spread across a few videos, but the first one is up now.
Part 1 - Tracking
TrackingSystem.cs
using UnityEngine;
using System.Collections;
public class TrackingSystem : MonoBehaviour {
public float speed = 3.0f;
GameObject m_target = null;
Vector3 m_lastKnownPosition = Vector3.zero;
Quaternion m_lookAtRotation;
// Update is called once per frame
void Update () {
if(m_target){
if(m_lastKnownPosition != m_target.transform.position){
m_lastKnownPosition = m_target.transform.position;
m_lookAtRotation = Quaternion.LookRotation(m_lastKnownPosition - transform.position);
}
if(transform.rotation != m_lookAtRotation){
transform.rotation = Quaternion.RotateTowards(transform.rotation, m_lookAtRotation, speed * Time.deltaTime);
}
}
}
public void SetTarget(GameObject target){
m_target = target;
}
}
EnemyAi.cs
using UnityEngine;
using System.Collections;
public class EnemyAi : MonoBehaviour {
public Transform pointA;
public Transform pointB;
public float speed;
// Update is called once per frame
void Update () {
transform.position = Vector3.Lerp(pointA.position, pointB.position, Mathf.Pow(Mathf.Sin(Time.time * speed), 2));
}
}
Part 2 - Projectiles & Shooting
A -
B -
BaseProjectile.cs
using UnityEngine;
using System.Collections;
public abstract class BaseProjectile : MonoBehaviour {
public float speed = 5.0f;
public abstract void FireProjectile(GameObject launcher, GameObject target, int damage, float attackSpeed);
}
NormalProjectile.cs
using UnityEngine;
using System.Collections;
public class NormalProjectile : BaseProjectile {
using UnityEngine;
using System.Collections;
public class NormalProjectile : BaseProjectile {
Vector3 m_direction;
bool m_fired;
GameObject m_launcher;
GameObject m_target;
int m_damage;
// Update is called once per frame
void Update () {
if(m_fired){
transform.position += m_direction * (speed * Time.deltaTime);
}
}
public override void FireProjectile(GameObject launcher, GameObject target, int damage, float attackSpeed){
if(launcher && target){
m_direction = (target.transform.position - launcher.transform.position).normalized;
m_fired = true;
m_launcher = launcher;
m_target = target;
m_damage = damage;
Destroy(gameObject, 10.0f);
}
}
void OnCollisionEnter(Collision other)
{
if(other.gameObject == m_target)
{
DamageData dmgData = new DamageData();
dmgData.damage = m_damage;
MessageHandler msgHandler = m_target.GetComponent<MessageHandler>();
if(msgHandler)
{
msgHandler.GiveMessage(MessageType.DAMAGED, m_launcher, dmgData);
}
}
if(other.gameObject.GetComponent<BaseProjectile>() == null)
Destroy(gameObject);
}
}
TrackingProjectile.cs
using UnityEngine;
using System.Collections;
public class TrackingProjectile : BaseProjectile {
GameObject m_target;
GameObject m_launcher;
int m_damage;
Vector3 m_lastKnownPosition;
// Update is called once per frame
void Update () {
if(m_target){
m_lastKnownPosition = m_target.transform.position;
}
else
{
if(transform.position == m_lastKnownPosition)
{
Destroy(gameObject);
}
}
transform.position = Vector3.MoveTowards(transform.position, m_lastKnownPosition, speed * Time.deltaTime);
}
public override void FireProjectile(GameObject launcher, GameObject target, int damage, float attackSpeed){
if(target){
m_target = target;
m_lastKnownPosition = target.transform.position;
m_launcher = launcher;
m_damage = damage;
}
}
void OnCollisionEnter(Collision other)
{
if(other.gameObject == m_target)
{
DamageData dmgData = new DamageData();
dmgData.damage = m_damage;
MessageHandler msgHandler = m_target.GetComponent<MessageHandler>();
if(msgHandler)
{
msgHandler.GiveMessage(MessageType.DAMAGED, m_launcher, dmgData);
}
}
if(other.gameObject.GetComponent<BaseProjectile>() == null)
Destroy(gameObject);
}
}
BeamProjectile.cs
using UnityEngine;
using System.Collections;
public class BeamProjectile : BaseProjectile {
public float beamLength = 10.0f;
GameObject m_launcher;
GameObject m_target;
int m_damage;
float m_attackSpeed;
float m_attackTimer;
// Update is called once per frame
void Update () {
m_attackTimer += Time.deltaTime;
if(m_launcher){
GetComponent<LineRenderer>().SetPosition(0, m_launcher.transform.position);
GetComponent<LineRenderer>().SetPosition(1, m_launcher.transform.position + (m_launcher.transform.forward * beamLength));
RaycastHit hit;
if(Physics.Raycast(m_launcher.transform.position, m_launcher.transform.forward, out hit, beamLength))
{
if(hit.transform.gameObject == m_target)
{
if(m_attackTimer >= m_attackSpeed)
{
DamageData dmgData = new DamageData();
dmgData.damage = m_damage;
MessageHandler msgHandler = m_target.GetComponent<MessageHandler>();
if(msgHandler)
{
msgHandler.GiveMessage(MessageType.DAMAGED, m_launcher, dmgData);
}
m_attackTimer = 0.0f;
}
}
}
}
}
public override void FireProjectile(GameObject launcher, GameObject target, int damage, float attackSpeed){
if(launcher){
m_launcher = launcher;
m_target = target;
m_damage = damage;
m_attackSpeed = attackSpeed;
m_attackTimer = 0.0f;
}
}
}
ShootingSystem.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ShootingSystem : MonoBehaviour {
public float fireRate;
public int damage;
public float fieldOfView;
public bool beam;
public GameObject projectile;
public List<GameObject> projectileSpawns;
List<GameObject> m_lastProjectiles = new List<GameObject>();
float m_fireTimer = 0.0f;
GameObject m_target;
// Update is called once per frame
void Update () {
if(!m_target)
{
if(beam)
RemoveLastProjectiles();
return;
}
if(beam && m_lastProjectiles.Count <= 0){
float angle = Quaternion.Angle(transform.rotation, Quaternion.LookRotation(m_target.transform.position - transform.position));
if(angle < fieldOfView){
SpawnProjectiles();
}
}else if(beam && m_lastProjectiles.Count > 0){
float angle = Quaternion.Angle(transform.rotation, Quaternion.LookRotation(m_target.transform.position - transform.position));
if(angle > fieldOfView){
RemoveLastProjectiles();
}
}else{
m_fireTimer += Time.deltaTime;
if(m_fireTimer >= fireRate){
float angle = Quaternion.Angle(transform.rotation, Quaternion.LookRotation(m_target.transform.position - transform.position));
if(angle < fieldOfView){
SpawnProjectiles();
m_fireTimer = 0.0f;
}
}
}
}
void SpawnProjectiles(){
if(!projectile){
return;
}
m_lastProjectiles.Clear();
for(int i = 0; i < projectileSpawns.Count; i++){
if(projectileSpawns[i]){
GameObject proj = Instantiate(projectile, projectileSpawns[i].transform.position, Quaternion.Euler(projectileSpawns[i].transform.forward)) as GameObject;
proj.GetComponent<BaseProjectile>().FireProjectile(projectileSpawns[i], m_target, damage, fireRate);
m_lastProjectiles.Add(proj);
}
}
}
public void SetTarget(GameObject target){
m_target = target;
}
void RemoveLastProjectiles()
{
while(m_lastProjectiles.Count > 0){
Destroy(m_lastProjectiles[0]);
m_lastProjectiles.RemoveAt(0);
}
}
}
Part 3 - TurretAi
RangeChecker.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class RangeChecker : MonoBehaviour {
public List<string> tags;
List<GameObject> m_targets = new List<GameObject>();
void OnTriggerEnter(Collider other)
{
bool invalid = true;
for(int i = 0; i < tags.Count; i++)
{
if(other.CompareTag(tags[i]))
{
invalid = false;
break;
}
}
if(invalid)
return;
m_targets.Add(other.gameObject);
}
void OnTriggerExit(Collider other)
{
for(int i = 0; i < m_targets.Count; i++)
{
if(other.gameObject == m_targets[i])
{
m_targets.Remove(other.gameObject);
return;
}
}
}
public List<GameObject> GetValidTargets()
{
return m_targets;
}
public bool InRange(GameObject go)
{
for(int i = 0; i < m_targets.Count; i++)
{
if(go == m_targets[i])
return true;
}
return false;
}
}
TurretAi.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TurretAi : MonoBehaviour {
public enum AiStates{NEAREST, FURTHEST, WEAKEST, STRONGEST};
public AiStates aiState = AiStates.NEAREST;
TrackingSystem m_tracker;
ShootingSystem m_shooter;
RangeChecker m_range;
// Use this for initialization
void Start () {
m_tracker = GetComponent<TrackingSystem>();
m_shooter = GetComponent<ShootingSystem>();
m_range = GetComponent<RangeChecker>();
}
// Update is called once per frame
void Update () {
if(!m_tracker || !m_shooter || !m_range)
return;
switch(aiState)
{
case AiStates.NEAREST:
TargetNearest();
break;
case AiStates.FURTHEST:
TargetFurthest();
break;
case AiStates.WEAKEST:
TargetWeakest();
break;
case AiStates.STRONGEST:
TargetStrongest();
break;
}
}
void TargetNearest()
{
List<GameObject> validTargets = m_range.GetValidTargets();
GameObject curTarget = null;
float closestDist = 0.0f;
for(int i = 0; i < validTargets.Count; i++)
{
float dist = Vector3.Distance(transform.position, validTargets[i].transform.position);
if(!curTarget || dist < closestDist)
{
curTarget = validTargets[i];
closestDist = dist;
}
}
m_tracker.SetTarget(curTarget);
m_shooter.SetTarget(curTarget);
}
void TargetFurthest()
{
List<GameObject> validTargets = m_range.GetValidTargets();
GameObject curTarget = null;
float furthestDist = 0.0f;
for(int i = 0; i < validTargets.Count; i++)
{
float dist = Vector3.Distance(transform.position, validTargets[i].transform.position);
if(!curTarget || dist > furthestDist)
{
curTarget = validTargets[i];
furthestDist = dist;
}
}
m_tracker.SetTarget(curTarget);
m_shooter.SetTarget(curTarget);
}
void TargetWeakest()
{
List<GameObject> validTargets = m_range.GetValidTargets();
GameObject curTarget = null;
int lowestHealth = 0;
for(int i = 0; i < validTargets.Count; i++)
{
int maxHp = validTargets[i].GetComponent<Health>().maxHealth;
if(!curTarget || maxHp < lowestHealth)
{
lowestHealth = maxHp;
curTarget = validTargets[i];
}
}
m_tracker.SetTarget(curTarget);
m_shooter.SetTarget(curTarget);
}
void TargetStrongest()
{
List<GameObject> validTargets = m_range.GetValidTargets();
GameObject curTarget = null;
int highestHealth = 0;
for(int i = 0; i < validTargets.Count; i++)
{
int maxHp = validTargets[i].GetComponent<Health>().maxHealth;
if(!curTarget || maxHp > highestHealth)
{
highestHealth = maxHp;
curTarget = validTargets[i];
}
}
m_tracker.SetTarget(curTarget);
m_shooter.SetTarget(curTarget);
}
}