坦克對戰AI設計

作業要求

從商店下載遊戲:“Kawaii” Tank 或 其他坦克模型,構建 AI 對戰坦克。具體要求

  • 使用“感知-思考-行爲”模型,建模 AI 坦克
  • 場景中要放置一些障礙阻擋對手視線
  • 坦克需要放置一個矩陣包圍盒觸發器,以保證 AI 坦克能使用射線探測對手方位
  • AI 坦克必須在有目標條件下使用導航,並能繞過障礙。(失去目標時策略自己思考)
  • 實現人機對戰

實現過程

“感知-思考-行爲”模型在AITank的具體解釋是:

  • 感知周圍是否出現玩家
  • 進行思考,若沒有玩家就進行行動巡邏,若附近有玩家就進行行動追捕。
  • 感知&行爲,若玩家到了AITank的射擊範圍則進行射擊行動,若沒有進入玩家射擊範圍則繼續進行追捕行動。

利用官方教程Tanks! Tutorial裏面的模型構建了自己的遊戲場景
如圖所示
在這裏插入圖片描述
設置navigation,障礙物設置navigation area 爲not walkable
在這裏插入圖片描述
進行bake,以便AI尋路
在這裏插入圖片描述
製作預製
在這裏插入圖片描述
爲enemy和player添加NavMeshAgent組件,當AI坦克發現玩家後能夠自動尋路。通過這些三角網格計算其中任意兩點之間的最短路徑用於遊戲對象的導航,作爲“感知-思考-行爲”模型中的“感知”
在這裏插入圖片描述

具體代碼

坦克基類,記錄血量和開炮操作

public class Tank : MonoBehaviour {
     private float hp =500.0f;
    // 初始化
     public Tank()
     {
         hp = 500.0f;
     }

     public float getHP()
     {
         return hp;
     }

     public void setHP(float hp)
     {
         this.hp = hp;
     }
     //標明子彈所屬的坦克類型,TankType是枚舉變量,在工廠類裏MyFactory定義。
     public void shoot(TankType type)
     {
         GameObject bullet = Singleton<MyFactory>.Instance.getBullets(type);
         bullet.transform.position = new Vector3(transform.position.x, 1.5f, transform.position.z) + transform.forward * 1.5f;
         bullet.transform.forward = transform.forward; //方向
         bullet.GetComponent<Rigidbody>().AddForce(bullet.transform.forward * 20, ForceMode.Impulse);
     }
}

玩家類

public class Player : Tank{
   // player被摧毀時發佈信息;
   public delegate void DestroyPlayer();
   public static event DestroyPlayer destroyEvent;
   void Start () {
       setHP(500);
}

// Update is called once per frame
void Update () {
    if(getHP() <= 0)    // Tank is destoryed
       {
           this.gameObject.SetActive(false);
           destroyEvent();
       }
}

   //向前移動
   public void moveForward()
   {
       gameObject.GetComponent<Rigidbody>().velocity = gameObject.transform.forward * 30;
   }
   //向後移動
   public void moveBackWard()
   {
       gameObject.GetComponent<Rigidbody>().velocity = gameObject.transform.forward * -30;
   }

   //通過水平軸上的增量,改變玩家坦克的歐拉角,從而實現坦克轉向
   public void turn(float offsetX)
   {
       float x = gameObject.transform.localEulerAngles.x;
       float y = gameObject.transform.localEulerAngles.y + offsetX*2;
       gameObject.transform.localEulerAngles = new Vector3(x, y, 0);
   }
}

AI坦克類

public class Player : Tank{
   // player被摧毀時發佈信息;
   public delegate void DestroyPlayer();
   public static event DestroyPlayer destroyEvent;
   void Start () {
       setHP(500);
}

// Update is called once per frame
void Update () {
    if(getHP() <= 0)    // Tank is destoryed
       {
           this.gameObject.SetActive(false);
           destroyEvent();
       }
}

   //向前移動
   public void moveForward()
   {
       gameObject.GetComponent<Rigidbody>().velocity = gameObject.transform.forward * 30;
   }
   //向後移動
   public void moveBackWard()
   {
       gameObject.GetComponent<Rigidbody>().velocity = gameObject.transform.forward * -30;
   }

