Get Started with Unity3D - 3

操作与总结

  1. 参考 Fantasy Skybox FREE 构建自己的游戏场景

    在Unity3D中,Skybox可以方便我们通过贴图快速创建游戏场景,而对于小白用户来说,Assets Store中的Skybox资源已然足够丰富,随意找到一个免费资源,已然足够精美。在本项目中,为了实现更好的效果,我们通过球形组件来模拟Skybox,并结合Standard Assets中的Water预设建立场景。

  2. 写一个简单的总结,总结游戏对象的使用

    作为游戏中的实体,游戏对象在游戏中的地位重要性不言而喻。因而,游戏对象的组织也是相当重要的课题。通过分离功能、分层管理,我们就能更精准、更有条理的对对象们进行操作。譬如在这两次作业中,MVC和预制的结合很好地帮助我们对对象进行管理,给予了我们项目更好的可维护性和可拓展性。如此产出的代码质量和第一次作业时的产物是不可同日而语的。

编程实践 - 动作分离版牧师与魔鬼

项目仓库地址(含视频)

为什么要动作分离?

在我们原本的架构中,对象的动作由其对应的Controller负责,这样虽然通过对象类型的不同区分了各个动作,然而却忽略了一个关键事实:在游戏中,对象的运动其实都是相似的,这种相似使得其可以单独作为一类存在。再者,我们也需要一个单独的Manager来对Action进行统一的管理,以此进一步降低维护成本。

如何实现?

  1. 动作基类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class SSAction : ScriptableObject
    {
    public bool enable = true;
    public bool destoried = false;

    public GameObject gameobject { get; set; }
    public Transform transform { get; set; }
    public ISSActionCallback callback { get; set; }

    protected SSAction() {}

    public virtual void Start ()
    {
    throw new System.NotImplementedException ();
    }

    public virtual void Update ()
    {
    throw new System.NotImplementedException ();
    }
    }

    SSAction为所有动作的基类,包含对对象进行操作的必须属性。同时,enable和destoried两个bool值分别决定其是否在Manager的Update中被更新,以及是否被Manager清除。callback为动作完成时的回调,在实现组合动作时通知上个action的完成,并让manager调用下一个action。

  2. 两种动作

    在本项目中,我们有两种动作亟待实现:船的移动动作和人物的上/下船动作。也就是实际上是一个直线平移,一个是跳跃动作(在本项目中,我们使用抛物线而不是折线实现)。那么首先是直线移动:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class CCMoveToAction : SSAction {
    public Vector3 target;
    public float speed;

    public static CCMoveToAction GetSSAction (Vector3 target, float speed, GameObject gameobject) {
    CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction> ();
    action.target = target;
    action.speed = speed;
    action.gameobject = gameobject;
    action.transform = gameobject.transform;
    return action;
    }

    public override void Start() {
    }
    public override void Update() {
    this.transform.position = Vector3.MoveTowards (this.transform.position, target, speed * Time.deltaTime);
    if (this.transform.position == target) {
    this.destoried = true;
    }
    }
    }

    需要特别注意的是Update在判断到达指定位置后需要将destoried设为true,通知manager释放掉这个action。然后我们看看抛物线运动:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class CCJumpToAction : SSAction {
    public Vector3 target;
    public float speedx;
    public float speedy;
    public static readonly float gravity = 100;

    public static CCJumpToAction GetSSAction (Vector3 target, float speed, GameObject gameobject) {
    CCJumpToAction action = ScriptableObject.CreateInstance<CCJumpToAction> ();
    action.gameobject = gameobject;
    action.transform = gameobject.transform;
    action.target = target;
    action.speedx = speed * ((target.x < action.transform.position.x) ? -1 : 1);
    float deltaTime = (target.x - action.transform.position.x) / action.speedx;
    action.speedx *= deltaTime * 2;
    deltaTime = 0.5F;
    action.speedy = Mathf.Abs((target.y - action.transform.position.y) / deltaTime + 0.5F * gravity * deltaTime);
    return action;
    }

    public override void Start () { }

    public override void Update ()
    {
    var newSpeedy = speedy - gravity * Time.deltaTime;
    this.transform.position += (Vector3.right * speedx * Time.deltaTime + Vector3.up * 0.5F * (newSpeedy + speedy) * Time.deltaTime);
    speedy = newSpeedy;
    if (this.transform.position.x == target.x || this.transform.position.y <= target.y) {
    this.transform.position = target;
    this.destoried = true;
    }
    }
    }

    和直线运动类似,不过我们需要在创建的时候计算初始速度。并且由于我们使用近似的轨迹算法,所以在最后判断到达的时候需要用不等号连接,确保不会越界。

  3. 动作管理

    顾名思义,动作管理类是为了管理SSAction派生出来的众多动作而设置的,它通过维护三个列表来实现对全体动作的管理,我们另外提供了一个子类,来对动作进行进一步的分类,使之和具体对象产生联系。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    public class SSActionManager : MonoBehaviour, ISSActionCallback {
    private Dictionary <int, SSAction> actions = new Dictionary <int, SSAction>();
    private List <SSAction> waitngAdd = new List<SSAction> ();
    private List <int> waitingDelete = new List<int> ();

    protected void Update() {
    foreach (SSAction ac in waitngAdd)
    actions [ac.GetInstanceID ()] = ac;
    waitngAdd.Clear ();

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

    foreach (int key in waitingDelete) {
    SSAction ac = actions [key];
    actions.Remove (key);
    DestroyObject (ac);
    }

    waitingDelete.Clear ();
    }

    public void RunAction (SSAction action, ISSActionCallback manager) {
    action.callback = manager;
    waitngAdd.Add (action);
    action.Start ();
    }

    public void SSActionEvent (SSAction source,
    SSActionEventType type,
    int intPar,
    string strPar,
    Object objPar) {}

    protected void Start() { }
    }

    public class EmiliaScenceActionManager : SSActionManager {
    private readonly static float defaultSpeed = 10;

    public void MoveBoat (BoatController boat) {
    CCMoveToAction action = CCMoveToAction.GetSSAction (boat.getDestination(), defaultSpeed, boat.boat);
    RunAction (action, this);
    }

    public void MoveCharacter (ICharacterController character, Vector3 destination) {
    CCJumpToAction action = CCJumpToAction.GetSSAction (destination, defaultSpeed, character.character);
    RunAction (action, this);
    }

    public void Update() {
    base.Update ();
    }
    }

    如此之后,我们在FirstScenceController中实例化一个EmiliaScenceActionManager即可实现对全体对象的动作管理。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×