Unity3d--牧师与魔鬼

一.游戏基本规则

Priests and Devils
Priests and Devils is a puzzle game in which you will help the Priests and Devils to cross the river within the time limit. There are 3 priests and 3 devils at one side of the river. They all want to get to the other side of this river, but there is only one boat and this boat can only carry two persons each time. And there must be one person steering the boat from one side to the other side. In the flash game,you can click on them to move them and click the go button to move the devils on either side of the river, they get killed and the game is over. You can try it in many > ways. Keep all priests alive! Good luck!

要运用智慧帮助3个牧师(白色)和3个魔鬼(黑色)渡河。
船最多可以载2名游戏角色。
船上有游戏角色时,你才可以点击这个船,让船移动到对岸。
当有一侧岸的魔鬼数多余牧师数时(包括船上的魔鬼和牧师),魔鬼就会失去控制,吃掉牧师(如果这一侧没有牧师则不会失败),游戏失败。
当所有游戏角色都上到对岸时,游戏胜利。
游戏中提及的事物:魔鬼,牧师,船,河流,两边的陆地。

玩家动作执行条件运行结果
点击角色(牧师/魔鬼)船没有正在移动,与船在同侧陆地牧师/魔鬼移动到船/陆地上
点击船船上有至少一个牧师/魔鬼船移动到另一侧陆地

牧师与魔鬼UML图参考如下:
在这里插入图片描述
我们需要四个model controllor,分别为move,coast(land),character,boat。分别控制移动,陆地,角色(牧师/魔鬼)以及船只。
然后后是场景控制,也就是在游戏场景中控制实现发生,需要用到四个model controllor。
UserGUI和ClickGUI分别控制与用户的交互界面以及与用户点击的交互。需要用到UserAction的接口
最后两个接口UserAction以及SceneControllor分别是场景控制与用户动作的接口。
导演是最高层的控制器,运行游戏时始终只有一个实例,它掌控着场景的加载、切换等。在这次游戏中没有用到。
接着是MVC架构:
在这里插入图片描述

  • MVC是界面人机交互程序设计的一种架构模式。它把程序分为三个部分:

    模型(Model):数据对象及关系,游戏对象、空间关系
    控制器(Controller):接受用户事件,控制模型的变化,一个场景一个主控制器,至少实现与玩家交互的接口(IPlayerAction,实现或管理运动
    界面(View):显示模型,将人机交互事件交给控制器处理,处收 Input 事件,渲染 GUI ,接收事件。

场景中的所有GameObject就是Model,它们受到Controller的控制,比如说牧师和魔鬼受到MyCharacterController类的控制,船受到BoatController类的控制,河岸受到CoastController类的控制。
View就是UserGUI和ClickGUI,它们展示游戏结果,并提供用户交互的渠道(点击物体和按钮)。
Controller:除了刚才说的MyCharacterController、BoatController、CoastController以外,还有更高一层的Controller:FirstController(场景控制器),FirstController控制着这个场景中的所有对象,包括其加载、通信、用户输入。
最高层的Controller是Director类,一个游戏中只能有一个实例,它控制着场景的创建、切换、销毁、游戏暂停、游戏退出等等最高层次的功能。

接下来是实现过程。
我将实现分为六个部分,分别是点击交互,场景控制,接口,四个模型类,导演以及用户交互。
在这里插入图片描述
以下将根据这六部分阐述实现逻辑。

  • SSDirector

    利用单例模式创建导演,一个游戏导演只能有一个,这里继承于System.Object,保持导演类一直存在,不被Unity内存管理而管理,导演类类似于生活中的导演,安排场景,场景切换,都是靠它来指挥。

public class SSDirector : System.Object
{
	private static SSDirector _instance;
	public ISceneController CurrentScenceController { get; set; }
	public static SSDirector GetInstance()
	{
		if (_instance == null)
		{
			_instance = new SSDirector();
		}
		return _instance;
	}
}

  • UserGUI

    建立用户的交互界面,比如按钮和标签

public class UserGUI : MonoBehaviour {

	private IUserAction action;
	public int sign = 0;

	bool isShow = false;
	void Start()
	{
		action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
	}
	void OnGUI()
	{
		GUIStyle text_style;
		GUIStyle button_style;
		text_style = new GUIStyle()
		{
			fontSize = 30
		};
		button_style = new GUIStyle("button")
		{
			fontSize = 15
		};
		if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", button_style))
		{
			if (isShow)
				isShow = false;
			else
				isShow = true;
		}
		if(isShow)
		{
			GUI.Label(new Rect(Screen.width / 2 - 85, 10, 200, 50), "让全部牧师和恶魔都渡河");
			GUI.Label(new Rect(Screen.width / 2 - 120, 30, 250, 50), "每一边恶魔数量都不能多于牧师数量");
			GUI.Label(new Rect(Screen.width / 2 - 85, 50, 250, 50), "点击牧师、恶魔、船移动");
		}
		if (sign == 1)
		{
			GUI.Label(new Rect(Screen.width / 2-90, Screen.height / 2-120, 100, 50), "Gameover!", text_style);
			if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "Restart", button_style))
			{
				action.Restart();
				sign = 0;
			}
		}
		else if (sign == 2)
		{
			GUI.Label(new Rect(Screen.width / 2 - 80, Screen.height / 2 - 120, 100, 50), "You Win!", text_style);
			if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "Restart", button_style))
			{
				action.Restart();
				sign = 0;
			}
		}
	}
}
  • ClickGUI
    检测船和角色是否被点击
    点击则触发接口中的动作。然后进入控制器进行对应的函数操作。
public class Click : MonoBehaviour
{
	IUserAction action;
	RoleModel role = null;
	BoatModel boat = null;
	public void SetRole(RoleModel role)
	{
		this.role = role;
	}
	public void SetBoat(BoatModel boat)
	{
		this.boat = boat;
	}
	void Start()
	{
		action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
	}
	void OnMouseDown()
	{
		if (boat == null && role == null) return;
		if (boat != null)
			action.MoveBoat();
		else if(role != null)
			action.MoveRole(role);
	}
}
  • ModelControllor

    这是对于四个模型的控制,将这四个模型控制放在同一个命名空间下可以减少耦合,也方便其他模块调用。

    • move:用于控制角色和船的移动。
      让角色水平移动再垂直移动或是先垂直移动再实现角色水平移动,通过每一帧检测是否移动
public class Move : MonoBehaviour
	{
		float move_speed = 250;                   
		int move_sign = 0;                        
		Vector3 end_pos;
		Vector3 middle_pos;

		void Update()
		{
			if (move_sign == 1)
			{
				transform.position = Vector3.MoveTowards(transform.position, middle_pos, move_speed * Time.deltaTime);
				if (transform.position == middle_pos)
					move_sign = 2;
			}
			else if (move_sign == 2)
			{
				transform.position = Vector3.MoveTowards(transform.position, end_pos, move_speed * Time.deltaTime);
				if (transform.position == end_pos)
					move_sign = 0;           
			}
		}
		public void MovePosition(Vector3 position)
		{
			end_pos = position;
			if (position.y == transform.position.y)         
			{  
				move_sign = 2;
			}
			else if (position.y < transform.position.y)      
			{
				middle_pos = new Vector3(position.x, transform.position.y, position.z);
			}
			else                                          
			{
				middle_pos = new Vector3(transform.position.x, position.y, position.z);
			}
			move_sign = 1;
		}
	}
  • Land:用于控制与河岸有关的动作,比如角色上下岸,船的离开和停靠。
    陆地的属性:陆地有两块,一个标志位来记录是开始的陆地还是结束陆地,陆地的位置,以及陆地上的角色,每个角色的位置
public class LandModel
	{
		GameObject land;                                
		Vector3[] positions;                            
		int land_sign;                                  
		RoleModel[] roles = new RoleModel[6];           
		public LandModel(string land_mark)
		{
			positions = new Vector3[] {new Vector3(46F,14.73F,-4), new Vector3(55,14.73F,-4), new Vector3(64F,14.73F,-4),
				new Vector3(73F,14.73F,-4), new Vector3(82F,14.73F,-4), new Vector3(91F,14.73F,-4)};
			if (land_mark == "start")
			{
				land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(70, 1, 0), Quaternion.identity) as GameObject;
				land_sign = 1;
			}
			else if(land_mark == "end")
			{
				land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(-70, 1, 0), Quaternion.identity) as GameObject;
				land_sign = -1;
			}
		}

		public int GetEmptyNumber()                      
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] == null)
					return i;
			}
			return -1;
		}

		public int GetLandSign() { return land_sign; }

		public Vector3 GetEmptyPosition()               
		{
			Vector3 pos = positions[GetEmptyNumber()];
			pos.x = land_sign * pos.x;                  
			return pos;
		}

		public void AddRole(RoleModel role)             
		{
			roles[GetEmptyNumber()] = role;
		}

		public RoleModel DeleteRoleByName(string role_name)      
		{ 
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null && roles[i].GetName() == role_name)
				{
					RoleModel role = roles[i];
					roles[i] = null;
					return role;
				}
			}
			return null;
		}

		public int[] GetRoleNum()
		{
			int[] count = { 0, 0 };                    
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null)
				{
					if (roles[i].GetSign() == 0)
						count[0]++;
					else
						count[1]++;
				}
			}
			return count;
		}	
	}
  • Role:用于控制6个角色的动作,比如上船,上岸等。
    一个角色的属性:标志角色是牧师还是恶魔,标志是否在船上
    角色模型的函数:去到陆地/船上(其实就是把哪个作为父节点,并且修改是否在船上标志),其他就是基本的get/set函数。
