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
    • 给游戏添加了切换视角的功能,玩家可以根据自己喜好在全局视角以及船只跟随视角之间任意切换,游戏的体验更佳


    结果演示

  • 相关阅读:
    Centos7安装JDK+部署Tomcat8
    blog开发day3+Django admin的简单使用
    基于form组件的注册 + 基于Ajax的注册
    form组件复习+文件上传
    登录验证+验证码
    Django认证系统--Auth模块
    中间件
    form组件
    Ajax
    自定义分页 Cookie Session和获得路径
  • 原文地址:https://www.cnblogs.com/lwfing/p/13834810.html
Copyright © 2011-2022 走看看