魔鬼与牧师(动作分离版本)

目录

前言

成果展示与获取

动作分离

动作分离设计

设计思路

动作设计

ActionController

回调函数接口

动作基类

单个动作

组合动作

动作管理器基类

本次游戏动作管理器实例

裁判类

模型设计

界面设计

点击脚本

用户界面的脚本


前言

在上一作魔鬼与牧师MVC版本的基本思想上制作一般动作分离的魔鬼与牧师游戏,故游戏本身的分析与上一作一致,只有代码思路不一样。

具体游戏分析请看:

http://t.csdnimg.cn/MOzvK

成果展示与获取

展示:https://www.bilibili.com/video/BV1wG411C7dr/?share_source=copy_web&vd_source=9d9b1417790062ff0fa21135bfdeae10

获取:https://gitee.com/Wesley-L/the-devil-and-the-pastor-action-separate.git

动作分离

在游戏设计中,动作分离是一种技术和方法,用于将游戏中的动作与角色或对象的状态分离开来。它可以帮助开发者更好地控制和管理游戏中的动作,提供更灵活和可扩展的游戏玩法。

动作分离的基本思想是将角色或对象的动作定义为独立的动作资源,而不是直接嵌入到角色或对象的状态中。这些动作资源可以是预先录制的动画片段,也可以是程序生成的动画。通过这种方式,开发者可以根据需要组合和控制这些动作资源,实现更丰富和多样化的游戏动作。

动作分离的好处包括:

  1. 灵活性:通过将动作与角色状态分离,开发者可以更加灵活地组合和切换不同的动作,从而实现更多样化的游戏玩法和交互方式。

  2. 可扩展性:动作分离使得添加新的动作变得更加容易,开发者可以简单地添加新的动作资源,而无需修改现有的游戏逻辑和代码。

  3. 可复用性:由于动作被独立定义和管理,它们可以在不同的角色或对象之间共享和重用,从而提高开发效率和资源利用率。

  4. 精细控制:动作分离使得开发者可以更精细地控制动作的细节,例如动画过渡、播放速度、循环方式等,从而实现更好的游戏体验。

在实际应用中,动作分离通常需要使用合适的工具和技术,例如动画编辑器、动作状态机等。开发者需要设计和定义角色的状态和转换条件,并将相应的动作资源与之关联起来。通过合理的状态管理和动作调度,可以实现流畅和逼真的游戏动作。

动作分离设计

设计思路

动作设计

ActionController

回调函数接口

这里采用接口来表示,每一个动作完成后,调用者会进行的操作,这一操作被放置在了回调函数里。

public enum SSActionEventType : int { Started, Competeted }
​
//接口
public interface ISSActionCallback {
    void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null);
}
动作基类

这里通过基类和虚函数来规定子类需要实现的操作,通过“可进行”和“可删除”两个变量来控制动作。

//动作基类
public class SSAction : ScriptableObject {
    public bool enable = true;                      //是否可进行
    public bool destroy = false;                    //是否已完成
​
    public GameObject gameobject;                   //动作对象
    public Transform transform;                     //动作对象的transform
    public ISSActionCallback callback;              //回调函数
​
    /*防止用户自己new对象*/
    protected SSAction() { }                      
​
    public virtual void Start() {
        throw new System.NotImplementedException();
    }
​
    public virtual void Update() {
        throw new System.NotImplementedException();
    }
}
单个动作

注意单个动作的以下两个操作:

//这里利用Unity自带的构造函数创建对象,并初始化
public static SSMoveToAction GetSSAction(Vector3 _target, float _speed) {
    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 List<SSAction> sequence;    //动作的列表
public int repeat = -1;            //-1就是无限循环
public int start = 0;              //当前做的动作的标号

注意以下几个函数:

//让序列中的每一个动作都执行
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);  //通知管理者
        }
    }
}
动作管理器基类

