2019-10-29

智能巡逻兵

目录:

  • 游戏概述
  • 制作巡逻兵
  • 制作巡逻路径
  • 制作巡逻兵工厂
  • 发布订阅者模式
  • 制作玩家

    游戏概述

    这次的游戏是基于智能巡逻兵的“困难”躲避游戏,玩家通过wads键控制方向,当玩家出现在巡逻兵侦察范围会自动追击,追到则失败。玩家需要摆脱巡逻兵,摆脱后巡逻兵依旧巡逻,分数+1。巡逻兵的巡逻路线采用随机生成矩形,然后再再边上随机点构成多边形,作为巡逻路线。

    制作巡逻兵

    在网上找的师兄的博客,然后对照他的步骤找的巡逻兵的资源

    在Patrol上添加一个Capsule Collider,用于检测巡逻兵与障碍物,玩家的碰撞。在Bip001上添加一个Capsule Collider,用于感知玩家。自定义Collider的形状,使巡逻兵有一定的视线范围。

    每个巡逻兵,需要发现玩家的标志变量,存储巡逻路径的变量,追踪玩家动作,巡逻动作。
    巡逻兵平常进行巡逻动作,按照存储的巡逻路径进行巡逻。当发现玩家后,停止巡逻动作,执行追逐玩家动作,如果被甩掉,则继续执行巡逻动作。

    巡逻兵数据

1
2
3
4
5
6
7
8
9
10

public class PatrolData : MonoBehaviour
{
public bool isPlayerInRange; // 玩家是否在侦测范围里
public bool isFollowing; // 是否正在追击
public bool isCollided; // 是否发生碰撞
public int patrolRegion; // 巡逻兵所在区域
public int playerRegion; // 玩家所在区域
public GameObject player; // 所追击的玩家
}

巡逻兵碰撞检测

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PatrolCollide : MonoBehaviour
{
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.tag == "Player") {
// 当玩家与巡逻兵相撞
this.GetComponent<Animator>().SetTrigger("shoot");
Singleton<GameEventManager>.Instance.OnPlayerCatched();
} else {
// 当巡逻兵碰到其他障碍物
this.GetComponent<PatrolData>().isCollided = true;
}
}
}

巡逻兵动作管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PatrolActionManager : ActionManager, ActionCallback
{
public PatrolAction patrol;
public PatrolFollowAction follow;

// 巡逻
public void Patrol(GameObject ptrl) {
this.patrol = PatrolAction.GetAction(ptrl.transform.position);
this.RunAction(ptrl, patrol, this);
}

// 追击
public void Follow(GameObject player, GameObject patrol) {
this.follow = PatrolFollowAction.GetAction(player);
this.RunAction(patrol, follow, this);
}

//停止所有动作
public void DestroyAllActions() {
DestroyAll();
}

public void ActionEvent(Action source, ActionEventType events = ActionEventType.Completed, int intParam = 0, string strParam = null, object objectParam = null){ }
}

巡逻兵追击玩家

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
public class PatrolFollowAction : Action
{
private float speed = 1.5f; // 跟随玩家的速度
private GameObject player; // 玩家
private PatrolData data; // 巡逻兵数据

public static PatrolFollowAction GetAction(GameObject player) {
PatrolFollowAction action = CreateInstance<PatrolFollowAction>();
action.player = player;
return action;
}

public override void Start() {
data = this.gameObject.GetComponent<PatrolData>();
}

public override void Update() {
if (Director.GetInstance().CurrentSceneController.getGameState().Equals(GameState.RUNNING)) {
// 追击玩家
transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
this.transform.LookAt(player.transform.position);
// 如果满足要求,而巡逻兵正在追击,则停止追击,开始巡逻
if (data.isFollowing && (!(data.isPlayerInRange && data.patrolRegion == data.playerRegion) || data.isCollided)) {
this.destroy = true;
this.enable = false;
this.callback.ActionEvent(this);
this.gameObject.GetComponent<PatrolData>().isFollowing = false;
Singleton<GameEventManager>.Instance.PlayerEscape(this.gameObject);
}
}
}
}

巡逻兵检测范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PlayerInRange : MonoBehaviour
{
void OnTriggerEnter(Collider collider) {
if (collider.gameObject.tag == "Player") {
// 玩家进入巡逻兵追捕范围
this.gameObject.transform.parent.GetComponent<PatrolData>().isPlayerInRange = true;
this.gameObject.transform.parent.GetComponent<PatrolData>().player = collider.gameObject;
}
}
void OnTriggerExit(Collider collider) {
if (collider.gameObject.tag == "Player") {
// 玩家离开巡逻兵追捕范围
this.gameObject.transform.parent.GetComponent<PatrolData>().isPlayerInRange = false;
this.gameObject.transform.parent.GetComponent<PatrolData>().player = null;
}
}
}

制作巡逻路径

巡逻路径,采取生成随机点列表作为路径,让巡逻兵从一个点,运动到另一个点,作为巡逻动作。
首先生成一个随机的矩形左下角点和边长,然后算出其余三个顶点,接着在它的四条边每条边随机选取一个点,作为巡逻路径点,这样可以得到一个随机的凸四边形

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
public float positionRange = 20.0f;
public float defaultSideLength = 5.0f;
public float yPosition = 1.0f;

/**
* Genrate the patrol path from a random rectangle
* by selecting points on its side randomly.
* @return {List<Vector2>} selected points.
*/
public List<Vector3> GetRandomRect(int sides = 4, float sideLength = 0) {
List<Vector3> rect = new List<Vector3>();

if (sideLength == 0) {
sideLength = defaultSideLength;
}

sideLength = Random.Range(10f, 20f);
Vector3 leftDown = new Vector3(
Random.Range(-positionRange, positionRange), yPosition, Random.Range(-positionRange, positionRange));
Vector3 rightDown = leftDown + Vector3.right * sideLength;
Vector3 rightUp = leftDown + Vector3.forward * sideLength;
Vector3 leftUp = rightDown + Vector3.forward * sideLength;

Vector3 temp = leftDown + Vector3.forward * sideLength * Random.Range(0f, 1f);
rect.Add(temp);
temp = leftUp + Vector3.right * sideLength * Random.Range(0f, 1f);
rect.Add(temp);
temp = rightUp + Vector3.forward * sideLength * Random.Range(0f, 1f);
rect.Add(temp);

if (sides >= 4) {
temp = rightDown + Vector3.right * (-sideLength) * Random.Range(0f, 0.5f);
rect.Add(temp);
if (sides == 5) {
temp = rightDown + Vector3.right * (-sideLength) * Random.Range(0f, 0.5f);
rect.Add(temp);
}
}
return rect;
}

制作巡逻兵工厂

只有巡逻兵的工厂,因为玩家只有一个。

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
public class PatrolFactory : MonoBehaviour
{
public GameObject patrol = null;
private List<PatrolData> used = new List<PatrolData>(); // 正在使用的巡逻兵

public List<GameObject> GetPatrols() {
List<GameObject> patrols = new List<GameObject>();
float[] pos_x = { -4.5f, 1.5f, 7.5f };
float[] pos_z = { 7.5f, 1.5f, -4.5f };
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
patrol.transform.position = new Vector3(pos_x[j], 0, pos_z[i]);
patrol.GetComponent<PatrolData>().patrolRegion = i * 3 + j + 1;
patrol.GetComponent<PatrolData>().playerRegion = 4;
patrol.GetComponent<PatrolData>().isPlayerInRange = false;
patrol.GetComponent<PatrolData>().isFollowing = false;
patrol.GetComponent<PatrolData>().isCollided = false;
patrol.GetComponent<Animator>().SetBool("pause", true);
used.Add(patrol.GetComponent<PatrolData>());
patrols.Add(patrol);
}
}
return patrols;
}

public void PausePatrol() {
//切换所有侦查兵的动画
for (int i = 0; i < used.Count; i++) {
used[i].gameObject.GetComponent<Animator>().SetBool("pause", true);
}
}

public void StartPatrol() {
//切换所有侦查兵的动画
for (int i = 0; i < used.Count; i++) {
used[i].gameObject.GetComponent<Animator>().SetBool("pause", false);
}
}
}

发布订阅者模式

多个订阅者拥有自己的对同一类时间的各自事件的处理逻辑,然后在每个订阅者实例化的时候,向一个专门负责事件发布与接收的地方注册自己的事件处理函数(回调函数)。然后发布者在感知并产生事件之后,向同样的负责事件分发的地方传送自己的事件,然后该事件依次进入不同订阅者的逻辑中,实施其自己的工作。
对于这个游戏来说,只有巡逻兵那个对事件发生并感知事件,事件逻辑已经足够简单,所以发布者和订阅者都可以简化成:发布者使每个巡逻兵,订阅者是FirstSceneController。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class PatrolActionManager : ActionManager, ActionCallback
{
public PatrolAction patrol;
public PatrolFollowAction follow;

// 巡逻
public void Patrol(GameObject ptrl) {
this.patrol = PatrolAction.GetAction(ptrl.transform.position);
this.RunAction(ptrl, patrol, this);
}

// 追击
public void Follow(GameObject player, GameObject patrol) {
this.follow = PatrolFollowAction.GetAction(player);
this.RunAction(patrol, follow, this);
}

//停止所有动作
public void DestroyAllActions() {
DestroyAll();
}

public void ActionEvent(Action source, ActionEventType events = ActionEventType.Completed, int intParam = 0, string strParam = null, object objectParam = null){ }
}

制作玩家

给预制挂载Collider和刚体属性
玩家动作触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class EnterRegion : MonoBehaviour
{
public int region; // 当前区域的区域编号
FirstSceneController sceneController; // 当前的场记

void OnTriggerEnter(Collider collider) {
sceneController = Director.GetInstance().CurrentSceneController as FirstSceneController;
if (collider.gameObject.tag == "Player") {
// 如果玩家进入区域,则标记玩家当前区域为该区域
sceneController.playerRegion = region;
}
}

private void OnTriggerExit(Collider collider) {
if (collider.gameObject.tag == "Patrol") {
// 如果巡逻兵尝试离开区域,则标记巡逻兵发生了碰撞,以控制转向
collider.gameObject.GetComponent<PatrolData>().isCollided = true;
}
}
}

游戏视频

游戏视频