3D游戏编程与设计 HW 4.5 牧师与恶魔(动作分离版)

    科技2022-07-11  172

    3D游戏编程与设计 HW 4.5 牧师与恶魔(动作分离版)

    文章目录

    3D游戏编程与设计 HW 4.5 牧师与恶魔(动作分离版)1.作业要求2.游戏制作① 设计思路② 设计代码一、ActionController.cs1)动作基类 SSAction2)基础动作 移动子类 SSMoveToAction (牧师与恶魔游戏只需设计直线运动)3)组合动作 SequenceAction4)动作管理 ActionManager 二、FirstSceneController.cs三、Check.cs(裁判类) ③ 修改已有代码一、Moveable.cs二、Interface.cs1)增加 动作事件回调接口 ISSActionCallback 三、Boat.cs 和 Character.cs四、FirstController.cs五、GameController 3.游戏截图

    完整游戏过程可见以下视频: https://www.bilibili.com/video/BV1gv411y7xz/

    完整代码可见以下仓库: https://gitee.com/beilineili/game3-d

    1.作业要求

    2.游戏制作

    ① 设计思路

    在上一版的牧师与恶魔中,场记管的事情太多,不仅要处理用户交互事件,还要进行游戏对象加载、游戏规则实现、运动实现等工作,显得非常臃肿。对这个问题,一个最直观的想法就是让更多的人(角色)来管理不同方面的工作。显然,这就是面向对象基于职责的思考。例如:就和踢足球一样,自己踢5人场,一个裁判就够了,但如果是国际比赛,就需要主裁判、边裁判、电子裁判等角色通过消息协同来完成更为复杂的工作。 在之前的牧师与恶魔的游戏制作中,我们使用FirstController来控制游戏中人物的动作。本次作业,我们对上一版的牧师与恶魔更新,将动作从FirstController中分离出来。为了用一组简单的动作组合成复杂的动作,我们采用 cocos2d 的方案,建立与 CCAtion 类似的类。先上设计图:

    设计思路如下:

    通过门面模式(控制器模式)输出组合好的几个动作,供原来程序调用。通过组合模式实现将基本动作组合接口回调(函数回调)实现管理者与被管理者解耦通过模板方法,让使用者减少对动作管理过程细节的要求

    ② 设计代码

    一、ActionController.cs

    1)动作基类 SSAction
    需要用户实现方法 Start 和 Update ,分别用于动作初始化和实现动作逻辑 // 所有动作的基类 public class SSAction : ScriptableObject { //不需要绑定GameObject对象的可编程基类 public bool enable = true; //是否进行 public bool destroy = false; //是否删除 //需要进行运动的游戏对象 public GameObject GameObject { get; set; } public Transform Transform { get; set; } //动作执行完后要通知的对象 public ISSActionCallback Callback { get; set; } // 申明虚方法,通过重写实现多态,由继承者来明确行为 public virtual void Start() { throw new System.NotImplementedException(); } public virtual void Update() { throw new System.NotImplementedException(); } }
    2)基础动作 移动子类 SSMoveToAction (牧师与恶魔游戏只需设计直线运动)
    //实现移动的基本动作 public class SSMoveToAction : SSAction { //目的地 public Vector3 target; //速度 public float speed; private SSMoveToAction() { } public static SSMoveToAction GetSSMoveToAction(Vector3 goal, float speed) { SSMoveToAction action = CreateInstance<SSMoveToAction>(); action.target = goal; action.speed = speed; return action; } //声明重写父类虚函数,不需要任何初始化操作 public override void Start() { } //游戏对象以速度speed向target直线运动 public override void Update() { Transform.position = Vector3.MoveTowards(Transform.position, target, speed * Time.deltaTime); if (Transform.position == target) { destroy = true; //动作完成时通过callback告诉动作管理者 Callback.ActionDone(this); } } }
    3)组合动作 SequenceAction
    牧师与恶魔里的动作,即上下船/岸动作,可以抽象为一个直角折线运动。这时,就需要两步直线运动,所以要实现组合动作。组合动作类实现一个动作组合序列,顺序播放动作 public class SequenceAction: SSAction, ISSActionCallback { //存储多个顺序执行的动作 public List<SSAction> sequence; //动作执行次数,为负数则要重复执行 public int repeat = -1; //当前执行的动作 public int currentActionIndex = 0; //创建一个动作顺序执行序列 public static SequenceAction GetSequenceAction(int repeat, int currentActionIndex, List<SSAction> sequence) { SequenceAction action = CreateInstance<SequenceAction>(); action.sequence = sequence; action.repeat = repeat; action.currentActionIndex = currentActionIndex; return action; } //执行当前动作 public override void Update() { if (sequence.Count == 0) return; if (currentActionIndex < sequence.Count) { sequence[currentActionIndex].Update(); } } public void ActionDone(SSAction source) { source.destroy = false; currentActionIndex++; //下一个动作 if (currentActionIndex >= sequence.Count) { //如果已经完成一次循环 currentActionIndex = 0; if (repeat > 0) repeat--; if (repeat == 0) { //如果已经完成,通知动作管理者 destroy = true; Callback.ActionDone(this); } } } public override void Start() { //为每个动作注入当前游戏对象,将自己作为动作事件的接收者 foreach(SSAction action in sequence) { action.GameObject = GameObject; action.Transform = Transform; action.Callback = this; action.Start(); } } //如果被注销,应该释放自己管理的动作 void OnDestroy() { foreach(SSAction action in sequence) { DestroyObject(action); } } }
    4)动作管理 ActionManager
    管理动作的执行,它来调度调配所有的动作的执行,决定游戏对象做某个动作或是一连串动作 //动作对象管理器的基类 //继承ISSActionCallback获取完成动作时的反馈信息 public class ActionManager : MonoBehaviour, ISSActionCallback { //动作字典 private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); //等待执行的动作列表 private List<SSAction> waitingAdd = new List<SSAction>(); //等待删除动作的key的列表 private List<int> waitingDelete = new List<int>(); protected void Update() { foreach(SSAction action in waitingAdd) { actions[action.GetInstanceID()] = action; } waitingAdd.Clear(); //执行每一个动作 foreach(KeyValuePair<int, SSAction> kv in actions) { SSAction action = kv.Value; if (action.destroy) { waitingDelete.Add(action.GetInstanceID()); } else if (action.enable) { action.Update(); } } //删除已完成动作 foreach(int key in waitingDelete) { SSAction action = actions[key]; actions.Remove(key); DestroyObject(action); } waitingDelete.Clear(); } //添加动作 public void AddAction(GameObject gameObject, SSAction action, ISSActionCallback callback) { action.GameObject = gameObject; action.Transform = gameObject.transform; action.Callback = callback; waitingAdd.Add(action); action.Start(); } public void ActionDone(SSAction source) { } }

    二、FirstSceneController.cs

    增加一个场记动作管理者类,避免场记代码冗余,它含有控制船只和人物运动的方法移动船:一个动作,从当前位置以speed的速率移动到destination移动角色:两个动作,从当前位置以speed的速率移动到middlePos,再以speed的速率移动到destination public class FirstSceneActionManager : ActionManager { public void MoveBoat(BoatController boatController) { SSMoveToAction action = SSMoveToAction.GetSSMoveToAction(boatController.GetDestination(), boatController.boat.movingSpeed); AddAction(boatController.boat._Boat, action, this); } public void MoveCharacter(MyNamespace.CharacterController characterCtrl, Vector3 destination) { Vector3 currentPos = characterCtrl.character.Role.transform.position; Vector3 middlePos = currentPos; if (destination.y > currentPos.y) middlePos.y = destination.y; else { middlePos.x = destination.x; } //两个动作,两段移动 SSAction action1 = SSMoveToAction.GetSSMoveToAction(middlePos, characterCtrl.character.movingSpeed); SSAction action2 = SSMoveToAction.GetSSMoveToAction(destination, characterCtrl.character.movingSpeed); //动作队列完成上船或上岸动作 SSAction seqAction = SequenceAction.GetSequenceAction(1, 0, new List<SSAction> { action1, action2 }); AddAction(characterCtrl.character.Role, seqAction, this); } }

    三、Check.cs(裁判类)

    增加了一个裁判类,用来判定游戏是否结束。一旦游戏结束,UserGUI即会得到信息来打印出胜或败的游戏结果将之前在 FirstController.cs 里的 Check 函数删去,在 FirstController 的 Awake 函数对裁判类实例进行初始化。 public class Check : MonoBehaviour { public FirstController sceneController; protected void Start() { sceneController = (FirstController)Director.GetInstance().CurrentSecnController; sceneController.gameStatusManager = this; } public int CheckGame() { //0-游戏继续,1-失败,2-成功 int rightPriests = (sceneController.rightCoastCtrl.GetCharacterNum())[0]; int rightDevils = (sceneController.rightCoastCtrl.GetCharacterNum())[1]; int leftPriests = (sceneController.leftCoastCtrl.GetCharacterNum())[0]; int leftDevils = (sceneController.leftCoastCtrl.GetCharacterNum())[1]; //所有角色都过河了 if (leftPriests + leftDevils == 6) return 2; if (sceneController.boatCtrl.boat.Location == Location.right) { rightPriests += sceneController.boatCtrl.GetCharacterNum()[0]; rightDevils += sceneController.boatCtrl.GetCharacterNum()[1]; } else { leftPriests += sceneController.boatCtrl.GetCharacterNum()[0]; leftDevils += sceneController.boatCtrl.GetCharacterNum()[1]; } // Lose,有一边恶魔数量多过牧师 if ((rightPriests < rightDevils && rightPriests > 0) || (leftPriests < leftDevils && leftPriests > 0)) { return 1; } return 0; //游戏还没结束 } }

    ③ 修改已有代码

    一、Moveable.cs

    将Moveable.cs删除,有动作管理类就不再需要移动控制器了。

    二、Interface.cs

    1)增加 动作事件回调接口 ISSActionCallback
    ActionDone 用于通知更高级的对象动作已执行完毕 public interface ISSActionCallback { void ActionDone(SSAction source); }

    三、Boat.cs 和 Character.cs

    将 Model 里的 Boat 和 Character 中与运动相关的方法删除,并添加speed public class Boat { public readonly Vector3 departure; public readonly Vector3 destination; public readonly Vector3[] departures; public readonly Vector3[] destinations; public readonly float movingSpeed = 20; public CharacterController[] passenger = new CharacterController[2]; public GameObject boat_ { get; set; } public Location Location { get; set; } public Boat() { // 船的起点和终点位置的坐标 departure = new Vector3(5, 1, 0); destination = new Vector3(-5, 1, 0); Location = Location.right; // 船上空位置的坐标 departures = new Vector3[] {new Vector3(4.5f, 1.5f, 0), new Vector3(5.5f, 1.5f, 0) }; destinations = new Vector3[] {new Vector3(-5.5f, 1.5f, 0),new Vector3(-4.5f, 1.5f, 0) }; // 用预制初始化船 boat_ = Object.Instantiate(Resources.Load("Prefab/Boat", typeof(GameObject)),departure, Quaternion.identity, null) as GameObject; boat_.name = "boat"; boat_.AddComponent(typeof(UserGUI)); } }

    四、FirstController.cs

    加入动作管理类 ActionManager private FirstSceneActionManager actionManager; void Start() { actionManager = GetComponent<FirstSceneActionManager>(); } 修改MoveBoat 和 CharacterClicked,使用动作管理器 ActionManager 来实现 public void MoveBoat() { if (boatCtrl.IsEmpty()) return; //修改船的移动动作和过程处理 actionManager.MoveBoat(boatCtrl); boatCtrl.SetPos(); UserGUI.status = CheckGameOver(); }

    五、GameController

    删除和移动动作相关的代码,改成直接设置 pos

    3.游戏截图

    从 Asset Store 中下载并添加了天空盒,以及流动的水元素 资源:流动的水游戏开始界面

    游戏结束界面

    Processed: 0.012, SQL: 8