   //通過水平軸上的增量,改變玩家坦克的歐拉角,從而實現坦克轉向
   public void turn(float offsetX)
   {
       float x = gameObject.transform.localEulerAngles.x;
       float y = gameObject.transform.localEulerAngles.y + offsetX*2;
       gameObject.transform.localEulerAngles = new Vector3(x, y, 0);
   }
}

子彈類
通過OnCollisionEnter事件判斷在子彈碰撞到其他物體時,爆炸範圍內的所有碰撞體對象,如果子彈是AI坦克發射的並且碰撞體爲玩家,則玩家坦克會扣血,子彈失活回收;如果子彈是玩家發射並且碰撞體是AI坦克,則AI坦克扣血。當子彈落地時應該把子彈回收。

public class Bullet : MonoBehaviour {
    // 子彈傷害半徑
    public float explosionRadius = 3f;
    private tankType type;

    public void setTankType(tankType type)
    {
        this.type = type;
    }

    private void Update()
    {
        if(this.transform.position.y < 0 && this.gameObject.activeSelf)
        {
            GameObjectFactory mf = Singleton<GameObjectFactory>.Instance;
            // 落地爆炸
            ParticleSystem explosion = mf.getPs();
            explosion.transform.position = transform.position;
            explosion.Play();
            mf.recycleBullet(this.gameObject);
        }
    }

    void OnCollisionEnter(Collision other)
    {
        // 獲得單實例工廠
        GameObjectFactory mf = Singleton<GameObjectFactory>.Instance;
        ParticleSystem explosion = mf.getPs();
        explosion.transform.position = transform.position;

        // 獲取爆炸範圍內的所有碰撞體
        Collider[] colliders = Physics.OverlapSphere(transform.position, explosionRadius);
        for(int i = 0; i < colliders.Length; i++)
        {
            if(colliders[i].tag == "tankPlayer" && this.type == tankType.Enemy || colliders[i].tag == "tankEnemy" && this.type == tankType.Player)
            {
                // 根據擊中坦克與爆炸中心的距離計算傷害值
                float distance = Vector3.Distance(colliders[i].transform.position, transform.position);//被擊中坦克與爆炸中心的距離
                float hurt = 100f / distance;
                float current = colliders[i].GetComponent<Tank>().getHp();
                c`這裏寫代碼片`olliders[i].GetComponent<Tank>().setHp(current - hurt);
            }
        }

        explosion.Play();
        if (this.gameObject.activeSelf)
        {
            mf.recycleBullet(this.gameObject);
        }
    }
}

工廠類,統一管理玩家、AI坦克、子彈、爆炸粒子等系統對象

public class GameObjectFactory : MonoBehaviour {
    // 玩家
    public GameObject player;
    // npc
    public GameObject tank;
    // 子彈
    public GameObject bullet;
    // 爆炸粒子系統
    public ParticleSystem ps;

    private Dictionary<int, GameObject> usingTanks;
    private Dictionary<int, GameObject> freeTanks;

    private Dictionary<int, GameObject> usingBullets;
    private Dictionary<int, GameObject> freeBullets;

    private List<ParticleSystem> psContainer;

    private void Awake()
    {
        usingTanks = new Dictionary<int, GameObject>();
        freeTanks = new Dictionary<int, GameObject>();
        usingBullets = new Dictionary<int, GameObject>();
        freeBullets = new Dictionary<int, GameObject>();
        psContainer = new List<ParticleSystem>();
    }

    // Use this for initialization
    void Start () {
        //回收坦克的委託事件
        AITank.recycleEvent += recycleTank;
    }

    public GameObject getPlayer()
    {
        return player;
    }

    public GameObject getTank()
    {
        if(freeTanks.Count == 0)
        {
            GameObject newTank = Instantiate<GameObject>(tank);
            usingTanks.Add(newTank.GetInstanceID(), newTank);
            //在一個隨機範圍內設置坦克位置
            newTank.transform.position = new Vector3(Random.Range(-100, 100), 0, Random.Range(-100, 100));
            return newTank;
        }
        foreach (KeyValuePair<int, GameObject> pair in freeTanks)
        {
            pair.Value.SetActive(true);
            freeTanks.Remove(pair.Key);
            usingTanks.Add(pair.Key, pair.Value);
            pair.Value.transform.position = new Vector3(Random.Range(-100, 100), 0, Random.Range(-100, 100));
            return pair.Value;
        }
        return null;
    }

    public GameObject getBullet(tankType type)
    {
        if (freeBullets.Count == 0)
        {
            GameObject newBullet = Instantiate(bullet);
            newBullet.GetComponent<Bullet>().setTankType(type);
            usingBullets.Add(newBullet.GetInstanceID(), newBullet);
            return newBullet;
        }
        foreach (KeyValuePair<int, GameObject> pair in freeBullets)
        {
            pair.Value.SetActive(true);
            pair.Value.GetComponent<Bullet>().setTankType(type);
            freeBullets.Remove(pair.Key);
            usingBullets.Add(pair.Key, pair.Value);
            return pair.Value;
        }
        return null;
    }

    public ParticleSystem getPs()
    {
        for(int i = 0; i < psContainer.Count; i++)
        {
            if (!psContainer[i].isPlaying) return psContainer[i];
        }
        ParticleSystem newPs = Instantiate<ParticleSystem>(ps);
        psContainer.Add(newPs);
        return newPs;
    }

    public void recycleTank(GameObject tank)
    {
        usingTanks.Remove(tank.GetInstanceID());
        freeTanks.Add(tank.GetInstanceID(), tank);
        tank.GetComponent<Rigidbody>().velocity = new Vector3(0, 0, 0);
        tank.SetActive(false);
    }

    public void recycleBullet(GameObject bullet)
    {
        usingBullets.Remove(bullet.GetInstanceID());
        freeBullets.Add(bullet.GetInstanceID(), bullet);
        bullet.GetComponent<Rigidbody>().velocity = new Vector3(0, 0, 0);
        bullet.SetActive(false);
    }
}

場記,獲得工廠實例,做好初始化工作,接受從UI類裏傳過來的要求實現對應的操作,由於Player和Enemy已經實現了自身對應的動作,所以場景中如果接受UI傳來的要求即讓對應的Player和Enemy實例執行對應的操作就行:

public class Player : Tank{
   // player被摧毀時發佈信息;
   public delegate void DestroyPlayer();
   public static event DestroyPlayer destroyEvent;
   void Start () {
       setHP(500);
}

// Update is called once per frame
void Update () {
    if(getHP() <= 0)    // Tank is destoryed
       {
           this.gameObject.SetActive(false);
           destroyEvent();
       }
}

   //向前移動
   public void moveForward()
   {
       gameObject.GetComponent<Rigidbody>().velocity = gameObject.transform.forward * 30;
   }
   //向後移動
   public void moveBackWard()
   {
       gameObject.GetComponent<Rigidbody>().velocity = gameObject.transform.forward * -30;
   }

   //通過水平軸上的增量,改變玩家坦克的歐拉角,從而實現坦克轉向
   public void turn(float offsetX)
   {
       float x = gameObject.transform.localEulerAngles.x;
       float y = gameObject.transform.localEulerAngles.y + offsetX*2;
       gameObject.transform.localEulerAngles = new Vector3(x, y, 0);
   }
}

UI類,檢測玩家輸入,對應操作坦克

public class Player : Tank{
   // player被摧毀時發佈信息;
   public delegate void DestroyPlayer();
   public static event DestroyPlayer destroyEvent;
   void Start () {
       setHP(500);
}

// Update is called once per frame
void Update () {
    if(getHP() <= 0)    // Tank is destoryed
       {
           this.gameObject.SetActive(false);
           destroyEvent();
       }
}

   //向前移動
   public void moveForward()
   {
       gameObject.GetComponent<Rigidbody>().velocity = gameObject.transform.forward * 30;
   }
   //向後移動
   public void moveBackWard()
   {
       gameObject.GetComponent<Rigidbody>().velocity = gameObject.transform.forward * -30;
   }

   //通過水平軸上的增量,改變玩家坦克的歐拉角,從而實現坦克轉向
   public void turn(float offsetX)
   {
       float x = gameObject.transform.localEulerAngles.x;
       float y = gameObject.transform.localEulerAngles.y + offsetX*2;
       gameObject.transform.localEulerAngles = new Vector3(x, y, 0);
   }
}

相關鏈接

演示視頻
項目地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章