Unity3D游戏编程-牧师与恶魔
前置说明
牧师与恶魔
牧师与恶魔需要从岸的一端到达另一端,河上只有一条船,一条船只能坐两个角色,并且至少需要一个角色在船上船才可以行驶。并且,如果在某一侧(包括岸上和船上),恶魔的数量大于牧师的数量,牧师就会被恶魔吃掉(如果仅有恶魔则无事发生),游戏失败。玩家要安排牧师与恶魔的过河顺序,让牧师与恶魔全部到达另一边岸上,才能游戏通关。
MVC架构
MVC是界面人机交互程序设计的一种架构模式。它把程序分为三个部分:
- 模型(Model):数据对象及关系
· 游戏对象、空间关系 - 控制器(Controller):接受用户事件,控制模型的变化
· 一个场景一个主控制器 · 至少实现与玩家交互的接口(IPlayerAction) · 实现或管理运动 - 界面(View):显示模型,将人机交互事件交给控制器处理
· 处收Input 事件 · 渲染GUI ,接收事件
例子: 应用程序浏览网页,是一个把信息拉取下来并显示到页面上的过程,页面就是view,拉取信息的类就是model,当用户进行了点击后,负责通知model去拉去信息,并且通知view进行显示的就是controller。
而基于职责的设计与游戏的MVC总体框架包括:
- 导演,1名(仅要一个)
·类型:SSDriector ·职责:把握全局;控制场景 - 场记若干,话剧有很多场,每场需要一个。
·抽象类型(角色):ISceneController ·具体类型:FirstController ·职责:第一场的场记,控制布景、演员的上下场、管理动作等执行 - 吃瓜群众,1个
· 抽象类型:IUserAction · 具体类型:IUserAction · 职责:门面,与controller沟通
两种结合,导演和场记相当于controller,游戏中的object就是model,GUI就是view。
作业要求
完成牧师与恶魔游戏,程序需要满足要求: · play the game · 列出游戏中提及的事物(Objects) · 用表格列出玩家动作表(规则表) · 将游戏中对象做成预制 · 在 GenGameObjects 中创建长方形、正方形、 及其色彩代表游戏中的对象。 · 使用C#集合类型有效组织对象 · 整个游戏仅主摄像机和一个Empty对象,其他对象必须代码动态生成。整个游戏不许出现Find游戏对象,SendMessage这类突破程序结构的通讯耦合语句。 · 使用课件架构图编程,不接受非MVC结构程序 · 船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件
项目配置
Windows 10 Unity 2020.3.17f1c1
Object
游戏中主要包括的Object有以下:
玩家动作(规则表)
动作 | 条件 | 效果 |
---|
玩家点击岸上角色 | 船上有空位 | 角色上船 | 玩家点击船上角色 | | 角色上岸 | 玩家点击船 | 船上有至少一个角色 | 开船 | 玩家点击restart | | 重新开始游戏 |
项目演示
视频演示
点击此处可以前往
项目下载
下载Assets文件夹,项目代码在Assets>Scripts中 点击此处可以前往gitee
文字说明
- 创建unity专案后,将保存的文件夹中的Assets替换成在上面项目下载的Assets文件夹
- 打开专案,然后点选Assets中的ass加载场景
- 运行即可开始游戏
- 白色长方体为牧师,红色圆球为恶魔
项目截图
实现过程和方法(算法)
Director
首先是Director的部分,Director的建立基本是默认的:
public class Director : System.Object {
private static Director _instance;
public SceneController currentSceneController { get; set; }
public static Director getInstance() {
if (_instance == null) {
_instance = new Director ();
}
return _instance;
}
}
这个Director是单例模式并且是懒汉模式的,因此如果在程序的其他地方进行调用getInstance,得到的_instance会是同一个,就可以实现类之间的通信。
- 单例模式:一些东西在整个游戏中只有一个,而又想可以方便地随时访问它,这时就可以考虑单例模式。单例模式保证应用只有全局惟一的实例,并且提供一个访问它的全局访问点。单例包含一个静态方法,叫做getInstance(),调用这个静态方法,它就立刻现身,随时可以工作。
- 懒汉模式:在启动时并不创建单例对象,仅在调用getInstance()的时候才创建。程序运行中,调用Director.getInstance会判断_instance是否为空。如果为空,就创建一个返回。如果不为空,说明之前已经创建过了,直接返回。
Controller
然后是场记的部分,对于不同的对象设置不同的Controller:
SceneController
Director中有:
public SceneController currentSceneController { get; set; }
而SceneController是一个接口:
public interface SceneController {
void loadResources ();
}
SceneController是导演Director控制场景控制器的渠道,Director可以调用SceneController接口中的方法。而实现SceneController的currentSceneController类,在FirstSceneController中就会被定义为FirstSceneController本身,loadResources需要在FirstSceneController中实现。
Moveable
(程序在BaseCode.cs中的31行开始) Moveable是为了给角色对象、船只移动而准备的类,因此需要先写。 update主要利用MoveTowards来使得对象移动到目的位置。 当角色对象控制器和船只控制器需要移动某一个对象时,可以调用setDestination函数来进行移动。 Moveable中还设置了reset用于重新开始,初始化游戏对象的移动状态。
CharacterController
(程序在BaseCode.cs中的73行开始) MyCharacterController类主要用于控制游戏中的牧师与恶魔,其构造函数会根据传入的字符串来创建牧师/恶魔对象,并且给他们每一个对象挂载ClickGUI函数用于判断用户点击,由于角色对象会运动,所以也需要挂载Moveable类。 类中还有其他可调用的函数:
- setName设置名称(如“牧师1/恶魔1”)
- setPosition设置对象位置
- moveToPosition设置对象移动到目标位置,调用moveable的setDestination
- getType返回对象的类型(如“牧师”、“恶魔”)
- getName返回对象的名称(如“牧师1/恶魔1”)
- getOnBoat对象上船,并且修改对象的状态(_isOnBoat)
- getOnCoast对象下船,并且修改对象的状态(_isOnBoat)
- isOnBoat返回对象_isOnBoat的状态
- getCoastController返回岸边控制器
- reset用于重新初始化
CoastController
(程序在BaseCode.cs中的148行开始) CoastController用于控制河岸对象,而河岸因为要用于判断获胜条件,也需要像角色对象分为牧师与恶魔一样,分成此岸和对岸(from和to)。 因为河岸需要给角色对象站立,游戏中的角色对象总共有六个,因此在构造函数需要构造六个空对象,并且根据传入的字符串,标记此次创建的是此岸还是对岸。 其他函数:
- getEmptyIndex获取岸上的空位置的下标,passengerPlaner是用来记录在一个河岸上的角色位置,如果为NULL表示该位置为空没有角色站立在此。
- getEmptyPosition获取岸上的空位置
- getOnCoast有角色下船,修改对应的岸上状态
- getOffCoast有角色上船,修改对应的岸上状态
- get_to_or_from返回这个河岸是此岸还是对岸
- getCharacterNum返回当前河岸牧师与恶魔的数量
- reset重置
BoatController
(程序在BaseCode.cs中的230行开始) BoatController用于控制船只,因为船只会移动到两个岸边,因此它的状态也分成在此岸或对岸(from和to)。 因为船上拥有两个位置,因此需要构造两个空对象;并且由玩家点击船只行驶,需要挂载ClickGUI类判断用户点击;由于船只会运动,所以也需要挂载Moveable类。 其他函数:
- Move控制船只在两岸中移动
- getEmptyIndex返回船上空余位置下标
- getEmptyPosition返回船上空余位置
- isEmpty返回船只是否空的
- GetOnBoat有角色上船,修改船的属性
- GetOffBoat角色下船,修改船的属性
- getGameobj返回船本体的游戏对象
- get_to_or_from返回船只当前所在河岸
- getCharacterNum返回船上牧师和恶魔的数量
- reset重置船只
UserAction
UserAction
UserAction是门面模式,与用户交互相关的,与玩家动作有关:
public interface UserAction {
void moveBoat();
void characterIsClicked(MyCharacterController characterCtrl);
void restart();
}
这里就包括了移动船只,角色被点击,游戏重新开始这三个动作,UserAction接收到UI传来的信号,告知FirstSceneController调用对应动作的函数作出反应。而UserAction里面的函数就要在FirstSceneController中定义。而ClickGUI与UserGUI则通过UserAction这个门面接口来与FirstSceneController打交道。
- 门面模式:定义一个用户动作接口,实现用户行为与游戏系统规则计算的分离,称这个接口就是游戏逻辑与用户之间交互的门面(Fasàde)。
ClickGUI
ClickGUI是用以判断用户点击了游戏对象(牧师、恶魔、船)的类
public class ClickGUI : MonoBehaviour {
UserAction action;
MyCharacterController characterController;
public void setController(MyCharacterController characterCtrl) {
characterController = characterCtrl;
}
void Start() {
action = Director.getInstance ().currentSceneController as UserAction;
}
void OnMouseDown() {
if (gameObject.name == "boat") {
action.moveBoat ();
} else {
action.characterIsClicked (characterController);
}
}
}
ClickGUI判断被鼠标点击的对象是谁来进行下一步操作,调用UserAction(action)的接口来完成功能。
UserGUI
UserGUI是用于当游戏结束(失败或者成功)时显示说明文字和重玩游戏按钮的。 游戏状态分别为:0-游戏进行中,1-游戏失败,2-游戏通关。
FirstController
FirstController就要负责实现loadResources、moveBoat、characterIsClicked、restart函数。
- Awake函数是负责游戏的初始化加载
- loadResources是从Aseets中取出对象,生成水面、船只、两个河岸,以及调用loadCharacter生成牧师和恶魔。
- loadCharacter负责在出发的河岸上生成三个牧师和三个恶魔。
- moveBoat检测船只上的人员是否为空,如果不是的话则可以移动船只,并且判断游戏的状态(是不是gameover)
- characterIsClicked检查被点击的对象,
若处于船上:取得船在哪个岸边、人物下船、调用函数修改游戏属性(包括船、河岸); 若处于岸上:取得人物所在哪个岸边、判断船还有没有空位、判断船和人物是否处于同一侧岸、调用函数修改游戏属性(包括船、河岸); 最后也要判断游戏的状态(是不是gameover)。 - check_game_over就是数出发岸和目标岸(包括船上)各自的牧师与恶魔数量,如果目标岸的角色总数=6,则游戏通关(状态为2);如果某一个岸恶魔的数量大于牧师,则游戏失败(状态为1);其余情况游戏继续(状态为0);
- restart重置游戏,分别调用各个对象自己的reset。
参考资料
1.学习Unity(5)小游戏实例——牧师与魔鬼 2.Unity中的单例模式 3.Unity3d-learning 牧师与恶魔
|