add "slimed" example
This commit is contained in:
parent
3ac0adea69
commit
1ab1d8e0ad
12
examples/slimed/Cargo.toml
Normal file
12
examples/slimed/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "slimed"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.55"
|
||||
libretrogd = { path = "../../libretrogd", features = [] }
|
||||
sdl2 = { version = "0.34.5", features = ["static-link", "bundled", "unsafe_textures" ] }
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.79"
|
||||
|
9
examples/slimed/assets/arena.map.json
Normal file
9
examples/slimed/assets/arena.map.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"width":40,
|
||||
"height":30,
|
||||
"layers":[
|
||||
[96,96,96,96,96,96,96,96,96,96,96,96,16,17,16,16,16,16,16,17,17,32,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,33,16,16,96,96,96,96,96,96,96,96,96,96,96,16,17,17,17,32,17,32,16,16,32,16,16,32,16,16,16,16,16,16,32,16,33,16,16,16,16,16,16,16,96,96,96,96,96,96,96,96,96,96,181,178,178,178,178,183,32,16,17,181,178,178,178,178,178,178,178,178,178,178,178,183,16,16,16,16,16,16,16,32,96,96,96,96,96,96,96,96,96,181,195,16,32,17,17,193,178,178,178,195,16,16,16,16,16,16,32,16,16,16,16,193,178,183,16,32,16,16,16,16,96,96,96,96,96,96,96,181,178,195,16,16,16,32,17,17,17,17,32,16,16,16,33,16,16,16,16,16,16,16,16,16,16,193,183,16,16,16,33,16,96,96,96,96,96,96,181,195,32,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,193,183,16,16,16,16,16,17,16,16,96,181,195,16,16,33,16,16,16,16,16,32,16,16,16,16,16,32,16,16,16,16,48,48,48,48,16,16,16,16,16,196,16,16,16,16,32,16,17,16,181,195,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,16,16,32,193,183,16,32,16,8,8,8,181,195,16,16,16,16,17,32,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,16,16,16,196,16,16,16,7,7,7,196,16,32,16,32,17,17,17,16,16,16,16,33,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,48,48,16,16,193,183,16,32,7,7,181,195,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,32,16,16,16,48,48,48,48,48,48,48,48,48,48,48,48,32,16,196,32,16,7,7,196,16,16,32,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,16,16,16,196,16,16,7,7,196,8,8,16,16,16,16,16,16,32,16,16,16,16,32,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,16,16,16,16,196,17,16,7,7,196,7,7,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,32,16,16,48,48,48,48,48,16,16,16,16,16,196,17,16,7,7,196,7,7,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,17,16,16,16,196,16,33,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,16,16,196,16,16,16,16,16,32,16,16,16,16,32,16,16,16,32,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,196,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,33,16,16,16,16,16,32,16,196,16,33,32,16,196,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,16,16,196,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,196,16,16,16,16,193,183,16,16,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,32,16,33,196,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,16,16,32,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,32,16,16,196,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,196,16,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,181,195,16,16,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,16,16,16,16,16,193,178,178,183,48,48,48,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,32,16,16,16,16,16,16,181,195,16,16,16,32,16,16,33,16,32,16,16,16,193,178,178,178,178,178,178,178,178,183,16,32,16,16,16,16,16,33,16,16,16,16,16,16,181,178,195,32,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,33,32,17,193,178,178,178,178,178,178,178,178,178,178,178,178,178,178,195,16,16,16,16,16,48,16,16,32,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,32,16,16,32,16,16,16,32,16,16,16,33,16,16,32,16,16,16,16,16,48,48],
|
||||
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,87,-1,104,108,108,108,105,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,98,105,-1,-1,-1,26,-1,26,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,80,108,108,108,97,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,46,46,46,102,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,-1,-1,27,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,51,-1,-1,26,-1,-1,-1,-1,-1,-1,27,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,-1,-1,52,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,67,-1,-1,27,279,-1,-1,294,-1,-1,-1,-1,-1,-1,-1,-1,-1,83,-1,-1,-1,46,46,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,65,67,-1,-1,-1,-1,-1,-1,26,-1,-1,266,68,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,29,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,65,67,-1,-1,-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,29,-1,27,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,65,67,-1,-1,-1,-1,-1,68,66,-1,-1,26,-1,-1,86,-1,-1,-1,-1,-1,29,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,14,14,-1,14,14,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,-1,282,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,104,-1,282,-1,-1,-1,259,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,281,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,-1,52,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,278,-1,-1,266,27,-1,278,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,27,-1,50,-1,-1,-1,-1,-1,-1,-1,-1,294,-1,-1,294,-1,27,279,-1,-1,294,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,278,-1,-1,278,-1,-1,80,-1,-1,-1,-1,26,-1,294,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,266,52,-1,-1,-1,27,279,-1,-1,294,-1,-1,294,-1,-1,294,-1,266,-1,-1,-1,26,-1,-1,-1,-1,-1,260,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,26,-1,-1,-1,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,27,-1,49,-1,278,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,49,51,-1,294,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,49,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,27,-1],
|
||||
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,-1,0,1,1,0,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,-1,-1,-1,-1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,0,0,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
|
||||
]
|
||||
}
|
BIN
examples/slimed/assets/blue_slime.pcx
Normal file
BIN
examples/slimed/assets/blue_slime.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/db16.pal
Normal file
BIN
examples/slimed/assets/db16.pal
Normal file
Binary file not shown.
BIN
examples/slimed/assets/dp.fnt
Normal file
BIN
examples/slimed/assets/dp.fnt
Normal file
Binary file not shown.
BIN
examples/slimed/assets/fist.pcx
Normal file
BIN
examples/slimed/assets/fist.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/green_slime.pcx
Normal file
BIN
examples/slimed/assets/green_slime.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/hero_female.pcx
Normal file
BIN
examples/slimed/assets/hero_female.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/hero_male.pcx
Normal file
BIN
examples/slimed/assets/hero_male.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/items.pcx
Normal file
BIN
examples/slimed/assets/items.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/orange_slime.pcx
Normal file
BIN
examples/slimed/assets/orange_slime.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/particles.pcx
Normal file
BIN
examples/slimed/assets/particles.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/sword.pcx
Normal file
BIN
examples/slimed/assets/sword.pcx
Normal file
Binary file not shown.
BIN
examples/slimed/assets/tiles.pcx
Normal file
BIN
examples/slimed/assets/tiles.pcx
Normal file
Binary file not shown.
9
examples/slimed/assets/title_screen.map.json
Normal file
9
examples/slimed/assets/title_screen.map.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"width":20,
|
||||
"height":15,
|
||||
"layers":[
|
||||
[48,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,16,16,16,16,48,48,48,48,16,16,32,16,16,16,16,16,16,16,16,33,16,16,16,32,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,32,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,32,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,32,16,16,33,16,16,16,16,16,16,33,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,32,16,16,16,16,16,16,16,32,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,32,33,16,16,16,16,16,33,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,48,32,16,16,16,16,16,16,33,16,16,32,16,16,16,16,16,16,32,48,48,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48],
|
||||
[-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,49,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1],
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
|
||||
]
|
||||
}
|
BIN
examples/slimed/assets/ui.pcx
Normal file
BIN
examples/slimed/assets/ui.pcx
Normal file
Binary file not shown.
93
examples/slimed/src/entities/events.rs
Normal file
93
examples/slimed/src/entities/events.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use libretrogd::entities::*;
|
||||
use libretrogd::events::*;
|
||||
|
||||
use crate::Core;
|
||||
use crate::entities::*;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Event {
|
||||
TurnAndMove(EntityId, Direction),
|
||||
MoveForward(EntityId),
|
||||
Remove(EntityId),
|
||||
RemoveAttachment(EntityId),
|
||||
AnimationFinished(EntityId),
|
||||
Spawn(EntityId),
|
||||
SpawnSlimeRandomly,
|
||||
SetActivity(EntityId, EntityActivity),
|
||||
Attack(EntityId),
|
||||
Hit(EntityId, EntityId, i32, Vector2),
|
||||
Kill(EntityId),
|
||||
Pickup(EntityId, EntityId),
|
||||
}
|
||||
|
||||
fn event_handler(event: &Event, context: &mut Core) -> bool {
|
||||
match event {
|
||||
Event::Remove(entity) => {
|
||||
if context.entities.has_entity(*entity) {
|
||||
remove_entity(&mut context.entities, *entity);
|
||||
}
|
||||
},
|
||||
Event::RemoveAttachment(entity) => {
|
||||
if context.entities.has_entity(*entity) {
|
||||
remove_entity_attachment(&mut context.entities, *entity);
|
||||
}
|
||||
}
|
||||
Event::TurnAndMove(entity, direction) => {
|
||||
if context.entities.has_entity(*entity) {
|
||||
turn_and_move_entity(context, *entity, *direction);
|
||||
}
|
||||
},
|
||||
Event::MoveForward(entity) => {
|
||||
if context.entities.has_entity(*entity) {
|
||||
move_entity_forward(context, *entity);
|
||||
}
|
||||
},
|
||||
Event::Spawn(entity) => {
|
||||
// todo
|
||||
},
|
||||
Event::AnimationFinished(entity) => {
|
||||
if context.entities.has_entity(*entity) {
|
||||
// if the entity's 'attack' animation just finished, move them back to 'idle'
|
||||
let activities = context.entities.components::<Activity>();
|
||||
if let Some(activity) = activities.get(entity) {
|
||||
if activity.0 == EntityActivity::Attacking {
|
||||
drop(activities);
|
||||
stop_attack(context, *entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::SpawnSlimeRandomly => {
|
||||
spawn_slime_randomly(context);
|
||||
},
|
||||
Event::SetActivity(entity, activity) => {
|
||||
if context.entities.has_entity(*entity) {
|
||||
set_entity_activity(&mut context.entities, *entity, *activity);
|
||||
}
|
||||
},
|
||||
Event::Attack(entity) => {
|
||||
if context.entities.has_entity(*entity) {
|
||||
attack(context, *entity);
|
||||
}
|
||||
},
|
||||
Event::Hit(target, source, damage, damage_position) => {
|
||||
if context.entities.has_entity(*target) {
|
||||
hit_entity(context, *target, *source, *damage, *damage_position);
|
||||
}
|
||||
},
|
||||
Event::Kill(entity) => {
|
||||
kill_entity(context, *entity);
|
||||
},
|
||||
Event::Pickup(picked_up_by, picked_up) => {
|
||||
if context.entities.has_entity(*picked_up_by) && context.entities.has_entity(*picked_up) {
|
||||
pickup(context, *picked_up_by, *picked_up);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn init_events(event_listener: &mut EventListeners<Event, Core>) {
|
||||
event_listener.clear();
|
||||
event_listener.add(event_handler);
|
||||
}
|
670
examples/slimed/src/entities/mod.rs
Normal file
670
examples/slimed/src/entities/mod.rs
Normal file
|
@ -0,0 +1,670 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use libretrogd::entities::*;
|
||||
use libretrogd::graphics::*;
|
||||
use libretrogd::math::*;
|
||||
use libretrogd::utils::rnd_value;
|
||||
|
||||
use crate::{Core, Game, TILE_HEIGHT, TILE_WIDTH, TileMap};
|
||||
|
||||
pub use self::events::*;
|
||||
pub use self::systems::*;
|
||||
|
||||
pub mod events;
|
||||
pub mod systems;
|
||||
|
||||
pub const FRICTION: f32 = 0.5;
|
||||
|
||||
pub const DEFAULT_PUSH_STRENGTH: f32 = 0.5;
|
||||
pub const DEFAULT_PUSH_DISSIPATION: f32 = 0.5;
|
||||
|
||||
pub const HIT_KNOCKBACK_STRENGTH: f32 = 8.0;
|
||||
pub const HIT_KNOCKBACK_DISSIPATION: f32 = 0.5;
|
||||
|
||||
pub const PICKUP_PRE_TIMER: f32 = 0.5;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum EntityActivity {
|
||||
Idle,
|
||||
Walking,
|
||||
Attacking,
|
||||
Dead,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Direction {
|
||||
South = 0,
|
||||
West = 1,
|
||||
East = 2,
|
||||
North = 3,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
pub fn new_random() -> Self {
|
||||
use Direction::*;
|
||||
match rnd_value(0, 3) {
|
||||
0 => South,
|
||||
1 => West,
|
||||
2 => East,
|
||||
3 => North,
|
||||
_ => panic!("unknown random direction!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum SlimeColor {
|
||||
Green = 0,
|
||||
Blue = 1,
|
||||
Orange = 2,
|
||||
}
|
||||
|
||||
impl SlimeColor {
|
||||
pub fn new_random() -> Self {
|
||||
use SlimeColor::*;
|
||||
match rnd_value(0, 2) {
|
||||
0 => Green,
|
||||
1 => Blue,
|
||||
2 => Orange,
|
||||
_ => panic!("unknown random slime color!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum PickupType {
|
||||
GreenGem,
|
||||
BlueGem,
|
||||
OrangeGem,
|
||||
Coin,
|
||||
}
|
||||
|
||||
impl PickupType {
|
||||
pub fn new_random() -> Self {
|
||||
use PickupType::*;
|
||||
match rnd_value(0, 3) {
|
||||
0 => GreenGem,
|
||||
1 => BlueGem,
|
||||
2 => OrangeGem,
|
||||
3 => Coin,
|
||||
_ => panic!("unknown random pickup type!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct Force {
|
||||
pub force: Vector2,
|
||||
pub dissipation_factor: f32,
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub struct Player;
|
||||
|
||||
pub struct Slime;
|
||||
|
||||
pub struct Activity(pub EntityActivity);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnimationDef {
|
||||
pub frames: &'static [usize],
|
||||
pub loops: bool,
|
||||
pub delay: f32,
|
||||
pub multi_direction_offset: Option<usize>,
|
||||
}
|
||||
|
||||
impl AnimationDef {
|
||||
#[inline]
|
||||
pub fn new(frames: &'static [usize], loops: bool, delay: f32, multi_direction_offset: Option<usize>) -> Self {
|
||||
AnimationDef {
|
||||
frames,
|
||||
loops,
|
||||
delay,
|
||||
multi_direction_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnimationInstance {
|
||||
pub def: Rc<AnimationDef>,
|
||||
pub frame_index: usize,
|
||||
pub frame_timer: f32,
|
||||
pub complete: bool,
|
||||
pub delay_override: Option<f32>,
|
||||
}
|
||||
|
||||
impl AnimationInstance {
|
||||
#[inline]
|
||||
pub fn from(def: Rc<AnimationDef>) -> Self {
|
||||
AnimationInstance {
|
||||
def,
|
||||
frame_index: 0,
|
||||
frame_timer: 0.0,
|
||||
complete: false,
|
||||
delay_override: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn change_to(&mut self, def: Rc<AnimationDef>) {
|
||||
self.def = def;
|
||||
self.reset();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
self.frame_index = 0;
|
||||
self.frame_timer = 0.0;
|
||||
self.complete = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnimateByActivity(pub Rc<HashMap<EntityActivity, Rc<AnimationDef>>>);
|
||||
|
||||
pub struct KillWhenAnimationFinishes;
|
||||
|
||||
pub struct Position(pub Vector2);
|
||||
|
||||
pub struct Velocity(pub Vector2);
|
||||
|
||||
pub struct Forces {
|
||||
pub forces: Vec<Force>,
|
||||
}
|
||||
|
||||
impl Forces {
|
||||
pub fn new() -> Self {
|
||||
Forces {
|
||||
forces: Vec::with_capacity(5),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_force(&self) -> Vector2 {
|
||||
let mut total_force = Vector2::ZERO;
|
||||
for force in self.forces.iter() {
|
||||
total_force += force.force;
|
||||
}
|
||||
total_force
|
||||
}
|
||||
|
||||
pub fn add(&mut self, force: Vector2, dissipation_factor: f32) {
|
||||
self.forces.push(Force { force, dissipation_factor });
|
||||
}
|
||||
|
||||
pub fn decay(&mut self) {
|
||||
for force in self.forces.iter_mut() {
|
||||
force.force *= force.dissipation_factor;
|
||||
}
|
||||
self.forces.retain(|f| !f.force.almost_zero(0.001));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bounds {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub radius: u32,
|
||||
}
|
||||
|
||||
pub struct FacingDirection(pub Direction);
|
||||
|
||||
pub struct IgnoresCollision;
|
||||
|
||||
pub struct IgnoresFriction;
|
||||
|
||||
pub struct Particle;
|
||||
|
||||
pub struct LifeTime(pub f32);
|
||||
|
||||
pub struct Pixel(pub u8);
|
||||
|
||||
pub struct Sprite {
|
||||
pub atlas: Rc<BitmapAtlas>,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
pub struct RandomlyWalksAround {
|
||||
pub min_walk_time: f32,
|
||||
pub max_walk_time: f32,
|
||||
pub chance_to_move: u32,
|
||||
pub min_cooldown: f32,
|
||||
pub max_cooldown: f32,
|
||||
pub cooldown_timer: f32,
|
||||
}
|
||||
|
||||
impl RandomlyWalksAround {
|
||||
pub fn new(min_walk_time: f32, max_walk_time: f32, chance_to_move: u32, min_cooldown: f32, max_cooldown: f32) -> Self {
|
||||
RandomlyWalksAround {
|
||||
min_walk_time,
|
||||
max_walk_time,
|
||||
chance_to_move,
|
||||
min_cooldown,
|
||||
max_cooldown,
|
||||
cooldown_timer: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_start_walking(&self) -> bool {
|
||||
rnd_value(0, 100) < self.chance_to_move
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WalkingTime(pub f32);
|
||||
|
||||
pub struct MovementSpeed(pub f32);
|
||||
|
||||
pub struct World;
|
||||
|
||||
pub struct SpawnTimer {
|
||||
pub timer: f32,
|
||||
pub min_time: f32,
|
||||
pub max_time: f32,
|
||||
pub max_allowed: usize,
|
||||
}
|
||||
|
||||
impl SpawnTimer {
|
||||
pub fn new(min_time: f32, max_time: f32, max_allowed: usize) -> Self {
|
||||
SpawnTimer {
|
||||
timer: 0.0,
|
||||
min_time,
|
||||
max_time,
|
||||
max_allowed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_timer(&mut self) {
|
||||
self.timer = rnd_value(self.min_time, self.max_time);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Camera {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
pub struct Pushable;
|
||||
|
||||
pub struct Pusher {
|
||||
pub strength: f32,
|
||||
pub push_force_dissipation: f32,
|
||||
}
|
||||
|
||||
impl Pusher {
|
||||
pub fn new() -> Self {
|
||||
Pusher {
|
||||
strength: DEFAULT_PUSH_STRENGTH,
|
||||
push_force_dissipation: DEFAULT_PUSH_DISSIPATION,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AttachedTo(pub EntityId);
|
||||
|
||||
pub struct Attachment(pub EntityId);
|
||||
|
||||
pub struct AttachmentOffset(pub Vector2);
|
||||
|
||||
pub struct AttachmentOffsetByDirection {
|
||||
pub offsets: [Vector2; 4],
|
||||
}
|
||||
|
||||
pub struct Attackable;
|
||||
|
||||
pub struct SpriteIndexByDirection {
|
||||
pub base_index: usize,
|
||||
}
|
||||
|
||||
pub struct Weapon {
|
||||
pub atlas: Rc<BitmapAtlas>,
|
||||
pub base_index: usize,
|
||||
pub offsets: [Vector2; 4],
|
||||
pub damage: i32,
|
||||
pub radius_of_effect: u32,
|
||||
}
|
||||
|
||||
pub struct Life(pub i32);
|
||||
|
||||
pub enum FlickerMethod {
|
||||
Color(u8),
|
||||
OnOff,
|
||||
}
|
||||
|
||||
pub struct TimedFlicker {
|
||||
pub method: FlickerMethod,
|
||||
pub flick: bool,
|
||||
pub pre_timer: Option<f32>,
|
||||
pub timer: f32,
|
||||
}
|
||||
|
||||
impl TimedFlicker {
|
||||
pub fn new(timer: f32, method: FlickerMethod) -> Self {
|
||||
TimedFlicker {
|
||||
timer,
|
||||
method,
|
||||
pre_timer: None,
|
||||
flick: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_pre_timer(timer: f32, pre_timer: f32, method: FlickerMethod) -> Self {
|
||||
TimedFlicker {
|
||||
timer,
|
||||
method,
|
||||
pre_timer: Some(pre_timer),
|
||||
flick: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, delta: f32) {
|
||||
if let Some(mut pre_timer) = self.pre_timer {
|
||||
pre_timer -= delta;
|
||||
if pre_timer <= 0.0 {
|
||||
self.pre_timer = None;
|
||||
} else {
|
||||
self.pre_timer = Some(pre_timer);
|
||||
}
|
||||
} else {
|
||||
self.timer -= delta;
|
||||
self.flick = !self.flick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HitParticleColor(pub u8);
|
||||
|
||||
pub struct Pickupable {
|
||||
pub kind: PickupType,
|
||||
pub pre_timer: f32,
|
||||
}
|
||||
|
||||
pub struct Pickuper;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub fn init_everything(context: &mut Game, map_file: &Path, min_spawn_time: f32, max_spawn_time: f32, max_slimes: usize) {
|
||||
init_entities(&mut context.core.entities);
|
||||
init_component_system(&mut context.component_systems);
|
||||
init_events(&mut context.event_listeners);
|
||||
context.core.event_publisher.clear();
|
||||
|
||||
context.core.tilemap = TileMap::load_from(map_file).unwrap();
|
||||
new_camera_entity(&mut context.core, 0, 0);
|
||||
new_world_entity(&mut context.core, min_spawn_time, max_spawn_time, max_slimes);
|
||||
}
|
||||
|
||||
pub fn init_entities(entities: &mut Entities) {
|
||||
entities.remove_all_entities();
|
||||
entities.init_components::<Weapon>();
|
||||
entities.init_components::<WalkingTime>();
|
||||
entities.init_components::<Pushable>();
|
||||
entities.init_components::<Pusher>();
|
||||
entities.init_components::<TimedFlicker>();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub fn new_world_entity(context: &mut Core, min_spawn_time: f32, max_spawn_time: f32, max_slimes: usize) -> EntityId {
|
||||
let id = context.entities.new_entity();
|
||||
|
||||
context.entities.add_component(id, World);
|
||||
|
||||
let mut spawn_timer = SpawnTimer::new(min_spawn_time, max_spawn_time, max_slimes);
|
||||
spawn_timer.reset_timer();
|
||||
context.entities.add_component(id, spawn_timer);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn new_camera_entity(context: &mut Core, x: i32, y: i32) -> EntityId {
|
||||
let id = context.entities.new_entity();
|
||||
context.entities.add_component(id, Camera { x, y });
|
||||
id
|
||||
}
|
||||
|
||||
pub fn new_slime_entity(context: &mut Core, x: i32, y: i32, direction: Direction, color: SlimeColor) -> EntityId {
|
||||
let id = context.entities.new_entity();
|
||||
|
||||
let (atlas, chance_to_move, movement_speed, min_walk_time, max_walk_time, min_walk_cooldown, max_walk_cooldown, life, hit_color) = match color {
|
||||
SlimeColor::Green => (context.green_slime.clone(), 10, 8.0, 0.5, 2.0, 0.5, 5.0, 1, 11),
|
||||
SlimeColor::Blue => (context.blue_slime.clone(), 40, 12.0, 0.5, 2.0, 0.5, 3.0, 2, 13),
|
||||
SlimeColor::Orange => (context.orange_slime.clone(), 90, 24.0, 0.5, 1.0, 0.5, 2.0, 3, 9),
|
||||
};
|
||||
|
||||
let activity = EntityActivity::Idle;
|
||||
let animate_by_activity = AnimateByActivity(context.slime_activity_states.clone());
|
||||
let animation = AnimationInstance::from(animate_by_activity.0.get(&activity).unwrap().clone());
|
||||
|
||||
context.entities.add_component(id, Slime);
|
||||
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
|
||||
context.entities.add_component(id, Velocity(Vector2::ZERO));
|
||||
context.entities.add_component(id, Forces::new());
|
||||
context.entities.add_component(id, Bounds { width: 16, height: 16, radius: 8 });
|
||||
context.entities.add_component(id, FacingDirection(direction));
|
||||
context.entities.add_component(id, Sprite { atlas, index: 0 });
|
||||
context.entities.add_component(id, Activity(activity));
|
||||
context.entities.add_component(id, animate_by_activity);
|
||||
context.entities.add_component(id, animation);
|
||||
context.entities.add_component(id, RandomlyWalksAround::new(min_walk_time, max_walk_time, chance_to_move, min_walk_cooldown, max_walk_cooldown));
|
||||
context.entities.add_component(id, MovementSpeed(movement_speed));
|
||||
context.entities.add_component(id, Pusher::new());
|
||||
context.entities.add_component(id, Pushable);
|
||||
context.entities.add_component(id, Attackable);
|
||||
context.entities.add_component(id, Life(life));
|
||||
context.entities.add_component(id, HitParticleColor(hit_color));
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn spawn_slime_randomly(context: &mut Core) -> EntityId {
|
||||
let (x, y) = context.tilemap.get_random_spawnable_coordinates();
|
||||
let id = new_slime_entity(context, x * TILE_WIDTH as i32, y * TILE_HEIGHT as i32, Direction::new_random(), SlimeColor::new_random());
|
||||
spawn_poof_cloud(context, x * TILE_WIDTH as i32, y * TILE_HEIGHT as i32, 4, 8);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn new_player_entity(context: &mut Core, x: i32, y: i32, direction: Direction) -> EntityId {
|
||||
let id = context.entities.new_entity();
|
||||
|
||||
let (atlas, weapon_offsets) = if rnd_value(0, 1) == 0 {
|
||||
(
|
||||
context.hero_female.clone(),
|
||||
[
|
||||
Vector2::new(-3.0, 13.0),
|
||||
Vector2::new(-14.0, 2.0),
|
||||
Vector2::new(14.0, 2.0),
|
||||
Vector2::new(3.0, -11.0)
|
||||
]
|
||||
)
|
||||
} else {
|
||||
(
|
||||
context.hero_male.clone(),
|
||||
[
|
||||
Vector2::new(-3.0, 13.0),
|
||||
Vector2::new(-13.0, 2.0),
|
||||
Vector2::new(13.0, 2.0),
|
||||
Vector2::new(3.0, -11.0)
|
||||
]
|
||||
)
|
||||
};
|
||||
|
||||
let activity = EntityActivity::Idle;
|
||||
let animate_by_activity = AnimateByActivity(context.hero_activity_states.clone());
|
||||
let animation = AnimationInstance::from(animate_by_activity.0.get(&activity).unwrap().clone());
|
||||
|
||||
let weapon = Weapon {
|
||||
atlas: context.sword.clone(),
|
||||
base_index: 0,
|
||||
offsets: weapon_offsets,
|
||||
damage: 1,
|
||||
radius_of_effect: 8,
|
||||
};
|
||||
|
||||
context.entities.add_component(id, Player);
|
||||
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
|
||||
context.entities.add_component(id, Velocity(Vector2::ZERO));
|
||||
context.entities.add_component(id, Forces::new());
|
||||
context.entities.add_component(id, Bounds { width: 14, height: 14, radius: 8 });
|
||||
context.entities.add_component(id, FacingDirection(direction));
|
||||
context.entities.add_component(id, Sprite { atlas, index: 0 });
|
||||
context.entities.add_component(id, Activity(activity));
|
||||
context.entities.add_component(id, animate_by_activity);
|
||||
context.entities.add_component(id, animation);
|
||||
context.entities.add_component(id, MovementSpeed(32.0));
|
||||
context.entities.add_component(id, Pusher::new());
|
||||
context.entities.add_component(id, Pushable);
|
||||
context.entities.add_component(id, weapon);
|
||||
context.entities.add_component(id, Pickuper);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn spawn_player_randomly(context: &mut Core) -> EntityId {
|
||||
let (x, y) = context.tilemap.get_random_spawnable_coordinates();
|
||||
new_player_entity(context, x * TILE_WIDTH as i32, y * TILE_HEIGHT as i32, Direction::South)
|
||||
}
|
||||
|
||||
fn new_animation_effect(context: &mut Core, x: i32, y: i32, animation_def: Rc<AnimationDef>, delay_scaling_factor: Option<f32>) -> EntityId {
|
||||
let id = context.entities.new_entity();
|
||||
context.entities.add_component(id, Particle);
|
||||
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
|
||||
context.entities.add_component(id, Sprite { atlas: context.particles.clone(), index: 0 });
|
||||
context.entities.add_component(id, KillWhenAnimationFinishes);
|
||||
context.entities.add_component(id, IgnoresCollision);
|
||||
context.entities.add_component(id, IgnoresFriction);
|
||||
|
||||
let mut animation = AnimationInstance::from(animation_def);
|
||||
if let Some(delay_scaling_factor) = delay_scaling_factor {
|
||||
animation.delay_override = Some(animation.def.delay * delay_scaling_factor);
|
||||
}
|
||||
context.entities.add_component(id, animation);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn new_poof_animation(context: &mut Core, x: i32, y: i32, variant: usize, delay_scaling_factor: Option<f32>) -> EntityId {
|
||||
let def = match variant {
|
||||
0 => context.poof1_animation_def.clone(),
|
||||
1 => context.poof2_animation_def.clone(),
|
||||
_ => panic!("unknown poof animation variant")
|
||||
};
|
||||
new_animation_effect(context, x, y, def, delay_scaling_factor)
|
||||
}
|
||||
|
||||
pub fn new_sparkles_animation(context: &mut Core, x: i32, y: i32, delay_scaling_factor: Option<f32>) -> EntityId {
|
||||
new_animation_effect(context, x, y, context.sparkles_animation_def.clone(), delay_scaling_factor)
|
||||
}
|
||||
|
||||
pub fn new_pixel_particle(context: &mut Core, x: i32, y: i32, velocity: Vector2, lifetime: f32, color: u8) -> EntityId {
|
||||
let id = context.entities.new_entity();
|
||||
context.entities.add_component(id, Particle);
|
||||
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
|
||||
context.entities.add_component(id, Velocity(velocity));
|
||||
context.entities.add_component(id, Pixel(color));
|
||||
context.entities.add_component(id, LifeTime(lifetime));
|
||||
context.entities.add_component(id, IgnoresCollision);
|
||||
context.entities.add_component(id, IgnoresFriction);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn spawn_pixel_cloud(context: &mut Core, x: i32, y: i32, count: usize, speed: f32, lifetime: f32, color: u8) {
|
||||
let mut angle = 0.0;
|
||||
for i in 0..count {
|
||||
angle += RADIANS_360 / count as f32;
|
||||
let velocity = Vector2::from_angle(angle) * speed;
|
||||
new_pixel_particle(context, x, y, velocity, lifetime, color);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_poof_cloud(context: &mut Core, x: i32, y: i32, count: usize, radius: i32) {
|
||||
for _ in 0..count {
|
||||
let x = x + rnd_value(-radius, radius);
|
||||
let y = y + rnd_value(-radius, radius);
|
||||
new_poof_animation(context, x, y, 0, match rnd_value(0, 5) {
|
||||
0 => Some(0.25),
|
||||
1 => Some(0.5),
|
||||
2 => Some(0.75),
|
||||
3 => Some(1.0),
|
||||
4 => Some(1.25),
|
||||
5 => Some(1.5),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_weapon_attachment_entity(context: &mut Core, attached_to: EntityId) -> Option<EntityId> {
|
||||
let sprite;
|
||||
let sprite_by_direction;
|
||||
let offset_by_direction;
|
||||
|
||||
let weapons = context.entities.components::<Weapon>();
|
||||
if let Some(weapon) = weapons.get(&attached_to) {
|
||||
sprite = Sprite { atlas: weapon.atlas.clone(), index: 0 };
|
||||
sprite_by_direction = SpriteIndexByDirection { base_index: weapon.base_index };
|
||||
offset_by_direction = AttachmentOffsetByDirection { offsets: weapon.offsets };
|
||||
} else {
|
||||
// if the entity has no weapon "equipped" then they cannot attack!
|
||||
return None;
|
||||
}
|
||||
drop(weapons);
|
||||
|
||||
let id = context.entities.new_entity();
|
||||
|
||||
// note: no point in setting up initial position/direction, as attachment entities get these
|
||||
// properties automatically applied from their parent each frame
|
||||
|
||||
context.entities.add_component(id, AttachedTo(attached_to));
|
||||
context.entities.add_component(id, Position(Vector2::ZERO));
|
||||
context.entities.add_component(id, FacingDirection(Direction::South));
|
||||
context.entities.add_component(id, sprite_by_direction);
|
||||
context.entities.add_component(id, sprite);
|
||||
context.entities.add_component(id, offset_by_direction);
|
||||
|
||||
context.entities.add_component(attached_to, Attachment(id));
|
||||
|
||||
Some(id)
|
||||
}
|
||||
|
||||
pub fn new_pickupable_entity(context: &mut Core, x: i32, y: i32, force: Force, kind: PickupType) -> EntityId {
|
||||
let id = context.entities.new_entity();
|
||||
|
||||
let mut forces = Forces::new();
|
||||
forces.forces.push(force);
|
||||
|
||||
let sprite_index = match kind {
|
||||
PickupType::BlueGem => 0,
|
||||
PickupType::GreenGem => 1,
|
||||
PickupType::OrangeGem => 2,
|
||||
PickupType::Coin => 3,
|
||||
};
|
||||
|
||||
context.entities.add_component(id, Pickupable { kind, pre_timer: PICKUP_PRE_TIMER });
|
||||
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
|
||||
context.entities.add_component(id, Velocity(Vector2::ZERO));
|
||||
context.entities.add_component(id, forces);
|
||||
context.entities.add_component(id, Sprite { atlas: context.items.clone(), index: sprite_index });
|
||||
context.entities.add_component(id, Bounds { width: 16, height: 16, radius: 8 });
|
||||
context.entities.add_component(id, LifeTime(10.0));
|
||||
context.entities.add_component(id, TimedFlicker::new_with_pre_timer(10.0, 7.0, FlickerMethod::OnOff));
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn spawn_pickups_from_entity(context: &mut Core, entity: EntityId) {
|
||||
let positions = context.entities.components::<Position>();
|
||||
let position = positions.get(&entity).unwrap().0;
|
||||
drop(positions);
|
||||
|
||||
let num_pickups = rnd_value(0, 5);
|
||||
for _ in 0..num_pickups {
|
||||
let angle = (rnd_value(0, 359) as f32).to_radians();
|
||||
let force_strength = rnd_value(0.5, 5.0);
|
||||
let force = Force {
|
||||
force: Vector2::from_angle(angle) * force_strength,
|
||||
dissipation_factor: 0.5,
|
||||
};
|
||||
let kind = PickupType::new_random();
|
||||
new_pickupable_entity(context, position.x as i32, position.y as i32, force, kind);
|
||||
}
|
||||
}
|
756
examples/slimed/src/entities/systems.rs
Normal file
756
examples/slimed/src/entities/systems.rs
Normal file
|
@ -0,0 +1,756 @@
|
|||
use libretrogd::{SCREEN_HEIGHT, SCREEN_WIDTH};
|
||||
use libretrogd::entities::*;
|
||||
use libretrogd::math::*;
|
||||
|
||||
use crate::{Core, TILE_HEIGHT, TILE_WIDTH};
|
||||
use crate::entities::*;
|
||||
use crate::tilemap::*;
|
||||
|
||||
pub fn remove_entity(entities: &mut Entities, entity: EntityId) {
|
||||
remove_entity_attachment(entities, entity);
|
||||
entities.remove_entity(entity);
|
||||
}
|
||||
|
||||
pub fn remove_entity_attachment(entities: &mut Entities, entity: EntityId) {
|
||||
let attachments = entities.components::<Attachment>();
|
||||
if let Some(attachment) = attachments.get(&entity) {
|
||||
let attached_entity_id = attachment.0;
|
||||
drop(attachments);
|
||||
entities.remove_entity(attached_entity_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_entity_forward(context: &mut Core, entity: EntityId) {
|
||||
let mut velocities = context.entities.components_mut::<Velocity>();
|
||||
let facing_directions = context.entities.components::<FacingDirection>();
|
||||
let movement_speeds = context.entities.components::<MovementSpeed>();
|
||||
|
||||
let velocity = velocities.get_mut(&entity).unwrap();
|
||||
let facing_direction = facing_directions.get(&entity).unwrap();
|
||||
let movement_speed = movement_speeds.get(&entity).unwrap();
|
||||
|
||||
let movement = match facing_direction.0 {
|
||||
Direction::North => Vector2::UP * movement_speed.0,
|
||||
Direction::East => Vector2::RIGHT * movement_speed.0,
|
||||
Direction::West => Vector2::LEFT * movement_speed.0,
|
||||
Direction::South => Vector2::DOWN * movement_speed.0
|
||||
};
|
||||
|
||||
velocity.0 += movement;
|
||||
}
|
||||
|
||||
pub fn turn_and_move_entity(context: &mut Core, entity: EntityId, direction: Direction) {
|
||||
// can this entity currently move at all?
|
||||
let activities = context.entities.components::<Activity>();
|
||||
if let Some(activity) = activities.get(&entity) {
|
||||
if activity.0 != EntityActivity::Idle && activity.0 != EntityActivity::Walking {
|
||||
return;
|
||||
}
|
||||
}
|
||||
drop(activities);
|
||||
|
||||
// make the entity face in the direction specified
|
||||
let mut facing_directions = context.entities.components_mut::<FacingDirection>();
|
||||
let facing_direction = facing_directions.get_mut(&entity).unwrap();
|
||||
facing_direction.0 = direction;
|
||||
drop(facing_directions);
|
||||
|
||||
move_entity_forward(context, entity);
|
||||
}
|
||||
|
||||
fn move_entity_with_collision(position: &mut Position, bounds: &Bounds, velocity: Option<&Velocity>, forces: Option<&Forces>, map: &TileMap, delta: f32) -> bool {
|
||||
const NUM_STEPS: usize = 2;
|
||||
const STEP_SCALE: f32 = 1.0 / NUM_STEPS as f32;
|
||||
|
||||
let mut collided = false;
|
||||
|
||||
// apply entity velocity + force (if any/either) and exit early with no collision if this entity
|
||||
// has no movement ... no need to check collisions in such a case
|
||||
let mut step_velocity = Vector2::ZERO;
|
||||
if let Some(velocity) = velocity {
|
||||
step_velocity += velocity.0 * delta;
|
||||
}
|
||||
if let Some(forces) = forces {
|
||||
step_velocity += forces.current_force();
|
||||
}
|
||||
if step_velocity.nearly_equal(Vector2::ZERO, 0.00001) {
|
||||
return collided;
|
||||
}
|
||||
|
||||
// entity is actually moving, so check collisions and move accordingly
|
||||
step_velocity *= STEP_SCALE;
|
||||
for _ in 0..NUM_STEPS {
|
||||
let old_position = position.0;
|
||||
|
||||
position.0.x += step_velocity.x;
|
||||
if map.is_colliding(&Rect::new(position.0.x as i32, position.0.y as i32, bounds.width, bounds.height)) {
|
||||
collided = true;
|
||||
position.0.x = old_position.x;
|
||||
}
|
||||
|
||||
position.0.y += step_velocity.y;
|
||||
if map.is_colliding(&Rect::new(position.0.x as i32, position.0.y as i32, bounds.width, bounds.height)) {
|
||||
collided = true;
|
||||
position.0.y = old_position.y;
|
||||
}
|
||||
}
|
||||
|
||||
collided
|
||||
}
|
||||
|
||||
pub fn set_entity_activity(entities: &mut Entities, entity: EntityId, new_activity: EntityActivity) {
|
||||
let mut activities = entities.components_mut::<Activity>();
|
||||
let mut activity = activities.get_mut(&entity).unwrap();
|
||||
|
||||
// only change the activity, and more importantly, the animation if we are actually applying
|
||||
// an actual activity change from what it was before
|
||||
if activity.0 != new_activity {
|
||||
activity.0 = new_activity;
|
||||
|
||||
let animate_by_activitys = entities.components::<AnimateByActivity>();
|
||||
if let Some(animate_by_activity) = animate_by_activitys.get(&entity) {
|
||||
if let Some(new_animation_def) = animate_by_activity.0.get(&new_activity) {
|
||||
let mut animations = entities.components_mut::<AnimationInstance>();
|
||||
let animation = animations.get_mut(&entity).unwrap();
|
||||
animation.change_to(new_animation_def.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kill_entity(context: &mut Core, entity: EntityId) {
|
||||
context.entities.add_component(entity, LifeTime(5.0));
|
||||
context.entities.add_component(entity, TimedFlicker::new_with_pre_timer(5.0, 4.0, FlickerMethod::OnOff));
|
||||
context.entities.remove_component::<RandomlyWalksAround>(entity);
|
||||
context.entities.remove_component::<WalkingTime>(entity);
|
||||
context.entities.remove_component::<Attackable>(entity);
|
||||
set_entity_activity(&mut context.entities, entity, EntityActivity::Dead);
|
||||
spawn_pickups_from_entity(context, entity);
|
||||
}
|
||||
|
||||
pub fn apply_damage_at(context: &mut Core, area: Circle, damage: i32, source: EntityId) {
|
||||
if let Some(attackables) = context.entities.components::<Attackable>() {
|
||||
let positions = context.entities.components::<Position>();
|
||||
let bounds = context.entities.components::<Bounds>();
|
||||
|
||||
//let source_position = Vector2::new(area.x as f32, area.y as f32);
|
||||
let source_position = positions.get(&source).unwrap();
|
||||
|
||||
for (entity, _) in attackables.iter() {
|
||||
// entity cannot (currently) attack itself ...
|
||||
if *entity == source {
|
||||
continue;
|
||||
}
|
||||
|
||||
let position = positions.get(entity).unwrap();
|
||||
let bound = bounds.get(entity).unwrap();
|
||||
|
||||
let circle = Circle::new(
|
||||
position.0.x as i32 + bound.width as i32 / 2,
|
||||
position.0.y as i32 + bound.height as i32 / 2,
|
||||
bound.radius
|
||||
);
|
||||
if area.overlaps(&circle) {
|
||||
context.event_publisher.queue(Event::Hit(*entity, source, damage, source_position.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_attack_area_of_effect(context: &mut Core, attacker: EntityId) -> Option<(Circle, i32)> {
|
||||
let positions = context.entities.components::<Position>();
|
||||
let facing_directions = context.entities.components::<FacingDirection>();
|
||||
let bounds = context.entities.components::<Bounds>();
|
||||
let weapons = context.entities.components::<Weapon>();
|
||||
|
||||
let position = positions.get(&attacker).unwrap();
|
||||
let bound = bounds.get(&attacker).unwrap();
|
||||
if let Some(weapon) = weapons.get(&attacker) {
|
||||
if let Some(facing_direction) = facing_directions.get(&attacker) {
|
||||
let center_point = position.0 + weapon.offsets[facing_direction.0 as usize];
|
||||
return Some((
|
||||
Circle::new(
|
||||
center_point.x as i32 + 8,
|
||||
center_point.y as i32 + 8,
|
||||
weapon.radius_of_effect,
|
||||
),
|
||||
weapon.damage
|
||||
));
|
||||
} else {
|
||||
return Some((
|
||||
Circle::new(
|
||||
position.0.x as i32 + bound.width as i32 / 2,
|
||||
position.0.y as i32 + bound.height as i32 / 2,
|
||||
weapon.radius_of_effect,
|
||||
),
|
||||
weapon.damage
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn attack(context: &mut Core, entity: EntityId) {
|
||||
let activities = context.entities.components::<Activity>();
|
||||
let activity = activities.get(&entity).unwrap();
|
||||
|
||||
match activity.0 {
|
||||
EntityActivity::Idle | EntityActivity::Walking => {
|
||||
drop(activities);
|
||||
// set attacking animation and "extend" the entity's weapon
|
||||
set_entity_activity(&mut context.entities, entity, EntityActivity::Attacking);
|
||||
if new_weapon_attachment_entity(context, entity).is_some() {
|
||||
// if the entity's weapon was actually extended, figure out where it hits
|
||||
// and who is being hit by it
|
||||
if let Some((area_of_effect, damage)) = get_attack_area_of_effect(context, entity) {
|
||||
apply_damage_at(context, area_of_effect, damage, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hit_entity(context: &mut Core, target: EntityId, source: EntityId, damage: i32, damage_position: Vector2) {
|
||||
let position;
|
||||
{
|
||||
let positions = context.entities.components::<Position>();
|
||||
position = positions.get(&target).unwrap().0;
|
||||
|
||||
// apply knockback force to target being hit
|
||||
let mut forces = context.entities.components_mut::<Forces>();
|
||||
if let Some(force) = forces.get_mut(&target) {
|
||||
let knockback_direction = (position - damage_position).normalize();
|
||||
force.add(knockback_direction * HIT_KNOCKBACK_STRENGTH, HIT_KNOCKBACK_DISSIPATION);
|
||||
}
|
||||
|
||||
// subtract damage from entity life, and kill if necessary
|
||||
let mut lifes = context.entities.components_mut::<Life>();
|
||||
if let Some(life) = lifes.get_mut(&target) {
|
||||
life.0 -= damage;
|
||||
if life.0 <= 0 {
|
||||
context.event_publisher.queue(Event::Kill(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spawn_pixel_cloud(context, position.x as i32, position.y as i32, 8, 64.0, 0.15, 15);
|
||||
context.entities.add_component(target, TimedFlicker::new(0.5, FlickerMethod::Color(4)));
|
||||
}
|
||||
|
||||
pub fn stop_attack(context: &mut Core, entity: EntityId) {
|
||||
// after an entity's attack has finished, they go back to idle and we "sheath" their weapon
|
||||
set_entity_activity(&mut context.entities, entity, EntityActivity::Idle);
|
||||
remove_entity_attachment(&mut context.entities, entity);
|
||||
}
|
||||
|
||||
pub fn pickup(context: &mut Core, picked_up_by: EntityId, picked_up: EntityId) {
|
||||
let kind;
|
||||
let position;
|
||||
{
|
||||
let positions = context.entities.components::<Position>();
|
||||
position = positions.get(&picked_up).unwrap().0;
|
||||
|
||||
let pickupables = context.entities.components::<Pickupable>();
|
||||
kind = pickupables.get(&picked_up).unwrap().kind;
|
||||
}
|
||||
|
||||
// TODO: tally up the kinds
|
||||
|
||||
new_sparkles_animation(context, position.x as i32, position.y as i32, None);
|
||||
remove_entity(&mut context.entities, picked_up);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fn update_system_movement(context: &mut Core) {
|
||||
if let Some(mut positions) = context.entities.components_mut::<Position>() {
|
||||
let velocities = context.entities.components::<Velocity>();
|
||||
let forces = context.entities.components::<Forces>();
|
||||
let bounds = context.entities.components::<Bounds>();
|
||||
let ignores_collision = context.entities.components::<IgnoresCollision>();
|
||||
|
||||
for (entity, position) in positions.iter_mut() {
|
||||
if ignores_collision.contains_key(entity) {
|
||||
if let Some(velocity) = velocities.get(entity) {
|
||||
position.0 += velocity.0 * context.delta;
|
||||
}
|
||||
} else {
|
||||
let velocity = velocities.get(entity);
|
||||
let force = forces.get(entity);
|
||||
|
||||
if velocity.is_some() || force.is_some() {
|
||||
move_entity_with_collision(
|
||||
position,
|
||||
bounds.get(entity).unwrap(),
|
||||
velocity,
|
||||
force,
|
||||
&context.tilemap,
|
||||
context.delta,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_friction(context: &mut Core) {
|
||||
if let Some(mut velocities) = context.entities.components_mut::<Velocity>() {
|
||||
let ignores_friction = context.entities.components::<IgnoresFriction>();
|
||||
|
||||
for (entity, velocity) in velocities.iter_mut() {
|
||||
if !ignores_friction.contains_key(entity) {
|
||||
velocity.0 *= FRICTION;
|
||||
if velocity.0.almost_zero(0.001) {
|
||||
velocity.0 = Vector2::ZERO;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_force_decay(context: &mut Core) {
|
||||
if let Some(mut forces) = context.entities.components_mut::<Forces>() {
|
||||
for (_, force) in forces.iter_mut() {
|
||||
force.decay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_pushing(context: &mut Core) {
|
||||
let positions = context.entities.components::<Position>();
|
||||
let bounds = context.entities.components::<Bounds>();
|
||||
let mut forces = context.entities.components_mut::<Forces>();
|
||||
let pushers = context.entities.components::<Pusher>().unwrap();
|
||||
let pushable = context.entities.components::<Pushable>().unwrap();
|
||||
|
||||
// TODO: this is slow
|
||||
|
||||
for (pusher_entity, pusher) in pushers.iter() {
|
||||
let pusher_position = positions.get(pusher_entity).unwrap();
|
||||
let pusher_bounds = bounds.get(pusher_entity).unwrap();
|
||||
let pusher_circle = Circle::new(pusher_position.0.x as i32, pusher_position.0.y as i32, pusher_bounds.radius);
|
||||
|
||||
for (pushable_entity, pushable) in pushable.iter() {
|
||||
// don't push ourself ...
|
||||
if *pushable_entity == *pusher_entity {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pushable_position = positions.get(pushable_entity).unwrap();
|
||||
let pushable_bounds = bounds.get(pushable_entity).unwrap();
|
||||
let pushable_circle = Circle::new(pushable_position.0.x as i32, pushable_position.0.y as i32, pushable_bounds.radius);
|
||||
|
||||
if pusher_circle.overlaps(&pushable_circle) {
|
||||
let push_direction = (pushable_position.0 - pusher_position.0).normalize();
|
||||
|
||||
let pushable_force = forces.get_mut(pushable_entity).unwrap();
|
||||
pushable_force.add(push_direction * pusher.strength, pusher.push_force_dissipation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_lifetime(context: &mut Core) {
|
||||
if let Some(mut lifetimes) = context.entities.components_mut::<LifeTime>() {
|
||||
for (entity, lifetime) in lifetimes.iter_mut() {
|
||||
lifetime.0 -= context.delta;
|
||||
if lifetime.0 < 0.0 {
|
||||
context.event_publisher.queue(Event::Remove(*entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_animation(context: &mut Core) {
|
||||
if let Some(mut animations) = context.entities.components_mut::<AnimationInstance>() {
|
||||
let kill_when_animation_finishes = context.entities.components::<KillWhenAnimationFinishes>();
|
||||
|
||||
for (entity, animation) in animations.iter_mut() {
|
||||
if animation.complete {
|
||||
continue;
|
||||
}
|
||||
|
||||
animation.frame_timer += context.delta;
|
||||
|
||||
let delay = if let Some(delay_override) = animation.delay_override {
|
||||
delay_override
|
||||
} else {
|
||||
animation.def.delay
|
||||
};
|
||||
|
||||
if animation.frame_timer >= delay {
|
||||
// move to the next frame in the current sequence
|
||||
animation.frame_timer = 0.0;
|
||||
if animation.frame_index == (animation.def.frames.len() - 1) {
|
||||
// we're at the last frame in the current sequence
|
||||
if !animation.def.loops {
|
||||
animation.complete = true;
|
||||
|
||||
context.event_publisher.queue(Event::AnimationFinished(*entity));
|
||||
if kill_when_animation_finishes.contains_key(entity) {
|
||||
context.event_publisher.queue(Event::Remove(*entity));
|
||||
}
|
||||
} else {
|
||||
animation.frame_index = 0;
|
||||
}
|
||||
} else {
|
||||
animation.frame_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_set_sprite_index_from_animation(context: &mut Core) {
|
||||
if let Some(animations) = context.entities.components::<AnimationInstance>() {
|
||||
let mut sprites = context.entities.components_mut::<Sprite>();
|
||||
let facing_directions = context.entities.components::<FacingDirection>();
|
||||
|
||||
for (entity, animation) in animations.iter() {
|
||||
if let Some(sprite) = sprites.get_mut(entity) {
|
||||
// base animation sprite-sheet index for the current animation state
|
||||
let mut index = animation.def.frames[animation.frame_index];
|
||||
|
||||
// add multi-direction offset if applicable
|
||||
let multi_direction_offset = animation.def.multi_direction_offset;
|
||||
let facing_direction = facing_directions.get(entity);
|
||||
if multi_direction_offset.is_some() && facing_direction.is_some() {
|
||||
index += multi_direction_offset.unwrap() * facing_direction.unwrap().0 as usize;
|
||||
}
|
||||
|
||||
sprite.index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_set_sprite_index_by_direction(context: &mut Core) {
|
||||
if let Some(sprite_index_by_directions) = context.entities.components::<SpriteIndexByDirection>() {
|
||||
let mut sprites = context.entities.components_mut::<Sprite>();
|
||||
let facing_directions = context.entities.components::<FacingDirection>();
|
||||
|
||||
for (entity, sprite_index_by_direction) in sprite_index_by_directions.iter() {
|
||||
if let Some(sprite) = sprites.get_mut(entity) {
|
||||
if let Some(facing_direction) = facing_directions.get(entity) {
|
||||
sprite.index = sprite_index_by_direction.base_index + facing_direction.0 as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_walking_time(context: &mut Core) {
|
||||
if let Some(mut walking_times) = context.entities.components_mut::<WalkingTime>() {
|
||||
let activities = context.entities.components::<Activity>();
|
||||
|
||||
for (entity, walking_time) in walking_times.iter_mut() {
|
||||
if let Some(activity) = activities.get(entity) {
|
||||
// dead entities can't walk!
|
||||
if activity.0 == EntityActivity::Dead {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if walking_time.0 > 0.0 {
|
||||
walking_time.0 -= context.delta;
|
||||
context.event_publisher.queue(Event::MoveForward(*entity));
|
||||
}
|
||||
}
|
||||
|
||||
// remove walking time components whose timers have elapsed
|
||||
walking_times.retain(|_, comp| comp.0 > 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_randomly_walk_around(context: &mut Core) {
|
||||
if let Some(mut randomly_walk_arounds) = context.entities.components_mut::<RandomlyWalksAround>() {
|
||||
let activities = context.entities.components::<Activity>();
|
||||
let mut walking_times = context.entities.components_mut::<WalkingTime>().unwrap();
|
||||
|
||||
for (entity, randomly_walk_around) in randomly_walk_arounds.iter_mut() {
|
||||
if let Some(activity) = activities.get(entity) {
|
||||
if activity.0 == EntityActivity::Idle {
|
||||
if randomly_walk_around.cooldown_timer > 0.0 {
|
||||
randomly_walk_around.cooldown_timer -= context.delta;
|
||||
if randomly_walk_around.cooldown_timer < 0.0 {
|
||||
randomly_walk_around.cooldown_timer = 0.0;
|
||||
}
|
||||
} else if randomly_walk_around.should_start_walking() {
|
||||
randomly_walk_around.cooldown_timer = rnd_value(randomly_walk_around.min_cooldown, randomly_walk_around.max_cooldown);
|
||||
|
||||
let direction = Direction::new_random();
|
||||
let walk_time = rnd_value(randomly_walk_around.min_walk_time, randomly_walk_around.max_walk_time);
|
||||
|
||||
walking_times.insert(*entity, WalkingTime(walk_time));
|
||||
context.event_publisher.queue(Event::TurnAndMove(*entity, direction));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_current_entity_activity(context: &mut Core) {
|
||||
if let Some(activities) = context.entities.components::<Activity>() {
|
||||
let velocities = context.entities.components::<Velocity>();
|
||||
|
||||
for (entity, activity) in activities.iter() {
|
||||
// try to detect current entity activity based on it's own movement speed
|
||||
// (intentionally NOT checking force velocity!)
|
||||
if let Some(velocity) = velocities.get(entity) {
|
||||
match activity.0 {
|
||||
EntityActivity::Idle => {
|
||||
if velocity.0.length_squared() > 0.0 {
|
||||
context.event_publisher.queue(Event::SetActivity(*entity, EntityActivity::Walking));
|
||||
}
|
||||
}
|
||||
EntityActivity::Walking => {
|
||||
if velocity.0.almost_zero(0.001) {
|
||||
context.event_publisher.queue(Event::SetActivity(*entity, EntityActivity::Idle));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_randomly_spawn_slimes(context: &mut Core) {
|
||||
if let Some((entity, _)) = context.entities.components::<World>().single() {
|
||||
let mut spawn_timers = context.entities.components_mut::<SpawnTimer>();
|
||||
if let Some(spawn_timer) = spawn_timers.get_mut(entity) {
|
||||
spawn_timer.timer -= context.delta;
|
||||
if spawn_timer.timer <= 0.0 {
|
||||
spawn_timer.reset_timer();
|
||||
let slime_count = context.entities.components::<Slime>().len();
|
||||
if slime_count < spawn_timer.max_allowed {
|
||||
context.event_publisher.queue(Event::SpawnSlimeRandomly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_camera_follows_player(context: &mut Core) {
|
||||
if let Some((player_entity, _)) = context.entities.components::<Player>().single() {
|
||||
if let Some((_, mut camera)) = context.entities.components_mut::<Camera>().single_mut() {
|
||||
let positions = context.entities.components::<Position>().unwrap();
|
||||
let position = positions.get(player_entity).unwrap();
|
||||
|
||||
let camera_x = position.0.x as i32 - (SCREEN_WIDTH as i32 / 2) + 8;
|
||||
let camera_y = position.0.y as i32 - (SCREEN_HEIGHT as i32 / 2) + 8;
|
||||
|
||||
// clamp camera position to the map boundaries
|
||||
let map_pixel_width = context.tilemap.width() * TILE_WIDTH;
|
||||
let map_pixel_height = context.tilemap.height() * TILE_HEIGHT;
|
||||
let max_camera_x = map_pixel_width - SCREEN_WIDTH;
|
||||
let max_camera_y = map_pixel_height - SCREEN_HEIGHT;
|
||||
|
||||
camera.x = camera_x.clamp(0, max_camera_x as i32);
|
||||
camera.y = camera_y.clamp(0, max_camera_y as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_turn_attached_entities(context: &mut Core) {
|
||||
if let Some(attachments) = context.entities.components::<Attachment>() {
|
||||
let mut facing_directions = context.entities.components_mut::<FacingDirection>();
|
||||
|
||||
for (parent_entity, attachment) in attachments.iter() {
|
||||
// the parent may not have a facing direction. and if so, we don't need to change the
|
||||
// attachment (child)
|
||||
let parent_facing_direction = if let Some(facing_direction) = facing_directions.get(&parent_entity) {
|
||||
facing_direction.0
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// change the direction of the attachment (child) to match the parent ... if the
|
||||
// attachment even has a direction itself ...
|
||||
if let Some(mut facing_direction) = facing_directions.get_mut(&attachment.0) {
|
||||
facing_direction.0 = parent_facing_direction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_position_attached_entities(context: &mut Core) {
|
||||
if let Some(attachments) = context.entities.components::<Attachment>() {
|
||||
let mut positions = context.entities.components_mut::<Position>();
|
||||
let facing_directions = context.entities.components::<FacingDirection>();
|
||||
let offsets = context.entities.components::<AttachmentOffset>();
|
||||
let offset_by_directions = context.entities.components::<AttachmentOffsetByDirection>();
|
||||
|
||||
for (parent_entity, attachment) in attachments.iter() {
|
||||
// get the parent position used as the base for the attached (child) entity. if the
|
||||
// parent doesn't have one (probably it is dead?), then skip this attachment
|
||||
let parent_position;
|
||||
if let Some(position) = positions.get(&parent_entity) {
|
||||
parent_position = position.0;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
let attached_entity = attachment.0;
|
||||
if let Some(mut attachment_position) = positions.get_mut(&attached_entity) {
|
||||
// start off the attachment by placing it directly at the parent
|
||||
attachment_position.0 = parent_position;
|
||||
|
||||
// then add whatever position offset it needs
|
||||
if let Some(offset) = offsets.get(&attached_entity) {
|
||||
attachment_position.0 += offset.0;
|
||||
} else if let Some(offset_by_direction) = offset_by_directions.get(&attached_entity) {
|
||||
if let Some(facing_direction) = facing_directions.get(&attached_entity) {
|
||||
attachment_position.0 += offset_by_direction.offsets[facing_direction.0 as usize];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_timed_flicker(context: &mut Core) {
|
||||
if let Some(mut timed_flicker) = context.entities.components_mut::<TimedFlicker>() {
|
||||
for (_, flicker) in timed_flicker.iter_mut() {
|
||||
flicker.update(context.delta);
|
||||
}
|
||||
|
||||
timed_flicker.retain(|_, flicker| flicker.timer > 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_system_pickups(context: &mut Core) {
|
||||
if let Some(mut pickupables) = context.entities.components_mut::<Pickupable>() {
|
||||
let pickupers = context.entities.components::<Pickuper>().unwrap();
|
||||
let positions = context.entities.components::<Position>();
|
||||
let bounds = context.entities.components::<Bounds>();
|
||||
|
||||
// don't really think this pre_timer thing is necessary anymore ... ?
|
||||
for (_, pickupable) in pickupables.iter_mut() {
|
||||
if pickupable.pre_timer > 0.0 {
|
||||
pickupable.pre_timer -= context.delta;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is slow
|
||||
|
||||
for (pickuper_entity, _) in pickupers.iter() {
|
||||
let pickuper_position = positions.get(pickuper_entity).unwrap();
|
||||
let pickuper_bounds = bounds.get(pickuper_entity).unwrap();
|
||||
let pickuper_circle = Circle::new(
|
||||
pickuper_position.0.x as i32 + pickuper_bounds.width as i32 / 2,
|
||||
pickuper_position.0.y as i32 + pickuper_bounds.height as i32 / 2,
|
||||
pickuper_bounds.radius
|
||||
);
|
||||
|
||||
for (pickupable_entity, pickupable) in pickupables.iter() {
|
||||
if pickupable.pre_timer <= 0.0 {
|
||||
let pickupable_position = positions.get(pickupable_entity).unwrap();
|
||||
let pickupable_bounds = bounds.get(pickupable_entity).unwrap();
|
||||
let pickupable_circle = Circle::new(
|
||||
pickupable_position.0.x as i32 + pickupable_bounds.width as i32 / 2,
|
||||
pickupable_position.0.y as i32 + pickupable_bounds.height as i32 / 2,
|
||||
pickupable_bounds.radius
|
||||
);
|
||||
|
||||
if pickupable_circle.overlaps(&pickuper_circle) {
|
||||
context.event_publisher.queue(Event::Pickup(*pickuper_entity, *pickupable_entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fn render_system_sprites(context: &mut Core) {
|
||||
context.sprite_render_list.clear();
|
||||
|
||||
if let Some(sprites) = context.entities.components::<Sprite>() {
|
||||
if let Some((_, camera)) = context.entities.components::<Camera>().single() {
|
||||
let positions = context.entities.components::<Position>().unwrap();
|
||||
let timed_flickers = context.entities.components::<TimedFlicker>().unwrap();
|
||||
|
||||
// build up list of entities to be rendered with their positions so we can sort them
|
||||
// and render these entities with a proper y-based sort order
|
||||
for (entity, _) in sprites.iter() {
|
||||
let mut blit_method = BlitMethod::Transparent(0);
|
||||
|
||||
// check for flicker effects
|
||||
if let Some(flicker) = timed_flickers.get(entity) {
|
||||
if !flicker.flick {
|
||||
match flicker.method {
|
||||
FlickerMethod::OnOff => {
|
||||
// skip to the next entity, this one isn't visible
|
||||
continue;
|
||||
},
|
||||
FlickerMethod::Color(color) => {
|
||||
blit_method = BlitMethod::TransparentSingle(0, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let position = positions.get(entity).unwrap();
|
||||
context.sprite_render_list.push((*entity, position.0, blit_method));
|
||||
}
|
||||
context.sprite_render_list.sort_unstable_by(|&a, &b| (a.1.y as i32).cmp(&(b.1.y as i32)));
|
||||
|
||||
// now render them in the correct order ...
|
||||
for (entity, position, blit_method) in context.sprite_render_list.iter() {
|
||||
let sprite = sprites.get(entity).unwrap();
|
||||
context.system.video.blit_region(
|
||||
*blit_method,
|
||||
sprite.atlas.bitmap(),
|
||||
&sprite.atlas[sprite.index],
|
||||
position.x as i32 - camera.x,
|
||||
position.y as i32 - camera.y,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_system_pixels(context: &mut Core) {
|
||||
if let Some(pixels) = context.entities.components::<Pixel>() {
|
||||
if let Some((_, camera)) = context.entities.components::<Camera>().single() {
|
||||
let positions = context.entities.components::<Position>();
|
||||
|
||||
for (entity, pixel) in pixels.iter() {
|
||||
if let Some(position) = positions.get(entity) {
|
||||
context.system.video.set_pixel(
|
||||
position.0.x as i32 - camera.x,
|
||||
position.0.y as i32 - camera.y,
|
||||
pixel.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_component_system(cs: &mut ComponentSystems<Core, Core>) {
|
||||
cs.reset();
|
||||
cs.add_update_system(update_system_lifetime);
|
||||
cs.add_update_system(update_system_current_entity_activity);
|
||||
cs.add_update_system(update_system_walking_time);
|
||||
cs.add_update_system(update_system_pushing);
|
||||
cs.add_update_system(update_system_movement);
|
||||
cs.add_update_system(update_system_turn_attached_entities);
|
||||
cs.add_update_system(update_system_position_attached_entities);
|
||||
cs.add_update_system(update_system_friction);
|
||||
cs.add_update_system(update_system_force_decay);
|
||||
cs.add_update_system(update_system_randomly_walk_around);
|
||||
cs.add_update_system(update_system_animation);
|
||||
cs.add_update_system(update_system_set_sprite_index_from_animation);
|
||||
cs.add_update_system(update_system_set_sprite_index_by_direction);
|
||||
cs.add_update_system(update_system_randomly_spawn_slimes);
|
||||
cs.add_update_system(update_system_camera_follows_player);
|
||||
cs.add_update_system(update_system_timed_flicker);
|
||||
cs.add_update_system(update_system_pickups);
|
||||
cs.add_render_system(render_system_sprites);
|
||||
cs.add_render_system(render_system_pixels);
|
||||
}
|
180
examples/slimed/src/main.rs
Normal file
180
examples/slimed/src/main.rs
Normal file
|
@ -0,0 +1,180 @@
|
|||
extern crate core;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use libretrogd::entities::*;
|
||||
use libretrogd::events::*;
|
||||
use libretrogd::graphics::*;
|
||||
use libretrogd::math::*;
|
||||
use libretrogd::states::*;
|
||||
use libretrogd::system::*;
|
||||
|
||||
use crate::entities::*;
|
||||
use crate::states::*;
|
||||
use crate::support::*;
|
||||
use crate::tilemap::*;
|
||||
|
||||
mod states;
|
||||
mod entities;
|
||||
mod support;
|
||||
mod tilemap;
|
||||
|
||||
pub const TILE_WIDTH: u32 = 16;
|
||||
pub const TILE_HEIGHT: u32 = 16;
|
||||
|
||||
pub struct Core {
|
||||
pub delta: f32,
|
||||
pub system: System,
|
||||
pub font: BitmaskFont,
|
||||
pub entities: Entities,
|
||||
pub event_publisher: EventPublisher<Event>,
|
||||
pub palette: Palette,
|
||||
pub fade_out_palette: Palette,
|
||||
pub tiles: Rc<BitmapAtlas>,
|
||||
pub hero_male: Rc<BitmapAtlas>,
|
||||
pub hero_female: Rc<BitmapAtlas>,
|
||||
pub green_slime: Rc<BitmapAtlas>,
|
||||
pub blue_slime: Rc<BitmapAtlas>,
|
||||
pub orange_slime: Rc<BitmapAtlas>,
|
||||
pub fist: Rc<BitmapAtlas>,
|
||||
pub sword: Rc<BitmapAtlas>,
|
||||
pub particles: Rc<BitmapAtlas>,
|
||||
pub items: Rc<BitmapAtlas>,
|
||||
pub ui: Rc<BitmapAtlas>,
|
||||
pub tilemap: TileMap,
|
||||
pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
|
||||
pub hero_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
|
||||
pub poof1_animation_def: Rc<AnimationDef>,
|
||||
pub poof2_animation_def: Rc<AnimationDef>,
|
||||
pub sparkles_animation_def: Rc<AnimationDef>,
|
||||
pub sprite_render_list: Vec<(EntityId, Vector2, BlitMethod)>,
|
||||
}
|
||||
|
||||
pub struct Game {
|
||||
pub core: Core,
|
||||
pub component_systems: ComponentSystems<Core, Core>,
|
||||
pub event_listeners: EventListeners<Event, Core>,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn new(mut system: System) -> Result<Self> {
|
||||
let palette = load_palette(Path::new("./assets/db16.pal"))?;
|
||||
system.palette = palette.clone();
|
||||
|
||||
let font = load_font(Path::new("./assets/dp.fnt"))?;
|
||||
|
||||
let tiles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/tiles.pcx"))?);
|
||||
let hero_male = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_male.pcx"))?);
|
||||
let hero_female = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/hero_female.pcx"))?);
|
||||
let green_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/green_slime.pcx"))?);
|
||||
let blue_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/blue_slime.pcx"))?);
|
||||
let orange_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/orange_slime.pcx"))?);
|
||||
let fist = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/fist.pcx"))?);
|
||||
let sword = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/sword.pcx"))?);
|
||||
let particles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/particles.pcx"))?);
|
||||
let items = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/items.pcx"))?);
|
||||
|
||||
let mut ui = load_bitmap_atlas(Path::new("./assets/ui.pcx"))?;
|
||||
ui.add(Rect::new(0, 0, 16, 16))?;
|
||||
ui.add(Rect::new(16, 0, 16, 16))?;
|
||||
for i in 0..8 {
|
||||
ui.add(Rect::new(i * 8, 16, 8, 8))?;
|
||||
}
|
||||
|
||||
let tilemap = TileMap::load_from(Path::new("./assets/title_screen.map.json"))?;
|
||||
|
||||
let entities = Entities::new();
|
||||
let component_systems = ComponentSystems::new();
|
||||
let event_publisher = EventPublisher::new();
|
||||
let event_listeners = EventListeners::new();
|
||||
|
||||
let slime_activity_states = HashMap::from([
|
||||
(EntityActivity::Idle, Rc::new(AnimationDef::new(&[1, 2], true, 1.0, Some(3)))),
|
||||
(EntityActivity::Walking, Rc::new(AnimationDef::new(&[1, 0, 2, 0], true, 0.25, Some(3)))),
|
||||
(EntityActivity::Attacking, Rc::new(AnimationDef::new(&[0], false, 0.3, Some(3)))),
|
||||
(EntityActivity::Dead, Rc::new(AnimationDef::new(&[12], false, 1.0, None))),
|
||||
]);
|
||||
let hero_activity_states = HashMap::from([
|
||||
(EntityActivity::Idle, Rc::new(AnimationDef::new(&[0], true, 0.5, Some(4)))),
|
||||
(EntityActivity::Walking, Rc::new(AnimationDef::new(&[0, 1, 0, 2], true, 0.15, Some(4)))),
|
||||
(EntityActivity::Attacking, Rc::new(AnimationDef::new(&[3], false, 0.3, Some(4)))),
|
||||
(EntityActivity::Dead, Rc::new(AnimationDef::new(&[16], false, 1.0, None))),
|
||||
]);
|
||||
let poof1_animation_def = Rc::new(AnimationDef::new(&[0, 1, 2], false, 0.15, None));
|
||||
let poof2_animation_def = Rc::new(AnimationDef::new(&[3, 4, 5], false, 0.15, None));
|
||||
let sparkles_animation_def = Rc::new(AnimationDef::new(&[6, 7, 8, 9], false, 0.1, None));
|
||||
|
||||
Ok(Game {
|
||||
core: Core {
|
||||
delta: 0.0,
|
||||
system,
|
||||
font,
|
||||
entities,
|
||||
event_publisher,
|
||||
palette,
|
||||
fade_out_palette: Palette::new_with_default(20, 12, 28),
|
||||
tiles,
|
||||
hero_male,
|
||||
hero_female,
|
||||
green_slime,
|
||||
blue_slime,
|
||||
orange_slime,
|
||||
fist,
|
||||
sword,
|
||||
particles,
|
||||
items,
|
||||
ui: Rc::new(ui),
|
||||
tilemap,
|
||||
slime_activity_states: Rc::new(slime_activity_states),
|
||||
hero_activity_states: Rc::new(hero_activity_states),
|
||||
poof1_animation_def,
|
||||
poof2_animation_def,
|
||||
sparkles_animation_def,
|
||||
sprite_render_list: Vec::with_capacity(1024),
|
||||
},
|
||||
component_systems,
|
||||
event_listeners,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn do_events(&mut self) {
|
||||
self.event_listeners.take_queue_from(&mut self.core.event_publisher);
|
||||
self.event_listeners.dispatch_queue(&mut self.core);
|
||||
}
|
||||
|
||||
pub fn update_frame_delta(&mut self, last_ticks: u64) -> u64 {
|
||||
let ticks = self.core.system.ticks();
|
||||
let elapsed = ticks - last_ticks;
|
||||
self.core.delta = (elapsed as f64 / self.core.system.tick_frequency() as f64) as f32;
|
||||
ticks
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let system = SystemBuilder::new().window_title("Slime Stabbing Simulator").vsync(true).build()?;
|
||||
let mut game = Game::new(system)?;
|
||||
let mut states = States::new();
|
||||
states.push(MainMenuState::new())?;
|
||||
|
||||
let mut is_running = true;
|
||||
let mut last_ticks = game.core.system.ticks();
|
||||
|
||||
while is_running && !states.is_empty() {
|
||||
game.core.system.do_events_with(|event| {
|
||||
if let sdl2::event::Event::Quit { .. } = event {
|
||||
is_running = false
|
||||
}
|
||||
});
|
||||
|
||||
last_ticks = game.update_frame_delta(last_ticks);
|
||||
states.update(&mut game)?;
|
||||
states.render(&mut game);
|
||||
game.core.system.display()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
205
examples/slimed/src/states.rs
Normal file
205
examples/slimed/src/states.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use std::path::Path;
|
||||
|
||||
use sdl2::keyboard::Scancode;
|
||||
|
||||
use libretrogd::entities::*;
|
||||
use libretrogd::math::Vector2;
|
||||
use libretrogd::states::*;
|
||||
|
||||
use crate::entities::*;
|
||||
use crate::Game;
|
||||
use crate::support::*;
|
||||
|
||||
pub struct MainMenuState {
|
||||
fade: f32,
|
||||
selection: i32,
|
||||
}
|
||||
|
||||
impl MainMenuState {
|
||||
pub fn new() -> Self {
|
||||
MainMenuState {
|
||||
fade: 0.0,
|
||||
selection: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameState<Game> for MainMenuState {
|
||||
fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> {
|
||||
if state == State::Active {
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
return Some(StateChange::Pop(1));
|
||||
}
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Up) {
|
||||
self.selection = (self.selection - 1).clamp(0, 1);
|
||||
}
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Down) {
|
||||
self.selection = (self.selection + 1).clamp(0, 1);
|
||||
}
|
||||
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Return) {
|
||||
match self.selection {
|
||||
0 => return Some(StateChange::Push(Box::new(GamePlayState::new()))),
|
||||
1 => return Some(StateChange::Pop(1)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.do_events();
|
||||
context.component_systems.update(&mut context.core);
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn render(&mut self, state: State, context: &mut Game) {
|
||||
context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, 0, 0);
|
||||
context.component_systems.render(&mut context.core);
|
||||
|
||||
let x = 32;
|
||||
let y = 160;
|
||||
let width = 48;
|
||||
let height = 40;
|
||||
const SPACER: i32 = 8;
|
||||
draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height);
|
||||
|
||||
let selection_y = y + SPACER + (self.selection as i32 * 16);
|
||||
context.core.system.video.print_string(">", x + SPACER, selection_y, 15, &context.core.font);
|
||||
|
||||
context.core.system.video.print_string("Play", x + SPACER + SPACER, y + SPACER, 15, &context.core.font);
|
||||
context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, 15, &context.core.font);
|
||||
}
|
||||
|
||||
fn transition(&mut self, state: State, context: &mut Game) -> bool {
|
||||
update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context)
|
||||
}
|
||||
|
||||
fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) {
|
||||
match new_state {
|
||||
State::Pending | State::Resume => {
|
||||
init_everything(context, Path::new("./assets/title_screen.map.json"), 0.2, 1.0, 32);
|
||||
}
|
||||
State::TransitionIn => {
|
||||
self.fade = 0.0;
|
||||
}
|
||||
State::TransitionOut(_) => {
|
||||
self.fade = 1.0;
|
||||
}
|
||||
State::Paused => {
|
||||
context.core.system.palette = context.core.palette.clone();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GamePlayState {
|
||||
fade: f32,
|
||||
in_menu: bool,
|
||||
selection: i32,
|
||||
}
|
||||
|
||||
impl GamePlayState {
|
||||
pub fn new() -> Self {
|
||||
GamePlayState {
|
||||
fade: 0.0,
|
||||
in_menu: false,
|
||||
selection: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GameState<Game> for GamePlayState {
|
||||
fn update(&mut self, state: State, context: &mut Game) -> Option<StateChange<Game>> {
|
||||
if state == State::Active {
|
||||
if self.in_menu {
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
self.in_menu = false;
|
||||
}
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Up) {
|
||||
self.selection = (self.selection - 1).clamp(0, 1);
|
||||
}
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Down) {
|
||||
self.selection = (self.selection + 1).clamp(0, 1);
|
||||
}
|
||||
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Return) {
|
||||
match self.selection {
|
||||
0 => self.in_menu = false,
|
||||
1 => return Some(StateChange::Pop(1)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Escape) {
|
||||
self.in_menu = true;
|
||||
}
|
||||
|
||||
if let Some((player_entity, _)) = context.core.entities.components::<Player>().single() {
|
||||
if context.core.system.keyboard.is_key_down(Scancode::Up) {
|
||||
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::North));
|
||||
}
|
||||
if context.core.system.keyboard.is_key_down(Scancode::Down) {
|
||||
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::South));
|
||||
}
|
||||
if context.core.system.keyboard.is_key_down(Scancode::Left) {
|
||||
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::West));
|
||||
}
|
||||
if context.core.system.keyboard.is_key_down(Scancode::Right) {
|
||||
context.core.event_publisher.queue(Event::TurnAndMove(*player_entity, Direction::East));
|
||||
}
|
||||
if context.core.system.keyboard.is_key_pressed(Scancode::Space) {
|
||||
context.core.event_publisher.queue(Event::Attack(*player_entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.do_events();
|
||||
context.component_systems.update(&mut context.core);
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn render(&mut self, state: State, context: &mut Game) {
|
||||
if let Some((_, camera)) = context.core.entities.components::<Camera>().single() {
|
||||
context.core.tilemap.draw(&mut context.core.system.video, &context.core.tiles, camera.x, camera.y);
|
||||
}
|
||||
context.component_systems.render(&mut context.core);
|
||||
|
||||
if self.in_menu {
|
||||
let x = 32;
|
||||
let y = 160;
|
||||
let width = 80;
|
||||
let height = 40;
|
||||
const SPACER: i32 = 8;
|
||||
draw_window(&mut context.core.system.video, &context.core.ui, x, y, x + width, y + height);
|
||||
|
||||
let selection_y = y + SPACER + (self.selection as i32 * 16);
|
||||
context.core.system.video.print_string(">", x + SPACER, selection_y, 15, &context.core.font);
|
||||
|
||||
context.core.system.video.print_string("Continue", x + SPACER + SPACER, y + SPACER, 15, &context.core.font);
|
||||
context.core.system.video.print_string("Quit", x + SPACER + SPACER, y + SPACER + 16, 15, &context.core.font);
|
||||
}
|
||||
}
|
||||
|
||||
fn transition(&mut self, state: State, context: &mut Game) -> bool {
|
||||
update_fade_transition(state, &mut self.fade, context.core.delta * 3.0, context)
|
||||
}
|
||||
|
||||
fn state_change(&mut self, new_state: State, old_state: State, context: &mut Game) {
|
||||
match new_state {
|
||||
State::Pending => {
|
||||
init_everything(context, Path::new("./assets/arena.map.json"), 0.5, 2.0, 100);
|
||||
spawn_player_randomly(&mut context.core);
|
||||
},
|
||||
State::TransitionIn => {
|
||||
self.fade = 0.0;
|
||||
}
|
||||
State::TransitionOut(_) => {
|
||||
self.fade = 1.0;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
83
examples/slimed/src/support.rs
Normal file
83
examples/slimed/src/support.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use libretrogd::graphics::*;
|
||||
use libretrogd::states::*;
|
||||
|
||||
use crate::{Game, TILE_HEIGHT, TILE_WIDTH};
|
||||
|
||||
pub fn load_palette(path: &Path) -> Result<Palette> {
|
||||
Palette::load_from_file(path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path))
|
||||
}
|
||||
|
||||
pub fn load_font(path: &Path) -> Result<BitmaskFont> {
|
||||
BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path))
|
||||
}
|
||||
|
||||
pub fn load_bitmap_atlas_autogrid(path: &Path) -> Result<BitmapAtlas> {
|
||||
let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
|
||||
let mut atlas = BitmapAtlas::new(bmp);
|
||||
atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?;
|
||||
Ok(atlas)
|
||||
}
|
||||
|
||||
pub fn load_bitmap_atlas(path: &Path) -> Result<BitmapAtlas> {
|
||||
let (bmp, _) = Bitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
|
||||
let atlas = BitmapAtlas::new(bmp);
|
||||
Ok(atlas)
|
||||
}
|
||||
|
||||
pub fn draw_window(dest: &mut Bitmap, ui: &BitmapAtlas, left: i32, top: i32, right: i32, bottom: i32) {
|
||||
dest.filled_rect(left + 8, top + 8, right - 8, bottom - 8, 1);
|
||||
|
||||
// corners
|
||||
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[2], left, top);
|
||||
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[3], right - 8, top);
|
||||
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[4], left, bottom - 8);
|
||||
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[5], right - 8, bottom - 8);
|
||||
|
||||
// top and bottom edges
|
||||
for i in 0..((right - left) / 8) - 2 {
|
||||
let x = left + 8 + (i * 8);
|
||||
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[9], x, top);
|
||||
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[8], x, bottom - 8);
|
||||
}
|
||||
|
||||
// left and right edges
|
||||
for i in 0..((bottom - top) / 8) - 2 {
|
||||
let y = top + 8 + (i * 8);
|
||||
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[6], left, y);
|
||||
dest.blit_region(BlitMethod::Transparent(0), &ui.bitmap(), &ui[7], right - 8, y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_fade_transition(state: State, fade: &mut f32, delta: f32, context: &mut Game) -> bool {
|
||||
match state {
|
||||
State::TransitionIn => {
|
||||
*fade += delta;
|
||||
if *fade >= 1.0 {
|
||||
*fade = 1.0;
|
||||
context.core.system.palette = context.core.palette.clone();
|
||||
true
|
||||
} else {
|
||||
context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
|
||||
false
|
||||
}
|
||||
},
|
||||
State::TransitionOut(_) => {
|
||||
*fade -= delta;
|
||||
if *fade <= 0.0 {
|
||||
*fade = 0.0;
|
||||
context.core.system.palette = context.core.fade_out_palette.clone();
|
||||
true
|
||||
} else {
|
||||
context.core.system.palette.lerp(0..=255, &context.core.fade_out_palette, &context.core.palette, *fade);
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
127
examples/slimed/src/tilemap.rs
Normal file
127
examples/slimed/src/tilemap.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
|
||||
use libretrogd::graphics::*;
|
||||
use libretrogd::math::*;
|
||||
use libretrogd::utils::rnd_value;
|
||||
|
||||
use crate::{TILE_HEIGHT, TILE_WIDTH};
|
||||
|
||||
pub const TILE_FLAG_NONE: i32 = 1;
|
||||
pub const TILE_FLAG_COLLISION: i32 = 0;
|
||||
pub const TILE_FLAG_SPAWNABLE: i32 = 1;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TileMap {
|
||||
width: u32,
|
||||
height: u32,
|
||||
layers: Vec<Box<[i32]>>,
|
||||
}
|
||||
|
||||
impl TileMap {
|
||||
pub fn load_from(path: &Path) -> Result<Self> {
|
||||
let f = File::open(path)?;
|
||||
let reader = BufReader::new(f);
|
||||
serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index_to(&self, x: i32, y: i32) -> Option<usize> {
|
||||
if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
|
||||
Some(((y * self.width as i32) + x) as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lower(&self) -> &Box<[i32]> {
|
||||
&self.layers[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn upper(&self) -> &Box<[i32]> {
|
||||
&self.layers[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn collision(&self) -> &Box<[i32]> {
|
||||
&self.layers[2]
|
||||
}
|
||||
|
||||
pub fn draw(&self, dest: &mut Bitmap, tiles: &BitmapAtlas, camera_x: i32, camera_y: i32) {
|
||||
let xt = camera_x / TILE_WIDTH as i32;
|
||||
let yt = camera_y / TILE_HEIGHT as i32;
|
||||
let xp = camera_x % TILE_WIDTH as i32;
|
||||
let yp = camera_y % TILE_HEIGHT as i32;
|
||||
|
||||
for y in 0..=15 {
|
||||
for x in 0..=20 {
|
||||
if let Some(index) = self.index_to(x + xt, y + yt) {
|
||||
let xd = (x * TILE_WIDTH as i32) - xp;
|
||||
let yd = (y * TILE_HEIGHT as i32) - yp;
|
||||
|
||||
let lower = self.layers[0][index];
|
||||
if lower >= 0 {
|
||||
dest.blit_region(BlitMethod::Solid, tiles.bitmap(), &tiles[lower as usize], xd, yd);
|
||||
}
|
||||
let upper = self.layers[1][index];
|
||||
if upper >= 0 {
|
||||
dest.blit_region(BlitMethod::Transparent(0), tiles.bitmap(), &tiles[upper as usize], xd, yd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_colliding(&self, rect: &Rect) -> bool {
|
||||
let x1 = rect.x / TILE_WIDTH as i32;
|
||||
let y1 = rect.y / TILE_HEIGHT as i32;
|
||||
let x2 = rect.right() / TILE_WIDTH as i32;
|
||||
let y2 = rect.bottom() / TILE_HEIGHT as i32;
|
||||
|
||||
for y in y1..=y2 {
|
||||
for x in x1..=x2 {
|
||||
match self.index_to(x, y) {
|
||||
Some(index) => {
|
||||
if self.collision()[index] == TILE_FLAG_COLLISION {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
None => return true
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_random_spawnable_coordinates(&self) -> (i32, i32) {
|
||||
// TODO: do this better
|
||||
let mut x;
|
||||
let mut y;
|
||||
|
||||
loop {
|
||||
x = rnd_value(0, self.width as i32 - 1);
|
||||
y = rnd_value(0, self.height as i32 - 1);
|
||||
if self.collision()[self.index_to(x, y).unwrap()] == TILE_FLAG_SPAWNABLE {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(x, y)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue