#9 Added level selection render

This commit is contained in:
Tibo De Peuter 2022-12-21 16:07:05 +01:00
parent 0786a41006
commit 2055ef234e
17 changed files with 401 additions and 34 deletions

View file

@ -64,7 +64,7 @@ TODO
- An example playthrough, with pictures and explanations - An example playthrough, with pictures and explanations
\pagebreak <div style="page-break-after: always; visibility: hidden">\pagebreak</div>
## Writing your own stages ## Writing your own stages
@ -250,10 +250,22 @@ If we look at the example, all the objects are
Entry = empty ConditionList + Action ('leave()') Entry = empty ConditionList + Action ('leave()')
``` ```
\pagebreak <div style="page-break-after: always; visibility: hidden">\pagebreak</div>
## Development notes ## Development notes
### Engine architecture
<mark>TODO</mark>
#### Monads/Monad stack
<mark>TODO</mark>
### Tests
<mark>TODO</mark>
### Assets & dependencies ### Assets & dependencies
The following assets were used (and modified if specified): The following assets were used (and modified if specified):
@ -268,6 +280,7 @@ The following assets were used (and modified if specified):
RPG-Engine makes use of the following libraries: RPG-Engine makes use of the following libraries:
- [directory](https://hackage.haskell.org/package/directory) for listing levels in a directory
- [gloss](https://hackage.haskell.org/package/gloss) for game rendering - [gloss](https://hackage.haskell.org/package/gloss) for game rendering
- [gloss-juicy](https://hackage.haskell.org/package/gloss-juicy) for rendering images - [gloss-juicy](https://hackage.haskell.org/package/gloss-juicy) for rendering images
- [hspec](https://hackage.haskell.org/package/hspec) for testing - [hspec](https://hackage.haskell.org/package/hspec) for testing
@ -278,12 +291,23 @@ RPG-Engine makes use of the following libraries:
The following ideas could (or should) be implemented in the future of this project. The following ideas could (or should) be implemented in the future of this project.
- [ ] Entity system: With en ES, you can implement moving entities and repeated input. It also resembles the typical - [ ] **Entity system:** With en ES, you can implement moving entities and repeated input. It also resembles the typical
game loop more closely which can make it easier to implement other ideas in the future. game loop more closely which can make it easier to implement other ideas in the future.
- [ ] **Game music:** Ambient game music and sound effects can improve the gaming experience I think.
- [ ] **Expand configuration file:** Implement the same methods for parsing stage description files to a configuration file,
containing keybinds, dimension sizes, even window titles, making this a truly customizable engine.
- [ ] **Camera follows player:** The camera should follow the player, making it always center. This allows for larger levels
increases the immersion of the game.
- [ ] Game music: Ambient game music and sound effects can improve the gaming experience I think. <div style="page-break-after: always; visibility: hidden">\pagebreak</div>
\pagebreak ## Conclusion
Parsing was way harder than I initially expected. About half of my time on this project was spent writing the parser.
<mark>TODO</mark>
<div style="page-break-after: always; visibility: hidden">\pagebreak</div>
## References ## References

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

View file

Before

Width:  |  Height:  |  Size: 870 B

After

Width:  |  Height:  |  Size: 870 B

Before After
Before After

View file

@ -19,8 +19,8 @@ levels: [
items: [ items: [
{ {
id: "key", id: "key",
x: 1, x: 0,
y: 2, y: 1,
name: "Sleutel", name: "Sleutel",
description: "Deze sleutel kan een deur openen", description: "Deze sleutel kan een deur openen",
useTimes: 1, useTimes: 1,
@ -35,8 +35,8 @@ levels: [
entities: [ entities: [
{ {
id: "door", id: "door",
x: 1, x: 0,
y: 4, y: 3,
name: "Deur", name: "Deur",
description: "Deze deur kan geopend worden met een sleutel", description: "Deze deur kan geopend worden met een sleutel",
direction: up, direction: up,

View file

@ -69,8 +69,8 @@ levels: [
actions: { actions: {
[inventoryContains(potion)] increasePlayerHp(potion), [inventoryContains(potion)] increasePlayerHp(potion),
[inventoryContains(sword)] decreaseHp(m1, sword), [inventoryContains(sword)] decreaseHp(devil, sword),
[] decreaseHp(m1, dagger), [] decreaseHp(devil, dagger),
[] leave() [] leave()
} }
} }

134
levels/level4.txt Normal file
View file

@ -0,0 +1,134 @@
player: {
hp: 50,
inventory: [
{
id: "dagger",
x: 0,
y: 0,
name: "Dolk",
description: "Basis schade tegen monsters",
useTimes: infinite,
value: 10,
actions: {}
}
]
}
levels: [
{
layout: {
| * * * * * *
| * s . . e *
| * * * * * *
},
items: [],
entities: []
},
{
layout: {
| * * *
| * e *
| * . *
| * . *
| * . *
| * . *
| * s *
| * * *
},
items: [
{
id: "key",
x: 0,
y: 1,
name: "Sleutel",
description: "Deze sleutel kan een deur openen",
useTimes: 1,
value: 0,
actions: {
[not(inventoryFull())] retrieveItem(key),
[] leave()
}
}
],
entities: [
{
id: "door",
x: 0,
y: 3,
name: "Deur",
description: "Deze deur kan geopend worden met een sleutel",
direction: up,
actions: {
[inventoryContains(key)] useItem(key),
[] leave()
}
}
]
},
{
layout: {
| * * * * * * * *
| * . . . . . . *
| * s . . . . . *
| * . . . . . e *
| * . . . . . . *
| * * * * * * * *
},
items: [
{
id: "sword",
x: 2,
y: 3,
name: "Zwaard",
description: "Meer schade tegen monsters",
useTimes: infinite,
value: 25,
actions: {
[not(inventoryFull())] retrieveItem(sword),
[] leave()
}
},
{
id: "potion",
x: 3,
y: 1,
name: "Levensbrouwsel",
description: "Geeft een aantal levenspunten terug",
useTimes: 1,
value: 50,
actions: {
[not(inventoryFull())] retrieveItem(potion),
[] leave()
}
}
],
entities: [
{
id: "devil",
x: 4,
y: 3,
name: "Duivel",
description: "Een monster uit de hel",
hp: 50,
value: 5,
actions: {
[inventoryContains(potion)] increasePlayerHp(potion),
[inventoryContains(sword)] decreaseHp(devil, sword),
[] decreaseHp(devil, dagger),
[] leave()
}
}
]
}
]

View file

@ -0,0 +1,138 @@
# Dit gehele bestand is een Block
# Dit is een entry: key + value
player: { # Value is hier een block
hp: 50,
inventory: [ # BlockList
{
id: "dagger",
x: 0,
y: 0,
name: "Dolk",
description: "Basis schade tegen monsters",
useTimes: infinite,
value: 10,
actions: {}
}
]
}
# Dit is een entry
levels: [
{
layout: {
| * * * * * *
| * s . . e *
| * * * * * *
},
items: [],
entities: []
},
{
layout: {
| * * *
| * e *
| * . *
| * . *
| * . *
| * . *
| * s *
| * * *
},
items: [
{
id: "key",
x: 1,
y: 2,
name: "Sleutel",
description: "Deze sleutel kan een deur openen",
useTimes: 1,
value: 0,
actions: {
[not(inventoryFull())] retrieveItem(key),
[] leave()
}
}
],
entities: [
{
id: "door",
x: 1,
y: 4,
name: "Deur",
description: "Deze deur kan geopend worden met een sleutel",
direction: up,
actions: {
[inventoryContains(key)] useItem(key),
[] leave()
}
}
]
},
{
layout: {
| * * * * * * * *
| * . . . . . . *
| * s . . . . . *
| * . . . . . e *
| * . . . . . . *
| * * * * * * * *
},
items: [
{
id: "sword",
x: 2,
y: 3,
name: "Zwaard",
description: "Meer schade tegen monsters",
useTimes: infinite,
value: 25,
actions: {
[not(inventoryFull())] retrieveItem(sword),
[] leave()
}
},
{
id: "potion",
x: 3,
y: 1,
name: "Levensbrouwsel",
description: "Geeft een aantal levenspunten terug",
useTimes: 1,
value: 50,
actions: {
[not(inventoryFull())] retrieveItem(potion),
[] leave()
}
}
],
entities: [
{
id: "devil",
x: 4,
y: 3,
name: "Duivel",
description: "Een monster uit de hel",
hp: 50,
value: 5,
actions: {
[inventoryContains(potion)] increasePlayerHp(potion),
[inventoryContains(sword)] decreaseHp(m1, sword),
[] decreaseHp(m1, dagger),
[] leave()
}
}
]
}
]

View file

@ -48,6 +48,7 @@ instance Living Player where
-- Current state of the game. -- Current state of the game.
data State = Menu data State = Menu
| LvlSelect
| Playing | Playing
| Pause | Pause
| Win | Win

View file

@ -14,7 +14,7 @@ import RPGEngine.Data
-- Get the next state based on the current state -- Get the next state based on the current state
nextState :: State -> State nextState :: State -> State
nextState Menu = Playing nextState Menu = LvlSelect
nextState Playing = Pause nextState Playing = Pause
nextState Pause = Playing nextState Pause = Playing
nextState _ = Menu nextState _ = Menu

View file

@ -15,8 +15,9 @@ import Graphics.Gloss.Interface.IO.Game
-- Handle all input for RPG-Engine -- Handle all input for RPG-Engine
handleAllInput :: InputHandler Game handleAllInput :: InputHandler Game
handleAllInput ev g@Game{ state = Playing } = handlePlayInputs ev g handleAllInput ev g@Game{ state = Playing } = handlePlayInputs ev g
handleAllInput ev g = handleAnyKey setNextState ev g handleAllInput ev g@Game{ state = LvlSelect } = handleLvlSelectInput ev g
handleAllInput ev g = handleAnyKey setNextState ev g
---------------------------------------------------------------------- ----------------------------------------------------------------------
@ -38,6 +39,10 @@ handlePlayInputs = composeInputHandlers [
handleKey (Char 'a') $ movePlayer West handleKey (Char 'a') $ movePlayer West
] ]
-- Input for selection a level to load
handleLvlSelectInput :: InputHandler Game
handleLvlSelectInput = composeInputHandlers []
-- Go to the next stage of the Game -- Go to the next stage of the Game
setNextState :: Game -> Game setNextState :: Game -> Game
setNextState game = game{ state = newState } setNextState game = game{ state = newState }

View file

@ -0,0 +1,12 @@
module RPGEngine.Input.LvlSelect
( getLvlList
) where
import GHC.IO (unsafePerformIO)
import System.Directory (getDirectoryContents)
lvlFolder :: FilePath
lvlFolder = "levels"
getLvlList :: [FilePath]
getLvlList = unsafePerformIO $ getDirectoryContents lvlFolder

View file

@ -9,14 +9,24 @@ module RPGEngine.Render
import RPGEngine.Data import RPGEngine.Data
( State(..), ( State(..),
Game(..) ) Game(..), Player (..) )
import RPGEngine.Render.Level import RPGEngine.Render.Level
( renderLevel ) ( renderLevel )
import Graphics.Gloss import Graphics.Gloss
( white, pictures, text, Display(InWindow), Color, Picture ) ( white,
import RPGEngine.Render.Player (renderPlayer) pictures,
text,
Display(InWindow),
Color,
Picture,
scale,
translate )
import RPGEngine.Render.Player (renderPlayer, focusPlayer)
import RPGEngine.Render.GUI (renderGUI) import RPGEngine.Render.GUI (renderGUI)
import Graphics.Gloss.Data.Picture (color)
import RPGEngine.Render.Core (overlay)
import RPGEngine.Input.LvlSelect (getLvlList)
import RPGEngine.Render.LvlSelect (renderLvlList)
----------------------------- Constants ------------------------------ ----------------------------- Constants ------------------------------
@ -32,11 +42,12 @@ initWindow = InWindow
-- Render the game -- Render the game
render :: Game -> Picture render :: Game -> Picture
render g@Game{ state = Menu } = renderMenu g render g@Game{ state = Menu } = renderMenu g
render g@Game{ state = Playing } = renderPlaying g render g@Game{ state = LvlSelect } = renderLevelSelection g
render g@Game{ state = Pause } = renderPause g render g@Game{ state = Playing } = renderPlaying g
render g@Game{ state = Win } = renderWin g render g@Game{ state = Pause } = renderPause g
render g@Game{ state = Lose } = renderLose g render g@Game{ state = Win } = renderWin g
render g@Game{ state = Lose } = renderLose g
---------------------------------------------------------------------- ----------------------------------------------------------------------
@ -44,6 +55,10 @@ render g@Game{ state = Lose } = renderLose g
renderMenu :: Game -> Picture renderMenu :: Game -> Picture
renderMenu _ = text "[Press any key to start]" renderMenu _ = text "[Press any key to start]"
-- TODO
renderLevelSelection :: Game -> Picture
renderLevelSelection _ = renderLvlList getLvlList
renderPlaying :: Game -> Picture renderPlaying :: Game -> Picture
renderPlaying g@Game{ playing = lvl, player = player } = pictures [ renderPlaying g@Game{ playing = lvl, player = player } = pictures [
renderLevel lvl, renderLevel lvl,
@ -51,9 +66,12 @@ renderPlaying g@Game{ playing = lvl, player = player } = pictures [
renderGUI g renderGUI g
] ]
-- TODO
renderPause :: Game -> Picture renderPause :: Game -> Picture
renderPause _ = text "[Press any key to continue]" renderPause g = pictures [renderPlaying g, pause]
where pause = pictures [
overlay,
color white $ scale 0.5 0.5 $ text "[Press any key to continue]"
]
-- TODO -- TODO
renderWin :: Game -> Picture renderWin :: Game -> Picture

View file

@ -1,6 +1,6 @@
module RPGEngine.Render.Core where module RPGEngine.Render.Core where
import Graphics.Gloss ( Picture, translate ) import Graphics.Gloss ( Picture, translate, pictures )
import GHC.IO (unsafePerformIO) import GHC.IO (unsafePerformIO)
import Graphics.Gloss.Juicy (loadJuicyPNG) import Graphics.Gloss.Juicy (loadJuicyPNG)
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
@ -21,7 +21,7 @@ assetsFolder :: FilePath
assetsFolder = "assets/" assetsFolder = "assets/"
unknownImage :: FilePath unknownImage :: FilePath
unknownImage = "unkown.png" unknownImage = "unknown.png"
allEntities :: [(String, FilePath)] allEntities :: [(String, FilePath)]
allEntities = [ allEntities = [
@ -32,6 +32,7 @@ allEntities = [
allEnvironment :: [(String, FilePath)] allEnvironment :: [(String, FilePath)]
allEnvironment = [ allEnvironment = [
("void", "void.png"), ("void", "void.png"),
("overlay", "overlay.png"),
("tile", "tile.png"), ("tile", "tile.png"),
("wall", "wall.png"), ("wall", "wall.png"),
("entrance", "entrance.png"), ("entrance", "entrance.png"),
@ -47,7 +48,7 @@ allItems = [
-- Map of all renders -- Map of all renders
library :: [(String, Picture)] library :: [(String, Picture)]
library = unknown:entities ++ environment ++ gui ++ items library = unknown:entities ++ environment ++ gui ++ items
where unknown = ("unkown", renderPNG (assetsFolder ++ unknownImage)) where unknown = ("unknown", renderPNG (assetsFolder ++ unknownImage))
entities = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "entities/" ++ s))) allEntities entities = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "entities/" ++ s))) allEntities
environment = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "environment/" ++ s))) allEnvironment environment = map (\(f, s) -> (f, renderPNG (assetsFolder ++ "environment/" ++ s))) allEnvironment
gui = [] gui = []
@ -71,4 +72,14 @@ getRender id = get filtered
setRenderPos :: Int -> Int -> Picture -> Picture setRenderPos :: Int -> Int -> Picture -> Picture
setRenderPos x y = translate floatX floatY setRenderPos x y = translate floatX floatY
where floatX = fromIntegral x * zoom * resolution where floatX = fromIntegral x * zoom * resolution
floatY = fromIntegral y * zoom * resolution floatY = fromIntegral y * zoom * resolution
overlay :: Picture
overlay = setRenderPos offX offY $ pictures voids
where voids = [setRenderPos x y void | x <- [0 .. width], y <- [0 .. height]]
void = getRender "overlay"
intZoom = round zoom :: Int
height = round $ 4320 / resolution / zoom
width = round $ 7680 / resolution / zoom
offX = negate (width `div` 2)
offY = negate (height `div` 2)

View file

@ -0,0 +1,15 @@
module RPGEngine.Render.LvlSelect
( renderLvlList
) where
import Graphics.Gloss ( Picture, pictures, translate, scale )
import Graphics.Gloss.Data.Picture (blank, text)
import RPGEngine.Render.Core (resolution, zoom)
-- Render all level names, under each other.
renderLvlList :: [FilePath] -> Picture
renderLvlList list = pictures $ map render entries
where entries = zip [0::Int .. ] list
render (i, path) = scale zoomed zoomed $ translate 0 (offset i) $ text path
zoomed = 0.1 * zoom
offset i = negate (2 * resolution * zoom * fromIntegral i)

View file

@ -1,11 +1,17 @@
module RPGEngine.Render.Player module RPGEngine.Render.Player
( renderPlayer ( renderPlayer
, focusPlayer
) where ) where
import RPGEngine.Data (Player(..)) import RPGEngine.Data (Player(..), Game(..))
import Graphics.Gloss (Picture, text) import Graphics.Gloss (Picture, text)
import RPGEngine.Render.Core (getRender, setRenderPos) import RPGEngine.Render.Core (getRender, setRenderPos, zoom, resolution)
import Graphics.Gloss.Data.Picture (translate)
renderPlayer :: Player -> Picture renderPlayer :: Player -> Picture
renderPlayer Player{ position = (x, y) } = setRenderPos x y $ getRender "player" renderPlayer Player{ position = (x, y) } = setRenderPos x y $ getRender "player"
focusPlayer :: Game -> Picture -> Picture
focusPlayer Game{ player = Player{ position = (x, y)}} = translate centerX centerY
where centerX = resolution * zoom * fromIntegral (negate x)
centerY = resolution * zoom * fromIntegral (negate y)

View file

@ -8,6 +8,7 @@ library
hs-source-dirs: lib hs-source-dirs: lib
build-depends: build-depends:
base >= 4.7 && <5, base >= 4.7 && <5,
directory >= 1.3.6.0,
gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3, gloss >= 1.11 && < 1.14, gloss-juicy >= 0.2.3,
parsec >= 3.1.15.1 parsec >= 3.1.15.1
exposed-modules: exposed-modules:
@ -20,6 +21,7 @@ library
RPGEngine.Input RPGEngine.Input
RPGEngine.Input.Core RPGEngine.Input.Core
RPGEngine.Input.Level RPGEngine.Input.Level
RPGEngine.Input.LvlSelect
RPGEngine.Input.Player RPGEngine.Input.Player
RPGEngine.Parse RPGEngine.Parse
@ -31,6 +33,7 @@ library
RPGEngine.Render.Core RPGEngine.Render.Core
RPGEngine.Render.GUI RPGEngine.Render.GUI
RPGEngine.Render.Level RPGEngine.Render.Level
RPGEngine.Render.LvlSelect
RPGEngine.Render.Player RPGEngine.Render.Player
executable rpg-engine executable rpg-engine

Binary file not shown.