Unity3D学习之路——AI小坦克
Unity3D学习之路——AI小坦克
作业要求:
- 坦克对战游戏 AI 设计
从商店下载游戏:“Kawaii” Tank 或 其他坦克模型,构建 AI 对战坦克。具体要求- 使用“感知-思考-行为”模型,建模 AI 坦克
- 场景中要放置一些障碍阻挡对手视线
- 坦克需要放置一个矩阵包围盒触发器,以保证 AI 坦克能使用射线探测对手方位
- AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
实现人机对战
成品图
主要是参考师兄的博客做的,明明很好玩的东西但自己做出来的效果不好。
实现的要求:
- AI小坦克自动追踪玩家坦克,利用课程上的AI寻路,当与玩家坦克距离小于一定程度时发射子弹射击。
- 玩家可操控角色躲避攻击并发射子弹攻击AI坦克。
- 为了增强游戏的可玩性,玩家坦克的子弹伤害比AI坦克子弹伤害要高。
实现过程:
- 有了之前几次作业的基础,现在懂得尽量减少类与类之间的耦合,这在大项目里是很重要的要求。
- Step1:坦克基类Tank.cs:血量,以及被攻击后血量改变的相关函数,还有开炮对应的操作:
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;}//标明子弹所属的坦克类型,TankType是枚举变量,在工厂类里MyFactory定义。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);}
}
- Step2:玩家类的实现:继承Tank类
public class Player : Tank{// player被摧毁时发布信息;public delegate void DestroyPlayer();public static event DestroyPlayer destroyEvent;void Start () {setHP(500);
}// Update is called once per frame
void Update () {if(getHP() <= 0) // Tank is destoryed{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);}
}
- Step3:AI坦克类的实现,也是要继承Tank类:
public class Enemy : Tank {public delegate void RecycleEnemy(GameObject enemy);//当enemy被摧毁时,通知工厂回收;public static event RecycleEnemy recycleEnemy;// player 的位置private Vector3 playerLocation;//游戏是否结束private bool gameover;private void Start(){playerLocation = GameDirector.getInstance().currentSceneController.getPlayer().transform.position;StartCoroutine(shoot());}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{// 自动向player移动NavMeshAgent agent = gameObject.GetComponent<NavMeshAgent>();agent.SetDestination(playerLocation);// gameObject.transform.position = new Vector3(transform.position.x+0.01f,transform.position.y,transform.position.z);}}else{//游戏结束,停止寻路NavMeshAgent agent = gameObject.GetComponent<NavMeshAgent>();agent.velocity = Vector3.zero;agent.ResetPath();}}// 协程实现每隔1s进行射击,开始喜欢协程了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);}}}
}
以上就实现了这个游戏中主要角色的动作了,将player和enemy坦克都会有的属性与动作,比如血量和射击shoot放到基类Tank,减少代码的冗余并增强代码的可读性;另外,也尽量着减少与其它类的关联。
- Step4: 子弹bullet类,有些要求是要注意的:设置发射子弹的坦克类型, 因为如果射到队友是不能算伤害的;如果是玩家发出的子弹伤害高一点,AI坦克的子弹伤害低一点:
public class Bullet : MonoBehaviour {public float explosionRadius = 3.0f;private TankType tankType;//设置发射子弹的坦克类型, 因为如果射到队友是不能算伤害的.public void setTankType(TankType type){tankType = type;}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 = 300.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);}}}
- Step5:接着就是工厂类了,工厂类无非就是场景要“演员”的时候就生产出来,不需要的时候就回收,代码与之前作业很类似,在这就不详细介绍了:
//其中public的四个变量是用来在unity中将player,enemy,bullet,explosion预制拖进代码里用于实例化对象的。
//其它变量和方法应该看名称都可以看懂。
public enum TankType { PLAYER , ENEMY};
public class MyFactory : MonoBehaviour {public GameObject player;public GameObject enemy;public GameObject bullet;public ParticleSystem explosion;private List<GameObject> usingTanks;private List<GameObject> freeTanks;private List<GameObject> usingBullets;private List<GameObject> freeBullets;private GameObject role;private List<ParticleSystem> particles;private void Awake(){usingTanks = new List<GameObject>();freeTanks = new List<GameObject>();usingBullets = new List<GameObject>();freeBullets = new List<GameObject>();particles = new List<ParticleSystem>();role = GameObject.Instantiate<GameObject>(player) as GameObject;role.SetActive(true);role.transform.position = Vector3.zero;}// Use this for initializationvoid Start () {Enemy.recycleEnemy += recycleEnemy;}// Update is called once per frame
public GameObject getPlayer(){ return role;}public GameObject getEnemys(){GameObject newTank = null;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);}newTank.SetActive(true);return newTank;}public GameObject getBullets(TankType type){GameObject newBullet;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);}newBullet.GetComponent<Bullet>().setTankType(type);newBullet.SetActive(true);return newBullet;}public ParticleSystem getParticleSystem(){foreach(var particle in particles){if (!particle.isPlaying){return particle;}}ParticleSystem newPS = GameObject.Instantiate<ParticleSystem>(explosion);particles.Add(newPS);return newPS;}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);}
}
- Step6:场景类:获得工厂实例,做好初始化工作,接受从UI类里传过来的要求实现对应的操作,由于Player和Enemy已经实现了自身对应的动作,所以场景中如果接受UI传来的要求即让对应的Player和Enemy实例执行对应的操作就行:
// SceneController.cs
public class SceneController : MonoBehaviour,IUserAction{public GameObject player;private int enemyCount = 6;private bool gameOver = false;private GameObject[] enemys;private MyFactory myFactory;public GameDirector director;private void Awake(){director = GameDirector.getInstance();director.currentSceneController = this;enemys = new GameObject[enemyCount];gameOver = false;myFactory = Singleton<MyFactory>.Instance;}void Start () {player = myFactory.getPlayer();for (int i = 0; i < enemyCount; i++){enemys[i]=myFactory.getEnemys();}Player.destroyEvent += setGameOver;}// Update is called once per framevoid Update () {Camera.main.transform.position = new Vector3(player.transform.position.x, 18, player.transform.position.z);}//返回玩家坦克的位置public GameObject getPlayer(){return player;}//返回游戏状态public bool getGameOver(){return gameOver;}//设置游戏结束public void setGameOver(){gameOver = true;}public void moveForward(){player.GetComponent<Player>().moveForward();}public void moveBackWard(){player.GetComponent<Player>().moveBackWard();}//通过水平轴上的增量,改变玩家坦克的欧拉角,从而实现坦克转向public void turn(float offsetX){player.GetComponent<Player>().turn(offsetX);}public void shoot(){player.GetComponent<Player>().shoot(TankType.PLAYER);}
}//IUserAction.cs
public interface IUserAction
{void moveForward();void moveBackWard();void turn(float offsetX);void shoot();bool getGameOver();
}
- Step7:最后就是UI类和导演Director类了,UI类就是检测玩家的输入,再对player坦克进行对应的操作,IUserGUI 类如下:
//IUserGUI.cs
public class IUserGUI : MonoBehaviour {IUserAction action;// Use this for initializationvoid Start () {action = GameDirector.getInstance().currentSceneController as IUserAction;}// Update is called once per framevoid Update () {if (!action.getGameOver()){if (Input.GetKey(KeyCode.W)){action.moveForward();}if (Input.GetKey(KeyCode.S)){action.moveBackWard();}if (Input.GetKeyDown(KeyCode.Space)){action.shoot();}//获取水平轴上的增量,目的在于控制玩家坦克的转向float offsetX = Input.GetAxis("Horizontal");action.turn(offsetX);}
}void OnGUI(){//gameover时生成提示if (action.getGameOver()){GUIStyle fontStyle = new GUIStyle();fontStyle.fontSize = 30;fontStyle.normal.textColor = new Color(0, 0, 0);GUI.Button(new Rect(Screen.width/2-50, Screen.height/2-50, 200, 50), "GameOver!");}}
}
导演类还是和以前一样:
//GameDirector.cs
public class GameDirector : System.Object {private static GameDirector _instance;public SceneController currentSceneController { get; set; }private GameDirector() { }public static GameDirector getInstance(){if(_instance == null){_instance = new GameDirector();}return _instance;}
}
哦还有个用于产生单例的类,这个类用了泛型编程:
//Singleton.cs
public class Singleton<T> : MonoBehaviour where T :MonoBehaviour {protected static T instance;public static T Instance{get{if(instance == null){instance = (T)FindObjectOfType(typeof(T));if(instance == null){Debug.LogError("An instance of " + typeof(T) + " is needed in the scene , but there is none.");}}return instance;}}
}
总结
- 这次作业学习了unity的AI技术还是很好玩的,虽然项目做得不好,但对自身代码的能力还是有所提高的,这是我整个项目的传送门。
- 如果blog编辑有错欢迎指出错误。
Unity3D学习之路——AI小坦克相关推荐
- Unity3d学习之路-牧师与魔鬼
Unity3d学习之路-牧师与魔鬼 游戏基本介绍 游戏规则: Priests and Devils is a puzzle game in which you will help the Priest ...
- Unity3D学习记录(跑酷小游戏第四阶段)
这磨人的小游戏项目也要接近尾声了,大概还差一两天的进度吧.最近在看佐佐木智广的<游戏剧本怎么写>,讲了很多Galgame的剧情.脚本设置,非常有意思的一本书,有时间我开个新坑来做一些读书笔 ...
- js function如何传入参数未字符串_JavaScript 学习之路- JS 小测验
在学习 JS 的过程中,很多知识往往看一遍就过去了,然后自以为掌握了,其实再次碰到不一定能答得出来,看到一些有趣的东西还是要动手实践一下,而且时不时复习一下,非常有利于加深记忆.今天是五一,假日期间, ...
- Unity3D学习之路Homework4—— 飞碟射击游戏
简单打飞碟小游戏 游戏规则与要求 规则 鼠标点击飞碟,即可获得分数,不同飞碟分数不一样,飞碟的初始位置与飞行速度随机,随着分数增加,游戏难度增加.初始时每个玩家都有6条生命,漏打飞碟扣除一条生命,直到 ...
- JavaSE学习之路:Idea小技巧一键生成标准JavaBean(一键生成构造方法和Setter和Getter方法)
Idea小技巧一键生成标准JavaBean(一键生成构造方法和Setter和Getter方法) 方法1:快捷键 快捷键:alt+insert或alt+fn+insert 1.生成有参和无参构造函数 p ...
- Unity3D学习之路
1.准备C#的开发环境 VS2015, Unity3D 5.5.1 2.准备通信协议 protobuf 3.3.0 具体请参考:Protobuf 3.3 使用总结 3.引入日志系统 :C#日志系统 L ...
- 数独java界面基础_Java从基础到进阶学习之路—-数独小游戏制作(二) | 学步园...
详细设计 游戏数据结构设计 显然,需要存储数据的地方只有九宫格地图部分. 对于地图,很明显我们可以采用二维数组int [] [] game;来存储地图中的数据.但是int的二维数组虽然直接简单,但是还 ...
- Unity3d学习之路-简单AR游戏
简单AR游戏 简单AR游戏 游戏规则 游戏实现 游戏场景的搭建 游戏逻辑的实现 游戏脚本挂载 游戏打包到安卓平台 实现效果 小结 游戏规则 识别指定图片,显示玩家和防御塔,点击按键对玩家进行上下左右移 ...
- 小程序学习之路(持续更新)
小程序学习之路 1. 小程序简介 2. 第一个小程序 3.小程序代码的构成 4.小程序的宿主环境 4.1 小程序的启动过程 4.2 页面渲染过程 5. WXML模板语法 1. 小程序简介 小程序与普通 ...
最新文章
- C++多线程:thread类创建线程的多种方式
- TensorFlow基础12-(keras.Sequential模型以及使用Sequential模型 实现手写数字识别)
- JAVA连接数据库使用的API是什么呢,如何使用JDBC API在Java中建立数据库连接?
- 学生时代的最后一个新年,请一定要做这五件事...
- matlab实现BCC异或,C#编程之C# BCC异或校验法
- 在iptables防火墙下开启vsftpd的端口
- echart php mysql简书_echarts-自定义构建
- mysql create table 语法详解
- IDEA中Git操作
- 【转来警醒自己】最近的一些面试感悟
- 安装好grunt,cmd 提示grunt不是内部或外部命令 怎么办?
- mysql唯一性约束冲突_如何解决逻辑删除与数据库唯一约束冲突
- solr6.6初探之主从同步
- 【亲测】CMD中文乱码终极解决方案
- 【Latex学习】Latex中插入超链接/网址
- 总结数据库连接失败等问题
- 【斯坦福大学公开课CS224W——图机器学习】一、图机器学习中的传统方法(1)
- Liunx 安装 phpStudy
- 按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)。Java实现
- elementUI按需引入时报错 :Cannot find module ‘babel-preset-es2015‘
热门文章
- 手写Callable
- 知乎网问题和答案爬取
- 关于C++中的argc,argv的使用方法
- 阿里云esc服务器绑定域名及阿里云域名备案简单流程
- Python+selenium自动获取Web端斗鱼直播信息
- Java集合类之Map接口之学生花名册
- 散粉在哪个步骤用_散粉是在哪个步骤用
- java 调用存储过程 无效的列索引_JAVA 调用存储过程报错 java.sql.SQLException: 无效的列索引...
- 解决:impdp导入.dmp文件
- python爬虫笔记(六)网络爬虫之实战(1)——淘宝商品比价定向爬虫(解决淘宝爬虫限制:使用cookies)...