首先回顾一下牧师与魔鬼的小游戏的要求:
在河的一边有三个牧师和三个恶魔。他们都想去这条河的另一边,但是只有一条船,而且这条船每次只能载两个人。一定有一个人把船从一边开到另一边。在flash游戏中,你可以点击它们来移动它们,点击go按钮来移动河两岸的恶魔,它们被杀死,游戏结束。你可以用很多方式来尝试。让所有的牧师都活着!
玩家动作 | 执行条件 | 执行结果 |
---|---|---|
点击牧师/魔鬼 | 游戏未结束,船没有正在移动,与船在相同的一边 | 牧师/魔鬼移动 |
点击船 | 游戏未结束,船上至少有一个角色 | 船移动到河流的另一头 |
点击Restart | 游戏结束(win or lose) | 各对象归位,游戏重新开始 |
和上一个版本不同的是,这次的牧师与魔鬼要求动作分离。在我的理解中,就是把所有游戏对象的动作都抽取出来,通过这个动作管理器统一安排动作的执行。上一个版本中,每个游戏对象有自己的控制器,如RoleModel类控制游戏角色(包括牧师和魔鬼)的动作,比如上下船;LandModel类控制河岸的动作,比如提供空位置给游戏角色。比如Move类控制牧师和魔鬼的移动。
这样,各个动作的脚本是挂载在各自的游戏对象身上执行的,表面看起来井井有条,实则不利于管理。使用动作分离器,就是将这些动作都安放在一个总的控制管理器中,由它统一安排动作的执行。
先放游戏截图(白色球体代表牧师,黑色方块代表魔鬼):
下面结合代码介绍实现。
· SSDirector
导演类。导演使用单例模式保证导演实例有且仅有一个,只负责在场景初始化时控制对应场景的场记。
public class SSDirector : System.Object
{
private 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;
}
}
· Interface
接口命名空间。提供场景控制、玩家动作控制的接口,只给用户界面提供API,降低耦合性,体现了良好的封装。
namespace Interfaces
{
public interface ISceneController
{
void LoadResources();
}
public interface UserAction
{
void MoveBoat();
void ObjectIsClicked(Character characterCtrl);
void Restart();
}
public enum SSActionEventType : int { Started, Completed }
public interface SSActionCallback
{
void SSActionCallback(SSAction source);
}
}
· FirstSceneActionManager
动作控制器,管理船和游戏角色的移动,这个类体现了动作分离的思想,脚本不需要挂载到对象身上运行,集中体现了和上一个版本的区别。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Interfaces;
public class FirstSceneActionManager : SSActionManager, SSActionCallback
{
public SSActionEventType Complete = SSActionEventType.Completed;
public void BoatMove(BoatController Boat)
{
Complete = SSActionEventType.Started;
CCMoveToAction action = CCMoveToAction.getAction(Boat.GetDestination(), Boat.GetMoveSpeed());
addAction(Boat.GetGameObject(), action, this);
Boat.ChangeState();
}
public void CharacterMove(Character GameObject, Vector3 Destination)
{
Complete = SSActionEventType.Started;
Vector3 CurrentPos = GameObject.GetPosition();
Vector3 MiddlePos = CurrentPos;
if (Destination.y > CurrentPos.y)
{
MiddlePos.y = Destination.y;
}
else
{
MiddlePos.x = Destination.x;
}
SSAction action1 = CCMoveToAction.getAction(MiddlePos, GameObject.GetMoveSpeed());
SSAction action2 = CCMoveToAction.getAction(Destination, GameObject.GetMoveSpeed());
SSAction seqAction = CCSequenceAction.getAction(1, 0, new List<SSAction> { action1, action2 });
this.addAction(GameObject.GetGameobject(), seqAction, this);
}
public void SSActionCallback(SSAction source)
{
Complete = SSActionEventType.Completed;
}
}
· Main
主类,负责实例化接口ISceneController和UserAction,实现加载资源和响应用户操作。当裁判类返回一个游戏结束的信息,则场记通知玩家交互类InteractGUI显示结束信息。
public class FirstController : MonoBehaviour, ISceneController, UserAction
{
public InteractGUI UserGUI;
public CoastController fromCoast;
public CoastController toCoast;
public BoatController boat;
//新增的裁判类
//裁判类需要用角色、船的控制器来初始化,以获取游戏进行的信息
public Judge judge;
private Character[] Character;
private FirstSceneActionManager FSAmanager;
void Awake()
{
SSDirector director = SSDirector.getInstance();
director.currentScenceController = this;
UserGUI = gameObject.AddComponent<InteractGUI>() as InteractGUI;
//存储6个游戏角色
Character = new Character[6];
LoadResources();
}
void Start()
{
FSAmanager = GetComponent<FirstSceneActionManager>();
}
public void LoadResources()
{
fromCoast = new CoastController("from");
toCoast = new CoastController("to");
boat = new BoatController();
//初始化裁判类
judge = new Judge(fromCoast, toCoast, boat);
GameObject river = Instantiate(Resources.Load("Prefabs/river", typeof(GameObject)), new Vector3(0, -7, 10), Quaternion.identity, null) as GameObject;
river.name = "river";
//加载牧师
for (int i = 0; i < 3; i++)
{
Character p = new Character("priest");
p.setName("priest" + i);
p.setPosition(fromCoast.getEmptyPosition());
p.getOnCoast(fromCoast);
fromCoast.getOnCoast(p);
Character[i] = p;
}
//加载魔鬼
for (int i = 0; i < 3; i++)
{
Character d = new Character("devil");
d.setName("devil" + i);
d.setPosition(fromCoast.getEmptyPosition());
d.getOnCoast(fromCoast);
fromCoast.getOnCoast(d);
Character[i + 3] = d;
}
}
//鼠标点击事件
public void ObjectIsClicked(Character Objects)
{
if (FSAmanager.Complete == SSActionEventType.Started) return;
if (Objects.isOnBoat())
{
CoastController whichCoast;
if (boat.get_State() == -1)
{
whichCoast = toCoast;
}
else
{
whichCoast = fromCoast;
}
//下船动作
boat.GetOffBoat(Objects.getName());
FSAmanager.CharacterMove(Objects, whichCoast.getEmptyPosition());
//上岸动作
Objects.getOnCoast(whichCoast);
whichCoast.getOnCoast(Objects);
}
else
{
CoastController whichCoast = Objects.getCoastController();
if (boat.getEmptyIndex() == -1)
{
return;
}
if (whichCoast.get_State() != boat.get_State())
return;
//上船动作
whichCoast.getOffCoast(Objects.getName());
FSAmanager.CharacterMove(Objects, boat.getEmptyPosition());
Objects.getOnBoat(boat);
boat.GetOnBoat(Objects);
}
//通知controller游戏结束,显示win或lose
UserGUI.SetState = judge.Check();
}
//船移动的动作
public void MoveBoat()
{
if (FSAmanager.Complete == SSActionEventType.Started || boat.isEmpty()) return;
FSAmanager.BoatMove(boat);
//新增的裁判类
UserGUI.SetState = judge.Check();
}
//游戏结束,玩家重新开始的动作
public void Restart()
{
fromCoast.reset();
toCoast.reset();
foreach (Character gameobject in Character)
{
gameobject.reset();
}
boat.reset();
}
}
· Judge
新增的裁判类,通过简单的计数,判断游戏是否结束
public class Judge : MonoBehaviour
{
CoastController fromCoast;
CoastController toCoast;
public BoatController boat;
public Judge(CoastController c1,CoastController c2, BoatController b)
{
fromCoast = c1;
toCoast = c2;
boat = b;
}
public int Check()
{ // 0->not finish, 1->lose, 2->win
int from_priest = 0;
int from_devil = 0;
int to_priest = 0;
int to_devil = 0;
int[] fromCount = fromCoast.GetobjectsNumber();
from_priest += fromCount[0];
from_devil += fromCount[1];
int[] toCount = toCoast.GetobjectsNumber();
to_priest += toCount[0];
to_devil += toCount[1];
if (to_priest + to_devil == 6) // win
return 2;
int[] boatCount = boat.GetobjectsNumber();
if (boat.get_State() == -1)
{ // boat at toCoast
to_priest += boatCount[0];
to_devil += boatCount[1];
}
else
{ // boat at fromCoast
from_priest += boatCount[0];
from_devil += boatCount[1];
}
if (from_priest < from_devil && from_priest > 0)
{ // lose
return 1;
}
if (to_priest < to_devil && to_priest > 0)
{
return 1;
}
return 0; // not finish
}
}
· InteractGUI
UI交互,实现用户的点击事件,以及在游戏结束后显示结果
public class InteractGUI : MonoBehaviour
{
UserAction UserAcotionController;
public int SetState { get { return GameState; } set { GameState = value; } }
static int GameState = 0;
// Use this for initialization
void Start()
{
UserAcotionController = SSDirector.getInstance().currentScenceController as UserAction;
}
private void OnGUI()
{
if (GameState == 1)
{
GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2-50, 100, 50), "Gameover!");
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart"))
{
GameState = 0;
UserAcotionController.Restart();
}
}
else if (GameState == 2)
{
GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2-50, 100, 50), "You Win!");
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart"))
{
GameState = 0;
UserAcotionController.Restart();
}
}
}
}
public class ClickGUI : MonoBehaviour
{
UserAction UserAcotionController;
Character GameObjectsInScene;
public void setController(Character characterCtrl)
{
GameObjectsInScene = characterCtrl;
}
void Start()
{
UserAcotionController = SSDirector.getInstance().currentScenceController as UserAction;
}
void OnMouseDown()
{
if (gameObject.name == "boat")
{
UserAcotionController.MoveBoat();
}
else
{
UserAcotionController.ObjectIsClicked(GameObjectsInScene);
}
}
}
· Character
游戏角色控制,简单来说有下岸、上船、下船、上岸四个动作。需要记录位置信息(是否在岸上,是在左岸还是右岸)以及移动信息(是否处于移动状态,是则无法响应点击事件)。
public class Character
{
CoastController coastController;
readonly GameObject Instance;
readonly ClickGUI clickGUI;
readonly int characterType; // 0->priest, 1->devil
int MovingState = -1; // Move = 1;Not Move = -1;
bool _isOnBoat = false;
public Character(string Type)
{
MovingState = -1;
if (Type == "priest")
{
Instance = Object.Instantiate(Resources.Load("Prefabs/priests", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
characterType = 0;
}
else
{
Instance = Object.Instantiate(Resources.Load("Prefabs/devils", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
characterType = 1;
}
clickGUI = Instance.AddComponent(typeof(ClickGUI)) as ClickGUI;
clickGUI.setController(this);
}
public void setName(string name)
{
Instance.name = name;
}
public void setPosition(Vector3 pos)
{
Instance.transform.position = pos;
}
public int getType()
{ // 0->priest, 1->devil
return characterType;
}
public string getName()
{
return Instance.name;
}
public void getOnBoat(BoatController boatCtrl)
{
coastController = null;
Instance.transform.parent = boatCtrl.GetGameObject().transform;
_isOnBoat = true;
}
public void getOnCoast(CoastController coastCtrl)
{
coastController = coastCtrl;
Instance.transform.parent = null;
_isOnBoat = false;
}
public bool isOnBoat()
{
return _isOnBoat;
}
public CoastController getCoastController()
{
return coastController;
}
public Vector3 GetPosition()
{
return Instance.transform.position;
}
public int GetMoveSpeed()
{
return 20;
}
public GameObject GetGameobject()
{
return Instance;
}
public void reset()
{
coastController = (SSDirector.getInstance().currentScenceController as FirstController).fromCoast;
getOnCoast(coastController);
setPosition(coastController.getEmptyPosition());
coastController.getOnCoast(this);
MovingState = -1;
}
public int GetMovingState()
{
return MovingState;
}
public void ChangeMovingstate()
{
MovingState = -MovingState;
}
}
· CoastController
河岸控制器,主要职能是返回河岸上空余的位置,以便游戏角色落脚。
public class CoastController
{
readonly GameObject coast;
readonly Vector3 from_pos = new Vector3(-16, -6, 10);
readonly Vector3 to_pos = new Vector3(16, -6, 10);
readonly Vector3[] positions;
readonly int State; // to->-1, from->1
Character[] passengerPlaner;
public CoastController(string _State)
{
positions = new Vector3[] {new Vector3(-21,-2.5F,10), new Vector3(-19,-2.5F,10), new Vector3(-17,-2.5F,10),
new Vector3(-15,-2.5F,10), new Vector3(-13,-2.5F,10), new Vector3(-11,-2.5F,10)};
passengerPlaner = new Character[6];
if (_State == "from")
{
coast = Object.Instantiate(Resources.Load("Prefabs/land1", typeof(GameObject)), from_pos, Quaternion.identity, null) as GameObject;
coast.name = "from";
State = 1;
}
else
{
coast = Object.Instantiate(Resources.Load("Prefabs/land2", typeof(GameObject)), to_pos, Quaternion.identity, null) as GameObject;
coast.name = "to";
State = -1;
}
}
public int getEmptyIndex()
{
for (int i = 0; i < passengerPlaner.Length; i++)
{
if (passengerPlaner[i] == null)
{
return i;
}
}
return -1;
}
public Vector3 getEmptyPosition()
{
Vector3 pos = positions[getEmptyIndex()];
pos.x *= State;
return pos;
}
public void getOnCoast(Character ObjectControl)
{
int index = getEmptyIndex();
passengerPlaner[index] = ObjectControl;
}
public Character getOffCoast(string passenger_name)
{ // 0->priest, 1->devil
for (int i = 0; i < passengerPlaner.Length; i++)
{
if (passengerPlaner[i] != null && passengerPlaner[i].getName() == passenger_name)
{
Character charactorCtrl = passengerPlaner[i];
passengerPlaner[i] = null;
return charactorCtrl;
}
}
return null;
}
public int get_State()
{
return State;
}
public int[] GetobjectsNumber()
{
int[] count = { 0, 0 };
for (int i = 0; i < passengerPlaner.Length; i++)
{
if (passengerPlaner[i] == null)
continue;
if (passengerPlaner[i].getType() == 0)
{ // 0->priest, 1->devil
count[0]++;
}
else
{
count[1]++;
}
}
return count;
}
public void reset()
{
passengerPlaner = new Character[6];
}
}
· BoatController
船的控制类,提供船上的空余位置,协助实现角色的上下船
public class BoatController
{
readonly GameObject boat;
readonly Vector3 fromPosition = new Vector3(-8, -5, 10);
readonly Vector3 toPosition = new Vector3(8, -5, 10);
readonly Vector3[] from_positions;
readonly Vector3[] to_positions;
int State; // to->-1; from->1
Character[] passenger = new Character[2];
int Speed = 15;
int MovingState = -1; // Move = 1;Not Move = -1;
public BoatController()
{
State = 1;
MovingState = -1;
from_positions = new Vector3[] { new Vector3(-9, -3.5F, 10), new Vector3(-7, -3.5F, 10) };
to_positions = new Vector3[] { new Vector3(7, -3.5F, 10), new Vector3(9, -3.5F, 10) };
boat = Object.Instantiate(Resources.Load("Prefabs/boat", typeof(GameObject)), fromPosition, Quaternion.identity, null) as GameObject;
boat.name = "boat";
boat.AddComponent(typeof(ClickGUI));
}
public int getEmptyIndex()
{
for (int i = 0; i < passenger.Length; i++)
{
if (passenger[i] == null)
{
return i;
}
}
return -1;
}
public bool isEmpty()
{
for (int i = 0; i < passenger.Length; i++)
{
if (passenger[i] != null)
{
return false;
}
}
return true;
}
public Vector3 getEmptyPosition()
{
Vector3 pos;
int emptyIndex = getEmptyIndex();
if (State == -1)
{
pos = to_positions[emptyIndex];
}
else
{
pos = from_positions[emptyIndex];
}
return pos;
}
public void GetOnBoat(Character ObjectControl)
{
int index = getEmptyIndex();
passenger[index] = ObjectControl;
}
public Character GetOffBoat(string passenger_name)
{
for (int i = 0; i < passenger.Length; i++)
{
if (passenger[i] != null && passenger[i].getName() == passenger_name)
{
Character charactorCtrl = passenger[i];
passenger[i] = null;
return charactorCtrl;
}
}
return null;
}
public GameObject GetGameObject()
{
return boat;
}
public void ChangeState()
{
State = -State;
}
public int get_State()
{ // to->-1; from->1
return State;
}
public int[] GetobjectsNumber()
{
int[] count = { 0, 0 };// [0]->priest, [1]->devil
for (int i = 0; i < passenger.Length; i++)
{
if (passenger[i] == null)
continue;
if (passenger[i].getType() == 0)
{
count[0]++;
}
else
{
count[1]++;
}
}
return count;
}
public Vector3 GetDestination()
{
if (State == 1) return toPosition;
else return fromPosition;
}
public int GetMoveSpeed()
{
return Speed;
}
public void reset()
{
State = 1;
boat.transform.position = fromPosition;
passenger = new Character[2];
MovingState = -1;
}
public int GetMovingState()
{
return MovingState;
}
public void ChangeMovingstate()
{
MovingState = -MovingState;
}
}
最后,放上GitHub传送门