牧师与魔鬼 动作分离版

牧师与魔鬼 动作分离版

目标

​ 建立动作管理器,使动作抽象出来,可以应用到任何游戏对象上,以此提高代码复用性。

UML图

这里写图片描述

核心设计

​ 将GenGameObject类物体移动的代码放在MoveToAction的类中,然后用ActionManager类对物体对象的动作进行管理,也就是说GenGameObject类的Update()不再进行物体的移动,而是在MoveToAction中执行。

​ 举个例子,船的移动,在GenGameObject.cs的代码如下:

    private FirstSceneActionManager actionManager;
    public void moveBoat()
    {
        if (numberOnBoat == 0) return;
        for(int i=0; i<2; i++)
        {
            if(objectOnBoat[i] != null) { objectOnBoat[i].transform.parent = boat.transform; }
        }
        if(boat.transform.position == leftBoatPos)
        {
            actionManager.moveBoat(boat, rightBoatPos, speed);
        }
        else if(boat.transform.position == rightBoatPos)
        {
            actionManager.moveBoat(boat, leftBoatPos, speed);
        }
    }

​ 可以看见,我们是通过调用actionManager.moveBoat来移动船只。actionManager是FirstSceneActionManager类型的。先让我们来看看FirstSceneActionManager.cs的代码。

public class FirstSceneActionManager : ActionManager {
    public void moveBoat(GameObject boat, Vector3 destination, float speed)
    {
        MoveToAction action = MoveToAction.getAction(destination, speed);
        this.addAction(boat, action);
    }

    public void moveCharacter(GameObject character, Vector3 destination, float speed)
    {
        Vector3 middlePos = character.transform.position;
        if(middlePos.y > destination.y) { middlePos.x = destination.x; }
        else { middlePos.y = destination.y; }
        ObjectAction action1 = MoveToAction.getAction(middlePos, speed);
        ObjectAction action2 = MoveToAction.getAction(destination, speed);
        ObjectAction seqAction = SequenceAction.getAction(0, new List<ObjectAction> { action1, action2 });
        this.addAction(character, seqAction);
    }
}

​ FirstSceneActionManager继承了ActionManager,前面说了,ActionManager是对物体动作的一个管理器,既然如此,为什么还要FirstSceneActionManager呢?目的就是为了让不同物体的动作可以更加具体化,例如:船的移动是通过moveBoat()控制,牧师与魔鬼的移动是moveCharacter()控制的。

​ 现在我们已经大概了解了动作分离的操作,简而言之就是在GenGameObject中通过动作管理器执行具体的动作。接下来看看具体的实现。

​ 针对FirstSceneActionManager的moveBoat()方法,先是声明定义了一个MoveToAction类型的action变量,看看MoveToAction的getAction()做了什么事:

//MoveToAction.cs
public static MoveToAction getAction(Vector3 position, float speed)
    {
        MoveToAction action = ScriptableObject.CreateInstance<MoveToAction>();
        action.position = position;
        action.speed = speed;
        return action;
    }

​ 该函数创建了一个MoveToAction的实例,然后为该实例的position、speed属性进行赋值。这两个属性是MoveToAction继承ObjectAction而来的,而ObjectAction是所有动作的一个基类,其代码简单,不再赘述,唯一需要注意的是ObjectAction类继承了ScriptableObject,ScriptableObject是不需要绑定GameObject对象的可编程基类。

​ 回到FirstSceneManager.cs的moveBoat(),该函数第二步是调用this.addAction(boat, action),该方法继承自ActionManager。

//ActionManager.cs
public void addAction(GameObject gameObject, ObjectAction action)
    {
        action.target = gameObject;
        action.transform = gameObject.transform;
        waitingAdd.Add(action);
        action.Start();
    }

​ 该方法设置了action的target、transform属性,然后向waitingAdd中添加了action对象,action.Start()的作用稍后会提到。waitingAdd中存放的action在Update()得到执行。

//ActionManager.cs
protected void Update()
    {
        foreach (ObjectAction ac in waitingAdd) { actions[ac.GetInstanceID()] = ac; }
        waitingAdd.Clear();

        foreach(KeyValuePair<int, ObjectAction> kv in actions)
        {
            ObjectAction ac = kv.Value;
            if (ac.destroy)
            {
                waitingDelete.Add(ac.GetInstanceID());
            }
            else
            {
                ac.Update();
            }
        }
        OnActionComplete();
    }

​ Update()先将waitingAdd中的所有action放在actions变量中,然后通过循环执行ac.Update()实现每帧的动作变化,当ac.destroy为true,意味着该动作完成(请看MoveToAction.cs的Update()),并将该动作放进waitingDelete里。Update()的最后一条语句执行OnActionComplete(),是对waitingDelete里的对象进行删除。

​ 至此我们已经知道了船是怎么通过动作管理器来实现移动的了,剩下的SequenceAction.cs是用来组合动作的,用于移动牧师与魔鬼,因为牧师与魔鬼的移动是先水平移动,再竖直方向移动。来看看SequenceAction的Update函数:

public override void Update()
    {
        if (sequence.Count <= currentActionIndex)
        {
            this.destroy = true;
            return;
        }
        sequence[currentActionIndex].Update();
        OnActionComplete();
    }

​ sequence中存放两个动作,通过currentActionIndex的值,控制哪个动作得到执行。currentActionIndex的初始值为0,此时sequence[currentActionIndex].Update()意味着执行第一个动作,每次Update()最后都执行OnActionComplete(),判断当前执行的动作是否已结束(通过destroy属性判断,同MoveToAction),如果已结束,就让currentActionIndex++,让下一帧开始执行下一个动作。当sequence.Count <= currentActionIndex,说明所有动作都结束了,并将destroy设为true,让管理者删除这个组合动作。最后要说说上文所说的ActionManager.cs中addAction()的最后一行代码的作用,就是为了设置组合动作中每一个动作的target、transform属性,详见SequenceAction.cs的Start()。

完整代码

https://github.com/Limsanity/Priests_and_Devils_v2

小结

​ 加入了动作管理器之后,我们的代码层次性就更明显,更好维护了。MVC结构和动作管理器的好处真得自己敲一次才能体会出来,建议大家看多个人的博客,比较学习。

猜你喜欢

转载自blog.csdn.net/qq_36184359/article/details/79888064