前言
中山大学数据科学与计算机学院3D游戏课程学习记录博客。
游戏代码:gitee
游戏视频:bilibili
参考师兄的博客: 师兄博客
游戏要求
-
列出游戏中提及的事物(Objects)
对象:牧师、魔鬼、船、河、陆地 -
用表格列出玩家动作表(规则表)
动作 | 条件 | 角色 |
---|---|---|
点击牧师或魔鬼 | 游戏未结束;角色与船同岸;角色在船上 | 角色上岸 |
点击牧师或魔鬼 | 游戏未结束;角色与船同岸;角色在岸上 | 角色上船 |
点击船 | 船上存在一个角色 | 船开到对岸 |
点击重新开始 | 无 | 重新开始 |
-
将游戏中对象做成预制
预制的制作方法还是比较简单的,直接把列表里的物体拉到下面的文件夹即可。但是太丑了,不如直接copy师兄用的精美预制。 -
在场景控制器 LoadResources 方法中加载并初始化 长方形、正方形、球 及其色彩代表游戏中的对象。
-
使用 C# 集合类型有效组织对象
-
整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!!! 。
-
整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的 通讯耦合 语句。
-
使用MVC 结构程序
-
注意细节
游戏实现
-
介绍MVC架构
M是Model:代表游戏中所有的游戏对象,它们受各自的Controller控制。Model是游戏中存在的实体。
V是View:代表游戏呈现出的界面。主要包括GUI和Click两部分,GUI负责展示游戏结果,Click负责处理用户点击事件。
C是Controller:代表游戏中的控制器,分为FitstController(场景控制器)和SSDirector(导演)。由于本游戏只有一个场景,所以导演只用负责退出和暂停;场景控制器需要控制场景中的所有物体。一共创建7个C#脚本来支持MVC架构:
-
实现
这个游戏看起来还是比较容易的,我们一步步来实现所有的要求,首先实现游戏对象的内部逻辑。
1.游戏对象内部逻辑的实现(ModelController.cs):
- 水(Water)
在这个游戏中,水不需要动,只需要写一个函数使用预设创建水的对象即可。
public class Water{
GameObject water;
public Water(){
//使用预制创建水的游戏对象
}
}
- 陆地(LandModel)
陆地有出发点和目的地两种,需要进行区分,需要变量landSign;
陆地需要存储角色的位置,需要Vector3数组进行保存,共6个位置;
陆地需要存储角色的信息,需要RoleMode数组进行保存,共6个角色;
陆地也需要一系列的成员函数来更改成员变量实现角色的上船和上岸动作。
public class LandModel{
GameObject land;
int landSign;
Vector3[] position;
RoleModel[] roles;
public LandModel(string str){
//使用预制创建陆地的对象
//初始化成员变量
//str用于区分出发点和目的地
}
//一系列用于处理角色上岸和上船的成员函数
public int getLandSign(){
return landSign;
}
public int getEmptyNumber(){
//返回空位置下标
}
public int[] getRoleNum(){
//返回角色数量,牧师/魔鬼
}
public void addRole(RoleModel role){
roles[getEmptyNumber()]=role;
}
public RoleModel deleteRole(string name){
//删除对应的角色,角色上船
}
}
- 船(BoatModel)
船可以被点击和移动,所以需要Move对象和Click对象(后面回提到);
船需要记录移动方向,所以需要变量boatSign;
船需要保存角色的位置,所以需要Vector3数组startPos和endPos;
船需要保存角色的信息,所以需要RoleModel数组roles;
船也需要一系列成员函数来移动和支持角色上船以及上岸动作。
public class BoatModel{
GameObject boat;
int boatSign;
Vector3[] startPos,endPos;
RoleModel[] roles;
Move move;
Click click;
public BoatModel(){
//用预制创建船的对象
//初始化成员变量
}
//一系列用于处理角色上岸和上船以及船移动的成员函数
public int getBoatSign(){
return boatSign;
}
public int getEmptyNumber(){
//返回空位置下标
}
public int[] getRoleNum(){
//返回角色数量,牧师/魔鬼
}
public addRole(RoleModel role){
roles[getEmptyNumber()]=role;
}
public RoleModel deleteRole(string name){
//删除对应的角色,角色上岸
}
public void boatMove(){
//船移动
}
}
- 角色/牧师和魔鬼(RoleMode)
角色需要标记身份,是否在船上,在哪块陆地上,因此需要变量roleSign,onBoat,landSign;
角色可以移动和被点击,因此需要Move和Click;
角色也需要一系列状态set和get函数,以及移动函数。
public class RoleModel{
GameObject role;
int roleSign;
int onBoat;
int landSign;
Move move;
Click click;
public RoleModel(string name,Vector3 pos){
//用预制创建角色的对象
//初始化成员变量
}
//一系列set和get函数,略
public void roleMove(){
//角色移动
}
}
实现以上四个类之后就完成了对游戏对象的创建以及对内部逻辑的实现,接下来就实现控制类,让游戏运转起来。
2.控制类的实现
(SSDirect.cs/Interfaces.cs/FirstController.cs/MoveController.cs/ClickController.cs):
SSDirect.cs使用单例模式,掌控全局;FirstController.cs是控制这个场景的,不过这个游戏只有一个场景,实际上SSDirect和FirstController的功能重复了;Interfaces.cs是给FirstController.cs提供接口的,规范FirstController.cs的内容。MoveController.cs是用来控制物体的移动功能,ClickController.cs是用来控制物体被点击的动作。接下来就把它们分别实现。
- 实现SSDirect.cs:
写一个单例模式即可。
public class SSDirect : System.Object{
private static SSDirect instance;
public ISceneController CurrentSceneController{ get; set;}
public static SSDirect getInstance(){
if(instance==null) instance=new SSDirect();
return instance;
}
}
- 实现Interfaces.cs:
其中包含两个类,为FirstController.cs提供接口。
ISceneController提供场景接口,IUserAction提供用户动作接口。
//场景接口
public interface ISceneController{
void LoadResoureces();
}
//用户动作接口
public interface IUserAction{
void moveBoat();
void moveRole(RoleModel role);
void reStart();
void check();
}
- 实现FirstController.cs:
其中具有之前的模型;
也要包含Interface中所有的成员函数。
public class FirstController : MonoBehaviour,ISceneController,IUserAction{
public LandModel startLand;
public LandModel endLand;
public Water water;
public BoatModel boat;
public RoleModel[] roles;
public UserGUI GUI;
void Start(){
SSDirector director=SSDirector.GetInstance();
director.CurrentSceneController=this;
GUI=gameObject.AddComponent<UserGUI>() as UserGUI;
LoadResources();
}
public void loadResources(){
//调用各对象的构造函数创建对象
}
public void moveBoat(){
//移动船
}
public void moveRole(){
//移动角色
}
public void reStart(){
SceneManager.LoadScene(0);
}
public int check(){
//0-游戏1继续;1-游戏成功;-1-游戏失败
}
}
check方法的实现:
先统计船上,出发点和目的地的角色数量
假如在目的地存在6个角色,则胜利
假如在任何时候,船上及岸同侧存在魔鬼多与牧师,则失败
除此以外,游戏继续
- 实现MoveController.cs:
public class MoveController : MonoBehaviour{
float moveSpeed=150f;
int moveSign=0;
Vector3 endPos,midPos;
void Update(){
if(moveSign==1){
transform.position=Vector3.MoveTowards(transform.position,midPos,moveSpeed*deltaTime);
if(transform.position==midPos){
moveSign=2;
}
}else if(moveSign==2){
transform.position=Vector3.MoveTowards(transform.position,endPos,moveSpeed*deltaTime);
if(transform.position==endPos){
moveSign=0;
}
}
}
public void MovePosition(Vector3 position){
endPos=position;
if(position.y == transform.position.y){
moveSign=2;
return;
}else if(position.y<transform.position.y){
midPos=new Vector3(position.x,transform.position.y,position.z);
}else{
midPos=new Vector3(transform.position.x,position.y,position.z);
}
moveSign=1;
}
}
- 实现ClickController.cs:
public class ClickController : MonoBehaviour{
IUserAction action;
RoleModel role=null;
BoatModel boat=null;
public void setRole(RoleModel role){
this.role=role;
}
public void setBoat(BoatModel boat){
this.boat=boat;
}
void Start(){
action=SSDirector.GetInstance().CurrentSceneController as IUserAction;
}
void OnMouseDown(){
if(boat==null && role==null) return;
if(boat!=null) action.MoveBoat();
else if(role!=null) action.MoveRole(role);
}
}
实现上述内容后就完成了对于控制器的设计。整个游戏的逻辑已经被设计出来,接下来需要的就是用V的部分把内容以GUI的形式呈现给玩家。
3.GUI类(UserGUI.cs):
- 实现UserGUI.cs:
主要绘制文本框和按钮即可,游戏对象不通过GUI展现。
需要一个变量sign标记游戏的胜负;
一个按钮来实现重新开始;
一个文本框公布游戏结果。
public class UserGUI : MonoBehaviour{
public IUserAction action;
public int sign=0;
void Start() {
action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
}
void OnGUI() {
GUIStyle text_style;
GUIStyle button_style;
text_style = new GUIStyle() {
fontSize = 30
};
button_style = new GUIStyle("button") {
fontSize = 15
};
if(sign==1) {
GUI.Label(new Rect(Screen.width/2-90,Screen.height/2-120,100,50),"Gameover!",text_style);
}else if(sign==2) {
GUI.Label(new Rect(Screen.width/2-80,Screen.height/2-120,100,50),"You Win!",text_style);
}
if (GUI.Button(new Rect(Screen.width/2-50,Screen.height/2-200,100,50),"Restart",button_style)) {
action.Restart();
sign = 0;
}
}
}
至此游戏的所有内容就被完全实现。具体的代码和预制可以看gitee,演示视频可以看bilibili。