Improve AnimationComponent and AnimationWindow (#900)

Add new logic and buttons to AnimationWindow for playing backwards and for the new loop types.
Add new methods to control looping: AnimationComponent::IsPingPong, AnimationComponent::IsPlayingOnce, AnimationComponent::SetPingPong, and AnimationComponent::SetPlayOnce.
Add checks in Scene::RunAnimationUpdateSystem for the new loops when the animation reaches the end.
This commit is contained in:
Romildo Franco
2024-07-23 06:46:11 -03:00
committed by GitHub
parent 510fd5dce7
commit ed6e930c96
8 changed files with 230 additions and 52 deletions
@@ -946,8 +946,8 @@ Describes an orientation in 3D space.
- Play()
- Stop()
- Pause()
- SetLooped(bool value)
- IsLooped() : bool result
- SetLooped(bool value) -- Sets the animation to repeat continuously.
- IsLooped() : bool -- Returns true if the animation is set to repeat continuously.
- IsPlaying() : bool result
- SetTimer(float value)
- GetTimer() : float result
@@ -957,6 +957,12 @@ Describes an orientation in 3D space.
- SetStart(float value)
- GetEnd() : float result
- SetEnd(float value)
- SetPingPong(bool value) -- Sets the animation to play forward and then backwards repeatedly.
- IsPingPong() : bool -- Returns true if the animation is set to play forward and then backwards repeatedly.
- SetPlayOnce() -- Sets the animation to play once.
- IsPlayingOnce() : bool -- Returns true if the animation is set to play once.
#### MaterialComponent
- _flags : int
+133 -42
View File
@@ -12,7 +12,6 @@ void AnimationWindow::Create(EditorComponent* _editor)
closeButton.SetTooltip("Delete AnimationComponent");
OnClose([=](wi::gui::EventArgs args) {
wi::Archive& archive = editor->AdvanceHistory();
archive << EditorComponent::HISTORYOP_COMPONENT_DATA;
editor->RecordEntity(archive, entity);
@@ -29,6 +28,8 @@ void AnimationWindow::Create(EditorComponent* _editor)
float hei = 18;
float wid = 200;
float step = hei + 4;
float padding = 4;
float lastDirection = 1;
infoLabel.Create("");
infoLabel.SetSize(XMFLOAT2(100, 50));
@@ -71,24 +72,72 @@ void AnimationWindow::Create(EditorComponent* _editor)
modeComboBox.SetTooltip("Choose how animation data is interpreted between keyframes.\nNote that Cubic spline sampling requires spline animation data, otherwise, it will fall back to Linear!\nCurrently spline animation data creation is not supported, but it can be imported from GLTF/VRM models.");
AddWidget(&modeComboBox);
loopedCheckBox.Create("Looped: ");
loopedCheckBox.SetTooltip("Toggle animation looping behaviour.");
loopedCheckBox.SetSize(XMFLOAT2(hei, hei));
loopedCheckBox.SetPos(XMFLOAT2(x, y += step));
loopedCheckBox.OnClick([&](wi::gui::EventArgs args) {
loopTypeButton.Create("LoopType");
loopTypeButton.SetText(ICON_LOOP);
loopTypeButton.SetDescription("Loop type: ");
loopTypeButton.SetSize(XMFLOAT2(hei, hei));
loopTypeButton.SetPos(XMFLOAT2(x, y += step));
loopTypeButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->SetLooped(args.bValue);
if (animation->IsLooped())
{
animation->SetPingPong();
}
else if (animation->IsPingPong())
{
animation->SetPlayOnce();
}
else
{
animation->SetLooped();
}
}
});
loopedCheckBox.SetCheckText(ICON_LOOP);
AddWidget(&loopedCheckBox);
AddWidget(&loopTypeButton);
playButton.Create(ICON_PLAY);
playButton.SetSize(XMFLOAT2(100, hei));
playButton.SetPos(XMFLOAT2(loopedCheckBox.GetPos().x + loopedCheckBox.GetSize().x + 5, y));
playButton.OnClick([&](wi::gui::EventArgs args) {
backwardsButton.Create("Backwards");
backwardsButton.SetText(ICON_PLAY);
backwardsButton.font.params.enableFlipHorizontally();
backwardsButton.SetTooltip("Play the animation backwards from the current position.");
backwardsButton.SetSize(XMFLOAT2(30, hei));
backwardsButton.SetPos(XMFLOAT2(loopTypeButton.GetPos().x + loopTypeButton.GetSize().x + padding, y));
backwardsButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
if (animation->timer > animation->start) // Only play backwards if it is not in the start position.
{
animation->speed = -speedSlider.GetValue();
animation->Play();
}
}
});
AddWidget(&backwardsButton);
backwardsFromEndButton.Create("BackwardsFromEnd");
backwardsFromEndButton.SetText(ICON_BACKWARD);
backwardsFromEndButton.SetTooltip("Play the animation backwards starting from the end.");
backwardsFromEndButton.SetSize(backwardsButton.GetSize());
backwardsFromEndButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x + padding, y));
backwardsFromEndButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->timer = animation->end;
animation->speed = -speedSlider.GetValue();
animation->Play();
}
});
AddWidget(&backwardsFromEndButton);
stopButton.Create("Stop");
stopButton.SetText(ICON_STOP);
stopButton.SetTooltip("Stop.");
stopButton.SetSize(backwardsButton.GetSize());
stopButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 2 + padding * 2, y));
stopButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
@@ -98,25 +147,47 @@ void AnimationWindow::Create(EditorComponent* _editor)
}
else
{
animation->Stop();
}
}
});
AddWidget(&stopButton);
playFromStartButton.Create("PlayFromStart");
playFromStartButton.SetText(ICON_FORWARD);
playFromStartButton.SetTooltip("Play the animation from the beginning.");
playFromStartButton.SetSize(backwardsButton.GetSize());
playFromStartButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 3 + padding * 3, y));
playFromStartButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->timer = animation->start;
animation->speed = speedSlider.GetValue();
animation->Play();
}
});
AddWidget(&playFromStartButton);
playButton.Create("Play");
playButton.SetText(ICON_PLAY);
playButton.SetTooltip("Play the animation from the current position.");
playButton.SetSize(backwardsButton.GetSize());
playButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 4 + padding * 4, y));
playButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
if (animation->timer < animation->end) // Only play forward if it is not in the end position.
{
animation->speed = speedSlider.GetValue();
animation->Play();
}
}
});
AddWidget(&playButton);
stopButton.Create(ICON_STOP);
stopButton.SetTooltip("Stop");
stopButton.SetSize(XMFLOAT2(70, hei));
stopButton.SetPos(XMFLOAT2(playButton.GetPos().x + playButton.GetSize().x + 5, y));
stopButton.OnClick([&](wi::gui::EventArgs args) {
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->Stop();
}
});
AddWidget(&stopButton);
timerSlider.Create(0, 1, 0, 100000, "Timer: ");
timerSlider.SetSize(XMFLOAT2(wid, hei));
timerSlider.SetPos(XMFLOAT2(x, y += step));
@@ -152,7 +223,9 @@ void AnimationWindow::Create(EditorComponent* _editor)
AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity);
if (animation != nullptr)
{
animation->speed = args.fValue;
// Adjusts the new animation speed based on the previous direction of animation->speed.
lastDirection = std::signbit(animation->speed) ? -1 : 1;
animation->speed = args.fValue * lastDirection;
}
});
speedSlider.SetEnabled(false);
@@ -969,7 +1042,6 @@ void AnimationWindow::Create(EditorComponent* _editor)
rootBoneComboBox.SetTooltip("Choose the root bone to evaluate root motion from.");
AddWidget(&rootBoneComboBox);
SetMinimized(true);
SetVisible(false);
@@ -1049,13 +1121,29 @@ void AnimationWindow::Update()
if (animation.IsPlaying())
{
playButton.SetText(ICON_PAUSE);
playButton.SetTooltip("Pause");
stopButton.SetText(ICON_PAUSE);
stopButton.SetTooltip("Pause");
}
else
{
playButton.SetText(ICON_PLAY);
playButton.SetTooltip("Play");
stopButton.SetText(ICON_STOP);
stopButton.SetTooltip("Stop");
}
if (animation.IsLooped())
{
loopTypeButton.SetText(ICON_LOOP);
loopTypeButton.SetTooltip("Looping is enabled. The animation will repeat continuously.");
}
else if (animation.IsPingPong())
{
loopTypeButton.SetText(ICON_PINGPONG);
loopTypeButton.SetTooltip("Ping-Pong is enabled. The animation will play forward and then backwards repeatedly.");
}
else
{
loopTypeButton.SetText("-");
loopTypeButton.SetTooltip("No loop. The animation will play once.");
}
if(!animation.samplers.empty())
@@ -1067,15 +1155,12 @@ void AnimationWindow::Update()
modeComboBox.SetSelectedByUserdataWithoutCallback(AnimationComponent::AnimationSampler().mode);
}
loopedCheckBox.SetCheck(animation.IsLooped());
rootMotionCheckBox.SetCheck(animation.IsRootMotion());
rootBoneComboBox.SetSelectedByUserdataWithoutCallback(animation.rootMotionBone);
timerSlider.SetRange(animation.start, animation.end);
timerSlider.SetValue(animation.timer);
amountSlider.SetValue(animation.amount);
speedSlider.SetValue(animation.speed);
startInput.SetValue(animation.start);
endInput.SetValue(animation.end);
}
@@ -1281,14 +1366,20 @@ void AnimationWindow::ResizeLayout()
add_fullwidth(infoLabel);
add(modeComboBox);
loopedCheckBox.SetPos(XMFLOAT2(margin_left, y));
const float l = loopedCheckBox.GetPos().x + loopedCheckBox.GetSize().x + padding;
const float r = width - margin_right - padding;
loopTypeButton.SetPos(XMFLOAT2(margin_left, y));
const float l = loopTypeButton.GetPos().x + loopTypeButton.GetSize().x + padding;
const float r = width - margin_right - padding * 4;
const float diff = r - l;
playButton.SetSize(XMFLOAT2(diff * 0.5f, playButton.GetSize().y));
stopButton.SetSize(playButton.GetSize());
playButton.SetPos(XMFLOAT2(loopedCheckBox.GetPos().x + loopedCheckBox.GetSize().x + padding, y));
stopButton.SetPos(XMFLOAT2(playButton.GetPos().x + playButton.GetSize().x + padding, y));
backwardsButton.SetSize(XMFLOAT2(diff/5, backwardsButton.GetSize().y));
backwardsFromEndButton.SetSize(backwardsButton.GetSize());
stopButton.SetSize(backwardsButton.GetSize());
playFromStartButton.SetSize(backwardsButton.GetSize());
playButton.SetSize(backwardsButton.GetSize());
backwardsButton.SetPos(XMFLOAT2(loopTypeButton.GetPos().x + loopTypeButton.GetSize().x + padding, y));
backwardsFromEndButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x + padding, y));
stopButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 2 + padding * 2, y));
playFromStartButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 3 + padding * 3, y));
playButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 4 + padding * 4, y));
y += stopButton.GetSize().y;
y += padding;
+4 -2
View File
@@ -12,8 +12,11 @@ public:
wi::gui::Label infoLabel;
wi::gui::ComboBox modeComboBox;
wi::gui::CheckBox loopedCheckBox;
wi::gui::Button loopTypeButton;
wi::gui::Button playButton;
wi::gui::Button playFromStartButton;
wi::gui::Button backwardsButton;
wi::gui::Button backwardsFromEndButton;
wi::gui::Button stopButton;
wi::gui::Slider timerSlider;
wi::gui::Slider amountSlider;
@@ -35,4 +38,3 @@ public:
void ResizeLayout() override;
};
+3
View File
@@ -69,7 +69,10 @@
#define ICON_SQUARE ICON_FA_SQUARE_FULL
#define ICON_CUBE ICON_FA_CUBE
#define ICON_LOOP ICON_FA_REPEAT
#define ICON_PINGPONG ICON_FA_ARROWS_LEFT_RIGHT_TO_LINE
#define ICON_PLAY ICON_FA_PLAY
#define ICON_BACKWARD ICON_FA_BACKWARD
#define ICON_FORWARD ICON_FA_FORWARD
#define ICON_PAUSE ICON_FA_PAUSE
#define ICON_STOP ICON_FA_STOP
#define ICON_PEN ICON_FA_PEN
+38 -4
View File
@@ -2681,12 +2681,46 @@ namespace wi::scene
}
if (animation.IsLooped() && animation.timer > animation.end)
if (animation.timer > animation.end && animation.speed > 0)
{
animation.timer = animation.start;
for (auto& channel : animation.channels)
if (animation.IsLooped())
{
channel.next_event = 0;
animation.timer = animation.start;
for (auto& channel : animation.channels)
{
channel.next_event = 0;
}
}
else if (animation.IsPingPong())
{
animation.timer = animation.end;
animation.speed = -animation.speed;
}
else
{
animation.timer = animation.end;
animation.Pause();
}
}
else if (animation.timer < animation.start && animation.speed < 0)
{
if (animation.IsLooped())
{
animation.timer = animation.end;
for (auto& channel : animation.channels)
{
channel.next_event = 0;
}
}
else if (animation.IsPingPong())
{
animation.timer = animation.start;
animation.speed = -animation.speed;
}
else
{
animation.timer = animation.start;
animation.Pause();
}
}
+33
View File
@@ -4010,6 +4010,10 @@ Luna<AnimationComponent_BindLua>::FunctionType AnimationComponent_BindLua::metho
lunamethod(AnimationComponent_BindLua, SetStart),
lunamethod(AnimationComponent_BindLua, GetEnd),
lunamethod(AnimationComponent_BindLua, SetEnd),
lunamethod(AnimationComponent_BindLua, SetPingPong),
lunamethod(AnimationComponent_BindLua, IsPingPong),
lunamethod(AnimationComponent_BindLua, SetPlayOnce),
lunamethod(AnimationComponent_BindLua, IsPlayingOnce),
lunamethod(AnimationComponent_BindLua, IsRootMotion),
lunamethod(AnimationComponent_BindLua, RootMotionOn),
lunamethod(AnimationComponent_BindLua, RootMotionOff),
@@ -4143,6 +4147,35 @@ int AnimationComponent_BindLua::SetEnd(lua_State* L)
}
return 0;
}
int AnimationComponent_BindLua::SetPingPong(lua_State* L)
{
int argc = wi::lua::SGetArgCount(L);
if (argc > 0)
{
bool value = wi::lua::SGetBool(L, 1);
component->SetPingPong(value);
}
else
{
wi::lua::SError(L, "SetPingPong(bool value) not enough arguments!");
}
return 0;
}
int AnimationComponent_BindLua::IsPingPong(lua_State* L)
{
wi::lua::SSetBool(L, component->IsPingPong());
return 1;
}
int AnimationComponent_BindLua::SetPlayOnce(lua_State* L)
{
component->SetPlayOnce();
return 0;
}
int AnimationComponent_BindLua::IsPlayingOnce(lua_State* L)
{
wi::lua::SSetBool(L, component->IsPlayingOnce());
return 1;
}
int AnimationComponent_BindLua::IsRootMotion(lua_State* L)
{
+4
View File
@@ -368,6 +368,10 @@ namespace wi::lua::scene
int SetStart(lua_State* L);
int GetEnd(lua_State* L);
int SetEnd(lua_State* L);
int SetPingPong(lua_State* L);
int IsPingPong(lua_State* L);
int SetPlayOnce(lua_State* L);
int IsPlayingOnce(lua_State* L);
// For Rootmotion
int IsRootMotion(lua_State* L);
+7 -2
View File
@@ -1342,7 +1342,8 @@ namespace wi::scene
EMPTY = 0,
PLAYING = 1 << 0,
LOOPED = 1 << 1,
ROOT_MOTION = 1 << 2
ROOT_MOTION = 1 << 2,
PING_PONG = 1 << 3,
};
uint32_t _flags = LOOPED;
float start = 0;
@@ -1481,6 +1482,8 @@ namespace wi::scene
inline bool IsPlaying() const { return _flags & PLAYING; }
inline bool IsLooped() const { return _flags & LOOPED; }
inline bool IsPingPong() const { return _flags & PING_PONG; }
inline bool IsPlayingOnce() const { return (_flags & (LOOPED | PING_PONG)) == 0; }
inline float GetLength() const { return end - start; }
inline bool IsEnded() const { return timer >= end; }
inline bool IsRootMotion() const { return _flags & ROOT_MOTION; }
@@ -1488,7 +1491,9 @@ namespace wi::scene
inline void Play() { _flags |= PLAYING; }
inline void Pause() { _flags &= ~PLAYING; }
inline void Stop() { Pause(); timer = 0.0f; last_update_time = timer; }
inline void SetLooped(bool value = true) { if (value) { _flags |= LOOPED; } else { _flags &= ~LOOPED; } }
inline void SetLooped(bool value = true) { if (value) { _flags |= LOOPED; _flags &= ~PING_PONG; } else { _flags &= ~LOOPED; } }
inline void SetPingPong(bool value = true) { if (value) { _flags |= PING_PONG; _flags &= ~LOOPED; } else { _flags &= ~PING_PONG; } }
inline void SetPlayOnce() { _flags &= ~(LOOPED | PING_PONG); }
inline void RootMotionOn() { _flags |= ROOT_MOTION; }
inline void RootMotionOff() { _flags &= ~ROOT_MOTION; }