美文网首页
Unity—— #18 抽象类别

Unity—— #18 抽象类别

作者: MisakiMel | 来源:发表于2019-07-21 11:21 被阅读0次

      在上节实现了手柄输入之后,提到过这节将会把两种输入方式的共通点抽象出来,写成一个抽象基类,而两种输入方式成为其的子类。
      首先我们可以来找找两种输入方式有什么共通点。在人物移动上,key string name两者对应的按钮不一样,肯定不用考虑。在移动信息处理上,虽然两者的处理方式不一样,但都需要变量来存储相关信息,可以把变量提取出来。这些都是两者都有的变量:

        [Header("===== Move =====")]
        private float targetUp;    //键入按钮直接转1跟0
        private float targetRight;
    
        private float curVelocityDup;    //平滑处理函数要用到的引用参数
        private float curVelocityDright;
    
        public float Dup;    //平滑处理
        public float Dright;
    
        public float Dmag;    //记录移动速度
        public Vector3 Dvec;  //记录移动方向
    

      在相机移动上,也有类似的变量是两者都有的:

        [Header("===== Camera Move =====")]
        public float CUp;  //相机移动方向
        public float CRight;
    

      还有3个动作的信号(跑、跳、滚)也是两者都有的变量:

        [Header("===== State =====")]
        public bool run;
        public bool jump;
        public bool attack;
    

      最后我们之前说过,两者都是需要椭圆映射法来解决斜向1.414的问题,所以都需要我们自己定义的SquareToCircle(Vector2 temp)函数:

        Vector2 SquareToCircle(Vector2 temp){
            Vector2 output = Vector2.zero;
            output.x = temp.x * Mathf.Sqrt (1 - (temp.y * temp.y) / 2);
            output.y = temp.y * Mathf.Sqrt (1 - (temp.x * temp.x) / 2);
            return output;
        }
    

      至此两者的共同点都基本找完了,其实都是两者都会用到的变量和函数。现在需要我们去新建一个抽象类,把这些通通都灌进去了。
      在这里我想先讨论一下为什么选择抽象类而不是接口。接口里面是不允许定义函数,只允许声明函数,实现部分是留给子类去解决的;而抽象类则允许定义函数在里面。因为现在这两个输入方式用到的椭圆映射法的函数是同一实现,并无区别的,所以使用抽象类更好一点。
      在project视窗,右键create→C# Script新建C#脚本文件,命名为IUserInput(前缀为I表明它是一个抽象类or接口):把刚才列出来的代码通通塞进去:

    public abstract class IUserInput : MonoBehaviour {
    
        public bool InputEnabled=true;
    
        [Header("===== Move =====")]
        public float Dup;
        public float Dright;
    
        protected float curVelocityDup;
        protected float curVelocityDright;
    
        protected float targetUp;
        protected float targetRight;
    
        public float Dmag;
        public Vector3 Dvec;
    
        [Header("===== Camera Move =====")]
        public float CUp;
        public float CRight;
    
        [Header("===== State =====")]
        public bool run;
        public bool jump;
        public bool attack;
    
    
        protected Vector2 SquareToCircle(Vector2 temp){
            Vector2 output = Vector2.zero;
            output.x = temp.x * Mathf.Sqrt (1 - (temp.y * temp.y) / 2);
            output.y = temp.y * Mathf.Sqrt (1 - (temp.x * temp.x) / 2);
            return output;
        }   
    }
    

      要注意的是,为了能让子类用上父类的变量和函数,要把原本修饰符是private的变量和函数改为protected,这样这些就只能子类去使用了,其他类是用不了的。基类就这样完事了,现在要考虑的就是两个子类和涉及到这两个子类的其他组件的修改。
      把PlayerInout和JoystrickInput的父类改为IUserInput,并把父类已经有的东西给删除掉。
    public class JoystickInput : IUserInputpublic class PlayerInput : IUserInput。诶有人可能会问:这里继承了IUserInput,那原本它们继承的MonoBehaviour现在没得继承了,岂不是很多函数都用不了了?非也,因为它们的父类IUserInput已经继承了MonoBehaviour了,所以它们自己也会继承MonoBehaviour的。
      现在要对一些涉及了输入类型变量的组件进行修改,在这里就有两个:ActorController.cs和CameraController.cs,在没有实现手柄输入前,这两个都声明了PlayerInput类型的变量pi,用来获取输入的相关信息。现在要把它们改为IUserInput:

        public IUserInput pi;
    

      在获取组件方面,不能用GetComponent<>(),因为对于IUserInput,其真正实现是两个子类,如果想实现两种输入方式都检测得到的话,要用GetComponents<>(),否则Unity只会按组件顺序优先取第一个组件,无论其是否被勾上,像这样:


      如果你用的是GetComponent<>(),那么无论你PlayerInput是否勾上,它都只会获取这个组件而忽略JoystickInput。所以我们应该用GetComponents<>()获取一个组件数组,然后逐个侦测看哪个组件被勾上,就选取该组件作为输入方式。
      在ActorController.cs里,我们是使用enabled属性去获知哪个输入方式是被勾上的:
        void Awake(){
            IUserInput[] temp =  GetComponents<IUserInput>();
                foreach (var item in temp){
                if (item.enabled == true) {
                    pi = item;
                }
            }
        ...
        }
    

      而对于CameraController.cs,之前获取PlayerInput的做法是把这个变量曝露出去,然后在外面把PlayerInput组件直接灌进来。这次我通过原本已有的PlayerHandle变量去获得IUserInput,因为在PlayerInpupt的ActorController.cs里面(如上)已经为我们选好了能用的输入方式,所以在这直接把它拿过来用就是了,没必要再来筛选一遍了。

    void Awake () {
            cameraHandle = transform.parent.gameObject;     //负责垂直旋转
            playerHandle = cameraHandle.transform.parent.gameObject;        //负责水平旋转
            //pi = playerHandle.GetComponent<ActorController>().pi;
            model = playerHandle.GetComponent<ActorController>().model;     //获取模型
            camera = Camera.main.gameObject;        //获取主要摄像头(Tag为MainCamera)
            tempEuler = 20.0f;
        }
    
        void Start(){
            pi = playerHandle.GetComponent<ActorController> ().pi;
        }
    

      不过要注意的是,这里pi的初始化是不能放在Awake()阶段的,因为pi初始化所需要的playerHandle变量也是在Awake()阶段才被初始化,所以是要到它被初始化后(Awake()函数执行完毕),才能去初始化pi。因此要把它的初始化放在Start()阶段。
      至此,整个抽象类别就完成了。现在可以来看看效果:


      可以看到现在是用手柄输入的,且各项功能正常。下一节我们将开始讨论防御的实现。

    相关文章

      网友评论

          本文标题:Unity—— #18 抽象类别

          本文链接:https://www.haomeiwen.com/subject/dacmlctx.html