unity 3D 牧师与魔鬼小游戏

2023-12-16 11:10
文章标签 3d unity 小游戏 魔鬼 牧师

本文主要是介绍unity 3D 牧师与魔鬼小游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、游戏说明文本与基本规则

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图

MVC:

我们在设计该游戏时使用MVC架构(模型、界面、控制器)。

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

模型(Model):数据对象及关系,游戏对象、空间关系,在游戏中,一切实体都可以被视为是模型,这种实体可以是具体的,也可以是抽象的。
控制器(Controller):接受用户事件,控制模型的变化,一个场景一个主控制器,至少实现与玩家交互的接口(IPlayerAction,实现或管理运动
界面(View):显示模型,将人机交互事件交给控制器处理,处收 Input 事件,渲染 GUI ,接收事件。
我们将刚才提及的游戏中的事物或者说游戏对象都分为模型Model,他们受到Controller控制器的控制进行游戏行为,具体的模型类有:Boat(船)、Land(河岸/陆地)、Move(处理游戏对象的移动)、Position(处理游戏对象的初始坐标)、Role(调整游戏对象方向方便移动);

控制器类需要接受用户事件并控制模型的变化,比如Boat模型就会有BoatController,BoatController会控制一切以船为对象的行为,比如:处理船被点击时的事件、往船上添加一名乘客,往船上减少一名乘客。必须要注意的是,直接控制模型的控制器之间是没有耦合关系的,也就是说,BoatController只能知道当前船上有多少名乘客,但是不知道他们都是谁(这个信息应当由各RoleController保存),还有,因为移动船要同时移动船上的所有乘客,但是BoatController不能直接移动乘客,因此实际上真正移动船的逻辑不能直接写在BoatController里(具体应该写在哪下文说)。Move模型就会有MoveController,MoveController控制了谁要移动,而且负责判断当前是否有模型正在移动。另外还有一类很重要的控制器就是场景控制器和导演。游戏就像是一场话剧,话剧会有多个场景,同时有一个导演贯穿了所有场景,导演控制器必须被写成单例模式,这就确保了各控制器属于同一场“话剧”。每一个场景都会有一个单独的场景控制器,但是在这个游戏里,只有唯一一个场景(FirstController)场景控制器管理所有该场景内的模型控制器,并且实现他们的综合行为。就这个游戏而言,FirstController会生成并且管理:一个BoatController,两个LandController,六个RoleController,一个MoveController。所有该场景内的综合行为都会在这个场景控制器内实现。比如:移动船,涉及到船和乘客的同时移动;移动角色,涉及到离岸、登船、人状态的转变三方信息;判断当前游戏是否成功或者失败。最后,以I开头的控制器都是接口,用于规范同一类型的控制器应当具有的行为,比如IObjectContoller应当被所有模型的控制器继承、IScenceController应当被所有场景的控制器继承、IUserAction负责与视图方面沟通的接口。

界面类显示游戏结果和提供用户交互渠道,具体的界面类为UserGUI(用户交互界面)管理比如说:标题、重新开始的按钮、游戏成功或者失败时的提示。

uml图如下:


  

三、具体实现

项目文件结构如下:

其中Resources为游戏对象的预制和材质,里面有四个游戏对象的预制及其材质

Scenes为游戏所用的场景,由于我们在该游戏中并没有切换场景,所以只需要使用默认场景即可

Scripts是代码部分也是我们设计中最重要的部分,我们按照MVC架构组织代码结构

Model部分:

Boat类:

public class Boat
{public GameObject boat;//船对象public Boat(Vector3 initPos){boat = GameObject.Instantiate(Resources.Load("Prefabs/boat", typeof(GameObject))) as GameObject;boat.transform.position = initPos;boat.AddComponent<Click>();}}

Click类:

public class Click : MonoBehaviour
{IObjectController clickAction;public void setClickAction(IObjectController clickAction) {this.clickAction = clickAction;}void OnMouseDown() {clickAction.DealClick();}
}

 Land类:

public class Land
{public GameObject land;public Land(Vector3 initPos){land = GameObject.Instantiate(Resources.Load("Prefabs/land", typeof(GameObject))) as GameObject;land.transform.position = initPos;}
}

Move类:

public class Move : MonoBehaviour
{public bool isMoving;public float speed = 5;public Vector3 destination;void Update(){if (transform.localPosition == destination) {isMoving = false;return;}isMoving = true;transform.localPosition = Vector3.MoveTowards(transform.localPosition, destination, speed * Time.deltaTime);}
}

Position类:

public class Position
{public static Vector3 boatLeftPos = new Vector3(-3.5f, -1, 12);public static Vector3 boatRightPos = new Vector3(3.5f, -1, 12);public static Vector3[] roleLeftPos = new Vector3[6]{new Vector3(-11, 0, 12), new Vector3(-10, 0, 12),new Vector3(-9, 0, 12),new Vector3(-8, 0, 12),new Vector3(-7, 0, 12),new Vector3(-6, 0, 12)};public static Vector3[] roleRightPos = new Vector3[6]{new Vector3(6, 0, 12), new Vector3(7, 0, 12),new Vector3(8, 0, 12),new Vector3(9, 0, 12),new Vector3(10, 0, 12),new Vector3(11, 0, 12)};public static Vector3[] landInitPos = new Vector3[2]{new Vector3(-8, -1, 12), new Vector3(8, -1, 12)};public static Vector3[] seatLeftPos = new Vector3[3]{new Vector3(-4.5f, 0, 12), new Vector3(-3.5f, 0, 12), new Vector3(-2.5f, 0, 12)};public static Vector3[] seatRightPos = new Vector3[3]{new Vector3(2.5f, 0, 12), new Vector3(3.5f, 0, 12), new Vector3(4.5f, 0, 12)};
}

Role类:

public class Role
{public GameObject role;public Role(int roleType, Vector3 initPos){string path = "Prefabs/" + ((roleType == FirstController.PRIEST) ? "priest" : "devil");role = GameObject.Instantiate(Resources.Load(path, typeof(GameObject))) as GameObject;role.transform.position = initPos;role.AddComponent<Click>();}
}

View部分:

UserGUI类:

public class UserGUI : MonoBehaviour
{IUserAction userAction;GUIStyle msgStyle, titleStyle;void Start(){userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;msgStyle = new GUIStyle();msgStyle.normal.textColor = Color.white;msgStyle.alignment = TextAnchor.MiddleCenter;msgStyle.fontSize = 30;titleStyle = new GUIStyle();titleStyle.normal.textColor = Color.white;titleStyle.alignment = TextAnchor.MiddleCenter;titleStyle.fontSize = 60;}void OnGUI() {// 重新开始的按钮if(GUI.Button(new Rect(Screen.width*0.4f, Screen.height*0.65f, Screen.width*0.2f, Screen.height*0.1f), "Restart")){userAction.Restart();}// 检查是否正确GUI.Label(new Rect(0, 0, Screen.width, Screen.height*0.2f), "Preists and Devils", titleStyle);if(userAction.GetGameState() == FirstController.WIN){GUI.Label(new Rect(0, Screen.height*0.8f, Screen.width, Screen.height*0.2f), "You Win.", msgStyle);}     else if(userAction.GetGameState() == FirstController.FAILED){GUI.Label(new Rect(0, Screen.height*0.8f, Screen.width, Screen.height*0.2f), "You failed.", msgStyle);}}
}

Controller部分:

BoatController类:

public class BoatController : IObjectController
{public bool onLeftside;IUserAction userAction;public int[] seat;public Boat boatModel;public BoatController(){userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;seat = new int[3];Reset();}public void Reset(){onLeftside = true;for(int i = 0; i < 3; i++){seat[i] = -1;}}public int embark(int roleID){for(int i = 0; i < 3; i++){if(seat[i] == -1){seat[i] = roleID;return i;}}return -1;}public int getEmptySeat(){for(int i = 0; i < 3; i++){if(seat[i] == -1){return i;}}return -1;}public void disembark(int roleID){for(int i = 0; i < 3; i++){if(seat[i] == roleID){seat[i] = -1;return;}}}public void CreateModel(){boatModel = new Boat(Position.boatLeftPos);boatModel.boat.GetComponent<Click>().setClickAction(this);}public void DealClick(){userAction.MoveBoat();}public GameObject GetModelGameObject(){return boatModel.boat;}}

FirstController类:

public class FirstController : MonoBehaviour, ISceneController, IUserAction
{public static int LEFTLAND = 0;public static int RIGHTLAND = 1;public static int BOAT = 2;public static int PRIEST = 0;public static int DEVIL = 1;public static int PLAYING = 0;public static int WIN = 1;public static int FAILED = 2;BoatController BoatCtrl;RoleController[] RoleCtrl = new RoleController[6];LandController[] LandCtrl = new LandController[2];MoveController MoveCtrl;int[] rolesID = new int[6]{0,1,2,3,4,5};int gameState;void Awake(){SSDirector director = SSDirector.GetInstance();director.CurrentSceneController = this;director.CurrentSceneController.Initialize();}public void Initialize(){//如果有,则释放原有的GameObjectfor(int i = 0; i < 6; i++){if(RoleCtrl[i] != null){Destroy(RoleCtrl[i].GetModelGameObject());}}for(int i = 0; i < 2; i++){if(LandCtrl[i] != null){Destroy(LandCtrl[i].GetModelGameObject());}}if(BoatCtrl != null){Destroy(BoatCtrl.GetModelGameObject());}// 加载控制器和模型BoatCtrl = new BoatController();BoatCtrl.CreateModel();for(int i = 0; i < 6; i++){int roleType = (i < 3) ? PRIEST : DEVIL;RoleCtrl[i] = new RoleController(roleType, rolesID[i]);RoleCtrl[i].CreateModel();}LandCtrl[0] = new LandController(LEFTLAND, rolesID);LandCtrl[1] = new LandController(RIGHTLAND, rolesID);LandCtrl[0].CreateModel();LandCtrl[1].CreateModel();MoveCtrl = new MoveController();//开始游戏gameState = PLAYING;}//将角色的ID转换成数组的下标int IDToNumber(int ID){for(int i = 0; i < 6; i++){if(rolesID[i] == ID){return i;}}return -1;}//点击船时执行public void MoveBoat(){if(gameState != PLAYING || MoveCtrl.IsMoving()) return;CheckAndSetGameState();if(BoatCtrl.onLeftside){MoveCtrl.SetMove(BoatCtrl.GetModelGameObject(), Position.boatRightPos);for(int i = 0; i < 3; i++){if(BoatCtrl.seat[i] != -1){RoleController r = RoleCtrl[IDToNumber(BoatCtrl.seat[i])];MoveCtrl.SetMove(r.GetModelGameObject(), Position.seatRightPos[i]);}}}else{MoveCtrl.SetMove(BoatCtrl.GetModelGameObject(), Position.boatLeftPos);for(int i = 0; i < 3; i++){if(BoatCtrl.seat[i] != -1){RoleController r = RoleCtrl[IDToNumber(BoatCtrl.seat[i])];MoveCtrl.SetMove(r.GetModelGameObject(), Position.seatLeftPos[i]);}} }BoatCtrl.onLeftside = !BoatCtrl.onLeftside;}//点击角色时执行public void MoveRole(int id){int num = IDToNumber(id);if(gameState != PLAYING || MoveCtrl.IsMoving()) return;int seat;switch(RoleCtrl[num].roleState){case 0: // LEFTLANDif(!BoatCtrl.onLeftside) return;LandCtrl[0].LeaveLand(id);seat = BoatCtrl.embark(id);RoleCtrl[num].GoTo(BOAT);if(seat == -1) return;MoveCtrl.SetMove(RoleCtrl[num].GetModelGameObject(), Position.seatLeftPos[seat]);break;case 1: // RIGHTLANDif(BoatCtrl.onLeftside) return;LandCtrl[1].LeaveLand(id);seat = BoatCtrl.embark(id);RoleCtrl[num].GoTo(BOAT);if(seat == -1) return;MoveCtrl.SetMove(RoleCtrl[num].GetModelGameObject(), Position.seatRightPos[seat]);break;case 2: //BOATif(BoatCtrl.onLeftside){seat = LandCtrl[0].getEmptySeat();BoatCtrl.disembark(id);LandCtrl[0].GoOnLand(id);RoleCtrl[num].GoTo(LEFTLAND);MoveCtrl.SetMove(RoleCtrl[num].GetModelGameObject(), Position.roleLeftPos[seat]);}else{seat = LandCtrl[1].getEmptySeat();BoatCtrl.disembark(id);LandCtrl[1].GoOnLand(id);RoleCtrl[num].GoTo(RIGHTLAND);MoveCtrl.SetMove(RoleCtrl[num].GetModelGameObject(), Position.roleRightPos[seat]);}break;default: break;}}//判断游戏状态public void CheckAndSetGameState(){if(gameState != PLAYING) return;//判断是否失败int[,] rolePos = new int[2, 3]{{0, 0, 0}, {0, 0, 0}};foreach(RoleController r in RoleCtrl){rolePos[r.roleType, r.roleState]++;}if((rolePos[0,0]>0 && rolePos[0,0]<rolePos[1,0]) || (rolePos[0,1]>0 && rolePos[0,1]<rolePos[1,1]) || (rolePos[0,2]>0 && rolePos[0,2] < rolePos[1,2])){gameState = FAILED;return;}  //判断是否成功foreach(RoleController r in RoleCtrl){if(r.roleType == 0 && r.roleState != RIGHTLAND){return;}}gameState = WIN;return;}//Reset按钮执行的功能public void Restart(){Initialize();gameState = PLAYING;}//获取游戏当前状态public int GetGameState(){return gameState;}
}

IObjectController类:

public interface IObjectController
{void DealClick();void CreateModel();void Reset();GameObject GetModelGameObject();
}

ISceneController类:

public interface ISceneController
{void Initialize();
}

IUserAction类:

public interface IUserAction {void MoveBoat();void MoveRole(int id);int GetGameState();void Restart();
}

LandController类:

public class LandController: IObjectController
{public int side;public int[] seat;IUserAction userAction;Land landModel;public LandController(int side, int[] rolesID){userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;this.side = side;this.seat = new int[6]{-1,-1,-1,-1,-1,-1};if(this.side == FirstController.LEFTLAND){for(int i = 0; i < 6; i++){this.seat[i] = rolesID[i];}}}public bool LeaveLand(int roleID){for(int i = 0; i < 6; i++){if(seat[i] == roleID){seat[i] = -1;return true;}}return false;}public bool GoOnLand(int roleID){for(int i = 0; i < 6; i++){if(seat[i] == -1){seat[i] = roleID;return true;}}return false;}public int getEmptySeat(){for(int i = 0; i < 6; i++){if(seat[i] == -1){return i;}}return -1;}public void CreateModel(){landModel = new Land(Position.landInitPos[side]);}public void Reset(){}public void DealClick(){}public GameObject GetModelGameObject(){return landModel.land;}
}

MoveController类:

public class MoveController
{GameObject moveObject;public bool IsMoving(){return(this.moveObject != null && this.moveObject.GetComponent<Move>().isMoving == true);}public void SetMove(GameObject moveObject, Vector3 destination) {// 设置一个新的移动Move test;this.moveObject = moveObject;if (!moveObject.TryGetComponent<Move>(out test)) {moveObject.AddComponent<Move>();}this.moveObject.GetComponent<Move>().destination = destination;}
}

RoleController类:

public class RoleController : IObjectController
{public int roleType;public int roleState;public int id;Role roleModel;IUserAction userAction;public RoleController(int roleType, int id){userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;this.roleType = roleType;this.id = id;Reset();}public void Reset(){this.roleState = FirstController.LEFTLAND;}public void GoTo(int pos){this.roleState = pos;}public void CreateModel(){roleModel = new Role(roleType, Position.roleLeftPos[id]);roleModel.role.GetComponent<Click>().setClickAction(this);}public void DealClick(){userAction.MoveRole(this.id);}public GameObject GetModelGameObject(){return roleModel.role;}
}

SSSirector类:

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;}
}

为了进一步说明代码是如何运作的,以下我就游戏行为“先点击一个牧师上船,再点击船移动,被判定游戏失败”为例介绍代码的工作流程。

  • IFirstController中的Awake函数和UserGUI中的start函数被执行,前者初始化了第一个场景中的所有3D对象,后者初始化了2D的GUI
  • 点击一个牧师,点击事件被某牧师模型(Role.cs)的Click组件(Click.cs)中的OnMouseDown函数捕获到
  • 该点击事件被传递到该牧师对应的RoleController.cs中(具体传递的机制请查看RoleController.cs中的CreatModel函数)
  • RoleController.cs调用DealClick函数解决该点击事件。但是以上已经解释过RoleController.cs实际上不能真正解决这个点击事件,因此DealClick的做法是通过唯一的导演找到管理他的场景控制器,请场景控制器解决这个问题。
  • 场景控制器调用指定的函数(在这里就是MoveRole函数),调动所有相关的模型控制器,真正处理该点击事件
  • 再点击船,此时的响应逻辑跟上述的完全一样,船模型中的点击事件会被一步步传递到场景控制器中,由场景控制器的MoveBoat函数处理该点击事件
  • MoveBoat函数执行时会检查当前游戏的状态,此时判断游戏失败,则会把场景控制器中的gameState变量设置为FAILED并不再允许处理点击事件
  • UserGUI中的OnGUI函数会一直监听场景控制器中的gameState的值,当它发现这个值变成FAILED后就会在绘制出“you failed”的文字。

四、游戏展示

游戏演示视频:unity 3d 牧师与魔鬼小游戏作业_哔哩哔哩bilibili

参考师兄的博客:Unity3D小游戏——牧师与魔鬼 - LoongChan - 博客园 (cnblogs.com)

写的很好,谢谢师兄!

这篇关于unity 3D 牧师与魔鬼小游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

SAM2POINT:以zero-shot且快速的方式将任何 3D 视频分割为视频

摘要 我们介绍 SAM2POINT,这是一种采用 Segment Anything Model 2 (SAM 2) 进行零样本和快速 3D 分割的初步探索。 SAM2POINT 将任何 3D 数据解释为一系列多向视频,并利用 SAM 2 进行 3D 空间分割,无需进一步训练或 2D-3D 投影。 我们的框架支持各种提示类型,包括 3D 点、框和掩模,并且可以泛化到不同的场景,例如 3D 对象、室

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

模具要不要建设3D打印中心

随着3D打印技术的日益成熟与广泛应用,模具企业迎来了自建3D打印中心的热潮。这一举措不仅为企业带来了前所未有的发展机遇,同时也伴随着一系列需要克服的挑战,如何看待企业引进增材制造,小编为您全面分析。 机遇篇: 加速产品创新:3D打印技术如同一把钥匙,为模具企业解锁了快速迭代产品设计的可能。企业能够迅速将创意转化为实体模型,缩短产品从设计到市场的周期,抢占市场先机。 强化定制化服务:面

Unity协程搭配队列开发Tips弹窗模块

概述 在Unity游戏开发过程中,提示系统是提升用户体验的重要组成部分。一个设计良好的提示窗口不仅能及时传达信息给玩家,还应当做到不干扰游戏流程。本文将探讨如何使用Unity的协程(Coroutine)配合队列(Queue)数据结构来构建一个高效且可扩展的Tips弹窗模块。 技术模块介绍 1. Unity协程(Coroutines) 协程是Unity中的一种特殊函数类型,允许异步操作的实现

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光 一,前言二,资源包内容三,免费获取资源包 一,前言 在创意的世界里,每一个细节都能决定一个项目的独特魅力。今天,要向大家介绍一款令人惊艳的粒子效果包 ——Super Confetti FX。 二,资源包内容 💥充满活力与动态,是 Super Confetti FX 最显著的标签。它宛如一位

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(4)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)-CSDN博客  这节就是真正的存储数据了   理清一下思路: 1.存储路径并检查 //2进制文件类存储private static string Data_Binary_Pa

Unity Adressables 使用说明(一)概述

使用 Adressables 组织管理 Asset Addressables 包基于 Unity 的 AssetBundles 系统,并提供了一个用户界面来管理您的 AssetBundles。当您使一个资源可寻址(Addressable)时,您可以使用该资源的地址从任何地方加载它。无论资源是在本地应用程序中可用还是存储在远程内容分发网络上,Addressable 系统都会定位并返回该资源。 您

Unity Adressables 使用说明(六)加载(Load) Addressable Assets

【概述】Load Addressable Assets Addressables类提供了加载 Addressable assets 的方法。你可以一次加载一个资源或批量加载资源。为了识别要加载的资源,你需要向加载方法传递一个键或键列表。键可以是以下对象之一: Address:包含你分配给资源的地址的字符串。Label:包含分配给一个或多个资源的标签的字符串。AssetReference Obj