您的位置:首页 > 移动开发 > Unity3D

Unity3D学习——牧师与恶魔过河游戏(组合模式、单实例模式)

2017-04-10 21:50 218 查看

成品展示

通过鼠标点击实现对象移动



原游戏Priests and Devils传送门,有兴趣的可以去玩下:

http://www.flash-game.net/game/2535/priests-and-devils.html

游戏制作

如果需要,可以按如下步骤先设置好预设后,再把文章最后的代码复制进脚本,即可运行察看游戏效果,所用unity版本为5.5

预制与脚本的挂载









下面预制中的牧师与恶魔我加了上两个Tag,以方便把它们分类统计。









游戏设计以及代码分析

采用MVC结构,单实例模式,类的UML图如下



组合模式产生动作,单实例模式方便管理

各类说明

导演类:管理游戏全局状态,获取当前游戏的场景,协调各个类之间的通讯,是单实例类,不被Unity内存管理所管理

界面:负责与用户交互

场记:管理本次场景所有的游戏对象,协调游戏对象(预制件级别)之间的通讯,响应外部输入事件,管理本场次的规则,杂务

动作管理者:被场记调用,为场景中的对象设计具体动作并执行

红色方框中为基本的动作类

各类具体代码

导演类 SSDirector

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum State { WIN, LOSE, PAUSE, CONTINUE , START };
public class SSDirector : System.Object
{
//计时器
public int totalSeconds = 60;
public int leaveSeconds;
public bool onCountDown = false;
public string countDownTitle = "Start";

public  State state { get; set; }
public static SSDirector _instance;
public ISceneController currentScenceController { get; set; }
public bool running { get; set; }

public static SSDirector getInstance()
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}

public int getFPS()
{
return Application.targetFrameRate;
}

public void setFPS(int fps)
{
Application.targetFrameRate = fps;
}

public void NextScene()
{
Debug.Log("抱歉,没下一个场景了");
}

public IEnumerator DoCountDown()
{
while (leaveSeconds > 0)
{
yield return new WaitForSeconds(1f);
leaveSeconds--;
}
}
}


界面交互 UserGUI

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/**
* 玩家动作接口,鼠标点击是唯一动作
*/
public interface IUserAction
{
void clickOne();
}

public class UserGUI : MonoBehaviour
{
private IUserAction action;
float width, height;

void Start()
{
action = SSDirector.getInstance().currentScenceController as IUserAction;
}

float castw(float scale)
{
return (Screen.width - width) / scale;
}

float casth(float scale)
{
return (Screen.height - height) / scale;
}

void OnGUI()
{
width = Screen.width / 12;
height = Screen.height / 12;

GUI.Label(new Rect(castw(2f)+20, casth(6f) - 20, 50, 50), SSDirector.getInstance().leaveSeconds.ToString());// 倒计时秒数 //

if (SSDirector.getInstance().state != State.WIN && SSDirector.getInstance().state != State.LOSE
&& GUI.Button(new Rect(10, 10, 80, 30), SSDirector.getInstance().countDownTitle))
{
if (SSDirector.getInstance().countDownTitle == "Start")
{

SSDirector.getInstance().currentScenceController.Resume();
SSDirector.getInstance().countDownTitle = "Pause";
SSDirector.getInstance().onCountDown = true;
StartCoroutine(SSDirector.getInstance().DoCountDown());
}
else
{
SSDirector.getInstance().currentScenceController.Pause();
SSDirector.getInstance().countDownTitle = "Start";
SSDirector.getInstance().onCountDown = false;
StopAllCoroutines();
}
}

if (SSDirector.getInstance().state == State.WIN)//胜利
{
StopAllCoroutines();
if (GUI.Button(new Rect(castw(2f), casth(6f), Screen.width / 8, height), "Win!"))
{
SSDirector.getInstance().currentScenceController.Restart();
}
}
else if (SSDirector.getInstance().state == State.LOSE)//失败
{
StopAllCoroutines();
if (GUI.Button(new Rect(castw(2f), casth(6f), Screen.width / 7, height), "Lose!"))
{
SSDirector.getInstance().currentScenceController.Restart();
}
}
}

/**
* 检测用户的点击动作
*/
void Update()
{
action.clickOne();
}

}


场景管理器 FirstSceneController

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/**
* 场记的接口,每个场记都要实现
* 加载场景资源、场景的暂停、恢复和重新布置
*/
public interface ISceneController
{
void LoadResources();
void Pause();
void Resume();
void Restart();
}

public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController {

public FirstSSActionManager actionManager;

List<GameObject> LeftObjList = new List<GameObject>();//存储在左岸的对象
List<GameObject> RightObjList = new List<GameObject>();//存储在右岸的对象
GameObject[] boat = new GameObject[2];

GameObject boat_obj, leftShore_obj, rightShore_obj;

Vector3 LeftShorePos = new Vector3(-12, 0, 0);
Vector3 RightShorePos = new Vector3(12, 0, 0);
Vector3 BoatLeftPos = new Vector3(-4, 0, 0);
Vector3 BoatRightPos = new Vector3(4, 0, 0);

void Awake()
{
SSDirector director = SSDirector.getInstance();
director.setFPS(60);
director.currentScenceController = this;
director.currentScenceController.LoadResources();
director.leaveSeconds = director.totalSeconds;

}

void Start () {

SSDirector.getInstance().state = State.PAUSE;
SSDirector.getInstance().countDownTitle = "Start";
actionManager = GetComponent<FirstSSActionManager>() as FirstSSActionManager;
}

void Update()
{
Judge();
}

public void LoadResources()
{
GameObject priest_obj, devil_obj;
Camera.main.transform.position = new Vector3(0, 0, -20);

/**
* 加载两岸
* 加载船
*/
leftShore_obj = Instantiate(Resources.Load("Prefabs/Shore"), LeftShorePos, Quaternion.identity) as GameObject;
rightShore_obj = Instantiate(Resources.Load("Prefabs/Shore"), RightShorePos, Quaternion.identity) as GameObject;
leftShore_obj.name = "left_shore";
rightShore_obj.name = "right_shore";

boat_obj = Instantiate(Resources.Load("Prefabs/Boat"), BoatLeftPos, Quaternion.identity) as GameObject;
boat_obj.name = "boat";

/**
* 一开始牧师、魔鬼、船都在左岸,注意:我是通过判断父对象决定物体在两岸还是船上的
*/
boat_obj.transform.parent = leftShore_obj.transform;

/**
* 把牧师与恶魔摆在左岸初始位置
*/
for (int i = 0; i < 3; ++i)
{
priest_obj = Instantiate(Resources.Load("Prefabs/Priest")) as GameObject;
priest_obj.name = i.ToString();//牧师编号0 1 2
priest_obj.transform.position = new Vector3(-16f + 1.5f * Convert.ToInt32(priest_obj.name), 2.7f, 0);
priest_obj.transform.parent = leftShore_obj.transform;
LeftObjList.Add(priest_obj);

devil_obj = Instantiate(Resources.Load("Prefabs/Devil")) as GameObject;
devil_obj.name = (i + 3).ToString();//魔鬼编号3 4 5
devil_obj.transform.position = new Vector3(-16f + 1.5f * Convert.ToInt32(devil_obj.name), 2.7f, 0);
devil_obj.transform.parent = leftShore_obj.transform;
LeftObjList.Add(devil_obj);
}
}

/**
* 实现用户动作接口中的点击对象函数
* 利用射线获取对象
*   对象名字 0—2代表牧师
*            3—5代表魔鬼
*            boat代表船
* 若对象名字为其他,或者没点中对象则不处理
*/
public void clickOne()
{
GameObject gameObj = null;

if (Input.GetMouseButtonDown(0) &&
(SSDirector.getInstance().state == State.START || SSDirector.getInstance().state == State.CONTINUE))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
gameObj = hit.transform.gameObject;
}
}

if (gameObj == null) return;
else if (gameObj.name == "0" || gameObj.name == "1" || gameObj.name == "2"
|| gameObj.name == "3" || gameObj.name == "4" || gameObj.name == "5")
{
MovePeople(gameObj);
}
else if(gameObj.name == "boat")
{
MoveBoat();
}
}

/**
* 人的移动分四种情况
* 上船:左岸上左船,右岸上右船
* 上岸:左船上左岸、右船上右岸
* 其他情况人都不会动
*/
void MovePeople(GameObject people)
{
int shoreNum, seatNum;//0为左,1为右,作为参数传给动作管理员

if (people.transform.parent == boat_obj.transform.parent && (boat[0] == null || boat[1] == null))//物体和船都在同一个岸且船有空位才能上船
{
seatNum = boat[0] == null ? 0 : 1;
if (people.transform.parent == leftShore_obj.transform)
{
shoreNum = 0;
for (int i = 0; i < LeftObjList.Count; i++)
{
if (people.name == LeftObjList[i].name)
{
actionManager.getOnBoat(people, shoreNum, seatNum);//让动作管理员执行上船动作
LeftObjList.Remove(LeftObjList[i]);
}
}
}
else
{
shoreNum = 1;
for (int i = 0; i < RightObjList.Count; i++)
{
if (people.name == RightObjList[i].name)
{
actionManager.getOnBoat(people, shoreNum, seatNum);
RightObjList.Remove(RightObjList[i]);
}
}
}
boat[seatNum] = people;
people.transform.parent = boat_obj.transform;
}
else if (people.transform.parent == boat_obj.transform)//若物体在船上就选择上岸
{
shoreNum = boat_obj.transform.parent == leftShore_obj.transform ? 0 : 1;
seatNum = (boat[0] != null && boat[0].name == people.name) ? 0 : 1;

actionManager.getOffBoat(people, shoreNum);//动作管理员去执行上岸动作

boat[seatNum] = null;
if(shoreNum == 0)
{
people.transform.parent = leftShore_obj.transform;
LeftObjList.Add(people);
}
else
{
people.transform.parent = rightShore_obj.transform;
RightObjList.Add(people);
}
}
}

