zoukankan      html  css  js  c++  java
  • unity 3d 游戏设计(三)牧师与魔鬼动作分离版

    Homework3编程实践

    牧师与魔鬼 动作分离版

    在上一周的编程实践中,我们已经完成了一个具有MVC架构的牧师与魔鬼的版本,而我们这周的任务就是实现一个动作管理器,把我们上周写在GameObjectControllerSceneController里的对于GameObject动作的处理写出来,通过一个SceneActionManager的实例来管理,让其专门负责GameObject的动作。下面我们来谈谈动作管理器。



    建立动作管理器的意义

    在上一个版本的简单的牧师与恶魔的游戏中,我为了避免FirstSceneController中对于物体移动操作的代码量过多,把物体的动作封装在GameObjectController里面,通过这种做法,FirstSceneController的代码量的确得到了一定的减少,但是这样的话我每写一个GameObjectController我就要多写一个物体运动的函数,一旦需要移动的GameObject多起来,可能冗余的代码量就会大量增多了,而且一旦我们需要修改Move,我们可能就每一个类都要进行修改了,显然这对于开发者非常不友好。

    这个时候,动作管理器的意义就体现出来了,就以我们这次的作业为例子,Move的一类的动作抽象成一个动作类MoveAction,然后通过ActionManager对其进行管理。我们在需要物体进行移动时,只需要新建一个MoveAction,调用ActionManaeraddAction,把具体的参数传给它,ActionManager就会执行我们的动作,我们再也不用将Move这类的动作一个一个写在我们的GameObjController中,大大减少了我们的代码量,大大提高了我们代码的复用性,一旦动作方面的需求修改,我们也只需要修改该Action类的内容,以及相关调用创建该Action的地方,更加利于我们的维护以及让程序更能适应需求变化



    在了解了动作管理器的意义之后,我们就可以开始我们动作管理器的编写了,我们先画一个动作管理器的UML图理清一下思路

    然后与上一节课的UML图结合起来变成整个程序的UML图,画出来UML图之后,我们程序的架构就很清晰了。

    下面我们分类进行描述

    ActionCallBack

    这个方法主要是提供一个让动作完成时调用的接口,一旦动作完成,该接口对应实现的方法就会被调用,动作管理器可以对动作的完成进行响应。

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


    SSAction

    SSAction这个类就是所有动作对象类抽象出来的一个不需要绑定 GameObject 对象的可编程基类,其动作的实现由其子类实现Update中的内容实现,这个在后面的MoveAction就有体现,所有的SSAction对象受ActionManager管理。

    public class SSAction : ScriptableObject {
    	public bool enable = true;
    	public bool destroy = false;
    
    	public GameObject gameObject;
    	public Transform transform;
    	public ActionCallback callback;
    
    	public virtual void Start()
    	{
    		throw new System.NotImplementedException();
    	}
    
    	public virtual void Update()
    	{
    		throw new System.NotImplementedException();
    	}
    }
    


    MoveAction

    MoveAction就是上面SSAction的一个子类,它的作用就是根据用户所提供的目的地和速度,使得物体完成一定速度的向某一目的地的直线运动。其通过Update的实现来完成逐帧的对于物体位置的变动,实现一个所谓的物体的"动作"。

    public class MoveAction : SSAction {
    	public Vector3 target;
    	public float speed;
    
    	private MoveAction(){
    	}
    
    	public static MoveAction getAction(Vector3 target, float speed) {
    		MoveAction action = ScriptableObject.CreateInstance<MoveAction> ();
    		action.target = target;
    		action.speed = speed;
    		return action;
    	}
    
    	// Use this for initialization
    	public override void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	public override void Update () {
    		transform.position = Vector3.MoveTowards(transform.position, target, speed*Time.deltaTime);
    		if (transform.transform.position == target) {
    			destroy = true;
    			callback.actionDone (this);
    		}
    	}
    }
    


    CCSequenceAction

    这是一个动作顺序执行序列的是SSAction的子类,其通过一个Action的List,和actionDone对于List的维护以及Update对于调用哪个Action的Update方法的控制,实现了一个动作顺序执行的效果。十分方便了我们对于一个连续动作的编程。

    public class CCSequenceAction : SSAction, ActionCallback {
    	public List<SSAction> sequence;
    	public int repeat = 1; // 1->only do it for once, -1->repeat forever
    	public int currentActionIndex = 0;
    
    	public static CCSequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence) {
    		CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();
    		action.sequence = sequence;
    		action.repeat = repeat;
    		action.currentActionIndex = currentActionIndex;
    		return action;
    	}
    
    	public override void Update() {
    		if (sequence.Count == 0)return;
    		if (currentActionIndex < sequence.Count) {
    			sequence[currentActionIndex].Update();
    		}
    	}
    
    	public void actionDone(SSAction source) {
    		source.destroy = false;
    		this.currentActionIndex++;
    		if (this.currentActionIndex >= sequence.Count) {
    			this.currentActionIndex = 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

    这个就是我们这一期的主角ActionManager了,其负责了action的增加、删除、执行。它通过在Update中调用SSAction的Update方法,实现对于动作的一个调度,管理动作的自动执行。

    public class SSActionManager : MonoBehaviour, ActionCallback {
    	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 addAction(GameObject gameObject, SSAction action, ActionCallback callback) {
    		action.gameObject = gameObject;
    		action.transform = gameObject.transform;
    		action.callback = callback;
    		waitingToAdd.Add(action);
    		action.Start();
    	}
    
    	public void actionDone(SSAction source) {
    
    	}
    }
    
    


    FirstSceneActionManager

    FirstSceneActionManager就是我们上面SSActionManager的一个子类,对于我们场景中具体的动作进行了封装,我们只需要在FirstSceneController中调用该方法,就可以实现我们之前的Move了,十分方便了我们的编程。

    public class FirstSceneActionManager : SSActionManager {
    	public void toggleBoat(BoatController boat) {
    		MoveAction action = MoveAction.getAction (boat.getTarget (), boat.speed);
    		this.addAction (boat.getBoat (), action, this);
    		boat.toggle ();
    	}
    
    	public void moveCharacter(ICharacterController character, Vector3 target) {
    		Vector3 nowPos = character.getPos ();
    		Vector3 tmpPos = nowPos;
    		if (target.y > nowPos.y) {
    			tmpPos.y = target.y;
    		} else {
    			tmpPos.x = target.x;
    		}
    		SSAction action1 = MoveAction.getAction(tmpPos, character.speed);
    		SSAction action2 = MoveAction.getAction(target, character.speed);
    		SSAction sequenceAction = CCSequenceAction.getAction(1, 0, new List<SSAction>{action1, action2});
    		this.addAction(character.getInstance(), sequenceAction, this);
    	}
    }
    

    FirstController

    在完成了FirstSceneActionManager后,我们把GameObjectController中以及FirstController中一些关于动作的部分删除,然后在原来动作的部分调用FirstSceneActionManager提供新的方法就可以了。(不过因为一开始设计架构的时候没有考虑加入动作管理器,加之最近时间较紧,没有太多时间对游戏进行重构,所以FirstController中仍然有着部分关于判断物体运动的逻辑)

    public class FirstController : MonoBehaviour, ISceneController, IUserAction {
        UserGUI userGUI;
    
        public LandController rightLand;
        public LandController leftLand;
        public BoatController boat;
        public ICharacterController[] characters;
    	private FirstSceneActionManager actionManager;
    
        void Awake()
        {
            GameDirector director = GameDirector.getInstance();
            director.currentSceneController = this;
            userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
            genGameObjects();
        }
    
    	void Start() {
    		actionManager = GetComponent<FirstSceneActionManager> ();
    	}
    
        public void genGameObjects()
        {
            characters = new ICharacterController[6];
            boat = new BoatController();
            leftLand = new LandController(-1);
            rightLand = new LandController(1);
    
            for (int i = 0; i < 3; i++)
            {
                ICharacterController priest = new ICharacterController(0, "priest" + i);
                priest.setPosition(rightLand.getEmptyPosition());
                priest.getOnLand(rightLand);
                rightLand.getOnLand(priest);
                characters[i] = priest;
            }
    
            for (int i = 0; i < 3; i++)
            {
                ICharacterController demon = new ICharacterController(1, "demon" + i);
                demon.setPosition(rightLand.getEmptyPosition());
                demon.getOnLand(rightLand);
                rightLand.getOnLand(demon);
                characters[i+3] = demon;
            }
        }
    
    
        public void ClickCharacter(ICharacterController character)
        {
            if (userGUI.status != 0 || !boat.available())
            {
                return;
            }
            if (character.isOnBoat()) {
                LandController land;
                if (boat.getBoatPos() == 0)
                {
                    land = leftLand;
                }
                else
                {
                    land = rightLand;
                }
                boat.getOffBoat(character.getName());
    			actionManager.moveCharacter (character, land.getEmptyPosition ());
                character.getOnLand(land);
                land.getOnLand(character);
            }
            else
            {
                LandController land = character.getLandController();
                if (boat.getEmptyIndex() == -1)
                    return;
                int landPos = land.getType(), boatPos = (boat.getBoatPos() == 0) ? -1 : 1;
                if (landPos != boatPos)
                    return;
                land.getOffLand(character.getName());
    			actionManager.moveCharacter (character, boat.getEmptyPosition ());
                character.getOnBoat(boat, boat.getEmptyIndex());
                boat.getOnBoat(character);
            }
            userGUI.status = checkResult();
        }
    
        public void ToggleBoat()
        {
    		if (userGUI.status != 0 || boat.isEmpty() || !boat.available())
                return;
    		actionManager.toggleBoat (boat);
            userGUI.status = checkResult();
        }
    
        int checkResult()
        {
            int leftPriests = 0;
            int rightPriests = 0;
            int leftDemons = 0;
            int rightDemons = 0;
    
            int[] leftStatus = leftLand.getStatus();
            leftPriests += leftStatus[0];
            leftDemons += leftStatus[1];
    
            if (leftPriests + leftDemons == 6)
                return 2;
    
            int[] rightStatus = rightLand.getStatus();
            rightPriests += rightStatus[0];
            rightDemons += rightStatus[1];
    
            int[] boatStatus = boat.getBoatStatus();
            if (boat.getBoatPos() == 0)
            {
                leftPriests += boatStatus[0];
                leftDemons += boatStatus[1];
            }
            else
            {
                rightPriests += boatStatus[0];
                rightDemons += boatStatus[1];
            }
    
            if (leftPriests > 0 && leftPriests < leftDemons)
                return 1;
            if (rightPriests > 0 && rightPriests < rightDemons)
                return 1;
    
            return 0;
        }
    
        public void restart()
        {
            boat.reset();
            leftLand.reset();
            rightLand.reset();
            for (int i = 0; i < characters.Length; i++)
                characters[i].reset();
        }
    }
    


    改进

    • 根据上课学到的内容,给Camera加上了SkyBox,美化了程序的UI
    • 给游戏添加了切换视角的功能,玩家可以根据自己喜好在全局视角以及船只跟随视角之间任意切换,游戏的体验更佳


    结果演示

  • 相关阅读:
    UVa 116 单向TSP(多段图最短路)
    POJ 1328 Radar Installation(贪心)
    POJ 1260 Pearls
    POJ 1836 Alignment
    POJ 3267 The Cow Lexicon
    UVa 1620 懒惰的苏珊(逆序数)
    POJ 1018 Communication System(DP)
    UVa 1347 旅行
    UVa 437 巴比伦塔
    UVa 1025 城市里的间谍
  • 原文地址:https://www.cnblogs.com/lwfing/p/13834810.html
Copyright © 2011-2022 走看看