Get Started with Unity3D - 7

坦克大战 - 人工智障版

参考博客

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;

// Use this for initialization
void Start () {
setHp(100f);
StartCoroutine(shoot());
}

// Update is called once per frame
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.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);

}

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 = 100f;
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.5f, player.transform.position.z) +
player.transform.forward * 1.5f;
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;

// Use this for initialization
void Start () {
actions = GameDirector.GetInstance().currentSceneController as UserActions;
}

// Update is called once per frame
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.02f;
private Vector3 AVelocity = Vector3.zero;
// Use this for initialization
void Start()
{
Character = (GameDirector.GetInstance().currentSceneController as FirstController).player.transform;
}

// Update is called once per frame
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(500f);//设置初始生命值为500
}
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();
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×