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

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

完整游戏过程可见以下视频:
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 中下载并添加了天空盒,以及流动的水元素
    资源:流动的水
  • 游戏开始界面

在这里插入图片描述

  • 游戏结束界面

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值