动作管理器是一个门面,它可以自己实现对于动作的控制,并可以提供接口给外部函数调用。 尤其注意这里的等待添加和等待删除的队列,由于每次update的过程中不可能立刻对新增动作和已执行的动作进行处理,因此需要一个队列来进行缓冲。被删除的动作对象也由Unity的Destroy进行操作。 对于门面模式,这里提供了函数RunAction(),外界仅需指定游戏对象、动作和回调处理器即可由这个类完成这些动作的实现。

public class SSActionManager : MonoBehaviour {
    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);
            Object.Destroy(ac);//让Unity帮着删除
        }
        waitingDelete.Clear();
    }
​
    //外界只需要调用动作管理类的RunAction函数即可完成动作。
    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 moveBoat(GameObject boat, Vector3 end, float speed) {
    boatMove = SSMoveToAction.GetSSAction(end, speed);
    this.RunAction(boat, boatMove, this);
}
​
public void moveRole(GameObject role, Vector3 middle, Vector3 end, float speed) {
    //两段移动
    SSAction action1 = SSMoveToAction.GetSSAction(middle, speed);
    SSAction action2 = SSMoveToAction.GetSSAction(end, speed);
    //两个动作,都只重复一次
    roleMove = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });
    this.RunAction(role, roleMove, this);
}

裁判类

只需将原有的判断函数移动到类中即可,作一些变量的修改:

public class GameCheck : MonoBehaviour {
    public Controller sceneController;
​
    protected void Start() {
        sceneController = (Controller)SSDirector.GetInstance().CurrentScenceController;
    }
​
    public int GameJudge()
    {
        int src_priest = sceneController.src_land.GetTotal(0);
        int src_ghost = sceneController.src_land.GetTotal(1);
        int des_priest = sceneController.des_land.GetTotal(0);
        int des_ghost = sceneController.des_land.GetTotal(1);
​
        if (des_priest + des_ghost == 6)
        {    //全部到终点,获胜
            return 1;
        }
​
        if (sceneController.boat.GetBoatMark() == 1)//由于在这一边船还没开,因此只需检测另一边的数量即可。
        {
            if (des_priest < des_ghost && des_priest > 0)
            {//失败
                return -1;
            }
        }
        else
        {
            if (src_priest < src_ghost && src_priest > 0)
            {//失败
                return -1;
            }
        }
​
        return 0;//游戏继续进行
    }
}

模型设计