public class RoleModel
	{
		GameObject role;
		int role_sign;             
		Click click;
		bool on_boat;              
		Move move;
		LandModel land_model = (SSDirector.GetInstance().CurrentScenceController as Controller).start_land;
		public RoleModel(string role_name)
		{
			if (role_name == "priest")
			{
				role = Object.Instantiate(Resources.Load("Prefabs/Priests", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -90, 0)) as GameObject;
				role_sign = 0;
			}
			else
			{
				role = Object.Instantiate(Resources.Load("Prefabs/Devils", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -90, 0)) as GameObject;
				role_sign = 1;
			}
			move = role.AddComponent(typeof(Move)) as Move;
			click = role.AddComponent(typeof(Click)) as Click;
			click.SetRole(this);
		}
		public int GetSign() { return role_sign;}
		public LandModel GetLandModel(){return land_model;}
		public string GetName() { return role.name; }
		public bool IsOnBoat() { return on_boat; }
		public void SetName(string name) { role.name = name; }
		public void SetPosition(Vector3 pos) { role.transform.position = pos; }
		public void Move(Vector3 vec)
		{
			move.MovePosition(vec);
		}
		public void GoLand(LandModel land)
		{  
			role.transform.parent = null;
			land_model = land;
			on_boat = false;
		}
		public void GoBoat(BoatModel boat)
		{
			role.transform.parent = boat.GetBoat().transform;
			land_model = null;          
			on_boat = true;
		}

	}
  • Boat:用于控制船的运动以及角色的上下船绑定。
    船的属性:船在开始/结束陆地旁的位置,在开始/结束陆地旁船上可以载客的两个位置(用Vector3的数组表示),船上载有的角色(用角色模型的数组来记录),标记船在开始陆地还是结束陆地的旁边。
public class BoatModel
	{
		GameObject boat;                                          
		Vector3[] start_empty_pos;                                    
		Vector3[] end_empty_pos;                                      
		Move move;                                                    
		Click click;
		int boat_sign = 1;                                                     
		RoleModel[] roles = new RoleModel[2];                                  

		public BoatModel()
		{
			boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), new Vector3(25, -2.5F, 0), Quaternion.identity) as GameObject;

			boat.name = "boat";
			move = boat.AddComponent(typeof(Move)) as Move;
			click = boat.AddComponent(typeof(Click)) as Click;
			click.SetBoat(this);
			start_empty_pos = new Vector3[] { new Vector3(18, 4, 0), new Vector3(32,4 , 0) };
			end_empty_pos = new Vector3[] { new Vector3(-32, 4, 0), new Vector3(-18,3 , 0) };
		}

		public bool IsEmpty()
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null)
					return false;
			}
			return true;
		}

		public void BoatMove()
		{
			if (boat_sign == -1)
			{
				move.MovePosition(new Vector3(25, -2.5F, 0));
				boat_sign = 1;
			}
			else
			{
				move.MovePosition(new Vector3(-25, -2.5F, 0));
				boat_sign = -1;
			}
		}

		public int GetBoatSign(){ return boat_sign;}

		public RoleModel DeleteRoleByName(string role_name)
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null && roles[i].GetName() == role_name)
				{
					RoleModel role = roles[i];
					roles[i] = null;
					return role;
				}
			}
			return null;
		}

		public int GetEmptyNumber()
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] == null)
				{
					return i;
				}
			}
			return -1;
		}

		public Vector3 GetEmptyPosition()
		{
			Vector3 pos;
			if (boat_sign == -1)
				pos = end_empty_pos[GetEmptyNumber()];
			else
				pos = start_empty_pos[GetEmptyNumber()];
			return pos;
		}

		public void AddRole(RoleModel role)
		{
			roles[GetEmptyNumber()] = role;
		}

		public GameObject GetBoat(){ return boat; }

		public int[] GetRoleNumber()
		{
			int[] count = { 0, 0 };
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] == null)
					continue;
				if (roles[i].GetSign() == 0)
					count[0]++;
				else
					count[1]++;
			}
			return count;
		}
	}
  • Interface
    这是将所有的接口放在同一命名空间下,同样方便了其他模块调用此命名空间。分别是场景控制器的接口,利用这个接口,得知当前场景是由哪个控制,然后向场景控制器传达要求,以及用户动作的接口,用户通过键盘、鼠标等对游戏发出指令,这个指令会触发游戏中的一些行为,由IUserAction来声明。
