游戏初版
在初版的《牧师与魔鬼》游戏中使用了MVC架构将模型、视图、控制相互分离,但是游戏对象的运动以及游戏输赢的判定集中到了场景控制器中,使稍显臃肿。
为了减少场景控制器的复杂程度,尝试将游戏对象的运动以及游戏输赢的判定分离出来。
动作分离
动作管理器设计
- 动作类
创建SSAction类作为动作基类,并派生出两个子类CCMoveToAction类和CCsequenceAction类。其中CCMoveToAction实现了基本的对象移动动作,而CCSequenceAction则是将基本动作组合成一个复杂动作。 - 动作控制类
创建SSActionManager类作为基类,并在Update()函数中调用添加进动作管理器类的动作的Update()函数,当动作完成后通过ISSActionCallback接口像动作管理器传递消息。CCActionManager类继承SSActionManager类,可以根据当前场景自定义接收到动作完成消息后的逻辑操作。
在该游戏例子中,在FirstController中声明一个CCActionManager动作管理器对象,需要移动对象时只需要声明一个CCMoveToAction对象或者CCSequenceAction对象并添加到动作管理器对象即可实现动作从场景控制器中分离。
代码实现
- SSAction类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSAction : ScriptableObject {
public bool enable = true;
public bool destory = false;
public GameObject gameobject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callback { get; set; }
protected SSAction () {}
// Use this for initialization
public virtual void Start () {
throw new System.NotImplementedException ();
}
// Update is called once per frame
public virtual void Update () {
throw new System.NotImplementedException ();
}
}
- CCMoveToAction类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCMoveToAction : SSAction
{
public Vector3 target;
public float speed;
public static CCMoveToAction GetSSAction(Vector3 target, float speed){
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction> ();
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) {
//waiting for destroy
this.destory = true;
this.callback.SSActionEvent (this);
}
}
public override void Start () {
}
}
- CCSequenceAction类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCSequenceAction : SSAction, ISSActionCallback
{
public List<SSAction> sequence;
public int repeat = -1; //repeat forever
public int start = 0;
public static CCSequenceAction GetSSAction(int repeat, int start , List<SSAction> sequence){
CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction> ();
action.repeat = repeat;
action.sequence= sequence;
action.start = start;
return action;
}
// Update is called once per frame
public override void Update ()
{
if (sequence.Count == 0) return;
if (start < sequence.Count) {
sequence [start].Update ();
}
}
public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
{
source.destory = false;
this.start++;
if (this.start >= sequence.Count) {
this.start = 0;
if (repeat > 0) repeat--;
if (repeat == 0) { this.destory = true; this.callback.SSActionEvent (this); }
}
}
// Use this for initialization
public override void Start () {
foreach (SSAction action in sequence) {
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this;
action.Start ();
}
}
void OnDestory() {
}
}
- SSActionManager类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSActionManager : MonoBehaviour {
protected Dictionary <int, SSAction> actions = new Dictionary <int, SSAction> ();
private List <SSAction> waitingAdd = new List<SSAction> ();
private List<int> waitingDelete = new List<int> ();
// Update is called once per frame
protected void Update () {
foreach (SSAction ac in waitingAdd) actions [ac.GetInstanceID ()] = ac;
waitingAdd.Clear ();
foreach (KeyValuePair <int, SSAction> kv in actions) {
SSAction ac = kv.Value;
if (ac.destory) {
waitingDelete.Add(ac.GetInstanceID()); // release action
} else if (ac.enable) {
ac.Update (); // update action
}
}
foreach (int key in waitingDelete) {
SSAction ac = actions[key];
actions.Remove(key);
Object.Destroy(ac);
}
waitingDelete.Clear ();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager) {
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
waitingAdd.Add (action);
action.Start ();
}
// Use this for initialization
protected void Start () {
}
}
- CCActionManager类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager : SSActionManager, ISSActionCallback {
private FirstController sceneController;
protected new void Start() {
sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
sceneController.actionManager = this;
}
// Update is called once per frame
protected new void Update ()
{
base.Update ();
}
public bool is_all_destory(){
foreach (KeyValuePair <int, SSAction> kv in this.actions) {
SSAction ac = kv.Value;
if(!ac.destory) return false;
}
return true;
}
#region ISSActionCallback implementation
public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
{
if(is_all_destory()){
sceneController.set_state();
sceneController.set_moving(false);
sceneController.check();
}
}
#endregion
}
- ISSActionCallback接口
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SSActionEventType:int { Started, Competeted }
public interface ISSActionCallback
{
void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0 ,
string strParam = null,
Object objectParam = null);
}
- FirstController类改动
声明一个动作管理器actionManager。
public CCActionManager actionManager { get; set; }
为船以及船上的对象分别声明一个CCMoveToAction对象,并添加进动作管理器。
public void move_boat(){
if(boat.boat_num != 0){
moving = true;
if(boat.boat_state){
CCMoveToAction move1 = CCMoveToAction.GetSSAction(boat_left_position, 3);
actionManager.RunAction(boat.role, move1, actionManager);
for(int i = 0; i < boat.boat_num; i++){
Vector3 target_position = boat.object_on_boat[i].transform.position - new Vector3(6.0f, 0, 0);
CCMoveToAction object_move = CCMoveToAction.GetSSAction(target_position, 3);
actionManager.RunAction(boat.object_on_boat[i], object_move, actionManager);
}
boat.boat_object_position.x -= 6.0f;
}
else{
CCMoveToAction move1 = CCMoveToAction.GetSSAction(boat_right_position, 3);
actionManager.RunAction(boat.role, move1, actionManager);
for(int i = 0; i < boat.boat_num; i++){
Vector3 target_position = boat.object_on_boat[i].transform.position + new Vector3(6.0f, 0, 0);
CCMoveToAction object_move = CCMoveToAction.GetSSAction(target_position, 3);
actionManager.RunAction(boat.object_on_boat[i], object_move, actionManager);
}
boat.boat_object_position.x += 6.0f;
}
}
}
裁判类
创建一个裁判类Judge,在判定游戏输赢前传入当前游戏状况,根据当前游戏状况判定输赢并将结果返回给场景控制器。
- Judge类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Judge
{
bool state;
int left_priest;
int right_priest;
int left_devil;
int right_devil;
public Judge(){
state = false;
left_priest = 3;
right_priest = 0;
left_devil = 3;
right_devil = 0;
}
public void set_Judge(bool flag, int lp, int rp, int ld, int rd) {
state = flag;
left_priest = lp;
right_priest = rp;
left_devil = ld;
right_devil = rd;
}
public string judge_res() {
int priest_num;
int devil_num;
if(state){
priest_num = 3 - left_priest;
devil_num = 3 - left_devil;
if(priest_num == 3 && devil_num == 3) return "Win";
else if((devil_num > priest_num && priest_num != 0) || (left_devil > left_priest && left_priest != 0)) return "Lose";
else return "Continue";
}
else{
priest_num = 3 - right_priest;
devil_num = 3 - right_devil;
if((devil_num > priest_num && priest_num != 0)|| (right_devil > right_priest && right_priest != 0)) return "Lose";
else return "Continue";
}
}
}
- FirstController类改动
将原本的check()函数内容转移至裁判类Judge中。
声明一个裁判对象judge,并在check()函数中通过裁判对象获取游戏结果。
Judge judge = new Judge();
public void check(){
judge.set_Judge(boat.boat_state, priest_left.Count, priest_right.Count, devil_left.Count, devil_right.Count);
result = judge.judge_res();
}