unity3d 没有navMesh的AI坦克大战的实现

unity3d 没有navMesh的AI坦克大战的实现

AI坦克大战

1.游戏简介:

> * 游戏目标:击毁地方基地。

> * AI行为:

1.拥有一定的视野范围。

2.若视野范围内出现敌方坦克,随机选择一个目标,旋转坦克机身,使坦克面朝目标并追踪敌方坦克。

3.目标进入攻击范围内,控制炮台瞄准目标并开火,击中率随机。

4.若视野范围内无任何敌方坦克,则向基地方向靠近。

5.基地进入攻击范围,且周围无敌方坦克时,攻击基地。

>*实现目标:做出给定坦克的AI模型,AI模型行为应与玩家控制的坦克行为较像。

>*点击下载完整项目资源

2.游戏预览

1.较平地面,有遮挡物情况演示:



2.陡峭地面坦克大战演示:








3.设计过程

1.寻找模型等游戏资源:

在Asset Store中搜索Kawaii_Tanks,下载项目。

在Asset Store中搜索Base,找到一个基地模型,下载。

打开里面的场景Base-01,有一个已经做好了的坦克,可以由玩家通过键盘,鼠标操纵。这次作业就利用这个作为模板,实现AI坦克。


2.我的AI实现思路:

让AI坦克判断当前所处状态,模拟用户输入,如键入方向键调整机身,模拟鼠标控制炮台运动,模拟按空格开火等。我个人觉得这是AI实现比较通用的做法,像这次这个坦克模型,就只能通过按方向键的方法实现移动,因为它没有动画,靠轮子驱动,其他实现移动的方法用起来坦克动的不正常。


3.实现AI的思路:

1.简单思路:AI坦克最基本的就是发现跟踪目标,自动位移,瞄准,开火这四个功能。但是如何实现这些功能呢?例如自动位移就有很多种实现方式,如直接修改transform,通过动画驱动物体位置变换,通过添加力或直接修改刚体的velocity,或者如这个坦克一般,通过给轮子加力矩,然后用转轴去带动坦克运行一般,通过纯物理方式进行。所以为了更好地实现AI坦克,首先就必须理解普通坦克四个功能的实现方法,并进行修改。


2.对普通坦克进行解析:

a.位移的实现:

仔细研究这个坦克的实现方法之后,我对原作者佩服得五体投地,坦克很简单的前进和转弯都是只利用方向键实现的,没有任何动画,而是通过给转动轮一个力矩让轮子转动,再用铰链hinge joint连接到一个类似转轴的东西上,转轴又通过hinge joint连接到主体上,带动主体运动。所以AI实现时只需根据需要,模拟键入方向键来控制坦克即可。AI坦克先模拟按左键或右键旋转机身面朝目标,然后再向前前进。

b.瞄准目标的实现:

普通坦克利用的是鼠标位置,炮台旋转到鼠标方向并调整垂直方向。在AI坦克中,可以直接把炮台旋转到目标位置附近,并随机调整炮台垂直方向,以实现随机命中。

b.开火的实现:

普通坦克利用的是键入空格键,坦克开火。在AI坦克中可以这样实现:判断一下,如果敌方坦克进入攻击范围,且炮台旋转到目标位置,则炮台通过BroadcastMessage向子对象炮口发送一个信号,炮口即可发射子弹。


3.一些附加功能的实现:

a.AI坦克自带视野:

如何实现“找到进入视野范围的敌方坦克”呢?

1.先通过findGameobjectsWithTag找到所有敌方坦克。

2.求出自身位置到目标位置的方向向量,目前坦克面朝的方向向量,利用Vector3.Angle()求出向量夹角。如果小于设定的视野角度,则可能在视野范围内。

3.从自身向目标发射一条射线,如果射线击中其他物体而非敌方坦克,说明两者之间阻碍物,此时敌方坦克不能算是“进入视野范围”。(更好的做法是向敌方坦克四个角都发射射线,若有一条射线击中坦克,则说明坦克进入视野范围)b.AI坦克旋转机身,使机身朝向目标:

b.旋转坦克机身使其面朝目标:

此处有两种做法,一种较复杂又傻逼,一种简单又省心(好吧,我用的是第一种,代码里也是第一种,就懒得改了)。

方法1(简单版):

利用localPositionOfTarget = this.transform.InverseTransformPoint(targetPosition),得出目标物体对于AI坦克的相对位置。(0,0,1)表示目标在AI坦克正前方一个单位处。此时可以判断,如果localPositionOfTarget.x > 0,则AI坦克应模拟按右键,向右旋转。否则,向左旋转。

方法2(脑缺版):

一开始不清楚有InverseTransformPoint这个函数,可以很方便地判断出目标位置在坦克面朝方向的第几象限,所以用了另一个方法。即分别求出transform.forward和两点间方向向量_dir,(利用Mathf.Atan),得到的是-180到180度的角,还需要进行处理为0--360度角,然后再分类讨论。。。

c.命中率随机的实现:

可以随机调整炮口的垂直方向角度,这个可以最方便地实现命中率随机。具体看AI_Cannon_Control的代码。有一些小细节是需要注意的,例如避免永远打不中对象的情况的发生。

d.如何防止高速小炮弹穿透物体而不引发碰撞事件:

在本例中应该并不存在此情况,因为炮弹够大且速度不是太快。但我还是考虑了这种情况。解决方法是,在FixedUpdate()中,从子弹向子弹前进方向发射一道射线,射线长度为一帧子弹运动距离,如果射线能够击中坦克,则判断出击中坦克,进行处理。同时,在CollisionEnter()中,也要判断一下有没有碰撞事件。可以加一些判断条件,避免被计算为两次碰撞事件。此方法可以解决穿透问题。


4.代码实现:

点击下载完整项目资源

1.基本方法:修改或重新编写所有与用户操作有关的控制脚本。

2.核心控制脚本为:

AI_ID_Control:该脚本实现角色控制,如果AI的ID设置为2,则会被自动控制。(原本的ID_Control版本为:如果ID为1,则坦克是由玩家操纵。)

AI_Wheel_Control:该脚本实现自动追踪视野范围内目标,为控制坦克运动的输入脚本。

AI_Wheel_Rotate:该脚本实现根据驱动轮子转动带动坦克运动。

AI_Turret_Control:该脚本实现炮台自动旋转到目标方向。

AI_Connol_Control:该脚本实现炮口垂直方向的瞄准,实现命中率随机。

AI_Fire_Control:实现特定条件下自动开火。

3.简单挂一下这6个脚本的代码,要理解坦克的运行,还需下载整个project,看一下AI坦克的预制。
AI_ID_Control.cs:

using UnityEngine;
//using System.Collections;
using System;

using UnityStandardAssets.CrossPlatformInput ;

// This script must be attached to Root Object of the tank.

public class AI_ID_Control : MonoBehaviour {

	[ Header ( "ID number settings" ) ]
	[ Tooltip ( "ID number for this AI tank." ) ] public int myID = 2 ;

	int currentID =	2 ;

	void Start () {
		BroadcastMessage ( "Get_ID" , myID , SendMessageOptions.DontRequireReceiver ) ;
		BroadcastMessage ( "Get_Current_ID" , currentID , SendMessageOptions.DontRequireReceiver ) ;
	}

	void Update () {
		if ( Input.GetKeyDown ( KeyCode.Escape ) ) {
			Application.Quit () ;
		}
		#if UNITY_ANDROID || UNITY_IPHONE
		if ( CrossPlatformInputManager.GetButtonDown ( "Switch" ) ) {
		#else
		if ( Input.GetKeyDown ( KeyCode.Return ) ) {
		#endif
			ID_Control_CS [] iDScripts = FindObjectsOfType < ID_Control_CS > () ;
			int [] iDArray = new int [ iDScripts.Length ] ;
			for ( int i = 0 ; i < iDScripts.Length ; i++ ) {
				iDArray [ i ] = iDScripts [ i ].myID ;
			}
			Array.Sort ( iDArray ) ;
			for ( int i = 0 ; i < iDArray.Length ; i++ ) {
				if ( iDArray [ i ] == currentID ) {
					if ( i == iDArray.Length - 1 ) {
						currentID = iDArray [ 0 ] ;
					} else {
						currentID = iDArray [ i + 1 ] ;
					}
					break ;
				}
			}
			BroadcastMessage ( "Get_Current_ID" , currentID , SendMessageOptions.DontRequireReceiver ) ;
		}
	}
}


AI_Wheel_Control.cs
using UnityEngine;
using System.Collections;
using UnityStandardAssets.CrossPlatformInput;
using System.Collections.Generic;
// This script must be attached to "MainBody".

public class AI_Wheel_Control : MonoBehaviour {

	[ Header ( "Driving settings" ) ]
	[ Tooltip ( "Torque added to each wheel." ) ] public float wheelTorque = 2000.0f ; // Reference to "Wheel_Rotate".
	[ Tooltip ( "Maximum Speed (Meter per Second)" ) ] public float maxSpeed = 7.0f ; // Reference to "Wheel_Rotate".
	[ Tooltip ( "Rate for ease of turning." ) , Range ( 0.0f , 1.0f ) ] public float turnClamp = 0.5f ;
	[ Tooltip ( "Velocity the parking brake automatically works." ) ] public float autoParkingBrakeVelocity = 0.5f ;
	[ Tooltip ( "Lag time for activating the parking brake." ) ] public float autoParkingBrakeLag = 0.5f ;
	[ Tooltip ( "'Solver Iteration Count' of all the rigidbodies in this tank." ) ] public int solverIterationCount = 7 ;

	[ Tooltip ( "ange of fild eye sight" ) ] public float viewAngel = 70;		//I add.
	[ Tooltip ( "target object if the tank can't find an enemy" ) ] public Transform originTarget;		//I add.
	[ Tooltip ( "attack radius" ) ] public float attackRadius = 70;		//I add.
	[HideInInspector] public float leftRate ; // Reference to "Wheel_Rotate".
	[HideInInspector] public float rightRate ; // Reference to "Wheel_Rotate".
	[HideInInspector] public Transform targetAITransform ; // Reference to "AI_Turret_Control" and "AI_Cannon_Control".
	[HideInInspector] public bool closeEnoughToShoot ; // Reference to "AI_Turret_Control" and "AI_Cannon_Control".
	Rigidbody thisRigidbody ;

	bool isParkingBrake = false ;
	float lagCount ;
	string thisTag;
	int myID ;
	bool isCurrentID = true ;

	void Awake () {
		// Layer Collision Settings.
		// Layer9 >> for wheels.
		// Layer10 >> for Suspensions.
		// Layer11 >> for MainBody.
		for ( int i =0 ; i <= 11 ; i ++ ) {
			Physics.IgnoreLayerCollision ( 9 , i , false ) ; // Reset settings.
			Physics.IgnoreLayerCollision ( 11 , i , false ) ; // Reset settings.
		}
		Physics.IgnoreLayerCollision ( 9 , 9 , true ) ; // Wheels do not collide with each other.
		Physics.IgnoreLayerCollision ( 9 , 11 , true ) ; // Wheels do not collide with MainBody.
		for ( int i =0 ; i <= 11 ; i ++ ) {
			Physics.IgnoreLayerCollision ( 10 , i , true ) ; // Suspensions do not collide with anything.
		}
	}

	void Start () {
		thisTag = this.transform.parent.gameObject.tag;
		if(thisTag == "myTank") {																				//if the tank is our friend		
			originTarget = GameObject.FindGameObjectWithTag ("myTarget").transform.FindChild("MainBody");		//The base is with tag "myTarget"!!
		} else {
			originTarget = GameObject.FindGameObjectWithTag ("enemyTarget").transform.FindChild("MainBody");	//The base is with tag "enemyTarget"!!
		}
		Debug.Log(thisTag + "aim at " + originTarget.parent.gameObject.name);

		this.gameObject.layer = 11 ; // Layer11 >> for MainBody.
		thisRigidbody = GetComponent < Rigidbody > () ;
		thisRigidbody.solverIterations = solverIterationCount ;
		closeEnoughToShoot = false;
		BroadcastMessage ( "Get_Wheel_Control" , this , SendMessageOptions.DontRequireReceiver ) ; // Send this reference to all the "Wheel_Rotate".
	}

	void Update () {
		if ( isCurrentID ) {
			float vertical = 1;				//上下键.
			float horizontal = 0;		//左右键.
			List<Transform> listOfCandidate = new List<Transform>();				//store all enemy tank in the view of the tank.

			if (targetAITransform == null || (targetAITransform.parent.gameObject.tag != "myTank" && targetAITransform.parent.gameObject.tag != "enemyTank" )) {
				//if the tank is tracking nothing or the base.
				GameObject[] ArrAI;
				if (thisTag == "myTank") {
					ArrAI = GameObject.FindGameObjectsWithTag ("enemyTank");		//I am a my_tank, find all enemy_tanks!	
				} else {
					ArrAI = GameObject.FindGameObjectsWithTag ("myTank");			
				}

				for (int i = 0; i < ArrAI.Length; i++) {										//find all enemy tank
					Transform thisTransform = ArrAI [i].transform.FindChild ("MainBody");		//store tank in listOfCandidate
					Vector3 _dir = (thisTransform.position - transform.position).normalized;
					float angleBetweenDirAndFor = Vector3.Angle (transform.forward, _dir);
//				Debug.Log ("angle: " + angleBetweenDirAndFor);
					if (angleBetweenDirAndFor >= viewAngel)										//out of filed of eyesight
					continue;
				
					RaycastHit hit;

					if (Physics.Raycast (transform.position, _dir, out hit)) {					//the ray of these objects hit an object
	//					Debug.Log("hit !!! " + hit.collider.gameObject.name);

						if (hit.rigidbody) {													//eye sight is not blocked by other objects.
							if (hit.rigidbody.gameObject.name == "MainBody") {					//hitted object is the tank!
								listOfCandidate.Add (thisTransform);
							}
						}
					}
				}
				;
				
				if (listOfCandidate.Count > 0) {														//random select a object
					targetAITransform = listOfCandidate [(Random.Range (0, listOfCandidate.Count))];
				} else {
					targetAITransform = originTarget;
				}
			}

			Vector3 _direction = (targetAITransform.position - transform.position).normalized;
			Vector3 _forwardDir = (new Vector3 (transform.forward.x, 0, transform.forward.z)).normalized;

			//This part is to control the tank rotate or go ahead.

			float _dirAngle = Mathf.Atan2 (_direction.z, _direction.x) * Mathf.Rad2Deg;			//0->180    -180->0
			float _forAngle = Mathf.Atan2 (_forwardDir.z, _forwardDir.x) * Mathf.Rad2Deg;		//0->180    -180->0
//			Debug.Log ("_dirAngle is: " + _dirAngle + " forAngle is: " + _forAngle);
			_dirAngle = halfCircleToCircle(_dirAngle);											//0->360
			_forAngle = halfCircleToCircle (_forAngle);											//0->360

			if (close_enough_to_attack (transform.position, targetAITransform.position, attackRadius)) {	//close
				closeEnoughToShoot = true;
				vertical = 0;	
				horizontal = 0;
				//rotate the turret, then fire!!
			} else {																				//not close enough, go ahead or rotate.
				closeEnoughToShoot = false;
				if (getAbsoluteNumOfFloat (_dirAngle - _forAngle) < 20) {							//close enought, go ahead
					vertical = 1;	
					horizontal = 0;
				} else {
					if (_dirAngle >= 0 && _dirAngle <= 180) {
						if (_forAngle > _dirAngle && (_forAngle < _dirAngle + 180)) {				//turn right
							vertical = 0;
							horizontal = Mathf.Clamp (1, -turnClamp, turnClamp);
						} else {																	//turn left
							vertical = 0;
							horizontal = Mathf.Clamp (-1, -turnClamp, turnClamp);
						}
					} else {
						float opositDirection = _dirAngle + 180;									//oposite Direction of _dirAngle
						if (_forAngle >= _dirAngle || (_forAngle + 360) <= opositDirection) {		//turn right
							vertical = 0;
							horizontal = Mathf.Clamp (1, -turnClamp, turnClamp);						
						} else {																	//turn left;
							vertical = 0;
							horizontal = Mathf.Clamp (-1, -turnClamp, turnClamp);
						}
					}
				}
			}
			leftRate = Mathf.Clamp (-vertical - horizontal, -1.0f, 1.0f);
			rightRate = Mathf.Clamp (vertical - horizontal, -1.0f, 1.0f);
		}
	}

	bool close_enough_to_attack(Vector3 thisPosition, Vector3 targetPosition, float radius) {	
		Vector3 diff = targetPosition - thisPosition;
//		Debug.Log ("radius: " + diff.sqrMagnitude);
		if (diff.sqrMagnitude < radius * radius)
			return true;
		else
			return false;
	}
	float halfCircleToCircle(float angle) {
		if (angle >= -180 && angle < 0) {
			return (angle + 360);
		}
		return angle;
	}

	float getAbsoluteNumOfFloat(float a) {
		if (a > 0.0f)
			return a;
		else
			return -a;
	}

	void FixedUpdate () {
		if ( leftRate == 0.0f && rightRate == 0.0f ) {
			float velocityMag = thisRigidbody.velocity.magnitude ;
			float angularVelocityMag = thisRigidbody.angularVelocity.magnitude ;
			if ( isParkingBrake == false ) {
				if ( velocityMag < autoParkingBrakeVelocity && angularVelocityMag < autoParkingBrakeVelocity ) {
					lagCount += Time.fixedDeltaTime ;
					if ( lagCount > autoParkingBrakeLag ) {
						isParkingBrake = true ;
						thisRigidbody.constraints = RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ | RigidbodyConstraints.FreezeRotationY ;
					}
				}
			} else {
				if ( velocityMag > autoParkingBrakeVelocity || angularVelocityMag > autoParkingBrakeVelocity ) {
					isParkingBrake = false ;
					thisRigidbody.constraints = RigidbodyConstraints.None ;
					lagCount = 0.0f ;
				}
			}
		} else {
			isParkingBrake = false ;
			thisRigidbody.constraints = RigidbodyConstraints.None ;
			lagCount = 0.0f ;
		}
	}

	void Get_ID ( int idNum ) {
		myID = idNum ;
	}

	void Get_Current_ID ( int idNum ) {
		if ( idNum == myID ) {
			isCurrentID = true ;
		} else {
			isCurrentID = false ;
		}
	}
}

AI_WheelRotate.cs
using UnityEngine;
using System.Collections;

// This script must be attached to all the Driving Wheels.

public class AI_Wheel_Rotate : MonoBehaviour {

	bool isLeft ;			//left wheel or right wheel?
	Rigidbody thisRigidbody ;
	float maxAngVelocity ;
	AI_Wheel_Control controlScript ;

	Transform thisTransform ;
	Vector3 initialAng ;

	void Start () {
		this.gameObject.layer = 9 ; // Layer9 >> for wheels.
		thisRigidbody = GetComponent < Rigidbody > () ;
		// Set direction.
		if ( transform.localPosition.y > 0.0f ) {
			isLeft = true ;
		} else {
			isLeft = false ;
		}
		// Get initial rotation.
		thisTransform = transform ;
		initialAng = thisTransform.localEulerAngles ;
	}

	void Get_Wheel_Control ( AI_Wheel_Control tempScript ) {
		controlScript = tempScript ;
		float radius = GetComponent < SphereCollider > ().radius ;
		maxAngVelocity = Mathf.Deg2Rad * ( ( controlScript.maxSpeed / ( 2.0f * Mathf.PI * radius ) ) * 360.0f ) ;
	}

	void FixedUpdate () {
		float rate ;
		if ( isLeft ) {
			rate = controlScript.leftRate ;
		} else {
			rate = controlScript.rightRate ;
		}
		thisRigidbody.AddRelativeTorque ( 0.0f , Mathf.Sign ( rate ) * controlScript.wheelTorque , 0.0f ) ;
		thisRigidbody.maxAngularVelocity = Mathf.Abs ( maxAngVelocity * rate ) ;
	}

	void Update () { // Stabilize wheels.
		float angY = thisTransform.localEulerAngles.y ;
		thisTransform.localEulerAngles = new Vector3 ( initialAng.x , angY , initialAng.z ) ;
	}
}


AI_Turret_Control.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI ;

using UnityStandardAssets.CrossPlatformInput ;

// This script must be attached to "Turret_Base".

public class AI_Turret_Control : MonoBehaviour {

	[ Header ( "Turret movement settings" ) ]
	[ Tooltip ( "Maximum Rotation Speed. (Degree per Second)" ) ] public float rotationSpeed = 15.0f ;
	[ Tooltip ( "Time to reach the maximum speed. (Sec)" ) ] public float acceleration_Time = 0.2f ;
	[ Tooltip ( "Angle range for slowing down. (Degree)" ) ] public float bufferAngle = 5.0f ;

	Transform thisTransform ;
	Transform rootTransform ;

	float currentAng ;
	Vector3 targetPos ;
	Transform targetTransform ;
//	Vector3 targetOffset ;
	AI_Wheel_Control AI_Wheel_Control_Script;

	[HideInInspector] public Vector3 local_TargetPos ; // Reference to "Cannon_Control_CS".
	[HideInInspector] public Vector3 adjustAng ; // Reference to "Cannon_Control_CS".
//	Vector3 previousMousePos ;
	float speedRate ;

	#if UNITY_ANDROID || UNITY_IPHONE
	bool isButtonDown = false ;
	public float spherecastRasius = 2.5f ;
	#endif

	int myID ;
	bool isCurrentID = true ;

	void Start () {
		thisTransform = this.transform ;
		rootTransform = thisTransform.root ;
		currentAng = thisTransform.localEulerAngles.y ;
		AI_Wheel_Control_Script = thisTransform.GetComponentInParent < AI_Wheel_Control > ();

		targetPos = thisTransform.position + thisTransform.forward * 500.0f ;
	}

	void LateUpdate () {
		if ( isCurrentID ) {
			#if UNITY_ANDROID || UNITY_IPHONE
			Mobile_Control () ;
			#else
			Mouse_Control () ;
			#endif
		}
	}

	#if !UNITY_ANDROID && !UNITY_IPHONE
	void Mouse_Control () {
		;
	}
	#endif

	#if UNITY_ANDROID || UNITY_IPHONE
	void Mobile_Control () {
	// Set Target.
	if ( CrossPlatformInputManager.GetButtonDown ( "Target_Press" ) ) {
	Cast_Ray_Mobile ( new Vector3 ( CrossPlatformInputManager.GetAxis ( "Target_X" ) , CrossPlatformInputManager.GetAxis ( "Target_Y" ) , 0.0f ) ) ;
	}
	// Control GunCam and Aiming.
	if ( CrossPlatformInputManager.GetButtonDown ( "GunCam_Press" ) ) {
	isButtonDown = true ;
	previousMousePos = new Vector3 ( CrossPlatformInputManager.GetAxis ( "GunCam_X" ) , CrossPlatformInputManager.GetAxis ( "GunCam_Y" ) , 0.0f ) ;
	BroadcastMessage ( "GunCam_On" , SendMessageOptions.DontRequireReceiver ) ;
	return ;
	}
	if ( isButtonDown && CrossPlatformInputManager.GetButton ( "GunCam_Press" ) ) {
	Vector3 currentMousePos = new Vector3 ( CrossPlatformInputManager.GetAxis ( "GunCam_X" ) , CrossPlatformInputManager.GetAxis ( "GunCam_Y" ) , 0.0f ) ;
	adjustAng += ( currentMousePos - previousMousePos ) * 0.02f ;
	previousMousePos = currentMousePos ;
	return ;
	}
	if ( CrossPlatformInputManager.GetButtonUp ( "GunCam_Press" ) ) {
	isButtonDown = false ;
	adjustAng = Vector3.zero ;
	BroadcastMessage ( "GunCam_Off" , SendMessageOptions.DontRequireReceiver ) ;
	}
	}
	#endif

	/*
	void Cast_Ray ( Vector3 screenPos , bool isLockOn ) {
		Ray ray = Camera.main.ScreenPointToRay ( screenPos ) ;
		RaycastHit raycastHit ;
		if ( Physics.Raycast ( ray , out raycastHit , 1000.0f ) ) {
			Transform colliderTransform = raycastHit.collider.transform ;
			if ( colliderTransform.root != rootTransform ) {
				if ( isLockOn ) {
					if ( raycastHit.transform.GetComponent < Rigidbody > () ) { //When 'raycastHit.collider.transform' is Turret, 'raycastHit.transform' is the MainBody.
						//				Debug.Log("ray hit the object " + raycastHit.transform.gameObject.name);
						targetTransform = colliderTransform ;
						targetOffset = targetTransform.InverseTransformPoint ( raycastHit.point ) ;
						return ;
					} else {
						targetTransform = null ;
					}
				}
				targetPos = raycastHit.point ;
			} else { // Ray hits itsel
				//		Debug.Log("hit itself!!!");
				screenPos.z = 50.0f ;
				targetPos = Camera.main.ScreenToWorldPoint ( screenPos ) ;
			}
		} else { // Ray does not hit anythig.
			//	Debug.Log("no hit!!");
			screenPos.z = 500.0f ;
			targetPos = Camera.main.ScreenToWorldPoint ( screenPos ) ;
		}
	}
*/

	#if UNITY_ANDROID || UNITY_IPHONE
	void Cast_Ray_Mobile ( Vector3 screenPos ) {
	Ray ray = Camera.main.ScreenPointToRay ( screenPos ) ;
	RaycastHit [] raycastHits ;
	raycastHits = Physics.SphereCastAll ( ray , spherecastRasius , 1000.0f ) ;
	foreach ( RaycastHit raycastHit in raycastHits ) {
	Transform colliderTransform = raycastHit.collider.transform ;
	if ( colliderTransform.root != rootTransform ) {
	if ( raycastHit.transform.GetComponent < Rigidbody > () ) { //When 'raycastHit.collider.transform' is Turret, 'raycastHit.transform' is the MainBody.
	targetTransform = colliderTransform ;
	targetOffset = targetTransform.InverseTransformPoint ( raycastHit.point ) ;
	return ;
	}
	}
	}
	Cast_Ray ( screenPos , true ) ;
	}
	#endif

	void FixedUpdate () {
		// Calculate Angle.
		if(AI_Wheel_Control_Script.targetAITransform != null) {
			targetPos = AI_Wheel_Control_Script.targetAITransform.position;
		}

		local_TargetPos = thisTransform.InverseTransformPoint ( targetPos ) ;
//		Debug.Log ("local position  is: " + local_TargetPos);
		float targetAng = Vector2.Angle ( Vector2.up , new Vector2 ( local_TargetPos.x , local_TargetPos.z ) ) * Mathf.Sign ( local_TargetPos.x ) ;		//angle
		if(targetAng < 0.5f && targetAng > -0.5f) {															//the turret have target at the enemy!
			if(AI_Wheel_Control_Script.closeEnoughToShoot)
				BroadcastMessage ("PointToTargetAndCloseEnough", SendMessageOptions.DontRequireReceiver);
		}
//		Debug.Log ("relatived target is: " + targetAng);
		// Calculate Turn Rate.
		float targetSpeedRate = Mathf.Lerp ( 0.0f , 1.0f , Mathf.Abs ( targetAng ) / ( rotationSpeed * Time.fixedDeltaTime + bufferAngle ) ) * Mathf.Sign ( targetAng ) ;
		// Calculate Rate
		speedRate = Mathf.MoveTowardsAngle ( speedRate , targetSpeedRate , Time.fixedDeltaTime / acceleration_Time ) ;
		// Rotate
		currentAng += rotationSpeed * speedRate * Time.fixedDeltaTime ;
		thisTransform.localRotation = Quaternion.Euler ( new Vector3 ( 0.0f , currentAng , 0.0f ) ) ;
	}

	void Get_ID ( int idNum ) {
		myID = idNum ;
	}

	void Get_Current_ID ( int idNum ) {
		if ( idNum == myID ) {
			isCurrentID = true ;
		} else {
			isCurrentID = false ;
		}
	}
}


AI_Cannon_Control.cs
using UnityEngine;
using System.Collections;

// This script must be attached to "Cannon_Base".

public class AI_Cannon_Control : MonoBehaviour {

	[ Header ( "Cannon movement settings" ) ]
	[ Tooltip ( "Rotation Speed. (Degree per Second)" ) ] public float rotationSpeed = 5.0f ;
	[ Tooltip ( "Angle range for slowing down. (Degree)" ) ] public float bufferAngle = 1.0f ;
	[ Tooltip ( "Maximum elevation angle. (Degree)" ) ] public float maxElev = 3f ;
	[ Tooltip ( "Maximum depression angle. (Degree)" ) ] public float maxDep = 0.5f ;

	Transform thisTransform ;
	AI_Turret_Control turretScript ;
	float currentAng ;
	float adjustAng;
	bool up;
	void Start () {
		thisTransform = this.transform ;
		up = true;
		turretScript = thisTransform.GetComponentInParent < AI_Turret_Control > () ;
		if ( turretScript == null ) {
			Debug.LogError ( "Cannon_Base cannot find Turret_Control_CS." ) ;
			Destroy ( this ) ;
		}
		currentAng = thisTransform.localEulerAngles.x ;
	}

	void FixedUpdate () {
		// Calculate Angle.
		if (up) {									//positive
			adjustAng = Random.Range (0, 300);
		} else {												//negative
			adjustAng = (-1) * Random.Range (0, 300);
		}

		float targetAng = Mathf.Rad2Deg * ( Mathf.Asin ( ( turretScript.local_TargetPos.y - thisTransform.localPosition.y ) / Vector3.Distance ( thisTransform.localPosition , turretScript.local_TargetPos ) ) ) ;
		targetAng += Mathf.DeltaAngle ( 0.0f , thisTransform.localEulerAngles.x ) + adjustAng ;
//		targetAng += Mathf.DeltaAngle ( 0.0f , thisTransform.localEulerAngles.x );
		// Calculate Speed Rate
		float speedRate = -Mathf.Lerp ( 0.0f , 1.0f , Mathf.Abs ( targetAng ) / ( rotationSpeed * Time.fixedDeltaTime + bufferAngle ) ) * Mathf.Sign ( targetAng ) ;
		// Rotate
		currentAng += rotationSpeed * speedRate * Time.fixedDeltaTime ;
		currentAng = Mathf.Clamp ( currentAng , -maxElev , maxDep ) ;
		thisTransform.localRotation = Quaternion.Euler ( new Vector3 ( currentAng , 0.0f , 0.0f ) ) ;
		if ((maxDep - currentAng) <= 0.01)
			up = true;
		if ((currentAng + maxElev) <= 0.01)
			up = false;
	}

}


AI_Fire_Control.cs
using UnityEngine;
using System.Collections;

using UnityStandardAssets.CrossPlatformInput;

// This script must be attached to "Cannon_Base".

public class AI_Fire_Control : MonoBehaviour {

	[ Header ( "Fire control settings" ) ]
	[ Tooltip ( "Loading time. (Sec)" ) ] public float reloadTime = 4.0f ;
	[ Tooltip ( "Recoil force with firing." ) ] public float recoilForce = 5000.0f ;

	bool isReady = true ;
	bool pointToTarget = false;
	Transform thisTransform ;
	Rigidbody parentRigidbody ;

	int myID ;
	bool isCurrentID = true ;

	void Start () {
		thisTransform = this.transform ;
		parentRigidbody = GetComponentInParent < Rigidbody > () ;
		if ( parentRigidbody == null ) {
			Debug.LogError ( "Rigidbody is not found in MainBody." ) ;
			Destroy ( this ) ;
		}
	}

	void Update () {
		if ( isCurrentID ) {
			#if UNITY_ANDROID || UNITY_IPHONE
			if ( CrossPlatformInputManager.GetButtonUp ( "GunCam_Press" ) && isReady ) {
			#else
			if ( pointToTarget && isReady ) {
			#endif
				// Send message to this and 'Barrel_Control' and 'Fire_Spawn'.
				BroadcastMessage ( "Fire" , SendMessageOptions.DontRequireReceiver ) ;
			}
		}
	}

	void PointToTargetAndCloseEnough() {
		pointToTarget = true;
	}

	void Fire () {
		parentRigidbody.AddForceAtPosition ( -thisTransform.forward * recoilForce , thisTransform.position , ForceMode.Impulse ) ;
		pointToTarget = false;
		isReady = false ;
		StartCoroutine ( "Reload" ) ;
	}

	IEnumerator Reload () {
		yield return new WaitForSeconds ( reloadTime ) ;
		isReady = true ;
	}

	void Get_ID ( int idNum ) {
		myID = idNum ;
	}

	void Get_Current_ID ( int idNum ) {
		if ( idNum == myID ) {
			isCurrentID = true ;
		} else {
			isCurrentID = false ;
		}
	}
}


3.注意:
这些脚本是我自己改写的,AI坦克核心的控制脚本。如果只看这些脚本,可能还是无法理解坦克究竟是怎么运动的,因为里面还要用到注入Wheel_Sysn等脚本。可以直接下载我的project,看一下AI坦克的构成部件,以及上面挂着的脚本。

5.一点心得:

1.这次主要目标是实现AI坦克,构建一个预制件。游戏是其次,加上最近时间比较紧,所以没有用到诸如工厂模式,导演和SceneController这一些东西,以后有时间再补上。
2.这次的AI坦克还是比较naive的。
3.学到了不少东西,嘿嘿。以后看看能不能结合navMesh再做一个。

猜你喜欢

转载自blog.csdn.net/kunailin/article/details/72528913