namespace interfacecon{
	public interface ISceneController                      
	{
		void LoadResources();
	}
	public interface IUserAction                           
	{
		void MoveBoat();                                   
		void Restart();                                    
		void MoveRole(RoleModel role);                     
		int Check();                                       
	}
}
  • Controller

    这是一个控制器,对场景中的具体对象进行操作,可以看到这个控制器继承了两个接口类并实现了它们的方法,控制器是场景中各游戏对象行为改变的核心。他需要引用模型控制器以及接口的命名空间来调用其中实现的函数来达到控制的目的。其中重新开始函数可以调用。不过在Unity3d 5.6.x版本中有一个问题就是重新加载后光会消失,解决方法是点击Window->light,将auto generate叉掉并点击build即可在每次加载后显示出光。

SceneManager.LoadScene(0);

实现重新开始的目的,起作用就是重新加载场景,与重新点击三角开启作用相同。

public class Controller : MonoBehaviour, ISceneController, IUserAction
{
	public LandModel start_land;            
	public LandModel end_land;              
	public BoatModel boat;                  
	private RoleModel[] roles;              
	UserGUI user_gui;
	void Start ()
	{
		SSDirector director = SSDirector.GetInstance();
		director.CurrentScenceController = this;
		user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
		LoadResources();
	}

	public void LoadResources()              
	{
		GameObject water = Instantiate(Resources.Load("Prefabs/Water", typeof(GameObject)), new Vector3(0,-10,-2), Quaternion.identity) as GameObject;
		water.name = "water";       
		start_land = new LandModel("start");
		end_land = new LandModel("end");
		boat = new BoatModel();
		roles = new RoleModel[6];

		for (int i = 0; i < 3; i++)
		{
			RoleModel role = new RoleModel("priest");
			role.SetName("priest" + i);
			role.SetPosition(start_land.GetEmptyPosition());
			role.GoLand(start_land);
			start_land.AddRole(role);
			roles[i] = role;
		}

		for (int i = 3; i < 6; i++)
		{
			RoleModel role = new RoleModel("devil");
			role.SetName("devil" + i);
			role.SetPosition(start_land.GetEmptyPosition());
			role.GoLand(start_land);
			start_land.AddRole(role);
			roles[i] = role;
		}
	}

	public void MoveBoat()                  
	{
		if (boat.IsEmpty() || user_gui.sign != 0) return;
		boat.BoatMove();
		user_gui.sign = Check();
	}

	public void MoveRole(RoleModel role)    
	{
		if (user_gui.sign != 0) return;
		if (role.IsOnBoat())
		{
			LandModel land;
			if (boat.GetBoatSign() == -1)
				land = end_land;
			else
				land = start_land;
			boat.DeleteRoleByName(role.GetName());
			role.Move(land.GetEmptyPosition());
			role.GoLand(land);
			land.AddRole(role);
		}
		else
		{                                
			LandModel land = role.GetLandModel();
			if (boat.GetEmptyNumber() == -1 || land.GetLandSign() != boat.GetBoatSign()) return;   
			land.DeleteRoleByName(role.GetName());
			role.Move(boat.GetEmptyPosition());
			role.GoBoat(boat);
			boat.AddRole(role);
		}
		user_gui.sign = Check();
	}

	public void Restart()
	{
		SceneManager.LoadScene(0);
	}

	public int Check()
	{
		int start_priest = (start_land.GetRoleNum())[0];
		int start_devil = (start_land.GetRoleNum())[1];
		int end_priest = (end_land.GetRoleNum())[0];
		int end_devil = (end_land.GetRoleNum())[1];

		if (end_priest + end_devil == 6)     
			return 2;

		int[] boat_role_num = boat.GetRoleNumber();
		if (boat.GetBoatSign() == 1)         
		{
			start_priest += boat_role_num[0];
			start_devil += boat_role_num[1];
		}
		else                                  
		{
			end_priest += boat_role_num[0];
			end_devil += boat_role_num[1];
		}
		if (start_priest > 0 && start_priest < start_devil) 
		{      
			return 1;
		}
		if (end_priest > 0 && end_priest < end_devil)        
		{
			return 1;
		}
		return 0;                                             
	}
}

接下来就是Prefabs预设的设定:
在这里插入图片描述
首先在场景中创建好各个对象并加入材料,然后将其摆放到正确位置并记录,以便实现代码时需要游戏对象的位置。然后将游戏对象拖入Prefabs文件夹,这样就可以利用预设了,在游戏中想要调用预设就使用路径"Prefabs/xxx"即可加载对应的预设。

最后将场景控制器的脚本挂载到创建的空物体上就可以开始运行游戏。
游戏展示:牧师与魔鬼
github地址:PriestsAndDevils
最后非常感谢感谢师兄的博客给我参考,实现的非常详细让我完全理解了实现过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值