lab简介
本实验作业旨在通过实现一个简单的MVC分离版的牧师与魔鬼游戏,帮助学习者理解和应用MVC(Model-View-Controller)设计模式以及cocos2d的动作管理器分离模式、门面模式。
牧师与魔鬼游戏是一个经典的逻辑解谜游戏,玩家需要帮助牧师和魔鬼安全地过河。以下是该游戏的简要规则解释:
-
游戏目标:将所有的牧师和魔鬼都安全地从河的一侧带到另一侧,达到胜利条件。
-
游戏元素:游戏中有两个岸边和一条小船,岸边分别用左岸和右岸表示。初始情况下,牧师和魔鬼以及小船都在左岸。
-
角色移动:每次只能移动一个或两个角色(牧师或魔鬼)。船的容量有限,最多只能搭载两个角色。
-
移动限制:移动时需要遵守以下限制条件:
- 在任何一侧,如果魔鬼的数量多于牧师,魔鬼会被牧师吃掉,游戏失败。
- 小船要么停在左岸,要么停在右岸,不能在中间的河中停靠。
- 每次移动,小船必须由一个角色驾驶。
-
胜利条件:当所有牧师和魔鬼都成功安全地运送到右岸时,达到胜利条件。如果在任何时刻,牧师被魔鬼吃掉或者未能满足移动限制,游戏失败。
-
游戏挑战:游戏的挑战在于找到一系列合法的移动方式,以确保牧师和魔鬼能够安全地过河,同时避免违反移动限制和胜利条件。
项目预览
MVC_w5
仓库:GitHub - yogaGalan/Unity3D_LAB: Unity3D course_lab
包含脚本与预制对象
使用方法:替换Assets文件夹并挂载Main脚本
项目结构
model类未聚合,较冗长,略。
代码结构
SSDirector类
在MVC模式中,导演类是控制器(Controller)的一部分,负责协调模型(Model)和视图(View)之间的交互。它主要承担以下功能:
-
场景管理:导演类负责管理游戏中的场景(Scene)。它可以加载、切换和管理不同的场景,例如游戏开始界面、游戏关卡、游戏结束界面等。导演类可以通过提供场景切换的方法,控制游戏的流程和状态。
-
资源管理:导演类可以负责加载和管理游戏所需的资源,如图像、音频、动画等。它可以在适当的时候加载和释放这些资源,以提高游戏的性能和内存管理。
-
全局状态管理:导演类通常会保存一些全局的状态信息,例如当前场景、游戏时间、分数等。这些状态信息可以在整个应用程序中共享和访问,方便不同模块之间的数据传递和通信。
在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;
}
}