Unity使用MVC架构制作牧师与魔鬼小游戏

MVC架构简介

MVC概述

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

MVC优点

  • 耦合性低:视图层和业务层分离,这样就允许更改视图层代码而不用重新编译模型和控制器代码,同样,一个应用的业务流程或者业务规则的改变只需要改动MVC的模型层即可。因为模型与控制器和视图相分离,所以很容易改变应用程序的数据层和业务规则。
  • 重用性高:MVC模式允许使用各种不同样式的视图来访问同一个服务器端的代码,因为多个视图能共享一个模型,它包括任何WEB(HTTP)浏览器或者无线浏览器(wap),比如,用户可以通过电脑也可通过手机来订购某样产品,虽然订购的方式不一样,但处理订购产品的方式是一样的。由于模型返回的数据没有进行格式化,所以同样的构件能被不同的界面使用。
  • 部署快,生命周期成本低:MVC使开发和维护用户接口的技术含量降低。使用MVC模式使开发时间得到相当大的缩减,它使程序员(Java开发人员)集中精力于业务逻辑,界面程序员(HTML和JSP开发人员)集中精力于表现形式上。
  • 可维护性高:分离视图层和业务逻辑层也使得WEB应用更易于维护和修改。

MVC缺点

  • 完全理解MVC比较复杂:由于MVC模式提出的时间不长,加上同学们的实践经验不足,所以完全理解并掌握MVC不是一个很容易的过程。
  • 调试困难:因为模型和视图要严格的分离,这样也给调试应用程序带来了一定的困难,每个构件在使用之前都需要经过彻底的测试。
  • 不适合小型,中等规模的应用程序:在一个中小型的应用程序中,强制性的使用MVC进行开发,往往会花费大量时间,并且不能体现MVC的优势,同时会使开发变得繁琐。
  • 增加系统结构和实现的复杂性:对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
  • 视图与控制器间的过于紧密的连接并且降低了视图对模型数据的访问:视图与控制器是相互分离,但却是联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。

项目配置

  • 首先新建项目
  • 将我的Asserts文件夹替换Unity项目中的Asserts文件夹
  • 把Assets/Resources/Scripts/Controllor.cs挂载到Main Camera上
  • 点击运行按钮开始游戏

项目实现

游戏中提及的事物

魔鬼、牧师、船、河、两边的陆地

用表格列出玩家的动作表

玩家动作 执行条件 执行结果
点击牧师/魔鬼 游戏进行中,船在岸边,该牧师/魔鬼与船在同一边的岸上/在船上 牧师/魔鬼上船/上岸
点击船 游戏进行中且船上有1或2个人 船移动到另一边

实现思路

程序设计框架:

使用MVC架构,MVC是界面人机交互程序设计的一种架构模式,它把程序分为三个部分:

  • 模型(Model):数据对象及关系

    • 游戏对象、空间关系
  • 控制器(Controller):接受用户事件,控制模型的变化
    • 一个场景一个主控制器
    • 至少实现与玩家交互的接口(IPlayerAction)
    • 实现或管理运动
  • 界面(View):显示模型,将人机交互事件交给控制器处理
    • 处理 Input 事件
    • 渲染 GUI ,接收事件

在本项目中:

  • 接口:ISceneController和IUserAction两个接口,分别定义场景操作和用户操作
  • 导演:SSDirector采用单例模式,把握全局,控制场景
  • Model:BoatModel、RoleModel、LandModel三个类
  • Controllor:Controllor类,接受用户事件,控制模型的变化
  • View:UserGUI类,与用户交互
  • 辅助脚本。Move和Click类,定义了移动的方式和对点击模型事件的处理

核心模块介绍

Model

BoatModel类有以下几个成员变量:

GameObject boat;
Vector3[] from_empty_pos;                                    //船在起点陆地的空位位置
Vector3[] to_empty_pos;                                      //船在终点陆地的空位位置
Move move;
Click click;
int boat_state = 1;                                          //0:船在终点;1:船在起点
RoleModel[] roles = new RoleModel[2];

定义了以下几个方法:

//构造函数
public BoatModel();
//检测船是否为空
public bool IsEmpty();
//移动船
public void BoatMove();
//获得船的状态:在终点返回0、在起点返回1
public int GetBoatState();
//根据角色名移除船上的一个角色
public RoleModel DeleteRole(string role_name);
//搜索船上的空位下标
public int GetEmptyIndex();
//获得船上空位的坐标
public Vector3 GetEmptyPosition();
//在船上添加一个角色
public void AddRole(RoleModel role);
//获得船对象
public GameObject GetBoat();
//重置船对象
public void Reset();
//计算船上牧师和魔鬼的数量
public int[] GetRoleType();

其中比较重要的算法实现如下:

public BoatModel()
{boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), new Vector3(-5.5F, 0, 0), Quaternion.identity) as GameObject;boat.name = "boat";move = boat.AddComponent(typeof(Move)) as Move;click = boat.AddComponent(typeof(Click)) as Click;click.SetBoat(this);from_empty_pos = new Vector3[] { new Vector3(-6, 1.5F, 0), new Vector3(-5, 1.5F, 0) };to_empty_pos = new Vector3[] { new Vector3(5, 1.5F, 0), new Vector3(6, 1.5F, 0) };
}
public void BoatMove()
{if (boat_state == 0){move.MoveTo(new Vector3(-5.5F, 0, 0));boat_state = 1;}else{move.MoveTo(new Vector3(5.5F, 0, 0));boat_state = 0;}
}
public RoleModel DeleteRole(string role_name)
{int i=-1;if (roles[0] != null && roles[0].GetName() == role_name)i=0;else if (roles[1] != null && roles[1].GetName() == role_name)i=1;if(i == 0||i == 1){RoleModel role = roles[i];roles[i] = null;return role;}return null;
}

RoleModel类有以下几个成员变量:

GameObject role;
int role_type;             //0为牧师,1为魔鬼
Move move;
Click click;
bool on_boat;              //是否在船上
LandModel land_model = (SSDirector.GetInstance().CurrentScenceController as Controllor).from_land;

定义了以下几个方法:

//构造函数
public RoleModel(string role_name);
//获得角色类型:0为牧师,1为魔鬼
public int GetType();
//获得陆地对象
public LandModel GetLandModel();
//获得角色名称
public string GetName();
//检测此角色是否在船上
public bool IsOnBoat();
//设置角色名称
public void SetName(string name);
//设置角色初始位置
public void SetPosition(Vector3 pos);
//上岸
public void GoLand(LandModel land);
//上船
public void GoBoat(BoatModel boat);
//重置角色,所有角色要回到起点陆地
public void Reset();

其中比较重要的算法实现如下:

