untiy小游戏——牧师与魔鬼_MVC架构

发布时间 2023-10-20 12:10:20作者: 一指流沙zjh

牧师与魔鬼_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图

image

核心代码分析

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()方法用于设置物体的目标位置。将传入的目标位置赋值给destmiddle。根据目标位置的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):将角色移动到指定的目标位置。调用moveableScriptsetDestination()方法设置目标位置。

getType():获取角色的类型。

getName():获取角色的名称。

getOnBoat(BoatController boatCtrl):将角色放置在船上。将角色的父对象设置为船的游戏对象,设置_isOnBoattrue

getOnCoast(CoastController coastCtrl):将角色放置在岸边。将角色的父对象设置为null,设置_isOnBoatfalse

isOnBoat():检查角色是否在船上,返回_isOnBoat的值。

getCoastController():获取角色所在的岸边控制器。

reset():重置角色的状态和位置。调用moveableScriptreset()方法,将角色放置在初始岸边,并设置位置、添加到岸边的角色列表

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_positionsto_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

这段代码是处理角色点击事件的方法。根据角色当前的状态(在船上或者在岸上),采取不同的操作:

  1. 如果角色在船上(isOnBoat()返回true),则执行以下操作:
    • 根据船的位置确定目标岸边的CoastController对象,将其赋值给whichCoast变量。
    • 调用船的GetOffBoat()方法,将该角色从船上移除。
    • 调用角色的moveToPosition()方法,将其移动到目标岸边的空位上。
    • 调用角色的getOnCoast()方法,将其放置在目标岸边。
    • 调用目标岸边的getOnCoast()方法,将该角色添加到目标岸边的角色列表中。
  2. 如果角色在岸上,则执行以下操作:
    • 获取角色所在的岸边的CoastController对象,将其赋值给whichCoast变量。
    • 如果船已满(getEmptyIndex()返回-1),则不进行操作,直接返回。
    • 如果船不在角色所在的岸边上(whichCoast.get_to_or_from() != boat.get_to_or_from()),则不进行操作,直接返回。
    • 调用角色所在岸边的getOffCoast()方法,将该角色从岸上移除。
    • 调用角色的moveToPosition()方法,将其移动到船上的空位上。
    • 调用角色的getOnBoat()方法,将其放置在船上。
    • 调用船的GetOnBoat()方法,将该角色添加到船上。

在以上操作完成后,最后更新游戏界面的状态(userGUI.status)并检查游戏是否结束(check_game_over())。

游戏界面展示

游戏初始界面

image

游戏成功

image

游戏失败

显示Gameover!
image