目录
1.游戏脚本介绍
在游戏中,您要帮助牧师和魔鬼在规定时间内过河。河的一边有3个牧师和3个魔鬼。他们都想到达河对岸,但只有一条船,而且这条船每次只能载2个人。而且必须有一个人驾驶小船从一边驶向另一边。如果任意一边河岸的牧师数量少于恶魔数量则游戏结束。
2.游戏实现要求
- 列出游戏中提及的事物(Objects)并在工程文件中做成预制
- 用表格列出玩家动作表(规则表)
- 在场景控制器
LoadResources
方法中加载并初始化 长方形、正方形、球 及其色彩代表游戏中的对象。 - 使用 C# 集合类型 有效组织对象
- 整个游戏仅主摄像机和 一个Empty对象,其他对象必须代码动态生成。整个游戏不许出现 Find 游戏对象, SendMessage 这类突破程序结构的通讯耦合语句。
- 不接受非 MVC 结构程序
- 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!
3. 游戏设计
3.1MVC架构设计
- M(Model):模型代表应用程序的数据及业务逻辑。通常指的是游戏世界的数据,例如玩家角色、敌人、物品等。还包括处理这些数据的方法和规则。Model 有对数据直接访问的权力,例如对数据库的访问。Model 不依赖 View 和 Controller,也就是说, Model 不关心它会被如何显示或是如何被操作。
- V(View):视图负责呈现应用程序的用户界面。在游戏中,视图包括玩家看到的图形、动画、文本、HUD(Heads-Up Display)等。视图的主要任务是将模型的数据可视化并呈现给玩家。不负责处理用户输入或应用逻辑,而只是展示信息。
- C(Controller):控制器是用户输入的处理者,它接收来自玩家的输入(如键盘、鼠标或触摸屏输入)并将其转化为对模型的操作。控制器还可以根据游戏规则来协调视图的更新。通过触发模型的方法来更新游戏状态,并可以通知视图更新以反映这些变化。
本游戏实现MVC框架的的UML图如下:
3.2游戏对象表
名称 | 实现方式 |
---|---|
牧师 | 白色胶囊 |
魔鬼 | 黑色胶囊 |
河岸 | 绿色长方体 |
船 | 棕色长方体 |
3.3玩家动作表
动作 | 条件 |
---|---|
船移动 | 船上至少存在一个牧师或魔鬼 |
牧师上船 | 船停留岸有牧师,船上有空位 |
牧师下船 | 船上有牧师 |
魔鬼上船 | 船停留岸有魔鬼,船上有空位 |
魔鬼下船 | 船上有魔鬼 |
3.4预制对象创建
4.游戏程序编写
1. Models
创建游戏中使用的模型的实体对象并设置相关的大小及位置信息。
Position类存储游戏中使用的游戏模型的相关位置信息。
Click类为模型赋予能够响应点击的功能。
1.1 Boat类
public class Boat
{
public GameObject boat;//船对象
public Role[] roles;//船上的角色
public bool isRight;
public int priestCount, devilCount;
public Boat(Vector3 position)
{
boat = GameObject.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject))) as GameObject;
boat.name = "boat";
boat.transform.position = position;
boat.transform.localScale = new Vector3(2.8f, 0.4f, 2);
roles = new Role[2];
isRight = false;
priestCount = devilCount = 0;
boat.AddComponent<BoxCollider>();
boat.AddComponent<Click>();
}
}
1.2 Click类
public interface ClickAction
{
void DealClick();
}
public class Click : MonoBehaviour
{
ClickAction clickAction;
public void setClickAction(ClickAction clickAction)
{
this.clickAction = clickAction;
}
void OnMouseDown()
{
clickAction.DealClick();
}
}
1.3 Position类
public class Position //存储所有对象的位置
{
//固定位置(世界坐标)
public static Vector3 left_shore = new Vector3(-8, -3, 0);
public static Vector3 right_shore = 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_shore = new Vector3[] { new Vector3(0.4f, 0.77f, 0), new Vector3(0.2f, 0.77f, 0), new Vector3(0, 0.77f, 0), new Vector3(-0.2f, 0.77f, 0), new Vector3(-0.4f, 0.77f, 0), new Vector3(-0.6f, 0.77f, 0) };
//角色相对于船的位置(相对坐标)
public static Vector3[] role_boat = new Vector3[] { new Vector3(0.2f, 3, 0), new Vector3(-0.2f, 3, 0) };
}
1.4 River类
public class River
{
public GameObject river;
public River(Vector3 position)
{
river = GameObject.Instantiate(Resources.Load("Prefabs/River", typeof(GameObject))) as GameObject;
river.transform.localScale = new Vector3(8, 2.5f, 2);
river.transform.position = position;
river.name = "river";
}
}
1.5 Role类
public class Role
{
public GameObject role;//model对应的游戏对象
public bool isPriest;
public bool inBoat;
public bool onRight;
public int id;
public Role(Vector3 position, bool isPriest, int id)
{
this.isPriest = isPriest;
this.id = id;
onRight = false;
inBoat = false;
role = GameObject.Instantiate(Resources.Load("Prefabs/" + (isPriest ? "priest" : "devil"), typeof(GameObject))) as GameObject;
role.transform.localScale = new Vector3(1, 1.2f, 1);
role.transform.position = position;
role.name = "role" + id;
role.AddComponent<Click>();
role.AddComponent<BoxCollider>();
}
}
1.6 Shore类
public class Shore
{
public GameObject shore;
public int priestCount, devilCount;
public Shore(Vector3 position)
{
shore = GameObject.Instantiate(Resources.Load("Prefabs/Shore", typeof(GameObject))) as GameObject;
shore.transform.localScale = new Vector3(8, 4.8f, 2);
shore.transform.position = position;
priestCount = devilCount = 0;
}
}
2.Controllers
为每个在游戏中会参与移动或交互行为的模型对象创建对应的控制器,因为主要的控制功能来自于鼠标点击,所以船和牧师、恶魔的控制器都继承ClickAction接口,通过点击控制游戏对象的移动等行为。移动控制器和河岸控制器的作用是移动相关的游戏对象或放置对应的Role对象,所以不实现ClickAction接口。
2.1 BoatCtrl类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BoatCtrl : ClickAction
{
Boat boatModel;
IUserAction userAction;
public BoatCtrl()
{
userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;
}
public void CreateBoat(Vector3 position)
{
if (boatModel != null)
{
Object.DestroyImmediate(boatModel.boat);
}
boatModel = new Boat(position);
boatModel.boat.GetComponent<Click>().setClickAction(this);
}
public Boat GetBoatModel()
{
return boatModel;
}
//将角色从岸上移动到船上,返回接下来角色应该到达的位置??
public Vector3 AddRole(Role roleModel)
{
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;
}
}
}
public void DealClick()
{
if (boatModel.roles[0] != null || boatModel.roles[1] != null)
{
userAction.MoveBoat();
}
}
}
2.2 MoveCtrl类
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 frame
void Update()
{
if (transform.localPosition == destination)
{
isMoving = false;
return;
}
isMoving = true;
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);
}
}
}
public class MoveCtrl
{
GameObject moveObject;
public bool GetIsMoving()
{
return (moveObject != null && moveObject.GetComponent<Move>().isMoving == true);
}
public void SetMove(Vector3 destination, GameObject moveObject)
{
Move test;
this.moveObject = moveObject;
if (!moveObject.TryGetComponent<Move>(out test))
{
moveObject.AddComponent<Move>();
}
this.moveObject.GetComponent<Move>().destination = destination;
if (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
{
this.moveObject.GetComponent<Move>().mid_destination = new Vector3(this.moveObject.transform.localPosition.x, destination.y, destination.z);
}
}
}
2.3 RoleCtrl类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RoleCtrl : ClickAction
{
Role roleModel;
IUserAction userAction;
public RoleCtrl()
{
userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;
}
public void CreateRole(Vector3 position, bool isPriest, int id)
{
if (roleModel != null)
{
Object.DestroyImmediate(roleModel.role);
}
roleModel = new Role(position, isPriest, id);
roleModel.role.GetComponent<Click>().setClickAction(this);
}
public Role GetRoleModel()
{
return roleModel;
}
public void DealClick()
{
userAction.MoveRole(roleModel);
}
}
2.4 ShoreCtrl类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShoreCtrl
{
Shore shoreModel;
public void CreateShore(Vector3 position)
{
if (shoreModel == null)
{
shoreModel = new Shore(position);
}
}
public Shore GetShore()
{
return shoreModel;
}
//将角色添加到岸上,返回角色在岸上的相对坐标
public Vector3 AddRole(Role roleModel)
{
roleModel.role.transform.parent = shoreModel.shore.transform;
roleModel.inBoat = false;
if (roleModel.isPriest) shoreModel.priestCount++;
else shoreModel.devilCount++;
return Position.role_shore[roleModel.id];
}
//将角色从岸上移除
public void RemoveRole(Role roleModel)
{
if (roleModel.isPriest) shoreModel.priestCount--;
else shoreModel.devilCount--;
}
}
3.Views
主要处理初始化场景,接收用户行为。
UserGUI类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
IUserAction userAction;
public string gameMessage;
public int time;
GUIStyle style, bigstyle;
// Start is called before the first frame update
void Start()
{
time = 60;
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 frame
void OnGUI()
{
userAction.Check();
GUI.Label(new Rect(160, Screen.height * 0.1f, 50, 200), "Preists and Devils", bigstyle);
GUI.Label(new Rect(250, 100, 50, 200), gameMessage, style);
}
}
4. 接口和主要类
4.1 IUserAction接口
定义用户的游戏行为,第一个是移动船,第二个是移动河岸或船上的牧师或恶魔。
其次游戏中还需要有判断游戏是否结束的函数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction
{
void MoveBoat();
void MoveRole(Role roleModel);
void Check();
}
4.2 ISceneController接口
定义场景控制器的行为是加载游戏资源
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneController
{
void LoadResources();
}
4.3 FirstController类
游戏中最重要的类,在游戏中类似于场记,其职责包括:
- 管理本次场景所有的游戏对象
- 协调游戏对象(预制件级别)之间的通讯
- 响应外部输入事件
- 管理本场次的规则(裁判)
- 各种杂务
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
ShoreCtrl leftShoreController, rightShoreController;
River river;
BoatCtrl boatController;
RoleCtrl[] roleControllers;
MoveCtrl moveController;
bool isRunning;
float time;
public void LoadResources()
{
//role
roleControllers = new RoleCtrl[6];
for (int i = 0; i < 6; ++i)
{
roleControllers[i] = new RoleCtrl();
roleControllers[i].CreateRole(Position.role_shore[i], i < 3 ? true : false, i);
}
//shore
leftShoreController = new ShoreCtrl();
leftShoreController.CreateShore(Position.left_shore);
leftShoreController.GetShore().shore.name = "left_shore";
rightShoreController = new ShoreCtrl();
rightShoreController.CreateShore(Position.right_shore);
rightShoreController.GetShore().shore.name = "right_shore";
//将人物添加并定位至左岸
foreach (RoleCtrl roleController in roleControllers)
{
roleController.GetRoleModel().role.transform.localPosition = leftShoreController.AddRole(roleController.GetRoleModel());
}
//boat
boatController = new BoatCtrl();
boatController.CreateBoat(Position.left_boat);
//river
river = new River(Position.river);
//move
moveController = new MoveCtrl();
isRunning = true;
time = 60;
}
public void MoveBoat()
{
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)
{
if (isRunning == false || moveController.GetIsMoving()) return;
if (roleModel.inBoat)
{
if (boatController.GetBoatModel().isRight)
{
moveController.SetMove(rightShoreController.AddRole(roleModel), roleModel.role);
}
else
{
moveController.SetMove(leftShoreController.AddRole(roleModel), roleModel.role);
}
roleModel.onRight = boatController.GetBoatModel().isRight;
boatController.RemoveRole(roleModel);
}
else
{
if (boatController.GetBoatModel().isRight == roleModel.onRight)
{
if (roleModel.onRight)
{
rightShoreController.RemoveRole(roleModel);
}
else
{
leftShoreController.RemoveRole(roleModel);
}
moveController.SetMove(boatController.AddRole(roleModel), roleModel.role);
}
}
}
public void Check()
{
if (isRunning == false) return;
this.gameObject.GetComponent<UserGUI>().gameMessage = "";
if (rightShoreController.GetShore().priestCount == 3)
{
this.gameObject.GetComponent<UserGUI>().gameMessage = "You Win!";
isRunning = false;
}
else
{
int leftPriestCount, rightPriestCount, leftDevilCount, rightDevilCount;
leftPriestCount = leftShoreController.GetShore().priestCount + (boatController.GetBoatModel().isRight ? 0 : boatController.GetBoatModel().priestCount);
rightPriestCount = rightShoreController.GetShore().priestCount + (boatController.GetBoatModel().isRight ? boatController.GetBoatModel().priestCount : 0);
leftDevilCount = leftShoreController.GetShore().devilCount + (boatController.GetBoatModel().isRight ? 0 : boatController.GetBoatModel().devilCount);
rightDevilCount = rightShoreController.GetShore().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;
}
}
}
void Awake()
{
SSDirector.GetInstance().CurrentSceneController = this;
LoadResources();
this.gameObject.AddComponent<UserGUI>();
}
void Update()
{
if (isRunning)
{
time -= Time.deltaTime;
this.gameObject.GetComponent<UserGUI>().time = (int)time;
if (time <= 0)
{
this.gameObject.GetComponent<UserGUI>().time = 0;
this.gameObject.GetComponent<UserGUI>().gameMessage = "Game Over!";
isRunning = false;
}
}
}
}
4.4 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;
}
}
gitee代码地址:Priests_and_Devils · 冯静雯/3D Unity - 码云 - 开源中国 (gitee.com)
bilibili视频地址:04 牧师与魔鬼游戏演示_哔哩哔哩_bilibili
参考代码: unity 实现游戏——牧师与魔鬼-CSDN博客