C# 统一-使用组件作为接口
需求: 可以通过C# 统一-使用组件作为接口,c#,unity3d,inheritance,interface,C#,Unity3d,Inheritance,Interface,需求: 可以通过界面与游戏对象实例上附加的不同类型的组件实例进行交互 例如,如果我有一个与士兵的游戏,并且假设医疗兵和狙击手都是士兵,我希望能够将士兵组件附加到士兵游戏对象,而不管该士兵实际上是狙击手还是医疗兵。然后,我可以做如下事情:soldier.GetComponent().Respawn()最终将调用Medic.Respawn()或Sniper.Respawn(),具体取决于士兵的实际类型 可能但不干净的解决方案1: 第一种简单的方法是让狙击手和医疗组件实现一个士兵接口。然而,这导致了几
界面
与游戏对象
实例上附加的不同类型的组件
实例进行交互
例如,如果我有一个与士兵的游戏,并且假设医疗兵和狙击手都是士兵,我希望能够将士兵
组件附加到士兵游戏对象
,而不管该士兵实际上是狙击手还是医疗兵
。然后,我可以做如下事情:soldier.GetComponent().Respawn()
最终将调用Medic.Respawn()
或Sniper.Respawn()
,具体取决于士兵的实际类型
可能但不干净的解决方案1:
第一种简单的方法是让狙击手
和医疗
组件实现一个士兵
接口
。然而,这导致了几个问题
例如,如果您想检查游戏对象
是否有一个实现士兵
的组件,您不能,因为士兵
只是一个接口,而不是实际的Unity组件
。因此,例如,对具有Medic
组件的GameObject调用GetComponent()
将不会返回该Medic
组件,即使Medic
确实实现了Soldier
(实际上,您可以通过迭代所有组件并使用is
操作符来检查这一点,但这将是肮脏而缓慢的)
可能但不干净的解决方案2:
第二种方法是创建一个基本的组件
类士兵
,医疗兵
和狙击手
类将从中继承
但这也带来了一些问题
首先,Unity事件(Awake()
,Start()
,等等)只在层次结构的叶类上被调用,迫使您手动调用父类上的相同函数。任何尝试过这样做的人都知道,很容易忘记调用某些东西,例如,这会导致不正确初始化对象
其次,继承的常见问题也在这里。例如,如果我希望我的Medic
和Sniper
组件不仅是Soldier
,而且是exploble
或VehicleDriver
或其他什么,我不能,因为C不支持多重继承
我正在考虑的方法:
我考虑了一种设计代码的方法,以解决上面列出的问题
这个想法是让一个组件
类充当接口,并让该接口组件与acutal组件在同一个游戏对象
上共存。换句话说,让两个游戏对象。其中一个既有士兵
又有医疗组件,另一个既有士兵又有狙击手组件。所有三个组件类,即士兵
、医疗兵
和狙击手
将完全独立,并且都继承自单一行为
代码的其他部分仅与士兵
组件交互。在本例中,您可以执行以下操作:soldier.GetComponent().Respawn()代码>
然后,“接口”组件(即士兵
)有责任使用实际组件(即医疗兵
或狙击手
)执行特定动作
但是,由于Soldier
不了解Medic
、Sniper
或将来可能添加的任何实现,因此Soldier
组件公开了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-实现“接口”组件的“实际”组件:
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
}
WaterComponentedEditor.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
}
请随意重复使用,除非你看到它有缺陷,在这种情况下,我真的很想知道
编辑:这个自定义编辑器的问题是,您可以让“接口”组件引用任何组件
,即使后者没有实现“接口”组件公开的真实接口。仍然可以在自定义编辑器脚本中执行一些运行时检查,但这并不干净。然而,我认为与这个问题相比,它的优点仍然足够好。好吧
GetComponent函数族现在支持将接口作为泛型参数
Unity 5.0发行说明:
不管怎样