Iteration 11
Previous: Iteration 10: Collisions
The source code for this iteration can be found here.
All the changes from the previous iteration can be viewed in diff format here.
Since the game is about destroying asteroids, it's about time we start implementing the shooting mechanics. We'll begin with a new data type for the bullets.
bulletSpeed = 10.0
bulletLine = LineSegment ((0,0),(0,bulletSpeed))
data Bullet = Bullet Body
instance LineRenderable Bullet where
interpolatedLines f (Bullet b) = [transform b' bulletLine]
where b' = interpolatedBody f b
We leverage the Body
functionality again, so the code for bullets has a very
similar structure with Player
and Asteroid
. The visual presentation for a
bullet is just a single pointing towards the bullet's movement direction.
The following utility function is used for creating new bullets from the player's ship.
-- | Initialize a new bullet with the given position and direction
initBullet :: Vec2 -> Float -> Bullet
initBullet pos angle = Bullet $ Body (pos' /+/ vel) angle vel 0 pos' angle
where vel = polar bulletSpeed angle
pos' = pos /+/ (polar 12.0 angle)
And finally a simple update function, which just uses the familiar updateBody
function, just like all our moving objects.
-- | Update a bullet to a new position
updateBullet :: Bullet -> Bullet
updateBullet (Bullet b) = Bullet $ updateBody b
We'll use the space bar to shoot.
shoot = Char ' '
This is the first occasion where we want to dynamically create new objects for our state, and for now we'll settle for a very simple solution instead of a more generic one. Since the player can only fire a single bullet during any one game tick, we embed the information directly to player data.
-- | Data type for tracking current player state
data Player = Player {
playerBody :: Body,
playerAlive :: Bool,
playerBullet :: Maybe Bullet
}
The type is Maybe Bullet
, so it'll be Nothing
if no bullet was fired and
Just Bullet
otherwise. This is set in the player's tick
function:
instance Tickable Player where
tick _ p@(Player _ False _) = p
tick kb p@(Player b _ _) = p {
playerBody = updatePlayerBody turn acc b,
playerBullet = bullet }
where turn | key turnLeft = -0.18
| key turnRight = 0.18
| otherwise = 0
acc | key thrust = 0.7
| otherwise = 0
bullet | key shoot = Just $ initBullet (bodyPos b) (bodyAngle b)
| otherwise = Nothing
key = isKeyDown kb
Just like with asteroids, we add a list of bullets to the GameState
data
structure.
-- | Data type for tracking game state
data GameState = GameState {
statePlayer :: Player,
stateAsteroids :: [Asteroid],
stateBullets :: [Bullet]
}
Bullets are rendered with the state just like asteroids.
instance LineRenderable GameState where
interpolatedLines f (GameState p a b) = plines ++ alines ++ blines
where plines = interpolatedLines f p
alines = concatMap (interpolatedLines f) a
blines = concatMap (interpolatedLines f) b
In the tick
function of State
, we update each bullet and add a possible new
bullet, if one exists.
-- | Tick state into a new game state
tickState :: Keyboard -> GameState -> GameState
tickState kb s@(GameState pl a b) = s {
statePlayer = collidePlayer p' a',
stateAsteroids = a',
stateBullets = b'
}
where p' = tick kb pl
a' = map updateAsteroid a
b' = map updateBullet $ newBullets
newBullets = case (playerBullet p') of
Nothing -> b
Just x -> x:b
Now we have the basics down, but of course, there are still several issues that we need to take care of in the few following iterations.
Next: Iteration 12: Bullet Limits