源码传送门
视频展示传送门,展示效果与第三次作业相同
运行说明:将Controllor.cs挂载Main Camera上,然后点击运行即可
1. 动作分离
目的:将物体的动作与空间属性分开来,从而降低耦合,易于开发者维护。当动作很多或是需要做同样动作的游戏对象很多的时候,使用动作管理器可以让动作很容易管理,也提高了代码复用性。
实现方案:新增一个动作管理器
- 动作管理器就是一个对象,管理整个场景中所有的动作
- 一个SceneController(场景管理器)只配备一个动作管理器对象
- 不管是游戏角色的移动还是船的移动,都归动作管理器负责
- 动作管理器可以添加动作(添加的时候要指定动作所作用的GameObject),监测已经完成的动作并清除
具体实现如下
- SSAction
SSAction是所有动作的基类。SSAction继承了ScriptableObject代表SSAction不需要绑定GameObject对象,且受Unity引擎场景管理。
public class SSAction : ScriptableObject // 动作-所有动作的父类
{
public bool enable = true; // 是否正在进行此动作
public bool destroy = false; // 是否需要被销毁
public GameObject gameobject; // 动作对象
public Transform transform; // 动作对象的transform
public ISSActionCallback callback; // 回调函数
protected SSAction() { } // 保证SSAction不会被new
// 子类可以使用这两个函数
public virtual void Start() {
throw new System.NotImplementedException();
}
public virtual void Update() {
throw new System.NotImplementedException();
}
}
- SSMoveToAction
以speed的速度向target目的地移动。
public class SSMoveToAction : SSAction // 移动
{
public Vector3 target; // 移动到的目的地
public float speed; // 移动的速度
private SSMoveToAction() { }
public static SSMoveToAction GetSSAction(Vector3 target, float speed) {
// 让unity自己创建一个MoveToAction实例,并自己回收
SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();
action.target = target;
action.speed = speed;
return action;
}
public override void Update() {
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
if (this.transform.position == target) {
this.destroy = true;
this.callback.SSActionEvent(this); //告诉动作管理或动作组合这个动作已完成
}
}
public override void Start() {
// 移动动作建立时候不做任何事情
}
}
- SequenceAction
继承了ISSActionCallback,船的移动用一个SSMoveToAction完成,而角色的移动需要两个SSMoveToAction动作组合。所以实现该组合动作。
public class SequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence; // 动作的列表
public int repeat = -1; // -1就是无限循环做组合中的动作
public int start = 0; // 当前做的动作的索引
public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence) {
// 让unity自己创建一个SequenceAction实例
SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
action.repeat = repeat;
action.sequence = sequence;
action.start = start;
return action;
}
public override void Update() {
if (sequence.Count == 0)
return;
if (start < sequence.Count)
sequence[start].Update(); // 一个组合中的一个动作执行完后会调用接口,所以这里看似没有start++实则是在回调接口函数中实现
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null) {
// 先保留这个动作,如果是无限循环动作组合之后还需要使用
source.destroy = false;
this.start++;
if (this.start >= sequence.Count) {
this.start = 0;
if (repeat > 0)
repeat--;
if (repeat == 0) {
this.destroy = true; // 整个组合动作就删除
this.callback.SSActionEvent(this); // 告诉组合动作的管理对象组合做完了
}
}
}
public override void Start() {
foreach (SSAction action in sequence) {
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this; // 组合动作的每个小的动作的回调是这个组合动作
action.Start();
}
}
void OnDestroy() {
// 如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放
}
}
- ISSActionCallback
动作和动作管理者的回调接口,动作管理者继承这个接口,并且实现接口的方法。当动作完成的时候,动作会调用这个接口,发送消息告诉动作管理者对象,这个动作已做完,然后管理者会对下一个动作进行处理。
public interface ISSActionCallback
{
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null);
}
- SSActionManager
动作管理基类,管理SequenceAction和SSAction,可以给它们传递游戏对象,让游戏对象做动作,控制动作的切换。
public class SSActionManager : MonoBehaviour, ISSActionCallback // action管理器
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); // 将执行的动作的字典集合,int为key,SSAction为value
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; // 获取动作实例的ID作为key
}
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);
Destroy(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, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null) {
// 牧师与魔鬼的游戏对象移动完成后就没有下一个要做的动作了,所以回调函数为空
}
}
- MySceneActionManager
是本游戏的动作管理器,设置当前场景控制器的动作管理者为MySceneActionManager,这样场景控制器就可以调用动作管理器的方法实现不同游戏对象(船和角色)的移动。
public class MySceneActionManager : SSActionManager // 本游戏管理器
{
private SSMoveToAction moveBoatToEndOrStart; // 移动船到结束岸,移动船到开始岸
private SequenceAction moveRoleToLandorBoat; // 移动角色到陆地,移动角色到船上
public Controllor sceneController;
protected void Start() {
sceneController = (Controllor)SSDirector.GetInstance().CurrentScenceController;
sceneController.actionManager = this;
}
public void moveBoat(GameObject boat, Vector3 target, float speed) {
moveBoatToEndOrStart = SSMoveToAction.GetSSAction(target, speed);
this.RunAction(boat, moveBoatToEndOrStart, this);
}
public void moveRole(GameObject role, Vector3 middle_pos, Vector3 end_pos, float speed) {
SSAction action1 = SSMoveToAction.GetSSAction(middle_pos, speed);
SSAction action2 = SSMoveToAction.GetSSAction(end_pos, speed);
moveRoleToLandorBoat = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });
this.RunAction(role, moveRoleToLandorBoat, this);
}
}
2. 设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束
实现思路:将
Controllor.cs中的Check函数改为Model.cs中的裁判类,并在Controllor中实例化即可(注意judger的实例化放在land和boat之后),实现如下:
// Model.cs
public class Judger
{
LandModel start_land;
LandModel end_land;
BoatModel boat;
public Judger(LandModel bl, LandModel el, BoatModel b) {
start_land = bl;
end_land = el;
boat = b;
}
public int Check() {
int start_priest = (start_land.GetRoleNum())[0];
int start_devil = (start_land.GetRoleNum())[1];
int end_priest = (end_land.GetRoleNum())[0];
int end_devil = (end_land.GetRoleNum())[1];
if (end_priest + end_devil == 6) // 获胜
return 3;
int[] boat_role_num = boat.GetRoleNum();
if (boat.GetBoatSign() == 1) { // 在开始岸和船上的角色
start_priest += boat_role_num[0];
start_devil += boat_role_num[1];
} else { // 在结束岸和船上的角色
end_priest += boat_role_num[0];
end_devil += boat_role_num[1];
}
if ((start_priest > 0 && start_priest < start_devil) || (end_priest > 0 && end_priest < end_devil)) { //失败
return 2;
}
return 1; //未完成
}
}
注意
Controllor继承的IUserAction必须实例化一个Check函数,否则报错如下
原Check函数已被移走,所以添加一个不做处理的Check函数即可
public int Check() {
return 1;
}
参考博客
师兄博客
本文介绍了Unity游戏开发中的动作分离原则,通过创建动作管理器来降低游戏对象动作与空间属性的耦合,提高代码复用性和维护性。此外,还设计了一个裁判类用于在游戏达到结束条件时通知场景控制器结束游戏。


277

被折叠的 条评论
为什么被折叠?



