Skip to content

Conversation

@DigiDuncan
Copy link
Collaborator

Easing Into It: The anim module

"Nature does not hurry, yet everything is accomplished."
Lao Tzu

Easing, made easy.

Good UI has juice. The way an interface moves can matter just as much as how it operates when it comes to the user's experience. But programming motion is hard; computers want to do things instantly, and snap from one thing to the next, things that we as humans aren't evolved to see as anything but jarring. How do we solve this problem? You can store states, incrementing positions and scales and opacities with delays and equations, but that puts tons of overhead on not only the program, but the programmer.

[an actor in black-and-white exaggeratedly throws up their hands in frustration] There has to be a better way!

Beloved by many are the easing functions, a simple way to plug in some known values, and get a nice smooth animated value in return. To use them, you only need to know a few basic things:

  • the start and end point of what you'd like smoothed
  • the start and end time for your animation
  • the current time
  • what easing method you'd like to use

That last one can be tricky, and it's why sites like easings.net exist at all. Even harder than knowing which curve to use, though, is knowing how to implement them, which is why I did it for you! Please, hold your applause to the end.

This PR introduces, primarily, two simple things:

  • ease(), a single function you can import for all your easing needs
  • Easing, a class containing every standard easing function, that you can pass right into ease()!

Every easing function from easings.net is implemented already, and adding more is trivial in the future.

Here's an example of how you might use ease() in a real world setting:

class PRWindow(Window):
    def __init__(self):
        super().__init__()
        self.sprite = SpriteCircle(25, arcade.color.CHARM_GREEN)

        # Define the parameters for our animation
        self.CIRCLE_START_X = 100.0
        self.CIRCLE_END_X = 1180.0
        self.CIRCLE_START_TIME = 1.0
        self.CIRCLE_END_TIME = 4.0

    def on_update(self, delta_time: float):
        time = GLOBAL_CLOCK.time
        self.sprite.center_x = ease(self.CIRCLE_START_X, self.CIRCLE_END_X,
                                    self.CIRCLE_START_TIME, self.CIRCLE_END_TIME,
                                    time, Easing.QUAD)

    def on_draw(self):
        arcade.draw_sprite(self.sprite)

In just a few short lines, we've made a moving object on our screen!

Note

This PR also deprecates the arcade.easing module, as its interface was somewhat confusing, and it was unused in the library.

@pushfoo
Copy link
Member

pushfoo commented Dec 19, 2025

The CI's doc build doing something weird again. I'm investigating it.

@pushfoo
Copy link
Member

pushfoo commented Dec 19, 2025

The doc currently "builds" but it's empty.

We could merge now if we need these functions to help fix other things. The API ref structure and docstrings won't get picked up with the current utils/update_quick_index.py's config section. That needs to be updated after we fix the docstrings.

@Cleptomania
Copy link
Member

I think the core module looks good. I like the structure of it.

Some nice to haves that I don't think particularly block merging the feature, but that I'd at least like to see as follow up PRs if they don't make their way into this one would be:

  • An example or two
  • Unit tests(there weren't previously unit tests for the easing functions, and given the nature of all these values being floats is probably annoying to write, but should be able to construe something for it). However, the previous easing functions were kind of just an afterthought, and if we want to strive towards a more mature anim package we should be looking to do this.
  • I think we need more docs for this, but I agree maybe we can sort out some of the written documentation for a more all-inclusive view of anim if we intend to add more to it.

@Cleptomania
Copy link
Member

This is currently targeted to development branch. We'll need to think about what version we intend to include this in, if we don't want to include this until 4.0, then should should merge into pyglet-3 branch for now. We can sort that out before we're going to merge it in case we change the branch structure for 4.0 development before then.

@DigiDuncan
Copy link
Collaborator Author

This is currently targeted to development branch. We'll need to think about what version we intend to include this in, if we don't want to include this until 4.0, then should should merge into pyglet-3 branch for now. We can sort that out before we're going to merge it in case we change the branch structure for 4.0 development before then.

Is there another 3.x release planned? I figured we were holding off until 4.0 anyway.

Copy link
Collaborator

@DragonMoffon DragonMoffon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need a second PR with examples, good docs, and testing, but getting this in now would be good

@eruvanos
Copy link
Member

I wonder if we can find a way to have a common setup for transitions/animations for the UI, too.
My first attempt was: #1420

@DigiDuncan
Copy link
Collaborator Author

I wonder if we can find a way to have a common setup for transitions/animations for the UI, too. My first attempt was: #1420

When anim is more fleshed out, I'd love to investigate this. I think my major concerns right now are giving people tooling to create those things themselves, the next steps is providing those out-of-the-box solutions.

@DigiDuncan
Copy link
Collaborator Author

Are we calling this ready to merge? I'd like @pushfoo to give me the go-ahead on making sure docs generate.

@eruvanos
Copy link
Member

Another think that comes to my mind,
pausing a game is often implemented by skipping the on_update code. The animation use a global clock, which would continue with the progress right?

@DigiDuncan
Copy link
Collaborator Author

Another think that comes to my mind, pausing a game is often implemented by skipping the on_update code. The animation use a global clock, which would continue with the progress right?

In my example, yes, you're correct -- I was aiming for the minimal "get this working" code. You could absolutely instead use your own clock the you hold in the Window's state, or implement these using on_update. The anim functions here are implementation-agnostic, they're just math helpers.

@pushfoo
Copy link
Member

pushfoo commented Dec 24, 2025

@eruvanos TL;DR: This is could be controlled by a slider from the Arcade GUI or help with the UI.

The PR replaces the limited and opinionated OOP with something which supports easing anything with the right math operations.

In my eyes, the main doc concerns are how the static methods aren't on the object. If we move them into it instead of having them as private-level module defs, docs should be easier to generate. Otherwise, we may need a helper to transplant docstrings from the wrapped functions onto their new staticmethod locations.

DigiDuncan and others added 8 commits December 24, 2025 03:51
* Convert EasingFunction into a typing.Protocol so it gets picked up by doc build + explain why

* Correct Sphinx style issues and broken cross-references

* Explain how of pyglet.math Matrix types won't work with easing (matmul)

* Add an __all__ to arcade.anim.easing
@pushfoo
Copy link
Member

pushfoo commented Dec 24, 2025

Animatable might be misleading as a name. If we change it to something like the following, I'd be fine with merging:

  • Interpolatable
  • CanInterpolate
  • SupportsInterpolation

Otherwise, I think this is a pretty good PR. It sets up adding an interpolate function which would let people supply their own replacement(s) for LERP when it can't unambiguously handle a datatype:

  • pyglet.math matrices
  • Values with looping axes that can go either way:
    • Pac-Man wrapping
    • Colors (HSV, etc.)
    • Rotations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants