文章目录
3D游戏编程与设计 HW 3.5 牧师与恶魔
完整游戏过程可见以下视频:
https://www.bilibili.com/video/BV1Vk4y117J2/
完整代码可见以下仓库:
https://gitee.com/beilineili/game3-d
1.游戏介绍
- 牧师和恶魔是一款益智游戏
- 你将帮助神父和恶魔在限定的时间内过河。河的一边有三个牧师和三个魔鬼。他们都想到河的另一边去,但是只有一条船,每次只能载两个人。必须有一个人把船从一边转向另一边。如果牧师们被河两岸的恶魔们打败(在岸上的牧师数量少于恶魔),他们就会被杀死,游戏也就结束了。
- 在游戏中,你可以点击它们来移动它们,点击go按钮来移动船到另一个方向。你可以用很多的方法来尝试。让所有的牧师活下去! 祝你好运!
2.题目要求
3.MVC结构
- MVC是模型(model)-视图(view)-控制器(controller)的缩写,它是一种软件设计模式,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
- MVC基本结构图:
- 在牧师与恶魔游戏设计中,可以分为
① SSDirector:整个结构的管理者,控制各个场景的切换协调;
② ISceneController(抽象):每个场景的管理者,是Director用来向场景控制器传递要求的接口
③ IUserAction(抽象):负责管理行为之间的交互,对于玩家的每个动作产生相应的响应结果 - 通过这样基于职责的设计,实现了赋予不同对象特定的职责,使游戏程序的结构更加清晰、可复用性更高。
4.游戏对象表,玩家动作表,事件表
游戏对象 | 对象类型 |
---|---|
Priest 牧师 | Cube |
Devil 魔鬼 | Sphere |
Boat 船 | Cube |
River 河流 | Cube |
Coast 河岸 | Cube |
动作 | 触发事件 |
---|---|
点击 Character 人物 | 人物上船,下船 |
点击 Boat 船只 | 船只过河 |
事件 | 结果 |
---|---|
其中一边恶魔数量大于牧师(包括船上和岸上的) | 玩家失败 |
所有牧师和恶魔都到了另一岸 | 玩家成功 |
5.类设计
基于职责设计,使用面向对象技术设计游戏。
项目的程序代码采用MVC结构,根据功能将代码文件分别放入Controllers, Models, Views文件夹中。
(一)Models
① Boat
- 有 location(船位置坐标)属性,有起点和终点,此外还需要维护船上的空位坐标以及角色信息
② Coast
- 同 Boat,需要维护岸上的空位坐标和角色信息
③ Character
- 有 name,location 和 isOnBoard 属性;其中 name 用于区分角色是牧师还是恶魔,IsOnBoat 用于区分是否在船上
(二)Views
① UserGUI
- Start 函数,通过获得 Director 的单例,初始化获得当前场记 CurrentSecnController
// 得到 GameController
void Start () {
status = 0;
action = Director.GetInstance().CurrentSecnController as IUserAction;
}
- OnGUI 函数,渲染游戏界面;
void OnGUI () {
textStyle = new GUIStyle {
fontSize = 40,
alignment = TextAnchor.MiddleCenter
};
hintStyle = new GUIStyle {
fontSize = 15,
fontStyle = FontStyle.Normal
};
btnStyle = new GUIStyle("button") {
fontSize = 30
};
// 打印标题和规则提示信息
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 195, 100, 50),
"Priest And Devil", textStyle);
GUI.Label(new Rect(Screen.width / 2 - 400, Screen.height / 2 - 125, 100, 50),
"White Cube: Periest\n Red Sphere: Devil\n\n" + rule, hintStyle);
// 打印游戏结果和重开游戏的按钮
if (status == 1) {
// Lose
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 85, 100, 50), "You Lose!", textStyle);
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart", btnStyle)) {
status = 0;
action.Restart();
}
} else if (status == 2) {
// Win
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 85, 100, 50), "You Win!", textStyle);
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart", btnStyle)) {
status = 0;
action.Restart();
}
}
}
- OnMouseDown 函数,监听捕捉用户的点击动作,转至 Controller 处理
void OnMouseDown() {
if (gameObject.name == "boat") {
action.MoveBoat();
} else {
action.CharacterClicked(characterCtrl);
}
}
(三)Controllers
① Director
- 获取当前游戏的场景
- 管理游戏全局状态
public class Director : System.Object
{
// Singlton instance.
private static Director instance;
public ISceneController CurrentSecnController { get; set; }
// get instance anytime anywhere!
public static Director GetInstance(){
if (instance == null){
instance = new Director();
}
return instance;
}
public int getFPS(){
return Application.targetFrameRate;
}
public void setFPS(int fps){
Application.targetFrameRate = fps;
}
}
② ISceneController
- 场记 GameController 与导演 Director 交互的接口
public interface ISceneController
{
void LoadResources();
}
③ IUserAction
- 玩家与 GameController 互动的用户交互接口
public interface IUserAction {
void MoveBoat();
void CharacterClicked(CharacterController characterCtrl);
void Restart();
}
④ Moveable
- SetDestination 用于控制对应游戏对象的运动
public void SetDestination(Vector3 pos) {
destination = pos;
middle = pos;
if (pos.y == transform.position.y) { // 移动船
status = 2;
} else if (pos.y < transform.position.y) { // 将角色从岸上移到船上
middle.y = transform.position.y;
} else { // 将角色从船上移到岸上
middle.x = transform.position.x;
}
status = 1;
}
⑤ GameController
- CoastController
GetEmptyIndex:岸上空位的下标
GetEmptyPosition:岸上空位的坐标
GetOnCoast:上岸
GetOffCoast:离岸
GetCharacterNum:计算岸上角色的数量 - BoatController
Move:移动船
GetEmptyIndex:船上空位的下标
IsEmpty:船是否有空位
GetEmptyPosition:船上空位的坐标
GetOnBoat:当玩家点击牧师或魔鬼,使之上船时,被调用。
GetOffBoat:当玩家点击牧师或魔鬼,使之上岸时,被调用。
GetCharacterNum:计算船上角色的数量 - CharacterController
配合 CoastController 和 BoatController
⑥ FirstController
- 在场景被加载唤醒(awake)时,它会自动注入导演,设置当前场景
void Awake() {
Director director = Director.GetInstance();
director.CurrentSecnController = this;
userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
characters = new MyNamespace.CharacterController[6];
LoadResources();
}
- CheckGameOver 判断游戏胜负
private int CheckGameOver() {
int rightPriest = 0;
int rightDevil = 0;
int leftPriest = 0;
int leftDevil = 0;
int status = 0;
rightPriest += rightCoastCtrl.GetCharacterNum()[0];
rightDevil += rightCoastCtrl.GetCharacterNum()[1];
leftPriest += leftCoastCtrl.GetCharacterNum()[0];
leftDevil += leftCoastCtrl.GetCharacterNum()[1];
// Win
if (leftPriest + leftDevil == 6) {
status = 2;
}
if (boatCtrl.boat.Location == Location.right) {
rightPriest += boatCtrl.GetCharacterNum()[0];
rightDevil += boatCtrl.GetCharacterNum()[1];
} else {
leftPriest += boatCtrl.GetCharacterNum()[0];
leftDevil += boatCtrl.GetCharacterNum()[1];
}
// Lose
if ((rightPriest < rightDevil && rightPriest > 0) ||
(leftPriest < leftDevil && leftPriest > 0)) {
status = 1;
}
return status;
}
6.游戏结果展示
完整游戏过程可见以下视频:
https://www.bilibili.com/video/BV1Vk4y117J2/