Devil and Priest: Separation of Actions

Keywords: Mobile github

First of all, review the requirements of the pastor and devil's mini-game:

There are three priests and three demons on one side of the river. They all wanted to go to the other side of the river, but there was only one boat, and the boat could only carry two people at a time. Someone must have driven the boat from one side to the other. In flash games, you can click on them to move them, click on the go button to move the demons on both sides of the river, they are killed, the game is over. You can try it in many ways. Let all the priests live!

Player action conditions for execution results of enforcement
Click on Priest/Devil The game is not over, the boat is not moving, on the same side as the boat. Priest/Devil Move
Click boat The game is not over. There is at least one character on board. The ship moved to the other end of the river.
Click Restart Winor lose Play the game again

Unlike the previous version, the priest and the devil asked for separation. In my understanding, it is to extract all the actions of the game objects, and arrange the execution of the actions through the action manager. In the previous version, each object had its own controller, such as the RoleModel class, which controlled the actions of the game characters (including priests and devils), such as getting on and off the boat, and the LandModel class, which controlled the actions of the river bank, such as providing empty space for the game characters. For example, the Move class controls the movement of priests and devils.

In this way, the scripts of each action are mounted on their own game objects and executed, which looks orderly on the surface, but is not conducive to management. The use of action separators means that these actions are placed in a general control manager, which unifies the execution of the actions.

Play a screenshot of the game (white sphere for priest, black box for devil):



The implementation is described below in conjunction with the code.
· SSDirector
Director category. The director uses the singleton mode to ensure that there is only one instance of the director, and is only responsible for controlling the scene record of the corresponding scene when the scene is initialized.

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
Interface namespaces. Provides the interface of scene control and player action control, only provides API for user interface, reduces coupling, and embodies good encapsulation.

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
Action controller, which manages the movement of boats and game characters, embodies the idea of action separation. The script does not need to be mounted on the object to run, which embodies the difference from the previous version.

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
The main class is responsible for instantiating interfaces ISceneController and UserAction to load resources and respond to user operations. When the referee class returns information about the end of a game, the game notes inform the player that the interactive class InteractGUI displays the end information.

public class FirstController : MonoBehaviour, ISceneController, UserAction
{
    public InteractGUI UserGUI;
    public CoastController fromCoast;
    public CoastController toCoast;
    public BoatController boat;
    //New Judgement Category
    //Judges need to be initialized with roles and ship controllers to get information about the game.
    public Judge judge;
    private Character[] Character;
    private FirstSceneActionManager FSAmanager;

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.currentScenceController = this;
        UserGUI = gameObject.AddComponent<InteractGUI>() as InteractGUI;
        //Store 6 Game Characters
        Character = new Character[6];
        LoadResources();
    }

    void Start()
    {
        FSAmanager = GetComponent<FirstSceneActionManager>();
    }

    public void LoadResources()
    {
        fromCoast = new CoastController("from");
        toCoast = new CoastController("to");
        boat = new BoatController();
        //Initialization of Judgement Class
        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";
        
        //Loading priest
        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;
        }
        
		//Loading the devil
        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;
        }
    }
	
	//Mouse click event
    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;
            }
            //Disembarkation
            boat.GetOffBoat(Objects.getName());
            FSAmanager.CharacterMove(Objects, whichCoast.getEmptyPosition());
            //Ashore action
            Objects.getOnCoast(whichCoast);
            whichCoast.getOnCoast(Objects);

        }
        else
        {
            CoastController whichCoast = Objects.getCoastController(); 

            if (boat.getEmptyIndex() == -1)
            {
                return;
            }

            if (whichCoast.get_State() != boat.get_State())  
                return;
                
			//Embarkation
            whichCoast.getOffCoast(Objects.getName());
            FSAmanager.CharacterMove(Objects, boat.getEmptyPosition());
            Objects.getOnBoat(boat);
            boat.GetOnBoat(Objects);
        }
        //Notify the controller that the game is over and display win or lose
        UserGUI.SetState = judge.Check();
    }

	//The movement of a ship
    public void MoveBoat()
    {
        if (FSAmanager.Complete == SSActionEventType.Started || boat.isEmpty()) return;
        FSAmanager.BoatMove(boat);
        //New Judgement Category
        UserGUI.SetState = judge.Check();
    }

	//Game End, Player Restart
    public void Restart()
    {
        fromCoast.reset();
        toCoast.reset();
        foreach (Character gameobject in Character)
        {
            gameobject.reset();
        }
        boat.reset();
    }
}

· Judge
The new referee class judges whether the game is over or not by simple counting.

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 interaction, user click events, and display results after the end of the game

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
Game role control, simply speaking, has four actions: landing, boarding, disembarking and landing. Location information (whether onshore, on the left or right) and mobile information (whether in mobile state, can not respond to click events) need to be recorded.

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
The main function of the riparian controller is to return to the remaining position on the riparian so that the game characters can settle down.

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
The control class of the ship, which provides the spare position on the ship, helps to realize the role of boarding and disembarking.

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;
    }
}

Finally, put GitHub on it Portal

Posted by php_wiz_kid on Wed, 25 Sep 2019 21:34:58 -0700