Необходимость:
Чтобы иметь возможность взаимодействовать с 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
, даже если последний не реализует реальный интерфейс, предоставляемый компонентом" интерфейс ".Все еще возможно выполнить некоторые проверки во время выполнения в пользовательском скрипте редактора, но это не так чисто.Однако я думаю, что преимущества остаются достаточно хорошими по сравнению с этим вопросом.