Unity - Использование компонентов в качестве интерфейса - PullRequest
0 голосов
/ 22 сентября 2018

Необходимость:

Чтобы иметь возможность взаимодействовать с Component экземплярами различного типа, прикрепленными к GameObject экземплярам через interface.

Например, если у меня есть игра с солдатами, и если предположить, что медики и снайперы оба являются солдатами, я хочу иметь возможность прикрепить компонент Soldier к солдату GameObject, независимо от того, является ли этот солдат Sniper или Medic.Затем я мог бы сделать что-то следующим образом: soldier.GetComponent<Soldier>().Respawn();, что в итоге вызовет либо Medic.Respawn(), либо Sniper.Respawn(), в зависимости от фактического типа солдата.

Возможное, но грязное решение 1:

Первым наивным подходом было бы использование компонентов Sniper и Medic для реализации Soldier interface.Однако это вызывает несколько проблем.

Например, если вы хотите проверить, есть ли у GameObject компонент, реализующий Soldier, вы не сможете этого сделать, потому что Soldier является только интерфейсом, а нефактическое единство Component.Таким образом, вызов GetComponent<Soldier>() для GameObject, имеющего, например, компонент Medic, не вернет этот компонент Medic, даже если Medic действительно реализует Soldier.

(На самом деле выможно проверить это путем перебора всех компонентов и использования оператора is, но это будет грязно и медленно).

Возможное, но грязное решение 2:

Второй подход заключается в создании базового Component класса Soldier, от которого наследуются классы Medic и Sniper.

Но это также создает несколько проблем.

Во-первых,события Unity (Awake(), Start() и т. д.) будут вызываться только на листовых классах иерархии, заставляя вас вручную вызывать те же функции в родительском классе.Любой, кто попробовал это, знает, что легко забыть вызвать что-то, что приводит к неправильно инициализированным объектам, например.

И, во-вторых, обычные проблемы наследования также здесь.Например, если я хочу, чтобы мои Medic и Sniper составляющие были не только Soldier, но также были Explodable или VehicleDriver или чем-то еще, я не могу, потому что C # не поддерживает множественное наследование.

Подход, о котором я думаю:

Я подумал о том, как спроектировать мой код так, чтобы проблемы, перечисленные выше, были решены.

Идея состоит в том, чтобы иметь класс Component, который действует как интерфейс, и чтобы этот компонент интерфейса сосуществовал с остальным компонентом в том же GameObject.Другими словами, пусть два игровых объекта.Один из них будет иметь компонент Soldier и Medic, а другой - компонент Soldier и Sniper.Все три класса компонентов, то есть Soldier, Medic и Sniper, будут полностью отделены друг от друга и все будут наследоваться от MonoBehaviour.

. Другие части кода будут взаимодействовать только с компонентом Soldier,В этом случае вы могли бы сделать: soldier.GetComponent<Soldier>().Respawn();.

Тогда, это будет обязанностью компонента "интерфейс" (то есть Soldier) использовать фактический компонент (то есть Medic илиSniper) для выполнения определенного действия.

Однако, поскольку Soldier ничего не знает о Medic, Sniper или какой-либо реализации, которая может быть добавлена ​​в будущем, SoldierКомпонент предоставляет фактические interface, которые должны быть реализованы Medic и Soldier.

Поскольку возможно реализовать несколько интерфейсов, используя это решение, можно будет использовать более одного "интерфейса"" составная часть.Например, игровой объект солдата может иметь следующие компоненты «интерфейса»: Soldier и Explodable и следующий «фактический» компонент: Medic, который будет реализовывать оба интерфейса Soldier.ISolder и Explodable.IExplodable.

Что вы думаете об этом решении? Спасибо!

РЕДАКТИРОВАТЬ:

Я закодировал то, что имел в виду, и это, кажется, работает хорошо.Я также создал скрипт редактора, позволяющий компоненту интерфейса ссылаться на «фактический» компонент, не имея открытых полей, но вместо свойств.Я выложу код, на случай, если кто-то захочет его:

