Unity3D-牧师与魔鬼

lab简介

        本实验作业旨在通过实现一个简单的MVC分离版的牧师与魔鬼游戏,帮助学习者理解和应用MVC(Model-View-Controller)设计模式以及cocos2d的动作管理器分离模式、门面模式。

        

  牧师与魔鬼游戏是一个经典的逻辑解谜游戏,玩家需要帮助牧师和魔鬼安全地过河。以下是该游戏的简要规则解释:

  1. 游戏目标:将所有的牧师和魔鬼都安全地从河的一侧带到另一侧,达到胜利条件。

  2. 游戏元素:游戏中有两个岸边和一条小船,岸边分别用左岸和右岸表示。初始情况下,牧师和魔鬼以及小船都在左岸。

  3. 角色移动:每次只能移动一个或两个角色(牧师或魔鬼)。船的容量有限,最多只能搭载两个角色。

  4. 移动限制:移动时需要遵守以下限制条件:

    • 在任何一侧,如果魔鬼的数量多于牧师,魔鬼会被牧师吃掉,游戏失败。
    • 小船要么停在左岸,要么停在右岸,不能在中间的河中停靠。
    • 每次移动,小船必须由一个角色驾驶。
  5. 胜利条件:当所有牧师和魔鬼都成功安全地运送到右岸时,达到胜利条件。如果在任何时刻,牧师被魔鬼吃掉或者未能满足移动限制,游戏失败。

  6. 游戏挑战:游戏的挑战在于找到一系列合法的移动方式,以确保牧师和魔鬼能够安全地过河,同时避免违反移动限制和胜利条件。

项目预览

MVC_w5

     

            仓库:GitHub - yogaGalan/Unity3D_LAB: Unity3D course_lab

包含脚本与预制对象

使用方法:替换Assets文件夹并挂载Main脚本

     

项目结构

model类未聚合,较冗长,略。

代码结构

SSDirector类

在MVC模式中,导演类是控制器(Controller)的一部分,负责协调模型(Model)和视图(View)之间的交互。它主要承担以下功能:

  1. 场景管理:导演类负责管理游戏中的场景(Scene)。它可以加载、切换和管理不同的场景,例如游戏开始界面、游戏关卡、游戏结束界面等。导演类可以通过提供场景切换的方法,控制游戏的流程和状态。

  2. 资源管理:导演类可以负责加载和管理游戏所需的资源,如图像、音频、动画等。它可以在适当的时候加载和释放这些资源,以提高游戏的性能和内存管理。

  3. 全局状态管理:导演类通常会保存一些全局的状态信息,例如当前场景、游戏时间、分数等。这些状态信息可以在整个应用程序中共享和访问,方便不同模块之间的数据传递和通信。

在COCOS2d游戏引擎中,导演类还具有其他特定的功能,例如游戏循环控制、帧率控制、动画管理等。它是整个游戏引擎的核心,负责驱动游戏的运行。

通过将导演类设计为单例对象,可以确保在整个应用程序中只有一个导演实例存在,避免了多个导演对象之间的冲突和资源浪费。

namespace PriestsAndDevils {

   

    /* 导演类,既单例对象,在此单场景游戏demo中只需要通过唯一控制类对象访问各控制器,*/

    public class SingleObject {

        private static SingleObject instance = new SingleObject();

        private FirstController mainController;

        public static SingleObject getInstance() {

            return instance;

        }

        public void SetMainController(FirstController controller) {

            mainController = controller;

        }

        public FirstController GetMainController() {

            return mainController;

        }

       

        public int getFPS(){

        return Application.targetFrameRate;

        }

        public void setFPS(int fps){

            Application.targetFrameRate=fps;

        }

    }

}

SenceController类

本游戏只有一个场景,所以该类只有一个实例为 FirstController

在游戏中类似于场记,其职责包括:

管理本次场景所有的游戏对象
协调游戏对象(预制件级别)之间的通讯
响应外部输入事件
管理本场次的规则(裁判)
各种杂务
为了完成以上职责,需要实现IUserAction接口以便于实现玩家与游戏之间的实现,函数的具体实现可以放在模型GenGameObject中,这里调用GenGameObject中的函数即可。在场景被加载(awake)时,它也会自动注入SSDirector,作为当前场景。


解析引用自:https://blog.csdn.net/weixin_64218808/article/details/133758506

需要注意的是,本代码采用门面模式,定义了Facade类间接调用控制器函数来实现接口。

部分代码:(代码过多,全部代码请查看github仓库)

/* 第一游戏场景的主控制器 */

    public class FirstController : MonoBehaviour {

        private BoatController boat; // 船控制器

        private ShoreController leftShore; // 左岸控制器

        private ShoreController rightShore; // 右岸控制器

        private RoleController [] roles; // 所有角色的控制器

        private GUIView functionView; // 功能视图,包括提示框,游戏信息等

        private int gameState = (int)GameState.Playing; // 游戏状态,0表示游戏正在进行,1表示游戏失败,2表示游戏成功

        void Awake() {

            // 设置单例对象中只有唯一一个主控制器

            SingleObject instance = SingleObject.getInstance();

            instance.SetMainController(this);

            // 展现游戏视图

            ShowView();

        }

        /* 通过创建控制器的方式初始化游戏视图 */

        public void ShowView() {

            boat = new BoatController();

            leftShore = new ShoreController((int)ShoreSide.Left, "LeftShore");

            rightShore = new ShoreController((int)ShoreSide.Right, "RightShore");

            roles = new RoleController[6];

            CreateRolesController();

            RiverView riverView = new RiverView();

            functionView = gameObject.AddComponent<GUIView>() as GUIView;

        }

        /* 为每个角色创建控制器并初始化视图 */

        private void CreateRolesController() {

            for (int i = 0; i < 3; i++) {

                roles[i] = new RoleController(0, "Priest", i + 1);

                roles[i].SetRolePos(rightShore.GetFreePosition());

                roles[i].PutRoleOnShore(rightShore);

                rightShore.PutRoleOnShore(roles[i].GetRoleViewName());

            }

            for (int i = 0; i < 3; i++) {

                roles[i + 3] = new RoleController(1, "Devil", i + 1);

                roles[i + 3].SetRolePos(rightShore.GetFreePosition());

                roles[i + 3].PutRoleOnShore(rightShore);

                rightShore.PutRoleOnShore(roles[i + 3].GetRoleViewName());

            }

        }

        /* 通过初始化控制器和视图的方式初始化游戏 */

        public void InitGame() {

            boat.InitBoatController();

            leftShore.InitShoreController();

            rightShore.InitShoreController();

            for (int i = 0; i < roles.Length; i++) {

                roles[i].InitRoleController();

            }

            functionView.InitGUIView();

            gameState = (int)GameState.Playing;

        }

        public ShoreController GetRightShore() {

            return rightShore;

        }

IUserAction接口类 / Facade门面类

按照MVC分离的理念,我们需要一个接口类。该接口定义了一组操作,该接口实现了用户行为与游戏系统规则计算的分离。这个接口就是游戏逻辑与用户界面之间交互的门面(Fasàde)。

于是我们按照游戏交互动作所需,定义门面类Facade实现接口。View模块通过调用门面类完成游戏交互。

namespace PriestsAndDevils {

    //供GUI界面人机交互使用的接口

    public interface IUserAction

    {

    void ShowView();

    void InitGame();

    void MoveBoat();

    void MoveRole(RoleController role);

    int CheckGameState();

    // 其他门面方法...

    }

    //实现IUserAction接口的门面类,使用间接调用firstController文件中的主控制器的函数

    //解耦controller与GUI(view),在多场景游戏中使得view代码可以复用

    public class Facade : IUserAction{

        private FirstController controller;

        public Facade(FirstController controller) {

            this.controller = controller;

        }

        public void ShowView(){

            this.controller.ShowView();

        }

        public void InitGame(){

            this.controller.InitGame();

        }

        public void MoveBoat() {

            this.controller.MoveBoat();

        }

        public void MoveRole(RoleController role) {

            this.controller.MoveRole(role);

        }

        public int CheckGameState(){

            int GS = this.controller.CheckGameState();

            return GS;

        }

       

    }

}

GUIView类

(位于UserGUI脚本中,该脚本除此类外定义了游戏对象的视图。)

GUIView类概念继承于MVC结构的View模块,负责游戏图像的绘制以及按钮交互。

部分代码:

public class GUIView : MonoBehaviour {

        private string tipContent; // 提示框显示内容

        private Facade facade;

        public GUIView() {

            tipContent = "点击角色或船只进行游戏!";

        }

        public void SetFacade(){

            FirstController mainController = SingleObject.getInstance().GetMainController();

            facade = new Facade(mainController); // 使用new关键字创建门面对象实例并赋值给接口引用

        }


 

        public void SetTipContent(string tip) {

            tipContent = tip;

        }

        /* 初始化提示框 */

        public void InitGUIView() {

            tipContent = "点击角色或船只进行游戏!";

        }

        public void ShowResetButton() {

            GUIStyle resetStyle = new GUIStyle("button");

            resetStyle.fontSize = 20;

            // 按下重置按钮,游戏被初始化

            if (GUI.Button(new Rect(30, 20, 70, 30), "Reset", resetStyle)) {

                SetFacade(); // 初始化 facade 对象

                facade.InitGame();

            }

        }

Model类

定义了各游戏对象的数据、数据处理逻辑与接口业务。值得注意的是,在MVC分离结构中,Model类只为控制器提供接口,并不与View模块进行交互。

部分代码:

 public class RoleModel {

        private int roleType;  // 角色类型,0表示牧师,1表示魔鬼

        private bool isOnBoat;  // 角色是否在船上

        public RoleModel(int type) {

            roleType = type;

        }

        public void SetRoleType(int type) {

            roleType = type;

        }

        public int GetRoleType() {

            return roleType;

        }

        public void SetIsOnBoat(bool isOnBoat) {

            this.isOnBoat = isOnBoat;

        }

        public bool GetIsOnBoat() {

            return isOnBoat;

        }

    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值