Timer-Based Movement - Frame Rate Independent Motion Example
When developing video games, one of the key challenges is ensuring smooth and consistent movement across different devices and frame rates. A game running on a high-end computer might process frames much faster than one on an older machine. If movement is tied directly to the frame rate, objects will move faster on some systems and slower on others, leading to inconsistent gameplay. To avoid this, we use timer-based movement, also known as frame rate independent motion.
This PlayBASIC code snippet shows how to make moving objects (balls) in a game move independently of the frame rate. This means the movement remains smooth and consistent regardless of how fast or slow the game runs. This is achieved by using timers to calculate the position of each ball based on elapsed time rather than relying on the game’s frame rate.
How It Works
1. Setting Frame Rate Goals
The program sets a target frame rate of 50 frames per second (FPS) and calculates how many milliseconds each frame should last:
1000 / 50 = 20ms per frame
This helps standardize movement calculations.
2. Ball Setup
The balls are defined using a custom type (`tBALL`), which includes:
- Position (`X`, `Y`) – The current coordinates of the ball.
- Size – The radius of the ball.
- Color – A randomly assigned color.
- Speed – The movement speed of the ball.
- Direction – The angle at which the ball moves.
- Start Time – The timestamp when the ball was created.
3. Adding Balls
New balls are randomly generated within the display area. Every 50 milliseconds, multiple new balls are added with:
- Random positions within the screen’s width and height.
- Random sizes and colors for variety.
- Random movement speeds and directions to create dynamic movement.
4. Moving the Balls
Each ball's movement is updated based on the elapsed time since its creation:
- 1. Calculate elapsed time – Determine how long the ball has existed.
- 2. Compute the distance traveled – Using the formula:
Distance = (Speed / Frame Rate Milliseconds) * Elapsed Time
- 3. Update position – The ball moves based on its speed, direction, and the elapsed time using trigonometric calculations:
X = OriginX + cos(Direction) * Distance Y = OriginY + sin(Direction) * Distance
This movement is frame rate independent, meaning the speed is consistent even if the frame rate fluctuates.
5. Rendering and Removal
- Each ball is drawn at its updated position.
- If a ball moves outside the screen, it is removed from the list.
6. Display Information
The program also provides real-time feedback by displaying:
- Total number of active balls
- Current frames per second (FPS)
Why Timer-Based Movement Matters
Many games rely on frame-dependent movement, where objects move a fixed amount per frame. This can cause issues:
- If the frame rate drops, movement appears slower.
- If the frame rate increases, movement appears faster.
By using time-based calculations, the movement remains consistent, making the game perform smoothly across different hardware and performance conditions.
This technique is essential for modern game development, ensuring a better player experience by maintaining consistent motion no matter the frame rate.
// Set our users ideal frames per second rate
Frame_RATE# = 50
// Ticks per frame
Frame_RATE_MILLISECOND_PER_FRAME# = 1000.0/Frame_RATE#
Type tBALL
X#
Y#
Size
Colour
Speed#
Direction#
StartTime
EndType
Dim Ball as TBall List
CurrentTime = timer() and $7fffffff
SurfaceWidth = GetSurfaceWidth()
SurfaceHeight = GetSurfaceHeight()
Do
Cls
CurrentTime= Timer() and $7fffffff
// Randomly Add more balls to the scene
if Add_Balls < CurrentTime
for lp =1 to rndrange(100,500)
Ball = new tBall
Ball.x = rndrange(100,SurfaceWidth-100)
Ball.y = rndrange(100,SurfaceHeight-100)
Ball.size = rndrange(10,20)
Ball.Colour = rndrgb()
Ball.Speed = rndrange#(1, 5)
Ball.Direction= rnd#(360)
Ball.StartTime= CurrentTime
next
Add_Balls=CurrentTime+50
endif
// Draw Balls
lockbuffer
For each Ball()
// How long as this ball been alive
// and move in this direction?
ElapsedTime = CurrentTime-Ball.StartTime
// compute how far this ball have
// moved since creation
Dist# = (Ball.Speed /Frame_RATE_MILLISECOND_PER_FRAME#)
Dist#*= ElapsedTime
// Compute the moved position from
// it's origin point
x#=Ball.x+cos(ball.direction)*Dist#
y#=Ball.y+sin(ball.direction)*Dist#
// Get size of this ball
Size=ball.size
// check if the ball is on screen or not ?
// if not; delete it from the list
if x#<(-size) or x#>(SurfaceWidth+size)_
or y#<(-ball.size) or y#>(SurfaceHeight+size)
Ball=null
continue
endif
// draw the ball since it's still visible
circlec x#,Y#,size,true,ball.colour
Next
unlockbuffer
text 10,10,"Balls #"+str$(GetListSize(Ball()))
text 10,20," Fps #" +str$(fps())
sync
Loop spacekey()
By implementing these principles in your PlayBASIC projects, you'll create games that feel polished and professional, no matter the hardware they're running on!