3d学习笔记(九)——AI坦克大战

AI坦克大战


实验内容

坦克对战游戏 AI 设计

  • 从商店下载游戏:“Kawaii” Tank 或 其他坦克模型,构建 AI 对战坦克。具体要求
  • 使用“感知-思考-行为”模型,建模 AI 坦克
  • 场景中要放置一些障碍阻挡对手视线
  • 坦克需要放置一个矩阵包围盒触发器,以保证 AI 坦克能使用射线探测对手方位
  • AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
  • 实验人机对战

实验结果


实验操作

实现的思路

个人觉得本次的实验,重点在于对上课时的“感知-思考-行为”模型的理解,同时还在于对老师要求的下载的预制的代码阅读(代码量有点多,需要花费大量时间来理解这个预制是如何进行工作的)

实现AI的思路,在于坦克能够根据目前的情况来进行相对应的判断,以下是我的设计思路:

  • 但视野内没有玩家,坦克会不停旋转,进行360度无死角的扫描
  • 但视野内出现玩家时,首先对玩家的位置进行判断,进行左转或者右转操作,并判断两者之间的距离,如果距离超过100,则进行前进;当距离小于50时,会进行开火

预制的代码解析:

以下是预制中部分需要用到的脚本文件的作用:

  • ID_Control_CS:是整个坦克的控制中心
  • Wheel_Control_CS:坦克移动的输入,不过只进行前进后退操作
  • Wheel_Rotate_CS:控制坦克的转动
  • Fire_Control_CS:控制坦克的开火

其他代码其实可以做进一步的修改,达到自己想要的AI效果

细节的实现

视野的扫描(感知)

通过发射射线来进行检查,射线撞到的第一个物体是否是玩家(可以添加标签来确定);
同时,我的视野是左边45度,到右边45度,这里可以通过调整射线的发射方向来实现,这个用矢量相加即可。我是用等差控制,不过这个有个弊端,离得远的话,可能有些在视野内扫描不到。但是这也符合了显示生活,远距离可能会看不清或者忽略。

操作的判断(思考)
  • 首先是判断是否找到玩家,没有则原地打转
  • 再判断彼此间的位置关系,如果在左边则左转,右边则右转。这里用到了函数InverseTransformPoint,返回一个向量代表两者间的关系
  • 再对坦克跟目标点之间距离的判断,如果超过100,则前进,如果小于50则开火射击
AI行动(行动)

通过添加一系列的参数,来代替键盘跟鼠标的输入,从而来调动坦克的行为。这里坦克如何行动不用我们去实现,我只需要模拟要的行为的相关按键即可实现。

用到参数如下:

  • fireButton:判断是否进行开火
  • leftButton:判断是否进行左转
  • rightButton:判断是否进行右转
  • upButton:判断是否进行前进

代码的具体实现:

主要是修改了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 <Game_Controller_CS> ();
            }
            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 <Wheel_Rotate_CS> ();
            */
        }

        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的代码:

扫描二维码关注公众号,回复: 1807055 查看本文章
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 <Barrel_Control_CS> ();
            fireScripts = GetComponentsInChildren <Fire_Spawn_CS> ();
        }

        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;
        }

    }

}

实验反思:

  • 由于预制是有提供场景的,因此并未用到课堂上学习到的导航
  • 另外,同样跟师兄博客的内容一样,给坦克加了刚体后,出现了莫名奇妙的鬼畜,应该是刚体本身跟地板的碰撞,因为坦克本身在颤抖
  • 其实感觉AI还是可以进一步来优化,不过时间比较紧,就没做得太完善了,暑假有时间再进一步的修改

猜你喜欢

转载自blog.csdn.net/qq_36312878/article/details/80738024
今日推荐