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小坦克相关推荐

  1. Unity3d学习之路-牧师与魔鬼

    Unity3d学习之路-牧师与魔鬼 游戏基本介绍 游戏规则: Priests and Devils is a puzzle game in which you will help the Priest ...

  2. Unity3D学习记录(跑酷小游戏第四阶段)

    这磨人的小游戏项目也要接近尾声了,大概还差一两天的进度吧.最近在看佐佐木智广的<游戏剧本怎么写>,讲了很多Galgame的剧情.脚本设置,非常有意思的一本书,有时间我开个新坑来做一些读书笔 ...

  3. js function如何传入参数未字符串_JavaScript 学习之路- JS 小测验

    在学习 JS 的过程中,很多知识往往看一遍就过去了,然后自以为掌握了,其实再次碰到不一定能答得出来,看到一些有趣的东西还是要动手实践一下,而且时不时复习一下,非常有利于加深记忆.今天是五一,假日期间, ...

  4. Unity3D学习之路Homework4—— 飞碟射击游戏

    简单打飞碟小游戏 游戏规则与要求 规则 鼠标点击飞碟,即可获得分数,不同飞碟分数不一样,飞碟的初始位置与飞行速度随机,随着分数增加,游戏难度增加.初始时每个玩家都有6条生命,漏打飞碟扣除一条生命,直到 ...

  5. JavaSE学习之路:Idea小技巧一键生成标准JavaBean(一键生成构造方法和Setter和Getter方法)

    Idea小技巧一键生成标准JavaBean(一键生成构造方法和Setter和Getter方法) 方法1:快捷键 快捷键:alt+insert或alt+fn+insert 1.生成有参和无参构造函数 p ...

  6. Unity3D学习之路

    1.准备C#的开发环境 VS2015, Unity3D 5.5.1 2.准备通信协议 protobuf 3.3.0 具体请参考:Protobuf 3.3 使用总结 3.引入日志系统 :C#日志系统 L ...

  7. 数独java界面基础_Java从基础到进阶学习之路—-数独小游戏制作(二) | 学步园...

    详细设计 游戏数据结构设计 显然,需要存储数据的地方只有九宫格地图部分. 对于地图,很明显我们可以采用二维数组int [] [] game;来存储地图中的数据.但是int的二维数组虽然直接简单,但是还 ...

  8. Unity3d学习之路-简单AR游戏

    简单AR游戏 简单AR游戏 游戏规则 游戏实现 游戏场景的搭建 游戏逻辑的实现 游戏脚本挂载 游戏打包到安卓平台 实现效果 小结 游戏规则 识别指定图片,显示玩家和防御塔,点击按键对玩家进行上下左右移 ...

  9. 小程序学习之路(持续更新)

    小程序学习之路 1. 小程序简介 2. 第一个小程序 3.小程序代码的构成 4.小程序的宿主环境 4.1 小程序的启动过程 4.2 页面渲染过程 5. WXML模板语法 1. 小程序简介 小程序与普通 ...

最新文章

  1. C++多线程:thread类创建线程的多种方式
  2. TensorFlow基础12-(keras.Sequential模型以及使用Sequential模型 实现手写数字识别)
  3. JAVA连接数据库使用的API是什么呢,如何使用JDBC API在Java中建立数据库连接?
  4. 学生时代的最后一个新年,请一定要做这五件事...
  5. matlab实现BCC异或,C#编程之C# BCC异或校验法
  6. 在iptables防火墙下开启vsftpd的端口
  7. echart php mysql简书_echarts-自定义构建
  8. mysql create table 语法详解
  9. IDEA中Git操作
  10. 【转来警醒自己】最近的一些面试感悟
  11. 安装好grunt,cmd 提示grunt不是内部或外部命令 怎么办?
  12. mysql唯一性约束冲突_如何解决逻辑删除与数据库唯一约束冲突
  13. solr6.6初探之主从同步
  14. 【亲测】CMD中文乱码终极解决方案
  15. 【Latex学习】Latex中插入超链接/网址
  16. 总结数据库连接失败等问题
  17. 【斯坦福大学公开课CS224W——图机器学习】一、图机器学习中的传统方法(1)
  18. Liunx 安装 phpStudy
  19. 按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)。Java实现
  20. elementUI按需引入时报错 :Cannot find module ‘babel-preset-es2015‘

热门文章

  1. 手写Callable
  2. 知乎网问题和答案爬取
  3. 关于C++中的argc,argv的使用方法
  4. 阿里云esc服务器绑定域名及阿里云域名备案简单流程
  5. Python+selenium自动获取Web端斗鱼直播信息
  6. Java集合类之Map接口之学生花名册
  7. 散粉在哪个步骤用_散粉是在哪个步骤用
  8. java 调用存储过程 无效的列索引_JAVA 调用存储过程报错 java.sql.SQLException: 无效的列索引...
  9. 解决:impdp导入.dmp文件
  10. python爬虫笔记(六)网络爬虫之实战(1)——淘宝商品比价定向爬虫(解决淘宝爬虫限制:使用cookies)...