Small Basic: Timeline for the games
A common game requirement is to synchronize numerous elements with a time line. For example a ball moving by 200 pixels in 2 seconds.
We have several possible approaches, this article will examine two especially :
- a simple and classic imprecise approach.
- a second approach with a more precise timeline.
The simple method
The simple, commonly used. method is to calculate the time to wait for each one pixel move. In our case, 2 seconds equals 2000 milliseconds and so we move to one pixel every 10 ms (2000 ms / 200 pixels). In a loop we move the ball to one pixel and we wait 10 ms, repeatedly 200 times, we will move our ball to 200 pixels in 2 seconds:
' Move delay in ms
delay = 2000
' Move width in pixel
move = 200
' Create the ball
GraphicsWindow.Show()
ball = Shapes.AddEllipse(20,20)
ballPos = 0
Shapes.Move(ball, 10 + ballPos,10)
' Reference the time
start = Clock.ElapsedMilliseconds
' Loop
For i = 1 To move
' Move the ball to one pixel
ballPos = ballPos + 1
Shapes.Move(ball, 10 + ballPos,10)
' We wait delay/move milliseconds
Program.Delay(delay/move)
EndFor
' Calculate the real time used
GraphicsWindow.Title = Clock.ElapsedMilliseconds - start
If we run the program, our ball apparently moves to 200 pixels in 2 seconds, but if we look at the windows title where we display the final time elapsed, we can see that it show a longer time spent.
Why the difference?
Simply because our program does not only wait, it moves the ball, and makes some calculations. All this takes time, and the sum of these is greater than our 2 seconds. Someone with a more powerful computer ill see a lower gap, On the other hand someone using a slower computer will see a larger difference. In a real game, there user will also interact and use the mouse or keyboard which will require processing that could also "slow down" our program even more.
Our ball will therefore not really be where it should be because of that time difference.
This lack of precision is not always a problem in a game, but if we want to make a quiz with a stopwatch, and we want that all players have the same opportunities, we cannot be satisfied with this, or players who will have a less powerful computer will have more time to respond :)
This method also has a second issue, let us imagine the case where we have several elements that move differently (not at the same speed for example), how to manage the time for each of the items? This method is likely to introduce strange interactions in such a scenario.
A more precise time line
To solve our problem of precision regardless of the computer or the time consumed by the program for these calculations, we have another available approach: the timeline concept.
A time line represents the time that "runs" for our game. At any time in the game it is able to tell us where we are compared with the beginning of the timeline, and so we can determine which state we are supposed be in our game.
If we take my example with the ball, when I'll be at 500 ms in the timeline, I known my ball will be at 50 pixels from the origin, and at 1500 ms it's 150 pixels.
In Small Basic this is simple to simulate a timeline. We use "Clock.ElapsedMilliseconds" which returns the milliseconds from the 1st January 1900. If we save the value when we start the timeline :
startTimeline=Clock.ElapsedMilliseconds
then if we want to known where we are in the timeline, we just do a subtract :
timeline=Clock.ElapsedMilliseconds-startTimeline
Take our moving ball and this time move it based on the timeline for more precision.
' Move delay in ms
delay = 2000
' Move width in pixel
move = 200
' Create the ball
GraphicsWindow.Show()
ball = Shapes.AddEllipse(20,20)
ballPos = 0
Shapes.Move(ball, 10 + ballPos,10)
' Start the timeline
startTimeline = Clock.ElapsedMilliseconds
' Loop
While ballPos < move
' Where we are in the timeline ?
timeline = Clock.ElapsedMilliseconds - startTimeline
' Calculate the ball position based on the timeline
ballPos = (timeline * move) / delay
' Move the ball
Shapes.Move(ball, 10 + ballPos,10)
' Simulate a long calculation to disrupts our program
If ballPos < move/2 Then
Program.Delay(Math.GetRandomNumber(150))
EndIf
EndWhile
' Calculate the real time used
GraphicsWindow.Title = Clock.ElapsedMilliseconds - startTimeline
If we run this program we find that the gap disappear.
Yet in the first part of the distance I do random breaks, it is found as the ball "stutters", but it always comes back to the place that is hers from the timeline.
To calculate the position of the ball just make a cross multiplication :
2000 ms (delay) => 200 pixels (move)
timeline => ballPos
so
ballPos=(move*timeline)/ballPos
Elapsed time
So our ball is sync with our timeline.This example is so simple, in a real game, having to recalculate a situation since the beginning of the timeline is not always the best option.
Sometimes it's better to calculate the time elapsed between two consultations of the timeline. With our ball, rather than calculate the position according to the timeline on each iteration of the loop, we will calculate the time that has elapsed since the last loop, then we will calculate the number of pixels that our ball would having traveled in that time.
' Move delay in ms
delay = 2000
' Move width in pixel
move = 200
' Create the ball
GraphicsWindow.Show()
ball = Shapes.AddEllipse(20,20)
ballPos = 0
Shapes.Move(ball, 10 + ballPos,10)
' Start the timeline
startTimeline = Clock.ElapsedMilliseconds
lastTimeline = 0
' Loop
While ballPos < move
' Where we are in the timeline ?
timeline = Clock.ElapsedMilliseconds - startTimeline
' Calculate the elapsed time
elapsed = timeline - lastTimeline
' Calculate the ball movement in the elapsed time
ballMovement = (elapsed * move) / delay
' Add the movement at the ball position
ballPos = ballPos + ballMovement
' Move the ball
Shapes.Move(ball, 10 + ballPos,10)
' Simulate a long calculation to disrupts our program
If ballPos < move/2 Then
Program.Delay(Math.GetRandomNumber(150))
EndIf
' Save the timeline for next loop
lastTimeline = timeline
EndWhile
' Calculate the real time used
GraphicsWindow.Title = Clock.ElapsedMilliseconds - startTimeline
This method is required in some cases. Imagine that our ball moves according to the keyboard keys pressed by the player, it is impossible to calculate the position of the ball since the beginning of the timeline. On the other hand make this calculation based on the elapsed time can work.
' Move delay in ms
delay = 2000
' Move width in pixel
move = 200
' Create the ball
GraphicsWindow.Show()
ball = Shapes.AddEllipse(20,20)
ballPos = 0
ballPosX = 0
ballPosY = 0
offX = 1
offY = 0
Shapes.Move(ball, 10 + ballPosX,10 + ballPosY)
' Handle the key press
GraphicsWindow.KeyDown = OnKeyDown
' Start the timeline
startTimeline = Clock.ElapsedMilliseconds
lastTimeline = 0
' Loop
While ballPos < move
' Where we are in the timeline ?
timeline = Clock.ElapsedMilliseconds - startTimeline
' Calculate the elapsed time
elapsed = timeline - lastTimeline
' Calculate the ball movement in the elapsed time
ballMovement = (elapsed * move) / delay
' Add the movement at the ball position
ballPos = ballPos + ballMovement
ballPosX = ballPosX + (ballMovement * offX)
ballPosY = ballPosY + (ballMovement * offY)
' Move the ball
Shapes.Move(ball, 10 + ballPosX,10 + ballPosY)
' Simulate a long calculation to disrupts our program
If ballPos < move/2 Then
Program.Delay(Math.GetRandomNumber(150))
EndIf
' Save the timeline for next loop
lastTimeline = timeline
EndWhile
' Calculate the real time used
GraphicsWindow.Title = Clock.ElapsedMilliseconds - startTimeline
Sub OnKeyDown
If GraphicsWindow.LastKey = "Up" Then
offX = 0
offY = -1
ElseIf GraphicsWindow.LastKey = "Down" Then
offX = 0
offY = 1
ElseIf GraphicsWindow.LastKey = "Left" Then
offX = -1
offY = 0
ElseIf GraphicsWindow.LastKey = "Right" Then
offX = 1
offY = 0
EndIf
EndSub
Our ball move always to 200 pixels in 2 seconds, but if we pressed the arrow keyboard keys our ball changes direction.
A timeline for each
In a game based all on once timeline is not necessarily the most practical thing.
The first reason is we often need to restart a timeline, for example when we press a key that move acharacter to 100 pixels, we will start a new timeline for the movement calculation to known where is it based on the timeline.
Each element in a game will have its own timelines according to these needs. For example a timeline to calculate its current movement, and a timeline for calculating an animation. The game itself often have a timeline, for the game time for example.
' Build the game board
GraphicsWindow.Show()
GraphicsWindow.KeyDown = OnKeyDown
gameTitle = Shapes.AddText("")
' First element : a ball
elm[0]["shape"] = Shapes.AddEllipse(20,20)
elm[0]["x"] = 40
elm[0]["y"] = 40
elm[0]["startTimeline"] = -1
elm[0]["timeline"] = -1
Shapes.Move(elm[0]["shape"], elm[0]["x"], elm[0]["y"])
' Second element : a box
GraphicsWindow.BrushColor = "Yellow"
elm[1]["shape"] = Shapes.AddRectangle(20,20)
elm[1]["x"] = 40
elm[1]["y"] = 140
elm[1]["startTimeline"] = Clock.ElapsedMilliseconds
elm[1]["timeline"] = -1
Shapes.Move(elm[1]["shape"], elm[1]["x"], elm[1]["y"])
' Init the game
gameStartTime = Clock.ElapsedMilliseconds
gameTime = 0
' Game loop
While "True"
' Game timeline calculation
gameTime = Clock.ElapsedMilliseconds - gameStartTime
' Display the title
title = "Game Time : " + Math.Floor(gameTime / 1000) + " seconds"
title = title + " | Ball : " + elm[0]["timeline"]
title = title + " | Box : " + elm[1]["timeline"]
Shapes.SetText(gameTitle, title)
' Calculate the timeline for each element
For i = 0 To 1
stl = elm[i]["startTimeline"]
If stl > 0 Then
elm[i]["timeline"] = Clock.ElapsedMilliseconds - stl
EndIf
EndFor
' Calculate the pulse of the ball
If elm[0]["timeline"] > 0 Then
zoom =1+(0.5*Math.Sin(elm[0]["timeline"]/100))
Shapes.Zoom(elm[0]["shape"], zoom, zoom)
EndIf
' Calculate the rotation of the box : One turn in two seconds
Shapes.Rotate(elm[1]["shape"], Math.Remainder((elm[1]["timeline"]/(1000 * 2)) * 360, 360) )
' We pause the computer
Program.Delay(50)
EndWhile
' Handle the keyboard
Sub OnKeyDown
' If press Escape quit the game
If GraphicsWindow.LastKey = "Escape" Then
Program.End()
EndIf
' If pressed space bar, enable/disable the pulse of the ball
If GraphicsWindow.LastKey = "Space" Then
If elm[0]["startTimeline"] > 0 Then
elm[0]["startTimeline"] = -1
elm[0]["timeline"] = -1
Else
elm[0]["startTimeline"] = Clock.ElapsedMilliseconds
elm[0]["timeline"] = 0
EndIf
EndIf
EndSub
In this example, we have three timelines, one for the game which display the time elapsed from the start of the game. A second timeline for the "Ball" object which start when we press the space bar. The last timeline is for the "Box" for the rotation calculation.
Conclusion
The timelines are used to managed movement, animations, visual effects, etc.
So according to your need, feel free to use one or more of these techniques.
You can retrieve all the sample in the GitHub repos https://github.com/Small-Basic-French/Exemples/tree/master/Timeline.