From 7a8af18d0e1003c26eb595b5faa71e51da6286a6 Mon Sep 17 00:00:00 2001 From: "Juan J. Martinez" Date: Mon, 17 Apr 2023 23:41:35 +0100 Subject: Added new shooter enemy --- data/map1.json | 23 ++++++++---- data/sprites.json | 83 ++++++++++++++++++++++++++++++------------- data/sprites.png | Bin 18258 -> 21424 bytes game.cabal | 2 ++ src/Game/Entities.hs | 6 ++++ src/Game/Entities/Blast.hs | 40 +++++++++++++++++++++ src/Game/Entities/Common.hs | 14 ++++++++ src/Game/Entities/Effect.hs | 3 +- src/Game/Entities/Entry.hs | 3 +- src/Game/Entities/Exit.hs | 3 +- src/Game/Entities/Pickup.hs | 3 +- src/Game/Entities/Player.hs | 3 +- src/Game/Entities/Robot.hs | 3 +- src/Game/Entities/Shooter.hs | 67 ++++++++++++++++++++++++++++++++++ src/Game/Entities/Slime.hs | 3 +- src/Game/Entities/Types.hs | 7 +++- src/Game/Map.hs | 5 +++ 17 files changed, 229 insertions(+), 39 deletions(-) create mode 100644 src/Game/Entities/Blast.hs create mode 100644 src/Game/Entities/Shooter.hs diff --git a/data/map1.json b/data/map1.json index c1f6030..0d532df 100644 --- a/data/map1.json +++ b/data/map1.json @@ -52,8 +52,8 @@ "type":"", "visible":true, "width":16, - "x":16, - "y":16 + "x":32, + "y":112 }, { "height":16, @@ -179,13 +179,13 @@ { "height":24, "id":25, - "name":"Robot", + "name":"Shooter", "rotation":0, "type":"", "visible":true, "width":16, - "x":32, - "y":40 + "x":64, + "y":136 }, { "height":16, @@ -219,6 +219,17 @@ "width":16, "x":16, "y":96 + }, + { + "height":24, + "id":33, + "name":"Shooter-l", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":112, + "y":136 }], "opacity":1, "type":"objectgroup", @@ -227,7 +238,7 @@ "y":0 }], "nextlayerid":6, - "nextobjectid":33, + "nextobjectid":34, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.7.2", diff --git a/data/sprites.json b/data/sprites.json index 3c0b7b7..c4fcfb3 100644 --- a/data/sprites.json +++ b/data/sprites.json @@ -35,8 +35,8 @@ "dust": { "frames": [ { "x": 0, "y": 24, "width": 16, "height": 16 }, - { "x": 16, "y": 24, "width": 16, "height": 16 }, - { "x": 32, "y": 24, "width": 16, "height": 16 } + { "x": 16, "y": 24, "width": 16, "height": 16 }, + { "x": 32, "y": 24, "width": 16, "height": 16 } ], "sets": [ [0, 1, 2] @@ -45,9 +45,9 @@ "battery": { "frames": [ { "x": 0, "y": 40, "width": 16, "height": 16 }, - { "x": 16, "y": 40, "width": 16, "height": 16 }, - { "x": 32, "y": 40, "width": 16, "height": 16 }, - { "x": 48, "y": 40, "width": 16, "height": 16 } + { "x": 16, "y": 40, "width": 16, "height": 16 }, + { "x": 32, "y": 40, "width": 16, "height": 16 }, + { "x": 48, "y": 40, "width": 16, "height": 16 } ], "sets": [ [0, 0, 0, 0, 0, 0, 0, 1, 2, 3] @@ -55,10 +55,10 @@ }, "hud": { "frames": [ - { "x": 48, "y": 24, "width": 8, "height": 8 }, - { "x": 56, "y": 24, "width": 8, "height": 8 }, - { "x": 48, "y": 32, "width": 8, "height": 8 }, - { "x": 56, "y": 32, "width": 8, "height": 8 } + { "x": 48, "y": 24, "width": 8, "height": 8 }, + { "x": 56, "y": 24, "width": 8, "height": 8 }, + { "x": 48, "y": 32, "width": 8, "height": 8 }, + { "x": 56, "y": 32, "width": 8, "height": 8 } ], "sets": [ [0, 1, 2, 3] @@ -67,7 +67,7 @@ "slime": { "frames": [ { "x": 0, "y": 56, "width": 16, "height": 16 }, - { "x": 16, "y": 56, "width": 16, "height": 16 } + { "x": 16, "y": 56, "width": 16, "height": 16 } ], "sets": [ [0, 1] @@ -76,31 +76,64 @@ "robot": { "frames": [ { "x": 0, "y": 72, "width": 16, "height": 24 }, - { "x": 16, "y": 72, "width": 16, "height": 24 }, - { "x": 32, "y": 72, "width": 16, "height": 24 }, - { "x": 48, "y": 72, "width": 16, "height": 24 }, - { "x": 64, "y": 72, "width": 16, "height": 24 }, - { "x": 80, "y": 72, "width": 16, "height": 24 } + { "x": 16, "y": 72, "width": 16, "height": 24 }, + { "x": 32, "y": 72, "width": 16, "height": 24 }, + { "x": 48, "y": 72, "width": 16, "height": 24 }, + { "x": 64, "y": 72, "width": 16, "height": 24 }, + { "x": 80, "y": 72, "width": 16, "height": 24 } ], "sets": [ [0, 1, 0, 2], [5, 4, 5, 3] ] }, + "shooter": { + "frames": [ + { "x": 0, "y": 96, "width": 16, "height": 24 }, + { "x": 16, "y": 96, "width": 16, "height": 24 }, + { "x": 32, "y": 96, "width": 16, "height": 24 }, + { "x": 48, "y": 96, "width": 16, "height": 24 }, + { "x": 64, "y": 96, "width": 16, "height": 24 }, + { "x": 80, "y": 96, "width": 16, "height": 24 } + ], + "sets": [ + [0, 1, 0, 2], + [5, 4, 5, 3] + ] + }, + "blast": { + "frames": [ + { "x": 32, "y": 56, "width": 16, "height": 8 }, + { "x": 48, "y": 56, "width": 16, "height": 8 } + ], + "sets": [ + [0, 1] + ] + }, + "impact": { + "frames": [ + { "x": 64, "y": 56, "width": 8, "height": 8 }, + { "x": 72, "y": 56, "width": 8, "height": 8 }, + { "x": 80, "y": 56, "width": 8, "height": 8 } + ], + "sets": [ + [0, 1, 2] + ] + }, "exit": { "frames": [ { "x": 0, "y": 160, "width": 16, "height": 24 }, - { "x": 16, "y": 160, "width": 16, "height": 24 }, - { "x": 32, "y": 160, "width": 16, "height": 24 }, - { "x": 48, "y": 160, "width": 16, "height": 24 }, - { "x": 64, "y": 160, "width": 16, "height": 24 }, - { "x": 80, "y": 160, "width": 16, "height": 24 }, + { "x": 16, "y": 160, "width": 16, "height": 24 }, + { "x": 32, "y": 160, "width": 16, "height": 24 }, + { "x": 48, "y": 160, "width": 16, "height": 24 }, + { "x": 64, "y": 160, "width": 16, "height": 24 }, + { "x": 80, "y": 160, "width": 16, "height": 24 }, { "x": 0, "y": 184, "width": 16, "height": 24 }, - { "x": 16, "y": 184, "width": 16, "height": 24 }, - { "x": 32, "y": 184, "width": 16, "height": 24 }, - { "x": 48, "y": 184, "width": 16, "height": 24 }, - { "x": 64, "y": 184, "width": 16, "height": 24 }, - { "x": 80, "y": 184, "width": 16, "height": 24 } + { "x": 16, "y": 184, "width": 16, "height": 24 }, + { "x": 32, "y": 184, "width": 16, "height": 24 }, + { "x": 48, "y": 184, "width": 16, "height": 24 }, + { "x": 64, "y": 184, "width": 16, "height": 24 }, + { "x": 80, "y": 184, "width": 16, "height": 24 } ], "sets": [ [0, 1, 2, 3, 4, 5, 5, diff --git a/data/sprites.png b/data/sprites.png index 1ad43de..0247640 100644 Binary files a/data/sprites.png and b/data/sprites.png differ diff --git a/game.cabal b/game.cabal index 338d1b3..f5f38b7 100644 --- a/game.cabal +++ b/game.cabal @@ -32,6 +32,8 @@ library Game.Entities.Pickup Game.Entities.Slime Game.Entities.Robot + Game.Entities.Shooter + Game.Entities.Blast Game.Controller Game.Utils Game.Toaster diff --git a/src/Game/Entities.hs b/src/Game/Entities.hs index 6506a60..25a7184 100644 --- a/src/Game/Entities.hs +++ b/src/Game/Entities.hs @@ -16,6 +16,7 @@ import Data.Foldable (find, traverse_) import Data.IORef import Data.List (sort) import qualified Game.Controller as C +import Game.Entities.Blast import Game.Entities.Common import Game.Entities.Const import Game.Entities.Effect @@ -24,6 +25,7 @@ import Game.Entities.Exit import Game.Entities.Pickup import Game.Entities.Player import Game.Entities.Robot +import Game.Entities.Shooter import Game.Entities.Slime import Game.Entities.Types import qualified Game.Map as M @@ -46,6 +48,7 @@ mkEntities sprites m controls = do toEntity :: IORef Entity -> M.Object -> IO Entity toEntity playerRef (M.SlimeEntity x y d) = mkSlime sprites x y d (collision playerRef 16) (M.isBlocked m) toEntity playerRef (M.RobotEntity x y d) = mkRobot sprites x y d (collision playerRef 24) (M.isBlocked m) + toEntity playerRef (M.ShooterEntity x y d) = mkShooter sprites x y d (collision playerRef 24) (inLine playerRef 24) (M.isBlocked m) (collision playerRef 8) toEntity playerRef (M.BatteryEntity x y) = mkBattery sprites x y (collision playerRef 16) toEntity _ (M.PlayerEntity _ _) = error "Player already processed" @@ -110,6 +113,9 @@ updateAll es state = do t ActionEntryDone -> processActions s {GS.levelCompleted = GS.ExitOff} ents t ActionExitDone -> processActions s {GS.levelCompleted = GS.ExitDone} ents t + ActionAddBlast x y d playerCollision isBlocked -> do + blast <- mkBlast es.sprites x y d playerCollision isBlocked + processActions s (ents ++ [blast]) t processActions s ents [] = pure (s, ents) -- Update entities skipping enemies if the player was hit diff --git a/src/Game/Entities/Blast.hs b/src/Game/Entities/Blast.hs new file mode 100644 index 0000000..6e9fec2 --- /dev/null +++ b/src/Game/Entities/Blast.hs @@ -0,0 +1,40 @@ +module Game.Entities.Blast (mkBlast) where + +import Data.Bits (Bits (..)) +import Game.Entities.Const +import Game.Entities.Types +import qualified Game.Sprites as S + +mkBlast :: S.SpriteSheet -> Int -> Int -> Dir -> Collision -> IsBlocked -> IO Entity +mkBlast sprites x y dir playerCollision isBlocked = do + s <- S.get sprites "blast" + pure + Entity + { typ = TypeEnemy, + x = x, + y = y, + delay = frameDelay, + frame = 0, + set = 0, + jumping = False, + gravity = gravityOff, + dir = dir, + sprite = s, + update = updateBlast playerCollision isBlocked, + destroy = False, + actions = [], + dat = NoData + } + +updateBlast :: Collision -> IsBlocked -> Entity -> IO Entity +updateBlast touchedPlayer isBlocked e = do + touched <- touchedPlayer e + pure $ if touched then e {destroy = True, actions = [ActionHitPlayer, ActionAddEffect (e.x + 4) e.y "impact"]} else update + where + update + | e.dir == DirLeft && isBlocked (e.x - 1) (e.y + 4) = e {destroy = True, actions = [ActionAddEffect e.x e.y "impact"]} + | e.dir == DirLeft = updatedFrame {x = e.x - 1} + | e.dir == DirRight && isBlocked (e.x + 16) (e.y + 4) = e {destroy = True, actions = [ActionAddEffect (e.x + 8) e.y "impact"]} + | e.dir == DirRight = updatedFrame {x = e.x + 1} + | otherwise = updatedFrame + updatedFrame = if e.delay > 0 then e {delay = e.delay - 1} else e {delay = frameDelay, frame = e.frame `xor` 1} diff --git a/src/Game/Entities/Common.hs b/src/Game/Entities/Common.hs index 82e790e..661fe07 100644 --- a/src/Game/Entities/Common.hs +++ b/src/Game/Entities/Common.hs @@ -2,6 +2,7 @@ module Game.Entities.Common ( toSpriteSet, frameLimit, collision, + inLine, updateFrame, updateGravity, ) @@ -34,6 +35,19 @@ collision playerRef otherHeight other = do && player.y + otherHeight - 4 < other.y + otherHeight && other.y + 4 < player.y + 24 +-- | Check if the player is in line with the entity and the entity is facing the player. +inLine :: IORef Entity -> Int -> Collision +inLine playerRef otherHeight other = do + player <- readIORef playerRef + pure $ + player.typ == TypePlayer + && player.y + otherHeight - 4 < other.y + otherHeight + && other.y + 4 < player.y + 24 + -- XXX: adjust perhaps? so the enemies don't shoot too close to the player + && ( (other.dir == DirLeft && player.x < other.x) + || (other.dir == DirRight && player.x > other.x) + ) + -- | Update frame animation for entities that have direction. updateFrame :: Bool -> Entity -> Entity updateFrame updated e diff --git a/src/Game/Entities/Effect.hs b/src/Game/Entities/Effect.hs index f8b668a..6f056d8 100644 --- a/src/Game/Entities/Effect.hs +++ b/src/Game/Entities/Effect.hs @@ -22,7 +22,8 @@ mkEffect sprites x y name = do sprite = s, update = pure . updateEffect, destroy = False, - actions = [] + actions = [], + dat = NoData } updateEffect :: Entity -> Entity diff --git a/src/Game/Entities/Entry.hs b/src/Game/Entities/Entry.hs index 28b1c4e..18e29df 100644 --- a/src/Game/Entities/Entry.hs +++ b/src/Game/Entities/Entry.hs @@ -25,7 +25,8 @@ mkEntry sprites x y = do sprite = s, update = pure . updateEntry, destroy = False, - actions = [] + actions = [], + dat = NoData } updateEntry :: Entity -> Entity diff --git a/src/Game/Entities/Exit.hs b/src/Game/Entities/Exit.hs index 206bfaa..f5e01a8 100644 --- a/src/Game/Entities/Exit.hs +++ b/src/Game/Entities/Exit.hs @@ -25,7 +25,8 @@ mkExit sprites x y playerCollision = do sprite = s, update = updateExit playerCollision, destroy = False, - actions = [] + actions = [], + dat = NoData } updateExit :: Collision -> Entity -> IO Entity diff --git a/src/Game/Entities/Pickup.hs b/src/Game/Entities/Pickup.hs index 68d3852..7d6022c 100644 --- a/src/Game/Entities/Pickup.hs +++ b/src/Game/Entities/Pickup.hs @@ -22,7 +22,8 @@ mkBattery sprites x y playerCollision = do sprite = s, update = updateBattery playerCollision, destroy = False, - actions = [] + actions = [], + dat = NoData } updateBattery :: Collision -> Entity -> IO Entity diff --git a/src/Game/Entities/Player.hs b/src/Game/Entities/Player.hs index 18625b6..620f236 100644 --- a/src/Game/Entities/Player.hs +++ b/src/Game/Entities/Player.hs @@ -27,7 +27,8 @@ mkPlayer sprites x y controls isBlocked = do sprite = s, update = updatePlayer controls isBlocked, destroy = False, - actions = [] + actions = [], + dat = NoData } updateHorizontal :: IsBlocked -> Bool -> Bool -> Entity -> Entity diff --git a/src/Game/Entities/Robot.hs b/src/Game/Entities/Robot.hs index 664079a..11a10c5 100644 --- a/src/Game/Entities/Robot.hs +++ b/src/Game/Entities/Robot.hs @@ -23,7 +23,8 @@ mkRobot sprites x y d playerCollision isBlocked = do sprite = s, update = updateRobot playerCollision isBlocked, destroy = False, - actions = [] + actions = [], + dat = NoData } updateRobot :: Collision -> IsBlocked -> Entity -> IO Entity diff --git a/src/Game/Entities/Shooter.hs b/src/Game/Entities/Shooter.hs new file mode 100644 index 0000000..6e9675f --- /dev/null +++ b/src/Game/Entities/Shooter.hs @@ -0,0 +1,67 @@ +module Game.Entities.Shooter (mkShooter) where + +import Data.Bits (Bits (..)) +import Game.Entities.Common +import Game.Entities.Const +import Game.Entities.Types +import qualified Game.Sprites as S + +blasterCoolDown :: Int +blasterCoolDown = 128 + +mkShooter :: S.SpriteSheet -> Int -> Int -> Dir -> Collision -> Collision -> IsBlocked -> Collision -> IO Entity +mkShooter sprites x y d playerCollision playerInLine isBlocked playerVsBlast = do + s <- S.get sprites "shooter" + pure + Entity + { typ = TypeEnemy, + x = x, + y = y, + delay = frameDelay, + frame = 0, + set = toSpriteSet d, + jumping = False, + gravity = gravityOff, + dir = d, + sprite = s, + update = updateShooter playerCollision playerInLine isBlocked playerVsBlast, + destroy = False, + actions = [], + dat = ShooterData 0 + } + +updateShooter :: Collision -> Collision -> IsBlocked -> Collision -> Entity -> IO Entity +updateShooter touchedPlayer playerInLine isBlocked playerVsBlast e = do + touched <- touchedPlayer e + line <- playerInLine e + pure $ update touched line (updateFrame True (updateCoolDown e)) + where + update :: Bool -> Bool -> Entity -> Entity + update wasTouched wasInLine ent + | wasInLine && ent.dat.coolDown == 0 = + ent + { dat = ShooterData blasterCoolDown, + actions = [ActionAddBlast blastX (ent.y + 8) ent.dir playerVsBlast isBlocked], + frame = 1, + delay = frameDelay * 2 + } + | wasTouched = ent {actions = [ActionHitPlayer]} + | testBit ent.delay 1 = ent + | ent.dir == DirLeft + && (isBlocked (ent.x - 1) (ent.y + 17) || isBlocked (ent.x - 1) (ent.y + 17) || not (isBlocked (ent.x - 1) (ent.y + 24))) = + ent {dir = DirRight, set = toSpriteSet DirRight} + | ent.dir == DirLeft = ent {x = ent.x - 1} + | ent.dir == DirRight + && (isBlocked (ent.x + 16) (ent.y + 17) || isBlocked (ent.x + 16) (ent.y + 17) || not (isBlocked (ent.x + 16) (ent.y + 24))) = + ent {dir = DirLeft, set = toSpriteSet DirLeft} + | ent.dir == DirRight = ent {x = ent.x + 1} + | otherwise = ent + + updateCoolDown :: Entity -> Entity + updateCoolDown ent + | ent.dat.coolDown > 0 = ent {dat = ShooterData (ent.dat.coolDown - 1)} + | otherwise = ent + + blastX = case e.dir of + DirLeft -> e.x - 16 + DirRight -> e.x + 16 diff --git a/src/Game/Entities/Slime.hs b/src/Game/Entities/Slime.hs index 7607fb7..acbe638 100644 --- a/src/Game/Entities/Slime.hs +++ b/src/Game/Entities/Slime.hs @@ -23,7 +23,8 @@ mkSlime sprites x y d playerCollision isBlocked = do sprite = s, update = updateSlime playerCollision isBlocked, destroy = False, - actions = [] + actions = [], + dat = NoData } updateSlime :: Collision -> IsBlocked -> Entity -> IO Entity diff --git a/src/Game/Entities/Types.hs b/src/Game/Entities/Types.hs index 05b419e..05726a8 100644 --- a/src/Game/Entities/Types.hs +++ b/src/Game/Entities/Types.hs @@ -6,6 +6,7 @@ module Game.Entities.Types Entities (..), Action (..), Entity (..), + EntityData (..), ) where @@ -16,6 +17,8 @@ data Dir = DirRight | DirLeft deriving (Eq, Show, Ord) data Type = TypePlayer | TypePickup | TypeEffect | TypeEnemy deriving (Eq) +data EntityData = NoData | ShooterData {coolDown :: Int} + type Collision = Entity -> IO Bool type IsBlocked = Int -> Int -> Bool @@ -36,6 +39,7 @@ data Action | ActionExitStarted | ActionEntryDone | ActionExitDone + | ActionAddBlast Int Int Dir Collision IsBlocked data Entity = Entity { typ :: Type, @@ -50,5 +54,6 @@ data Entity = Entity sprite :: S.Sprite, update :: Entity -> IO Entity, destroy :: Bool, - actions :: [Action] + actions :: [Action], + dat :: EntityData } diff --git a/src/Game/Map.hs b/src/Game/Map.hs index 9616971..1d395dc 100644 --- a/src/Game/Map.hs +++ b/src/Game/Map.hs @@ -43,6 +43,7 @@ data Object | BatteryEntity Int Int | SlimeEntity Int Int Dir | RobotEntity Int Int Dir + | ShooterEntity Int Int Dir deriving (Show, Eq, Ord) data JsonMapData = JsonMapData @@ -86,10 +87,14 @@ instance JSON Object where SlimeEntity <$> valFromObj "x" obj <*> valFromObj "y" obj <*> pure DirRight Just "Robot" -> RobotEntity <$> valFromObj "x" obj <*> valFromObj "y" obj <*> pure DirRight + Just "Shooter" -> + ShooterEntity <$> valFromObj "x" obj <*> valFromObj "y" obj <*> pure DirRight Just "Slime-l" -> SlimeEntity <$> valFromObj "x" obj <*> valFromObj "y" obj <*> pure DirLeft Just "Robot-l" -> RobotEntity <$> valFromObj "x" obj <*> valFromObj "y" obj <*> pure DirLeft + Just "Shooter-l" -> + ShooterEntity <$> valFromObj "x" obj <*> valFromObj "y" obj <*> pure DirLeft Just (JSString (JSONString s)) -> Error $ "unsupported entity " ++ show s e -> Error $ "unsupported entity in " ++ show e readJSON _ = mzero -- cgit v1.2.3