Hi all,
I’m a hobbyist with very little experience, but have been trying to avoid buying pre-built assets and am having a stab at creating my own A.I. logic. I’ve been scouring the forums to get ideas on how to implement it, and have settled on a simple finite state machine (fsm). Though i’m thinking my problem may need a stacked fsm…
I’ve got Defend, Seek, Attack and Evade modes working, but the ships end up just circling each other switching rapidly between all states.
Any advice greatly appreciated.
using UnityEngine;
public class SpaceBattle : MonoBehaviour
{
public Transform moon;
public GameObject[] enemyShips;
private Transform target;
public GameObject Bullet_Emitter;
public GameObject Bullet;
public float Bullet_Forward_Force;
public bool chasing = false;
public float distance;
public float nearest = 10000;
public string targeted;
public float rotationSpeed;
public float maxDist;
public float minDist;
public float fireDist;
public string mode="Seek";
//debug
public string TargetName;
public float moveSpeed;
private void Start()
{
enemyShips = GameObject.FindGameObjectsWithTag("EnemyShip");
}
void Update()
{
enemyShips = null;
enemyShips = GameObject.FindGameObjectsWithTag("EnemyShip");
Debug.Log("Enemy Ships Found: " + enemyShips.Length);
if (target != null && target!=moon)
{
//If target is further than maxDist we lost the target
if ((target.transform.position - transform.position).magnitude >= maxDist)
{
target.GetComponentInParent<EnemyShip>().targeted =null;
target = null;
nearest = 10000;
}
// else nearest = (target.transform.position - transform.position).magnitude;
}
target = null;
// Look for a nearby target
nearest = 1000;
for (int i = 0; i < enemyShips.Length; i++)
{
if (enemyShips*.GetComponent<EnemyShip>().targeted != null)*
{
distance = (enemyShips*.transform.position - transform.position).magnitude;*
Debug.Log(“Checking " + enemyShips*.name + “… distance is " + distance.ToString()+” and Max:”+maxDist+" Nearest:“+nearest+” i="+i.ToString());*
if (distance < nearest)
{
Debug.Log("Found a nearer target: " + enemyShips*.name + " " + distance);*
nearest = distance;
target = enemyShips*.transform;*
}
}
}
if (target != null) target.GetComponentInParent().targeted = this.name;
if (target == null || (moon.transform.position - transform.position).magnitude>1000) target = moon;
TargetName = target.name;
if (target == moon)
{
mode = “Defend”;
}
else if (nearest <= minDist)
{
mode = “Evade”;
}
else if (nearest>=fireDist && mode==“Evade”)
{
mode = “Attack”;
}
else if (mode==“Seek” && nearest<=fireDist)
{
mode = “Attack”;
}
else if (nearest<=maxDist)
{
mode = “Seek”;
}
switch (mode)
{
case “Seek”:
{
SeekTarget(transform, target);
MoveToTarget(transform, target);
break;
}
case “Defend”:
{
SeekTarget(transform, moon);
MoveToTarget(transform, moon);
break;
}
case “Evade”:
{
EvadeTarget(transform, target);
break;
}
case “Attack”:
{
SpeedToTarget(transform, target);
FireAtTarget(transform, target);
break;
}
}
}
void EvadeTarget(Transform rTransform, Transform rtarget)
{
rTransform.rotation = Quaternion.Slerp(rTransform.rotation, Quaternion.LookRotation(rTransform.position - rtarget.position), rotationSpeed * Time.deltaTime);
rTransform.position += rTransform.forward * moveSpeed * Time.deltaTime;
}
void SeekTarget(Transform rTransform, Transform rtarget)
{
rTransform.rotation = Quaternion.Slerp(rTransform.rotation, Quaternion.LookRotation(rtarget.position - rTransform.position), rotationSpeed * Time.deltaTime);
Debug.Log("Seeking " + rtarget.name);
}
void MoveToTarget(Transform rTransform, Transform rtarget)
{
rTransform.position += rTransform.forward * moveSpeed * Time.deltaTime;
Debug.Log("Moving towards " + rtarget.name);
}
void SpeedToTarget(Transform rTransform, Transform rtarget)
{
rTransform.rotation = Quaternion.Slerp(rTransform.rotation, Quaternion.LookRotation(predictedPosition(rtarget.position,rTransform.position,rtarget.GetComponentInParent().velocity, 30f) - rTransform.position), rotationSpeed * Time.deltaTime);
rTransform.position += rTransform.forward * (moveSpeed*1.5f) * Time.deltaTime;
Debug.Log("Attacking " + rtarget.name);
}
void FireAtTarget(Transform rTransform, Transform rtarget)
{
if (Physics.Raycast(rTransform.position, rTransform.forward, 100) && GameObject.FindGameObjectsWithTag(“FriendlyFire”).Length < 3)
{
GameObject Temporary_Bullet_Handler;
Temporary_Bullet_Handler = Instantiate(Bullet, Bullet_Emitter.transform.position, Bullet_Emitter.transform.rotation) as GameObject;
Temporary_Bullet_Handler.transform.Rotate(Vector3.left * 90);
Rigidbody Temporary_RigidBody;
Temporary_RigidBody = Temporary_Bullet_Handler.GetComponent();
Temporary_RigidBody.AddForce(transform.forward * Bullet_Forward_Force);
Destroy(Temporary_Bullet_Handler, 0.5f);
}
}
private Vector3 predictedPosition(Vector3 targetPosition, Vector3 shooterPosition, Vector3 targetVelocity, float projectileSpeed)
{
Vector3 displacement = targetPosition - shooterPosition;
float targetMoveAngle = Vector3.Angle(-displacement, targetVelocity) * Mathf.Deg2Rad;
//if the target is stopping or if it is impossible for the projectile to catch up with the target (Sine Formula)
if (targetVelocity.magnitude == 0 || targetVelocity.magnitude > projectileSpeed && Mathf.Sin(targetMoveAngle) / projectileSpeed > Mathf.Cos(targetMoveAngle) / targetVelocity.magnitude)
{
Debug.Log(“Position prediction is not feasible.”);
return targetPosition;
}
//also Sine Formula
float shootAngle = Mathf.Asin(Mathf.Sin(targetMoveAngle) * targetVelocity.magnitude / projectileSpeed);
return targetPosition + targetVelocity * displacement.magnitude / Mathf.Sin(Mathf.PI - targetMoveAngle - shootAngle) * Mathf.Sin(shootAngle) / targetVelocity.magnitude;
}
}