public RoleModel(string role_name)
{if (role_name == "priest"){role = Object.Instantiate(Resources.Load("Prefabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity) as GameObject;role_type = 0;}else{role = Object.Instantiate(Resources.Load("Prefabs/Devil", typeof(GameObject)), Vector3.zero, Quaternion.identity) as GameObject;role_type = 1;}move = role.AddComponent(typeof(Move)) as Move;click = role.AddComponent(typeof(Click)) as Click;click.SetRole(this);
}
public void GoLand(LandModel land)
{  move.MoveTo(land.GetEmptyPosition());role.transform.parent = null;land_model = land;on_boat = false;
}
public void GoBoat(BoatModel boat)
{move.MoveTo(boat.GetEmptyPosition());role.transform.parent = boat.GetBoat().transform;land_model = null;          on_boat = true;
}
public void Reset()
{land_model = (SSDirector.GetInstance().CurrentScenceController as Controllor).from_land;GoLand(land_model);SetPosition(land_model.GetEmptyPosition());land_model.AddRole(this);
}

LandModel类有以下几个成员变量:

GameObject land;
Vector3[] positions;                            //保存每个角色放在陆地上的位置
int land_type;                                  //终点陆地标志为0,起点陆地标志为1
RoleModel[] roles = new RoleModel[6];           //陆地上有的角色

定义了以下几个方法:

//构造函数
public LandModel(string land_name);
//得到陆地上哪一个位置是空的
public int GetEmptyIndex();
//确定陆地类型:0-终点、1-起点
public int GetLandType();
//得到陆地上空位置
public Vector3 GetEmptyPosition();
//向陆地上添加角色
public void AddRole(RoleModel role);
//根据名称移除陆地上的角色
public RoleModel DeleteRole(string role_name);
//计算陆地上牧师和魔鬼的数量
public int[] GetRoleType();
//重置陆地,生成新的六个角色
public void Reset();

其中比较重要的算法实现如下:

public LandModel(string land_name)
{positions = new Vector3[] {new Vector3(-6.5F,2,0), new Vector3(-7.5F,2,0), new Vector3(-8.5F,2,0),new Vector3(-9.5F,2,0), new Vector3(-10.5F,2,0), new Vector3(-11.5F,2,0)};if (land_name == "from"){land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(11, -0.5F, 0), Quaternion.identity) as GameObject;land_type = 1;}else{land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(-11, -0.5F, 0), Quaternion.identity) as GameObject;land_type = 0;}
}
public Vector3 GetEmptyPosition()
{Vector3 pos = positions[GetEmptyIndex()];if(land_type == 0)pos.x = -pos.x;                  //两个陆地是x坐标对称,起点为负,终点为正return pos;
}

Controllor

Controllor类有以下几个成员变量:

public LandModel from_land;            //左侧起点陆地
public LandModel to_land;              //右侧终点陆地
public BoatModel boat;                  //船
private RoleModel[] roles;              //角色
UserGUI user_gui;                       //GUI界面

定义了以下几个方法:

void Start ();
//创建水、陆地、牧师、魔鬼、船
public void LoadResources();
//在船上有人且游戏正在进行中的状态下移动船,并检查游戏是否结束
public void MoveBoat();
//移动角色,并检查游戏是否结束
public void MoveRole(RoleModel role);
//重置游戏
public void Restart();
//检查游戏状态:0-游戏继续、1-游戏失败、2-游戏获胜
public int Check();

其中比较重要的算法实现如下:

void Start ()
{SSDirector director = SSDirector.GetInstance();director.CurrentScenceController = this;user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;transform.position = transform.rotation * (new Vector3(0, 1, -14));//设置Main Camera位置LoadResources();
}
public void LoadResources()
{GameObject water = Instantiate(Resources.Load("Prefabs/Water", typeof(GameObject)), new Vector3(0, -1, 0), Quaternion.identity) as GameObject;water.name = "water";       from_land = new LandModel("from");to_land = new LandModel("to");boat = new BoatModel();roles = new RoleModel[6];//初始时角色全置于左侧起点陆地for (int i = 0; i < 6; i++){RoleModel role;if(i<3){role = new RoleModel("priest");role.SetName("priest" + i);}else{role = new RoleModel("devil");role.SetName("devil" + (i-3));}role.SetPosition(from_land.GetEmptyPosition());role.GoLand(from_land);from_land.AddRole(role);roles[i] = role;}
}
public int Check()
{int from_priests = (from_land.GetRoleType())[0];int from_devils = (from_land.GetRoleType())[1];int to_priests = (to_land.GetRoleType())[0];int to_devils = (to_land.GetRoleType())[1];//全部角色均到达终点则获胜if (to_priests + to_devils == 6)return 2;//一侧魔鬼多于牧师则失败int[] boat_role_type = boat.GetRoleType();if (boat.GetBoatState() == 1)         //船在起点岸边{from_priests += boat_role_type[0];from_devils += boat_role_type[1];}else                             //船在终点岸边{to_priests += boat_role_type[0];to_devils += boat_role_type[1];}if ((from_priests > 0 && from_priests < from_devils)||(to_priests > 0 && to_priests < to_devils)) //失败{      return 1;}//游戏继续return 0;
}

View

UserGUI类有以下几个成员变量:

private IUserAction action;
public int state = 0;//游戏状态:0-正在进行中;1-获胜;2-失败
bool ShowRule = false;//显示游戏规则
private GUIStyle title_style;
private GUIStyle result_style;
private GUIStyle rule_style;
private GUIStyle button_style;

定义了以下几个方法:

//初始化字体样式
void init();
void Start();
//记录用户点击Rule按钮事件
void rule();
//每帧刷新界面
void OnGUI();

其中比较重要的算法实现如下:

void Start()
{//获得控制器对象action = SSDirector.GetInstance().CurrentScenceController as IUserAction;init();
}
void OnGUI()
{GUI.Label(new Rect(Screen.width / 2 - 130, 10, 200, 50), "牧师与魔鬼", title_style);if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", button_style)) rule();if(ShowRule){GUI.Label(new Rect(Screen.width / 2 - 260, 70, 200, 20), "点击牧师(白)或魔鬼(黑)可控制角色上船或上岸,点击船开船", rule_style);GUI.Label(new Rect(Screen.width / 2 - 160, 95, 300, 20), "船至多载2人,至少由1人驾驶。", rule_style);GUI.Label(new Rect(Screen.width / 2 - 250, 120, 240, 20), "让全部牧师和魔鬼渡河,每一边魔鬼数量都不能多于牧师", rule_style);}if (state == 1){GUI.Label(new Rect(Screen.width / 2-110, Screen.height / 2-70, 100, 50), "GAME OVER!", result_style);if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "Restart", button_style)){action.Restart();state = 0;}}else if (state == 2){GUI.Label(new Rect(Screen.width / 2 - 80, Screen.height / 2 - 70, 100, 50), "YOU WIN!", result_style);if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "Restart", button_style)){action.Restart();state = 0;}}
}

效果展示

玩法说明

牧师和魔鬼是一款益智游戏,你将帮助牧师和魔鬼在限定时间内过河。河的一边有三个牧师和三个魔鬼。他们都想去这条河的对岸,但是只有一艘船,这艘船每次只能载两个人。必须有一个人把船从一边开到另一边。在flash游戏中,你可以点击它们移动它们,然后点击船将船移动到另一个方向。如果在河的两边,牧师的人数超过了魔鬼,他们就会被杀,游戏就结束了。你可以用很多方法来尝试。让所有牧师活着!祝你好运!

效果截图

启动游戏,初始界面如下图:

点击左上角Rule按钮可以查看游戏规则

游戏进行中效果图:点击最左侧的牧师和最右侧的魔鬼,然后点击船

游戏失败界面,点击Restart按钮可以重新开始游戏

游戏获胜界面,点击Restart按钮可以重新开始游戏

心得体会

  • 了解了MVC的框架模式
  • 学习了使用代码动态生成游戏对象
  • 实践了使用Unity开发小游戏的基本操作
  • 学习了使用 IMGUI 构建 UI
  • 学习使用 OnGUI() 事件,提升 debug 能力

参考资料

什么是MVC

空间与运动

Unity使用MVC架构制作牧师与魔鬼小游戏相关推荐

  1. Unity牧师与魔鬼小游戏(动作分离版)

    Unity牧师与魔鬼小游戏(动作分离版) 前言 这是中大计算机学院3D游戏编程课的一次作业,在这里分享一下设计思路. 主要代码上传到了gitee上,请按照后文的操作运行. 项目地址:https://g ...

  2. Unity牧师与魔鬼小游戏

    Unity牧师与魔鬼小游戏 前言 这是中大计算机学院3D游戏编程课的一次作业,在这里分享一下设计思路. 主要代码上传到了gitee上,请按照后文的操作运行. 项目地址:https://gitee.co ...

  3. unity课设小游戏_Unity制作20个迷你小游戏实例训练视频教程

    本教程是关于Unity制作20个迷你小游戏实例训练视频教程,时长:20小时,大小:3.8 GB,MP4高清视频格式,教程使用软件:Unity,附源文件,作者:Raja Biswas,共97个章节,语言 ...

  4. 计算机课玩的小游戏怎么找,能够回味电脑课的小游戏是什么 怎么制作这两个小游戏...

    能够回味电脑课的小游戏是什么,怎么制作这两个小游戏.在我的世界里回味已经是很多人的游玩乐趣之一了,因为这个游戏超高的自由度还有各种玩法可以让我们回味童年的种种. 这些小游戏乍一看都不怎么需要技巧 简单 ...

  5. 使用小程序制作一个飞机大战小游戏

    此文主要基于微信小程序制作一个飞机大战小游戏,上手即用,操作简单. 一.创建小程序 二.页面实现 三.代码块 一.创建小程序 访问微信公众平台,点击账号注册. 选择小程序,并在表单填写所需的各项信息进 ...

  6. HBuilder制作英雄皮肤抽奖小游戏

    用HBuilder制作英雄皮肤抽奖小游戏,主要用到"轮播图"和"定时器",至于"轮播图"和"定时器" 是什么,请自行百度 ...

  7. python +pygame 制作五子连珠小游戏

    python +pygame 制作五子连珠小游戏 学习python半年了,今天分享一个利用pygame制作的五子连珠游戏. 一.代码: 1.球类,ball.py """ ...

  8. Appgamekit制作消消乐小游戏(附代码)# 1

    Appgamekit制作消消乐小游戏(附代码)# 1 其实作者我也是刚刚才接触的Appgamekit,而且以前我是学C/C++的,所以我学的东西拿来这里就只有代码的结构思路会清晰一点了.(但是思路其实 ...

  9. 利用switch语句制作的抽卡小游戏。

    标题:利用switch语句制作的抽卡小游戏. 输出效果: 代码部分: 基本只用了switch的嵌套来完成 //该游戏是使用switch语句开发的数字小游戏,作者:initial.M //抽卡游戏规则介 ...

最新文章

  1. mysql 修复_修复崩溃的Mysql
  2. python免安装环境 linux_python如何打包脚本(库也一起打包),直接在linux环境运行,不需要安装库?...
  3. 双线路切换-冗余备份实验
  4. split python
  5. 实现RTP协议的H.264视频传输系统
  6. uva 1632——Alibaba
  7. python import无法导入该脚本的父目录下的另一个子目录的模块
  8. php打包命令,PHP调用Linux的命令行执行文件压缩命令
  9. MySQL-第十四篇事务管理
  10. Gaussview保存文件时,对话框一直弹不出来的原因
  11. ObjectARX如何打开一个dwg文件
  12. 淘宝客赚钱方式及怎么入门和推广引流详解
  13. 智能家居-斐讯N1安装篇
  14. 如何使用swagger的API接口获取数据并且封装
  15. excel应用之合并单元格保留内容和替换软回车
  16. 聚类算法(4)--Hierarchical clustering层次聚类
  17. python 两个word文档之间的相似度_如何用 word2vec 计算两个句子之间的相似度?
  18. 专访傲游CEO陈明杰:为何微软对IE8“又爱又恨”
  19. 电信保温杯笔记——《统计学习方法(第二版)——李航》第17章 潜在语义分析
  20. python实现微信聊天机器人

热门文章

  1. we learn考试能切屏吗_2020年成人高考考试小技巧速看!
  2. Lunix安装Nginx
  3. ERP项目如期进行的五个要素
  4. taobao.trade.ordersku.update( 更新交易的销售属性 )
  5. KEIL设置使用外部编辑器进行代码编辑(以Sublime Text 3为例)
  6. MybatisPlus分页查询
  7. 免费中文分词系统与资源收集
  8. 视频怎么转音频mp3
  9. 《紫林MP3歌词管家》 Beta版 V0.1
  10. Mysql日期查询大全