美文网首页
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 抽象类别

      在上节实现了手柄输入之后,提到过这节将会把两种输入方式的共通点抽象出来,写成一个抽象基类,而两种输入方式成为其...

  • 生成器(Builder)

    定义它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。...

  • EIT造型 + 内涵 = 设计模式

    一、什么是EIT造型? 首先,我们先来了解类别有哪些? 一般(具象)类别:一般的函数。 抽象类别:有一个或者多个函...

  • 3. 创造型-抽象工厂模式

    模式简述 当一个类别的产品还有多个系列区分时,为了按系列生产商品,使用抽象工厂区分将生产产品的固定流程抽象出来抽象...

  • 设计模式之生成器

    生成器 又名:建造模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实...

  • 建造者模式

    一句话概述 建造模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现...

  • 抽象工厂模式

    抽象工厂模式实例解析: 通过对于游戏英雄的初始化来说,使用抽象工厂模式,可以在增加英雄类别的时候不用去关心其内部的...

  • 分类分析

    分类分析定义:根据现有数据中对用户或者产品等的类别特征,抽象归纳为模型,并能为新的用户或者产品等进行类别预测的过程...

  • java基础面试题2

    1,从数组中找出三个数,使它们的和为定值,求出所有这样的组合。 2,抽象类和接口的区别抽象类主要用来抽象类别,接口...

  • Unity3D部署导出的Webgl项目同时支持手机

    Foldcc2017-8-18 Unity在抛弃掉flash后支持导出全新的H5项目WegGl,但是目前Unity...

网友评论

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

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