魔鬼与牧师——动作分离版

首先回顾一下牧师与魔鬼的小游戏的要求:

在河的一边有三个牧师和三个恶魔。他们都想去这条河的另一边,但是只有一条船,而且这条船每次只能载两个人。一定有一个人把船从一边开到另一边。在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传送门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值