代码中的主要类和接口包括:

  1. ISceneController 接口:定义了一个加载场景资源的方法 LoadResources()

  2. IUserAction 接口:定义了用户操作相关的方法,包括移动船只(MoveBoat())、重新开始游戏(Restart())和移动角色(MoveRole(RoleModel role))。

  3. SSDirector 类:导演类,用于管理当前场景控制器和实现单例模式。

  4. RoleModel 类:角色模型类,表示游戏中的角色。每个角色都有一个角色标识(role_sign),可以是牧师或魔鬼,以及一个表示是否在船上的状态(on_boat)。角色可以在陆地和船只之间移动,具有移动方法(Move(Vector3 end))和上下船的方法(ToLand(LandModel land)ToBoat(BoatModel boat))。

  5. LandModel 类:陆地模型类,表示游戏中的陆地。每个陆地对象有一个标识(land_mark),用于区分起始陆地和目标陆地。陆地上可以放置角色,通过 AddRole(RoleModel role) 方法添加角色,通过 RemoveRole(string name) 方法移除角色。还提供了获取空位置和计算特定类型角色数量的方法。

  6. BoatModel 类:船只模型类,表示游戏中的船只。船只有一个标识(boat_mark),用于区分船只所在的陆地。船上可以放置两个角色,通过 AddRole(RoleModel role) 方法添加角色,通过 RemoveRole(string name) 方法移除角色。还提供了获取空位置和计算船上角色数量的方法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace GhostBoatGame
{
    //加载场景接口
    public interface ISceneController
    {
        void LoadResources();
    }

    //用户操作接口
    public interface IUserAction
    {
        void MoveBoat(); //移动船
        void Restart(); //重新开始
        void MoveRole(RoleModel role); //移动角色
    }

    //导演类
    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;
        }
    }

    public class RoleModel
    {
        GameObject role;
        int role_sign;  //0为牧师,1为魔鬼
        bool on_boat;   //是否在船上
        LandModel land = (SSDirector.GetInstance().CurrentScenceController as Controller).src_land;//所在的陆地

        GhostBoatActionManager moveController;
        Clickable click;//给model赋予属性,可点击、可移动,相当于添加了脚本。
        public float speed = 15;

        public RoleModel(int id, Vector3 pos)
        {
            if (id == 0)
            {
                role_sign = 0;
                role = Object.Instantiate(Resources.Load("Prefabs/Priest", typeof(GameObject)), pos, Quaternion.identity) as GameObject;
            }
            else
            {
                role_sign = 1;
                role = Object.Instantiate(Resources.Load("Prefabs/Ghost", typeof(GameObject)), pos, Quaternion.identity) as GameObject;
            }
            click = role.AddComponent(typeof(Clickable)) as Clickable;
            click.SetRole(this);
            moveController = (SSDirector.GetInstance().CurrentScenceController as Controller).action_manager;
        }

        public int GetSign()
        {
            return role_sign;
        }

        public string GetName()
        {
            return role.name;
        }

        public LandModel GetLandModel()
        {
            return land;
        }

        public bool IsOnBoat()
        {
            return on_boat;
        }

        public void SetName(string name)
        {
            role.name = name;
        }

        public void Move(Vector3 end)//移动操作的对外接口
        {
            Vector3 middle = new Vector3(role.transform.position.x, end.y, end.z);
            moveController.moveRole(role, middle, end, speed);
        }

        public void ToLand(LandModel land)//上岸的移动
        {
            Vector3 pos = land.GetEmptyPosition();
            Vector3 middle = new Vector3(role.transform.position.x, pos.y, pos.z);
            moveController.moveRole(role, middle, pos, speed);
            this.land = land;
            on_boat = false;
        }

        public void ToBoat(BoatModel boat)//上船的移动
        {
            Vector3 pos = boat.GetEmptyPosition();
            Vector3 middle = new Vector3(pos.x, role.transform.position.y, pos.z);
            moveController.moveRole(role, middle, pos, speed);
            this.land = null;
            on_boat = true;
        }

        public void Reset()
        {
            LandModel land = (SSDirector.GetInstance().CurrentScenceController as Controller).src_land;
            ToLand(land);
            land.AddRole(this);
        }
    }

    public class LandModel
    {
        GameObject land;   //陆地对象
        public int land_mark;//src为1,des为-1。
        RoleModel[] roles = new RoleModel[6];//陆地上的角色对象
        Vector3[] role_positions;//每个角色的位置

        public LandModel(int sign)
        {//根据对象标识初始化
            land_mark = sign;
            land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(10.5F * land_mark, 0.5F, 0), Quaternion.identity) as GameObject;
            role_positions = new Vector3[] { new Vector3(6.5F * land_mark, 1.8F, 0), new Vector3(8.0F * land_mark, 1.8F, 0), new Vector3(9.5F * land_mark, 1.8F, 0), new Vector3(11.0F * land_mark, 1.8F, 0), new Vector3(12.5F * land_mark, 1.8F, 0), new Vector3(14.0F * land_mark, 1.8F, 0) };
        }

        public int GetLandMark()
        {
            return land_mark;
        }

        public Vector3 GetEmptyPosition()
        {//找到当前空位置
            int pos = -1;
            for (int i = 0; i < 6; i++)
            {
                if (roles[i] == null)
                {
                    pos = i;
                    break;
                }
            }

            return role_positions[pos];
        }

        public void AddRole(RoleModel role)
        {//添加role
            for (int i = 0; i < 6; i++)
            {
                if (roles[i] == null)
                {
                    roles[i] = role;
                    break;
                }
            }
        }

        public RoleModel RemoveRole(string name)
        {//删除role
            for (int i = 0; i < 6; i++)
            {
                if (roles[i] != null && roles[i].GetName() == name)
                {
                    roles[i] = null;
                    return roles[i];
                }
            }
            return null;
        }

        public int GetTotal(int id)
        {
            int sum = 0;
            if (id == 0)
            {//牧师
                for (int i = 0; i < 6; i++)
                {
                    if (roles[i] != null && roles[i].GetSign() == 0)
                    {
                        sum++;
                    }
                }
            }
            else if (id == 1)
            {//魔鬼
                for (int i = 0; i < 6; i++)
                {
                    if (roles[i] != null && roles[i].GetSign() == 1)
                    {
                        sum++;
                    }
                }
            }
            return sum;
        }

        public void Reset()
        {
            roles = new RoleModel[6];
        }
    }

    public class BoatModel
    {
        GameObject boat;//船对象
        Vector3[] src_empty_pos;//船在src陆地的两个空位位置
        Vector3[] des_empty_pos;//船在des陆地的两个空位位置
        public float speed = 15;

        GhostBoatActionManager moveController;
        Clickable click;

        int boat_mark = 1;//船在src为1,在des为-1。
        RoleModel[] roles = new RoleModel[2];//船上的两个角色。

        public BoatModel()
        {//初始化对象
            boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), new Vector3(4.5F, 0.5F, 0), Quaternion.identity) as GameObject;
            boat.name = "boat";
            moveController = (SSDirector.GetInstance().CurrentScenceController as Controller).action_manager;
            click = boat.AddComponent(typeof(Clickable)) as Clickable;
            src_empty_pos = new Vector3[] { new Vector3(3.8F, 1.1F, 0), new Vector3(5.2F, 1.1F, 0) };
            des_empty_pos = new Vector3[] { new Vector3(-5.2F, 1.1F, 0), new Vector3(-3.8F, 1.1F, 0) };
        }

        public int Total()
        {
            int sum = 0;
            for (int i = 0; i < 2; i++)
            {
                if (roles[i] != null)
                {
                    sum++;
                }
            }
            return sum;
        }

        public void Move()//移动船只的同时,调用角色的函数Move,同时将角色移到指定位置。
        {
            if (boat_mark == -1)
            {
                moveController.moveBoat(boat, new Vector3(4.5F, 0.5F, 0), speed);
                for (int i = 0; i < 2; i++)
                {
                    if (roles[i] != null)
                    {
                        roles[i].Move(src_empty_pos[i]);
                    }
                }
                boat_mark = 1;
            }
            else
            {
                moveController.moveBoat(boat, new Vector3(-4.5F, 0.5F, 0), speed);
                for (int i = 0; i < 2; i++)
                {
                    if (roles[i] != null)
                    {
                        roles[i].Move(des_empty_pos[i]);
                    }
                }
                boat_mark = -1;
            }
        }

        public int GetBoatMark()
        {
            return boat_mark;
        }

        public Vector3 GetEmptyPosition()
        {//找到当前空位置
            if (boat_mark == 1)
            {
                int pos = -1;
                for (int i = 0; i < 2; i++)
                {
                    if (roles[i] == null)
                    {
                        pos = i;
                        break;
                    }
                }
                return src_empty_pos[pos];
            }
            else
            {
                int pos = -1;
                for (int i = 0; i < 2; i++)
                {
                    if (roles[i] == null)
                    {
                        pos = i;
                        break;
                    }
                }
                return des_empty_pos[pos];
            }
        }

        public void AddRole(RoleModel role)
        {//添加role
            for (int i = 0; i < 2; i++)
            {
                if (roles[i] == null)
                {
                    roles[i] = role;
                    break;
                }
            }
        }

        public RoleModel RemoveRole(string name)
        {//删除role
            for (int i = 0; i < 2; i++)
            {
                if (roles[i] != null && roles[i].GetName() == name)
                {
                    roles[i] = null;
                    return roles[i];
                }
            }
            return null;
        }

        public void Reset()
        {
            if (boat_mark == -1)
            {
                moveController.moveBoat(boat, new Vector3(4.5F, 0.5F, 0), speed);
                boat_mark = 1;
            }
            roles = new RoleModel[2];
        }
    }



}

