目录
一、牧师与魔鬼游戏介绍
为了完成牧师与魔鬼,首先需要了解牧师与魔鬼这一款游戏,它的游戏内容如下:
牧师和魔鬼是一款益智游戏,您将在其中帮助牧师和魔鬼过河。河的一侧有3个祭司和3个魔鬼。他们都想去这条河的另一边,但只有一条船,这条船每次只能载两个人。而且必须有一个人将船从一侧驾驶到另一侧。您可以单击按钮来移动它们,然后单击移动按钮将船移动到另一个方向。如果靠岸的船上和同一侧岸上的牧师被岸上的魔鬼人数所淹没,他们就会被杀死,游戏就结束了。您可以通过多种方式尝试它。让所有的祭司活着!最后所有牧师和魔鬼都成功过河,则表示游戏胜利。
你还可以通过网站 ( http://www.flash-game.net/game/2535/priests-and-devils.html ) 来试玩一下这个游戏来更加了解这个游戏的规则,我将使用unity来实现这一游戏,在编写代码实现的过程中程序需要满足以下要求:
- 请将游戏中对象做成预制
- 在场景控制器
LoadResources
方法中加载并初始化 长方形、正方形、球 及其色彩代表游戏中的对象。 - 使用 C# 集合类型 有效组织对象
- 整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!!! 。 整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的 通讯耦合 语句。
- 请使用MVC架构图编程,不接受非 MVC 结构程序
- 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件
二、实现游戏前的准备
1、MVC架构
由于我要实现的是动作分离版的牧师与魔鬼,所以我们需要先了解一下游戏的总体框架MVC框架。
Model
其中Model负责的是各个游戏对象的属性和基本行为,包括人物角色(魔鬼、牧师),船,以及河的两岸。船和岸是作为一个容器,需要有容纳人物的位置,也需要记录每个位置对应的xyz坐标。而它们都需要有一定的方法去返回自身的信息,比如在左还是右、是第几个人物、返回对象类型的方法等等。
View
这个就是与用户交互的接口,其中需要接受来自用户的点击信息,并且根据点击的物体不同而传递不同的信息。还有就是反馈,游戏开始或结束需要反馈相应的信息给用户,也就是一个简单的界面。
Controller
控制器需要将M和V连接器来,达到控制全局的目的,不仅需要从model中获取相应的信息,并且利用这些信息判断他们的位置,还需要从View中获取相应的用户输入,进行相应的物体移动,在Model和View之间充当桥梁的作用。同时还需要判断游戏进行程度,也就是判断输赢,并且将输赢信息返回给View,从而达到反馈的目的。
针对本游戏实现代码的MVC框架如下:
2、游戏事物(Objects)
名称 | 实现方式 |
---|---|
牧师 | 白色长方体 |
恶魔 | 黑色球体 |
河岸 | 绿色长方体 |
船 | 棕色长方体 |
3、玩家动作表
动作 | 条件 |
---|---|
船移动 | 船上必须有一个人 |
牧师在左岸上船 | 左岸必须有牧师,船上有空位,船在左岸 |
牧师在右岸上船 | 右岸必须有牧师,船上有空位,船在右岸 |
牧师在左岸下船 | 船上有牧师,船在左岸 |
牧师在右岸下船 | 船上有牧师,船在右岸 |
恶魔在左岸上船 | 左岸必须有恶魔,船上有空位,船在左岸 |
恶魔在右岸上船 | 右岸必须有恶魔,船上有空位,船在右岸 |
恶魔在左岸下船 | 船上有恶魔,船在左岸 |
恶魔在右岸下船 | 船上有恶魔,船在右岸 |
4、预制对象(Prefabs)
通过创建GameObject并使用Metariel设置好对应的颜色和形状作为预制,最后完成的预制对象如下图:
三、编写实现游戏的代码
根据前面构建的MVC框架,先创建脚本scripts,然后具体实现过程如下:
1、SSDirector类
该类在游戏中类似于导演,担任的职责如下:
- 获取当前游戏的场景
- 控制场景运行、切换、入栈与出栈
- 暂停、恢复、退出
- 管理游戏全局状态
- 设定游戏的配置
- 设定游戏全局视图
为了完成以上职责,需要先拥有一些全局属性,如 running,fps等,这样可以让任何地方的代码访问它。然后还需要创建控制器对象来访问不同的控制器。代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//Director.cs
public class SSDirector : System.Object
{
private static SSDirector instance;
public FirstController controller;
//public ISceneController sceneController{get; set;}
//public IUserActions action{get;set;}
public bool running{get; set;}
public static SSDirector getInstance()
{
if (instance == null)
{
instance = new SSDirector();
}
return instance;
}
public int getFPS(){
return Application.targetFrameRate;
}
public void setFPS(int fps){
Application.targetFrameRate=fps;
}
}
2、SceneController类
在游戏中类似于场记,其职责包括:
- 管理本次场景所有的游戏对象
- 协调游戏对象(预制件级别)之间的通讯
- 响应外部输入事件
- 管理本场次的规则(裁判)
- 各种杂务
为了完成以上职责,需要实现IUserAction接口以便于实现玩家与游戏之间的实现,函数的具体实现可以放在模型GenGameObject中,这里调用GenGameObject中的函数即可。在场景被加载(awake)时,它也会自动注入SSDirector,作为当前场景。
public class FirstController : MonoBehaviour, IUserActions
{
private GenGameObject model;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
void Awake()
{
SSDirector director = SSDirector.getInstance();
director.setFPS(60);
//director.controller = this;
//director.action = this;
director.controller = this;
//model.Start();
//director.controller.LoadResources();
//director.sceneController.LoadResources();
}
public void LoadResources() //加载物体
{
model.LoadResources();
}
public void setModel(GenGameObject Model)
{
model = Model;
}
public void priestOn()
{
model.priestOn();
}
public void devilOn()
{
model.devilOn();
}
public void changeState()
{
model.change_state();
}
public void priestOnEnd()
{
model.priestOnEnd();
}
public void devilOnEnd()
{
model.devilOnEnd();
}
public int Check()
{
return model.Check();
}
public void priestOff()
{
model.priestOff();
}
public void devilOff()
{
model.devilOff();
}
public void Restart()
{
model.Restart();
}
}
3、IUserAction接口
接口定义了一组操作,该接口实现了用户行为与游戏系统规则计算的分离。这个接口就是游戏逻辑与用户界面之间交互的门面(Fasàde),用户可以通过这个接口中的操作进行游戏,要实现的操作可以参考上述的玩家动作表。
public interface IUserActions
{
void priestOn();
void priestOnEnd();
void devilOn();
void devilOnEnd();
void changeState();
int Check();
void priestOff();
void devilOff();
void Restart();
}
4、UserGUI类
UserGUI类用来设置按钮并调用接口,完成用户交互。
public class UserGUI : MonoBehaviour
{
SSDirector instance;
IUserActions action;
void Start()
{
instance = SSDirector.getInstance();
action = instance.controller as IUserActions;
}
private void OnGUI()
{
GUIStyle fontStyle = new GUIStyle();
fontStyle.fontSize = 40;
fontStyle.normal.textColor = new Color(255, 255, 255);
if (action.Check() == 1)
{
GUI.Label(new Rect(440, 200, 100, 100), "Win", fontStyle);
if (GUI.Button(new Rect(435, 360, 80, 50), "Restart"))
{
action.Restart();
}
}
if (action.Check() == 2)
{
GUI.Label(new Rect(390, 200, 100, 100), "GameOver", fontStyle);
if (GUI.Button(new Rect(435, 360, 80, 50), "Restart"))
{
action.Restart();
}
}
if (GUI.Button(new Rect(130, 0, 80, 50), "恶魔上船"))
{
action.devilOn();
}
if (GUI.Button(new Rect(270, 0, 80, 50), "牧师上船"))
{
action.priestOn();
}
if (GUI.Button(new Rect(600, 0, 80, 50), "牧师上船"))
{
action.priestOnEnd();
}
if (GUI.Button(new Rect(750, 0, 80, 50), "恶魔上船"))
{
action.devilOnEnd();
}
if (GUI.Button(new Rect(450, 0, 80, 50), "移动"))
{
action.changeState();
}
if (GUI.Button(new Rect(450, 50, 80, 50), "牧师下船"))
{
action.priestOff();
}
if (GUI.Button(new Rect(450, 100, 80, 50), "恶魔下船"))
{
action.devilOff();
}
}
}
5、GenGameObject类
在这个类中完成玩家的交互函数的具体实现,用栈来分别存储左岸和右岸的牧师和魔鬼,以此来判断何时游戏失败,何时游戏成功
public class GenGameObject : MonoBehaviour
{
private SSDirector instance;
int onBoat;
public int speed;
int boat_state = 1;
GameObject[] objectOnBoat = new GameObject[2];
readonly Vector3 shore_begin = new Vector3(10, 0, 0);
readonly Vector3 shore_end = new Vector3(-10, 0, 0);
readonly Vector3 boat_begin = new Vector3(-3, 0, 0);
readonly Vector3 boat_end = new Vector3(3, 0, 0);
readonly Vector3 priests_begin = new Vector3(-9f, 2, 0);
readonly Vector3 priests_last = new Vector3(9f, 2, 0);
readonly Vector3 devils_begin = new Vector3(-15f, 2, 0);
readonly Vector3 devils_last = new Vector3(15f, 2, 0);
readonly Vector3 gap = new Vector3(-1.5f, 0, 0);
Stack<GameObject> priests_start = new Stack<GameObject>();
Stack<GameObject> devils_start = new Stack<GameObject>();
Stack<GameObject> priests_end = new Stack<GameObject>();
Stack<GameObject> devils_end = new Stack<GameObject>();
GameObject boat;
public void Start()
{
speed = 10;
onBoat = 0;
boat_state = 1;
instance = SSDirector.getInstance();
instance.controller.setModel(this);
LoadResources();
}
public void LoadResources() //加载物体
{
UnityEngine.Debug.Log("load...\n");
Instantiate(Resources.Load("Prefabs/Shore"), shore_begin, Quaternion.identity);
Instantiate(Resources.Load("Prefabs/Shore"), shore_end, Quaternion.identity);
boat = Instantiate(Resources.Load("Prefabs/Boat"), boat_begin, Quaternion.identity) as GameObject;
boat.name = "boat";
for (int i = 0; i < 3; i++)
{
GameObject priest = Instantiate(Resources.Load("Prefabs/Priest"), (priests_begin - gap * i), Quaternion.identity) as GameObject;
priest.name = "Priest";
priests_start.Push(priest);
GameObject devil = Instantiate(Resources.Load("Prefabs/Devil"), (devils_begin - gap * i), Quaternion.identity) as GameObject;
devil.name = "Devil";
devils_start.Push(devil);
}
}
public int Check()//返回当前状态
{
int devilleft, devilright, preleft, preright;
devilleft = devilright = preleft = preright = 0;
devilleft += devils_start.Count;
devilright += devils_end.Count;
preleft += priests_start.Count;
preright += priests_end.Count;
if(boat_state==1 && onBoat>0)
{
for(int i=0;i<2;i++)
{
if (objectOnBoat[i] != null && objectOnBoat[i].name == "Devil")
devilleft += 1;
else if (objectOnBoat[i] != null && objectOnBoat[i].name == "Priest")
preleft += 1;
}
}
else if(boat_state==0 && onBoat>0)
{
for (int i = 0; i < 2; i++)
{
if (objectOnBoat[i]!=null && objectOnBoat[i].name == "Devil")
devilright += 1;
else if (objectOnBoat[i] != null && objectOnBoat[i].name == "Priest")
preright += 1;
}
}
if (devils_end.Count == 3 && priests_end.Count == 3)
{
return 1;
}
if (devilleft>preleft && preleft>0)
{
// UnityEngine.Debug.Log(preleft);
return 2;
}
else if(devilright >preright && preright>0)
{
// UnityEngine.Debug.Log(preright);
return 2;
}
return 3;
}
private void Update()
{
Check();
}
public void priestOn()//牧师上船
{
if (priests_start.Count == 0)
return;
if (onBoat < 2)
{
GameObject temp = priests_start.Pop();
temp.transform.parent = boat.transform;
if (objectOnBoat[0]==null)
{
temp.transform.localPosition = new Vector3(0.3f, 1.5f, 0);
objectOnBoat[0] = temp;
}
else
{
temp.transform.localPosition = new Vector3(-0.3f, 1.5f, 0);
objectOnBoat[1] = temp;
}
onBoat++;
}
}
public void priestOff()//牧师下船
{
for (int i = 0; i < 2; i++)
{
if (objectOnBoat[i] != null && objectOnBoat[i].name == "Priest")
{
if (boat.transform.position != boat_begin && boat.transform.position != boat_end)
return;
GameObject pri = objectOnBoat[i];
pri.transform.parent = null;
if (boat.transform.position == boat_end)
{
pri.transform.position = priests_last + gap * priests_end.Count;
priests_end.Push(pri);
}
else if (boat.transform.position == boat_begin)
{
pri.transform.position = priests_begin - gap * priests_start.Count;
priests_start.Push(pri);
}
objectOnBoat[i] = null;
onBoat--;
return;
}
}
}
public void devilOn()//恶魔上船
{
if (devils_start.Count == 0)
return;
if (onBoat < 2)
{
GameObject temp = devils_start.Pop();
temp.transform.parent = boat.transform;
if (objectOnBoat[0]==null)
{
temp.transform.localPosition = new Vector3(0.3f, 1.5f, 0);
objectOnBoat[0] = temp;
}
else
{
temp.transform.localPosition = new Vector3(-0.3f, 1.5f, 0);
objectOnBoat[1] = temp;
}
onBoat++;
// UnityEngine.Debug.Log(onBoat);
}
}
public void devilOff()//恶魔下船
{
for (int i = 0; i < 2; i++)
{
if (objectOnBoat[i] != null && objectOnBoat[i].name == "Devil")
{
if (boat.transform.position != boat_begin && boat.transform.position != boat_end)
return;
GameObject pri = objectOnBoat[i];
pri.transform.parent = null;
if (boat_state==0)
{
pri.transform.position = devils_last + gap * devils_end.Count;
devils_end.Push(pri);
// UnityEngine.Debug.Log(devils_end.Count);
}
else if (boat_state == 1)
{
pri.transform.position = devils_begin - gap * devils_start.Count;
devils_start.Push(pri);
}
objectOnBoat[i] = null;
onBoat--;
return;
}
}
}
public void Restart()//游戏重新开始
{
int num = priests_end.Count;
for (int i = 0; i < num; i++)
{
GameObject temp = priests_end.Pop();
temp.transform.position = priests_begin - gap * priests_start.Count;
priests_start.Push(temp);
}
num = devils_end.Count;
for (int i = 0; i < num; i++)
{
GameObject temp = devils_end.Pop();
temp.transform.position = devils_begin - gap * devils_start.Count;
devils_start.Push(temp);
}
if(onBoat>0)
for (int i = 0; i < 2; i++)
{
if (objectOnBoat[i] == null)
{
continue;
}
GameObject temp = objectOnBoat[i];
temp.transform.parent = null;
if (temp.name == "Priest")
{
temp.transform.position = priests_begin - gap * priests_start.Count;
priests_start.Push(temp);
}
else
{
temp.transform.position = devils_begin - gap * devils_start.Count;
priests_start.Push(temp);
}
objectOnBoat[i] = null;
}
onBoat = 0;
boat.transform.position = boat_begin;
boat_state = 1;
}
public void priestOnEnd()//右岸牧师上船
{
if (priests_end.Count == 0)
return;
if (onBoat < 2)
{
GameObject temp = priests_end.Pop();
temp.transform.parent = boat.transform;
if (objectOnBoat[0]==null)
{
temp.transform.localPosition = new Vector3(0.3f, 1.5f, 0);
objectOnBoat[0] = temp;
}
else
{
temp.transform.localPosition = new Vector3(-0.3f, 1.5f, 0);
objectOnBoat[1] = temp;
}
onBoat++;
}
}
public void devilOnEnd()//右岸恶魔上船
{
if (devils_end.Count == 0)
return;
if (onBoat < 2)
{
GameObject temp = devils_end.Pop();
temp.transform.parent = boat.transform;
if (objectOnBoat[0]==null)
{
temp.transform.localPosition = new Vector3(0.3f, 1.5f, 0);
objectOnBoat[0] = temp;
}
else
{
temp.transform.localPosition = new Vector3(-0.3f, 1.5f, 0);
objectOnBoat[1] = temp;
}
onBoat++;
}
}
public void boatMove()//船移动
{
if (onBoat != 0)
{
if (boat_state == 0)
boat.transform.position = boat_end;
else
boat.transform.position = boat_begin;
}
}
public void change_state()
{
if (boat_state == 0)
boat_state = 1;
else
boat_state = 0;
boatMove();
}
}
代码存放的位置:张菁/3D游戏编程与设计 (gitee.com)