一点写在前面的内容:一直沉迷于斗地主,搞了很久想要自己实现一个斗地主的人机对打,传说中的托管,实际证明还是实力薄弱,快到截止时间还没做好,只能做做已经有模板的坦克大战,之后再补上我喜欢的斗地主。
本周内容:
以下作业三选一
1、 有趣 AR 小游戏制作
2、 坦克对战游戏 AI 设计
从商店下载游戏:“Kawaii” Tank 或 其他坦克模型,构建 AI 对战坦克。具体要求
3、P&D 过河游戏智能帮助实现,程序具体要求:
本片博客代码框架和格式参考师兄博客:http://www.chenxd59.cn/?p=213
师兄博客代码简单易懂,思路清晰整洁,UML图画的很好。
首先挂个图展示最终成果:
图片有点大(上传不了): https://pan.baidu.com/s/1W0cVufmrCeu_KhoDVAZbJA
接下去讲实现步骤和操作代码:
1.完成地图预制和渲染,完成nevmesh
导入资源:
1.到官方商店下载项目Tanks! Tutorial。
2.打开原配置地图进行部分修改和渲染,完成nevmesh
3.借用资源包里面的坦克预制体实现player坦克和Enemy坦克预制
坦克预制体如下:
(player预制体,下面一圈红色是血条,用来显示坦克生命值)
这里两个坦克分别预制主要是为了好区分,当然你也可以实现代码彩色渲染区别,那么也可以不要两个预制。
渲染生成Nevmesh的时候记住设置一些障碍物是不可以行走的,不然坦克就穿越而过
2.AI算法实现
大致是跟着师兄的UML实现的,删减了修改了少部分的方法,增加了血条记录等功能。
导演场记和工厂模式因为比较熟悉所以这里不讲,代码详见github。
首先定义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 = 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 = 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 = 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