1.游戏简介
牧师与魔鬼是一款益智小游戏,在游戏中有两个角色:牧师与魔鬼。玩家需要设计上船方案帮助牧师过顺利河。邪恶的魔鬼会攻击善良的牧师,面对成群的魔鬼的攻击,必须有一定数量的牧师来抵御魔鬼的攻击,所以一定要保证牧师的数量不能少于魔鬼的数量。
2.游戏规则
游戏中有3个牧师以及3个魔鬼,无论何时何地,牧师的数量均不能少于魔鬼,若某一边(包括船上)的魔鬼数量大于牧师数量,则游戏失败。游戏可以通过船只运送牧师与魔鬼,船只一次性只能运送两名角色,牧师与魔鬼均可登船且船上可同时存在魔鬼与牧师,船只上必须有角色才能向对岸移动。玩家如果能在60秒的时间内探索出能让牧师顺利过河的方案并实施,则游戏胜利,若未能在60秒的时间内探索出能让牧师顺利过河的方案或某一时刻的某一岸上出现魔鬼数量大于牧师数量,则游戏失败。
3.游戏玩法
玩家通过鼠标左键点击牧师(白色圆柱)、恶魔(黑色圆柱)可以完成它们上船、上岸的动作。玩家通过鼠标左键点击点击船(水面上的方块)可以让船向对岸移动。
4.游戏MVC图
BoatModel、LandModel、RiverModel、RoleModel为船只、河岸、河流、角色模型,用于模型的初始化。
MainControllor为主控制器,用于加载资源、开始游戏、重启游戏、判断游戏是否结束等。
IUserAction内含有用户能通过GUI进行的操作。
BoatControllor、LandControllor、RiverControllor、RoleControllor用于船只、河岸、河流、角色模型的创建以及控制。
SceneControllor用于控制场景,游戏中只有一处场景。
UserGUI为用户控制相关的GUI等。
5.游戏编写
本游戏基于Unity引擎编写,使用的系统为Windows10,使用的脚本文件语言为C#,首先需要确保保证电脑中安装Unity引擎,我的版本是2023.3.8f1c1,还需下载VSCode或VStudio用于编写代码,还需要Unity的Plastic SCM用于管理代码,具体安装方法这里不过多介绍。
接着我们打开Unity,找到项目,选择新项目。
选择3D,项目命名为Priest And Devil,保存地址我选择的是D盘。(启动版本管理可选可不选)
现在我们进入了Unity引擎界面,如图所示,什么内容也没有。
本项目中运用到的代码、文件非常多,为了保证界面的简洁性,建议在Assests项目栏中如下图建立几个文件夹方便管理。
建立方法如下:在Assets栏中空白区用鼠标右键点击,选择Create中的Folder生成我们需要的游戏文件夹。
游戏中GameObject如下:
GameObject | 数目 |
牧师priest | 3 |
恶魔devil | 3 |
船只boat | 1 |
河流river | 1 |
河岸land | 2 |
现在我们创建这些GameObject。
首先进入Materials文件夹,我们需要每个material的贴图(记得贴图得要是JPG模式)。
河岸land贴图
船只boat贴图
河流river贴图
牧师priest贴图
恶魔devil贴图
我们需要创建Material,创建方式如下:在Assets栏中空白区用鼠标右键点击,选择Create中的生成Material。
Material的贴图方式如下:点击Material,将贴图用鼠标移动到Albedo前的小框中。
完成上述步骤后Materials文件夹应该如下:
现在我们创建Prefab,点击Assets中的Resouces文件夹,创建Prefabs文件夹并在文件夹内右键空白处,选择3D Object中的Prefab,如下图所示:
分别建立devil、boat、priest、land和river的Prefab,并贴上Material,贴Material的方式为:左键点击Prefab,右侧inspector栏会显示Prefab相关信息,将Material拉入空白处,如图所示:
创捷完后如图所示:
现在编写脚本,打开Scripts文件夹,创建如下Controllers、View和Models几个文件夹:
Controllers内有如下几个脚本:
我们现在编写船只控制器BoatController脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//
public class BoatController : ClickAction
{
BoatModel boatModel;
IUserAction userAction;
public BoatModelController()
{
userAction = SSDirector.GetInstance().CurrentSenceController as IUserAction;
}
public void CreateBoat(Vector3 position)
{
if (boatModel == null)
boatModel = new BoatModel();
boatModel.Init(position);
boatModel.boat.GetComponent<Click>().setClickAction(this);
}
public BoatModel GetBoatModel()
{
return boatModel;
}
//将角色加载到船只上,返回接下来角色计算后应该到达的位置
public Vector3 AddRole(RoleModel roleModel)
{
//船上有两个位置,分别判断两个位置是否为空
if (boatModel.roles[0] == null)
{
boatModel.roles[0] = roleModel;
roleModel.isInBoat = true;
roleModel.role.transform.parent = boatModel.boat.transform;
if (roleModel.isPriest)
boatModel.priestNum++;
else
boatModel.devilNum++;
return PositionModel.boatRoles[0];
}
if (boatModel.roles[1] == null)
{
boatModel.roles[1] = roleModel;
roleModel.isInBoat = true;
roleModel.role.transform.parent = boatModel.boat.transform;
if (roleModel.isPriest)
boatModel.priestNum++;
else
boatModel.devilNum++;
return PositionModel.boatRoles[1];
}
return roleModel.role.transform.localPosition;
}
//将角色从船上移除
public void RemoveRole(RoleModel roleModel)
{
//船上有两个位置,分别判断两个位置当中有没有要移除的角色
if (boatModel.roles[0] == roleModel)
{
boatModel.roles[0] = null;
if (roleModel.isPriest)
boatModel.priestNum--;
else
boatModel.devilNum--;
}
if (boatModel.roles[1] == roleModel)
{
boatModel.roles[1] = null;
if (roleModel.isPriest)
boatModel.priestNum--;
else
boatModel.devilNum--;
}
}
//处理点击事件
public void DealClick()
{
if (boatModel.roles[0] != null || boatModel.roles[1] != null)
userAction.MoveBoat();
}
}
我们现在编写裁判类控制器JudgeController脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class JudgeController : MonoBehaviour
{
public FirstController mainController;
public LandModel leftLandModel;
public LandModel rightLandModel;
public BoatModel boatModel;
// 在游戏第一帧运行update前运行这个start函数
void Start()
{
mainController = (FirstController)SSDirector.GetInstance().CurrentSenceController;
this.leftLandModel = mainController.leftLandController.GetLandModel();
this.rightLandModel = mainController.rightLandController.GetLandModel();
this.boatModel = mainController.boatController.GetBoatModel();
}
// 每一帧都会运行这个update函数
void Update()
{
if (!mainController.isRuning)
return;
if (mainController.time <= 0)
{
mainController.JudgeCallback(false, "Game Over!");
return;
}
this.gameObject.GetComponent<UserGUI>().gameMessage = "";
//判断是否已经胜利
if (rightLandModel.priestNum == 3)
{
mainController.JudgeCallback(false, "You Win!");
return;
}
else
{
//若任意一侧,牧师数量不为0且牧师数量少于恶魔数量,则游戏失败
int leftPriestNum; //左岸牧师数量
int leftDevilNum; //左岸恶魔数量
int rightPriestNum; //右岸牧师数量
int rightDevilNum; //右岸恶魔数量
leftPriestNum = leftLandModel.priestNum + (boatModel.isRight ? 0 : boatModel.priestNum);
leftDevilNum = leftLandModel.devilNum + (boatModel.isRight ? 0 : boatModel.devilNum);
if (leftPriestNum != 0 && leftPriestNum < leftDevilNum)
{
mainController.JudgeCallback(false, "Game Over!");
return;
}
rightPriestNum = rightLandModel.priestNum + (boatModel.isRight ? boatModel.priestNum : 0);
rightDevilNum = rightLandModel.devilNum + (boatModel.isRight ? boatModel.devilNum : 0);
if (rightPriestNum != 0 && rightPriestNum < rightDevilNum)
{
mainController.JudgeCallback(false, "Game Over!");
return;
}
}
}
}
我们现在编写河岸控制器LandController脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LandController
{
private LandModel landModel;
//加载陆地模型
public void CreateLand(string name, Vector3 position)
{
if (landModel==null)
landModel = new LandModel();
landModel.Init(name, position);
landModel.priestNum = landModel.devilNum = 0;
}
//调用陆地模型
public LandModel GetLandModel()
{
return landModel;
}
//将人物添加到岸上,返回角色在岸上的相对坐标
public Vector3 AddRole(RoleModel roleModel)
{
if (roleModel.isPriest)
landModel.priestNum++;
else
landModel.devilNum++;
roleModel.role.transform.parent = landModel.land.transform;
roleModel.isInBoat = false;
return PositionModel.roles[roleModel.tag];
}
//将角色从岸上移除
public void RemoveRole(RoleModel roleModel)
{
if (roleModel.isPriest)
landModel.priestNum--;
else
landModel.devilNum--;
}
}
我们现在编写主控制器MainController脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainController : MonoBehaviour, SceneController, IUserAction
{
public CCActionManager actionManager;
public LandModelController rightLandController; //右岸控制器
public LandModelController leftLandController; //左岸控制器
public RiverModel riverModel; //河流Model
public BoatController boatController; //船控制器
public RoleController[] roleControllers; //人物控制器集合
//private MoveController moveController; //移动控制器
public bool isRuning; //游戏进行状态
public float time; //游戏进行时间
public void JudgeCallback(bool isRuning, string message)
{
this.gameObject.GetComponent<UserGUI>().gameMessage = message;
this.gameObject.GetComponent<UserGUI>().time = (int)time;
this.isRuning = isRuning;
}
//导入资源
public void LoadResources()
{
//人物初始化
roleControllers = new RoleModelController[6];
for (int i = 0; i < 6; i++)
{
roleControllers[i] = new RoleModelController();
roleControllers[i].CreateRole(PositionModel.roles[i], i < 3 ? true : false, i);
}
//左右岸初始化
leftLandController = new LandModelController();
leftLandController.CreateLand("left_land", PositionModel.left_land);
rightLandController = new LandModelController();
rightLandController.CreateLand("right_land", PositionModel.right_land);
//将人物添加并定位至左岸
foreach (RoleModelController roleModelController in roleControllers)
{
roleModelController.GetRoleModel().role.transform.localPosition = leftLandController.AddRole(roleModelController.GetRoleModel());
}
//河流Model实例化
riverModel = new RiverModel(PositionModel.river);
//船初始化
boatController = new BoatModelController();
boatController.CreateBoat(PositionModel.left_boat);
//移动控制器实例化
//moveController = new MoveController();
//数据初始化
isRuning = false;
time = 60;
}
//移动船
public void MoveBoat()
{
//判断当前游戏是否在进行,同时是否有对象正在移动
if ((!isRuning) || actionManager.IsMoving())
return;
//判断船在左侧还是右侧
Vector3 destination = boatController.GetBoatModel().isRight ? PositionModel.left_boat : PositionModel.right_boat;
actionManager.MoveBoat(boatController.GetBoatModel().boat, destination, 5);
/* if (boatRoleController.GetBoatModel().isRight)
moveController.SetMove(PositionModel.left_boat, boatRoleController.GetBoatModel().boat);
else
moveController.SetMove(PositionModel.right_boat, boatRoleController.GetBoatModel().boat);*/
//移动后,将船的位置取反
boatController.GetBoatModel().isRight = !boatController.GetBoatModel().isRight;
}
//移动人物
public void MoveRole(RoleModel roleModel)
{
//判断当前游戏是否在进行,同时是否有对象正在移动
if ((!isRuning) || actionManager.IsMoving())
return;
Vector3 destination, mid_destination;
if (roleModel.isInBoat)
{
//若人在船上,则将其移向岸上
if (boatController.GetBoatModel().isRight)
destination = rightLandController.AddRole(roleModel);
else
destination = leftLandController.AddRole(roleModel);
if (roleModel.role.transform.localPosition.y > destination.y)
mid_destination = new Vector3(destination.x, roleModel.role.transform.localPosition.y, destination.z);
else
mid_destination = new Vector3(roleModel.role.transform.localPosition.x, destination.y, destination.z);
actionManager.MoveRole(roleModel.role, mid_destination, destination, 5);
roleModel.isRight = boatController.GetBoatModel().isRight;
boatController.RemoveRole(roleModel);
}
else
{
//若人在岸上,则将其移向船
if (boatController.GetBoatModel().isRight == roleModel.isRight)
{
if (roleModel.isRight)
{
rightLandController.RemoveRole(roleModel);
}
else
{
leftLandController.RemoveRole(roleModel);
}
destination = boatController.AddRole(roleModel);
if (roleModel.role.transform.localPosition.y > destination.y)
mid_destination = new Vector3(destination.x, roleModel.role.transform.localPosition.y, destination.z);
else
mid_destination = new Vector3(roleModel.role.transform.localPosition.x, destination.y, destination.z);
actionManager.MoveRole(roleModel.role, mid_destination, destination, 5);
}
}
}
//游戏开始
public void MyStart()
{
//对各数据进行初始化
time = 60;
leftLandController.CreateLand("left_land", PositionModel.left_land);
rightLandController.CreateLand("right_land", PositionModel.right_land);
for (int i = 0; i < 6; i++)
{
roleControllers[i].CreateRole(PositionModel.roles[i], i < 3 ? true : false, i);
roleControllers[i].GetRoleModel().role.transform.localPosition = leftLandController.AddRole(roleControllers[i].GetRoleModel());
}
boatController.CreateBoat(PositionModel.left_boat);
isRuning = true;
}
//游戏重置
public void Restart()
{
//对各数据进行初始化
time = 60;
leftLandController.CreateLand("left_land", PositionModel.left_land);
rightLandController.CreateLand("right_land", PositionModel.right_land);
for (int i = 0; i < 6; i++)
{
roleControllers[i].CreateRole(PositionModel.roles[i], i < 3 ? true : false, i);
roleControllers[i].GetRoleModel().role.transform.localPosition = leftLandController.AddRole(roleControllers[i].GetRoleModel());
}
boatController.CreateBoat(PositionModel.left_boat);
isRuning = true;
}
void Awake()
{
SSDirector.GetInstance().CurrentSenceController = this;
LoadResources();
this.gameObject.AddComponent<UserGUI>();
this.gameObject.AddComponent<CCActionManager>();
this.gameObject.AddComponent<JudgeController>();
}
void Update()
{
if (isRuning)
{
time -= Time.deltaTime;
this.gameObject.GetComponent<UserGUI>().time = (int)time;
}
}
}
我们现在编写移动控制器MoveController脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveCamera : MonoBehaviour
{
void Start()
{
transform.position = new Vector3((float)1.11,1, (float)-18);
}
}
我们现在编写角色控制器RoleController脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RoleController :ClickAction
{
RoleModel roleModel;
IUserAction userAction;
//用户行为类userAction初始化
public RoleModelController()
{
userAction = SSDirector.GetInstance().CurrentSenceController as IUserAction;
}
//创建角色模型
public void CreateRole(Vector3 position, bool isPriest, int tag)
{
if (roleModel == null)
roleModel = new RoleModel();
roleModel.Init(position, isPriest, tag);
roleModel.role.GetComponent<Click>().setClickAction(this);
}
//调用角色模型
public RoleModel GetRoleModel()
{
return roleModel;
}
//处理点击事件
public void DealClick()
{
userAction.MoveRole(roleModel);
}
}
我们现在编写场景控制器SceneController脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//加载资源
public interface SceneController
{
void LoadResources();
}
我们现在编写系统控制器SceneController脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSDirector : System.Object
{
private static SSDirector _instance;
public ISceneController CurrentSenceController { get; set; }
public static SSDirector GetInstance()
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}
}
到此为止Controllers文件夹内的脚本编写完成。
Models内有如下几个脚本:
我们现在编写船只模型BoatModel:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BoatModel {
public GameObject boat; //船对象
public RoleModel[] roles; //船上的角色的指针
public bool isRight; //判断船在左侧还是右侧
public int priestNum, devilNum; //船上牧师与恶魔的数量
public void Init(Vector3 position)
{
if (boat != null)
Object.DestroyImmediate(boat);
priestNum = devilNum = 0;
roles = new RoleModel[2];
boat = GameObject.Instantiate(Resources.Load("Prefabs/boat", typeof(GameObject))) as GameObject;
boat.name = "boat";
boat.transform.position = position;
boat.transform.localScale = new Vector3(4, (float)1.5, 3);
boat.AddComponent<BoxCollider>();
boat.AddComponent<Click>();
isRight = false;
}
}
我们现在编写河岸模型LandModel脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LandModel
{
public GameObject land; //岸的游戏对象
public int priestNum, devilNum; //岸上牧师与恶魔的数量
public void Init(string name, Vector3 position)
{
if (land != null)
Object.DestroyImmediate(land);
priestNum = devilNum = 0;
land = GameObject.Instantiate(Resources.Load("Prefabs/land", typeof(GameObject))) as GameObject;
land.name = name;
land.transform.position = position;
land.transform.localScale = new Vector3(13, 5, 3);
}
}
我们现在编写角色模型RoleModel脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RoleModel{
public GameObject role; //角色的游戏对象
public bool isPriest; //区分角色是牧师还是恶魔
public int tag; //给对象标号,方便查找
public bool isRight; //区分角色是在左侧还是右侧
public bool isInBoat; //区分角色是在船上还是在岸上
//初始化函数
public void Init(Vector3 position, bool isPriest, int tag)
{
if (role != null)
Object.DestroyImmediate(role);
this.isPriest = isPriest;
this.tag = tag;
isRight = false;
isInBoat = false;
role = GameObject.Instantiate(Resources.Load("Prefabs/" + (isPriest ? "priest" : "devil"), typeof(GameObject))) as GameObject;
role.transform.localScale = new Vector3(1, 1, 1);
role.transform.position = position;
role.name = "role" + tag;
role.AddComponent<Click>();
role.AddComponent<BoxCollider>();
}
}
我们现在编写河流模型RiverModel脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RiverModel
{
private GameObject river; //河流的游戏对象
public RiverModel(Vector3 position)
{
river = Object.Instantiate(Resources.Load("Prefabs/river", typeof(GameObject))) as GameObject;
river.name = "river";
river.transform.position = position;
river.transform.localScale = new Vector3(15, 2, 3);
}
}
我们现在编写位置模型PositionModel脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PositionModel
{
public static Vector3 right_land = new Vector3(15, -4, 0); //右侧岸的位置
public static Vector3 left_land = new Vector3(-13, -4, 0); //左侧岸的位置
public static Vector3 river = new Vector3(1, -(float)5.5, 0); //河流的位置
public static Vector3 right_boat = new Vector3(7, -(float)3.8, 0); //船在右边的位置
public static Vector3 left_boat = new Vector3(-5, -(float)3.8, 0); //船在左边的位置
//角色在岸上的相对位置(6个)
public static Vector3[] roles = new Vector3[]{new Vector3((float)-0.2, (float)0.7, 0) ,new Vector3((float)-0.1, (float)0.7,0),
new Vector3(0, (float)0.7,0),new Vector3((float)0.1, (float)0.7,0),new Vector3((float)0.2, (float)0.7,0),new Vector3((float)0.3, (float)0.7,0)};
//角色在船上的相对位置(2个)
public static Vector3[] boatRoles = new Vector3[] { new Vector3((float)-0.1, (float)1.2, 0), new Vector3((float)0.2, (float)1.2, 0) };
}
我们现在编写点击事件Click脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Click : MonoBehaviour
{
ClickAction clickAction;
public void setClickAction(ClickAction clickAction)
{
this.clickAction = clickAction;
}
void OnMouseDown()
{
clickAction.DealClick();
}
}
到此为止Models文件夹内的脚本编写完成。
View内有如下一个脚本:
我们现在编写控制用户GUL的UserGUI脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UserGUI : MonoBehaviour
{
private IUserAction userAction;
public string gameMessage;
public int time;
public bool ButtionRestartIsShow;
void Start()
{
gameMessage = "";
time = 60;
userAction = SSDirector.GetInstance().CurrentSenceController as IUserAction;
ButtionRestartIsShow = false;
}
void OnGUI()
{
//小字体初始化
GUIStyle style = new GUIStyle();
style.normal.textColor = Color.white;
style.fontSize = 30;
//大字体初始化
GUIStyle bigStyle = new GUIStyle();
bigStyle.normal.textColor = Color.white;
bigStyle.fontSize = 50;
GUI.Label(new Rect(375, 30, 50, 200), "牧师与魔鬼", bigStyle);
GUI.Label(new Rect(320, 100, 50, 200), gameMessage, style);
GUI.Label(new Rect(0, 0, 100, 50), "Time: " + time, style);
if (GUI.Button(new Rect(470, 160, 100, 50), "Start"))
{
userAction.MyStart();
ButtionRestartIsShow = true;
}
if(ButtionRestartIsShow == true)
{
if (GUI.Button(new Rect(850, 50, 100, 50), "Retart"))
{
userAction.Restart();
}
}
}
}
到此为止View文件夹内的脚本编写完成。