Unity 继承组件

为什么要继承组件?

比如游戏中,有很多种类型的小怪。每个小怪身上都有一个控制脚本。

当然,不可能所有小怪都用同样的控制脚本。

但是,在玩家攻击小怪的时候,不管是哪一种小怪,它都要减少血量。

当然,不可能枚举所有类型的控制脚本,然后调用它们各自的函数来减少血量。

这就到了面向对象思想发挥作用的时候了。

我们将所有小怪的共性,都抽象成一个小怪基类控制脚本,它包含所有小怪所拥有的基本逻辑。

比如都有血量,都有状态机,都有碰撞检测。

然后呢,每种不同的小怪,都有它不同的行为模式。

比如有的小怪,默认状态是在路边睡觉,有的小怪默认状态要巡逻。

这就需要各自派生的控制脚本中去实现了。

怎么对派生出来的各种小怪控制脚本执行统一的操作?

比如我现在有一个子弹的脚本,当它击打到小怪身上的时候,就要获取小怪身上的组件。

但是有很多种小怪,每种小怪身上挂的组件都是小怪基类组件的特定派生类,它们是不同的,当然不可能枚举地去获取派生类组件。

但它们又是相同的,它们都继承于同一基类。

获取基类的组件就好了。

需要注意的点

如果一个组件继承了另一个组件,并且派生类组件中有基类组件中使用过的 MonoBehaviour 生命周期函数,比如Update,那么在游戏的运行中,基类组件的同名的生命周期函数会被隐藏(不是重写)。如果需要它运行,则需要基类中,将该基类生命周期函数设置为public,然后在派生类中的相应生命周期函数中调用基类的该生命周期函数,比如 base.Update()。

示例

基类组件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class EmenyController : MonoBehaviour
{
    public FiniteStateMachine finiteStateMachine;

    public void SetTransition(TransitCondition t)
    {
        finiteStateMachine.Transit(t);
        Debug.Log(t.ToString());

        //转换到追逐状态时,显示UI
        if (t == TransitCondition.SawPlayer || t == TransitCondition.BeAttacked)
            floatingHealthBar.active = true;
        else
            floatingHealthBar.active = false;
    }

    public void Update()    //会被隐藏而不再执行
    {
        isAlive();
        UpdateUI();
    }

    public float health;
    public GameObject floatingHealthBar;

    public void OnHurt(float value)
    {
        health -= value;

        //播放受击动画
        GetComponent<Animator>().SetTrigger("Hurt");
    }

    void UpdateUI()
    {
        //血条
        floatingHealthBar.transform.LookAt(Camera.main.transform);
        floatingHealthBar.GetComponent<Slider>().value = health / 100;
    }

    void isAlive()
    {
        if (health <= 0)
        {
            GetComponent<Animator>().SetTrigger("Dead");
            enabled = false;
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision == null) return;

        if (collision.gameObject.tag == "Attack")
            SetTransition(TransitCondition.BeAttacked);
    }
}

派生类组件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class QQController : EmenyController
{
    void Awake()
    {
        InstantiateFiniteStateMachine();
    }

    public Transform home;
    void InstantiateFiniteStateMachine()
    {
        QQRestState restState = new QQRestState(gameObject);
        restState.AddTransition(TransitCondition.SawPlayer, StateIndex.ChasingPlayer);
        restState.AddTransition(TransitCondition.BeAttacked, StateIndex.ChasingPlayerAdvanced);

        QQReturnHomeState qqReturnHomeState = new QQReturnHomeState(home);
        qqReturnHomeState.AddTransition(TransitCondition.AtHome, StateIndex.Sleep);
        qqReturnHomeState.AddTransition(TransitCondition.SawPlayer, StateIndex.ChasingPlayer);
        qqReturnHomeState.AddTransition(TransitCondition.BeAttacked, StateIndex.ChasingPlayerAdvanced);

        QQChaseState chaseState = new QQChaseState();
        chaseState.AddTransition(TransitCondition.LostPlayer, StateIndex.ReturnHome);

        QQChaseStateAdvanced chaseStateAdvanced = new QQChaseStateAdvanced();
        chaseState.AddTransition(TransitCondition.LostPlayer, StateIndex.ReturnHome);

        finiteStateMachine = new FiniteStateMachine();
        finiteStateMachine.AddState(restState);
        finiteStateMachine.AddState(chaseState);
        finiteStateMachine.AddState(chaseStateAdvanced);
        finiteStateMachine.AddState(qqReturnHomeState);
    }

    public GameObject player;
    public float playerDistance;
    public float homeDistance;
    void Update()
    {
        base.Update();

        if (player == null || player.active == false)
            player = GameObject.FindGameObjectWithTag("Player");

        playerDistance = Vector3.Distance(player.transform.position, transform.position);
        homeDistance = Vector3.Distance(home.position, transform.position);

        Debug.Log(finiteStateMachine.currentStateIndex);
    }

    void FixedUpdate()
    {
        finiteStateMachine.currentState.Work(player, gameObject);
        finiteStateMachine.currentState.isTransitable(player, gameObject);
    }
}

附加知识点:隐藏和覆盖的区别

隐藏

当子类继承父类,并且子类中有父类同名函数,那么子类将隐藏父类中所有同名函数,不可以对父类中同名函数直接进行访问。

如果子类需要调用父类中函数,需要用 base.函数名 去访问。

覆盖

当父类中同名函数被virtual修饰时,此时子类对父类函数进行重写并覆盖。

区别

当基类指针指向子类,并调用子类中的同名函数。如果该基类的函数是被隐藏的,则仍然可以通过该基类指针调用到。如果该函数被重写了,则调用的是子类重写的同名函数,父类的函数相当于被抹去了,不可被调用。

共同点

无论是重写,还是隐藏,通过子类对象进行函数调用,都只会调用子类的函数,而隐藏父类的所有同名函数。

猜你喜欢

转载自blog.csdn.net/weixin_43673589/article/details/124307061