/**
* 若船上有人,则船可驶向对岸
* 让动作管理器去负责移动小船
*/
void MoveBoat()
{
if(boat[0]!=null || boat[1] != null)
{
actionManager.moveBoat(boat_obj);

boat_obj.transform.parent = boat_obj.transform.parent == leftShore_obj.transform ?
rightShore_obj.transform : leftShore_obj.transform;
}
}

/**
* 判断游戏输赢状态并告知导演
* 计算分三步,先统计左岸的牧师、恶魔
*             然后统计右岸的牧师、恶魔
*             再判断船在左岸还是右岸,把船上恶魔与牧师加到对应岸的数量上
*  若某一岸恶魔 > 牧师,则游戏失败
*  若全到右岸,则胜利
*  否则游戏继续
*/
public void Judge()
{
int left_d = 0, left_p = 0, right_d = 0, right_p = 0;

foreach (GameObject element in LeftObjList)
{
if (element.tag == "Priest") left_p++;
if (element.tag == "Devil") left_d++;
}

foreach (GameObject element in RightObjList)
{
if (element.tag == "Priest") right_p++;
if (element.tag == "Devil") right_d++;
}

for (int i = 0; i < 2; i++)
{
if (boat[i] != null && boat_obj.transform.parent == leftShore_obj.transform)//船在左岸
{
if (boat[i].tag == "Priest") left_p++;
else left_d++;
}
if (boat[i] != null && boat_obj.transform.parent == rightShore_obj.transform)//船在右岸
{
if (boat[i].tag == "Priest") right_p++;
else right_d++;
}
}

if ((left_d > left_p && left_p != 0) || (right_d > right_p && right_p != 0) || SSDirector.getInstance().leaveSeconds == 0)
{
SSDirector.getInstance().state = State.LOSE;
}
else if (right_d == right_p && right_d == 3)//全过河,赢了
{
SSDirector.getInstance().state = State.WIN;
}
}

public void Pause()
{
SSDirector.getInstance().state = State.PAUSE;
}

public void Resume()
{
SSDirector.getInstance().state = State.CONTINUE;
}

public void Restart()
{
Application.LoadLevel(Application.loadedLevelName);
SSDirector.getInstance().state = State.START;
}
}


动作管理者 FirstSSActionManager

我把三个基本动作的类SSAction、MoveToAction、 SequenceAction也一起放到了这里,然后SSActionManager是对基本动作的管理。

再写一个FirstSSActionManager继承SSActionManager,专门实现对FirstSceneController场景里对象的动作设计,这样场记可以更方便地让FirstActonManager执行动作。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISSActionCallback
{
void actionDone(SSAction source);
}

/**
* SSAction是动作的基类,保存要移动的游戏对象属性
*/
public class SSAction : ScriptableObject
{

public bool enable = true;
public bool destroy = false;

public GameObject gameObject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callback { get; set; }

public virtual void Start()
{
throw new System.NotImplementedException();
}

public virtual void Update()
{
throw new System.NotImplementedException();
}
}

/**
* MoveToAction是SSAction的一个子类,代表平移的动作。
*/
public class MoveToAction : SSAction
{
public Vector3 target;
public float speed;

private MoveToAction() { }
public static MoveToAction getAction(Vector3 target, float speed)
{
MoveToAction action = ScriptableObject.CreateInstance<MoveToAction>();
action.target = target;
action.speed = speed;
return action;
}

public override void Update()
{
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
if (this.transform.position == target)
{
this.destroy = true;
this.callback.actionDone(this);
}
}

public override void Start() {}

}

/**
* SequenceAction是SSAction的另一个子类,它代表一系列组合动作。
*/
public class SequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence;
public int repeat = -1; //-1表示无限循环,0表示只执行一遍,repeat> 0 表示重复repeat遍
public int currentAction = 0;//当前动作列表里,执行到的动作序号

public static SequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence)
{
SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
action.sequence = sequence;
action.repeat = repeat;
action.currentAction = currentActionIndex;
return action;
}

public override void Update()
{
if (sequence.Count == 0) return;
if (currentAction < sequence.Count)
{
sequence[currentAction].Update();
}
}

public void actionDone(SSAction source)
{
source.destroy = false;
this.currentAction++;
if (this.currentAction >= sequence.Count)
{
this.currentAction = 0;
if (repeat > 0) repeat--;
if (repeat == 0)
{
this.destroy = true;
this.callback.actionDone(this);
}
}
}

public override void Start()
{
foreach (SSAction action in sequence)
{
action.gameObject = this.gameObject;
action.transform = this.transform;
action.callback = this;
action.Start();
}
}

void OnDestroy()
{
foreach (SSAction action in sequence)
{
DestroyObject(action);
}
}
}

/**
* SSActionManager统筹上面三个动作类
*/
public class SSActionManager : MonoBehaviour
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingToAdd = new List<SSAction>();
private List<int> watingToDelete = new List<int>();

protected void Update()
{
foreach (SSAction ac in waitingToAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingToAdd.Clear();

foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
{
watingToDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
ac.Update();
}
}

foreach (int key in watingToDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
watingToDelete.Clear();
}

public void RunAction(GameObject gameObject, SSAction action, ISSActionCallback whoToNotify)
{
action.gameObject = gameObject;
action.transform = gameObject.transform;
action.callback = whoToNotify;
waitingToAdd.Add(action);
action.Start();
}

}

/**
* 专门设计动作并执行
*/
public class FirstSSActionManager : SSActionManager, ISSActionCallback
{
public FirstSceneController scene;
public MoveToAction action1, action2;
public SequenceAction saction;
float speed = 30f;

/**
* 为小船设置水平移动动作
*/
public void moveBoat(GameObject boat)
{
action1 = MoveToAction.getAction((boat.transform.position ==  new Vector3(4, 0, 0)? new Vector3(-4, 0, 0) : new Vector3(4, 0, 0)), speed);
this.RunAction(boat, action1, this);
}

/**
* 人的上船动作
* 为人设置水平和垂直两个分解动作,然后组合起来执行
*/
public void getOnBoat(GameObject people, int shore, int seat)
{
if (shore == 0 && seat == 0)
{
action1 = MoveToAction.getAction(new Vector3(-5f, 2.7f, 0), speed);//右移
action2 = MoveToAction.getAction(new Vector3(-5f, 1.2f, 0), speed);//下移
}
else if(shore==0 && seat == 1)
{
action1 = MoveToAction.getAction(new Vector3(-3f, 2.7f, 0), speed);
action2 = MoveToAction.getAction(new Vector3(-3f, 1.2f, 0), speed);
}
else if (shore == 1 && seat == 0)
{
action1 = MoveToAction.getAction(new Vector3(3f, 2.7f, 0), speed);
action2 = MoveToAction.getAction(new Vector3(3f, 1.2f, 0), speed);
}
else if (shore == 1 && seat == 1)
{

action1 = MoveToAction.getAction(new Vector3(5f, 2.7f, 0), speed);
action2 = MoveToAction.getAction(new Vector3(5f, 1.2f, 0), speed);
}

SequenceAction saction = SequenceAction.getAction(0, 0, new List<SSAction> { action1, action2 });//组合动作
this.RunAction(people, saction, this);
}

/**
* 人的下船动作
*/
public void getOffBoat(GameObject people, int shoreNum)
{
action1 = MoveToAction.getAction(new Vector3(people.transform.position.x, 2.7f, 0), speed);//上移

if (shoreNum == 0)  action2 = MoveToAction.getAction(new Vector3(-16f + 1.5f * Convert.ToInt32(people.name), 2.7f, 0), speed);//左移
else action2 = MoveToAction.getAction(new Vector3(16f - 1.5f * Convert.ToInt32(people.name), 2.7f, 0), speed);//右移

SequenceAction saction = SequenceAction.getAction(0, 0, new List < SSAction >{ action1, action2});//组合动作
this.RunAction(people, saction, this);
}

protected void Start()
{
scene = (FirstSceneController)SSDirector.getInstance().currentScenceController;
scene.actionManager = this;
}

protected new void Update()
{
base.Update();
}

public void actionDone(SSAction source)
{
Debug.Log("Done");
}
}


对类之间的操作还不熟练,自己设计得很粗糙,有出错的地方请大家指正TAT。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: