遊戲智能AITank
從商店下載遊戲:“Kawaii” Tank 或 其他坦克模型,構建 AI 對戰坦克。具體要求
- 使用“感知-思考-行爲”模型,建模 AI 坦克
- 場景中要放置一些障礙阻擋對手視線
- 坦克需要放置一個矩陣包圍盒觸發器,以保證 AI 坦克能使用射線探測對手方位
- AI 坦克必須在有目標條件下使用導航,並能繞過障礙。(失去目標時策略自己思考)
- 實現人機對戰
Github的地址是:GitHub
對於這個任務,我首先在UnityHub裏面找到一份坦克的教程:
然後按照它的教程完成所需要的內容。
在這裏開始我們的作業任務:
首先是完成AITank的代碼,首先AITank保持巡邏的狀態一直到在附近發現了玩家爲止,在發現玩家之後NPC會對玩家進行追捕,這個時候就把玩家的位置設置爲NPC坦克的目標點,同時在玩家進入NPC的範圍之後開始按照一秒一炮的頻率開炮。代碼如下:
public class AITank : Tank {
public delegate void recycle(GameObject tank);
public static event recycle recycleEvent;
private Vector3 target;
private bool gameover;
// 巡邏
private static Vector3[] points = { new Vector3(37.6f,0,0), new Vector3(40.9f,0,39), new Vector3(13.4f, 0, 39),
new Vector3(13.4f, 0, 21), new Vector3(0,0,0), new Vector3(-20,0,0.3f), new Vector3(-20, 0, 32.9f),
new Vector3(-37.5f, 0, 40.3f), new Vector3(-37.5f,0,10.4f), new Vector3(-40.9f, 0, -25.7f), new Vector3(-15.2f, 0, -37.6f),
new Vector3(18.8f, 0, -37.6f), new Vector3(39.1f, 0, -18.1f)
};
private int destPoint = 0;
private NavMeshAgent agent;
private bool isPatrol = false;
private void Awake()
{
destPoint = UnityEngine.Random.Range(0, 13);
}
// Use this for initialization
void Start () {
setHp(100f);
StartCoroutine(shoot());
agent = GetComponent<NavMeshAgent>();
}
private IEnumerator shoot()
{
while (!gameover)
{
for(float i = 1; i > 0; i -= Time.deltaTime)
{
yield return 0;
}
// 進入射程開始開炮
if(Vector3.Distance(transform.position, target) < 20)
{
GameObjectFactory mf = Singleton<GameObjectFactory>.Instance;
GameObject bullet = mf.getBullet(tankType.Enemy);
bullet.transform.position = new Vector3(transform.position.x, 1.5f, transform.position.z) + transform.forward * 1.5f;
bullet.transform.forward = transform.forward;
Rigidbody rb = bullet.GetComponent<Rigidbody>();
rb.AddForce(bullet.transform.forward * 20, ForceMode.Impulse);
}
}
}
// Update is called once per frame
void Update () {
gameover = GameDirector.getInstance().currentSceneController.isGameOver();
if (!gameover)
{
target = GameDirector.getInstance().currentSceneController.getPlayerPos();
if (getHp() <= 0 && recycleEvent != null)
{//如果npc坦克被摧毀,則回收它
recycleEvent(this.gameObject);
}
else
{
if(Vector3.Distance(transform.position, target) <= 30)
{
isPatrol = false;
agent.autoBraking = true;
agent.SetDestination(target);
}
else
{
patrol();
}
}
}
else
{
NavMeshAgent agent = GetComponent<NavMeshAgent>();
agent.velocity = Vector3.zero;
agent.ResetPath();
}
}
private void patrol()
{
if(isPatrol)
{
if(!agent.pathPending && agent.remainingDistance < 0.5f)
GotoNextPoint();
}
else
{
agent.autoBraking = false;
GotoNextPoint();
}
isPatrol = true;
}
private void GotoNextPoint()
{
agent.SetDestination(points[destPoint]);
destPoint = (destPoint + 1) % points.Length;
}
}
然後是對於子彈的設置,通過OnCollisionEnter事件判斷子彈碰撞到其他物體時,爆炸範圍內所有的碰撞體對象如果是敵方陣營的就會扣血(按照子彈爆炸的距離和敵方坦克的距離來計算上海),同時當子彈落地也就是沒有用之後要進行回收。
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);
}
}
}
在這裏使用過一個GameObjectFactory工廠來維護遊戲對象。
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);
}
}
場記SceneController代碼:
public class SceneController : MonoBehaviour, IUserAction {
public GameObject player;
private bool gameOver = false;
private int enemyCount = 6;
private GameObjectFactory mf;
private MainCameraControl cameraControl;
private void Awake()
{
GameDirector director = GameDirector.getInstance();
director.currentSceneController = this;
mf = Singleton<GameObjectFactory>.Instance;
player = mf.getPlayer();
cameraControl = GetComponent<MainCameraControl>();
cameraControl.setTarget(player.transform);
}
// Use this for initialization
void Start () {
for(int i = 0; i < enemyCount; i++)
{
GameObject gb = mf.getTank();
cameraControl.setTarget(gb.transform);
}
Player.destroyEvent += setGameOver;
// 初始化相機位置
cameraControl.SetStartPositionAndSize();
}
// 更新相機位置
void Update () {
Camera.main.transform.position = new Vector3(player.transform.position.x, 15, player.transform.position.z);
}
public Vector3 getPlayerPos()
{
return player.transform.position;
}
public bool isGameOver()
{
return gameOver;
}
public void setGameOver()
{
gameOver = true;
}
public void moveForward()
{
player.GetComponent<Rigidbody>().velocity = player.transform.forward * 20;
}
public void moveBackWard()
{
player.GetComponent<Rigidbody>().velocity = player.transform.forward * -20;
}
public void turn(float offsetX)
{
float y = player.transform.localEulerAngles.y + offsetX * 5;
float x = player.transform.localEulerAngles.x;
player.transform.localEulerAngles = new Vector3(x, y, 0);
}
public void shoot()
{
GameObject bullet = mf.getBullet(tankType.Player);
// 設置子彈位置
bullet.transform.position = new Vector3(player.transform.position.x, 1.5f, player.transform.position.z) + player.transform.forward * 1.5f;
// 設置子彈方向
bullet.transform.forward = player.transform.forward;
// 發射子彈
Rigidbody rb = bullet.GetComponent<Rigidbody>();
rb.AddForce(bullet.transform.forward * 20, ForceMode.Impulse);
}
}
具體就是如上了。