WaterComponent.cs - Компонент «интерфейс» для водных объектов:

using System;
using UnityEngine;

public class WaterComponent : MonoBehaviour
{
    #region Interface

    public interface IWater
    {
        bool IsPointSubmerged(Vector3 worldPoint);

        Vector3 GetNormalAtPoint(Vector3 worldPoint);
    }

    #endregion Interface

    #region Properties

    public IWater Water
    {
        get
        {
            return waterImplementation;
        }
        set
        {
            Component asComponent = value as Component;

            if (null != value && null == waterComponent)
            {
                throw new ArgumentException($"The given {typeof(IWater).Name} is not a {typeof(Component).Name}.");
            }

            waterComponent = asComponent;
            waterImplementation = value;
        }
    }

    #endregion Properties

    #region Fields

    [SerializeField]
    private Component waterComponent;

    private IWater waterImplementation;

    #endregion Fields

    #region Public methods

    public bool IsPointSubmerged(Vector3 worldPoint)
    {
        return waterImplementation.IsPointSubmerged(worldPoint);
    }

    public Vector3 GetNormalAtPoint(Vector3 worldPoint)
    {
        return waterImplementation.GetNormalAtPoint(worldPoint);
    }

    #endregion Public methods

    #region Unity events

    private void Awake()
    {
        waterImplementation = waterComponent as IWater;
    }

    #endregion Unity events
}

RealWater.cs - «Фактический»компонент, реализующий компонент "interface":

using UnityEngine;

public class RealWater : MonoBehaviour, WaterComponent.IWater
{
    #region WaterComponent.IWater implementation

    public bool IsPointSubmerged(Vector3 worldPoint)
    {
        return SpecificIsPointSubmerged(worldPoint);
    }

    public Vector3 GetNormalAtPoint(Vector3 worldPoint)
    {
        return SpecificGetWaterAtPoint(worldPoint);
    }

    #endregion WaterComponent.IWater implementation

    #region Non-public methods

    private bool SpecificIsPointSubmerged(Vector3 worldPoint)
    {
        return true;
    }

    private Vector3 SpecificGetWaterAtPoint(Vector3 worldPoint)
    {
        return transform.up;
    }

    #endregion Non-public methods
}

WaterComponentEditor.cs - Пользовательский редактор, позволяющий не открывать открытые поля:

с использованием UnityEditor;

[CustomEditor(typeof(WaterComponent))]
[CanEditMultipleObjects]
public class WaterComponentEditor : Editor
{
    #region Serialized properties

    private SerializedProperty waterProperty;

    #endregion Serialized properties

    #region Overridden methods

    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        EditorGUI.BeginChangeCheck();

        EditorGUILayout.PropertyField(waterProperty);

        if (EditorGUI.EndChangeCheck())
        {
            ((WaterComponent) target).Water = waterProperty.exposedReferenceValue as WaterComponent.IWater;
        }

        serializedObject.ApplyModifiedProperties();
    }

    #endregion Overridden methods

    #region Unity events

    private void OnEnable()
    {
        waterProperty = serializedObject.FindProperty("waterComponent");
    }

    #endregion Unity events
}

Не стесняйтесь использовать повторно, если только вы не видите в этом изъяна, и в этом случае я бы очень хотел об этом знать !!

РЕДАКТИРОВАТЬ: Ну, проблема с этим пользовательским редактором в том, что вы можете иметь "интерфейс "ссылка на компонент any Component, даже если последний не реализует реальный интерфейс, предоставляемый компонентом" интерфейс ".Все еще возможно выполнить некоторые проверки во время выполнения в пользовательском скрипте редактора, но это не так чисто.Однако я думаю, что преимущества остаются достаточно хорошими по сравнению с этим вопросом.

1 Ответ

0 голосов
/ 22 сентября 2018

Ну ...

Семейство функций GetComponent теперь поддерживает интерфейсы в качестве универсального аргумента.

Замечания к выпуску Unity 5.0: https://unity3d.com/fr/unity/whats-new/unity-5.0

Что бы ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...