游戏设计要求
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
程序设计要求
- 必须使用订阅与发布模式传消息
- 工厂模式生产巡逻兵
游戏实现
巡逻兵
为巡逻兵的父节点添加刚体和Capsule Collider,用于检测与玩家和墙壁的碰撞;为其子节点添加Box Collider,用于检测玩家是否进入巡逻兵的追踪范围。
- DirectionController
用于存储东南西北四个方向,并控制巡逻兵转向
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 public class DirectionController : MonoBehaviour {
private Dictionary<int, Vector3> directions;//存储东南西北四个方向,巡逻兵沿矩形路径巡逻
// Use this for initialization
void Awake () {
directions = new Dictionary<int, Vector3>();
directions.Add(0, new Vector3(1, 0, 0));
directions.Add(1, new Vector3(0, 0, 1));
directions.Add(2, new Vector3(-1, 0, 0));
directions.Add(3, new Vector3(0, 0, -1));
}
public Vector3 RandomDirection()//随机获得一个方向
{
return directions[Random.Range(0, 4)];
}
public Vector3 ChangeDirection(Vector3 direction)//改变方向
{
if (direction.Equals(directions[0]))
{
return directions[1];
}
else if (direction.Equals(directions[1]))
{
return directions[2];
}
else if (direction.Equals(directions[2]))
{
return directions[3];
}
else if (direction.Equals(directions[3]))
{
return directions[0];
}
return RandomDirection();
}
}
- PatrolData
用于保存巡逻兵的基本信息
1
2
3
4
5
6
7 public class PatrolData : MonoBehaviour {
public Vector3 position; //记录巡逻兵的初始位置
public bool follow; //记录巡逻兵是否追踪玩家
public GameObject player; //记录玩家
public Vector3 direction; //记录巡逻兵的巡逻方向
public float distance; //记录巡逻兵沿着某一方向行走的距离
}
- PatrolFactory
用于生产并初始化巡逻兵
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 public class PatrolFactory : MonoBehaviour {
public GameObject patrolPrefab;
public List<GameObject> GetPatrol()
{
GameObject newObject;
List<GameObject> patrols=new List<GameObject>();
int[] pos_x = { -15, 0, 15 };
int[] pos_z = { -20, 20 };
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 2; j++)
{
newObject = Instantiate<GameObject>(patrolPrefab, new Vector3(pos_x[i], 0, pos_z[j]), Quaternion.identity);
newObject.GetComponent<PatrolData>().position = new Vector3(pos_x[i], 0, pos_z[j]);
newObject.GetComponent<PatrolData>().follow = false;
newObject.GetComponent<PatrolData>().player = SSDirector.GetInstance().CurrentSceneController.GetPlayer();
newObject.GetComponent<PatrolData>().direction = Singleton<DirectionController>.Instance.RandomDirection();
newObject.GetComponent<PatrolData>().distance = 0;
patrols.Add(newObject);
}
}
return patrols;
}
}
- PatrolAction
巡逻兵巡逻动作
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 public class PatrolAction : SSAction {
private float speed = 5;
private Vector3 direction;
private float distance;
private float turnSpeed = 10;
public static PatrolAction GetSSAction()
{
PatrolAction action = ScriptableObject.CreateInstance<PatrolAction>();
return action;
}
// Use this for initialization
public override void Start()
{
direction = gameobject.GetComponent<PatrolData>().direction;
distance = gameobject.GetComponent<PatrolData>().distance;
}
// Update is called once per frame
public override void Update () {
if (gameobject.GetComponent<PatrolData>().follow)
{
this.destroy = true;
this.callback.SSActionEvent(this,0,gameobject);
return;
}
direction = gameobject.GetComponent<PatrolData>().direction;
distance = gameobject.GetComponent<PatrolData>().distance;
if (distance < 10)//巡逻兵移动
{
transform.position += direction * speed * Time.deltaTime;
//将方向转换为四元数
Quaternion quaDir = Quaternion.LookRotation(direction, Vector3.up);
//缓慢转动到目标点
transform.rotation = Quaternion.Lerp(transform.rotation, quaDir, Time.fixedDeltaTime * turnSpeed);
gameobject.GetComponent<PatrolData>().distance += speed * Time.deltaTime;
}
else//巡逻兵转向
{
gameobject.GetComponent<PatrolData>().distance = 0;
gameobject.GetComponent<PatrolData>().direction =
Singleton<DirectionController>.Instance.ChangeDirection(direction);
}
}
}
- PatrolFollowAction
巡逻兵追踪玩家动作
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 public class PatrolFollowAction : SSAction
{
private GameObject player;
private float speed = 5.8f;
public static PatrolFollowAction GetSSAction()
{
PatrolFollowAction action = ScriptableObject.CreateInstance<PatrolFollowAction>();
return action;
}
// Use this for initialization
public override void Start()
{
player = gameobject.GetComponent<PatrolData>().player;
}
// Update is called once per frame
public override void Update()
{
if (!gameobject.GetComponent<PatrolData>().follow)
{
this.destroy = true;
this.callback.SSActionEvent(this,1,gameobject);
return;
}
transform.position = Vector3.MoveTowards(transform.position, player.transform.position, speed * Time.deltaTime);
transform.LookAt(player.transform.position);
}
}
玩家
为玩家预制添加刚体和Capsule Collider,使玩家能够与其他物体发生碰撞。
- UserGUI
接受玩家输入,控制玩家移动和使用技能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 private void Update()
{
if (action.GetGameState() == GameState.GAMEOVER) return;
//获取方向键的偏移量
translationX = Input.GetAxis("Horizontal");
translationZ = Input.GetAxis("Vertical");
//移动玩家
action.PlayerMove(new Vector3(translationX,0,translationZ));
if (coolTime<=0 && Input.GetButtonDown("Jump"))//如果不在冷却时间内,按下空格可以触发加速技能
{
action.SpeedUp();
coolTime = 10;
}
else
{
coolTime -= Time.deltaTime;
}
}
private void FixedUpdate()
{
action.PlayerRotate(new Vector3(translationX, 0, translationZ));//玩家旋转方向
}
- FirstController
实现玩家移动、转向和加速,部分代码如下
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 public void PlayerMove(Vector3 target)//玩家向目标移动
{
if (gameState != GameState.GAMEOVER)
{
if (target.Equals(Vector3.zero))
{
player.GetComponent<Animator>().SetBool("run", false);
}
else
{
player.GetComponent<Animator>().SetBool("run", true);
player.transform.position += target * moveSpeed * Time.deltaTime;
}
}
}
public void SpeedUp()//加速
{
moveSpeed = 10;
speedTime = 3;
}
public void PlayerRotate(Vector3 direction)
{
if (gameState != GameState.GAMEOVER)
{
if (!direction.Equals(Vector3.zero))
{
//将方向转换为四元数
Quaternion quaDir = Quaternion.LookRotation(direction, Vector3.up);
//缓慢转动到目标点
player.transform.rotation = Quaternion.Lerp(player.transform.rotation, quaDir, Time.fixedDeltaTime * turnSpeed);
}
}
}
巡逻兵的碰撞检测
- PatrolCollision
巡逻兵的Capsule Collider的碰撞检测,用于检测与玩家和墙壁的碰撞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 public class PatrolCollision : MonoBehaviour {
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Player")//当巡逻兵碰撞上玩家时,游戏结束
{
Singleton<GameEventManager>.Instance.PlayerCollide();
gameObject.GetComponentInChildren<Animator>().SetTrigger("attack");
}
else//当巡逻兵碰撞到其他物体如墙壁时,转换方向
{
gameObject.GetComponent<PatrolData>().distance = 0;
gameObject.GetComponent<PatrolData>().direction =
Singleton<DirectionController>.Instance.ChangeDirection(gameObject.GetComponent<PatrolData>().direction);
}
}
}
- PatrolFollowCollision
巡逻兵的Box Collider上的碰撞检测,用于检测玩家进入和退出碰撞盒,决定是否追踪玩家
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 public class PatrolFollowCollision : MonoBehaviour {
private void OnTriggerEnter(Collider collider)//当玩家进入范围内时,追踪玩家
{
if (collider.gameObject.tag == "Player")
{
gameObject.GetComponent<BoxCollider>().size = new Vector3(15, 1, 15);//将碰撞盒变大防止反复进入和退出范围
Singleton<GameEventManager>.Instance.PlayerClose(gameObject);
}
}
private void OnTriggerExit(Collider collider)//当玩家脱离范围时,停止追踪
{
if (collider.gameObject.tag == "Player")
{
Singleton<GameEventManager>.Instance.Escape(gameObject);
gameObject.GetComponent<BoxCollider>().size = new Vector3(10, 1, 10);//恢复碰撞盒大小
}
}
}
订阅与发布模式部分
- GameEventManager
定义事件管理器,专门用于发布事件
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 public class GameEventManager : MonoBehaviour {
public delegate void GameOver();
public static event GameOver OnGameOver;
public delegate void PatrolFollow(GameObject ob);
public static event PatrolFollow OnPatrolFollow;
public delegate void PlayerEscape(GameObject ob);
public static event PlayerEscape OnPlayerEscape;
public void PlayerCollide()//玩家碰撞
{
if (OnGameOver != null)
{
OnGameOver();
}
}
public void PlayerClose(GameObject ob)//玩家靠近巡逻兵
{
if (OnPatrolFollow != null)
{
OnPatrolFollow(ob);
}
}
public void Escape(GameObject ob)//玩家脱离巡逻兵
{
if (OnPlayerEscape != null)
{
OnPlayerEscape(ob);
}
}
}
- FirstController
场景控制器作为订阅者,只要相应事件发生,就会调用注册的方法
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 void OnEnable()
{
GameEventManager.OnGameOver += GameOver;
GameEventManager.OnPatrolFollow += Follow;
GameEventManager.OnPlayerEscape += Escape;
}
void OnDisable()
{
GameEventManager.OnGameOver -= GameOver;
GameEventManager.OnPatrolFollow -= Follow;
GameEventManager.OnPlayerEscape -= Escape;
}
public void GameOver()//游戏结束
{
gameState = GameState.GAMEOVER;
player.GetComponent<Animator>().SetBool("death", true);
player.GetComponent<Animator>().SetTrigger("dieTrigger");
actionManager.DestroyAllActions();
foreach(GameObject patrol in Patrols)
{
patrol.GetComponentInChildren<Animator>().SetBool("run", false);
patrol.transform.localEulerAngles = new Vector3(0, patrol.transform.localEulerAngles.y, 0);
patrol.transform.position = new Vector3(patrol.transform.position.x, 0, patrol.transform.position.z);
}
}
void Follow(GameObject ob)//巡逻兵跟随
{
ob.GetComponentInParent<PatrolData>().follow = true;
}
void Escape(GameObject ob)//玩家脱离
{
ob.GetComponentInParent<PatrolData>().follow = false;
Singleton<ScoreRecorder>.Instance.AddScore();
}具体代码和游戏视频请参考我的github:Patrol
本次实验用到了订阅与发布模式,这种模式使得游戏的各个部分相对独立,随着工程的不断扩展变大,程序代码也会不断变多,使用这种方式来减少各部分之间的耦合,就变得非常有意义。