【跟我一起學Unity3D】做一個2D的90坦克大戰之AI系統


對於AI,我的初始想法非常easy,首先他要能動,而且是在地圖里面動。 懂得撞牆后轉彎,然后懂得射擊,其它的沒有了,基於這個想法,我首先創建了一個MyTank類,用於管理玩家的坦克的活動,然后創建AITank類,AITank類繼承MyTank類。這種話。在AITank類上,僅僅須要添加AI就能夠了。詳細的狀態機實現,就放到MyTank類上就可以。

首先來分析一下MyTank這個類,就從有限狀態機開始吧。

一輛坦克的狀態有以下幾個:

    protected enum State
    {
        Idle,
        LeftWalk,
        RightWalk,
        UpWalk,
        DownWalk,
        Fire
    };

各自是:站立。向左走,向右走,向上走,向下走,開火。

然后就是分別實現每一個狀態,首先是轉向:

    protected void UpdateRotate(State state)
    {
        switch (state)
        {
            case State.LeftWalk:
                this.transform.rotation = Quaternion.identity;
                this.transform.Rotate(Vector3.forward * m_fAngle);
                m_curAngle = Vector3.left;
                break;
            case State.RightWalk:
                this.transform.rotation = Quaternion.identity;
                this.transform.Rotate(Vector3.forward * -m_fAngle);
                m_curAngle = Vector3.right;
                break;
            case State.UpWalk:
                this.transform.rotation = Quaternion.identity;
                m_curAngle = Vector3.up;
                break;
            case State.DownWalk:
                this.transform.rotation = Quaternion.identity;
                this.transform.Rotate(Vector3.forward * m_fAngle * 2);
                m_curAngle = Vector3.down;
                break;
        }
    }

然后是行走和開火:

    public virtual void UpdateState()
    {
        Debug.Log(m_sName + ":UpdateState()");
        switch (m_curState)
        {
            case State.Idle:
                break;
            case State.LeftWalk:
                this.transform.position += Vector3.left * Time.deltaTime * m_fMoveSpeed;
                break;
            case State.UpWalk:
                this.transform.position += Vector3.up * Time.deltaTime * m_fMoveSpeed;
                break;
            case State.DownWalk:
                this.transform.position += Vector3.down * Time.deltaTime * m_fMoveSpeed;
                break;
            case State.RightWalk:
                this.transform.position += Vector3.right * Time.deltaTime * m_fMoveSpeed;
                break;
            case State.Fire:
                doFire();
                break;
        }
    }
當中,doFire的函數實現例如以下:

    protected virtual void doFire()
    {
        GameObject bullet = Instantiate(m_gBullet) as GameObject;
        bullet.name = m_sName + "Bullet";
        bullet.GetComponent<CBullet>().m_vDirection = m_curAngle;
        bullet.GetComponent<CBullet>().m_fMoveSpeed = m_fMoveSpeed + 1.0f;
        bullet.transform.position = new Vector3(this.transform.position.x, this.transform.position.y,0);
        Destroy(bullet, 5.0f);
    }

設計為虛函數的原因是。AITank須要重寫這個函數。
然后就須要監聽各個按鍵,我設定為,按下A,W,S,D為方向鍵。松開就停止移動。然后按下K則開火,所以在Update函數里面應該這么實現:

    void Update()
    {
        SetCurState(State.Idle);
        //轉向
        if (Input.GetKey(KeyCode.A))
        {
            UpdateRotate(State.LeftWalk);
            SetCurState(State.LeftWalk);
        }
        if (Input.GetKey(KeyCode.W))
        {
            UpdateRotate(State.UpWalk);
            SetCurState(State.UpWalk);
        }
        if (Input.GetKey(KeyCode.S))
        {
            UpdateRotate(State.DownWalk);
            SetCurState(State.DownWalk);
        }
        if (Input.GetKey(KeyCode.D))
        {
            UpdateRotate(State.RightWalk);
            SetCurState(State.RightWalk);
        }
        if (Input.GetKeyDown(KeyCode.K))
        {
            SetCurState(State.Fire);
        }
        UpdateState();
    }

當中SetCurState這個函數是用來設置當前狀態的,而且把上一次的狀態也存儲起來,方便使用。

    protected void SetCurState(State curState)
    {
        if (curState != m_curState)
            m_lastState = m_curState;
        m_curState = curState;
    }

最后就到了一些碰撞檢測的函數了,比如,碰到敵人的子彈就生命值減一,生命值為0了就宣布游戲結束。

    void OnTriggerEnter2D(Collider2D other)
    {
        Debug.Log(m_sName + " OnTriggerEnter : " + other.gameObject.name);
        //被打中了
        if (other.gameObject.name == "AIBullet")
        {
            Destroy(other.gameObject);
            if (Camera.main.GetComponent<CCamera>().ReduceMyLeft() > 0)
            {
                GameObject temp = Instantiate(m_BoomAnimation, this.gameObject.transform.position, Quaternion.identity) as GameObject;
                Destroy(temp, 0.5f);
                string name = this.gameObject.name;
                GameObject temp_tank = Instantiate(this.gameObject, m_initPosition, Quaternion.identity) as GameObject;
                temp_tank.name = name;
            }
            else
            {
                //你輸了
                Camera.main.GetComponent<CCamera>().m_bIsLose = true;
            }
            Destroy(this.gameObject);

        }
    }

上面就是整個MyTank的實現過程了

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以下到了AITank的實現,AITank主要是加入一些觸發器,比如檢測到碰撞就轉彎,每隔兩秒就發射子彈等等,作為一個很easy的AI,就不須要考慮那么多。首先是碰撞檢測:

    void OnCollisionEnter2D(Collision2D coll)
    {
        Debug.Log(m_sName + " OnCollisionEnter2D : " + coll.gameObject.name);
        ChangeStateFromWall();
    }
遇到碰撞就轉向,然后ChangeStateFromWall的詳細實現例如以下:

    void ChangeStateFromWall()
    {
        switch (m_curState)
        {
            case State.Idle:
                SetCurState(State.DownWalk);
                break;
            case State.LeftWalk:
                SetCurState(State.UpWalk);
                break;
            case State.UpWalk:
                SetCurState(State.RightWalk);
                break;
            case State.DownWalk:
                SetCurState(State.LeftWalk);
                break;
            case State.RightWalk:
                SetCurState(State.DownWalk);
                break;
            case State.Fire:
                SetCurState(m_lastState);
                break;
        }
        this.UpdateRotate(m_curState);
    }
}

這是一個循環轉向的函數,假設當前方向是左邊,就往上走,假設當前方向是上邊。就往右走....;假設在開火狀態的話。就返回上一狀態。最后更新AI的角度。

然后到開火,開火的設定很easy,調用到一個函數:InvokeRepeating。這個是一個反復定時器。函數原型為:

void InvokeRepeating (string methodName,float time,float repeatRate) 

第一個參數是傳入的方法。第二個參數是在幾秒后開始。第三個參數是開始后每隔幾秒反復運行。

我們須要的是在AI坦克生成后一秒發射子彈 然后周期是每隔兩秒發射一次。所以在Start函數中應該這么寫:

        //每隔2秒開一次火
        InvokeRepeating("doFire", 1, 2);
然后,我們須要重寫doFire這個函數:

    protected override void doFire()
    {
        base.doFire();
    }

這樣,就完畢了每兩秒發射一發子彈的功能了。

上面就是AI的基本功能了,以后能夠慢慢優化它。使它成為一個強大的AI。

項目源代碼:【跟我一起學Unity3D】做一個2D的90坦克大戰之各種各樣的牆<<附上項目源代碼>>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM