一、作业要求
坦克对战游戏 AI 设计
- 从商店下载游戏:“Kawaii” Tank 或 其他坦克模型,构建 AI 对战坦克。具体要求
使用“感知-思考-行为”模型,建模 AI 坦克 - 场景中要放置一些障碍阻挡对手视线
- 坦克需要放置一个矩阵包围盒触发器,以保证 AI 坦克能使用射线探测对手方位
- AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
- 实现人机对战
二、相关知识
1.“感知-思考-行为”模型
“sense-think-act” paradigm(范式) 是构造 agent、robot、NPC(Non-Player Character) 的基础概念。
- 感知是 agent 接收世界信息的行为,其获取的数据将是思考的输入。
- Think 就是算法,它的输入是感知的数据,输出是行为(behaviours)。 思考的算法,通常就是我们所说的游戏规则的一部分,即 agent 能做什么,该做什么。
- 行动(Act)将思考(Think)的结果作为输入,该部分的任务就是使得 agent 行为更符合物理世界的规律,使得“心想事成”这样理想的结果变得不确定。
2.导航与寻路
Unity 导航系统允许创建给游戏角色导航的游戏世界。官方文档导航系统
- NavMesh (Navigation Mesh) 是一种数据结构,它描述了游戏对象可行走的表面。通过三角网格,计算其中任意两点之间的最短路径,用于游戏对象的导航。它是根据场景几何结构自动创建或烘焙构建。
- NavMesh Agent组件创建具有寻路能力的角色。Agent 使用NavMesh 推理,避免彼此以及移动障碍物。
- Off-Mesh Link组件允许将不连接的块之间建立“传送门”。例如,跳过沟渠或围栏,或在穿过它之前打开门,都可以被描述为 Off-Mesh Link。
- NavMesh 障碍 组件允许您描述 agent 在移动时应避免的移动障碍。由物理系统控制的桶或箱子就是很典型的障碍。在障碍物移动的过程中,- Agent 尽力避开它,但一旦障碍物变得静止,它将在导航网格上开一个洞,以便 Agent 可以改变他们的路径以绕过它,或者静止的障碍物阻塞路径,使得 Agent 找到其他路线。
三、关键部分
1.工厂模式生产需要用到的资源如坦克、子弹等,以及它们的回收利用。
//生产坦克
if (freeTanks.Count <= 0){
newTank = GameObject.Instantiate<GameObject>(enemy) as GameObject;
usingTanks.Add(newTank);
newTank.transform.position = new Vector3(Random.Range(-100, 100), 0, Random.Range(-100, 100));
}
else
{
newTank = freeTanks[0];
freeTanks.RemoveAt(0);
usingTanks.Add(newTank);
}
//生产子弹
if(freeBullets.Count <= 0){
newBullet = GameObject.Instantiate<GameObject>(bullet) as GameObject;
usingBullets.Add(newBullet);
newBullet.transform.position = new Vector3(Random.Range(-100, 100), 0, Random.Range(-100, 100));
}
else
{
newBullet = freeBullets[0];
freeBullets.RemoveAt(0);
usingBullets.Add(newBullet);
}
//回收
public void recycleEnemy(GameObject enemyTank)
{
usingTanks.Remove(enemyTank);
freeTanks.Add(enemyTank);
enemyTank.GetComponent<Rigidbody>().velocity = Vector3.zero;
enemyTank.SetActive(false);
}
public void recycleBullet(GameObject Bullet)
{
usingBullets.Remove(Bullet);
freeBullets.Add(Bullet);
Bullet.GetComponent<Rigidbody>().velocity = Vector3.zero;
Bullet.SetActive(false);
}
2.坦克、子弹等基类
//坦克基类
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;
}
public void beShooted()
{
hp -= 100;
}
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);
}
}
//玩家操纵的坦克,移动及血量
void Start () {
setHP(700);
}
void Update () {
if(getHP() <= 0)
{
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);
}
//敌人的坦克寻路及射击
void Update() {
playerLocation = GameDirector.getInstance().currentSceneController.getPlayer().transform.position;
gameover = GameDirector.getInstance().currentSceneController.getGameOver();
if (!gameover)
{
if (getHP() <= 0 && recycleEnemy != null)
{
recycleEnemy(this.gameObject);
}
else
{
NavMeshAgent agent = gameObject.GetComponent<NavMeshAgent>();
agent.SetDestination(playerLocation);
}
}
else
{
NavMeshAgent agent = gameObject.GetComponent<NavMeshAgent>();
agent.velocity = Vector3.zero;
agent.ResetPath();
}
}
IEnumerator shoot()
{
while (!gameover)
{
for(float i =1;i> 0; i -= Time.deltaTime)
{
yield return 0;
}
if(Vector3.Distance(playerLocation,gameObject.transform.position) < 14)
{
shoot(TankType.ENEMY);
}
}
}
3.子弹的设定
//射出的子弹
private void OnCollisionEnter(Collision collision)
{
if(collision.transform.gameObject.tag == "tankEnemy" && this.tankType == TankType.ENEMY ||
collision.transform.gameObject.tag == "tankPlayer" && this.tankType == TankType.PLAYER)
{
return;
}
//工厂模式生产子弹
MyFactory factory = Singleton<MyFactory>.Instance;
ParticleSystem explosion = factory.getParticleSystem();
explosion.transform.position = gameObject.transform.position;
Collider[] colliders = Physics.OverlapSphere(gameObject.transform.position, explosionRadius);
foreach(var collider in colliders)
{
float distance = Vector3.Distance(collider.transform.position, gameObject.transform.position);
float hurt;
//玩家射出的和敌人射出的伤害不同
if (collider.tag == "tankEnemy" && this.tankType == TankType.PLAYER)
{
hurt = 400.0f / distance;
collider.GetComponent<Tank>().setHP(collider.GetComponent<Tank>().getHP() - hurt);
}
else if(collider.tag == "tankPlayer" && this.tankType == TankType.ENEMY)
{
hurt = 100.0f / distance;
collider.GetComponent<Tank>().setHP(collider.GetComponent<Tank>().getHP() - hurt);
}
explosion.Play();
}
//回收子弹
if (gameObject.activeSelf)
{
factory.recycleBullet(gameObject);
}
}
四、成品
坦克等的模型资源在Assert商店中有
做出来效果如下
最后感谢师兄的博客给了很大的帮助!
视频:视频