unity3D学习---使用Navmesh实现AI坦克大战
一点写在前面的内容:一直沉迷于斗地主,搞了很久想要自己实现一个斗地主的人机对打,传说中的托管,实际证明还是实力薄弱,快到截止时间还没做好,只能做做已经有模板的坦克大战,之后再补上我喜欢的斗地主。
本周内容:
4、作业 以下作业三选一
1、 有趣 AR 小游戏制作
2、 坦克对战游戏 AI 设计
从商店下载游戏:“Kawaii” Tank 或 其他坦克模型,构建 AI 对战坦克。具体要求
- 使用“感知-思考-行为”模型,建模 AI 坦克
- 场景中要放置一些障碍阻挡对手视线
- 坦克需要放置一个矩阵包围盒触发器,以保证 AI 坦克能使用射线探测对手方位
- AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
- 实现人机对战
- 实现状态图的自动生成
- 讲解图数据在程序中的表示方法
- 利用算法实现下一步的计算
- 参考:P&D 过河游戏智能帮助实现
师兄博客代码简单易懂,思路清晰整洁,UML图画的很好。
首先挂个图展示最终成果:
图片有点大(上传不了): https://pan.baidu.com/s/1W0cVufmrCeu_KhoDVAZbJA
接下去讲实现步骤和操作代码:
1.完成地图预制和渲染,完成nevmesh
导入资源:
1.到官方商店下载项目Tanks! Tutorial。
2.打开原配置地图进行部分修改和渲染,完成nevmesh
3.借用资源包里面的坦克预制体实现player坦克和Enemy坦克预制
文章图片
坦克预制体如下:
文章图片
(player预制体,下面一圈红色是血条,用来显示坦克生命值)
文章图片
(普通坦克预制体,同样是附带一个血条表示生命值)
这里两个坦克分别预制主要是为了好区分,当然你也可以实现代码彩色渲染区别,那么也可以不要两个预制。
渲染生成Nevmesh的时候记住设置一些障碍物是不可以行走的,不然坦克就穿越而过
2.AI算法实现
文章图片
大致是跟着师兄的UML实现的,删减了修改了少部分的方法,增加了血条记录等功能。
导演场记和工厂模式因为比较熟悉所以这里不讲,代码详见github。
【unity3D学习---使用Navmesh实现AI坦克大战】首先定义6个接口函数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction{
void moveForward();
//向前进
void moveBackWard();
//向后退
void turn(float offsetX);
//偏转一定角度
void shoot();
//坦克射击
bool isGameOver();
//判断游戏状态
void show();
//记录游戏规则
}
场控书写和实现:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
public class SceneController : MonoBehaviour, IUserAction{
public GameObject player;
//玩家坦克
private bool gameOver = false;
//游戏是否结束
private myFactory mF;
//工厂
void Awake() {//一些初始的设置
GameDirector director = GameDirector.getInstance();
director.sencontor = this;
mF = Singleton.Instance;
player = mF.getPlayer();
}
void Start () {
}
void Update () {
// 相机跟随玩家坦克
Camera.main.transform.position = new Vector3(player.transform.position.x, 15, player.transform.position.z);
} public Vector3 get_pos_of_player() {//返回玩家坦克的位置
return player.transform.position;
} public bool isGameOver() {//返回游戏是否结束
return gameOver;
} public void moveForward() {
player.GetComponent().velocity = player.transform.forward * 20;
}
public void moveBackWard() {
player.GetComponent().velocity = player.transform.forward * -20;
}
public void moveLeftWard()
{//可以实现左转和前进同步
}
public void moveRightWard()
{
}
public void show()
{
GUI.Label(new Rect(100, 100, 200, 20), "上下键或者ws键控制坦克前进后退,ad键或者左右键控制坦克移动,空格键或者enter键射击,摧毁所有坦克,您将取得胜利");
}
public void turn(float offsetX) {//通过水平轴上的增量,改变玩家坦克的欧拉角,从而实现坦克转向
float x = player.transform.localEulerAngles.y + offsetX * 5;
float y = player.transform.localEulerAngles.x;
player.transform.localEulerAngles = new Vector3 (y, x, 0);
} public void shoot(){
//增加游戏难度
for (float i = 0.1f;
i > 0;
i -= Time.deltaTime)
{}
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();
rb.AddForce(bullet.transform.forward * 20, ForceMode.Impulse);
//发射子弹
}}
因为子弹偏多和普通坦克偏多,所以选择共产模式,同时增加了订阅与发布的模式,回收子弹和废弃坦克
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum tankType:int{Player, Enemy}
public class myFactory : MonoBehaviour {//工厂模式
public GameObject bullet;
//子弹
public ParticleSystem poxpS;
//爆炸粒子系统
public GameObject player;
//player
public GameObject tank;
//npc//键值对,记录保存子弹信息和坦克信息
private Dictionary usedtank;
private Dictionary freetank;
private Dictionary usedbullet;
private Dictionary freebullet;
private List contanier;
void Awake() {
usedtank = new Dictionary();
freetank = new Dictionary();
usedbullet = new Dictionary();
freebullet = new Dictionary();
contanier = new List();
}//npc坦克被摧毁时,会执行这个委托函数
void Start() {
Enemy.recycleEvent += recycleTank;
}
//获取玩家坦克
public GameObject getPlayer() {
return player;
} public GameObject getBullet(tankType type) {
if (freebullet.Count == 0) {
GameObject newBullet = Instantiate(bullet);
newBullet.GetComponent().setTankType(type);
usedbullet.Add(newBullet.GetInstanceID(), newBullet);
return newBullet;
}
foreach (KeyValuePair pair in freebullet) {
pair.Value.SetActive(true);
pair.Value.GetComponent().setTankType(type);
freebullet.Remove(pair.Key);
usedbullet.Add(pair.Key, pair.Value);
return pair.Value;
}
return null;
} public ParticleSystem getPs() {
for (int i = 0;
i < contanier.Count;
i++) {
if (!contanier[i].isPlaying) {
return contanier[i];
}
}
ParticleSystem newPs = Instantiate(pS);
contanier.Add(newPs);
return newPs;
} public void recycleTank(GameObject tank) {
usedtank.Remove(tank.GetInstanceID());
freetank.Add(tank.GetInstanceID(), tank);
tank.GetComponent().velocity = new Vector3(0, 0, 0);
tank.SetActive(false);
} public void recycleBullet(GameObject bullet) {
usedbullet.Remove(bullet.GetInstanceID());
freebullet.Add(bullet.GetInstanceID(), bullet);
bullet.GetComponent().velocity = new Vector3(0, 0, 0);
bullet.SetActive(false);
}
}
两种坦克自带属性方法的完成:
普通坦克距离目标坦克(player)一定范围内能发动射击,造成player的伤害,同时player血条值下降。如果距离目标坦克较远,那么普通坦克将朝着player移动,移动与射击两个活动是同时可以触发的,协程处理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
public class Enemy : Tank {//npc坦克
public delegate void recycle(GameObject Tank);
public static event recycle recycleEvent;
//npc坦克被销毁之后,可通知工厂回收
private Vector3 target;
//目标,即玩家坦克的位置
private bool gameover;
//游戏是否结束,决定是否继续运动或射击
public Slider m_Slider;
void Start() {
setHp(500f);
//设置初始生命值为200
StartCoroutine(shoot());
//开始射击的协程
this.m_Slider.value = https://www.it610.com/article/getHp();
} void Update () {
gameover = GameDirector.getInstance().sencontor.isGameOver();
if (!gameover) {
target = GameDirector.getInstance().sencontor.get_pos_of_player();
//触发回收事件
if (getHp() <= 0 && recycleEvent != null) {
recycleEvent(this.gameObject);
}
else if (getHp()>= 0)
{//向玩家坦克移动
NavMeshAgent agent = GetComponent();
agent.SetDestination(target);
this.m_Slider.value = https://www.it610.com/article/getHp();
}
else
{}
} }
//协程实现npc坦克每隔1.5s进行射击
IEnumerator shoot() {
while(!gameover) {
for (float i = 1.5f;
i> 0;
i -= Time.deltaTime) {
yield return 0;
}
//和玩家坦克距离小于15,则射击
if (Vector3.Distance(transform.position, target) < 15) {
myFactory mF = Singleton.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();
rb.AddForce(bullet.transform.forward * 20, ForceMode.Impulse);
//发射子弹
}
}
}
}
player坦克能够随时射击子弹,但是为了控制卡顿和没必要的资源浪费,规定了player一秒能发射的子弹数目是固定的(有上限),每个子弹发射存在时间间隔,虽然人为控制的速度也会造成子弹其实不会那么密集。
player子弹射击到npc,将会咋成npc血条下降,血条为0时发布坦克回收时间,回收坦克和子弹。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
public class Player : Tank {//玩家坦克
public delegate void destroy();
public static event destroy destroyEvent;
public Slider m_Slider;
void Start() {
setHp(500f);
//设置初始生命值为500
m_Slider.value = https://www.it610.com/article/getHp();
}
void Update () {
if (getHp() <= 0 ) {//生命值<=0,表示玩家坦克被摧毁
this.gameObject.SetActive(false);
if (destroyEvent != null) {//执行委托事件
destroyEvent();
}
}
else
{
//控制血条
m_Slider.value = getHp();
}
}
}
大体代码就是这样,这部分着重于对AI的理解以及Nevmesh的渲染处理,难度比不上状态机,但是也是一个值得学习的方面,我的斗地主得接下去找时间实现了,没有在这周之前实现实在是有点忏愧。
视屏链接:https://pan.baidu.com/s/1ZD_2BrlossS4pLH9dYieEQ
推荐阅读
- Unity3D|unity3d-多人坦克对战
- Unity3D|Unity3d之坦克对战游戏 AI设计
- 萝卜爆肝Python自学学习路线
- 深度学习与神经网络|第3周 用1层隐藏层的神经网络分类二维数据
- 深度学习|基于tensorflow2+textCNN的中文垃圾邮件分类
- 深度学习|基于keras深度学习模型新闻标签一二级分类
- 如何查看所有测试历史(进来学习下)
- 赴海门学习第一天收获
- 书法的秘密就是学习的秘密.--看《书法没有秘密》有感
- 网络安全技术|【网络安全】最全渗透学习攻略