unity3D游戏——魔鬼与牧师(Devil and Priest)的MVC实现

2023-11-23 00:30

本文主要是介绍unity3D游戏——魔鬼与牧师(Devil and Priest)的MVC实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

游戏对象及其对应的预制体

天空盒的制作

定义玩家行为

UML设计图

基于MVC架构的脚本文件

Models

船(Boat)

点击事件(Click)以及接口点击事件的动作(ClickAction)

陆地(Land)

位置(Position)

河流(River)

角色(Role)

Controllers

船控制器(BoatControl)

陆地控制器(LandControl)

角色控制器(RoleControl)

游戏对象的移动(Move)

游戏对象的移动控制器(MoveControl)

用户的操作动作(IUserAction)

场景控制的接口(ISceneController)

导演类(SSDirector)

主导全场的核心控制器(FirstController)

View

用户交互类(UserGUI)

结语


前言

魔鬼与牧师(Devil and Priest)是一个经典的智力解谜游戏,玩家需要帮助三位魔鬼和三位牧师,将所有的牧师带过河,同时还要确保牧师不会被魔鬼吃掉。

下面是游戏的规则和背景故事:

规则:
1. 在河的一边有三个魔鬼、三个牧师以及一条船。
2. 船最多可以承载两个角色(牧师或魔鬼)且必须承载一个角色。
3. 当河的一侧的魔鬼数量大于牧师数量时,魔鬼会吃掉牧师。
4. 玩家需要将所有的牧师都安全地运送到另一侧的河岸,而且保证没有牧师被魔鬼吃掉。

背景故事:
有三个牧师和三个魔鬼被困在一座孤岛上,他们想要回到对岸。但是,这座孤岛上有一条河,河上只有一艘小船,而且这条河非常危险,同时魔鬼会吃掉牧师。他们需要找到一种方法使所有的牧师都能够安全地过河。

玩家需要根据规则,使用合适的策略移动牧师和魔鬼,以确保所有的牧师都能够安全地过河。游戏中的挑战在于玩家需要平衡每一步的移动,避免牧师被魔鬼吃掉,并且在规定的条件下完成游戏。

魔鬼与牧师游戏是一个简单而有趣的逻辑解谜游戏,可以帮助玩家锻炼思维能力和问题解决能力。

首先让我们看一下游戏的演示视频吧:

魔鬼与牧师游戏演示视频

游戏对象及其对应的预制体

我们需要创建魔鬼与牧师的预制体,同时还需要船、陆地、河流,虽然陆地有左岸、右岸的区分,但是我们可以使用同一个预制体来创建两个对象来实现。预制体的制作完全可以按照你自己的需要以及审美来实现,具体的制作预制体的过程请自行百度查阅。

天空盒的制作

为了给游戏提供一个氛围感,我们可以给游戏添加一个天空盒,这一个天空盒我们可以自己导入素材,也可以自行制作。接下来我将介绍如何导入天空盒素材:

  • 下载天空盒相关项目,它会包含一些天空资源,例如:
    • 使用 skybox 在 unity Store 中搜索
    • 选择 Free Assets
    • 选择 Fantasy Skybox FREE,然后 Add to my Assets
    • 选择 Open in Unity
    • 网页自动打开 Editor 的 Package manager
    • 选择 Fantasy Skybox FREE 包,点 download 按钮
    • 完成后 Import 导入。导入所有资源
    • 最后,Fantasy Skybox FREE 目录出现在你的项目中
      • CubeMaps(立方图和六面体贴图)
      • Panoramics(全景图)
      • Scenes(演示场景)
  • 在导入素材后我们也可以自己定义天空盒材料
    • Assets 上下文菜单 -> create -> Material 起名 mysky
    • 在 Inspector 视图中选择 Shader 是上述三种之一,例如:skybox/6side
    • 将对应纹理(texture)/图片拖入对应参数
  • 而如果要将天空盒分配给您正在处理的场景,请执行以下操作:
    • 从菜单栏中选择 Window > Rendering > Lighting Settings。
    • 在随后出现的窗口中选择 Environment 选项卡。
    • 将新的天空盒材质拖放到 Skybox Material 字段。

如图所示,在本游戏中,这是我所添加的天空盒,具体的天空盒实现以及选择完全按照你自己的想法实现即可。

定义玩家行为

行为效果备注
点击魔鬼或者牧师魔鬼以及牧师在船和岸上来回切换必须点击魔鬼或者牧师其中的一个人
点击船船在左右岸上来回移动船上至少有一个角色,最多两个角色

UML设计图

在本游戏的实现中,我们采用的是MVC分离版本,我们在实现游戏时,将会创建model、controller以及view的脚本文件,分别实现相对应的功能。同时最核心的脚本代码就是controller里面的firstcontroller,同时还有导演类、用户交互类以及GUI类等等,首先我们需要使用UML绘图工具为各个文件的交互关系设计UML图,如图所示:

基于MVC架构的脚本文件

Models

脚本文件主要实现了该游戏的主要几个游戏对象——魔鬼、牧师、船、陆地、河流,以及角色的位置确定、处理点击事件的声明等。下面是Mode各个脚本文件的具体实现:

船(Boat)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Boat
{   //创建一个船对象public GameObject boat;//创建一个数组存储在船上的对象public Role[] roles;//判断该船是否在岸的右边public bool isRight;//创建变量分别存储船上的牧师和魔鬼的数量public int priestCount, devilCount;//Boat的构造函数public Boat(Vector3 position) {//从资源中加载船的预制体,并且使用Instantiate方法创建该预制体的游戏对象boat = GameObject.Instantiate(Resources.Load("prefab/boat", typeof(GameObject))) as GameObject;//将该游戏对象命名为“boat”boat.name = "boat";//接受一个position的输入,将我们创建的船的对象的position设置为接收的positionboat.transform.position = position;//设置船对象在各个方向轴上的缩放比例boat.transform.localScale = new Vector3(2.8f,0.4f,2);//在船上创建一个拥有两个位置的数组roles = new Role[2];//初始化为船刚开始在岸的左边isRight = false;//初始化船上的牧师与魔鬼数量为0priestCount = devilCount = 0;//接受组件盒子碰撞以及点击事件boat.AddComponent<BoxCollider>();boat.AddComponent<Click>();}
}

