首页 > 代码库 > 【跟我一起学Unity3D】做一个2D的90坦克大战之AI系统

【跟我一起学Unity3D】做一个2D的90坦克大战之AI系统

对于AI,我的初始想法很简单,首先他要能动,并且是在地图里面动, 懂得撞墙后转弯,然后懂得射击,其他的没有了,基于这个想法,我首先创建了一个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主要是添加一些触发器,例如检测到碰撞就转弯,每隔两秒就发射子弹等等,作为一个非常简单的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的角度。

然后到开火,开火的设定非常简单,调用到一个函数: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坦克大战之AI系统