Hello Guys,
Yes, I know there are several threads about this concern, and I feel like I read everyone about it, but none has really helped me and I didn’t find a good solution to solve this.
I want to do a simple AI for a space game, which gets a target, approches it, shoots it, flies to a temp target to start a new attack run and repeat.
The problem I have is the obstacle Avoidance. For now, I am using a trigger to tell if an object is ahead and then use raycasts to figure out which location to go. This is not really working though. The fighters often get stuck on a collision mesh and behave weird.
The fighters have rigid body attached, to keep it simple I use drag to get some satisfying steering behavior.
Does anybody have any idea how I can improve the AI? Is there an alternate approach? Thank you very much!
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
public class AIScript4 : NetworkBehaviour {
public Transform target; // target the fighter is aiming for
public Transform front; //front of the fighter
public Transform shipL; //left wings of the fighter
public Transform shipR; //right wings of the fighter
public Transform[] cannonEnds; //Array in which the cannon-ends are listed
public Transform bombSlot;
public GameObject bulletPrefab; //Bullet which will be shot
public GameObject ionShotPrefab;
public GameObject explosionPrefab; //Explosion when the fighter gets destroyed
public int ownTeam; //The team which will be targetted
public int fireDistance; //range which the fighter can shoot
public int sightDistance; // distance of the ray casts
public int turnAwayDistance; //distance when the fighter turns away from the target and starts a new attack run
public float speed; //speed of the fighter
public float rotateSpeed; //speed of the rotation;
public float rayAngle; //angle of the raycasts, optimized to save memory
public int maxRayCasts;
public bool debug=true; //turn on to see messages and things like the raycasts
public bool isBomber;
private bool obstacle; //tells if there is an obstacle to be avoided
private Vector3 dir;
private Vector3 avoidDir;
private Vector3 newDir;
private int tempTime;
private int tempTimeBombs;
private int rayCount=0;
private Transform saveTarget;
void Start (){
AssignTarget();
}
void FixedUpdate (){
if(!isServer)
return;
if(target==null) //Assign next target when there is no target anymore
AssignTarget();
else{
dir = (target.position - transform.position).normalized;
if((Vector3.Distance(target.position, transform.position) < turnAwayDistance) && (saveTarget != target) && (saveTarget != null)){ //When the fighter is too close to the temporary target it should fly to the new target again
Destroy(target.gameObject);
target = saveTarget;
}
if(Vector3.Distance(target.position, transform.position) < turnAwayDistance) //When the fighter is too close to the target, it should fly away to start a new attack run
setTempTarget();
if((rayCount > maxRayCasts) && (saveTarget == target)){ //when the fighter used too much rayCasts to find a way out, it means he is stuck and shall start a new attackrun
rayCount = 0;
setTempTarget();
}
if(Vector3.Distance(target.position, transform.position) < fireDistance){ //if target is in range
if(Mathf.Abs(Vector3.Angle(target.position - transform.position, transform.forward)) < 45) //if the angle between the fighter and the target is below 45 degrees, the fighter almost looks at it and it can shoot
fireLasers();
}
if(isBomber && (Vector3.Distance(target.position, transform.position) < fireDistance*1.5f)){
fireBombs();
}
}
if(obstacle)
ObstacleCheck();
moveShip();
}
void ObstacleCheck (){
RaycastHit hit;
if(Physics.Raycast(front.position, front.forward, out hit, sightDistance)){
AvoidObstacle(front);
dir = avoidDir;
}
else if(Physics.Raycast(shipL.position, shipL.forward, out hit, sightDistance)){ //if we have an incoming obstacle Infront of the Left Wings
AvoidObstacle(shipL);
dir = avoidDir;
}
else if(Physics.Raycast(shipR.position, shipR.forward, out hit, sightDistance)){ //if we have an incoming obstacle Infront of the Right Wings
AvoidObstacle(shipR);
dir = avoidDir;
}
}
void AvoidObstacle ( Transform rayTarget ){
int i=0;
RaycastHit hit;
while(i<30){ //we cycle through different vectors until we find the way out
////////////////////////////////////////////////////////////////////////
//// DEBUG RAYS
///////////////////////////////////////////////////////////////////////
if(debug){ ///In Debug Mode, the rays shall be shown
if(Physics.Raycast(rayTarget.position, rayTarget.TransformDirection(new Vector3(0,-i*rayAngle,1)), out hit, sightDistance*(i+1))){
Debug.DrawLine(rayTarget.position, hit.point, Color.red,1);
}
if(Physics.Raycast(rayTarget.position, rayTarget.TransformDirection(new Vector3(0,i*rayAngle,1)), out hit, sightDistance*(i+1))){
Debug.DrawLine(rayTarget.position, hit.point, Color.red,1);
}
if(Physics.Raycast(rayTarget.position, rayTarget.TransformDirection(new Vector3(i*rayAngle,0,1)), out hit, sightDistance*(i+1))){
Debug.DrawLine(rayTarget.position, hit.point, Color.red,1);
}
if(Physics.Raycast(rayTarget.position, rayTarget.TransformDirection(new Vector3(-i*rayAngle,0,1)), out hit, sightDistance*(i+1))){
Debug.DrawLine(rayTarget.position, hit.point, Color.red,1);
}
}
////////////////////////////////////////////////////////////////////////
//// CHECK IN UP DIRECTION
///////////////////////////////////////////////////////////////////////
if(!(Physics.Raycast(rayTarget.position, rayTarget.TransformDirection(new Vector3(0,i*rayAngle,1)), out hit, sightDistance*(i+1)))){
if(debug)
print("Ship Exit Up.");
avoidDir = transform.TransformDirection(new Vector3(0,i,1));
break;
}
////////////////////////////////////////////////////////////////////////
//// CHECK IN DOWN DIRECTION
///////////////////////////////////////////////////////////////////////
if(!(Physics.Raycast(rayTarget.position, rayTarget.TransformDirection(new Vector3(0,-i*rayAngle,1)), out hit, sightDistance*(i+1)))){
if(debug)
print("Ship Exit down.");
avoidDir = transform.TransformDirection(new Vector3(0,-i,1));
break;
}
////////////////////////////////////////////////////////////////////////
//// CHECK IN RIGHT DIRECTION
///////////////////////////////////////////////////////////////////////
if(!(Physics.Raycast(rayTarget.position, rayTarget.TransformDirection(new Vector3(i*rayAngle,0,1)), out hit, sightDistance*(i+1)))){
if(debug)
print("Ship Exit Right.");
avoidDir = transform.TransformDirection(new Vector3(i,0,1));
break;
}
////////////////////////////////////////////////////////////////////////
//// CHECK IN LEFT DIRECTION
///////////////////////////////////////////////////////////////////////
if(!(Physics.Raycast(rayTarget.position, rayTarget.TransformDirection(new Vector3(-i*rayAngle,0,1)), out hit, sightDistance*(i+1)))){
if(debug)
print("Ship Exit Left.");
avoidDir = transform.TransformDirection(new Vector3(-i,0,1));
break;
}
i++; //if no escape is in sight, raise the search-radius
rayCount++;
}
}
void AssignTarget (){
string enemyTeam= "";
if(ownTeam == 1)
enemyTeam = "Team2";
else if(ownTeam == 2)
enemyTeam = "Team1";
GameObject[] targets;
targets = GameObject.FindGameObjectsWithTag(enemyTeam + "Target");
int targetNr;
if (targets != null)
targetNr = Random.Range (0, targets.Length);
else
targetNr = 0;
if(targets[targetNr] != null)
target = targets[targetNr].transform;
saveTarget = target;
}
void fireLasers (){
if(tempTime == 30){ //Controls the reload time
tempTime = 0;
for(int i=0; i < cannonEnds.Length; i++){
// Create the Bullet from the Bullet Prefab
GameObject bullet = (GameObject) Instantiate(bulletPrefab, cannonEnds[i].position + new Vector3(0,0,bulletPrefab.transform.lossyScale.z), cannonEnds[i].rotation);
// Add velocity to the bullet
bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 700;
//ToDo: bullet.GetComponent<LaserShotScript>().SetMotherShip(this.gameObject);
NetworkServer.Spawn(bullet);
// Destroy the bullet after 6 seconds
Destroy(bullet, 2.0f);
}
}
tempTime++;
}
void fireBombs (){
if(tempTimeBombs == 150){ //Controls the reload time
tempTimeBombs = 0;
GameObject ionShot = (GameObject) Instantiate(ionShotPrefab, bombSlot.position, bombSlot.rotation);
ionShot.SendMessage("SetTarget", target);
NetworkServer.Spawn(ionShot);
Destroy(ionShot, 3.5f);
}
tempTimeBombs++;
}
void OnTriggerStay (Collider col){ //If something enters the trigger, the fighter will make sure to avoid it
if(col.gameObject != target){
if(debug)
Debug.Log("There is an Obstacle infront!");
obstacle = true;
}
}
void OnTriggerEnter (Collider col){
if(col.gameObject != target)
obstacle = true;
}
void OnTriggerExit (){
obstacle = false;
}
void setTempTarget (){ //When the fighter is close to the target, it should find another temporary target so it can start another attack run. This target is placed in the same direction as the real target, and a little random up, down, left or right.
saveTarget = target;
GameObject newTarget = (GameObject) GameObject.CreatePrimitive(PrimitiveType.Cube);
newTarget.transform.position = target.position + target.forward * Random.Range(600.0f, 1000.0f) + target.up * Random.Range(300f, 700.0f) + new Vector3(0,Random.Range(-200.0f, 200.0f),Random.Range(-200.0f, -200.0f));
target = newTarget.transform;
}
void moveShip (){
// YELLOW: the direction the target is
// GREEN: the direction the ship is currently facing
// WHITE: the avoid direction when there is an obstacle
if(debug){ //when debug is active, it will draw the Directions
Debug.DrawRay(transform.position, dir*180, Color.yellow);
Debug.DrawRay(transform.position, transform.forward*100, Color.green);
Debug.DrawRay(transform.position, avoidDir*60, Color.white);
Debug.DrawRay(transform.position, newDir*120, Color.blue);
}
if(avoidDir != dir){
if(!Physics.Raycast(transform.position, dir, sightDistance/2)){
avoidDir = dir;
}
}
newDir = avoidDir;
Rigidbody rb = GetComponent<Rigidbody>();
//get the angle between transform.forward and target delta
float angleDiff = Vector3.Angle(transform.forward, newDir);
// get its cross product, which is the axis of rotation to
// get from one vector to the other
Vector3 cross = Vector3.Cross(transform.forward, newDir);
//apply torque along that axis according to the magnitude of the angle.
rb.AddTorque(cross * angleDiff * rotateSpeed, ForceMode.Force);
//apply velocity
rb.velocity = transform.forward * speed * (180-angleDiff)/180 * Time.deltaTime * 1000;
}
}