牧师与魔鬼_MVC架构
游戏介绍
牧师和魔鬼是一款益智游戏,您将在其中帮助牧师和魔鬼过河。河的一侧有3个祭司和3个魔鬼。他们都想去这条河的另一边,但只有一条船,这条船每次只能载两个人。而且必须有一个人将船从一侧驾驶到另一侧。您可以单击按钮来移动它们,然后单击移动按钮将船移动到另一个方向。如果靠岸的船上和同一侧岸上的牧师被岸上的魔鬼人数所淹没,他们就会被杀死,游戏就结束了。您可以通过多种方式尝试它。让所有的祭司活着!最后所有牧师和魔鬼都成功过河,则表示游戏胜利
游戏实现效果
参考小破站视频:
unity开发_牧师与魔鬼小游戏_哔哩哔哩_bilibili
MVC架构解读
MVC模式是软件工程中常见的一种软件架构模式,该模式把软件系统(项目)分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
使用MVC模式有很多优势,例如:简化后期对项目的修改、扩展等维护操作;使项目的某一部分变得可以重复利用;使项目的结构更加直观。
具体来讲,MVC模式可以将项目划分为模型(M)、视图(V)和控制器(C)三个部分,并赋予各个部分不同的功能,方便开发人员进行分组。
(1)视图(View):负责界面的显示,以及与用户的交互功能,例如表单、网页等。
(2)控制器(Controller):可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型。
实际开发中,通常用控制器对客户端的请求数据进行封装(如将form表单发来的若干个表单字段值,封装到一个实体对象中),然后调用某一个模型来处理此请求,最后再转发请求(或重定向)到视图(或另一个控制器)。
(3)模型(Model):模型持有所有的数据、状态和程序逻辑。模型接受视图数据的请求,并返回最终的处理结果。
MVC架构UML图
整体的代码组织架构按照以下UML图
核心代码分析
Moveable类
public class Moveable: MonoBehaviour {
readonly float move_speed = 20;
// change frequently
int moving_status; // 0->not moving, 1->moving to middle, 2->moving to dest
Vector3 dest;
Vector3 middle;
/// <summary>
/// 更新船的状态
/// </summary>
void Update() {
if (moving_status == 1) {
transform.position = Vector3.MoveTowards (transform.position, middle, move_speed * Time.deltaTime);
if (transform.position == middle) {
moving_status = 2;
}
} else if (moving_status == 2) {
transform.position = Vector3.MoveTowards (transform.position, dest, move_speed * Time.deltaTime);
if (transform.position == dest) {
moving_status = 0;
}
}
}
public void setDestination(Vector3 _dest) {
dest = _dest;
middle = _dest;
if (_dest.y == transform.position.y) { // boat moving
moving_status = 2;
}
else if (_dest.y < transform.position.y) { // character from coast to boat
middle.y = transform.position.y;
} else { // character from boat to coast
middle.x = transform.position.x;
}
moving_status = 1;
}
public void reset() {
moving_status = 0;
}
}
move_speed
:浮点型常量,表示移动速度。moving_status
:整型变量,表示移动状态。0表示不移动,1表示向中间位置移动,2表示向目标位置移动。dest
:Vector3类型变量,表示目标位置。middle
:Vector3类型变量,表示中间位置。
Update()
方法在每一帧被调用,用于更新物体的状态。根据moving_status
的值进行不同的移动操作:
- 如果
moving_status
为1,将物体逐渐移动到中间位置middle
。使用Vector3.MoveTowards()
方法按照指定的速度移动物体。如果物体达到中间位置,则将moving_status
设置为2。 - 如果
moving_status
为2,将物体逐渐移动到目标位置dest
。同样使用Vector3.MoveTowards()
方法移动物体。如果物体达到目标位置,则将moving_status
设置为0。
setDestination()
方法用于设置物体的目标位置。将传入的目标位置赋值给dest
和middle
。根据目标位置的y坐标和物体当前位置的y坐标的比较,确定物体是从岸边移动到船上,还是从船上移动到岸边。根据情况设置middle
的y坐标或x坐标,并将moving_status
设置为1或2,表示开始移动。
reset()
方法用于重置物体的移动状态,将moving_status
设置为0,表示停止移动。
MyCharacterController类
public class MyCharacterController {
readonly GameObject character;
readonly Moveable moveableScript;
readonly ClickGUI clickGUI;
readonly int characterType; // 0->priest, 1->devil
// change frequently
bool _isOnBoat;
CoastController coastController;
public MyCharacterController(string which_character) {
if (which_character == "priest") {
character = Object.Instantiate (Resources.Load ("Perfabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
characterType = 0;
} else {
character = Object.Instantiate (Resources.Load ("Perfabs/Devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
characterType = 1;
}
moveableScript = character.AddComponent (typeof(Moveable)) as Moveable;
//创建clickGUI
clickGUI = character.AddComponent (typeof(ClickGUI)) as ClickGUI;
clickGUI.setController (this);
}
public void setName(string name) {
character.name = name;
}
public void setPosition(Vector3 pos) {
character.transform.position = pos;
}
public void moveToPosition(Vector3 destination) {
moveableScript.setDestination(destination);
}
public int getType() { // 0->priest, 1->devil
return characterType;
}
public string getName() {
return character.name;
}
public void getOnBoat(BoatController boatCtrl) {
coastController = null;
character.transform.parent = boatCtrl.getGameobj().transform;
_isOnBoat = true;
}
public void getOnCoast(CoastController coastCtrl) {
coastController = coastCtrl;
character.transform.parent = null;
_isOnBoat = false;
}
public bool isOnBoat() {
return _isOnBoat;
}
public CoastController getCoastController() {
return coastController;
}
public void reset() {
moveableScript.reset ();
coastController = (Director.getInstance ().currentSceneController as FirstController).fromCoast;
getOnCoast (coastController);
setPosition (coastController.getEmptyPosition ());
coastController.getOnCoast (this);
}
}
character
:GameObject类型的变量,表示角色的游戏对象。
moveableScript
:Moveable类型的变量,表示角色的移动脚本。
clickGUI
:ClickGUI类型的变量,表示角色的点击交互脚本。
characterType
:整型变量,表示角色的类型。0表示牧师,1表示恶魔。
_isOnBoat
:布尔型变量,表示角色是否在船上。
coastController
:CoastController类型的变量,表示角色所在的岸边控制器。
MyCharacterController(string which_character)
:构造函数,根据输入的角色类型创建对应的游戏对象,并设置相关属性和组件。
setName(string name)
:设置角色的名称。
setPosition(Vector3 pos)
:设置角色的位置。
moveToPosition(Vector3 destination)
:将角色移动到指定的目标位置。调用moveableScript
的setDestination()
方法设置目标位置。
getType()
:获取角色的类型。
getName()
:获取角色的名称。
getOnBoat(BoatController boatCtrl)
:将角色放置在船上。将角色的父对象设置为船的游戏对象,设置_isOnBoat
为true
。
getOnCoast(CoastController coastCtrl)
:将角色放置在岸边。将角色的父对象设置为null
,设置_isOnBoat
为false
。
isOnBoat()
:检查角色是否在船上,返回_isOnBoat
的值。
getCoastController()
:获取角色所在的岸边控制器。
reset()
:重置角色的状态和位置。调用moveableScript
的reset()
方法,将角色放置在初始岸边,并设置位置、添加到岸边的角色列表
CoastController类
public class CoastController {
readonly GameObject coast;
readonly Vector3 from_pos = new Vector3(9,1,0);
readonly Vector3 to_pos = new Vector3(-9,1,0);
readonly Vector3[] positions;
readonly int to_or_from; // to->-1, from->1// change frequently
MyCharacterController[] passengerPlaner;
public CoastController(string _to_or_from) {
positions = new Vector3[] {new Vector3(6.5F,2.25F,0), new Vector3(7.5F,2.25F,0), new Vector3(8.5F,2.25F,0),
new Vector3(9.5F,2.25F,0), new Vector3(10.5F,2.25F,0), new Vector3(11.5F,2.25F,0)};
passengerPlaner = new MyCharacterController[6];
if (_to_or_from == "from") {
coast = Object.Instantiate (Resources.Load ("Perfabs/Stone", typeof(GameObject)), from_pos, Quaternion.identity, null) as GameObject;
coast.name = "from";
to_or_from = 1;
} else {
coast = Object.Instantiate (Resources.Load ("Perfabs/Stone", typeof(GameObject)), to_pos, Quaternion.identity, null) as GameObject;
coast.name = "to";
to_or_from = -1;
}
}
public int getEmptyIndex() {
for (int i = 0; i < passengerPlaner.Length; i++) {
if (passengerPlaner [i] == null) {
return i;
}
}
return -1;
}
public Vector3 getEmptyPosition() {
Vector3 pos = positions [getEmptyIndex ()];
pos.x *= to_or_from;
return pos;
}
public void getOnCoast(MyCharacterController characterCtrl) {
int index = getEmptyIndex ();
passengerPlaner [index] = characterCtrl;
}
public MyCharacterController getOffCoast(string passenger_name) { // 0->priest, 1->devil
for (int i = 0; i < passengerPlaner.Length; i++) {
if (passengerPlaner [i] != null && passengerPlaner [i].getName () == passenger_name) {
MyCharacterController charactorCtrl = passengerPlaner [i];
passengerPlaner [i] = null;
return charactorCtrl;
}
}
Debug.Log ("cant find passenger on coast: " + passenger_name);
return null;
}
public int get_to_or_from() {
return to_or_from;
}
public int[] getCharacterNum() {
int[] count = {0, 0};
for (int i = 0; i < passengerPlaner.Length; i++) {
if (passengerPlaner [i] == null)
continue;
if (passengerPlaner [i].getType () == 0) { // 0->priest, 1->devil
count[0]++;
} else {
count[1]++;
}
}
return count;
}
public void reset() {
passengerPlaner = new MyCharacterController[6];
}
}
coast
:GameObject类型的变量,表示岸边的游戏对象。
from_pos
:Vector3类型的变量,表示岸边的起始位置。
to_pos
:Vector3类型的变量,表示岸边的目标位置。
positions
:Vector3数组,表示岸边上的空位位置。
to_or_from
:整型变量,表示岸边的类型。-1表示目标位置(to),1表示起始位置(from)。
passengerPlaner
:MyCharacterController类型的数组,表示岸边上的角色。
CoastController(string _to_or_from)
:构造函数,根据输入的岸边类型创建对应的游戏对象,并设置相关属性和位置。
getEmptyIndex()
:获取岸边上的空位索引。遍历passengerPlaner
数组,找到第一个为空的索引,如果找不到则返回-1。
getEmptyPosition()
:获取岸边上的空位位置。通过调用getEmptyIndex()
获取空位索引,然后根据索引从positions
数组中获取对应的位置。根据to_or_from
的值乘以位置的x坐标,以适应不同岸边的位置。
getOnCoast(MyCharacterController characterCtrl)
:角色上岸。将角色控制器添加到passengerPlaner
数组中的空位上。
getOffCoast(string passenger_name)
:角色下岸。根据角色名称在passengerPlaner
数组中查找对应的角色控制器,并将其移出数组并返回。
get_to_or_from()
:获取岸边的类型。
getCharacterNum()
:获取岸边上牧师和恶魔的数量。遍历passengerPlaner
数组,计算牧师和恶魔的数量并返回一个整型数组。
reset()
:重置岸边的状态。将passengerPlaner
数组重置为空。
BoatController类
public class BoatController {
readonly GameObject boat;
readonly Moveable moveableScript;
readonly Vector3 fromPosition = new Vector3 (5, 1, 0);
readonly Vector3 toPosition = new Vector3 (-5, 1, 0);
readonly Vector3[] from_positions;
readonly Vector3[] to_positions;
// change frequently
int to_or_from; // to->-1; from->1
MyCharacterController[] passenger = new MyCharacterController[2];
public BoatController() {
to_or_from = 1;
from_positions = new Vector3[] { new Vector3 (4.5F, 1.5F, 0), new Vector3 (5.5F, 1.5F, 0) };
to_positions = new Vector3[] { new Vector3 (-5.5F, 1.5F, 0), new Vector3 (-4.5F, 1.5F, 0) };
boat = Object.Instantiate (Resources.Load ("Perfabs/Boat", typeof(GameObject)), fromPosition, Quaternion.identity, null) as GameObject;
boat.name = "boat";
moveableScript = boat.AddComponent (typeof(Moveable)) as Moveable;
boat.AddComponent (typeof(ClickGUI));
}
public void Move() {
if (to_or_from == -1) {
moveableScript.setDestination(fromPosition);
to_or_from = 1;
} else {
moveableScript.setDestination(toPosition);
to_or_from = -1;
}
}
public int getEmptyIndex() {
for (int i = 0; i < passenger.Length; i++) {
if (passenger [i] == null) {
return i;
}
}
return -1;
}
public bool isEmpty() {
for (int i = 0; i < passenger.Length; i++) {
if (passenger [i] != null) {
return false;
}
}
return true;
}
public Vector3 getEmptyPosition() {
Vector3 pos;
int emptyIndex = getEmptyIndex ();
if (to_or_from == -1) {
pos = to_positions[emptyIndex];
} else {
pos = from_positions[emptyIndex];
}
return pos;
}
public void GetOnBoat(MyCharacterController characterCtrl) {
int index = getEmptyIndex ();
passenger [index] = characterCtrl;
}
public MyCharacterController GetOffBoat(string passenger_name) {
for (int i = 0; i < passenger.Length; i++) {
if (passenger [i] != null && passenger [i].getName () == passenger_name) {
MyCharacterController charactorCtrl = passenger [i];
passenger [i] = null;
return charactorCtrl;
}
}
Debug.Log ("Cant find passenger in boat: " + passenger_name);
return null;
}
public GameObject getGameobj() {
return boat;
}
public int get_to_or_from() { // to->-1; from->1
return to_or_from;
}
public int[] getCharacterNum() {
int[] count = {0, 0};
for (int i = 0; i < passenger.Length; i++) {
if (passenger [i] == null)
continue;
if (passenger [i].getType () == 0) { // 0->priest, 1->devil
count[0]++;
} else {
count[1]++;
}
}
return count;
}
public void reset() {
moveableScript.reset ();
if (to_or_from == -1) {
Move ();
}
passenger = new MyCharacterController[2];
}
}
boat
:GameObject类型的变量,表示船只的游戏对象。moveableScript
:Moveable类型的变量,用于控制船只的移动。fromPosition
:Vector3类型的变量,表示船只的起始位置。toPosition
:Vector3类型的变量,表示船只的目标位置。from_positions
:Vector3数组,表示船只上的起始位置。to_positions
:Vector3数组,表示船只上的目标位置。to_or_from
:整型变量,表示船只的位置。-1表示目标位置(to),1表示起始位置(from)。passenger
:MyCharacterController类型的数组,表示船只上的角色。BoatController()
:构造函数,创建船只的游戏对象,并设置相关属性和位置。同时添加Moveable和ClickGUI组件。Move()
:移动船只。根据当前船只的位置,设置移动目的地为起始位置或目标位置,然后更新船只的位置。getEmptyIndex()
:获取船只上的空位索引。遍历passenger
数组,找到第一个为空的索引,如果找不到则返回-1。isEmpty()
:检查船只是否为空。遍历passenger
数组,如果找到任何非空的角色则返回false,否则返回true。getEmptyPosition()
:获取船只上的空位位置。根据空位索引和船只的位置,从对应的位置数组(from_positions
或to_positions
)中获取空位位置。GetOnBoat(MyCharacterController characterCtrl)
:角色上船。将角色控制器添加到passenger
数组中的空位上。GetOffBoat(string passenger_name)
:角色下船。根据角色名称在passenger
数组中查找对应的角色控制器,并将其移出数组并返回。getGameobj()
:获取船只的游戏对象。get_to_or_from()
:获取船只的位置。getCharacterNum()
:获取船只上牧师和恶魔的数量。遍历passenger
数组,计算牧师和恶魔的数量并返回一个整型数组。reset()
:重置船只的状态。重置船只的移动位置、角色数组,并将船只移动到起始位置(如果当前在目标位置)。
characterIsClicked类
public void characterIsClicked(MyCharacterController characterCtrl) {
if (characterCtrl.isOnBoat ()) {
CoastController whichCoast;
if (boat.get_to_or_from () == -1) { // to->-1; from->1
whichCoast = toCoast;
} else {
whichCoast = fromCoast;
}
boat.GetOffBoat (characterCtrl.getName());
characterCtrl.moveToPosition (whichCoast.getEmptyPosition ());
characterCtrl.getOnCoast (whichCoast);
whichCoast.getOnCoast (characterCtrl);
} else { // character on coast
CoastController whichCoast = characterCtrl.getCoastController ();
if (boat.getEmptyIndex () == -1) { // boat is full
return;
}
if (whichCoast.get_to_or_from () != boat.get_to_or_from ()) // boat is not on the side of character
return;
whichCoast.getOffCoast(characterCtrl.getName());
characterCtrl.moveToPosition (boat.getEmptyPosition());
characterCtrl.getOnBoat (boat);
boat.GetOnBoat (characterCtrl);
}
u
这段代码是处理角色点击事件的方法。根据角色当前的状态(在船上或者在岸上),采取不同的操作:
- 如果角色在船上(
isOnBoat()
返回true
),则执行以下操作:- 根据船的位置确定目标岸边的
CoastController
对象,将其赋值给whichCoast
变量。 - 调用船的
GetOffBoat()
方法,将该角色从船上移除。 - 调用角色的
moveToPosition()
方法,将其移动到目标岸边的空位上。 - 调用角色的
getOnCoast()
方法,将其放置在目标岸边。 - 调用目标岸边的
getOnCoast()
方法,将该角色添加到目标岸边的角色列表中。
- 根据船的位置确定目标岸边的
- 如果角色在岸上,则执行以下操作:
- 获取角色所在的岸边的
CoastController
对象,将其赋值给whichCoast
变量。 - 如果船已满(
getEmptyIndex()
返回-1),则不进行操作,直接返回。 - 如果船不在角色所在的岸边上(
whichCoast.get_to_or_from() != boat.get_to_or_from()
),则不进行操作,直接返回。 - 调用角色所在岸边的
getOffCoast()
方法,将该角色从岸上移除。 - 调用角色的
moveToPosition()
方法,将其移动到船上的空位上。 - 调用角色的
getOnBoat()
方法,将其放置在船上。 - 调用船的
GetOnBoat()
方法,将该角色添加到船上。
- 获取角色所在的岸边的
在以上操作完成后,最后更新游戏界面的状态(userGUI.status
)并检查游戏是否结束(check_game_over()
)。
游戏界面展示
游戏初始界面
游戏成功
游戏失败
显示Gameover!