• Unity3D 学习笔记


    注:本游戏开发环境为Unity3D 5.3.4 

    本星期要求:

    1. 模仿 AnimationEvent 编写一个 StateEvents 类
    2. 用户可以创建一个指定时间、指定状态触发的事件类
    3. 事件可以采用反射机制,调用调用客户的方法;或使用订阅发布方法调用客户的方法。
    4. 在你的动画控制程序中使用 StateEvents 类

    我采用的是上星期的Garen Pick the Balls小游戏,将Legacy动画部分用Mecanim重写。

    要点:

    1. 初次状态机开发,尚未实现Run和Attack同时进行(Blend Tree)

    2. Run状态的实现

    3. 位移旋转的新实现方法

    4. StatesEvents的实现方法

    5. C#反射机制

    必须要承认的是,这次开发尚有许多不完善的地方。

    通过这点也深刻认识到状态机的复杂性,在于如何将一个想实现的功能细分成许多小的原子状态,定义好混合状态,以及状态之间的迁移及其条件。

    希望各位多多赐教。

    1.首先,将所有素材的Animation Type改为Generic.

    自己新建Animator Controller - GarenController拖到Garen上。

    状态设计:Attack, Run, Idle三个

    迁移设计:考虑到Attack带一个状态事件StateEvent, 当运行到Attack一半时会发出通知给Judge回收小球,因此不希望有其他动作来中断Attack.

    Run和Attack,在发出攻击指令之后Run可以向Attack迁移。

    Run和Idle,在用户发出和取消移动指令之后Run和Idle相互迁移。

    状态机变量设计:

    Trigger Attack_Trigger,

    Bool Is_Run

    2.Run状态的实现,位移旋转实现的新方法

    与上次不同,这次实现位移和旋转的方法是:

    //In Update()                         
    float vertical = Input.GetAxis ("Vertical");
    float horizontal = Input.GetAxis ("Horizontal");
    Vector3 translation = new Vector3 (-horizontal, 0, -vertical) * speed * Time.deltaTime;、
    transform.position
    += translation; transform.rotation = Quaternion.LookRotation (Vector3.RotateTowards (transform.forward, translation, angularSpeed * Time.deltaTime, 0.0f));

    而判断用户是否有发出运动指令的方法是判断 if (translation == Vector3.zero)

    是, setbool("Is_Run", true)

    否,setbool("Is_Run", false);

    注意:Run状态自循环,且两个迁移无退出时间(uncheck Has exit time, Run可以在任何时间被中断)

    3.StateEvents类的实现方法

    吐槽一句:这里,和下面反射机制,完全是满足作业要求的技术训练,目前的Unity还是支持AnimationEvents的,比这里更加简便。

    定义StateEvents类:

    为了保证泛用性,采用动态传入方法的机制。

    public class StateEvent : System.Object {
            private float t;
            private bool fired;
            object method_base;
            System.Reflection.MethodInfo method_to_call;
            object[] param;
    
            public StateEvent(float trigger_time, object method_base, object method_to_call, object[] param) {
                    this.t = trigger_time;
                    this.fired = false;
                    this.method_base = method_base;
                    this.method_to_call = method_to_call as System.Reflection.MethodInfo;
                    this.param = param;
            }
    
            public bool triggerIt(float time) {
                    if ((time > this.t) & !fired) {
                            method_to_call.Invoke (method_base, param);
                            fired = true;
                            return true;
                    } else if ((time <= this.t) & fired) {
                            fired = false;
                            return false;
                    } else {
                            return false;
                    }
            }
    }

    用户定义且添加一个事件的方法是(这里略去attackOnHit()的定义,和上星期的一样):

            //Events Defined here.
            private System.Type garenControllerType = System.Type.GetType("GarenController");
            private StateEvent attack_to_half;
    
            // Use this for initialization
            void Start () {
    
                    //Init
                    animator = GetComponent<Animator> ();
    
                    attack_to_half = new StateEvent (0.5f, this, garenControllerType.GetMethod ("attackOnHit"), new object[]{});
    
            }

    这里attack_to_half里面就有了到什么时间,用什么参数Invoke哪个类里什么方法的信息。

    Update():

            void FixedUpdate () {
                    //Determining if state has changed from compare.
                    cur_ASI = animator.GetCurrentAnimatorStateInfo(0);
                    if (pre_ASI.fullPathHash == cur_ASI.fullPathHash) {
                            updateState (cur_ASI);    
                    } else {
                            //update ASI to current.
                            pre_ASI = cur_ASI;
                    }
            }

    updateState(AnimatorStateInfo):

    判断状态的机制!是要用当前animatorStateInfo.fullPathHash来判断,这个量会包含Animation Layer信息。

    SID_ATTACK1获得方法是Animator.StringToHash("Base Layer.Attack1");  同样指明了Layer信息。

    还是那句话,这是为了满足TriggerEvents硬造出来的机制,Unity开发者显然没有想到用户还需要自行去比较状态,因此代码有点Awkward.

            void updateState(AnimatorStateInfo cur_ASI){
                    if (cur_ASI.fullPathHash == SID_ATTACK1) {
                            attack_to_half.triggerIt (cur_ASI.normalizedTime);
                    }
            }

    4. C#反射机制:

    做法是:定义反射

    System.Reflection.MethodInfo method = System.Type.GetType("Your_Class").GetMethod("Method_To_Invoke");

    //把method当皮球传来传去... 或者上面这句话本来就在其他地方

    //需要呼叫这个方法时

    method.Invoke (object method_base, object[] param);

    method_base 是这个方法所在的类, param可以这样初始化param = new Object[] {p1, p2, ... , pn}; 对应方法的参数p1, p2, ... , pn.

    所以,Garen击打之后发消息给Judge的整条信息通路是:

    State进行到50%时,TriggerIt() 通过反射调用GarenController里的attackOnHit(), attackOnHit调用onHit(), onHit()通过订阅发布模式发消息给Judge.

    GarenController.cs:

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using Com.GarenPickingBalls;
    
    public class StateEvent : System.Object {
            private float t;
            private bool fired;
            object method_base;
            System.Reflection.MethodInfo method_to_call;
            object[] param;
    
            public StateEvent(float trigger_time, object method_base, object method_to_call, object[] param) {
                    this.t = trigger_time;
                    this.fired = false;
                    this.method_base = method_base;
                    this.method_to_call = method_to_call as System.Reflection.MethodInfo;
                    this.param = param;
            }
    
            public bool triggerIt(float time) {
                    if ((time > this.t) & !fired) {
                            method_to_call.Invoke (method_base, param);
                            fired = true;
                            return true;
                    } else if ((time <= this.t) & fired) {
                            fired = false;
                            return false;
                    } else {
                            return false;
                    }
            }
    }
            
    public class GarenController : MonoBehaviour {
    
            private iJudgeInterface iJudge = Judge.GetInstance () as iJudgeInterface;
    
            private Animator animator;
    
            private float speed = 3f;
            private float angularSpeed = 10f;
    
            private int SID_ATTACK1 = Animator.StringToHash ("Base Layer.Attack1");
    
            public static event onHitActionHandler onHit;
    
            private AnimatorStateInfo pre_ASI;
            private AnimatorStateInfo cur_ASI;
    
            //Events Defined here.
            private System.Type garenControllerType = System.Type.GetType("GarenController");
            private StateEvent attack_to_half;
    
            // Use this for initialization
            void Start () {
    
                    //Init
                    animator = GetComponent<Animator> ();
    attack_to_half = new StateEvent (0.5f, this, garenControllerType.GetMethod ("attackOnHit"), new object[]{}); pre_ASI = animator.GetCurrentAnimatorStateInfo (0); cur_ASI = animator.GetCurrentAnimatorStateInfo (0); initAllStates (); } // Update is called once per frame void FixedUpdate () { //Determining if state has changed from compare. cur_ASI = animator.GetCurrentAnimatorStateInfo(0); if (pre_ASI.fullPathHash == cur_ASI.fullPathHash) { updateState (cur_ASI); } else { //update ASI to current. pre_ASI = cur_ASI; } if (iJudge.getGameStatus () == 1) { if (cur_ASI.fullPathHash != SID_ATTACK1) { float vertical = Input.GetAxis ("Vertical"); float horizontal = Input.GetAxis ("Horizontal"); Vector3 translation = new Vector3 (-horizontal, 0, -vertical) * speed * Time.deltaTime; transform.position += translation; transform.rotation = Quaternion.LookRotation (Vector3.RotateTowards (transform.forward, translation, angularSpeed * Time.deltaTime, 0.0f)); if (translation == Vector3.zero) { resetRun (); } else { Run (); } } if (Input.GetMouseButtonDown (0)) { resetRun (); Attack (); } } else { resetRun (); } } void updateState(AnimatorStateInfo cur_ASI){ if (cur_ASI.fullPathHash == SID_ATTACK1) { attack_to_half.triggerIt (cur_ASI.normalizedTime); } } public void Run(){ animator.SetBool ("Is_Run", true); } public void Attack(){ animator.SetTrigger("Attack_Trigger"); } public void resetRun() { animator.SetBool("Is_Run", false); } void initAllStates() { resetRun (); } public void attackOnHit() { onHit (); } }
  • 相关阅读:
    pm2
    php 基础知识
    EBADF, read
    php apache
    noah
    ejs
    node linux
    枚举系统进程
    c++ 进程权限的提升
    Liunx的目录结构
  • 原文地址:https://www.cnblogs.com/wangsta/p/5422900.html
Copyright © 2020-2023  润新知