From dbb4799f832ef3c1d943e1708c0b0193446e938f Mon Sep 17 00:00:00 2001 From: "Juan J. Martinez" Date: Sat, 18 Feb 2023 16:15:12 +0000 Subject: Slimes hit enemies --- src/Game/Entities.hs | 72 ++++++++++++++++++++++++++++++++++++---------------- src/Game/State.hs | 3 ++- 2 files changed, 52 insertions(+), 23 deletions(-) (limited to 'src/Game') diff --git a/src/Game/Entities.hs b/src/Game/Entities.hs index 8decf07..cfdfcd0 100644 --- a/src/Game/Entities.hs +++ b/src/Game/Entities.hs @@ -13,12 +13,15 @@ import qualified SDL data Dir = DirRight | DirLeft deriving (Eq) -data Type = TypePlayer | TypePickup | TypeEffect +data Type = TypePlayer | TypePickup | TypeEffect | TypeEnemy toSpriteSet :: Dir -> Int toSpriteSet DirRight = 0 toSpriteSet DirLeft = 1 +hitDelay :: Int +hitDelay = 72 + frameDelay :: Int frameDelay = 6 @@ -45,6 +48,7 @@ type IsBlocked = Int -> Int -> Bool data Entities = Entities { sprites :: S.SpriteSheet, player :: IORef Entity, + state :: IORef GS.State, entities :: [Entity] } @@ -83,6 +87,19 @@ collision playerRef other = do && player.y + 12 < other.y + 16 && other.y + 4 < player.y + 24 +-- | Update game state to reflect that the player was hit by an enemy. +hitPlayer :: IORef GS.State -> IO () +hitPlayer stateRef = + stateRef $~ updatePlayerHit + where + updatePlayerHit :: GS.State -> GS.State + updatePlayerHit s = s {GS.lives = s.lives - 1, GS.hitDelay = hitDelay} + +-- | Update game state to reflect that the player picked up a battery. +collectedBattery :: IORef GS.State -> IO () +collectedBattery stateRef = + stateRef $~ (\s -> s {GS.batteries = s.batteries + 1}) + mkEntities :: S.SpriteSheet -> M.Map -> IORef C.Controls -> IORef GS.State -> IO Entities mkEntities sprites m controls stateRef = do player <- case find M.isPlayer (M.objects m) of @@ -91,11 +108,11 @@ mkEntities sprites m controls stateRef = do playerRef <- newIORef player entities <- traverse (toEntity playerRef) $ sort $ filter (not . M.isPlayer) (M.objects m) -- the entities list has always player first - pure $ Entities sprites playerRef (player : entities) + pure $ Entities sprites playerRef stateRef (player : entities) where toEntity :: IORef Entity -> M.Object -> IO Entity - toEntity playerRef (M.SlimeEntity x y) = mkSlime sprites x y playerRef (M.isBlocked m) - toEntity playerRef (M.BatteryEntity x y) = mkBattery sprites x y playerRef stateRef + toEntity playerRef (M.SlimeEntity x y) = mkSlime sprites x y playerRef (M.isBlocked m) (hitPlayer stateRef) + toEntity playerRef (M.BatteryEntity x y) = mkBattery sprites x y playerRef (collectedBattery stateRef) toEntity _ (M.PlayerEntity _ _) = error "Player already processed" processSpawn :: S.SpriteSheet -> Spawn -> IO Entity @@ -106,8 +123,16 @@ updateAll es = do -- update the player first (including the reference) updatedPlayer <- player.update player _ <- writeIORef es.player updatedPlayer + state <- readIORef es.state -- then the other entities - updated <- (updatedPlayer :) <$> traverse (\e -> e.update e) others + updated <- + if state.hitDelay > 0 + then do + -- if the player was hit, update state and don't update the enemies + _ <- writeIORef es.state state {GS.hitDelay = state.hitDelay - 1} + (updatedPlayer :) <$> traverse (\e -> if notEnemy e then e.update e else pure e) others + else -- otherwise update all + (updatedPlayer :) <$> traverse (\e -> e.update e) others -- collect new entities new <- traverse (processSpawn es.sprites) (concatMap (\e -> e.spawns) updated) -- clear spawns (new entities), filter out destroyed entities, and add the new ones @@ -116,11 +141,18 @@ updateAll es = do player = head es.entities others = tail es.entities + notEnemy :: Entity -> Bool + notEnemy ent = case ent.typ of + TypeEnemy -> False + _ -> True + render :: SDL.Renderer -> Entities -> IO () render renderer es = do + state <- readIORef es.state -- always render player last traverse_ renderOne others - renderOne player + -- won't draw all the frames if the player was hit + if testBit state.hitDelay 2 then pure () else renderOne player where player = head es.entities others = tail es.entities @@ -156,8 +188,8 @@ updateEffect e | e.frame + 1 < frameLimit e = e {delay = frameDelay, frame = e.frame + 1} | otherwise = e {destroy = True} -mkBattery :: S.SpriteSheet -> Int -> Int -> IORef Entity -> IORef GS.State -> IO Entity -mkBattery sprites x y playerRef stateRef = do +mkBattery :: S.SpriteSheet -> Int -> Int -> IORef Entity -> IO () -> IO Entity +mkBattery sprites x y playerRef collectedBattery' = do s <- S.get sprites "battery" pure Entity @@ -170,19 +202,15 @@ mkBattery sprites x y playerRef stateRef = do gravity = gravityOff, dir = DirRight, sprite = s, - update = updateBattery (collision playerRef) collectedBattery, + update = updateBattery (collision playerRef) collectedBattery', destroy = False, spawns = [] } - where - collectedBattery :: IO () - collectedBattery = do - stateRef $~ (\s -> s {GS.batteries = s.batteries + 1}) updateBattery :: Collision -> IO () -> Entity -> IO Entity -updateBattery touchedPlayer collectedBattery e = do +updateBattery touchedPlayer collectedBattery' e = do touched <- touchedPlayer e - if touched then e {destroy = True} <$ collectedBattery else pure updateBatteryFrame + if touched then e {destroy = True} <$ collectedBattery' else pure updateBatteryFrame where updateBatteryFrame :: Entity updateBatteryFrame @@ -190,12 +218,12 @@ updateBattery touchedPlayer collectedBattery e = do | e.frame + 1 < frameLimit e = e {delay = frameDelay, frame = e.frame + 1} | otherwise = e {delay = frameDelay, frame = 0} -mkSlime :: S.SpriteSheet -> Int -> Int -> IORef Entity -> IsBlocked -> IO Entity -mkSlime sprites x y playerRef isBlocked = do +mkSlime :: S.SpriteSheet -> Int -> Int -> IORef Entity -> IsBlocked -> IO () -> IO Entity +mkSlime sprites x y playerRef isBlocked hitPlayer' = do s <- S.get sprites "slime" pure Entity - { typ = TypePickup, + { typ = TypeEnemy, x = x, y = y, delay = frameDelay, @@ -204,16 +232,16 @@ mkSlime sprites x y playerRef isBlocked = do gravity = gravityOff, dir = DirRight, sprite = s, - update = updateSlime (collision playerRef) isBlocked, + update = updateSlime (collision playerRef) isBlocked hitPlayer', destroy = False, spawns = [] } -updateSlime :: Collision -> IsBlocked -> Entity -> IO Entity -updateSlime touchedPlayer isBlocked e = do +updateSlime :: Collision -> IsBlocked -> IO () -> Entity -> IO Entity +updateSlime touchedPlayer isBlocked hitPlayer' e = do touched <- touchedPlayer e let updated = updateSlimeFrame - pure $ if touched then e {destroy = True} else updateMovement updated + if touched then fmap (const e) hitPlayer' else pure $ updateMovement updated where updateMovement :: Entity -> Entity updateMovement ent diff --git a/src/Game/State.hs b/src/Game/State.hs index 2223434..d943ac0 100644 --- a/src/Game/State.hs +++ b/src/Game/State.hs @@ -4,5 +4,6 @@ data State = State { batteries :: Int, totalBatteries :: Int, lives :: Int, - totalLives :: Int + totalLives :: Int, + hitDelay :: Int } -- cgit v1.2.3