坦克对战游戏 AI 设计
游戏设计要求:
从商店下载游戏:“Kawaii” Tank 或 其他坦克模型,构建 AI 对战坦克。具体要求
- 使用“感知-思考-行为”模型,建模 AI 坦克
- 场景中要放置一些障碍阻挡对手视线
- 坦克需要放置一个矩阵包围盒触发器,以保证 AI 坦克能使用射线探测对手方位
- AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
- 实现人机对战
项目架构
软件版本
项目使用的开发软件为Unity 3D 2020.1.4f1c1。
文件组织
项目的资源文件夹包括Assets和Packages两个子文件夹。其中,Packages子文件夹存储了系统自带的一些包,在这个项目中并没有特别使用。而Assets文件夹则存储了这次游戏项目使用的资源,场景,脚本,动画,预制等。其中,_TerrainAutoUpgrade,Kawaii_Tanks_Project和Standard Assets文件夹是游戏引用的一些资源包。而Scenes文件夹存储了游戏的场景(这个游戏中只有一个场景)。AI文件夹存储了游戏中使用的NavMesh导航网格。
项目地址
由于整个游戏文件夹过大,这里按照实验要求仅将Assets文件夹传到了公开的仓库上。仓库的链接为https://github.com/alphabstc/3D-AI-Tank。新建一个Unity 3D项目,按照下面的指引将仓库内容导入,将脚本拖到对应的对象上,应该可以创建出一个可以正常运行的游戏。
感知-思考-行动模型设计思路
感知
感知是 agent 接收世界信息的行为,其获取的数据将是思考的输入。在游戏中,程序是可以获得游戏世界任意信息的,设计一个“干死”玩家的算法通常是比较容易的,因此如何限制信息获取是设计不同级别 agent 的核心问题。
在游戏中,定义获取信息能力通常可以从视觉、听觉和嗅觉等渠道去考虑。在这个游戏中,坦克AI获取信息主要是通过视觉获得的。每个AI坦克获取的信息是当前摄像机所能感受到的信息,AI坦克如果可以直接看到对手坦克,则可以从视觉方面获得对手坦克的位置信息。由于有房子,树木等障碍物的阻挡,AI坦克获得的对手信息不一定完全一致,有的AI坦克甚至暂时不能发现玩家。本游戏默认一方的多个坦克都是独立行动的,不会共享对手的信息来围攻对方。这一方面也符合实际坦克对战的情况,使得坦克对战更加真实,另一方面这也避免了AI坦克“群殴”玩家,增强了游戏的可玩性。
思考
本游戏中,坦克的思考主要体现在利用NavMesh进行寻路。同时,思考也体现在坦克的进攻防御策略上。AI坦克根据感知到的对手位置与前进方向,根据当前自己的位置,前进方向与血量,做出决策是进攻(是直线前进还是迂回前进),还是防御(直接逃跑还是躲在房子等掩体后面)。
行动
每个AI坦克都会有前进,发射子弹的行为。AI坦克的子弹不可以连续不断地发射,其也需要有一个准备和反应的时间,这也符合人来操纵AI坦克的特点。同时,AI坦克的发射子弹,转向,移动等行动的切换速度不能太快,行动速度也不能太快,其需要具有渐变性,如同人操纵AI坦克一样,AI坦克的行动要有一个缓冲的时间。同样,AI坦克的子弹发射不能百发百中,其需要像真人操纵坦克一样,要允许存在一定的误差。
游戏设计实现
首先下载与导入Kawaii Tank 的 package。
然后导入标准素材库的如下内容:
然后可以观察Kawaii Tank中的场景,体验一下该场景。
类似地,可以制作出一个新的场景如下:
该场景增加了一些凹凸不平的沙地,增加了一些丘陵地形,以及一些森林,还有风场。使得场景更复杂,增加可玩性。同时也使得场景中障碍物更多,更需要玩家操作的技巧性。玩家的移动与设计都需要更加仔细地考虑与设计。同时,也调整了玩家和敌人的血量,以及调整了敌人的数量。
选中场景中的地形并在Navigation窗口选择Object,设置如下的属性:
再选择Bake,设置如下属性:
然后给所有的AI坦克的MainBody添加Nav Mesh Agent组件。
然后在Bake选项卡中点击"Bake":
之后可以生成导航网格图如下:
然后给坦克添加AI脚本,使得坦克可以追踪玩家:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class PlaceTarget : MonoBehaviour
{
public GameObject target; //获取目标点,注意在面板中赋值
NavMeshAgent mr; //声明变量初始化
void Start()
{
//获取到自身的NavMeshAgent组件
mr = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
//使用方法获取目标点坐标,,和前一行代码作用相同
mr.SetDestination(target.transform.position);
}
}
修改AI坦克发射炮弹的代码如下:
void Update ()
{
if (idScript.isPlayer) {
#if UNITY_ANDROID || UNITY_IPHONE
Mobile_Input ();
#else
Desktop_Input ();
#endif
} else
{
count = count + Time.deltaTime;
if(count > 6.0f)
{
Fire();
count = 0;
}
}
}
使得AI坦克每6s发射一次炮弹,这样也和人类玩家尽力瞄准再发射炮弹的特性更相似。避免AI坦克发射炮弹速度过快,使得玩家很快被AI坦克消灭。
人类通常需要看到进度条,才更能有决心与动力去做一件事情。为了增加游戏的可玩性,这里使得玩家的血量始终显示,而敌人(AI坦克)在被玩家击中时显示血量:
void Update ()
{
// 游戏玩家一直显示血条,AI坦克受到攻击后显示血条
if(idScript.isPlayer)
{
displayScript.Get_Damage(durability, initialDurability);
}
// Destruct
if (idScript.isPlayer) {
#if UNITY_ANDROID || UNITY_IPHONE
if (CrossPlatformInputManager.GetButtonDown ("Destruct")) {
#else
if (Input.GetKeyDown (KeyCode.Return)) {
#endif
Start_Destroying ();
}
}
}
同时,如果敌人血量降到0以下,则让敌人消失:
void Start_Destroying ()
{
if(idScript.isPlayer == false)
{
this.gameObject.SetActive(false);
}
// Send message to all the parts.
BroadcastMessage ("Destroy", SendMessageOptions.DontRequireReceiver);
// Create destroyedPrefab.
if (destroyedPrefab) {
GameObject tempObject = Instantiate (destroyedPrefab, bodyTransform.position, Quaternion.identity) as GameObject;
tempObject.transform.parent = bodyTransform;
}
// Remove the Damage text.
if (displayScript) {
Destroy (displayScript.gameObject);
}
// Destroy this script.
Destroy (this);
}
同时,降低攻击力与敌人攻击速度如下:
[ Header ("Firing settings")]
[ Tooltip ("Prefab of muzzle fire.")] public GameObject firePrefab;
[ Tooltip ("Prefab of bullet.")] public GameObject bulletPrefab;
[ Tooltip ("Attack force of the bullet.")] public float attackForce = 80.0f;
[ Tooltip ("Speed of the bullet. (Meter per Second)")] public float bulletVelocity = 230.0f;
[ Tooltip ("Offset distance for spawning the bullet. (Meter)")] public float spawnOffset = 1.0f;
游戏可以通过减少敌人的攻击力与速度,增加敌人的数量,来增加游戏的可玩性。玩家一开始面对较多敌人的夹击,丢血较多,可能只剩下一点血,让情况非常紧急。而随着玩家消灭更多的敌人,玩家受到敌人攻击频率降低,可能可以以较低的血量消灭掉所有敌人。这也增加了游戏的可玩性:
游戏效果
玩家被消灭
敌人的视角: