巡逻兵
游戏设计要求
- 创建一个地图和若干巡逻兵
- 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次确定下一个目标位置,用自己当前位置为原点计算
- 巡逻兵碰撞到障碍物如树,则会自动选下一个点为目标
- 巡逻兵在设定范围内感知到玩家,会自动追击玩家
- 失去玩家目标后,继续巡逻
- 计分:每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束
程序设计要求
- 必须使用订阅与发布模式传消息、工厂模式生产巡逻兵
游戏演示
演示了玩家左键劈砍,右键范围攻击,space跳跃,
巡逻兵触发追踪时候扑到动作,以及碰到玩家挥砍动作
还有玩家攻击时候碰到巡逻兵,巡逻兵会直接消失
准备工作
首先需要下载模型,推荐一个网站还算比较好的免费网站,其中我的作业模型就是从里面下载的,场景模型本来也下载了,但是建立一个场景实在太费时间,所以就直接借用了之前大佬的场景。
人物的动作
首先打开下载好的人物模型,点开人物旁边的按钮可以看到很多部件以及动作,可以点开玩一下。如果决定用这个人物模型,就放置到Resources中的Prefabs文件夹里。放到
在Prefabs中的人物就没有那些动作了,这时候就需要自己设计AnimatorController。
新建一个文件夹AnimatorController,里面用来控制动作,在里面Create,Animator Controller,就比如新建了一个skeleton。
双击skeleton打开,我已经建立好了,稍微有点复杂,首先在左边的Parameters新建变量,我建立了五个变量,bool型的run,bool型的death,以及Trigger型的attack1,attack2,jump。Trigger类型给的解释是,先设置true,然后自动设置为false,但似乎运行的时候有点奇怪,这个是作为skeleton的动作设计,鼠标左键,从右向左劈砍,鼠标右键,旋转劈砍,空格键跳跃。所以从AnyState开始有三个动作,都是Trigger触发,执行完毕后进入到idle状态,在idle状态下,如果run为true就run,death为true就death,其他同理,这时候不能连到Exit,原因是连到Exit就会从入口再次进入,就会发现死了一遍又又一遍。
状态绑定动作,就是Motion那里选择动作,一定要记住将动作的Animation Type改成Generic,才可以从Motion里面选择,并且会生成结尾为Avatar的东西,这个在与物体绑定的时候有用。
下面是转换时候的设置,至于下面为什么有两个run,这个原因是有时候动作切换很不自然,或者延迟太高,多个组合的话会比较好调整,具体设计方法,把loop打勾,然后apply一下就好。这里还有个小技巧,就是两个状态之间的连线怎么删掉的问题,在Transition下面的点减号就行。
然后在人物增加新组件,Animator,在Controller和Avatar,选择刚刚做好和生成的文件。这样的话一个具有动作的人物就设置好了。
碰撞检测
这个需要设置碰撞盒,这个是我player的碰撞盒,差不多是药丸形状的,注意IsTrigger不打勾,说明碰到了就是碰到了,而不是碰到了触发一下。
巡逻兵也需要设置碰撞盒,但是比玩家复杂一点,因为巡逻兵需要检测玩家的范围,所以还要给巡逻兵建立一个box Collider,用户触发追踪事件。
人物效果看起来是这样的
当然场景也是需要设置9个碰撞盒的,并且勾上IsTrigger,用来判断人物在哪一块区域。
到此为止准备工作算是做完了。
代码实现
这次我整理和参考了以前的PPT和大佬的代码,严格按照所将的设计模式组织代码。
动作代码的实现
首先动作代码可以复用的,一点都不用改的是,SSActionManager.cs
,SSAction.cs
,ISSActionInterface.cs
其中ISSActionInterface.cs
是ISSActionCallback
接口。需要改动的就是CCSequenceAction.cs
和CCMoveToAction.cs
以及CCActionManager.cs
,似乎CC开头的都是自己定义的。这个图还是牧师与魔鬼那章PPT的图,其中CCSequenceAction.cs
是定义动作序列,因为坐船是,人物先移动船的上方,在移到船上面,相当于两个动作,所以用动作序列,而船的移动只有左右,所以用CCMoveToAction.cs
实现单个动作。本次作业设计的动作也就两个,第一个是PatrolAction.cs
实现巡逻动作,一个是PatrolFollowAction.cs
实现跟踪动作,然后这个两个动作的管理在PatrolActionManager.cs
中,因为PatrolActionManager.cs
实现了ISSActionCallback
的接口,所有动作的都是靠这个接口来完成的,所以具体的动作管理,也是靠这个接口。
PatrolAction.cs 巡逻动作实现
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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;
//移动的距离随机,在4-7之间
action.move_length = Random.Range(4, 7);
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();
//如果有跟随的玩家,并且玩家在自己所在的区域,就会调用ISSActionCallback接口中的函数
//所以说动作的管理是靠ISSActionCallback实现的,同样在跟踪的时候的动作也会有切换到巡逻状态的方法。
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;
}
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.SOUTH)
{
dirction = Dirction.EAST;
}
move_sign = true;
}
}
}
这段代码可以和CCMoveToActon
的代码比较一下,因为两个是同一个框架的不同实现,可以发现除了实现细节不一样,其他都一样,包括函数类型都一样。可以看到这部分的代码,只要实现特定的动作,所以以后在写新动作处理的时候,可以直接在这部分修改,或者另开一个文件继承SSAction
。
PatrolFollowAction.cs 追踪动作实现
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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();
//如果没有跟随的玩家,或者玩家不在自己所在的区域,就会调用ISSActionCallback接口中的函数,切换到巡逻状态
//所以说动作的管理是靠ISSActionCallback实现的,同样在巡逻的时候的动作也会有切换到追踪状态的方法。
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);
}
}
这部分代码和之前的代码框架一样,不做过多的解释。
PatrolActionManager.cs 具体的动作管理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatrolActionManager : SSActionManager, ISSActionCallback
{
//这个函数是游戏初始化的时候用到,最重要的实现还是在SSActionEvent函数中实现,也就是ISSActionCallback接口的函数
public void GoPatrol(GameObject patrol)
{
GoPatrolAction go_patrol = GoPatrolAction.GetSSAction(patrol.transform.position);
this.RunAction(patrol, go_patrol, this);
}
//停止所有动作
public void DestroyAllAction()
{
DestroyAll();
}
//动作管理最终要的部分,实现各个动作的切换,以及条件判断。
public void SSActionEvent(SSAction source, int intParam = 0, GameObject objectParam = null)
{
if (intParam == 0)
{
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直接生成实例,调用PlayerEscape就相当于发出通知,那么在OnEnable函数就会触发加分的函数
Singleton<GameEventManager>.Instance.PlayerEscape();
}
}
}
这段代码再和CCActionManager比较下,发现最重要的还是SSActionEvent函数的实现,start和Update,可以换成其他函数,直接被调用,而不是默认调用,大同小异。
门面模式
对于单个场景的游戏来说,完全不用改动的代码是SSDirector.cs
和ISenceInterface.cs
,需要改动的是FirstSceneController.cs
,UserGUI.cs
和IUserInterface.cs
,其中FirstSceneController.cs
主要处理场景的资载入,并且实现IUserAction
接口,由于是但场景,对ISceneController
接口实现的并不多。UserGuI
主要实现界面的呈现,用户交互,其中的接口是在FirstController
中实现,所以通过UserGuI
可以控制游戏人物,以及游戏的重新开始之类的。
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();
private int show_time = 8;
void Start ()
{
action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
text_style.normal.textColor = new Color(205, 179, 128, 1);
text_style.fontSize = 16;
score_style.normal.textColor = new Color(3,101,100,1);
score_style.fontSize = 16;
over_style.fontSize = 25;
//好的方法实现一个时间差,StartCoroutine函数和yield return成对出现。
StartCoroutine(ShowTip());
}
void Update()
{
//人物的动作和移动,得到水平两个方向的偏移量来控制移动,通过按键控制动作。
float translationX = Input.GetAxis("Horizontal");
float translationZ = Input.GetAxis("Vertical");
action.MovePlayer(translationX, translationZ);
action.Attack();
}
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() && action.GetScore() != 10)
{
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;
}
}
else if(action.GetScore() == 10)
{
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 - 87 ,10, 100, 100), "按WSAD或方向键移动", text_style);
GUI.Label(new Rect(Screen.width / 2 - 87, 30, 100, 100), "成功躲避巡逻兵追捕加1分", text_style);
GUI.Label(new Rect(Screen.width / 2 - 87, 50, 100, 100), "获得十分就胜利", text_style);
}
}
public IEnumerator ShowTip()
{
while (show_time >= 0)
{
yield return new WaitForSeconds(1);
show_time--;
}
}
}
结构类似于PPT的结构,这部分大部分都是界面要素,接口的使用很简单
FirstSceneController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController
{
public PatorlFactory _PatorlFactory; //巡逻者工厂
public ScoreRecorder _ScoreRecorder; //记录员
public PatrolActionManager _PatrolActionManager; //运动管理器
public int wall_sign = -1; //当前玩家所处哪个格子
public GameObject player; //玩家
public Camera main_camera; //主相机
private List<GameObject> patrols; //场景中巡逻者列表
private float player_speed = 5; //玩家移动速度
private float rotate_speed = 135f; //玩家旋转速度
private bool game_over = false; //游戏结束
void Update()
{
for (int i = 0; i < patrols.Count; i++)
{
patrols[i].gameObject.GetComponent<PatrolData>().wall_sign = wall_sign;
}
//20分结束游戏
if(_ScoreRecorder.score == 20)
{
Gameover();
}
}
void Start()
{
//这一块注意一下,关于实例化,和实例化的查找
//首先director是单例模式,所以用GetInstance方法,即使没有实例也会创建实例,有实例就确保只有一个实例
//那么_PatorlFactory和_PatrolActionManager是怎么回事
//首先看到代码可以这样写,用Singleton,这个是直接查找有没有这个类,没有就报错了
//所以使用这个有个前提条件,就是必须已经存在了,所以在游戏开始之前必须把代码绑定到对应的物体上
//如果不采用这种方法怎么办,可以手动添加,用AddComponent就相当于把手动添加的部分,用代码实现了,就是注释掉的部分
SSDirector director = SSDirector.GetInstance();
director.CurrentScenceController = this;
_PatorlFactory = Singleton<PatorlFactory>.Instance;
_PatrolActionManager = Singleton<PatrolActionManager>.Instance;
//_PatrolActionManager = gameObject.AddComponent<PatrolActionManager>() as PatrolActionManager;
//ISceneController接口中函数的实现
LoadResources();
//设置摄像机跟随的对象,也就是玩家,如果不用工厂模式的话,直接在场景中生成玩家,然后把玩家拖到摄像机的Targer就可以了
//这一块也要注意,main_camera的位置必须在LoadResources之后,因为main_camera跟随的对象是player,如果player没有实例化就无法运行
//所以如果一开始在场景中就有player的话,下面的代码位置就可以随便放。
main_camera.GetComponent<CameraFlow>().follow = player;
_ScoreRecorder = Singleton<ScoreRecorder>.Instance;
}
public void LoadResources()
{
player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
//导入资源的时候为了巡逻兵有动作
patrols = _PatorlFactory.GetPatrols();
for (int i = 0; i < patrols.Count; i++)
{
_PatrolActionManager.GoPatrol(patrols[i]);
}
}
public void Attack()
{
if (!game_over)
{
//Fire1对应鼠标左键
if(Input.GetButtonDown("Fire1"))
{
player.GetComponent<Animator>().SetTrigger("attack1");
}
//Fire2对应鼠标右键
if (Input.GetButtonDown("Fire2"))
{
player.GetComponent<Animator>().SetTrigger("attack2");
}
//Jump对应空格键space
if (Input.GetButtonDown("Jump"))
{
player.GetComponent<Animator>().SetTrigger("jump");
}
}
}
public void MovePlayer(float translationX, float translationZ)
{
if(!game_over)
{
//移动的时候播放run动画,否则停止run动画
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);
//以防以外发生
if (player.transform.localEulerAngles.x != 0 || player.transform.localEulerAngles.z != 0)
{
player.transform.localEulerAngles = new Vector3(0, player.transform.localEulerAngles.y, 0);
}
if (player.transform.position.y != 0)
{
player.transform.position = new Vector3(player.transform.position.x, 0, player.transform.position.z);
}
}
}
public int GetScore()
{
return _ScoreRecorder.score;
}
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()
{
_ScoreRecorder.score++;
}
void Gameover()
{
game_over = true;
_PatorlFactory.StopPatrol();
_PatrolActionManager.DestroyAllAction();
}
}
整体框架类似于PPT中的,另外实现接口类的函数就可以了
工厂模式
工厂模式相对来说比较简单,一般来说一个类型有一个工厂,由于本例是一个玩家角色,所以直接把玩家和巡逻兵放在一起生产了。其中只要写Factory就好了。
其中可以发现DiskFactory上面有Singleton,是用来找DiskFactory用的
PatrolFactory.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PatorlFactory : MonoBehaviour {
//工厂模式就负责生产,其他的不负责,所以可以看到这个类只和角色有关系和其他所有代码都没有关系
private GameObject player = null; //玩家
private GameObject patrol = null; //巡逻兵
private List<GameObject> patrolList = new List<GameObject>(); //正在被使用的巡逻兵
private Vector3[] vec = new Vector3[9]; //保存每个巡逻兵的初始位置
public GameObject LoadPlayer()
{
player = Instantiate(Resources.Load("Prefabs/Player"), new Vector3(0, 9, 0), Quaternion.identity) as GameObject;
return player;
}
public List<GameObject> LoadPatrol()
{
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/Patrol1"));
patrol.transform.position = vec[i];
patrol.GetComponent<PatrolData>().sign = i + 1;
patrol.GetComponent<PatrolData>().start_position = vec[i];
patrolList.Add(patrol);
}
return patrolList;
}
//游戏结束的时候会暂停所有动作
public void StopPatrol()
{
for (int i = 0; i < patrolList.Count; i++)
{
patrolList[i].gameObject.GetComponent<Animator>().SetBool("run", false);
}
}
}
发布与订阅模式(观察者模式)
借用一下大佬的图,用观察者模式写代码会觉得很清爽,首先CystalCollide和PlayerCollide是直接挂载在相对应的物体上的,可以事先写好,然后用里面的函数触发GameEventManager。
PlayerInDetection.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//玩家进入巡逻兵的追踪范围
public class PlayerInDetection : MonoBehaviour
{
//玩家进入巡逻兵的追踪范围
void OnTriggerEnter(Collider collider)
{
//加上判断Player的原因是,人物会与地板碰撞
if (collider.gameObject.tag == "Player")
{
this.gameObject.transform.parent.GetComponent<PatrolData>().follow_player = true;
this.gameObject.transform.parent.GetComponent<PatrolData>().player = collider.gameObject;
//触发巡逻兵向前扑的动作
this.gameObject.transform.parent.GetComponent<Animator>().SetTrigger("shock");
}
}
//玩家离开巡逻兵的追踪范围
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;
}
}
}
PlayerCollideDetection.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//玩家与巡逻兵碰撞
public class PlayerCollideDetection : MonoBehaviour {
//当玩家与巡逻兵碰撞
void OnCollisionEnter(Collision other)
{
//在这里皮了一下,因为一开始是想做攻击判定的,但是剑和人的模型是一起的
//没法单独给剑加一个碰撞盒,所以只好放弃
//于是想攻击的时候直接让巡逻兵的碰撞盒半径为0,然后播放死亡动画也可以达到目的
//然后发现会鬼畜,就注释掉了,现在只要在攻击的时候碰到巡逻兵,巡逻兵会直接消失
//一般情况下的,玩家会自己挂掉,并且播放死亡动画,巡逻兵也会砍一刀然后死掉。
if (other.gameObject.tag == "Player" && other.gameObject.GetComponent<Animator>().GetBool("attack1"))
{
//this.gameObject.GetComponent<CapsuleCollider>().radius = 0;
this.gameObject.SetActive(false);
return;
}
else if (other.gameObject.tag == "Player")
{
other.gameObject.GetComponent<Animator>().SetBool("death",true);
this.GetComponent<Animator>().SetTrigger("shoot");
Singleton<GameEventManager>.Instance.PlayerGameover();
}
}
}
GameEventManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameEventManager : MonoBehaviour
{
//玩家躲开追踪时候触发
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();
}
}
}
这样所有的代码都已经说完了。
总结
所有C#文件,一共20个文件。
其中可以挂载在预制物体上的有5个。
AreaDetection.cs
挂载在场景的9个方格中,用于检测玩家是否进入该方格CameraFlow.cs
挂载在摄像机上,用于实现第三人称视角PatrolData.cs
挂载在巡逻兵上,用于表示巡逻兵的数据结构PlayerInDetection.cs
挂载在巡逻兵上,用于检测玩家是否进入追踪范围PlayerCollideDetection.cs
挂载在巡逻兵上,用于检测玩家是否与巡逻兵碰撞
其中动作设计的部分有6个
SSDirector.cs
完全不用改动SSActionManager.cs
完全不用改动PatrolAction.cs
PatrolActionManager.cs
PatrolFollowAction.cs
ISSActionInterface.cs
完全不用改动
其中门面模式的部分
FirstSceneController.cs
ISenceInterface.cs
单场景完全不用改动IUserInterface.cs
UserGUI.cs
SSDirector.cs
完全不用改动
工厂模式的部分
PatorlFactory.cs
Singleton.cs
其实也不算工厂特有,都可以用,完全不用改动观察者模式
PlayerCollideDetection.cs
PlayerInDetection.cs
GameEventManager.cs