一、游戏介绍
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 boat to the other direction. If the priests are out numbered by 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!
实现效果
实现过程
1.定义玩家行为
玩家动作 | 效果 | 备注 |
---|---|---|
点击牧师或魔鬼 | 牧师或魔鬼在船与岸之间切换 | 必须点击船上或船所靠岸上的对象 |
点击船只 | 船从一边的岸移动到另一边 | 船上至少有一人,至多有两人 |
2.设定模型预制件
3.基于MVC框架确定脚本结构
项目构成由上述UML图所示,其中代码又可根据MVC架构分为三部分。
(1)Models
Models文件夹中存放了各对象模块,存放了各对象的属性并实现了初始化方法,便于后续调用
(2)Controllers
Controllers文件夹中存放了项目中的控制模块,由它负责所有游戏对象的生成和变化,以及相应用户事件。
(3)View
定义了与用户交互的UI组件
4.具体实现
下面将展示部分核心代码,全部代码链接如下:
https://github.com/parprika01/3DGameWork/tree/Priests-and-Devils/Script
(1)Models
船类的属性中包含了对象的位置信息以及当前船上的角色对象。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Boat
{
public GameObject gameObject;
public bool isLeft = true;
public Role[] roles = new Role[2];
public void Initialize(Vector3 position){
gameObject = GameObject.Instantiate(Resources.Load("Prefabs/Boat",typeof(GameObject))) as GameObject;
gameObject.name = "boat";
gameObject.transform.position = position;
roles[0] = roles[1] = null;
}
}
角色类封装了角色的类型,当前位置信息,并在实例化时拥有自增的id。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Role
{
public GameObject gameObject;
public bool isPriest;
public bool isLeft = true;
public bool isOnShore = true;
public static int num = 0;
public int id;
public void Initialize(bool isPriest, Vector3 position){
id = ++num;
if (isPriest)
gameObject = GameObject.Instantiate(Resources.Load("Prefabs/Priest",typeof(GameObject))) as GameObject;
else
gameObject = GameObject.Instantiate(Resources.Load("Prefabs/Devil",typeof(GameObject))) as GameObject;
gameObject.name = "role" + id;
gameObject.transform.position = position;
this.isPriest = isPriest;
}
}
位置类存储了各类对象的位置信息,便于游戏中各对象的位置变换。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Position
{
public static Vector3 leftShore = new Vector3(10,0,0);
public static Vector3 rightShore = new Vector3(-10,0,0);
public static Vector3 river = new Vector3(0,-0.5f,0);
public static Vector3 leftBoat = new Vector3(-1.8f,1,0);
public static Vector3 rightBoat = new Vector3(1.8f,1,0);
public static Vector3[] RroleShore = new Vector3[] {new Vector3(4.8f,2.5f,0), new Vector3(6.4f,2.5f,0), new Vector3(8f,2.5f,0), new Vector3(9.6f,2.5f,0), new Vector3(11.2f,2.5f,0), new Vector3(12.8f,2.5f,0)};
public static Vector3[] LroleShore = new Vector3[] {new Vector3(-4.8f,2.5f,0), new Vector3(-6.4f,2.5f,0), new Vector3(-8f,2.5f,0), new Vector3(-9.6f,2.5f,0), new Vector3(-11.2f,2.5f,0), new Vector3(-12.8f,2.5f,0)};
public static Vector3[] roleBoat = new Vector3[] {new Vector3(-1f,0.5f,0), new Vector3(1f,0.5f,0)};
}
(2)Controllers
规则管理类(裁判)制定了游戏失败和游戏成功的规则,并且藉由数据管理类的信息进行结果的判断。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RuleManager : MonoBehaviour
{
private FirstController sceneController;
private DataManager.shoreInfo pi;
void Start()
{
sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
sceneController.ruleManager = this;
}
// Update is called once per frame
void Update()
{
pi = sceneController.dataManager.GetShoreInfo();
}
public bool IsWin() {
return !IsGameOver() && sceneController.dataManager.getRightPriestNum() >= 3;
}
public bool IsGameOver() {
if (sceneController.timer <= 0f || pi.leftRoleSub < 0 || pi.rightRoleSub < 0) {
return true;
} else return false;
}
}
数据管理类存储并管理了用于游戏中逻辑判断的数据,并且根据动作管理类与规则管理类的需求提供其所需要的数据。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DataManager : MonoBehaviour
{
private FirstController sceneController;
private Boat boat;
private Role[] roles;
private int[] leftShoreOccupied = new int[6]{1,2,3,4,5,6};
private int[] rightShoreOccupied = new int[6]{0,0,0,0,0,0};
public int[] boatOccupied = new int[2]{0,0};
private posInfo pi;
private shoreInfo si;
struct posInfo {
public Vector3 position;
public int index;
}
public int rightPriestNum = 0;
public struct shoreInfo {
public int leftRoleSub;
public int rightRoleSub;
}
// Start is called before the first frame update
void Start()
{
sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
sceneController.dataManager = this;
pi = new posInfo();
si= new shoreInfo();
si.leftRoleSub = 0;
si.rightRoleSub = 0;
}
// Update is called once per frame
void Update()
{
boat = sceneController.boat;
roles = sceneController.roles;
}
public Vector3 GoToBoat(ref Role role){
role.isOnShore = false;
int index = getRoleShoreIndex(role);
if (role.isLeft) leftShoreOccupied[index] = 0;
else {
rightShoreOccupied[index] = 0;
if (role.isPriest) {
rightPriestNum--;
si.rightRoleSub--;
}
}
if (boatOccupied[0] == 0 && role.isLeft == boat.isLeft) {
boatOccupied[0] = role.id;
sceneController.boat.roles[0] = role;
return Position.roleBoat[0] + boat.gameObject.transform.position;
} else if (boatOccupied[1] == 0 && role.isLeft == boat.isLeft) {
boatOccupied[1] = role.id;
sceneController.boat.roles[1] = role;
return Position.roleBoat[1] + boat.gameObject.transform.position;
}
role.isOnShore = true;
if (role.isLeft) leftShoreOccupied[index] = role.id;
else {
rightShoreOccupied[index] = role.id;
if (role.isPriest) {
rightPriestNum++;
si.rightRoleSub++;
}
}
return role.gameObject.transform.position;
}
public Vector3 LeaveBoat(ref Role role){
role.isOnShore = true;
bool cur = role.isLeft;
role.isLeft = boat.isLeft;
for (int i = 0; i < 2; i++){
if (boatOccupied[i] == role.id) {
role.isLeft = boat.isLeft;
pi = FindShorePosition(boat.isLeft);
if (pi.index < 6){
leftShoreOccupied[pi.index] = role.id;
if (role.isPriest) si.leftRoleSub++;
}
else {
rightShoreOccupied[pi.index - 6] = role.id;
if (role.isPriest) {
si.rightRoleSub++;
rightPriestNum++;
}
}
boatOccupied[i] = 0;
sceneController.boat.roles[i] = null;
return pi.position;
}
}
role.isOnShore = false;
role.isLeft = cur;
return role.gameObject.transform.position;
}
public shoreInfo GetShoreInfo(){
return si;
}
public bool IsBoatEmpty() {
return boatOccupied[0] != 0 || boatOccupied[1] != 0;
}
public Vector3 MoveBoat() {
sceneController.boat.isLeft = !sceneController.boat.isLeft;
if (!boat.isLeft) return Position.rightBoat;
else return Position.leftBoat;
}
private posInfo FindShorePosition(bool isLeft){
if (isLeft) {
for(int i = 5; i >= 0; i--) {
if (leftShoreOccupied[i] == 0) {
pi.position = Position.LroleShore[i];
pi.index = i;
return pi;
}
}
} else {
for(int i = 0; i < 6; i++) {
if (rightShoreOccupied[i] == 0) {
pi.position = Position.RroleShore[i];
pi.index = i + 6;
return pi;
}
}
}
return pi;
}
public int getRightPriestNum() {
return rightPriestNum;
}
private int getRoleShoreIndex(Role role){
if (role.isLeft) {
for (int i = 0; i < 6; i++) {
if (leftShoreOccupied[i] == role.id) return i;
}
} else {
for (int i = 0; i < 6; i++) {
if (rightShoreOccupied[i] == role.id) return i;
}
}
return 0;
}
}
动作管理类通过Update方法时刻检测鼠标点击的信号,并根据点击的对象的类型以及状态运行对应的动作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager : SSActionManager, ISSActionCallback {
private FirstController sceneController;
private CCMoveToAction move;
private CCSequenceAction move1;
public float speed = 4f;
protected new void Start() {
sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
sceneController.actionManager = this;
}
// Update is called once per frame
protected new void Update ()
{
base.Update ();
if (sceneController.isRun && Input.GetMouseButtonDown(0)){
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit)){
GameObject clickedObject = hit.collider.gameObject;
// 如果点击船,船不为空时可移动
if (sceneController.boat.gameObject == clickedObject){
if (sceneController.dataManager.IsBoatEmpty()) {
Vector3 target = sceneController.dataManager.MoveBoat();
move = CCMoveToAction.GetSSAction(target,speed);
RunAction(sceneController.boat.gameObject,move,this);
for (int i = 0; i < 2; i++) {
Role cur = sceneController.boat.roles[i];
if (cur != null) {
move = CCMoveToAction.GetSSAction(target + Position.roleBoat[i],speed);
RunAction(cur.gameObject,move,this);
}
}
}
}
// 如果点击角色
for (int i = 0; i < 6; i++) {
if (sceneController.roles[i].gameObject != clickedObject) continue;
if (sceneController.roles[i].isOnShore) {
move1 = GetMoveAction(sceneController.dataManager.GoToBoat(ref sceneController.roles[i]),sceneController.roles[i].gameObject.transform.position,speed);
} else {
move1 = GetMoveAction(sceneController.dataManager.LeaveBoat(ref sceneController.roles[i]),sceneController.roles[i].gameObject.transform.position,speed);
}
RunAction(sceneController.roles[i].gameObject,move1,this);
return;
}
}
}
}
private CCSequenceAction GetMoveAction(Vector3 target, Vector3 cur, float speed) {
List<SSAction> moves = new List<SSAction>();
Vector3 node1 = new Vector3(cur.x, 6f, cur.z);
Vector3 node2 = new Vector3(target.x, 6f, target.z);
moves.Add(CCMoveToAction.GetSSAction(node1,speed));
moves.Add(CCMoveToAction.GetSSAction(node2,speed));
moves.Add(CCMoveToAction.GetSSAction(target,speed));
return CCSequenceAction.GetSSAction(1,0,moves);
}
#region ISSActionCallback implementation
public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
{
}
#endregion
}
场景管理类中声明了刚刚提到的上述各类,便于各类间信息的传递,同时实现了ISceneController以及IUserAction接口的方法,便于SSDirector类的管理以及用户UI操作的处理。
using UnityEngine.SceneManagement;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction {
public CCActionManager actionManager { get; set;}
public RuleManager ruleManager { get; set;}
public DataManager dataManager{ get; set;}
// public GameObject move1,move2;
public Boat boat = new Boat();
public Shore[] shores = new Shore[2];
public River river = new River();
public Role[] roles = new Role[6];
public bool isRun = false;
public float timer = 60.0f;
// the first scripts
void Awake () {
SSDirector director = SSDirector.getInstance ();
director.setFPS (60);
director.currentSceneController = this;
director.currentSceneController.LoadResources ();
Debug.Log ("awake FirstController!");
}
// loading resources for first scence
public void LoadResources () {
boat.Initialize(Position.leftBoat);
shores[0] = new Shore();
shores[1] = new Shore();
shores[0].Initialize(Position.leftShore);
shores[1].Initialize(Position.rightShore);
river.Initialize(Position.river);
for(int i = 0; i < roles.Length; i++){
roles[i] = new Role();
if(i < 3)
roles[i].Initialize(true,Position.LroleShore[i]);
else
roles[i].Initialize(false,Position.LroleShore[i]);
}
}
#region IUserAction implementation
public bool GameOver ()
{
if (ruleManager.IsGameOver()) {
isRun = false;
return true;
} else return false;
}
public void Pause ()
{
// throw new System.NotImplementedException ();
isRun = false;
}
public void Resume ()
{
// throw new System.NotImplementedException ();
isRun = true;
}
public bool Win()
{
if (ruleManager.IsWin()) {
isRun = false;
return true;
} else return false;
}
public void GameStart()
{
isRun = true;
}
public void GameRestart()
{
Scene currentScene = SceneManager.GetActiveScene();
SceneManager.LoadScene(currentScene.name);
}
public int GetRemainTime()
{
return (int)timer;
}
#endregion
void Start () {
}
void Update () {
if(isRun) {
timer -= Time.deltaTime;
}
if(timer <= 0) {
isRun = false;
}
}
}
(3)View
用户GUI类用于GUI组件的设置及渲染
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
IUserAction sceneController;
public GUIStyle customButton;
public GUIStyle customBox;
public GUIStyle customTimer;
public GameObject winNotice, gameOverNotice;
String ButtonName1 = "Start";
String ButtonName2 = "Pause";
void Start(){
sceneController = SSDirector.getInstance().currentSceneController as IUserAction;
winNotice.SetActive(false);
gameOverNotice.SetActive(false);
}
void OnGUI()
{
GUI.Label (new Rect (20,20,100,40), "RemainTime: " + sceneController.GetRemainTime(),customTimer);
GUI.Box(new Rect(20,60,200,180), "Loader Menu", customBox);
if(GUI.Button(new Rect(20,90,160,40), ButtonName1, customButton))
{
if (ButtonName1 == "Start") {
sceneController.GameStart();
ButtonName1 = "ReStart";
} else {
sceneController.GameRestart();
ButtonName1 = "Start";
}
}
if(GUI.Button(new Rect(20,150,160,40), ButtonName2, customButton))
{
if (ButtonName2 == "Pause") {
sceneController.Pause();
ButtonName2 = "Resume";
} else {
sceneController.Resume();
ButtonName2 = "Pause";
}
}
if(sceneController.GameOver())
{
print("GameOver");
gameOverNotice.SetActive(true);
}
if(sceneController.Win())
{
print("Win");
winNotice.SetActive(true);
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。