界面设计

点击脚本

可点击对象的脚本,用于处理游戏中的角色和船只的点击事件。

  1. IUserAction action; 定义了一个用户操作接口变量 action,用于调用游戏控制器中的方法。
  2. RoleModel role = null; 定义了一个角色模型变量 role,用于存储当前点击对象的角色。
  3. public void SetRole(RoleModel role) 是一个公共方法,用于设置当前点击对象的角色。
  4. private void Start() 是一个启动方法,用于获取游戏控制器对象并赋值给 action 变量。
  5. private void OnMouseDown() 是一个鼠标点击事件方法,在点击发生时调用。通过比较点击对象的名称来区分是船只还是角色。
    • 如果点击对象的名称是 "boat",则调用 action.MoveBoat() 方法,移动船只。
    • 否则,调用 action.MoveRole(role) 方法,移动角色。角色信息存储在 role 变量中,通过 SetRole() 方法进行设置
public class Clickable : MonoBehaviour
{
    IUserAction action;
    RoleModel role = null;

    public void SetRole(RoleModel role)
    {
        this.role = role;
    }
    private void Start()
    {
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;//获取控制器
    }
    private void OnMouseDown()//点击时调用控制器中的相关函数,用gameObject的name来区分对象。
    {
        if (gameObject.name == "boat")
        {
            action.MoveBoat();
        }
        else
        {
            action.MoveRole(role);
        }
    }
}

