Unity实现Priests and Deivls游戏
前言
这是中山大学数据科学与计算机学院2019年3D游戏编程与设计的第三次作业
所有项目与代码已上传至github当中,欢迎大家访问。
github个人主页: https://starashzero.github.io
3D游戏编程与设计主页: https://starashzero.github.io/3DGameDesign
本次作业项目地址: https://github.com/StarashZero/3DGameDesign/tree/master/hw3
游戏简介
3个牧师与3个恶魔要过河,需要将牧师安全的运过去。河中有一条船,每次最多只能运两个人过河,如果任意一边的恶魔数量大于牧师数量,牧师就会被杀死。
(界面略丑,没有艺术细胞真是抱歉了,嘛,不是重点)
游戏玩法
点击牧师(白色圆柱)、恶魔(红色圆柱)可以让它们上船、上岸。
点击船(水面上的方块)可以让船移动。
游戏规则
- 任意一边的牧师数量不能小于恶魔数量(船上的牧师和恶魔也会被计算),除非牧师数目为0。
- 船上必须有人才能使船移动。
- 没有反悔选项(防止作弊),一旦游戏结束只能重来.
- 游戏限时60s.
- 当三个牧师都到了右边河岸,游戏胜利.
游戏试玩
项目要求
参考潘老师的博客第三章最后一部分
关于程序的要求如下:
- 请将游戏中对象做成预制
- 在GenGameObjects中创建长方形、正方形、球及其色彩代表游戏中的对象。
- 使用C#集合类型有效组织对象
- 整个游戏仅主摄像机和一个 Empty 对象,其他对象必须代码动态生成!!! 。 整个游戏不许出现 Find 游戏对象, SendMessage这类突破程序结构的 通讯耦合 语句违背本条准则,不给分
- 请使用课件架构图编程,不接受非 MVC 结构程序
- 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!
在编写程序时均按照上述要求编写,不过对于要求3,我选择的是直接用数组来组织对象,因为我觉得这是最合适、效率较高的。
项目代码结构
游戏对象事先做成预制
项目的程序代码采用MVC结构,根据功能将代码文件分别放入Controllers, Models, Views文件夹中。
为了方便理解,我准备了一个UML图来解释各文件之间的关系(部分借鉴了潘老师的图,偷偷懒)。
(MVC确实好用,好用就好用在它能加速掉发)
大体结构是按照课程网站来做的,所有游戏对象的数据被储存在各自model对象当中。新增部分的解释如下:
- 在游戏中可能会变更数据的model都分配了一个各自的controller来管理大部分数据变更。
- 人物和船具有可被点击的属性,因此其controller需要实现ClickAction的接口,以此来处理点击事件。
- 游戏中物体的移动由MoveController统一管理,其携带一个Move脚本。
- 游戏所有物体的特殊位置信息储存在PositionModel当中,PositionModel中的数据均为静态类型,因此可以全局访问。
除了UML图里的文件,还有一个MoveCamera.cs脚本,这个脚本只是用来移动主摄像机的位置而已,因此不在程序结构之中。
各部分代码解释
Models:
首先是Models部分,Models当中主要用于存储游戏对象的各种数据,只携带初始化函数,变更数据通过Controller。
-
RoleModel.cs
public class RoleModel{ public GameObject role; //角色的游戏对象 public bool isPriest; //区分角色是牧师还是恶魔 public int tag; //给对象标号,方便查找 public bool isRight; //区分角色是在左侧还是右侧 public bool isInBoat; //区分角色是在船上还是在岸上 //初始化函数 public RoleModel(Vector3 position, bool isPriest, int tag) { 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>(); } }
存储游戏当中角色(牧师、恶魔)的相关信息。
定义了一个初始化函数,参数设置对象的位置、角色类型和标号,函数中会调用预制生成对象,并进行一些参数设置(位置、碰撞、点击等)。 -
BoatModel.cs
public class BoatModel { public GameObject boat; //船对象 public RoleModel[] roles; //船上的角色的指针 public bool isRight; //判断船在左侧还是右侧 public int priestNum, devilNum; //船上牧师与恶魔的数量 public BoatModel(Vector3 position) { 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.cs
public class LandModel { public GameObject land; //岸的游戏对象 public int priestNum, devilNum; //岸上牧师与恶魔的数量 public LandModel(string name, Vector3 position) { 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); } }
储存岸的相关信息。
初始化函数的参数设置岸的名字(左岸或者右岸)和位置。 -
RiverModel.cs
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.cs
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,