-
Notifications
You must be signed in to change notification settings - Fork 2
Iteration 3
Previous: Iteration 2: Rotating the Ship
The source code for this iteration can be found here.
All the changes from the previous iteration can be viewed in diff format here.
In past iterations we've only updated the game window with a static image, so the next step is to add some movement. This means that game state will somehow need to change between calls to our display callback.
First we'll add a new type class to Haskeroids/Tick:
module Haskeroids.Tick where
class Tickable t where
tick :: t -> t
This means that for Tickable data, we can call the tick function to get a new version of the data.
Now let's make our GameState
an instance of Tickable
.
instance Tickable GameState where
tick = tickState
-- | Tick state into a new game state
tickState :: GameState -> GameState
tickState (GameState pl) = GameState $ tick pl
This returns a new state that contains a ticked player, so we need to make our
Player
data tickable too. For this iteration, we'll just make the player ship
rotate in place.
instance Tickable Player where
tick (Player b) = Player $ rotate 0.1 b
Like the tickState
function, this too creates a new Player
that has a
rotated body. We'll add the rotate
method to Haskeroids/Geometry/Transform:
-- | Rotate a body
rotate :: Float -> Body -> Body
rotate d b@(Body {bodyAngle=a}) = b {bodyAngle = a+d}
Now we just need something to call the tick method for our state, and to somehow pass the new updated state to our rendering method. For this, we add a new callback function to Haskeroids/Callbacks:
-- | Periodical logic tick
logicTick :: (LineRenderable t, Tickable t) => t -> IO ()
logicTick t = do
let newTickable = tick t
displayCallback $= renderViewport newTickable
addTimerCallback 33 $ logicTick newTickable
postRedisplay Nothing
The logicTick
callback takes a single parameter, which has to be both tickable
and renderable. It then updates the displayCallback to use the new, updated
state and re-schedules itself 33ms into the future, fixing our logic step to
an average of 30 updates per second. The postRedisplay
call notifies GLUT that
the window needs to be redrawn.
To actually call logicTick for the first time, we modify initializeCallbacks
in Haskeroids/Initialize.
-- | Set up GLUT callbacks
initializeCallbacks = do
displayCallback $= renderViewport initialGameState
addTimerCallback 0 $ logicTick initialGameState
Using addTimerCallback for updates like this is actually a pretty lousy choice, as it makes no guarantees about the accuracy of the timer. It only promises that the callback will not be triggered for at least 33ms, but there's no upper bound. This means that we are likely to get uneven frameframe, but we'll keep the implementation simple for now and improve this later.
Next: Iteration 4: Keyboard Controls