游戏要求
- 使用unity3d的NavMesh实现AI坦克大战
- 敌人可以自动找到玩家,并能自动避开障碍物
- 玩家可以攻击敌人,敌人也可以攻击玩玩家
视频地址: https://v.youku.com/v_show/id_XMzY3NDY0NjY2NA==.html?spm=a2h0j.11185381.listitem_page1.5~A
资源准备
- 到官方商店下载项目Tanks! Tutorial。
- 我们可以只用里面的坦克模型,子弹模型,以及地图(也可以自己制作地图)
我们需要对地图进行烘焙,以便敌人可以AI寻路
我们可以将地图上的一些建筑,或者树设为障碍物,让敌人可以自动避开他们给坦克都加上NavMeshAgent组件
代码部分
Tank.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tank : MonoBehaviour {
public float Hp { get; set; }
}
Player.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : Tank {
// Use this for initialization
void Start () {
Hp = 10;
}
// Update is called once per frame
void Update () {
if (Hp <= 0)
{
ParticleSystem ps = Singleton<Factory>.Instance.GetTankPs();
ps.transform.position = transform.position;
ps.Play();
this.gameObject.SetActive(false);
Director.getInstance().currentSceneController.SetGameOver(true);
}
}
}
Enemy.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Enemy : Tank {
// Use this for initialization
private Vector3 target;
private NavMeshAgent agent;
private bool gameover;
void Start () {
Hp = 5;
agent = GetComponent<NavMeshAgent>();
StartCoroutine(shoot());
}
// Update is called once per frame
void Update () {
gameover = Director.getInstance().currentSceneController.GetGameOver();
if (!gameover)
{
target = Director.getInstance().currentSceneController.player.transform.position;
if (Hp <= 0)
{
ParticleSystem ps = Singleton<Factory>.Instance.GetTankPs();
ps.transform.position = transform.position;
ps.Play();
Director.getInstance().currentSceneController.Score += 1;
Singleton<Factory>.Instance.RecycleEnemy(this.gameObject);
}
else
{
transform.LookAt(target);
agent.SetDestination(target);
}
}
else
{
agent.velocity = Vector3.zero;
agent.ResetPath();
}
}
IEnumerator shoot()
{//协程实现npc坦克每隔1s进行射击
while (!gameover)
{
for (float i = 1; i > 0; i -= Time.deltaTime)
{
yield return 0;
}
if (Vector3.Distance(transform.position, target) < 20)
{//和玩家坦克距离小于20,则射击
Factory f = Singleton<Factory>.Instance;
GameObject bullet =f.GetBullet(1);//获取子弹,传入的参数表示发射子弹的坦克类型
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<Rigidbody>();
rb.AddForce(bullet.transform.forward * 20, ForceMode.Impulse);//发射子弹
}
}
}
}
Bullet.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour {
public int type; // 0 表示player的子弹, 1 表示enemy的子弹,用来防止被自己人射死 // Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnCollisionEnter(Collision collision)
{
ParticleSystem ps = Singleton<Factory>.Instance.GetBulletPs();
ps.transform.position = transform.position;
if (collision.gameObject.tag == "tank" && this.type == 0)
{
collision.gameObject.GetComponent<Tank>().Hp -= 3;
} else if (collision.gameObject.tag == "Player" && this.type == 1)
{
collision.gameObject.GetComponent<Tank>().Hp -= 2;
}
ps.Play();
if (this.gameObject.activeSelf)
{
Singleton<Factory>.Instance.RecycleBullet(this.gameObject);
}
}
}
Factory.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Factory : MonoBehaviour {
public GameObject player;
public GameObject enemy;
public GameObject bullet;
public ParticleSystem bulletPs;
public ParticleSystem tankPs;
// Use this for initialization
private Queue<GameObject> enemys = new Queue<GameObject>();
private Queue<GameObject> bullets = new Queue<GameObject>();
private List<ParticleSystem> bulletPses = new List<ParticleSystem>();
private List<ParticleSystem> tankPses = new List<ParticleSystem>();
void Start () {
}
// Update is called once per frame
void Update () {
}
public GameObject GetPlayer()
{
return Instantiate<GameObject>(player);
}
public GameObject GetEnemy()
{
GameObject e = null;
Vector3 pos;
if (enemys.Count == 0)
{
e = Instantiate<GameObject>(enemy);
pos = new Vector3(Random.Range(-90, 90), 0, Random.Range(-90, 90));
while (Vector3.Distance(Director.getInstance().currentSceneController.player.transform.position, pos) < 20)
{
pos = new Vector3(Random.Range(-90, 90), 0, Random.Range(-90, 90));
}
e.transform.position = pos;
return e;
}
e = enemys.Dequeue();
pos = new Vector3(Random.Range(-90, 90), 0, Random.Range(-90, 90));
while (Vector3.Distance(Director.getInstance().currentSceneController.player.transform.position, pos) < 20)
{
pos = new Vector3(Random.Range(-90, 90), 0, Random.Range(-90, 90));
}
e.transform.position = pos;
return e;
}
public GameObject GetBullet(int type)
{
GameObject b = null;
if (bullets.Count == 0)
{
b = Instantiate<GameObject>(bullet);
b.GetComponent<Bullet>().type = type;
return b;
}
b = bullets.Dequeue();
b.GetComponent<Bullet>().type = type;
return b;
}
public void RecycleEnemy(GameObject e)
{
e.SetActive(false);
enemys.Enqueue(e);
}
public void RecycleBullet(GameObject b)
{
b.SetActive(false);
bullets.Enqueue(b);
}
public ParticleSystem GetBulletPs()
{
for (int i = 0; i < bulletPses.Count; ++i)
{
if (!bulletPses[i].isPlaying)
{
return bulletPses[i];
}
}
ParticleSystem p = Instantiate<ParticleSystem>(bulletPs);
bulletPses.Add(p);
return p;
}
public ParticleSystem GetTankPs()
{
for (int i = 0; i < tankPses.Count; ++i)
{
if (!tankPses[i].isPlaying)
{
return tankPses[i];
}
}
ParticleSystem p = Instantiate<ParticleSystem>(tankPs);
tankPses.Add(p);
return p;
}
}
IUserAction
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction{
void move();
void shoot();
}
SceneControll.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneController : MonoBehaviour, IUserAction {
public GameObject player;
public float moveSpeed = 10.0f; //玩家移动速度
public float rotateSpeed = 60.0f; //玩家旋转速度
public int enemyNum = 6;
public GameObject BustedTank;
public int Score { get; set; }
private bool isGameOver = false;
private Factory f;
void Awake()
{
f = Singleton<Factory>.Instance;
player = f.GetPlayer();
Director director = Director.getInstance();
director.currentSceneController = this;
Score = 0;
}
public void move()
{
float h = Input.GetAxisRaw("Horizontal"); //获取玩家水平轴上的输入
float v = Input.GetAxisRaw("Vertical"); //获取玩家在垂直方向的输入
player.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime * v);
//v<0表示获取玩家向后的输入,玩家以moveSpeed的速度向后运动
player.transform.Rotate(Vector3.up * h * rotateSpeed * Time.deltaTime);
}
public void shoot()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GameObject b = f.GetBullet(0);
b.transform.position = new Vector3(player.transform.position.x, 1.5f, player.transform.position.z) + player.transform.forward * 1.5f;
b.transform.forward = player.transform.forward;//设置子弹方向
Rigidbody rb = b.GetComponent<Rigidbody>();
rb.AddForce(b.transform.forward * 20, ForceMode.Impulse);//发射子弹
}
}
// Use this for initialization
void Start () {
for (int i = 0; i < enemyNum; ++i)
{
f.GetEnemy();
}
}
// Update is called once per frame
void Update () {
if (!isGameOver)
{
Camera.main.transform.position = new Vector3(player.transform.position.x, 15, player.transform.position.z);
move();
shoot();
if(Score == enemyNum)
{
isGameOver = true;
}
}
}
public void SetGameOver(bool gameover)
{
isGameOver = gameover;
if(gameover)
{
GameObject b = Instantiate<GameObject>(BustedTank);
b.transform.position = player.transform.position;
}
}
public bool GetGameOver()
{
return isGameOver;
}
}
Director.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Director : System.Object {
private static Director _instance;
public SceneController currentSceneController { get; set; }
public static Director getInstance()
{
if (_instance == null)
{
_instance = new Director();
}
return _instance;
}
}
UserGUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour {
// Use this for initialization
GUIStyle style;
SceneController scene;
void Start () {
style = new GUIStyle();
style.normal.textColor = Color.red;
style.fontSize = 20;
scene = Director.getInstance().currentSceneController;
}
// Update is called once per frame
void Update () {
}
void OnGUI()
{
if(scene.GetGameOver())
{
GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 25, 200, 50), "GameOver, Your Score is " + scene.Score.ToString(), style);
} else
{
GUI.Label(new Rect(Screen.width / 2 - 50, 10, 100, 50), Director.getInstance().currentSceneController.Score.ToString(), style);
}
}
}
Singleton.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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;
}
}
}