点击事件(Click)以及接口点击事件的动作(ClickAction)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Click : MonoBehaviour
{//声明变量,用于存储点击事件的处理方法ClickAction clickAction;//用于接收点击事件的参数,并且将其传给类中定义的clickActionpublic void setClickAction(ClickAction clickAction) {this.clickAction = clickAction;}//unity的一个内置方法,可以将点击事件的处理方式委托给其它方法void OnMouseDown() {clickAction.DealClick();}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;//提供一个接口
public interface ClickAction
{//提供一个函数来解决点击事件void DealClick();
}

陆地(Land)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Land 
{//创建一个陆地的游戏对象public GameObject land;//创建变量用于存储在陆地上的牧师与魔鬼的数量public int priestCount, devilCount;//land的构造函数public Land (Vector3 position){//从资源中加载陆地的预制体,并且使用Instantiate方法创建该预制体的游戏对象land = GameObject.Instantiate(Resources.Load("prefab/land", typeof(GameObject))) as GameObject;//设置陆地的游戏对象在各个方向轴上的缩放比例大小land.transform.localScale = new Vector3(8,4.8f,2);//接受构造函数传进来的position//将陆地对象的position设置为传进来的positionland.transform.position = position;//初始化陆地上牧师和魔鬼的数量为0priestCount = devilCount = 0;}
}

位置(Position)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Position  //存储所有对象的位置
{//游戏对象的固定位置(世界坐标)public static Vector3 left_land = new Vector3(-8,-3,0);public static Vector3 right_land = new Vector3(8,-3,0);public static Vector3 river = new Vector3(0,-4,0);public static Vector3 left_boat = new Vector3(-2.3f,-2.3f,-0.4f);public static Vector3 right_boat = new Vector3(2.4f, -2.3f, -0.4f);//角色相对于(在)岸边的位置(相对坐标)public static Vector3[] role_land = new Vector3[] {new Vector3(0.4f,0.77f,0), new Vector3(0.25f,0.77f,0), new Vector3(0.1f,0.77f,0), new Vector3(-0.05f,0.77f,0), new Vector3(-0.2f,0.77f,0), new Vector3(-0.35f,0.77f,0)};//角色相对于(在)船的位置(相对坐标)public static Vector3[] role_boat = new Vector3[] {new Vector3(0.2f,3,0), new Vector3(-0.2f,3,0)};}

河流(River)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class River
{//创建一个河流的游戏对象public GameObject river;//河流的构造函数,接受传进来的positionpublic River(Vector3 position) {//从资源中加载河流的预制体,并且使用Instantiate方法创建该预制体的游戏对象river = GameObject.Instantiate(Resources.Load("prefab/river", typeof(GameObject))) as GameObject;//命名河流游戏对象为riverriver.name = "river";  //设置河流在各个方向轴上的缩放比例river.transform.localScale = new Vector3(8,2.5f,2);//将河流的position设置为构造函数传进来的positionriver.transform.position = position;}
}

角色(Role)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Role 
{   //创建一个角色的游戏对象public GameObject role;//创建一个变量判断该角色是否为牧师public bool isPriest;//创建一个变量判断角色是否在船上public bool inBoat;//创建一个变量判断角色是否在岸的右边public bool onRight;//创建一个变量来标记该角色是属于第几个对象public int id;//Role的构造函数,接受参数为position、isPriest以及idpublic Role (Vector3 position, bool isPriest, int id) {//绑定参数this.isPriest = isPriest;this.id = id;//初始化各个变量onRight = false;inBoat = false;//从资源中加载角色的预制体,并且使用Instantiate方法创建该预制体的游戏对象//判断传进来的参数isPriest是否为True,如果是,那么加载牧师的预制体//如果不是,就加载魔鬼的预制体role = GameObject.Instantiate(Resources.Load("prefab/" + (isPriest ? "priest" : "devil"), typeof(GameObject))) as GameObject;//重命名该游戏角色role.name = "role" + id; //设置角色在各个方向轴上的缩放比例大小role.transform.localScale = new Vector3(1,1.2f,1);//将角色的position设置为构造函数传进来的positionrole.transform.position = position;//接收组件盒子碰撞以及点击事件role.AddComponent<Click>();role.AddComponent<BoxCollider>();}
}

Controllers

这些脚本文件主要实现了一些控制器,用于控制船、角色、陆地、运动以及用户交互动作、场景更新控制等等,其中,最为核心的控制部分为FirstController,它协调了所有的控制器,是本游戏实现的主要大脑,负责游戏的生成和变化、以及判断游戏的状态等等。下面是Controllers各个部分的脚本文件的实现代码:

船控制器(BoatControl)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class BoatControl : ClickAction
{//首先创建一个船的类对象Boat boatModel;//创建一个用户操作动作的类对象,用于用户进行操作IUserAction userAction;//构造函数public BoatControl() {//实现使用userAction变量来调用IuserAction接口的方法,处理船的控制逻辑userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;}//定义一个函数,用来创建一个船的对象public void CreateBoat(Vector3 position) {//如果船的对象为空,那么就销毁该船的游戏对象if (boatModel != null) {Object.DestroyImmediate(boatModel.boat);}//新建一个船的游戏对象,并且赋给类中定义的对象boatModel = new Boat(position);//获取Click组件,调用setClickAction方法boatModel.boat.GetComponent<Click>().setClickAction(this);}//返回当前的船的游戏对象public Boat GetBoatModel() {return boatModel;}//将角色从岸上移动到船上,接收的对象为需要移动的角色对象,返回移动后的角色的位置坐标public Vector3 AddRole(Role roleModel) {//首先初始化index=-1,然后判断船上是否有空的位置可以容纳角色对象int index = -1;if (boatModel.roles[0] == null) index = 0;else if (boatModel.roles[1] == null) index = 1;//如果船上已经满员了,返回角色的原始的位置if (index == -1) return roleModel.role.transform.localPosition;//如果没有满员,将角色添加到船上的对应位置boatModel.roles[index] = roleModel;//同时更新角色是否在船上的标记变量roleModel.inBoat = true;//将角色挂载到船上,以后的位置变换为相对船而变换roleModel.role.transform.parent = boatModel.boat.transform;//更新船上的角色(牧师还是魔鬼)数量if (roleModel.isPriest) boatModel.priestCount++;else boatModel.devilCount++;//返回角色的变换后的位置信息return Position.role_boat[index];}//将角色从船上移动到岸上,接收参数为角色对象,表示需要移动的对象public void RemoveRole(Role roleModel) {//循环遍历船上的角色数组for (int i = 0; i < 2; ++i){//如果当前遍历的角色与需要移动的角色相同,那么就移动该角色到岸上if (boatModel.roles[i] == roleModel) {boatModel.roles[i] = null;//更新船上的角色(牧师还是魔鬼)数量if (roleModel.isPriest) boatModel.priestCount--;else boatModel.devilCount--;break;}}}//重写DealClick函数public void DealClick() {//如果船上的角色数组不为空,那么调用移动角色的函数if (boatModel.roles[0] != null || boatModel.roles[1] != null) {userAction.MoveBoat();}}
}

陆地控制器(LandControl)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LandControl
{//首先创建一个陆地的对象Land landModel;//用于创建陆地对象,接收参数为positionpublic void CreateLand(Vector3 position) {//如果陆地对象为空,那么创建一个新的陆地对象if (landModel == null) {landModel = new Land(position);}}//用于获取陆地的对象public Land GetLand() {return landModel;}//将角色添加到岸上,接收参数为一个角色对象,然后返回角色在岸上的相对坐标public Vector3 AddRole2Land(Role roleModel) {//将角色挂载到陆地对象上,那么以后的操作都是相对于陆地了roleModel.role.transform.parent = landModel.land.transform;//更新角色对象中的是否在船上的变量为falseroleModel.inBoat = false;//更新陆地上的角色(牧师还是魔鬼)数量if (roleModel.isPriest) landModel.priestCount++;else landModel.devilCount++;//返回角色移动后的位置信息return Position.role_land[roleModel.id];}//将角色从岸上移除public void RemoveRole(Role roleModel) {//更新陆地对象中,角色(牧师还是魔鬼)的数量if (roleModel.isPriest) landModel.priestCount--;else landModel.devilCount--;}
}

角色控制器(RoleControl)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//用于控制对象的动作
public class RoleControl : ClickAction
{//首先创建一个需要操作的角色对象Role roleModel;//创建一个用户操作动作的类对象,用于用户进行操作IUserAction userAction;//构造函数,实现使用userAction变量来调用IuserAction接口的方法,处理角色的控制逻辑public RoleControl() {userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;}//用于创建一个新的角色对象,接收参数为位置信息,是否为牧师信息,以及角色的id号public void CreateRole(Vector3 position, bool isPriest, int id) {//如果角色对象不为空,那么将该对象销毁if (roleModel != null) {Object.DestroyImmediate(roleModel.role);}//创建一个新的角色对象roleModel = new Role(position, isPriest, id);//获取Click组件,调用setClickAction方法来处理roleModel.role.GetComponent<Click>().setClickAction(this);}//获取返回的角色对象public Role GetRoleModel() {return roleModel;}//重写DealClick函数,调用移动角色的函数public void DealClick() {userAction.MoveRole(roleModel);}
}

游戏对象的移动(Move)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//用于控制对象的移动行为
public class Move : MonoBehaviour
{//用于标记该对象是否正在移动public bool isMoving = false;//表示该对象的移动速度public float speed = 5;//表示移动的对象的目标位置public Vector3 destination;//表示了移动对象到目标位置的中间位置,主要是为了产生一个动态以及美观的移动public Vector3 mid_destination;// Update is called once per framevoid Update(){//如果对象的移动目标等于对象正在的位置上,那么表示没有移动if (transform.localPosition == destination) {isMoving = false;return;}//否则,就是角色正在移动isMoving = true;//如果移动目标的x与y位置与角色正在的位置不同,那么移动该角色到目标的中间位置if (transform.localPosition.x != destination.x && transform.localPosition.y != destination.y) {transform.localPosition = Vector3.MoveTowards(transform.localPosition, mid_destination, speed * Time.deltaTime);}//否则,表示角色已经到达了中间位置,需要将角色移动到目标位置else {transform.localPosition = Vector3.MoveTowards(transform.localPosition, destination, speed * Time.deltaTime);}}
}

游戏对象的移动控制器(MoveControl)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//用于控制移动的行为
public class MoveControl 
{//创建一个游戏对象,表示需要移动的游戏对象GameObject moveObject;//用于获取移动状态public bool GetIsMoving() {//当需要移动的对象不为空,而且附加的Move组件的ismoving为True时,返回Truereturn (moveObject != null && moveObject.GetComponent<Move>().isMoving == true);}//用于设置移动目标和相关参数,接收参数为目标位置以及一个需要移动的对象public void SetMove(Vector3 destination, GameObject moveObject) {//创建一个Move对象,用于对象的移动实现Move model;//将参数绑定this.moveObject = moveObject;//如果moveObject没有附加Move组件,那么为该游戏对象附加一个Move组件if (!moveObject.TryGetComponent<Move>(out model)) {moveObject.AddComponent<Move>();}//绑定Move组件中的移动目标位置this.moveObject.GetComponent<Move>().destination = destination;//如果需要移动对象的目标y位置大于目标的y位置,就将一个新的Vector3对象赋给移动对象的Move组件中的中间位置信息//其中x和z为目标的x和z,而y为对象本地的yif (this.moveObject.transform.localPosition.y > destination.y) {this.moveObject.GetComponent<Move>().mid_destination = new Vector3(destination.x, this.moveObject.transform.localPosition.y, destination.z);}else {//将一个新的Vector3对象赋给Move组件中的中间位置//其中x为移动对象的本地的x,而y和z为目标的y和zthis.moveObject.GetComponent<Move>().mid_destination = new Vector3(this.moveObject.transform.localPosition.x, destination.y, destination.z);}}
}

用户的操作动作(IUserAction)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//创建一个接口,定义用户的一些基本操作方式
public interface IUserAction {//用于移动船void MoveBoat();//用于移动角色,将角色从岸上移动到船上//将角色从船上移动到岸上void MoveRole(Role roleModel);//检查游戏的状态与条件void Check();
}

场景控制的接口(ISceneController)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//用于场景控制的接口
//而其中的LoadResources方法用于加载场景所需的资源
public interface ISceneController
{void LoadResources();
}

导演类(SSDirector)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//实现了单例模式,保证了在整个游戏的过程中只有一个实例存在
//方便访问和管理游戏中的全局对象,协调游戏场景和场景控制器
public class SSDirector : System.Object
{//静态变量,用于保存类的唯一实例static SSDirector _instance;//用于获取和设置当前场景的控制器对象public ISceneController CurrentSceneController {get; set;}//用于获取类的实例public static SSDirector GetInstance() {if (_instance == null) {_instance = new SSDirector();}return _instance;}
}

主导全场的核心控制器(FirstController)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//这是本游戏的最核心的控制部分,协调所有游戏对象的实现与信息调试
public class FirstController : MonoBehaviour, ISceneController, IUserAction {//创建陆地的对象,为左岸以及右岸LandControl leftLandController, rightLandController;//创建河流对象River river;//创建一个船控制器,调用将角色移动到船上或者反过来的实现函数BoatControl boatController;//创建一个角色控制器数组,每一个角色对象都需要一个独立的控制器RoleControl[] roleControllers;//创建一个控制移动的控制器对象MoveControl moveController;//创建一个布尔变量,用于标记游戏是否在运行bool isRunning;//创建一个变量,标记游戏的剩余时间float time;//用于加载资源、初始化游戏场景public void LoadResources() {//初始化每一个角色控制器roleControllers = new RoleControl[6];for (int i = 0; i < 6; ++i) {roleControllers[i] = new RoleControl();roleControllers[i].CreateRole(Position.role_land[i], i < 3 ? true : false, i);}//初始化陆地控制器//创建左岸与右岸以及获取名字leftLandController = new LandControl();leftLandController.CreateLand(Position.left_land);leftLandController.GetLand().land.name = "left_land";rightLandController = new LandControl();rightLandController.CreateLand(Position.right_land);rightLandController.GetLand().land.name = "right_land";//初始化地将人物添加并定位到左岸  foreach (RoleControl roleController in roleControllers){roleController.GetRoleModel().role.transform.localPosition = leftLandController.AddRole2Land(roleController.GetRoleModel());}//初始化船控制器,创建一个船控制器即可boatController = new BoatControl();boatController.CreateBoat(Position.left_boat);//初始化一条河river = new River(Position.river);//初始化移动控制器moveController = new MoveControl();//更新游戏是否运行为TrueisRunning = true;//初始化游戏剩余时间为60秒time = 60;}//实现移动船的函数public void MoveBoat() {//如果游戏没有运行或者移动控制器里面的获取对象是否移动为False时,就直接返回,函数结束if (isRunning == false || moveController.GetIsMoving()) return;//检查船的当前位置,然后设置船需要移动的目标位置(左岸或者右岸)if (boatController.GetBoatModel().isRight) {moveController.SetMove(Position.left_boat, boatController.GetBoatModel().boat);}else {moveController.SetMove(Position.right_boat, boatController.GetBoatModel().boat);}//更新参数boatController.GetBoatModel().isRight = !boatController.GetBoatModel().isRight;}//实现角色的移动的函数public void MoveRole(Role roleModel) {//如果游戏没有运行或者移动控制器里面的获取对象是否移动为False时,就直接返回,函数结束if (isRunning == false || moveController.GetIsMoving()) return;//如果角色在船上,那么根据船的位置设定移动的方向if (roleModel.inBoat) {if (boatController.GetBoatModel().isRight) {moveController.SetMove(rightLandController.AddRole2Land(roleModel), roleModel.role);}else {moveController.SetMove(leftLandController.AddRole2Land(roleModel), roleModel.role);}//检查角色的位置与岸的位置是否匹配,如果匹配,那么移动角色到岸上roleModel.onRight = boatController.GetBoatModel().isRight;boatController.RemoveRole(roleModel);}//如果角色在岸上,检查角色所在岸和船所在岸是否匹配,如果匹配,就将角色移动到船上else {if (boatController.GetBoatModel().isRight == roleModel.onRight) {if (roleModel.onRight) {rightLandController.RemoveRole(roleModel);}else {leftLandController.RemoveRole(roleModel);}moveController.SetMove(boatController.AddRole(roleModel), roleModel.role);}}}//检查游戏的状态与判断游戏是否获胜或者失败public void Check() {//如果游戏没有运行,那么直接返回,函数结束if (isRunning == false) return;//通过组件获取userGui的组件,并且将游戏提示清空this.gameObject.GetComponent<UserGUI>().gameMessage = "";//如果右边岸上的牧师等于三个,那么游戏结束,并且将游戏提示设为“You Win!”//同时更新游戏是否运行的参数if (rightLandController.GetLand().priestCount == 3) {this.gameObject.GetComponent<UserGUI>().gameMessage = "You Win!";isRunning = false;}//否则,如果左岸上的牧师数量不为0且小于魔鬼数量//或者右岸上的牧师数量不为0且小于魔鬼数量//或者游戏时间结束了//判断游戏结束,同时更新游戏是否运行的参数else {int leftPriestCount, rightPriestCount, leftDevilCount, rightDevilCount;leftPriestCount = leftLandController.GetLand().priestCount + (boatController.GetBoatModel().isRight ? 0 : boatController.GetBoatModel().priestCount);rightPriestCount = rightLandController.GetLand().priestCount + (boatController.GetBoatModel().isRight ? boatController.GetBoatModel().priestCount : 0);leftDevilCount = leftLandController.GetLand().devilCount + (boatController.GetBoatModel().isRight ? 0 : boatController.GetBoatModel().devilCount);rightDevilCount = rightLandController.GetLand().devilCount + (boatController.GetBoatModel().isRight ? boatController.GetBoatModel().devilCount : 0);if (leftPriestCount != 0 && leftPriestCount < leftDevilCount || rightPriestCount != 0 && rightPriestCount < rightDevilCount) {this.gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";isRunning = false;}}}//用于初始化场景和游戏对象//SSDirector是一个单例模式的类,通过GetInstance()方法获取实例//然后将当前场景控制器设置为当前对象,以便其他部分可以通过SSDirector来访问和管理场景控制器。void Awake() {SSDirector.GetInstance().CurrentSceneController = this;LoadResources();//向当前对象(即当前脚本所在的对象)添加了一个 UserGUI 组件。this.gameObject.AddComponent<UserGUI>();}//更新函数void Update() {//如果游戏正在运行,不断更新游戏结束的时间if (isRunning) {time -= Time.deltaTime;//将游戏剩余时间赋给组件的time属性,用于在界面上显示游戏剩余时间this.gameObject.GetComponent<UserGUI>().time = (int)time;//如果时间小于等于0,那么判断游戏结束,同时更新参数if (time <= 0) {this.gameObject.GetComponent<UserGUI>().time = 0;this.gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";isRunning = false;}}}
}

View

在该部分View中,只有一个脚本文件,主要功能是实现与用户交互的GUI类,用于提示游戏失败与成功,同时倒计时游戏的剩余时间。下面是该脚本文件的具体代码实现:

用户交互类(UserGUI)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//负责显示游戏界面和一些文本信息
public class UserGUI : MonoBehaviour
{//创建一个用户交互的对象,用于引入用户交互的接口IUserAction userAction;//创建一个变量用于存储需要显示的信息public string gameMessage ;//创建一个变量,用于计算游戏的剩余时间public int time;//unity内置的类GUIStyle style, bigstyle;// Start is called before the first frame updatevoid Start(){//初始化一些样式,主要是字体显示的样式//初始化时间为60秒time = 60;//获取当前场景的用户操作接口,并且将其赋给userAction变量userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;style = new GUIStyle();style.normal.textColor = Color.white;style.fontSize = 30;bigstyle = new GUIStyle();bigstyle.normal.textColor = Color.white;bigstyle.fontSize = 50;}// Update is called once per framevoid OnGUI() {//不断检查游戏状态userAction.Check();//绘制三个标签用户显示信息GUI.Label(new Rect(160, Screen.height * 0.1f, 50, 200), "Prieists and Devils", bigstyle);GUI.Label(new Rect(250, 100, 50, 200), gameMessage, style);GUI.Label(new Rect(0,0,100,50), "Time: " + time, style);}
}

结语

至此,该游戏——魔鬼与牧师的unity实现就介绍完毕了,在该游戏实现中,我们主要采用了MVC架构将游戏的各个部件拆分开来实现,同时有一个最核心的控制器来控制协调所有的控制器以及Model,还有一些用户交互动作的实现也分开在不同的类文件中,还有场景更新等等。我们将所有的代码实现划分到各个不同的文件中,以及采用MVC架构的目的主要是为了使得我们的代码更加清晰、有序以及可维护,使得我们的代码可扩展。

在下一个实验中,我们将会介绍该游戏——魔鬼与牧师的动作分离版本,即不再采用MVC架构,而是采用动作分离的方式来维护我们的脚本代码文件。

最后,让我们一起动起手来,实现属于我们自己的游戏吧!

完整代码的Github地址:Github代码地址

这篇关于unity3D游戏——魔鬼与牧师(Devil and Priest)的MVC实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/414022

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

国产游戏崛起:技术革新与文化自信的双重推动

近年来,国产游戏行业发展迅猛,技术水平和作品质量均得到了显著提升。特别是以《黑神话:悟空》为代表的一系列优秀作品,成功打破了过去中国游戏市场以手游和网游为主的局限,向全球玩家展示了中国在单机游戏领域的实力与潜力。随着中国开发者在画面渲染、物理引擎、AI 技术和服务器架构等方面取得了显著进展,国产游戏正逐步赢得国际市场的认可。然而,面对全球游戏行业的激烈竞争,国产游戏技术依然面临诸多挑战,未来的