巡逻兵小游戏
1、设计要求
游戏规则
使用WSAD或方向键上下左右移动player,进入巡逻兵的追捕后逃脱可积累一分,若与巡逻兵碰撞则游戏结束,收集完地图上的所有水晶即可获胜。游戏设计要求:
- 创建一个地图和若干巡逻兵(使用动画);
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算;
- 巡逻兵碰撞到障碍物,则会自动选下一个点为目标;
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
- 失去玩家目标后,继续巡逻;
- 计分:玩家每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;
程序设计要求:
- 必须使用订阅与发布模式传消息
- 工厂模式生产巡逻兵
2、游戏预览
附上录制小视频的地址: https://www.bilibili.com/video/av23317658/
游戏截图:
3、设计过程
1、首先上unity3d的官网上的asset store上找一些能用的免费的资源
2、设计预制
利用找到的免费资源和一些unity自带的模型设计各种你需要的预制,这些都是游戏中可能会使用到的。我的预制有这几种:地图、玩家以及巡逻兵。
地图的话,可以按照自己的喜好来设计,设置墙体、障碍以及金币什么的都是可以的,按自己喜欢的来
然后我们需要在地图、玩家以及巡逻兵身上设计各种碰撞器,另外我们还需要在玩家和巡逻兵身上设置刚体组件
3、编写程序
这次作业老师要求我们使用订阅与发布模式来传消息,刚接触到这个概念的时候还是十分陌生的,那么究竟什么是订阅与发布模式呢?首先我们来思考这样一个问题,假设一个程序的后台有这样一种功能:
当产生警告的时候,程序需要执行若干种操作来进行响应。我们想象一下,如果产生警告的时候我们需要做好几十件事情的话,那么这个类是有多复杂。所以我们需要把报警信息和各种方法解耦合,这个时候就需要用到我们的订阅与发布模式了。
事件触发者称为消息发布者,即图中的P。事件接受称为消息订阅者,即图中的S。P与S之间通过S.P(即订阅器)连接。这样就实现了P与S的解耦。首先,P就把消息发送到指定的订阅器上,S如果想接收消息,就要向订阅器进行订阅,订阅成功后,S就可以接收来自S.P的消息了同理,S还可以向S.P进行退订操作,成功退订后,S就无法接收到来自指定S.P的消息了。这样就完美的解决了P与S之间的解耦。
4、UML图
AreaCollide.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AreaCollide : MonoBehaviour
{
public int sign = 0;
FirstSceneController sceneController;
private void Start()
{
sceneController = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
}
void OnTriggerEnter(Collider collider)
{
//标记玩家进入自己的区域
if (collider.gameObject.tag == "Player")
{
sceneController.wall_sign = sign;
}
}
}
FirstSceneController.csusing System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public interface ISceneController
{
//加载场景资源
void LoadResources();
}
public interface IUserAction
{
//移动玩家
void MovePlayer(float translationX, float translationZ);
//得到分数
int GetScore();
//得到水晶数量
bool GetGameover();
//重新开始
void Restart();
}
public interface ISSActionCallback
{
void SSActionEvent(SSAction source,int intParam = 0,GameObject objectParam = null);
}
public interface IGameStatusOp
{
void PlayerEscape();
void PlayerGameover();
}
public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController
{
public PropFactory patrol_factory; //巡逻者工厂
public ScoreRecorder recorder; //记录员
public PatrolActionManager action_manager; //运动管理器
public int wall_sign = -1; //当前玩家所处哪个格子
public GameObject player; //玩家
public Camera main_camera; //主相机
public float player_speed = 5; //玩家移动速度
public float rotate_speed = 135f; //玩家旋转速度
private List<GameObject> patrols; //场景中巡逻者列表
private bool game_over = false; //游戏结束
void Update()
{
for (int i = 0; i < patrols.Count; i++)
{
patrols[i].gameObject.GetComponent<PatrolData>().wall_sign = wall_sign;
}
}
void Start()
{
SSDirector director = SSDirector.GetInstance();
director.CurrentScenceController = this;
patrol_factory = Singleton<PropFactory>.Instance;
action_manager = gameObject.AddComponent<PatrolActionManager>() as PatrolActionManager;
LoadResources();
//main_camera.GetComponent<CameraFlow>().follow = player;
recorder = Singleton<ScoreRecorder>.Instance;
}
public void LoadResources()
{
Instantiate(Resources.Load<GameObject>("Prefabs/Plane"));
player = Instantiate(Resources.Load("Prefabs/hope"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
patrols = patrol_factory.GetPatrols();
//所有侦察兵移动
for (int i = 0; i < patrols.Count; i++)
{
action_manager.GoPatrol(patrols[i]);
}
}
//玩家移动
public void MovePlayer(float translationX, float translationZ)
{
if(!game_over)
{
if (translationX != 0 || translationZ != 0)
{
player.GetComponent<Animator>().SetBool("run", true);
}
else
{
player.GetComponent<Animator>().SetBool("run", false);
}
//移动和旋转
player.transform.Translate(0, 0, translationZ * player_speed * Time.deltaTime);
player.transform.Rotate(0, translationX * rotate_speed * Time.deltaTime, 0);
//防止碰撞带来的移动
player.GetComponent<Rigidbody>().freezeRotation = true;
//防止上天入地
if (player.transform.position.y != 0)
{
player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);
}
}
}
public int GetScore()
{
return recorder.GetScore();
}
public bool GetGameover()
{
return game_over;
}
public void Restart()
{
SceneManager.LoadScene("Scenes/mySence");
}
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;
patrol_factory.StopPatrol();
action_manager.DestroyAllAction();
}
}
GameEventManager.csusing System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameEventManager : MonoBehaviour
{
//分数变化
//delegate是委托类型的声明, 能够持有对某个方法的引用的类,需要匹配签名
public delegate void ScoreEvent();
public static event ScoreEvent ScoreChange;
//游戏结束变化
public delegate void GameoverEvent();
public static event GameoverEvent GameoverChange;
//玩家逃脱
public void PlayerEscape()
{
if (ScoreChange != null)
{
ScoreChange();
}
}
//玩家被捕
public void PlayerGameover()
{
if (GameoverChange != null)
{
GameoverChange();
}
}
}
PatrolCollide.csusing System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatrolCollide : MonoBehaviour
{
void OnTriggerEnter(Collider collider)
{
if (collider.gameObject.tag == "Player")
{
//玩家进入侦察兵追捕范围
this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = 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>().follow_player = false;
this.gameObject.transform.parent.GetComponent<PatrolData>().player = null;
}
}
}
PatrolData.csusing System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatrolData : MonoBehaviour
{
public int sign; //标志巡逻兵在哪一块区域
public bool follow_player = false; //是否跟随玩家
public int wall_sign = -1; //当前玩家所在区域标志
public GameObject player; //玩家游戏对象
public Vector3 start_position; //当前巡逻兵初始位置
}
PlayerCollide.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCollide : MonoBehaviour
{
void OnCollisionEnter(Collision other)
{
//当玩家与侦察兵相撞
if (other.gameObject.tag == "Player")
{
other.gameObject.GetComponent<Animator>().SetTrigger("death");
this.GetComponent<Animator>().SetTrigger("shoot");
Singleton<GameEventManager>.Instance.PlayerGameover();
}
}
}
PropFactory.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PropFactory : MonoBehaviour
{
private GameObject patrol = null; //巡逻兵
private List<GameObject> used = new List<GameObject>(); //正在被使用的巡逻兵
private Vector3[] vec = new Vector3[9]; //保存每个巡逻兵的初始位置
public FirstSceneController sceneControler; //场景控制器
public List<GameObject> GetPatrols()
{
int[] pos_x = { -6, 4, 13 };
int[] pos_z = { -4, 6, -13 };
int index = 0;
//生成不同的巡逻兵初始位置
for(int i=0;i < 3;i++)
{
for(int j=0;j < 3;j++)
{
vec[index] = new Vector3(pos_x[i], 0, pos_z[j]);
index++;
}
}
for(int i=0; i < 9; i++)
{
patrol = Instantiate(Resources.Load<GameObject>("Prefabs/Patrol"));
patrol.transform.position = vec[i];
patrol.GetComponent<PatrolData>().sign = i + 1;
patrol.GetComponent<PatrolData>().start_position = vec[i];
used.Add(patrol);
}
return used;
}
public void StopPatrol()
{
//切换所有侦查兵的动画
for (int i = 0; i < used.Count; i++)
{
used[i].gameObject.GetComponent<Animator>().SetBool("run", false);
}
}
}
SSAction.csusing System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSAction : ScriptableObject
{
public bool enable = true; //是否正在进行此动作
public bool destroy = false; //是否需要被销毁
public GameObject gameobject; //动作对象
public Transform transform; //动作对象的transform
public ISSActionCallback callback; //动作完成后的消息通知者
protected SSAction() { }
//子类可以使用下面这两个函数
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
public class GoPatrolAction : SSAction
{
private enum Dirction { EAST, NORTH, WEST, SOUTH };
private float pos_x, pos_z; //移动前的初始x和z方向坐标
private float move_length; //移动的长度
private float move_speed = 1.2f; //移动速度
private bool move_sign = true; //是否到达目的地
private Dirction dirction = Dirction.EAST; //移动的方向
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(4, 7);
return action;
}
public override void Update()
{
//防止碰撞发生后的旋转,这里的参数设置为true即为禁止旋转
gameobject.GetComponent<Rigidbody>().freezeRotation = true;
// 这里是防止因碰撞使得巡逻兵掉到地下或者飞上天了
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()
{
this.gameobject.GetComponent<Animator>().SetBool("run", true);
data = this.gameobject.GetComponent<PatrolData>();
}
void Gopatrol()
{
// 到达了一条边的尽头就要换个方向继续进行移动
if (move_sign)
{
//不需要转向则设定一个目的地,按照矩形移动
switch (dirction)
{
case Dirction.EAST:
pos_x -= move_length;
break;
case Dirction.NORTH:
pos_z += move_length;
break;
case Dirction.WEST:
pos_x += move_length;
break;
case Dirction.SOUTH:
pos_z -= move_length;
break;
}
move_sign = false;
}
// 巡逻兵旋转自身,老是朝着Vector3所在的位置
this.transform.LookAt(new Vector3(pos_x, 0, pos_z));
float distance = Vector3.Distance(transform.position, new Vector3(pos_x, 0, pos_z));
//当前位置与目的地距离浮点数的比较
//如果当前位置与目标位置相差大于0.9,则换一个方向继续巡逻
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.SOUTH)
{
dirction = Dirction.EAST;
}
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()
{
//防止碰撞后发生旋转
gameobject.GetComponent<Rigidbody> ().freezeRotation = true;
//防止碰撞后上天入地,老把对象固定在地上
if (transform.position.y != 0)
{
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
}
//每帧都刷新朝向目标的角度,永远对准这target
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);
// 旋转自身使得自身老是朝着目标对象target所在的位置
this.transform.LookAt(player.transform.position);
}
}
SSActionManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSActionManager : MonoBehaviour, ISSActionCallback
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); //将执行的动作的字典集合
private List<SSAction> waitingAdd = new List<SSAction>(); //等待去执行的动作列表
private List<int> waitingDelete = new List<int>(); //等待删除的动作的key
protected void Update()
{
//把等待去执行的动作列表放入字典集合里
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
//清空动作列表
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
{
waitingDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
//运动学运动更新
ac.Update();
}
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
waitingDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
{
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
waitingAdd.Add(action);
action.Start();
}
public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
{
if(intParam == 0)
{
//侦查兵跟随玩家
//获取靠近玩家的方法放在follow里
PatrolFollowAction follow = PatrolFollowAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().player);
this.RunAction(objectParam, follow, this);
}
else
{
//侦察兵按照初始位置开始继续巡逻
GoPatrolAction move = GoPatrolAction.GetSSAction(objectParam.gameObject.GetComponent<PatrolData>().start_position);
this.RunAction(objectParam, move, this);
//玩家逃脱
Singleton<GameEventManager>.Instance.PlayerEscape();
}
}
// 游戏结束后,摧毁所有动作,巡逻兵不再移动
public void DestroyAll()
{
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
ac.destroy = true;
}
}
}
// 这个类可以合并进入SSActionManager
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();
}
}
SSDirector.csusing System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSDirector : System.Object
{
private static SSDirector _instance; //导演类的实例
public ISceneController CurrentScenceController { get; set; }
public static SSDirector GetInstance()
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}
}
ScoreRecorder.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreRecorder : MonoBehaviour
{
public FirstSceneController sceneController;
public int score = 0; //分数
// Use this for initialization
void Start()
{
sceneController = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController;
sceneController.recorder = this;
}
public int GetScore()
{
return score;
}
public void AddScore()
{
score++;
}
}
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;
}
}
}
UserGUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour {
private IUserAction action;
private GUIStyle score_style = new GUIStyle();
private GUIStyle text_style = new GUIStyle();
private GUIStyle over_style = new GUIStyle();
public int show_time = 8; //展示提示的时间长度
void Start ()
{
action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
text_style.normal.textColor = new Color(0, 0, 0, 1);
text_style.fontSize = 16;
score_style.normal.textColor = new Color(1,0.92f,0.016f,1);
score_style.fontSize = 16;
over_style.fontSize = 25;
//展示提示
StartCoroutine(ShowTip());
}
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), "分数:", text_style);
GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString(), score_style);
if(action.GetGameover())
{
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 250, 100, 100), "游戏结束", over_style);
if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "重新开始"))
{
action.Restart();
return;
}
}
if(show_time > 0)
{
GUI.Label(new Rect(Screen.width / 2-80 ,10, 100, 100), "按WSAD或方向键移动", text_style);
}
}
public IEnumerator ShowTip()
{
while (show_time >= 0)
{
yield return new WaitForSeconds(1);
show_time--;
}
}
}