First we need to clarify what "smooth movement" is.
Let's first talk about smooth movement in a pixelated space.
Not taking into account motion blur and sub-pixel movement, the smoothest amount of movement you can have on a pixel screen is one pixel. So your code
pygame.draw.circle(SCREEN, (255, 255, 255), (newx, SCREENRECT.centery), 30)
pygame.display.update()
newx+= 1
actually procudes the smoothest movement in space possible.
Your code produces a smooth movement of roughly 60 pixels per second because of the clock.tick(60)
call. This is smooth, but not very fast.
To get fast movement, you now could increase the amount of pixels your image jumps per step by using newx+= 2
or newx+= 5
. But this, in line with the above definition, will no longer produce smooth movement. Why? Because you move more than 1px in one step, which we percieve as stuttering.
So, what can be done? If we want fast movement, but can only get it smooth by moving one pixel at a time, we need to move 1px at a time more often per second. And that means: increasing the renderings per second, or, more common, frames per second.
(Note that we have not yet talked about timesteps here at all.)
So as a first step, decrease the waiting time between frames (Clock.tick()
essentially is a pause function with dynamic duration) by increasing the argument. Try clock.tick(120)
or clock.tick(360)
.
At some point you will notice that the perceived speed of the motion does no longer increase. This is because you reach the limit of how long your rendering actually takes to compute. At this point, optimization has to happen if the movement is still to slow.
Your drawing routine is inefficient for various reasons:
It clears the whole screen to black every frame with SCREEN.fill((0, 0, 0))
, although technically you would just neet to clear the area where the circle is.
You compute and draw a circle in every frame using pygame.draw.circle()
. It would be more efficient to pre-render the circle to a surface an blit that surface to the background.
These would be starting points to get your simple animation smooth and fast. You can optimize further by abandoning Pygame Surfaces and switching to OpenGL. But it is important to note that there is always a limit to the speed of smooth, i.e. 1px movement. Most of the time the practical limit is the monitor refresh rate. If your monitor updates the application's display from the frame buffer memory, say, at 90 times per second (90 Hz), then 90 px / s is the most of smooth movement in space that you can get.
Now you were asking about timesteps, so let's talk about smooth movement in time.
We perceive an object moving smoothly if it moves by equal amounts of space in equal amounts of time. If you have tuned up the FPS in the above example to 360, you might have noticed that the circle stutters and moves irregularily. This is because at some frames Pygame manages to draw everything in less than 1/360 s, and in some frames not. This means that some times the circle does move 360px in one second, and some times the circle takes longer than one second wall clock time to move 360 px. This is because rendering time is not guaranteed. And this is where timesteps come in.
The principle of timestepping is to keep the movement in sync with the wall clock time. That means, instead of blindly moving a fixed amount of pixels per frame, you measure the time your frames take, and adjust the amount of movement (and possibly the time to pause) accordingly. The answer of Yos233 shows one way to do this using the return value of clock.tick()
.
Timestepping requires that there is a target speed of your animation. Say an object shall move 50 px per second. The basic procedure is
Draw the object.
Wait until 1/50 s has passed.
Update position by 1 px.
Repeat.
The critical point is when in 2. you see that actually more than 1/50 s has already passed. Here, to keep the movement smooth in time, you have to quantify how much time over 1/50 s has passed, and add to the amount of space to move accordingly.
Note that in this case you inevitably will have to move more than 1px to stay in sync; this means that your movement will be smooth in time, but no longer smooth in space.
To sum up:
1px movement is the smoothest you can get, and it is limited by the frame rate your whole setup can achieve.
Timestepping will smooth out your movement over time, but will actually increase jumps and stutter if the target speed can not be met.
For further reading on timesteps, check out the excellent article Fix Your Timestep!.