个人觉得本次的实验,重点在于对上课时的“感知-思考-行为”模型的理解,同时还在于对老师要求的下载的预制的代码阅读(代码量有点多,需要花费大量时间来理解这个预制是如何进行工作的)
实现AI的思路,在于坦克能够根据目前的情况来进行相对应的判断,以下是我的设计思路:
以下是预制中部分需要用到的脚本文件的作用:
其他代码其实可以做进一步的修改,达到自己想要的AI效果
通过发射射线来进行检查,射线撞到的第一个物体是否是玩家(可以添加标签来确定);
同时,我的视野是左边45度,到右边45度,这里可以通过调整射线的发射方向来实现,这个用矢量相加即可。我是用等差控制,不过这个有个弊端,离得远的话,可能有些在视野内扫描不到。但是这也符合了显示生活,远距离可能会看不清或者忽略。
通过添加一系列的参数,来代替键盘跟鼠标的输入,从而来调动坦克的行为。这里坦克如何行动不用我们去实现,我只需要模拟要的行为的相关按键即可实现。
用到参数如下:
主要是修改了ID_Control_CS跟Wheel_Control_CS、Fire_Control_CS三个文件的内容:
ID_Control_CS的代码:
using UnityEngine;
using System;
using System.Collections;
using UnityEngine.UI;
// This script must be attached to the top object of the tank.
namespace ChobiAssets.KTP
{
public class ID_Control_CS : MonoBehaviour
{
[Header ("ID settings")]
[Tooltip ("ID number")] public int id = 0;
[HideInInspector] public bool isPlayer; // Referred to from child objects.
[HideInInspector] public Game_Controller_CS controllerScript;
[HideInInspector] public TankProp storedTankProp; // Set by "Game_Controller_CS".
[HideInInspector] public Turret_Control_CS turretScript;
[HideInInspector] public Camera_Zoom_CS mainCamScript;
[HideInInspector] public GunCamera_Control_CS gunCamScript;
void Start ()
{ // Do not change to "Awake ()".
// Send this reference to the "Game_Controller" in the scene.
GameObject gameController = GameObject.FindGameObjectWithTag ("GameController");
if (gameController) {
controllerScript = gameController.GetComponent ();
}
if (controllerScript) {
controllerScript.Receive_ID (this);
} else {
Debug.LogError ("There is no 'Game_Controller' in the scene.");
}
// Broadcast this reference.
BroadcastMessage ("Get_ID_Script", this, SendMessageOptions.DontRequireReceiver);
}
#if !UNITY_ANDROID && !UNITY_IPHONE
[HideInInspector] public bool aimButton;
[HideInInspector] public bool aimButtonDown;
[HideInInspector] public bool aimButtonUp;
[HideInInspector] public bool dragButton;
[HideInInspector] public bool dragButtonDown;
[HideInInspector] public bool fireButton;//判断是否开火
[HideInInspector] public bool leftButton = false;//判断是否向左
[HideInInspector] public bool rightButton = false;//判断是否向右
[HideInInspector] public bool upButton = false;//判断是否前进
void Update ()
{
if (isPlayer) {
aimButton = Input.GetKey (KeyCode.Space);
aimButtonDown = Input.GetKeyDown (KeyCode.Space);
aimButtonUp = Input.GetKeyUp (KeyCode.Space);
dragButton = Input.GetMouseButton (1);
dragButtonDown = Input.GetMouseButtonDown (1);
fireButton = Input.GetMouseButton (0);
}
else
{
}
}
#endif
void Destroy ()
{ // Called from "Damage_Control_CS".
gameObject.tag = "Finish";
}
public void Get_Current_ID (int currentID)
{ // Called from "Game_Controller_CS".
if (id == currentID) {
isPlayer = true;
} else {
isPlayer = false;
}
// Call Switch_Player.
turretScript.Switch_Player (isPlayer);
mainCamScript.Switch_Player (isPlayer);
gunCamScript.Switch_Player (isPlayer);
}
}
}
Wheel_Control_CS的代码:
using UnityEngine;
using System.Collections;
#if UNITY_ANDROID || UNITY_IPHONE
using UnityStandardAssets.CrossPlatformInput;
#endif
// This script must be attached to "MainBody".
namespace ChobiAssets.KTP
{
public class Wheel_Control_CS : MonoBehaviour
{
[ Header ("Driving settings")]
[ Tooltip ("Torque added to each wheel.")] public float wheelTorque = 3000.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, 2.0f)] public float turnClamp = 0.8f;
[ Tooltip ("'Solver Iteration Count' of all the rigidbodies in this tank.")] public int solverIterationCount = 7;
// Reference to "Wheel_Rotate".
[HideInInspector] public float leftRate;
[HideInInspector] public float rightRate;
Rigidbody thisRigidbody;
bool isParkingBrake = false;
float lagCount;
float speedStep;
float autoParkingBrakeVelocity = 0.5f;
float autoParkingBrakeLag = 0.5f;
ID_Control_CS idScript;
/* for reducing Calls.
Wheel_Rotate_CS[] rotateScripts;
*/
void Awake ()
{
this.gameObject.layer = 11; // Layer11 >> for MainBody.
thisRigidbody = GetComponent < Rigidbody > ();
thisRigidbody.solverIterations = solverIterationCount;
/* for reducing Calls.
rotateScripts = GetComponentsInChildren ();
*/
}
void Update ()
{
if (idScript.isPlayer) {
#if UNITY_ANDROID || UNITY_IPHONE
Mobile_Input ();
#else
Desktop_Input ();
#endif
}
else
{
RaycastHit hit;
Vector3 target = new Vector3(0,0,0);
float i = 1.0f;
bool flag = false;
//当检测到时,需要break去进行操作
while(i >= -1.0f)
{
Vector3 _dir = new Vector3(1, 0, 0);
_dir.z += i;
i -= 0.01f;
if (Physics.Raycast(transform.position, _dir, out hit))
{
if (hit.rigidbody != null)
{
//Debug.Log(hit.rigidbody.gameObject.tag);
//Debug.Log(hit.rigidbody.gameObject.transform.position);
if (hit.rigidbody.gameObject.tag == "Player")
{
flag = true;
target = hit.rigidbody.gameObject.transform.position;
break;
}
}
}
}
if(flag)
{
Vector3 localPositionOfTarget = this.transform.InverseTransformPoint(target);
//确定好运动的方向
if(localPositionOfTarget.x > 0)
{
idScript.rightButton = true;
idScript.leftButton = false;
}
else if(localPositionOfTarget.x < 0)
{
idScript.rightButton = false;
idScript.leftButton = true;
}
else
{
idScript.rightButton = false;
idScript.leftButton = false;
}
//确定是否前进与开火
var dis = Vector3.Distance(target, this.transform.position);
if(dis > 50)
{
idScript.upButton = true;
idScript.fireButton = false;
}
else
{
idScript.upButton = false;
idScript.fireButton = true;
}
}
else
{
idScript.fireButton = false;
idScript.leftButton = true;
idScript.rightButton = false;
idScript.upButton = false;
}
AI_Input();
}
}
#if UNITY_ANDROID || UNITY_IPHONE
void Mobile_Input ()
{
if (CrossPlatformInputManager.GetButtonDown ("Up")) {
speedStep += 0.5f;
speedStep = Mathf.Clamp (speedStep, -1.0f, 1.0f);
} else if (CrossPlatformInputManager.GetButtonDown ("Down")) {
speedStep -= 0.5f;
speedStep = Mathf.Clamp (speedStep, -1.0f, 1.0f);
}
float vertical = speedStep;
float horizontal = 0.0f;
if (CrossPlatformInputManager.GetButton ("Left")) {
horizontal = Mathf.Lerp (-turnClamp, -1.0f, Mathf.Abs (vertical / 1.0f));
} else if (CrossPlatformInputManager.GetButton ("Right")) {
horizontal = Mathf.Lerp (turnClamp, 1.0f, Mathf.Abs (vertical / 1.0f));
}
if (vertical < 0.0f) {
horizontal = -horizontal; // like a brake-turn.
}
leftRate = Mathf.Clamp (-vertical - horizontal, -1.0f, 1.0f);
rightRate = Mathf.Clamp (vertical - horizontal, -1.0f, 1.0f);
}
#else
void AI_Input()
{
if (idScript.upButton)
{
speedStep += 0.5f;
speedStep = Mathf.Clamp(speedStep, -1.0f, 1.0f);
}
else if (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.S))
{
speedStep -= 0.5f;
speedStep = Mathf.Clamp(speedStep, -1.0f, 1.0f);
}
else if (Input.GetKeyDown(KeyCode.X))
{
speedStep = 0.0f;
}
float vertical = speedStep;
//float horizontal = Input.GetAxis("Horizontal");
float horizontal = 0;
if (idScript.leftButton) horizontal = -0.05f;
if (idScript.rightButton) horizontal = 0.05f;
float clamp = Mathf.Lerp(turnClamp, 1.0f, Mathf.Abs(vertical / 1.0f));
horizontal = Mathf.Clamp(horizontal, -clamp, clamp);
if (vertical < 0.0f)
{
horizontal = -horizontal; // like a brake-turn.
}
leftRate = Mathf.Clamp(-vertical - horizontal, -1.0f, 1.0f);
rightRate = Mathf.Clamp(vertical - horizontal, -1.0f, 1.0f);
}
void Desktop_Input ()
{
if (Input.GetKeyDown (KeyCode.UpArrow) || Input.GetKeyDown (KeyCode.W)) {
speedStep += 0.5f;
speedStep = Mathf.Clamp (speedStep, -1.0f, 1.0f);
} else if (Input.GetKeyDown (KeyCode.DownArrow) || Input.GetKeyDown (KeyCode.S)) {
speedStep -= 0.5f;
speedStep = Mathf.Clamp (speedStep, -1.0f, 1.0f);
} else if (Input.GetKeyDown (KeyCode.X)) {
speedStep = 0.0f;
}
float vertical = speedStep;
float horizontal = Input.GetAxis ("Horizontal");
//Debug.Log(horizontal);
float clamp = Mathf.Lerp (turnClamp, 1.0f, Mathf.Abs (vertical / 1.0f));
horizontal = Mathf.Clamp (horizontal, -clamp, clamp);
if (vertical < 0.0f) {
horizontal = -horizontal; // like a brake-turn.
}
leftRate = Mathf.Clamp (-vertical - horizontal, -1.0f, 1.0f);
rightRate = Mathf.Clamp (vertical - horizontal, -1.0f, 1.0f);
}
#endif
void FixedUpdate ()
{
// Auto Parking Brake using 'RigidbodyConstraints'.
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;
}
/* for reducing Calls.
for (int i = 0; i < rotateScripts.Length; i++) {
rotateScripts [i].FixedUpdate_Me ();
}
*/
}
void Destroy ()
{ // Called from "Damage_Control_CS".
StartCoroutine ("Disable_Constraints");
}
IEnumerator Disable_Constraints ()
{
// Disable constraints of MainBody's rigidbody.
yield return new WaitForFixedUpdate (); // This wait is required for PhysX.
thisRigidbody.constraints = RigidbodyConstraints.None;
Destroy (this);
}
void Get_ID_Script (ID_Control_CS tempScript)
{
idScript = tempScript;
}
void Pause (bool isPaused)
{ // Called from "Game_Controller_CS".
this.enabled = !isPaused;
}
}
}
Fire_Control_CS的代码:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
#if UNITY_ANDROID || UNITY_IPHONE
using UnityStandardAssets.CrossPlatformInput;
#endif
// This script must be attached to "Cannon_Base".
namespace ChobiAssets.KTP
{
public class Fire_Control_CS : 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;
Transform thisTransform;
Rigidbody bodyRigidbody;
ID_Control_CS idScript;
Barrel_Control_CS[] barrelScripts;
Fire_Spawn_CS[] fireScripts;
void Awake ()
{
thisTransform = this.transform;
barrelScripts = GetComponentsInChildren ();
fireScripts = GetComponentsInChildren ();
}
void Update ()
{
if (idScript.isPlayer) {
#if UNITY_ANDROID || UNITY_IPHONE
Mobile_Input ();
#else
Desktop_Input ();
#endif
}
else
{
if(isReady && idScript.fireButton) Fire();
}
}
#if UNITY_ANDROID || UNITY_IPHONE
void Mobile_Input ()
{
if (CrossPlatformInputManager.GetButtonUp ("GunCam_Press") && isReady) {
Fire ();
}
}
#else
void Desktop_Input ()
{
if (idScript.fireButton && isReady) {
Fire ();
}
}
#endif
void Fire ()
{
// Call barrelScripts and fireScripts to fire.
for (int i = 0; i < barrelScripts.Length; i++) {
barrelScripts [i].Fire ();
}
for (int i = 0; i < fireScripts.Length; i++) {
fireScripts [i].StartCoroutine ("Fire");
}
// Add recoil shock.
bodyRigidbody.AddForceAtPosition (-thisTransform.forward * recoilForce, thisTransform.position, ForceMode.Impulse);
isReady = false;
StartCoroutine ("Reload");
}
IEnumerator Reload ()
{
yield return new WaitForSeconds (reloadTime);
isReady = true;
}
void Destroy ()
{ // Called from "Damage_Control_CS".
Destroy (this);
}
void Get_ID_Script (ID_Control_CS tempScript)
{
idScript = tempScript;
bodyRigidbody = idScript.storedTankProp.bodyRigidbody;
}
void Pause (bool isPaused)
{ // Called from "Game_Controller_CS".
this.enabled = !isPaused;
}
}
}