Unity3D小游戏——牧师与魔鬼

0.前言  

  本博客为2021学年中山大学3D游戏编程与设计课第二次课程作业。实现游戏“牧师与魔鬼”,并且要求使用MVC架构。与该作业有关的博客文章有很多,但是绝大部分都写的比较简略,即使阅读后仍然难以对整个作业的实现过程有比较清晰的认知,因此我希望根据自己实现过程中的理解写一篇比较详细的博客,给以后有需要的师弟师妹提供一点帮助。在此特别鸣谢这位前辈(https://blog.csdn.net/weixin_43867940/article/details/108818301)留下的博客,没有他的启发我将难以完成这次作业。

1.设计

整体文件框架如下

 

1.1资源的设计  

  在这里,资源的设计就是指预制件的设计。对于每一种类型的游戏实体,预制件只需要有一个,因此,在这个游戏中,需要四个预制件:船、岸、牧师、魔鬼。为了简单,预制件可以使用正方体、球体等可以在Unity中直接创建的物体代替。然后为每一个预制件创建一个材质,把材质拖到对应的预制件上,就完成了最简单的资源的创建工作。其中预制件放在Resources/Prefabs文件夹中,材质放在Resources/Prefabs文件夹中。

          

 

 

 1.2代码的设计 

   根据作业要求,代码设计需要使用MVC架构。即需要实现模型——视图——控制器的分离。

  模型:在游戏中,一切实体都可以被视为是模型,这种实体可以是具体的,也可以是抽象的。其中具体的模型有:船、岸、牧师、魔鬼。而抽象的模型有:点击、移动。模型只关注这个实体本身具有的最基础的属性和方法,不考虑它与其他模型之间的交互。比如说对于船模型,它具有两个基础的特征:可以被创建的以及可以被点击的。至于船如何搭载乘客,船被点击之后作何行为,这并不是模型这个层次要关心的问题。又比如“移动”这个抽象的模型,它最基础的属性和方法有:移动的速度和移动的目的地,至于它要移动的是谁,也不是模型本身惯性的。“点击”这个模型中,它只关注谁被点击了,而不关注点击触发的事件是什么。另外,根据我自己的理解,我还添加了“位置”模型,该模型只预先记录游戏过程中所有可能移动到的位置。

 

 

   控制器:控制器用于控制模型的行为。因此理论上来说,每一个模型的副本都会有一个对应的控制器(位置模型除外),比如Boat模型就会有BoatController,BoatController会控制一切以船为对象的行为,比如:处理船被点击时的事件、往船上添加一名乘客,往船上减少一名乘客。必须要注意的是,直接控制模型的控制器之间是没有耦合关系的,也就是说,BoatController只能知道当前船上有多少名乘客,但是不知道他们都是谁(这个信息应当由各RoleController保存),还有,因为移动船要同时移动船上的所有乘客,但是BoatController不能直接移动乘客,因此实际上真正移动船的逻辑不能直接写在BoatController里(具体应该写在哪下文说)。Move模型就会有MoveController,MoveController控制了谁要移动,而且负责判断当前是否有模型正在移动。

  另外还有一类很重要的控制器就是场景控制器和导演。游戏就像是一场话剧,话剧会有多个场景,同时有一个导演贯穿了所有场景,导演控制器必须被写成单例模式,这就确保了各控制器属于同一场“话剧”。每一个场景都会有一个单独的场景控制器,但是在这个游戏里,只有唯一一个场景(FirstController)场景控制器管理所有该场景内的模型控制器,并且实现他们的综合行为。就这个游戏而言,FirstController会生成并且管理:一个BoatController,两个LandController,六个RoleController,一个MoveController。所有该场景内的综合行为都会在这个场景控制器内实现。比如:移动船,涉及到船和乘客的同时移动;移动角色,涉及到离岸、登船、人状态的转变三方信息;判断当前游戏是否成功或者失败。

  最后,以I开头的控制器都是接口,用于规范同一类型的控制器应当具有的行为,比如IObjectContoller应当被所有模型的控制器继承、IScenceController应当被所有场景的控制器继承、IUserAction负责与视图方面沟通的接口。

 

 

   视图:在这里视图就是GUI,视图控制了所有与3D游戏实体不相关的,GUI上的组件。比如说:标题、重新开始的按钮、游戏成功或者失败时的提示。

 

 

 3.工作流程

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

  • 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”的文字。

4.资源

4.1运行方式

在Unity中创建一个新的3D项目,将该项目的Assert文件夹替换为以下链接中提供的Assert文件夹。将Assert/Scripts/UserGUI拖动到Main Camera上,创建一个新的空GameObject,将FirstController拖动到GameObject上,点击运行即可。

4.2代码

https://gitee.com/GallonC/unityhomework/tree/master/homework2/Assets

4.3视频

 https://www.bilibili.com/video/BV1634y1m75A?spm_id_from=333.999.0.0

posted @ 2021-10-18 17:26  LoongChan  阅读(555)  评论(0编辑  收藏  举报