"魔鬼与牧师"是个很经典的小游戏,也被扩展成牧羊人等多个版本,但核心玩法都大致相同:
规则简述:有三个牧师与三个魔鬼,要多次乘坐一艘核载2人的小船过河。但在任意一边的河岸上,若魔鬼的数量大于牧师的数量,则魔鬼就会伺机动手伤害牧师。你的任务就是在保证牧师不受伤害的情况下,顺利将所有人送到对岸。
元素提取:牧师*3,魔鬼*3,小河,船,河岸*2
玩家的动作以及条件:
1. 选择牧师或魔鬼上/下船
2.开船到对岸。(每次开船都判定游戏是否失败)
一,制造各个物体的模型
为求简明,将各个物体的模型设计列成下表:
物体 | 尺寸 | 颜色 |
牧师*3 | 1*1*2 | 青色 |
魔鬼*3 | 1.2*1.2*2.5 | 红色 |
船 | 1*1*1 | 淡黄色 |
河水 | 3*3*1 | 蓝色 |
河岸*2 | 5*5*3 | 绿色 |
二,编写项目代码
接下来,分别对于用户交互界面、模型初始化和全局控制进行代码编写:
用户交互界面:
1.先在Start函数中定义action,同时初始化变量sign用于记录游戏状态(如胜利/失败/游戏开始):
private IUserAction action;
public int sign = 0;
void Start()
{
action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
}
2.再在OnGUI函数中定义游戏的字体大小和不同状态:
void OnGUI()
{
GUIStyle text_style;
GUIStyle button_style;
text_style = new GUIStyle()
{
fontSize = 30
};
button_style = new GUIStyle("button")
{
fontSize = 15
};
// 如果还没开始则显示规则
if (sign == 0)
{
//点击开始按钮,状态从未开始变为已开始
if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 4, 100, 50), "Start", button_style))
{
sign = 1;
}
else//未点击开始按钮时,在主界面展示游戏规则
{
GUI.Label(new Rect(Screen.width / 2 - 85, 110, 200, 50), "玩法:点击牧师、恶魔、船移动");
GUI.Label(new Rect(Screen.width / 2 - 85, 130, 250, 50), "胜利:让全部牧师和恶魔都渡河");
GUI.Label(new Rect(Screen.width / 2 - 85, 150, 250, 50), "失败:任意一边的恶魔数量多于牧师");
}
}
// sign == 2表示失败, sign == 3表示成功
else if (sign == 2)
{
GUI.Label(new Rect(Screen.width / 2 - 60, Screen.height / 2 - 120, 100, 50), "Gameover!", text_style);
if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 3, 100, 50), "Restart", button_style))
{
action.Restart();
sign = 1;
}
}
else if (sign == 3)
{
GUI.Label(new Rect(Screen.width / 2 - 60, Screen.height / 2 - 120, 100, 50), "You Win!", text_style);
if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 4, 100, 50), "Restart", button_style))
{
action.Restart();
sign = 1;
}
}
}
模型初始化:
先创建一个名为mygame的命名空间,再在其中添加接口和不同模型的类。
命名空间中的接口:
public interface ISceneController //加载场景
{
void LoadResources();
}
public interface IUserAction //用户互动会发生的事件
{
void MoveBoat(); //移动船
void Restart(); //重新开始
void MoveRole(RoleModel role); //移动角色
int Check(); //检测游戏结束
}
命名空间中的各个模型对应的类:
//Software School Director
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;
}
}
public class LandModel
{
GameObject land; //陆地对象
Vector3[] positions; //用向量保存每个角色放在陆地上的位置
int land_sign; //到达陆地标志为-1,开始陆地标志为1,方便计算失败条件
RoleModel[] roles = new RoleModel[6]; //陆地上角色的座位
public LandModel(string land_mark)
{
//设置好空位,供人物的放置
positions = new Vector3[] {new Vector3(7, 2, 30), new Vector3(9, 2, 30), new Vector3(11, 2, 30),
new Vector3(13, 2, 30), new Vector3(15, 2, 30), new Vector3(17, 2, 30)};
if (land_mark == "start")//是起始那边的岸
{
land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(12, 0, 30), Quaternion.identity) as GameObject;
land_sign = 1;
}
else//是对岸
{
land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(-12, 0, 30), 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()];
// 因为两个陆地是x坐标对称,这样能转化得到负轴的位置
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()
{
//count[0]是牧师数,count[1]是魔鬼数
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;
}
public void Reset()
{
roles = new RoleModel[6];
}
}
public class BoatModel
{
GameObject boat;
Vector3[] start_empty_pos; // 船在开始陆地的空位位置
Vector3[] end_empty_pos; // 船在结束陆地的空位位置
Move move;
Click click;
int boat_sign = 1; // 船在开始还是结束陆地(1-开始、-1-结束)
RoleModel[] roles = new RoleModel[2]; // 船上的角色
public BoatModel()
{
boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), new Vector3(4, 0, 30), 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(5, 1, 30), new Vector3(3, 1, 30) };
end_empty_pos = new Vector3[] { new Vector3(-3, 1, 30), new Vector3(-5, 1, 30) };
}
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(4, 0, 30));
boat_sign = 1;
}
else
{
move.MovePosition(new Vector3(-4, 0, 30));
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[] GetRoleNum()
{
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;
}
public void Reset()
{
if (boat_sign == -1)
BoatMove();
roles = new RoleModel[2];
}
}
public class RoleModel
{
GameObject role;
int role_sign; //0为牧师,1为恶魔
Click click;
bool on_boat; //是否在船上
Move move;
LandModel land_model = (SSDirector.GetInstance().CurrentScenceController as Controllor).start_land;
public RoleModel(string role_name)
{
if (role_name == "priest")
{
role = Object.Instantiate(Resources.Load("Prefabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -90, 0)) as GameObject;
role_sign = 0;
}
else
{
role = Object.Instantiate(Resources.Load("Prefabs/Devil", 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);//click的作用对象是本身
}
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;
}
public void Reset()
{
land_model = (SSDirector.GetInstance().CurrentScenceController as Controllor).start_land;
GoLand(land_model);
SetPosition(land_model.GetEmptyPosition());
land_model.AddRole(this);
}
}
public class Move : MonoBehaviour
{
float move_speed = 100; //移动速度
int move_sign = 0; //0是不动,1水平移动,2竖直移动
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)
{ //y相同,船只需要水平移动
move_sign = 2;
}
else if (position.y < transform.position.y)
{ //目的地的y更低,涉及到垂直方向下降,角色从陆地到船
middle_pos = new Vector3(position.x, transform.position.y, position.z);
move_sign = 1;
}
else
{ //目的地的y更高,涉及到垂直方向上升,角色从船到陆地
middle_pos = new Vector3(transform.position.x, position.y, position.z);
move_sign = 1;
}
}
}
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);
}
}
}
全局控制:
此处需要为所有需要变化的模型(包括船、人和站人的两岸)创建相应的变量,并且利用这些变量定义好在mygame中声明过的所有函数(需要在代码开头加上using mygame)。
public class Controllor : 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, -1, 30), 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;//记录在roles[]中
}
for (int i = 0; i < 3; 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 + 3] = role;
}
}
public void MoveBoat()
{ //移动船
if (boat.IsEmpty() || user_gui.sign != 1)
return;
boat.BoatMove();//船非空、游戏开始且未结束时才能移动
}
public void MoveRole(RoleModel role)
{ // 游戏开始且未结束时才能移动角色
if (user_gui.sign != 1)
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();
if (user_gui.sign == 2)
{
Restart();
}
}
public void Restart()
{
start_land.Reset();
end_land.Reset();
boat.Reset();
for (int i = 0; i < roles.Length; i++)
{
roles[i].Reset();
}
}
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 3;
int[] boat_role_num = boat.GetRoleNum();
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) || (end_priest > 0 && end_priest < end_devil))
{ //失败
return 2;
}
return 1; //未完成
}
}
三,运行效果
视频链接如下: