Unity-小游戏 牧师和恶魔
游戏介绍
当游戏开始时,三个牧师和三个恶魔都在河的一侧。游戏的目标是帮助他们渡河到达对岸,并且在任何一侧,牧师的数量不能少于恶魔的数量,否则恶魔会袭击牧师。游戏的规则如下:
角色和道具:
- 牧师(P): 游戏中有三个牧师,用P表示。
- 恶魔(D): 游戏中有三个恶魔,用D表示。
- 船(B): 游戏中有一艘船,用B表示。船的最大容量为两人。
初始状态:
- 初始时,三个牧师(PPP)和三个恶魔(DDD)以及船(B)都在河的一侧。
目标状态:
- 游戏的目标是将所有的牧师和恶魔都安全地带到河的另一侧,且在任何一侧恶魔的数量都不能多于牧师的数量(例如,在河的任意一侧,PD或PDD是合法状态,但DD是非法状态)。
移动规则:
- 船每次最多可以携带两个角色(牧师、恶魔或者两者混合)过河。
- 在任何一侧,恶魔的数量不能多于牧师的数量,否则恶魔会袭击牧师,游戏失败。
- 牧师和恶魔都可以操作船,但船上的角色数量不能超过两个。
游戏结束:
游戏结束有两种情况:
- 当所有的牧师和恶魔都安全地渡到了河的对岸,且符合条件(恶魔数量不多于牧师数量)时,游戏胜利。
- 如果在任何一侧,恶魔数量多于牧师数量,恶魔就会袭击牧师,游戏失败。
"牧师与恶魔"是一种经典的智力游戏,需要玩家根据规则巧妙地安排角色的位置和船的乘客,以确保所有的角色都能安全渡河,是一个考验逻辑思维和规划能力的游戏。
设计架构(MVC)
利用MVC(Model-View-Controller)思想,游戏结构更加清晰。
1. 类介绍
-
导演(Director)类:使用单实例模式实现,负责游戏全局设定、场景管理等工作。
-
场景经理(SceneManager)类:实现游戏模型管理与游戏逻辑的实现,符合MVC框架,将用户视图与游戏场景模型的逻辑分离。
-
人机交互(IUserAction)接口:描述游戏规则对应操作,定义了游戏中的用户交互行为。
2. MVC框架工作流程
-
导演对象设置:导演类负责游戏的全局设定,包括初始化游戏场景、角色等信息。
-
场景切换:场景经理负责管理游戏场景,包括切换不同的游戏场景,确保游戏流程的顺利进行。
-
用户操作处理:用户的操作由实现了
IUserAction
接口的类处理,该接口定义了游戏规则对应的操作,例如玩家移动、攻击等。
3. 优点
-
结构清晰:MVC框架使得游戏结构更加清晰,分离了数据模型、用户视图和控制逻辑。
-
模块化开发:不同的模块(Model、View、Controller)可以独立开发和测试,提高了代码的可维护性和可扩展性。
-
逻辑分离:通过MVC,实现了用户视图与游戏场景模型的逻辑分离,降低了耦合性。
-
统一的场景控制:建立了统一的场景控制接口,支持不同场景不同的业务逻辑,增加了灵活性。
具体设计方案如下所示:
具体代码实现
IUserAction
public interface IUserAction
{
// 移动船
void moveBoat();
// 处理角色点击事件
void characterIsClicked(MyCharacterController characterCtrl);
// 重新开始游戏
void restart();
}
接口IUserAction
定义了游戏操作的三个方法:moveBoat()
用于移动船,characterIsClicked(MyCharacterController characterCtrl)
用于处理角色点击事件,restart()
用于重新开始游戏。
ISceneController
// ISceneController 接口,定义了游戏场景控制器的方法契约。
public interface ISceneController
{
// 加载游戏资源的方法,由实现接口的类负责具体实现。
void loadResources();
}
代码解析:
-
ISceneController
是一个接口,定义了游戏场景控制器的契约。接口是一种抽象的数据类型,它只包含了方法、属性、事件或索引器的声明,没有提供方法的实现。接口提供了一种规范,让多个类能够按照相同的标准进行实现,从而实现代码的组织和协作。 -
loadResources()
方法的功能是加载游戏所需的资源。具体的实现将由实现了ISceneController
接口的类来完成。该方法允许游戏场景控制器在游戏开始时加载所需的资源,以便游戏能够正确地运行。
UserGUI
// UserGUI 类,继承自 MonoBehaviour,负责游戏界面的显示和用户输入的处理。
public class UserGUI : MonoBehaviour
{
// 用户操作接口。
private IUserAction action;
// 游戏状态,0 表示游戏进行中,1 表示游戏结束(失败),2 表示游戏结束(胜利)。
public int status = 0;
GUIStyle style;
GUIStyle buttonStyle;
// 在 Start 方法中进行初始化操作。
void Start()
{
// 获取用户操作接口。
action = SSDirector.getInstance().currentSceneController as IUserAction;
// 设置文本样式。
style = new GUIStyle();
style.fontSize = 40;
style.alignment = TextAnchor.MiddleCenter;
// 设置按钮样式。
buttonStyle = new GUIStyle("button");
buttonStyle.fontSize = 30;
}
// 在 OnGUI 方法中绘制游戏界面和处理用户输入。
void OnGUI()
{
// 根据游戏状态显示不同的界面。
if (status == 0)
{
// 游戏进行中,显示“Restart”按钮,点击按钮可以重新开始游戏。
if (GUI.Button(new Rect(Screen.width - 160, 10, 140, 70), "Restart", buttonStyle))
{
status = 0;
action.restart();
}
}
else if (status == 1)
{
// 游戏结束(失败),显示“Gameover!”文本和“Restart”按钮,点击按钮可以重新开始游戏。
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 85, 100, 50), "Gameover!", style);
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart", buttonStyle))
{
status = 0;
action.restart();
}
}
else if (status == 2)
{
// 游戏结束(胜利),显示“You win!”文本和“Restart”按钮,点击按钮可以重新开始游戏。
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 85, 100, 50), "You win!", style);
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart", buttonStyle))
{
status = 0;
action.restart();
}
}
}
}
代码解析:
-
UserGUI
类是一个继承自MonoBehaviour
的类,用于处理游戏界面的显示和用户输入。 -
action
变量是用来存储用户操作接口的实例,通过SSDirector.getInstance().currentSceneController
获取。 -
status
变量表示游戏状态,0 表示游戏进行中,1 表示游戏结束(失败),2 表示游戏结束(胜利)。 -
在
Start()
方法中进行初始化操作,设置了文本和按钮的样式。 -
OnGUI()
方法用于在游戏界面上绘制文本和按钮,并处理用户的点击事件。根据游戏状态的不同,显示不同的界面元素。 -
如果游戏状态是进行中(
status == 0
),则显示“Restart”按钮,点击按钮可以重新开始游戏。 -
如果游戏状态是失败(
status == 1
),则显示“Gameover!”文本和“Restart”按钮,点击按钮可以重新开始游戏。 -
如果游戏状态是胜利(
status == 2
),则显示“You win!”文本和“Restart”按钮,点击按钮可以重新开始游戏。
SSDirector
// SSDirector 类,继承自 System.Object,实现了单例模式,用于管理当前场景的场景控制器。
public class SSDirector : System.Object
{
// 静态变量,用于保存唯一的 SSDirector 实例。
private static SSDirector _instance;
// 当前场景的场景控制器,实现了 ISceneController 接口。
public ISceneController currentSceneController {
get; set; }
// 获取单例实例的静态方法。
public static SSDirector getInstance()
{
// 如果实例为空,创建一个新的 SSDirector 实例。
if (_instance == null)
{
_instance = new SSDirector();
}
// 返回唯一的实例。
return _instance;
}
}
代码解析:
-
SSDirector
类是一个单例类,用于管理当前场景的场景控制器。 -
currentSceneController
属性用于保存当前场景的场景控制器实例,该控制器类必须实现了ISceneController
接口。 -
getInstance()
方法是获取SSDirector
实例的静态方法。在该方法中,如果_instance
为空(即还没有实例化),就创建一个新的SSDirector
实例,然后返回这个实例。这样保证了在整个应用程序中,SSDirector
类只有一个实例存在,避免了多次实例化的开销,同时也保证了数据的一致性和安全性。 -
通过单例模式,
SSDirector
类提供了一个全局唯一的访问点,使得其他类可以通过SSDirector.getInstance()
来获取SSDirector
的实例,从而访问和管理当前场景的场景控制器。
FirstController
由于代码太多,下面抽取标志性部分展示
代码注释和文字分析:
// FirstController 类,继承自 MonoBehaviour,实现了 ISceneController 和 IUserAction 接口,
// 用于控制游戏场景的初始化、游戏规则和用户操作。
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
// 水面的位置。
readonly Vector3 water_pos = new Vector3(0, 0.5F, 0);
// 用户界面管理器。
UserGUI userGUI;
// 岸边的控制器。
public CoastController fromCoast;
public CoastController toCoast;
// 船的控制器。
public BoatController boat;
// 角色控制器数组。
private MyCharacterController[] characters;
// Awake 方法在对象被创建时调用,用于初始化数据。
void Awake()
{
// 获取场景总管,将当前场景控制器设置为当前场景。
SSDirector director = SSDirector.getInstance();
director.currentSceneController = this;
// 在游戏对象上添加 UserGUI 组件,用于管理用户界面。
userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
// 初始化角色控制器数组和加载游戏资源。
characters = new MyCharacterController[6];
loadResources();
}
// 检查游戏是否结束的方法,返回值:0->未结束, 1->失败, 2->胜利
int check_game_over()
{
// 统计各个岸上的牧师和魔鬼数量。
int from_priest = 0;
int from_devil = 0;
int to_priest = 0;
int to_devil = 0;
// 统计 fromCoast 上的角色数量。
int[] fromCount = fromCoast.getCharacterNum();
from_priest += fromCount[0];
from_devil += fromCount[1];
// 统计 toCoast 上的角色数量。
int[] toCount = toCoast.getCharacterNum();
to_priest += toCount[0];
to_devil += toCount[1];
// 如果 toCoast 上的牧师和魔鬼总数为 6,则游戏胜利。
if (to_priest + to_devil == 6)
return 2;
// 统计船上的角色数量。
int[] boatCount = boat.getCharacterNum();
if (boat.get_to_or_from() == -1)
{
// 船在 toCoast 上
to_priest += boatCount[0];
to_devil += boatCount[1];
}
else
{
// 船在 fromCoast 上
from_priest += boatCount[0];
from_devil += boatCount[1];
}
// 如果 fromCoast 上的牧师数量小于魔鬼数量并且有牧师,则游戏失败。
if (from_priest < from_devil && from_priest > 0)
{
// 失败
return 1;
}
// 如果 toCoast 上的牧师数量小于魔鬼数量并且有牧师,则游戏失败。
if (to_priest < to_devil && to_priest > 0)
{
return 1;
}
// 游戏未结束。
return 0;
}
// 重新开始游戏的方法,用于重置船和岸上的角色。
public void restart()
{
boat.reset();
fromCoast.reset();
toCoast.reset();
for (int i = 0; i < characters.Length; i++)
{
characters[i].reset();
}
}
}
代码解析:
-
FirstController
类是游戏场景的控制器,实现了ISceneController
和IUserAction
接口。 -
在
Awake()
方法中,获取场景总管SSDirector
的实例,并将当前场景控制器设置为当前场景。同时,初始化用户界面UserGUI
组件和角色控制器数组,并加载游戏资源。 -
check_game_over()
方法用于检查游戏是否结束。如果游戏胜利(toCoast 上的牧师和魔鬼总数为 6),返回 2;如果游戏失败(from
Coast 或 toCoast 上的牧师数量小于魔鬼数量并且有牧师),返回 1;否则,返回 0 表示游戏未结束。
restart()
方法用于重新开始游戏。在该方法中,重置船的状态和岸上的角色状态,使游戏回到初始状态。
开发总结
在开发这个“牧师与恶魔”游戏的过程中,我借鉴了很多网上的参考博客和代码学到了许多关于游戏开发的重要概念和技能。以下是开发总结的一些关键点:
1. 游戏设计与规划
- 游戏规则定义: 游戏规则需要明确定义,包括角色能力、游戏胜利条件和失败条件等。
- 角色设计: 游戏中的角色应该具有明确的身份和特性,使得游戏具有足够的深度和策略性。
2. 编程技能
- 接口与实现: 使用接口将游戏逻辑和用户操作分离,提高了代码的模块化和可维护性。
- 单例模式: 使用单例模式来管理游戏状态,确保在整个游戏生命周期内只有一个实例。
- GUI 编程: 学会使用 Unity 的 GUI 系统来创建游戏界面,包括按钮、标签等 UI 元素。
3. 逻辑思维与问题解决
- 游戏状态判断: 编写游戏逻辑,判断游戏是否胜利或失败,需要逻辑思维和问题解决能力。
- 游戏机制设计: 设计游戏规则和角色能力,需要深入思考游戏的平衡性和趣味性。
4. 用户体验与界面设计
- 用户界面设计: 设计用户友好的游戏界面,包括按钮的位置、字体的大小等,以提供良好的用户体验。
- 游戏反馈: 在游戏中提供适当的反馈,例如游戏胜利或失败时显示相应的提示信息,增加游戏的互动性。
视频展示
unity小游戏 牧师与恶魔
参考博客
http://t.csdnimg.cn/UbqPT
http://t.csdnimg.cn/T5rqW