智能巡逻兵
提交要求:
游戏设计要求:
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
程序设计要求:
-
必须使用订阅与发布模式传消息
- subject:OnLostGoal
- Publisher: ?
- Subscriber: ?
-
工厂模式生产巡逻兵
-
友善提示1:生成 3~5个边的凸多边型
- 随机生成矩形
- 在矩形每个边上随机找点,可得到 3 - 4 的凸多边型
- 5 ?
-
友善提示2:参考以前博客,给出自己新玩法
游戏规则:
- 玩家每甩掉一个巡逻兵得1分,得8分获得胜利,撞到巡逻兵游戏失败。
- 左上角显示游戏分数,左下角是小地图方便观察全景。
- 每个方格有一个巡逻兵,当玩家进入方格后,方格内的巡逻兵会感知到玩家,自动追踪。
- 游戏胜利或失败后,出现“Restart”按键,点击重新开始游戏。
游戏效果展示:
项目链接:
项目设置:
- 新建Plane预制,包括四面的外墙wall,中间分隔方块的innerwall,和每个方格的trigger。为trigger挂上AreaCollider.cs。
- 从“Adventure - Sample Game”中导入Player。新建一个Animator Controller,名称为PlayerController。加上Rigidbody和Capsule Colloder部件。
- 从“Toon Soldiers WW2”中导入士兵作为Patrol。新建一个Animator Controller,名称为PatrolController。加上Rigidbody和Capsule Colloder部件。挂上PatrolData.cs和PlayerCollider.cs。
-
新建Second Camera,设置如下。
-
新建空对象,挂载如下scripts。
代码说明
- Actions.cs: SSAction类和SSActionManager类与之前项目相同。PatrolActionManger类负责巡逻兵的运动管理,包括Patrol的运动和销毁。GoPatrolAction类负责巡逻兵巡逻的动作,视线拉巡逻兵走任意四边形。PatrolFollowAction类负责巡逻兵追逐玩家的动作,当玩家进入巡逻兵所在方格时,巡逻兵追逐玩家,当玩家离开方格时,巡逻兵不再追逐,调用回调函数,继续巡逻。
public class PatrolActionManager : SSActionManager
{
private GoPatrolAction go_patrol;
public void GoPatrol(GameObject patrol)
{
go_patrol = GoPatrolAction.GetSSAction(patrol.transform.position);
this.RunAction(patrol, go_patrol, this);
}
public void DestroyAllAction()
{
DestroyAll();
}
}
public class GoPatrolAction : SSAction
{
private enum Dirction { E, N, W, S };
private float pos_x, pos_z;
private float move_length;
private float move_speed = 1.2f;
private bool move_sign = true;
private Dirction dirction = Dirction.E;
private PatrolData data;
private GoPatrolAction() { }
public static GoPatrolAction GetSSAction(Vector3 location)
{
GoPatrolAction action = CreateInstance<GoPatrolAction>();
action.pos_x = location.x;
action.pos_z = location.z;
action.move_length = Random.Range(5, 8);
return action;
}
public override void Update()
{
if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);
if (transform.position.y != 0)
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
Gopatrol();
if (data.follow_player && data.wall_sign == data.sign){
this.destroy = true;
this.callback.SSActionEvent(this,0,this.gameobject);
}
}
public override void Start()
{
data = this.gameobject.GetComponent<PatrolData>();
}
void Gopatrol()
{
if (move_sign){
switch (dirction){
case Dirction.E:
pos_x -= move_length;
break;
case Dirction.N:
pos_z += move_length;
break;
case Dirction.W:
pos_x += move_length;
break;
case Dirction.S:
pos_z -= move_length;
break;
}
move_sign = false;
}
this.transform.LookAt(new Vector3(pos_x, 0, pos_z));
float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));
if (distance > 0.9){
transform.position = Vector3.MoveTowards(this.transform.position, new Vector3(pos_x, 0, pos_z), move_speed * Time.deltaTime);
}
else{
dirction = dirction + 1;
if(dirction > Dirction.S)dirction = Dirction.E;
move_sign = true;
}
}
}
public class PatrolFollowAction : SSAction
{
private float speed = 2f;
private GameObject player;
private PatrolData data;
private PatrolFollowAction() { }
public static PatrolFollowAction GetSSAction(GameObject player)
{
PatrolFollowAction action = CreateInstance<PatrolFollowAction>();
action.player = player;
return action;
}
public override void Update()
{
if (transform.localEulerAngles.x != 0 || transform.localEulerAngles.z != 0)
transform.localEulerAngles = new Vector3(0, transform.localEulerAngles.y, 0);
if (transform.position.y != 0)
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
Follow();
if (!data.follow_player || data.wall_sign != data.sign){
this.destroy = true;
this.callback.SSActionEvent(this,1,this.gameobject);
}
}
public override void Start()
{
data = this.gameobject.GetComponent<PatrolData>();
}
void Follow()
{
transform.position = Vector3.MoveTowards(this.transform.position, player.transform.position, speed * Time.deltaTime);
this.transform.LookAt(player.transform.position);
}
}
- PatrolData.cs: 定义了Patrol具有的一些属性。其中follow_player标志这个巡逻兵是否在追逐玩家,wall_sign指的是Patrol在第几个方格。
public class PatrolData : MonoBehaviour
{
public int sign;
public bool follow_player = false;
public int wall_sign = -1;
public GameObject player;
public Vector3 start_position;
}
- PlayerCollider.cs: 挂载在Patrol上,当Patrol和Player碰撞时,游戏结束。
public class PlayerCollider : MonoBehaviour
{
void OnCollisionEnter(Collision other)
{
if (other.gameObject.name == "Player(Clone)")
Singleton<GameEventManager>.Instance.PlayerGameover();
}
}
- PropFactory.cs: Patrol工厂类,首先得到九个巡逻兵的起始位置,然后根据预设新建巡逻兵,设置他们的位置,PatrolData中的sign和start_position,最后将它们添加到list里。
public class PropFactory : MonoBehaviour
{
private GameObject patrol = null;
private List<GameObject> used = new List<GameObject>();
private Vector3[] pos = new Vector3[9];
public Controller sceneController;
public List<GameObject> GetPatrols()
{
int[] pos_x = { -6, 4, 13 };
int[] pos_z = { -4, 6, -13 };
int[] num = {8, 1, 9, 7, 2, 6, 4, 3, 5};
int index = 0;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
pos[index] = new Vector3(pos_x[i], 5, pos_z[j]);
index++;
}
}
for(int i = 0; i < 9; i++){
patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
patrol.transform.position = pos[i];
patrol.GetComponent<PatrolData>().sign = num[i];
patrol.GetComponent<PatrolData>().start_position = pos[i];
used.Add(patrol);
}
return used;
}
}
- AreaCollider.cs:挂载在Plane的trigger上。负责当和Player发生碰撞时,将sceneController中的wall_sign变量设置为trigger的sign,以此使sceneController知道Player在哪一个方格,需要设置所在方格内的Patrol追逐Player。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AreaCollider : MonoBehaviour
{
public int sign = 0;
public Controller sceneController;
private void Start()
{
sceneController = (Controller)SSDirector.GetInstance().CurrentSceneController;
}
void OnTriggerEnter(Collider collider)
{
if (collider.gameObject.name == "Player(Clone)"){
sceneController = (Controller)SSDirector.GetInstance().CurrentSceneController;
sceneController.wall_sign = sign;
}
}
}
- Controller.cs:Update函数完成了追逐Player的Patrol的更新。LoadResource函数完成了资源的加载,主要是Plane、Player、Patrols。MovePlayer完成在游戏未结束时Player的移动和转向。
public class Controller : MonoBehaviour, IUserAction, ISceneController
{
public PropFactory patrol_factory;
public ScoreRecorder recorder;
public PatrolActionManager action_manager;
public int wall_sign = 1;
public GameObject player;
public float player_speed = 30;
public float rotate_speed = 10;
private List<GameObject> patrols;
private bool game_over = false;
public int GetScore()
{
return recorder.GetScore();
}
public bool GetGameover()
{
return game_over;
}
public bool GetWin()
{
return recorder.score == 8;
}
public void Restart()
{
SceneManager.LoadScene(0);
}
void OnEnable()
{
GameEventManager.ScoreChange += AddScore;GameEventManager.GameoverChange += Gameover;
}
void OnDisable()
{
GameEventManager.ScoreChange -= AddScore;GameEventManager.GameoverChange -= Gameover;
}
void AddScore()
{
recorder.AddScore();
}
void Gameover(){
game_over = true;
action_manager.DestroyAllAction();
}
void Update()
{
for (int i = 0; i < patrols.Count; i++) {
patrols[i].gameObject.GetComponent<PatrolData>().wall_sign = wall_sign;
}
for (int i = 0; i < patrols.Count; i++){
if (patrols [i].gameObject.GetComponent<PatrolData> ().sign == patrols [i].gameObject.GetComponent<PatrolData> ().wall_sign) {
patrols [i].gameObject.GetComponent<PatrolData> ().follow_player = true;
patrols [i].gameObject.GetComponent<PatrolData> ().player = player;
} else {
patrols [i].gameObject.GetComponent<PatrolData>().follow_player = false;
patrols [i].gameObject.GetComponent<PatrolData>().player = null;
}
}
}
void Start()
{
wall_sign = 1;
SSDirector director = SSDirector.GetInstance();
director.CurrentSceneController = this;
patrol_factory = Singleton<PropFactory>.Instance;
action_manager = gameObject.AddComponent<PatrolActionManager>() as PatrolActionManager;
LoadResources();
recorder = Singleton<ScoreRecorder>.Instance;
}
public void LoadResources()
{
Instantiate(Resources.Load<GameObject>("Prefabs/Plane"));
player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(-10, 0, 10), Quaternion.Euler(0, 90, 0)) as GameObject;
patrols = patrol_factory.GetPatrols();
for (int i = 0; i < patrols.Count; i++){
action_manager.GoPatrol(patrols[i]);
}
}
public Vector3 movement;
public void MovePlayer(float translationX, float translationZ)
{
if(!game_over)
{
Vector3 direction = new Vector3(translationX, 0, translationZ).normalized;
player.transform.position = Vector3.MoveTowards(player.transform.position, player.transform.position+direction, player_speed * Time.deltaTime);
player.transform.LookAt(player.transform.position+direction);
}
}
}
- ScoreRecorder.cs:负责分数的计算和返回,比较简单。
public class ScoreRecorder : MonoBehaviour
{
public Controller sceneController;
public int score = 0;
void Start()
{
sceneController = (Controller)SSDirector.GetInstance().CurrentSceneController;
sceneController.recorder = this;
}
public int GetScore()
{
return score;
}
public void AddScore()
{
score++;
}
}
- Interface.cs:接口。
public interface ISceneController{void LoadResources();}
public interface IUserAction
{
void MovePlayer(float translationX, float translationZ);
int GetScore();
bool GetGameover();
bool GetWin();
void Restart();
}
public interface ISSActionCallback{void SSActionEvent(SSAction source,int intParam = 0,GameObject objectParam = null);}
public interface IGameStatusOp
{
void PlayerEscape();
void PlayerGameover();
}
- GameEventManager.cs:订阅与发布模式中发布事件的类。订阅者订阅GameEventManager类,当其他类使用GameEventManager的方法发布消息时,订阅者(这里是Controller)会调用相应的方法(这里是得分、游戏结束和获胜)。
public class GameEventManager : MonoBehaviour
{
public delegate void ScoreEvent();
public static event ScoreEvent ScoreChange;
public delegate void GameoverEvent();
public static event GameoverEvent GameoverChange;
public delegate void WinEvent();
public static event WinEvent WinChange;
public void PlayerEscape()
{
if (ScoreChange != null){
ScoreChange();
}
}
public void PlayerGameover()
{
if (GameoverChange != null){
GameoverChange();
}
}
public void PlayerWin()
{
if (WinChange != null){
WinChange();
}
}
}
- UserGUI.cs:左上角显示分数。游戏失败提示GameOver,显示Restar按键。游戏获胜提示YouWin,显示Restart按键。
public class UserGUI : MonoBehaviour {
private IUserAction action;
private GUIStyle style = new GUIStyle();
void Start ()
{
style.fontSize = 25;
action = SSDirector.GetInstance().CurrentSceneController as IUserAction;
}
void Update()
{
float translationX = Input.GetAxis("Horizontal");
float translationZ = Input.GetAxis("Vertical");
action.MovePlayer(translationX, translationZ);
}
private void OnGUI()
{
GUI.Label(new Rect(10, 5, 200, 50), "Score:");
GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString());
if(action.GetGameover()){
GUI.Label(new Rect(Screen.width / 2 - 70, 80, 200, 50), "Game Over", style);
if (GUI.Button(new Rect(Screen.width / 2 - 50, 150, 100, 50), "Restart"))
{
action.Restart();
return;
}
}
if(action.GetWin()){
GUI.Label(new Rect(Screen.width / 2 - 70, 80, 200, 50), "You Win", style);
if (GUI.Button(new Rect(Screen.width / 2 - 50, 150, 100, 50), "Restart"))
{
action.Restart();
return;
}
}
}
}
- FollowCam.cs:挂载在MainCamera上,使MainCamera始终跟随Player。
public class FollowCam : MonoBehaviour
{
public Transform playerTransform;
public Vector3 deviation;
void Start()
{
playerTransform = GameObject.Find("Player(Clone)").GetComponent<Transform>();
deviation = transform.position - playerTransform.position;
}
void Update()
{
transform.position = playerTransform.position + deviation;
transform.LookAt (playerTransform);
}
}
ollowCam.cs:挂载在MainCamera上,使MainCamera始终跟随Player。
public class FollowCam : MonoBehaviour
{
public Transform playerTransform;
public Vector3 deviation;
void Start()
{
playerTransform = GameObject.Find("Player(Clone)").GetComponent<Transform>();
deviation = transform.position - playerTransform.position;
}
void Update()
{
transform.position = playerTransform.position + deviation;
transform.LookAt (playerTransform);
}
}
- Singleton.cs、SSDirector.cs:和之前的项目是一致的。