unity3D学习---使用Navmesh实现AI坦克大战

   一点写在前面的内容:一直沉迷于斗地主,搞了很久想要自己实现一个斗地主的人机对打,传说中的托管,实际证明还是实力薄弱,快到截止时间还没做好,只能做做已经有模板的坦克大战,之后再补上我喜欢的斗地主。


本周内容:

4、作业

以下作业三选一

1、 有趣 AR 小游戏制作

2、 坦克对战游戏 AI 设计

从商店下载游戏:“Kawaii” Tank 或 其他坦克模型,构建 AI 对战坦克。具体要求

  • 使用“感知-思考-行为”模型,建模 AI 坦克
  • 场景中要放置一些障碍阻挡对手视线
  • 坦克需要放置一个矩阵包围盒触发器,以保证 AI 坦克能使用射线探测对手方位
  • AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
  • 实现人机对战

3、P&D 过河游戏智能帮助实现,程序具体要求:

  • 实现状态图的自动生成
  • 讲解图数据在程序中的表示方法
  • 利用算法实现下一步的计算
  • 参考:P&D 过河游戏智能帮助实现
(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坦克预制

unity3D学习---使用Navmesh实现AI坦克大战_第1张图片

坦克预制体如下:

unity3D学习---使用Navmesh实现AI坦克大战_第2张图片  (player预制体,下面一圈红色是血条,用来显示坦克生命值)

unity3D学习---使用Navmesh实现AI坦克大战_第3张图片      (普通坦克预制体,同样是附带一个血条表示生命值)

这里两个坦克分别预制主要是为了好区分,当然你也可以实现代码彩色渲染区别,那么也可以不要两个预制。

渲染生成Nevmesh的时候记住设置一些障碍物是不可以行走的,不然坦克就穿越而过


2.AI算法实现

unity3D学习---使用Navmesh实现AI坦克大战_第4张图片


大致是跟着师兄的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






你可能感兴趣的:(unity3D学习---使用Navmesh实现AI坦克大战)