0
\$\begingroup\$

I'm currently struggling to find a good approach for an easing class for my game.

The class holds some easing effects, like elastic-in-out, and should be called by menu buttons, players and enemies to polish my animations a bit. (think about a cool bouncing button that fades in)..

This is my very basic easing class so far:

class Easing { protected: double currentTime; double startTime; double endTime; public: virtual double ease(double t) { return 0.0f; } }; class Easing_ElasticInOut : Easing { public: virtual double ease(double t) override { t--; return sqrt(1 - t*t); }; }; 

In my menu I've got a bunch of buttons and I'd like to animate them to ease in when they appear.

void EditorMenu::render() { if (is_open()) { // Easing_ElasticInOut ??? // Draw a button with circle_radius depending on the easing } } 

This render() method gets called all the time.

How would you design these effects? I'm not sure about how to only call them once and let them finish, especially when there are more buttons or things at once, that need to be animated...

\$\endgroup\$
2
  • 1
    \$\begingroup\$ You are definitely going to need some state about things animating. At minimum you'll need an enum value specifying the type of easing they are doing, and also how far along they are in the animation process. You may need more info like source and destination locations or similar, but that depends on how you are setting it up a bit. \$\endgroup\$ Commented Jan 10, 2017 at 23:23
  • \$\begingroup\$ This question appears to be in regards to the programming of the design of features. Please address the usage guidelines for game-design; it is not for programming the design. \$\endgroup\$ Commented Jun 5, 2017 at 17:08

1 Answer 1

2
\$\begingroup\$

I've myself implemented this using a helper class called InterpolationHelper and a class that handles the actual easing, called InterpolationCurve. In addition, there's an enum InterpolationCurveType, which decides which type of easing is used. You could replace the enum with overloads of the InterpolationCurve, but I felt that going with an enum was the easiest way to implement this, while still supporting inheritance if needed.

class InterpolationHelper { public: InterpolationHelper() {} InterpolationHelper(float duration, const InterpolationCurve& curve) : m_Duration(duration), m_Curve(curve) { m_StartTime = GetCurrentGlobalTime(); m_FinishTime = m_StartTime + duration; // Make sure that the units match up here. } bool IsFinished() const { return GetCurrentGlobalTime() >= m_FinishTime; } bool IsEmpty() const { return m_Duration == 0.0f && m_StartTime == 0.0f && m_FinishTime == 0.0f; } float GetPercentage() const { const float progress = (GetCurrentGlobalTime() - m_StartTime) / (m_Duration); return m_Curve.GetCurveValue(std::clamp(progress, 0.0f, 1.0f)); } protected: private: float m_Duration = 0.0f; float m_StartTime = 0.0f; float m_FinishTime = 0.0f; InterpolationCurve m_Curve; } 

So first we declare the class that helps you with the actual interpolation helper. You would instantiate an InterpolationHelper in your class, and pair it up with some start/begin values, and use the value returned by GetPercentage() as your Lerp progress value. The pattern which I use in my code very often looks something like this:

if (!m_CircleInterpolationHelper.IsEmpty()) { myCircleRadius = Lerp(0.0f, 25.0f, m_CircleInterpolationHelper.GetPercentage()); // Clears the interpolation once done. if (m_CircleInterpolationHelper.IsFinished()) { m_CircleInterpolationHelper = InterpolationHelper() } } 

One thing you really want to watch out for is only instantiating the interpolation once you want your animation to start, so for an example you would create your interpolation helper when the menu is opened.

void Editor::OnMenuOpen() { m_CircleInterpolationHelper = InterpolationHelper(1.0f, InterpolationCurve(EInterpolationCurveType::LINEAR)); } 

So, now we just need to take care of the actual easing implementation. This is just a wrapper class for your easings. For the linear type, we can just fall through with the progress value fed to the function. You can add whatever math-curves you'd like to use for your interpolations to the case-statement.

enum class InterpolationCurveType { LINEAR, SMOOTHSTEP } class InterpolationCurve { public: InterpolationCurve(InterpolationCurveType type) : m_Type(type) {} float GetCurveValue(float progress) const { switch(m_Type) { case InterpolationCurveType::LINEAR: return progress; case InterpolationCurveType::SMOOTHSTEP: return progress * progress * (3 - 2 * progress); // More cases here, maybe allow sampling a bezier curve or something other cool. } return 0.0f; } protected: private: InterpolationCurveType m_Type; } 

As a final disclaimer, the code above is untested and might not work right out of the box, but I'm sure the idea behind the code gets through.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.