用户界面的脚本

用户界面的脚本,用于显示游戏状态和处理重新开始游戏的逻辑。

  1. private IUserAction action; 定义了一个用户操作接口变量 action,用于调用游戏控制器中的方法。
  2. public int status = 0; 定义了一个整型变量 status,表示游戏的状态,默认值为 0。
  3. bool isShow = false; 定义了一个布尔变量 isShow,用于控制界面的显示状态,默认为 false。
  4. GUIStyle white_style = new GUIStyle(); 定义了一个 GUIStyle 变量 white_style,用于设置白色字体样式。
  5. GUIStyle black_style = new GUIStyle(); 定义了一个 GUIStyle 变量 black_style,用于设置黑色字体样式。
  6. GUIStyle title_style = new GUIStyle(); 定义了一个 GUIStyle 变量 title_style,用于设置标题字体样式。

在 Start() 方法中,首先获取当前场景的控制器对象并赋值给 action 变量。然后,根据需要设置不同样式的字体,包括白色字体、黑色字体和标题字体。

在 OnGUI() 方法中,根据游戏的状态来显示相应的界面。如果游戏状态为 -1,即失败状态,显示 "You Lose!" 的黑色字体和一个重新开始按钮。点击重新开始按钮时,调用游戏控制器的 Restart() 方法重新开始游戏,并将 status 设置为 0。如果游戏状态为 1,即胜利状态,显示 "You Win!" 的黑色字体和一个重新开始按钮。点击重新开始按钮时,同样调用游戏控制器的 Restart() 方法重新开始游戏,并将 status 设置为 0。

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    public int status = 0;
    bool isShow = false;

    GUIStyle white_style = new GUIStyle();
    GUIStyle black_style = new GUIStyle();
    GUIStyle title_style = new GUIStyle();

    void Start()
    {
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;

        //字体初始化
        white_style.normal.textColor = Color.white;
        white_style.fontSize = 20;

        black_style.normal.textColor = Color.black;
        black_style.fontSize = 30;

        title_style.normal.textColor = Color.black;
        title_style.fontSize = 45;
    }

    void OnGUI()
    {
       

        if (status == -1)
        {
            GUI.Label(new Rect(Screen.width / 2 - 60, 180, 100, 30), "You Lose!", black_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 40, 240, 100, 30), "Restart"))
            {
                action.Restart();
                status = 0;
            }
        }
        else if (status == 1)
        {
            GUI.Label(new Rect(Screen.width / 2 - 62, 180, 100, 30), "You Win!", black_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 40, 240, 100, 30), "Restart"))
            {
                action.Restart();
                status = 0;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

We3le7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值