我有一个称为IComponent的基本核心接口。
public interface IComponent
{
void Initialize();
void Update();
void Draw(Color tint);
BoundingBox BoundingBox { get; }
bool Initialized { get; }
}
任何可绘制的类(按钮,文本,纹理等)都会使用它。
我想开发一套可以应用于任何IComponent的效果,例如:
Shake(IComponent component, TimeSpan duration)
->使组件振动给定的持续时间。它的工作方式是最初存储传递的组件的中心位置,并在每次更新时偏移它,直到持续时间结束。
Translate(IComponent component, Vector2 destination, TimeSpan timeToReach)
->使组件在一定时间后移至给定的目的地。它通过在每次更新时逐渐偏移组件来工作。
您可以想象更多...
因此,假设我希望某个类(Texture:IComponent)摇动,同时还要移动到某个点。我想到可能像这样使用装饰器模式:
创建纹理=>摇动包装(持续时间为5秒)=>摇动包装(持续时间为10秒)
但这有一些问题。
首先,Shake包装器可以单独使用静态静止纹理即可正常工作,但与Translate结合使用时将失败,因为它将使纹理不断返回其原始位置并且无法正确平移。
其次,虽然翻译需要10秒钟才能完成,但振动仅需要5秒钟,所以此后我不知道如何自动从链中删除Shake包装器,并且在完成后最终也要删除Translate,因此原始纹理的后面。
第三,装饰器模式隐藏了包装对象的任何特定功能,因此在用Shake包装Texture之后,我将无法调用Texture的setPixelColor(),除非在包装它之前也创建了另一个对Texture的直接引用。
欢迎提供任何有关如何优雅地应对此类挑战的建议。谢谢。
注意:实际上,我可能会将这些效果应用到所有创建的IComponent对象的2%。
最佳答案
也许是以下方法的另一种方法。
在您的示例中,震动和平移都是动画效果。所有动画都需要某种形式的持续时间,并应用于组件。这可能导致以下首先采取的措施:
public interface IAnimationEffect
{
IComponent targetComponent;
int Duration { get; set; }
}
动画的持续时间似乎是一种不错的初始方法,但是,当您需要组合动画时,每个动画都需要在与其他动画相同的时间内操纵其封装的IComponent的BoundingRect。为了能够像这样堆叠动画,需要指示动画效果的draw方法来绘制动画的特定帧。
IAnimationEffect接口的改进版本为:
public interface IAnimationEffect
{
IComponent targetComponent;
int StartFrame { get; set; }
int EndFrame { get; set; }
void CalculateFrame(int frame);
}
哪个类负责绘制您的IComponents(现在将其称为
DrawingEngine
),现在也负责保存所有适用动画效果的内部列表。它还需要在每秒帧数中具有某种时间轴和渲染逻辑,以便可以执行特定动画帧的计算。public class ShakeAnimationEffect : IAnimationEffect
{
public IComponent TargetComponent { get; set; }
public int StartFrame { get; set; }
public int EndFrame { get; set; }
// Some shake specific properties can be added, to control the type of vibration etc
// (could be a rotating vibration an updown shake), but these really should have dedicated classes of their own.
public void CalculateFrame(int frame)
{
// your maths manipulations for calculating the bounds of the IComponent go here
}
}
public class TranslateAnimation : IAnimationEffect
{
public IComponent targetComponent { get; set; }
public int StartFrame { get; set; }
public int EndFrame { get; set; }
public int TranslateX { get; set; }
public int TranslateY { get; set; }
public void CalculateFrame(int frame)
{
// your maths manipulations for calculating the bounds of the IComponent go here
}
}
如上所述,您的AnimationEffects类对绘制IComponents不承担任何责任,而这仍然是DrawingEngine的责任。
您需要在DrawingLoop之前直接引入一个循环,以运行所有动画效果,而要做的就是更新恰好具有与之关联的动画效果的对象的边界。然后,像往常一样继续绘制对象。
假设您有以下内容:
// suppose your drawing engine has the following:
List<IComponent> components = new List<IComponent>();
//now just add the following
List<IAnimationEffect> animationEffects = new List<IAnimationEffect>();
// create some animation effects and register them to your list
animationEffects.Add(new ShakeAnimationEffect
{
TargetComponent = Lorry,
StartFrame = 0,
EndFrame = 150, //At 30 frames per second, this would be a 5sec animation
}
);
//sample pseudecode
RunAnimationCalculations();
RunDrawingLoop();
RunAnimationCalculations要做的全部工作就是遍历您的animationEffects集合,并在当前帧中运行Calculate传递,以便在随后绘制该帧之前更新IComponent的边界。
祝好运!