I am trying to make an animation system that affects different types so I thought I would use generics.
I have an animation controller that gets all animation objects into a collection (or at least that was the plan) then then run the animation on all the animation objects.
The problem is I can’t do it this way in C# as it does not allow polymorphic collections for different types on generics.
Here is the code structure involved for different animations:
public abstract class Animation<T> {
protected T TargetValue {protected set; get;} // could be int/float/vector2 etc etc
protected T StartValue {protected set; get;}
public T CurrentValue {protected set; get;}
public Action OnUpdate;
public virtual T Animate(float deltaTime) => OnUpdate?.Invoke();
}
public class Fade : Animation<float> { // lerp between two floats
public override float Animate(float deltaTime) {
CurrentValue = float.Lerp(StartValue,TargetValue,deltaTime);
base.Animate(deltaTime);
}
}
public class Translation : Animation<Vector2> { // move to target Vector2
public override Vector2 Animate(float deltaTime) {
CurrentValue = Vector2.Lerp(StartValue,TargetValue,deltaTime);
base.Animate(deltaTime);
}
}
Now my animation controller class is supposed to get all animations an run animations on them:
public class AnimationController {
private List<Animation<???>> _animations = new List<Animation<???>>();
void Init() => _animations = GetAllComponents<Animation>(); // get all animations as collection
public void Animation(float deltaTime)
for(int i = 0; i < _animations.Count; i++)
_animations[i].Animate(deltaTime);
}
}
However, this is not an option for me as I cannot have collections of generics with different types for the generic T
.
So what would be a better design for this system because I do not know a good solution ???
2
Approach One
Each concrete implementation of Animation
appears to be used by other classes in different ways:
- At the minimum,
Animation
items need to be stored in a collection. - There is a need to call the
Animation.Animate(float)
method on each item in the collection. This caller (which processes the collection) does not need to be aware of the type of state parameters (time-varying variables). - Each
Animation
item may also be used by some other classes (which are not illustrated in the code example above). Each of these other classes make use of the state parameters (time-varying variables) of some specific concreteAnimation
items; therefore, they must be aware of the types.
Suggestion (C# specific. The suggestion may need to be tailored or modified for other languages.)
Firstly, define an Animation
interface or abstract class that is non-generic.
public interface Animation
{
void Animate(float deltaTime);
}
Then, define a generic Animation
interface where the types of time-varying variables are specified.
public interface Animation<T> : Animation
{
T CurrentValue { get; }
}
Finally, have your abstract classes implement Animation<T>
. By doing so, they also indirectly implement Animation
(the non-generic interface), which allows the AnimationController
to call its void Animate(float)
method even if AnimationController
doesn’t know the types of each item’s time-varying variables.
This is a very common usage pattern in C# generics.
Summary of Approach One
In C#, it is widely recognized that generic classes may require some kind of uniform handling in which the generic parameters (the <T>
) don’t matter and shouldn’t matter. To do so, the widely-accepted practice is to create a parent interface or parent abstract class that is non-generic, that provides limited access to the concrete classes, without the knowledge of the <T>
.
Approach Two
Provide an abstraction for the concrete implementations of time-varying variables (float
, Vector2
, etc). Let’s call it Lerpable
.
The Lerpable
interface will have a method called Lerp
.
1