坦克大战 - 人工智障版 参考博客
B站视频
游戏预览:
这次实现的是一个带有人工智障的坦克大战!一起来看看具体的做法吧(。ì _ í。)
Tank!——官方的资源包 Tank!是官方教程包中的 坦克大战游戏制作教程 ,除了教导我们如何制作一个坦克大战以外,包内也包含了精美而充足的游戏资源供我们使用!
导入这个包以后,我们能看到许多现成的资源:Model、Prefabs……把地形实例化后,往上添加自己喜欢的 Model 就能简单地做成一个地图啦。
从 NavMash 开始 既然要做 AI,自动寻路就是一个问题了。好在 Unity 已经帮我们做了很多事情,我们只需要简单的设置即可使用~首先,设置地形为 Walkable,地形上的部件为 Not Walkable,然后就可以通过烘培生成一个可以被 Nav Agent 识别的地形啦。为我们的 tank 装上 Nav Agent 组件,就可以让它享受到 Unity 的自动寻路功能了。
AI 的逻辑设定 AI 的目的是淘汰玩家,我们设置的逻辑是 AI 会不断尝试接近 player,并且在足够接近的情况下开始发射炮弹:
Enemy.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.AI;public class Enemy : Tank { public delegate void recycle (GameObject Tank ) ; public static event recycle recycleEvent; private Vector3 target; private bool gameover; void Start ( ) { setHp(100 f); StartCoroutine(shoot()); } void Update ( ) { gameover = (GameDirector.GetInstance().currentSceneController as FirstController).isGameOver(); if (!gameover) { target = (GameDirector.GetInstance().currentSceneController as FirstController).player.transform.position; if (getHp() <= 0 && recycleEvent != null ) { recycleEvent(this .gameObject); } else { var agent = GetComponent<NavMeshAgent>(); agent.SetDestination(target); } } else { var agent = GetComponent<NavMeshAgent>(); agent.velocity = Vector3.zero; agent.ResetPath(); } } IEnumerator shoot ( ) { while (!gameover) { if (Vector3.Distance(transform.position, target) < 20 ) { var factory = Factory.GetInstance(); GameObject bullet = factory.GetBullet(TankType.Enemy); bullet.transform.position = new Vector3(transform.position.x, 1.5 f, transform.position.z) + transform.forward * 1.5 f; bullet.transform.forward = transform.forward; Rigidbody rb = bullet.GetComponent<Rigidbody>(); rb.AddForce(bullet.transform.forward * 20 , ForceMode.Impulse); } yield return new WaitForSeconds (1 ) ; } } }
不过这里有一个问题亟待解决:通过协程实现的定时发射炮弹似乎会同时发射两发……由于长时间无法解决,我们就顺水推舟的把敌方坦克设定为“双管坦克”了 =v=
代码细节 剩下的东西基本都是之前学过的了,通过单例的工厂类(Factory.cs)生成敌方坦克,通过场记类(FirstController.cs)实现场景对象的交互,通过委托实现事件的触发……详情就看代码吧~
Bullet.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 using System.Collections;using System.Collections.Generic;using UnityEngine;public class Bullet : MonoBehaviour { private TankType type; void OnCollisionEnter (Collision other ) { if (other.gameObject.name.Contains("Shell" )) return ; Factory factory = Factory.GetInstance(); if (other.gameObject.tag == "Player" && this .type == TankType.Enemy || other.gameObject.tag == "tankPlayer" && this .type == TankType.player) { float hurt = 100 f; float current = other.gameObject.GetComponent<Tank>().getHp(); other.gameObject.GetComponent<Tank>().setHp(current - hurt); var anime = factory.GetHitAnimation(); anime.transform.position = transform.position; anime.GetComponent<ParticleSystem>().Play(); } else { var anime = factory.GetNotHitAnimation(); anime.transform.position = transform.position; anime.GetComponent<ParticleSystem>().Play(); } if (this .gameObject.activeSelf) { factory.recycleBullet(this .gameObject); } } public void setTankType (TankType type ) { this .type = type; } }
Factory.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 using System.Collections;using System.Collections.Generic;using UnityEngine;public enum TankType : int { player, Enemy }public class Factory : MonoBehaviour { public GameObject player; public GameObject enemy; public GameObject bullet; public GameObject shellExlosion_nothit; public GameObject shellExlosion_hit; private Dictionary<int , GameObject> busyTanks; private Dictionary<int , GameObject> idelTanks; private Dictionary<int , GameObject> busyBullets; private Dictionary<int , GameObject> idelBullets; private List<GameObject> shellExlosions_nothit; private List<GameObject> shellExlosions_hit; private static Factory _instance; public static Factory GetInstance ( ) { if (_instance == null ) { _instance = new Factory(); } return _instance; } Factory() { _instance = this ; busyTanks = new Dictionary<int , GameObject>(); idelTanks = new Dictionary<int , GameObject>(); busyBullets = new Dictionary<int , GameObject>(); idelBullets = new Dictionary<int , GameObject>(); shellExlosions_hit = new List<GameObject>(); shellExlosions_nothit = new List<GameObject>(); } private void Start ( ) { Enemy.recycleEvent += recycleTank; } public GameObject GetEnemy ( ) { if (idelTanks.Count == 0 ) { var newTank = Instantiate<GameObject>(enemy); busyTanks.Add(newTank.GetInstanceID(), newTank); newTank.transform.position = new Vector3(Random.Range(-100 , 100 ), 0 , Random.Range(-100 , 100 )); return newTank; } foreach (var pair in idelTanks) { pair.Value.SetActive(true ); idelTanks.Remove(pair.Key); busyTanks.Add(pair.Key, pair.Value); pair.Value.transform.position = new Vector3(Random.Range(-100 , 100 ), 0 , Random.Range(-100 , 100 )); return pair.Value; } return null ; } public GameObject GetBullet (TankType type ) { if (idelBullets.Count == 0 ) { var newBullet = Instantiate<GameObject>(bullet); newBullet.GetComponent<Bullet>().setTankType(type); busyBullets.Add(newBullet.GetInstanceID(), newBullet); return newBullet; } foreach (var pair in idelBullets) { pair.Value.SetActive(true ); idelBullets.Remove(pair.Key); busyBullets.Add(pair.Key, pair.Value); pair.Value.GetComponent<Bullet>().setTankType(type); return pair.Value; } return null ; } public GameObject GetHitAnimation ( ) { foreach (var anime in shellExlosions_hit) { if (!anime.GetComponent<ParticleSystem>().isPlaying) { return anime; } } var newAnime = Instantiate<GameObject>(shellExlosion_hit); shellExlosions_hit.Add(newAnime); return newAnime; } public GameObject GetNotHitAnimation ( ) { foreach (var anime in shellExlosions_nothit) { if (!anime.GetComponent<ParticleSystem>().isPlaying) { return anime; } } var newAnime = Instantiate<GameObject>(shellExlosion_nothit); shellExlosions_nothit.Add(newAnime); return newAnime; } public void recycleTank (GameObject tank ) { busyTanks.Remove(tank.GetInstanceID()); idelTanks.Add(tank.GetInstanceID(), tank); tank.GetComponent<Rigidbody>().velocity = Vector3.zero; tank.SetActive(false ); } public void recycleBullet (GameObject bullet ) { busyBullets.Remove(bullet.GetInstanceID()); idelBullets.Add(bullet.GetInstanceID(), bullet); bullet.GetComponent<Rigidbody>().velocity = Vector3.zero; bullet.SetActive(false ); } }
FirstController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 using System.Collections;using System.Collections.Generic;using UnityEngine;public class FirstController : SceneController, UserActions { public void LoadResources ( ) { } static FirstController _instance; public GameObject player; private readonly static int initialTanks = 6 ; private Factory factory; private bool gameover; public static FirstController GetInstance ( ) { if (_instance == null ) { _instance = new FirstController(); } return _instance; } public void moveForward ( ) { player.GetComponent<Rigidbody>().velocity = player.transform.forward * 10 ; } public void moveBackWard ( ) { player.GetComponent<Rigidbody>().velocity = player.transform.forward * -10 ; } 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 ( ) { GameObject bullet = factory.GetBullet(TankType.player); bullet.transform.position = new Vector3(player.transform.position.x, 1.5 f, player.transform.position.z) + player.transform.forward * 1.5 f; bullet.transform.forward = player.transform.forward; Rigidbody rb = bullet.GetComponent<Rigidbody>(); rb.AddForce(bullet.transform.forward * 20 , ForceMode.Impulse); } public bool isGameOver ( ) { return gameover; } private FirstController ( ) { factory = Factory.GetInstance(); player = factory.player; Player.destroyEvent += SetGameOver; for (int i = 0 ; i < initialTanks; ++i) { factory.GetEnemy(); } } public void SetGameOver ( ) { gameover = true ; } }
GameDirector.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System.Collections;using System.Collections.Generic;using UnityEngine;public class GameDirector : Object { private static GameDirector _instance; public static GameDirector GetInstance ( ) { if (_instance == null ) _instance = new GameDirector(); return _instance; } private GameDirector ( ) { currentSceneController = FirstController.GetInstance(); } public SceneController currentSceneController; }
IUserControl.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 using System.Collections;using System.Collections.Generic;using UnityEngine;public class IUserControl : MonoBehaviour { UserActions actions; void Start ( ) { actions = GameDirector.GetInstance().currentSceneController as UserActions; } void Update ( ) { if (!actions.isGameOver()) { if (Input.GetKey(KeyCode.W)) { actions.moveForward(); } if (Input.GetKey(KeyCode.S)) { actions.moveBackWard(); } if (Input.GetKeyDown(KeyCode.Space)) { actions.shoot(); } float offsetX = Input.GetAxis("Horizontal1" ); actions.turn(offsetX); } } }
myCameraController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System.Collections;using System.Collections.Generic;using UnityEngine;public class myCameraController : MonoBehaviour { Transform Character; public float smoothTime = 0.02 f; private Vector3 AVelocity = Vector3.zero; void Start ( ) { Character = (GameDirector.GetInstance().currentSceneController as FirstController).player.transform; } void Update ( ) { transform.position = Vector3.SmoothDamp(transform.position, Character.position + Vector3.up * 20 , ref AVelocity, smoothTime); } }
player.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 using System.Collections;using System.Collections.Generic;using UnityEngine;public class Player : Tank { public delegate void destroy ( ) ; public static event destroy destroyEvent; void Start ( ) { setHp(500 f); } void Update ( ) { if (getHp() <= 0 ) { this .gameObject.SetActive(false ); if (destroyEvent != null ) { destroyEvent(); } } } }
SceneControlelr.cs
1 2 3 4 5 6 7 8 using System.Collections;using System.Collections.Generic;using UnityEngine;public interface SceneController { void LoadResources ( ) ; }
Tank.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.AI;public class Tank : MonoBehaviour { private float hp; public float getHp ( ) { return hp; } public void setHp (float hp ) { this .hp = hp; } }
UserActions.cs
1 2 3 4 5 6 7 8 9 10 11 12 using System.Collections;using System.Collections.Generic;using UnityEngine;public interface UserActions { void moveForward ( ) ; void moveBackWard ( ) ; void turn (float offsetX ) ; void shoot ( ) ; bool isGameOver ( ) ; }