Tutorial
April 8, 2025 ยท View on GitHub
Tutorial
First, create an interface that will determine which "polymorphic methods" your structs will have. You must remember to put the [PolymorphicStructInterface] attribute on it:
[PolymorphicStructInterface]
public interface ITestPolymorphicStruct
{
public bool DoSomething(ref TestData data);
}
In this example, TestData would be any struct containing data that the DoSomething method might want to operate on. Now, create structs that implement that interface and have the [PolymorphicStruct] attribute on them.
[PolymorphicStruct]
public struct TranslateForward : ITestPolymorphicStruct
{
public float TranslationSpeed;
public bool DoSomething(ref TestData data)
{
data.Position += math.forward() * TranslationSpeed * data.DeltaTime;
return true;
}
}
[PolymorphicStruct]
public struct Rotate : ITestPolymorphicStruct
{
public float3 RotationSpeed;
public bool DoSomething(ref TestData data)
{
data.Rotation = math.mul(RotationSpeed * data.DeltaTime, data.Rotation);
return true;
}
}
[PolymorphicStruct]
public struct ScaleUp : ITestPolymorphicStruct
{
public Entity ScaleSpeedEntity;
public bool DoSomething(ref TestData data)
{
if(data.ScaleSpeedLookup.TryGetComponent(ScaleSpeedEntity, out ScaleSpeed scaleSpeed))
{
data.Scale += scaleSpeed.Value * data.DeltaTime;
return true;
}
return false;
}
}
Now that we've written our 3 "child" structs that all do different things and all hold different data, a PolyTestPolymorphicStruct will automatically be generated by C# source generators. The generated struct takes the name of the polymorphic interface (ITestPolymorphicStruct), removes the "I" if present, and adds the "Poly" prefix. This PolyTestPolymorphicStruct will represent a union of the TranslateForward, Rotate, and ScaleUp structs, meaning it will contain a union of all the data that all of its child structs may contain.
With this new PolyTestPolymorphicStruct union struct, we can do things like this:
// Notice that "child" sctucts are implicitly casted to their "parent" type
PolyTestPolymorphicStruct translate = new TranslateForward { TranslationSpeed = 5f };
PolyTestPolymorphicStruct rotate = new Rotate { RotationSpeed = new float3(1f,1f,1f) };
PolyTestPolymorphicStruct scale = new ScaleUp { ScaleSpeedEntity = targetEntity };
// Add all structs to the same list
NativeList<PolyTestPolymorphicStruct> myList = new NativeList<PolyTestPolymorphicStruct>(Allocator.Temp);
myList.Add(translate);
myList.Add(rotate);
myList.Add(scale);
// Build our TestData
TestData myData = new TestData { // Here we'd fill our test data };
// Call DoSomething() on all elements of the list
for (int i = 0; i < myList.Length; i++)
{
bool success = myList[i].DoSomething(ref testData);
}
With the ability to cast "child" structs to their "parent" structs, we can achieve polymorphic behaviours. When we iterate on myList and call DoSomething() on its elements, some elements will do a translation, some will do a rotation, and some will do scaling.
Note that "parent" structs can be casted to their "child" structs:
PolyTestPolymorphicStruct parent = new TranslateForward { TranslationSpeed = 5f };
TranslateForward child = parent;
However, if the "parent" struct was constructed with a different "child" type than the one we're casting to, the data the child will contain will be unpredictable. You can check which child type a parent struct was constructed with by checking the CurrentTypeId field of the generated parent struct.
Once you have a polymorphic struct, you can create a partial declaration of it in order to turn it into a component, make it implement interfaces, add attributes, etc...
// Turn our generated polymorphic struct into a dynamic buffer element, with internal capacity of zero
[InternalBufferCapacity(0)]
public partial PolyTestPolymorphicStruct : IBufferElementData
{ }
Note that this package provides 3 different ways of generating or using polymorphic structs. This will be elaborated on in the Polymorphic struct types section