本文简述了一种 PlayMode 的实现方式
PlayMode(播放模式)应该是编程开发中常见的概念,一般来讲有以下几种播放模式:
enum class EPlayMode : uint8
{
Once, // 单次播放
Loop, // 循环播放
PingPong, // 往返播放
};
总结来讲, PlayMode 影响的是 时间因子(TimeFactor) 的计算,我们可以抽象出以下结构来进行实现说明:
class CPlayMode
{
public:
CPlayMode(EPlayMode Mode_, float Duration_) :
Mode(Mode_),
Duration(Duration_),
Elapsed(0)
{
}
void Update(float DeltaTime);
float GetFactor() const;
protected:
EPlayMode Mode;
float Duration;
float Elapsed;
};
开始实现之前,我们先定义几个辅助函数:
float FloatDivide(float Dividend, float Divisor)
{
if (Divisor != 0)
{
return Dividend / Divisor;
}
return 0;
}
float FloatModulo(float Dividend, float Divisor)
{
if (Divisor != 0)
{
return Dividend - ((int)(Dividend / Divisor)) * Divisor;
}
return 0;
}
作用就是处理浮点数的除法和取模(用以解决除零等问题).
之后便是正式的实现了:
void CPlayMode::Update(float DeltaTime)
{
switch (Mode)
{
case EPlayMode::Once:
{
Elapsed += DeltaTime;
if (Elapsed > Duration)
{
Elapsed = Duration;
}
}
break;
case EPlayMode::Loop:
{
Elapsed += DeltaTime;
if (Elapsed > Duration)
{
Elapsed = FloatModulo(Elapsed, Duration);
}
}
break;
case EPlayMode::PingPong:
{
Elapsed += DeltaTime;
float LoopDuration = 2.0f * Duration;
if (Elapsed > LoopDuration)
{
Elapsed = FloatModulo(Elapsed, LoopDuration);
}
}
break;
}
}
Update 函数主要用于更新 Elapsed:
- 对于 Once 模式,直接使用 Duration 截断 Elapsed
- 对于 Loop 模式,简单处理的话可以直接 Elapsed - Duration(当 Elapsed 超过 Duration 时),但是使用 FloatModulo 会更健壮一些(可以处理 DeltaTime 过大的情况)
- 对于 PingPong 模式,处理上会取巧一些,直接将往返一次算作一次循环,这样处理上就和 Loop 模式类似了(原本的 Duration 需要加倍)
GetFactor 就是按照 Elapsed 获取当前的 TimeFactor :
float CPlayMode::GetFactor() const
{
switch (Mode)
{
case EPlayMode::Once:
case EPlayMode::Loop:
{
return FloatDivide(Elapsed, Duration);
}
case EPlayMode::PingPong:
{
if (Elapsed <= Duration)
{
return FloatDivide(Elapsed, Duration);
}
else
{
return 1.0f - FloatDivide(Elapsed - Duration, Duration);
}
}
}
return 0;
}
其中 PingPong 模式的处理会有些特殊,但是了解了上面 Elapsed 的处理方式(PingPong 模式下)就容易理解了,有兴趣的朋友可以细看一下.