Compare commits
160 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
930696783d | ||
|
60d6511035 | ||
|
0abe049061 | ||
|
1401094b13 | ||
2591cb85de | |||
2653c9e127 | |||
|
24fae60c4c | ||
|
3ba8505104 | ||
|
6c22f05a30 | ||
|
67899182a4 | ||
8753436eb9 | |||
b9ea9b9a78 | |||
8b7ee82387 | |||
|
ce2e028c72 | ||
|
1494b188c8 | ||
|
086eb1d86f | ||
|
b765513075 | ||
|
0d453ed708 | ||
|
eed8f324af | ||
a271875d73 | |||
c5cc13f9e7 | |||
|
0a69b80b34 | ||
055ac10ca0 | |||
f81920162d | |||
003f27a06e | |||
22fd05f13b | |||
4c113824c7 | |||
eed3f3d752 | |||
e9583a7266 | |||
|
38e63414b0 | ||
|
e8a00b9907 | ||
|
cf64fb4851 | ||
|
53a746c551 | ||
|
3fbb50f985 | ||
|
eb49b08bab | ||
6b77476d09 | |||
f9091fbce2 | |||
3da8bb06ed | |||
|
3c1d56a46b | ||
|
3007f96ca4 | ||
|
1928d0d292 | ||
|
dfeb6dfbf7 | ||
|
c8da93bfdb | ||
|
0c9005939c | ||
a32c20fdee | |||
e4ae9b18b3 | |||
d729ae811e | |||
733839ca10 | |||
5e1ae73b2b | |||
7edfe6f132 | |||
068c8be089 | |||
8c8d0ab595 | |||
602f600379 | |||
fdb93c03fd | |||
|
b24c09fde3 | ||
|
a8884757f5 | ||
a74ba44e77 | |||
2758d0de55 | |||
5497d8e279 | |||
dbc620aa03 | |||
86603bf729 | |||
07d678ffba | |||
d2a14ce650 | |||
|
0b1cd6c678 | ||
|
b85c61d532 | ||
e759e0ddbe | |||
2014ad42c8 | |||
|
ed70f6ac8c | ||
|
8411b60c5c | ||
|
5f2ad0b87c | ||
b954e79287 | |||
4adadbe8cd | |||
fcf8226f65 | |||
1ec856e1f5 | |||
22dc823f17 | |||
afe9d64c2f | |||
|
f1a071f5da | ||
623b6dbad3 | |||
|
41c1ba7774 | ||
|
e11686fbe0 | ||
f54a88cfd5 | |||
9bd4cb4122 | |||
b332f7ffb8 | |||
2fec1a9765 | |||
dda941ba3a | |||
1534883d78 | |||
|
a4448775a7 | ||
a5614c54b2 | |||
|
4073923edf | ||
25c98de3ba | |||
|
976b230986 | ||
d985cb2fad | |||
be91f70053 | |||
810ebc72e1 | |||
|
bc8d03a261 | ||
|
ccfa199f13 | ||
e1d9100613 | |||
|
74ba0ffab9 | ||
|
e809f9838e | ||
8e8cf7bf4c | |||
10949850f6 | |||
c91d82080a | |||
319405e761 | |||
|
3b9eebbb3c | ||
2f1a275465 | |||
846b75d6e5 | |||
|
fcaeee4877 | ||
|
c5f716cca1 | ||
c207966b75 | |||
50baf53b9e | |||
e7cd020666 | |||
c4fec8c835 | |||
021e597747 | |||
|
67d76dab08 | ||
2d6d1f3629 | |||
da3b52e395 | |||
97449cbc44 | |||
|
b1a1b2e105 | ||
53b3a33b4e | |||
1abc80be64 | |||
5d019bf39f | |||
cc4affdbd1 | |||
7979295f7a | |||
7dac0bbfd1 | |||
a88cae2875 | |||
82d5b336c6 | |||
289befc802 | |||
5bb8261119 | |||
0d862760bd | |||
198edc6556 | |||
ad5d6bc1ab | |||
560329fa2b | |||
3a2caecfff | |||
|
a4f2ee84d4 | ||
|
9035b1d872 | ||
010ad7ce26 | |||
fa7040f626 | |||
eff489d9ff | |||
83aaa01133 | |||
1bc5aa966d | |||
bed3f407d6 | |||
2a24d45cce | |||
a68d3e8e70 | |||
6c288e9178 | |||
73ac4d25a9 | |||
c4dd17f25d | |||
7452803ba9 | |||
43c60c6dfc | |||
8b15d8fcaf | |||
f67b654088 | |||
0c4928b892 | |||
dcbe3eab2e | |||
43bcfe8623 | |||
d972238574 | |||
ff36b1c2c3 | |||
3d29a971cf | |||
c5a2877efd | |||
cf342aa932 | |||
1585521fe3 | |||
561bb5abf5 |
1
.gitignore
vendored
@ -16,3 +16,4 @@ Cargo.lock
|
||||
/target
|
||||
|
||||
.project
|
||||
savestate.json
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "one-breath"
|
||||
name = "ldgame"
|
||||
version = "0.1.0"
|
||||
authors = ["Evan Pratten <ewpratten@gmail.com>"]
|
||||
edition = "2018"
|
||||
@ -13,9 +13,4 @@ serialstudio = "0.1.0"
|
||||
serde = "1.0.125"
|
||||
serde_json = "1.0.64"
|
||||
failure = "0.1.8"
|
||||
parry2d = "0.4.0"
|
||||
log = "0.4.14"
|
||||
env_logger = "0.8.3"
|
||||
nalgebra = "0.26.1"
|
||||
rand = "0.8.3"
|
||||
tiled = "0.9.4"
|
||||
|
17
README.md
@ -1,8 +1,19 @@
|
||||
# ludum-dare-48
|
||||
<img src="./assets/img/logos/readme.png" width="100%">
|
||||
|
||||
# Deep Breath
|
||||
|
||||
|
||||
[](https://github.com/Ewpratten/ludum-dare-48/actions/workflows/build.yml)
|
||||
[](https://github.com/Ewpratten/ludum-dare-48/actions/workflows/bundle.yml)
|
||||
[](https://ldjam.com/events/ludum-dare/48/$236526)
|
||||
[](https://www.rust-lang.org/)
|
||||
[](https://www.raylib.com/)
|
||||
|
||||
**Deep Breath** is an exploration game where you explore an underwater cave in hopes of finding your lost transponder. Items and upgrades can be acquired along the way to assist your search.
|
||||
|
||||
This game was written in [Rust](https://www.rust-lang.org/), on top of [Rust bindings](https://github.com/deltaphc/raylib-rs) to the [`raylib`](https://github.com/raysan5/raylib) graphics library. For most of the team, this has been our first big Rust project.
|
||||
|
||||
This has been our second game produced for Ludum Dare. Check out the first [here](https://ldjam.com/events/ludum-dare/46/micromanaged-mike).
|
||||
|
||||
## Development Resources
|
||||
|
||||
@ -21,6 +32,10 @@ Core libraries:
|
||||
- [`serde`](https://serde.rs/)
|
||||
- [`serialstudio-rs`](https://github.com/Ewpratten/serialstudio-rs)
|
||||
|
||||
Sound Samples:
|
||||
- [JavierZumer](https://freesound.org/people/JavierZumer/sounds/257236/)
|
||||
- [Noted451](https://freesound.org/people/Noted451/sounds/531015/)
|
||||
|
||||
### VSCode Setup
|
||||
|
||||
If using VSCode, disable the `Rust` extension, and install everything in the **Workspace Recommendations** (You will see this list by searching `@recommended` in the extensions panel)
|
||||
|
BIN
assets/audio/breath.mp3
Normal file
BIN
assets/audio/die.mp3
Normal file
BIN
assets/audio/fishPickup.mp3
Normal file
BIN
assets/audio/shopSong.mp3
Normal file
BIN
assets/audio/succ.mp3
Normal file
BIN
assets/audio/swim1.mp3
Normal file
BIN
assets/audio/swim2.mp3
Normal file
BIN
assets/audio/swim3.mp3
Normal file
BIN
assets/audio/swim4.mp3
Normal file
BIN
assets/audio/swimSong.mp3
Normal file
BIN
assets/audio/uiBuy.mp3
Normal file
BIN
assets/audio/uiClick.mp3
Normal file
BIN
assets/audio/waterrecordings.wav
Normal file
BIN
assets/audio/zap.mp3
Normal file
BIN
assets/img/enemies/octopus.aseprite
Normal file
66
assets/img/enemies/octopus.json
Normal file
@ -0,0 +1,66 @@
|
||||
{ "frames": {
|
||||
"octopus 0.aseprite": {
|
||||
"frame": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"octopus 1.aseprite": {
|
||||
"frame": { "x": 20, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"octopus 2.aseprite": {
|
||||
"frame": { "x": 40, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"octopus 3.aseprite": {
|
||||
"frame": { "x": 60, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"octopus 4.aseprite": {
|
||||
"frame": { "x": 80, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"octopus 5.aseprite": {
|
||||
"frame": { "x": 100, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 100
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "octopus.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 120, "h": 20 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/enemies/octopus.png
Normal file
After Width: | Height: | Size: 758 B |
BIN
assets/img/enemies/octopusSuck.aseprite
Normal file
50
assets/img/enemies/octopusSuck.json
Normal file
@ -0,0 +1,50 @@
|
||||
{ "frames": {
|
||||
"octopusSuck 0.aseprite": {
|
||||
"frame": { "x": 0, "y": 0, "w": 30, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 30, "h": 20 },
|
||||
"sourceSize": { "w": 30, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"octopusSuck 1.aseprite": {
|
||||
"frame": { "x": 30, "y": 0, "w": 30, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 30, "h": 20 },
|
||||
"sourceSize": { "w": 30, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"octopusSuck 2.aseprite": {
|
||||
"frame": { "x": 60, "y": 0, "w": 30, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 30, "h": 20 },
|
||||
"sourceSize": { "w": 30, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"octopusSuck 3.aseprite": {
|
||||
"frame": { "x": 90, "y": 0, "w": 30, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 30, "h": 20 },
|
||||
"sourceSize": { "w": 30, "h": 20 },
|
||||
"duration": 100
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "octopusSuck.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 120, "h": 20 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/enemies/octopusSuck.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/img/enemies/pufferFish.aseprite
Normal file
50
assets/img/enemies/pufferFishAttack.json
Normal file
@ -0,0 +1,50 @@
|
||||
{ "frames": {
|
||||
"pufferFish 0.aseprite": {
|
||||
"frame": { "x": 0, "y": 0, "w": 39, "h": 25 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 39, "h": 25 },
|
||||
"sourceSize": { "w": 39, "h": 25 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 1.aseprite": {
|
||||
"frame": { "x": 39, "y": 0, "w": 39, "h": 25 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 39, "h": 25 },
|
||||
"sourceSize": { "w": 39, "h": 25 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 2.aseprite": {
|
||||
"frame": { "x": 78, "y": 0, "w": 39, "h": 25 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 39, "h": 25 },
|
||||
"sourceSize": { "w": 39, "h": 25 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 3.aseprite": {
|
||||
"frame": { "x": 117, "y": 0, "w": 39, "h": 25 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 39, "h": 25 },
|
||||
"sourceSize": { "w": 39, "h": 25 },
|
||||
"duration": 100
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "pufferFishAttack.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 156, "h": 25 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/enemies/pufferFishAttack.png
Normal file
After Width: | Height: | Size: 974 B |
42
assets/img/enemies/pufferFishBigIdle.json
Normal file
@ -0,0 +1,42 @@
|
||||
{ "frames": {
|
||||
"pufferFish 0.aseprite": {
|
||||
"frame": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 1.aseprite": {
|
||||
"frame": { "x": 19, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 2.aseprite": {
|
||||
"frame": { "x": 38, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "pufferFishBigIdle.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 57, "h": 19 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/enemies/pufferFishBigIdle.png
Normal file
After Width: | Height: | Size: 587 B |
50
assets/img/enemies/pufferFishExpand.json
Normal file
@ -0,0 +1,50 @@
|
||||
{ "frames": {
|
||||
"pufferFish 0.aseprite": {
|
||||
"frame": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 1.aseprite": {
|
||||
"frame": { "x": 19, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 2.aseprite": {
|
||||
"frame": { "x": 38, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 3.aseprite": {
|
||||
"frame": { "x": 57, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "pufferFishExpand.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 76, "h": 19 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/enemies/pufferFishExpand.png
Normal file
After Width: | Height: | Size: 768 B |
66
assets/img/enemies/pufferFishIdle.json
Normal file
@ -0,0 +1,66 @@
|
||||
{ "frames": {
|
||||
"pufferFish 0.aseprite": {
|
||||
"frame": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 1.aseprite": {
|
||||
"frame": { "x": 19, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 2.aseprite": {
|
||||
"frame": { "x": 38, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 3.aseprite": {
|
||||
"frame": { "x": 57, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 4.aseprite": {
|
||||
"frame": { "x": 76, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
},
|
||||
"pufferFish 5.aseprite": {
|
||||
"frame": { "x": 95, "y": 0, "w": 19, "h": 19 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 19, "h": 19 },
|
||||
"sourceSize": { "w": 19, "h": 19 },
|
||||
"duration": 100
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "pufferFish.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 114, "h": 19 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/enemies/pufferFishIdle.png
Normal file
After Width: | Height: | Size: 420 B |
BIN
assets/img/enemies/whirlpool.aseprite
Normal file
51
assets/img/enemies/whirlpool.json
Normal file
@ -0,0 +1,51 @@
|
||||
{ "frames": {
|
||||
"whirlpool 0.aseprite": {
|
||||
"frame": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 300
|
||||
},
|
||||
"whirlpool 1.aseprite": {
|
||||
"frame": { "x": 20, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 300
|
||||
},
|
||||
"whirlpool 2.aseprite": {
|
||||
"frame": { "x": 40, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 300
|
||||
},
|
||||
"whirlpool 3.aseprite": {
|
||||
"frame": { "x": 60, "y": 0, "w": 20, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 20, "h": 20 },
|
||||
"sourceSize": { "w": 20, "h": 20 },
|
||||
"duration": 300
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "whirlpool.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 80, "h": 20 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" },
|
||||
{ "name": "Layer 2", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/enemies/whirlpool.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/img/items/air1.png
Normal file
After Width: | Height: | Size: 221 B |
BIN
assets/img/items/air2.png
Normal file
After Width: | Height: | Size: 222 B |
BIN
assets/img/items/air3.png
Normal file
After Width: | Height: | Size: 255 B |
BIN
assets/img/items/flashlight1.png
Normal file
After Width: | Height: | Size: 256 B |
BIN
assets/img/items/flashlight2.png
Normal file
After Width: | Height: | Size: 272 B |
BIN
assets/img/items/flashlight3.png
Normal file
After Width: | Height: | Size: 184 B |
BIN
assets/img/items/flippers1.png
Normal file
After Width: | Height: | Size: 299 B |
BIN
assets/img/items/flippers2.png
Normal file
After Width: | Height: | Size: 358 B |
BIN
assets/img/items/flippers3.png
Normal file
After Width: | Height: | Size: 368 B |
BIN
assets/img/items/stun1.png
Normal file
After Width: | Height: | Size: 168 B |
BIN
assets/img/items/stun2.png
Normal file
After Width: | Height: | Size: 239 B |
BIN
assets/img/items/stun3.png
Normal file
After Width: | Height: | Size: 286 B |
BIN
assets/img/logos/readme-54.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
assets/img/logos/readme.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
assets/img/map/backBack.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
assets/img/map/backFront.png
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 323 KiB |
BIN
assets/img/map/darkness.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
assets/img/map/fish.aseprite
Normal file
26
assets/img/map/fish.json
Normal file
@ -0,0 +1,26 @@
|
||||
{ "frames": {
|
||||
"fish 62.aseprite": {
|
||||
"frame": { "x": 806, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 50
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "fish.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 819, "h": 9 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/map/fish.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/img/map/fishIdle.aseprite
Normal file
130
assets/img/map/fishStill.json
Normal file
@ -0,0 +1,130 @@
|
||||
{ "frames": {
|
||||
"fishIdle 0.aseprite": {
|
||||
"frame": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 1.aseprite": {
|
||||
"frame": { "x": 13, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 2.aseprite": {
|
||||
"frame": { "x": 26, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 3.aseprite": {
|
||||
"frame": { "x": 39, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 4.aseprite": {
|
||||
"frame": { "x": 52, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 5.aseprite": {
|
||||
"frame": { "x": 65, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 6.aseprite": {
|
||||
"frame": { "x": 78, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 7.aseprite": {
|
||||
"frame": { "x": 91, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 8.aseprite": {
|
||||
"frame": { "x": 104, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 9.aseprite": {
|
||||
"frame": { "x": 117, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 10.aseprite": {
|
||||
"frame": { "x": 130, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 11.aseprite": {
|
||||
"frame": { "x": 143, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 12.aseprite": {
|
||||
"frame": { "x": 156, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
},
|
||||
"fishIdle 13.aseprite": {
|
||||
"frame": { "x": 169, "y": 0, "w": 13, "h": 9 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 13, "h": 9 },
|
||||
"sourceSize": { "w": 13, "h": 9 },
|
||||
"duration": 100
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "fishIdle.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 182, "h": 9 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/map/fishStill.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/img/map/shop.aseprite
Normal file
BIN
assets/img/map/shop.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
assets/img/map/shopHighRes.png
Normal file
After Width: | Height: | Size: 29 KiB |
67
assets/img/map/transponder.json
Normal file
@ -0,0 +1,67 @@
|
||||
{ "frames": {
|
||||
"Sprite-0002 0.": {
|
||||
"frame": { "x": 0, "y": 0, "w": 10, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 20 },
|
||||
"sourceSize": { "w": 10, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"Sprite-0002 1.": {
|
||||
"frame": { "x": 10, "y": 0, "w": 10, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 20 },
|
||||
"sourceSize": { "w": 10, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"Sprite-0002 2.": {
|
||||
"frame": { "x": 20, "y": 0, "w": 10, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 20 },
|
||||
"sourceSize": { "w": 10, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"Sprite-0002 3.": {
|
||||
"frame": { "x": 30, "y": 0, "w": 10, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 20 },
|
||||
"sourceSize": { "w": 10, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"Sprite-0002 4.": {
|
||||
"frame": { "x": 40, "y": 0, "w": 10, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 20 },
|
||||
"sourceSize": { "w": 10, "h": 20 },
|
||||
"duration": 100
|
||||
},
|
||||
"Sprite-0002 5.": {
|
||||
"frame": { "x": 50, "y": 0, "w": 10, "h": 20 },
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"spriteSourceSize": { "x": 0, "y": 0, "w": 10, "h": 20 },
|
||||
"sourceSize": { "w": 10, "h": 20 },
|
||||
"duration": 100
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"app": "http://www.aseprite.org/",
|
||||
"version": "1.2.27-x64",
|
||||
"image": "transponder.png",
|
||||
"format": "RGBA8888",
|
||||
"size": { "w": 60, "h": 20 },
|
||||
"scale": "1",
|
||||
"frameTags": [
|
||||
],
|
||||
"layers": [
|
||||
{ "name": "Layer 1", "opacity": 255, "blendMode": "normal" },
|
||||
{ "name": "Layer 2", "opacity": 255, "blendMode": "normal" }
|
||||
],
|
||||
"slices": [
|
||||
]
|
||||
}
|
||||
}
|
BIN
assets/img/map/transponder.png
Normal file
After Width: | Height: | Size: 909 B |
BIN
assets/img/map/tut1.png
Normal file
After Width: | Height: | Size: 286 B |
BIN
assets/img/map/tut2.png
Normal file
After Width: | Height: | Size: 306 B |
50
assets/shaders/pixel.fs
Normal file
@ -0,0 +1,50 @@
|
||||
#version 330
|
||||
|
||||
// Input vertex attributes (from vertex shader)
|
||||
in vec2 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
|
||||
// Input uniform values
|
||||
uniform sampler2D texture0;
|
||||
uniform vec4 colDiffuse;
|
||||
|
||||
// Time fed from CPU
|
||||
uniform float time = 0.0;
|
||||
|
||||
// Output fragment color
|
||||
out vec4 finalColor;
|
||||
|
||||
// Viewport dimensions
|
||||
const vec2 viewport = vec2(1080.0, 720.0);
|
||||
|
||||
// Pixel scaling
|
||||
const vec2 pixelScale = vec2(2.0, 2.0);
|
||||
|
||||
void main()
|
||||
{
|
||||
|
||||
// Calculate the distance to merge pixels
|
||||
float dx = pixelScale.x * (1.0 / viewport.x);
|
||||
float dy = pixelScale.y * (1.0 / viewport.y);
|
||||
|
||||
// Get the base UV coordinate of the pixel
|
||||
vec2 baseUV = fragTexCoord;
|
||||
|
||||
// Use a wave function to translate the pixel UV
|
||||
float X = baseUV.x*0.5+time;
|
||||
float Y = baseUV.y*0.25+time;
|
||||
baseUV.y += cos(X+Y)*0.0025*cos(Y);
|
||||
baseUV.x += sin(X-Y)*0.012*sin(Y);
|
||||
|
||||
// Calculate a UV for this new blocky pixel
|
||||
vec2 pixelatedUV = vec2(dx * floor(baseUV.x / dx), dy * floor(baseUV.y / dy));
|
||||
|
||||
// Rebuild the texture with the new UVs
|
||||
vec3 tc = texture(texture0, pixelatedUV).rgb;
|
||||
|
||||
// Apply a color filter
|
||||
tc = tc + vec3(0, 0.05, 0.15);
|
||||
|
||||
// Build the final pixel
|
||||
finalColor = vec4(tc, 1.0);
|
||||
}
|
1
assets/tileEditor/game.js
Normal file
8
assets/tileEditor/index.html
Normal file
@ -0,0 +1,8 @@
|
||||
<style>
|
||||
html {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
</style>
|
||||
<canvas id="game" width="1080" height="720"></canvas>
|
||||
<script src="game.js"></script>
|
||||
<script src="main.js"></script>
|
314
assets/tileEditor/main.js
Normal file
@ -0,0 +1,314 @@
|
||||
images = ["", "tileset.png"];
|
||||
|
||||
var map = [];
|
||||
var boolMap = [];
|
||||
|
||||
// 0 1 2 3
|
||||
// 4 5 6 7
|
||||
// 8 9 1011
|
||||
// 12131415
|
||||
|
||||
// up right down left
|
||||
var types = [
|
||||
[0, 0, 0, 0, 15],
|
||||
[0, 0, 0, 1, 14],
|
||||
[0, 0, 1, 0, 3],
|
||||
[0, 0, 1, 1, 2],
|
||||
[0, 1, 0, 0, 12],
|
||||
[0, 1, 0, 1, 13],
|
||||
[0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 11],
|
||||
[1, 0, 0, 1, 10],
|
||||
[1, 0, 1, 0, 7],
|
||||
[1, 0, 1, 1, 6],
|
||||
[1, 1, 0, 0, 8],
|
||||
[1, 1, 0, 1, 9],
|
||||
[1, 1, 1, 0, 4],
|
||||
[1, 1, 1, 1, 5]
|
||||
];
|
||||
|
||||
var brushSize = 1;
|
||||
|
||||
var collisions = [];
|
||||
|
||||
for (var y = 0; y < 216; y++) {
|
||||
var arr = [];
|
||||
var arr2 = [];
|
||||
for (var x = 0; x < 72; x++) {
|
||||
arr.push(0);
|
||||
arr2.push(false);
|
||||
}
|
||||
map.push(arr);
|
||||
boolMap.push(arr2);
|
||||
}
|
||||
|
||||
setup(60);
|
||||
|
||||
function onAssetsLoaded() {
|
||||
for (var y = 0; y < 4; y++) {
|
||||
for (var x = 0; x < 4; x++) {
|
||||
var newCanv = document.createElement("canvas");
|
||||
newCanv.width = 10;
|
||||
newCanv.height = 10;
|
||||
var newCtx = newCanv.getContext("2d");
|
||||
newCtx.drawImage(sprites.tileset.spr, x * 10, y * 10, 10, 10, 0, 0, 10, 10);
|
||||
sprites[x + y * 4] = { spr: newCanv, drawLimitSize: 10 };
|
||||
}
|
||||
}
|
||||
parseMap();
|
||||
}
|
||||
|
||||
function update() {}
|
||||
|
||||
function input() {
|
||||
if (keyDown[k.w]) {
|
||||
moveCamera(0, -5);
|
||||
}
|
||||
if (keyDown[k.s]) {
|
||||
moveCamera(0, 5);
|
||||
}
|
||||
if (keyDown[k.a]) {
|
||||
moveCamera(-5, 0);
|
||||
}
|
||||
if (keyDown[k.d]) {
|
||||
moveCamera(5, 0);
|
||||
}
|
||||
|
||||
if (keyPress[k.q]) {
|
||||
if (brushSize > 1) {
|
||||
brushSize--;
|
||||
}
|
||||
}
|
||||
if (keyPress[k.e]) {
|
||||
brushSize++;
|
||||
}
|
||||
|
||||
camera.zoom += scroll;
|
||||
if (camera.zoom < 1) {
|
||||
camera.zoom = 1;
|
||||
}
|
||||
|
||||
if (keyPress[k.ENTER]) {
|
||||
renderMap();
|
||||
}
|
||||
|
||||
if (keyPress[k.SPACE]) {
|
||||
collisions = makeOptimizedCollision();
|
||||
}
|
||||
|
||||
var x = Math.floor(mousePosition().x / 10);
|
||||
var y = Math.floor(mousePosition().y / 10);
|
||||
x = x < 0 ? 0 : x;
|
||||
y = y < 0 ? 0 : y;
|
||||
x = x > map[0].length - 1 ? map[0].length - 1 : x;
|
||||
y = y > map.length - 1 ? map.length - 1 : y;
|
||||
if (mouseDown[0]) {
|
||||
for (var yy = -brushSize / 2; yy < brushSize / 2; yy++) {
|
||||
for (var xx = -brushSize / 2; xx < brushSize / 2; xx++) {
|
||||
var posX = ~~(1 + xx + x);
|
||||
var posY = ~~(1 + yy + y);
|
||||
if (posX > -1 && posY > -1 && posY < map.length && posX < map[0].length) {
|
||||
boolMap[posY][posX] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mouseDown[2]) {
|
||||
for (var yy = -brushSize / 2; yy < brushSize / 2; yy++) {
|
||||
for (var xx = -brushSize / 2; xx < brushSize / 2; xx++) {
|
||||
var posX = ~~(1 + xx + x);
|
||||
var posY = ~~(1 + yy + y);
|
||||
if (posX > -1 && posY > -1 && posY < map.length && posX < map[0].length) {
|
||||
boolMap[posY][posX] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
parseMap();
|
||||
var w = map[0].length * 10;
|
||||
var h = map.length * 10;
|
||||
rect(w / 2, h / 2, w, h, "blue");
|
||||
for (var y = 0; y < map.length; y++) {
|
||||
for (var x = 0; x < map[0].length; x++) {
|
||||
if (map[y][x] !== -1) {
|
||||
img(sprites[map[y][x]], x * 10 + 5, y * 10 + 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(var i=0;i<collisions.length;i++) {
|
||||
rectOut(collisions[i].x,collisions[i].y,collisions[i].width,collisions[i].height,"#ff0000");
|
||||
}
|
||||
}
|
||||
|
||||
function renderMap() {
|
||||
var renderCvs = document.createElement("canvas");
|
||||
renderCvs.width = map[0].length * 10;
|
||||
renderCvs.height = map.length * 10;
|
||||
var renderCtx = renderCvs.getContext("2d");
|
||||
camera.x = 0;
|
||||
camera.y = 0;
|
||||
drawMode = 0;
|
||||
absDraw = true;
|
||||
curCtx = renderCtx;
|
||||
for (var y = 0; y < map.length; y++) {
|
||||
for (var x = 0; x < map[0].length; x++) {
|
||||
if (map[y][x] !== -1) {
|
||||
img(sprites[map[y][x]], x * 10 + 5, y * 10 + 5);
|
||||
|
||||
if (map[y][x] === 5) {
|
||||
var closest = 6;
|
||||
for (var y2 = -4; y2 < 5; y2++) {
|
||||
for (var x2 = -4; x2 < 5; x2++) {
|
||||
if(y2 === 0 && x2 === 0) {
|
||||
continue;
|
||||
}
|
||||
var posX = x + x2;
|
||||
var posY = y + y2;
|
||||
if (posX > -1 && posY > -1 && posY < map.length && posX < map[0].length) {
|
||||
if (boolMap[posY][posX] === false) {
|
||||
var distance = dist({ x: x, y: y }, { x: posX, y: posY });
|
||||
if (distance < closest) {
|
||||
closest = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (closest > 1) {
|
||||
rect(x * 10 + 5, y * 10 + 5, 10, 10, `#000000${Math.round(map_range(closest, 1, 6, 50, 255)).toString(16)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
document.body.appendChild(renderCvs);
|
||||
|
||||
collisions = makeOptimizedCollision();
|
||||
|
||||
var txt = document.createElement("textarea");
|
||||
txt.innerText = JSON.stringify(collisions);
|
||||
document.body.appendChild(txt);
|
||||
}
|
||||
|
||||
function map_range(value, low1, high1, low2, high2) {
|
||||
return low2 + ((high2 - low2) * (value - low1)) / (high1 - low1);
|
||||
}
|
||||
|
||||
function parseMap() {
|
||||
for (var y = 0; y < map.length; y++) {
|
||||
for (var x = 0; x < map[0].length; x++) {
|
||||
if (boolMap[y][x] === false) {
|
||||
map[y][x] = -1;
|
||||
continue;
|
||||
}
|
||||
var t = 0,
|
||||
b = 0,
|
||||
l = 0,
|
||||
r = 0;
|
||||
if (y === 0) {
|
||||
t = 1;
|
||||
} else {
|
||||
t = boolMap[y - 1][x] ? 1 : 0;
|
||||
}
|
||||
if (x === 0) {
|
||||
l = 1;
|
||||
} else {
|
||||
l = boolMap[y][x - 1] ? 1 : 0;
|
||||
}
|
||||
if (y === map.length - 1) {
|
||||
b = 1;
|
||||
} else {
|
||||
b = boolMap[y + 1][x] ? 1 : 0;
|
||||
}
|
||||
if (x === map[0].length - 1) {
|
||||
r = 1;
|
||||
} else {
|
||||
r = boolMap[y][x + 1] ? 1 : 0;
|
||||
}
|
||||
for (var i = 0; i < 16; i++) {
|
||||
if (types[i][0] === t && types[i][1] === r && types[i][2] === b && types[i][3] === l) {
|
||||
map[y][x] = types[i][4];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function makeOptimizedCollision() {
|
||||
var tileCount = 0;
|
||||
var worldW = map[0].length;
|
||||
var worldH = map.length;
|
||||
cols = [];
|
||||
|
||||
// 2d array of booleans for if a tile has gotten a collision made for it
|
||||
var hasCollision = [];
|
||||
|
||||
// fill arrays
|
||||
for (var y = 0; y < worldH; y++) {
|
||||
var hasCollisionRow = [];
|
||||
for (var x = 0; x < worldW; x++) {
|
||||
hasCollisionRow.push(false);
|
||||
}
|
||||
hasCollision.push(hasCollisionRow);
|
||||
}
|
||||
|
||||
// try to make large rectangles that cover multiple walls to make collision more efficient
|
||||
|
||||
for (var y = 0; y < worldH; y++) {
|
||||
for (var x = 0; x < worldW; x++) {
|
||||
if (!hasCollision[y][x] && boolMap[y][x]) {
|
||||
// find right limit
|
||||
var xPos = x;
|
||||
while (xPos < worldW && boolMap[y][xPos]) {
|
||||
xPos++;
|
||||
}
|
||||
xPos--;
|
||||
|
||||
// find bottom limit
|
||||
var yPos = y;
|
||||
var fullRow = true;
|
||||
// go down row by row
|
||||
while (yPos < worldH-1 && boolMap[yPos][xPos] && fullRow) {
|
||||
yPos++;
|
||||
// go through the whole row, make sure it is full
|
||||
var rowX = xPos;
|
||||
while (rowX > -1 && boolMap[yPos][rowX]) {
|
||||
rowX--;
|
||||
}
|
||||
// if the row is not full, stop
|
||||
if (rowX + 1 !== x) {
|
||||
fullRow = false;
|
||||
yPos--;
|
||||
}
|
||||
}
|
||||
|
||||
// track what tiles have gotten collision
|
||||
for (var y2 = y; y2 < yPos + 1; y2++) {
|
||||
for (var x2 = x; x2 < xPos + 1; x2++) {
|
||||
hasCollision[y2][x2] = true;
|
||||
tileCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// find collider dimensions
|
||||
var colX = (x + xPos + 1) / 2;
|
||||
var colY = (y + yPos + 1) / 2;
|
||||
var colW = xPos - x + 1;
|
||||
var colH = yPos - y + 1;
|
||||
|
||||
// add collider
|
||||
cols.push({ x: colX * 10, y: colY * 10, width: colW * 10, height: colH * 10 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`tiles: ${tileCount}, boxes: ${cols.length}`)
|
||||
return cols;
|
||||
}
|
BIN
assets/tileEditor/tileset.png
Normal file
After Width: | Height: | Size: 894 B |
@ -9,10 +9,10 @@ set -e
|
||||
# Make a uni-bundle
|
||||
echo "Creating a fat bundle for all platforms"
|
||||
rm -rf ./bundle/release
|
||||
rm -rf ./bundle/one-breath.zip
|
||||
rm -rf ./bundle/ldgame.zip
|
||||
mkdir -p ./bundle/release
|
||||
cp -r ./assets ./bundle/release
|
||||
cp ./bundle/linux/release/one-breath ./bundle/release/one-breath
|
||||
cp ./bundle/windows/release/one-breath.exe ./bundle/release/one-breath.exe
|
||||
cp ./bundle/linux/release/ldgame ./bundle/release/ldgame
|
||||
cp ./bundle/windows/release/ldgame.exe ./bundle/release/ldgame.exe
|
||||
cd ./bundle/release
|
||||
zip -r ../one-breath.zip ./
|
||||
zip -r ../ldgame.zip ./
|
@ -12,7 +12,7 @@ rm -rf ./bundle/linux/release-x86_64-unknown-linux-gnu.zip
|
||||
mkdir -p ./bundle/linux/release
|
||||
|
||||
echo "Copying binary"
|
||||
cp ./target/x86_64-unknown-linux-gnu/release/one-breath ./bundle/linux/release
|
||||
cp ./target/x86_64-unknown-linux-gnu/release/ldgame ./bundle/linux/release
|
||||
|
||||
echo "Copying assets"
|
||||
cp -r ./assets ./bundle/linux/release
|
||||
|
@ -11,7 +11,7 @@ rm -rf ./bundle/windows/release-x86_64-pc-windows-gnu.zip
|
||||
mkdir -p ./bundle/windows/release
|
||||
|
||||
echo "Copying binary"
|
||||
cp ./target/x86_64-pc-windows-gnu/release/one-breath.exe ./bundle/windows/release
|
||||
cp ./target/x86_64-pc-windows-gnu/release/ldgame.exe ./bundle/windows/release
|
||||
|
||||
echo "Copying assets"
|
||||
cp -r ./assets ./bundle/windows/release
|
||||
|
@ -6,9 +6,10 @@ pub trait EnemyBase {
|
||||
fn render(
|
||||
&mut self,
|
||||
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||
player: &mut Player,
|
||||
resources: &mut GlobalResources,
|
||||
dt: f64,
|
||||
);
|
||||
fn handle_logic(&mut self, player: &mut Player, dt: f64);
|
||||
fn handle_getting_attacked(&mut self, stun_duration: f64);
|
||||
fn handle_logic(&mut self, player: &mut Player, dt: f64) -> u8;
|
||||
fn handle_getting_attacked(&mut self, stun_duration: f64, current_time: f64);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
use raylib::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const JELLYFISH_STUN_DURATION: f64 = 0.75;
|
||||
const JELLYFISH_STUN_DURATION: f64 = 1.5;
|
||||
const JELLYFISH_STUN_REACH: f32 = 20.0;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
@ -28,6 +28,7 @@ impl EnemyBase for JellyFish {
|
||||
fn render(
|
||||
&mut self,
|
||||
context_2d: &mut raylib::prelude::RaylibMode2D<raylib::prelude::RaylibDrawHandle>,
|
||||
_player: &mut Player,
|
||||
resources: &mut GlobalResources,
|
||||
dt: f64,
|
||||
) {
|
||||
@ -76,16 +77,17 @@ impl EnemyBase for JellyFish {
|
||||
&& !is_jelly_stunned;
|
||||
}
|
||||
|
||||
fn handle_logic(&mut self, player: &mut Player, dt: f64) {
|
||||
fn handle_logic(&mut self, player: &mut Player, _dt: f64) -> u8 {
|
||||
// Handle stunning the player
|
||||
if self.do_stun_player {
|
||||
if self.position.distance_to(player.position).abs() <= JELLYFISH_STUN_REACH {
|
||||
player.set_stun_seconds(JELLYFISH_STUN_DURATION);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn handle_getting_attacked(&mut self, stun_duration: f64) {
|
||||
fn handle_getting_attacked(&mut self, stun_duration: f64, _current_time: f64) {
|
||||
self.stunned_timer = stun_duration;
|
||||
self.max_stunned_time = stun_duration;
|
||||
}
|
||||
|
@ -1,2 +1,5 @@
|
||||
pub mod base;
|
||||
pub mod jellyfish;
|
||||
pub mod octopus;
|
||||
pub mod whirlpool;
|
||||
pub mod pufferfish;
|
145
src/entities/enemy/octopus.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use crate::{
|
||||
lib::utils::calculate_linear_slide,
|
||||
pallette::{TRANSLUCENT_RED_64, TRANSLUCENT_WHITE_128},
|
||||
player::Player,
|
||||
};
|
||||
|
||||
use super::base::EnemyBase;
|
||||
use rand::Rng;
|
||||
use raylib::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const OCTOPUS_SUCK_AIR_DELAY: f64 = 3.5;
|
||||
const OCTOPUS_SUCK_AIR_RANGE: f32 = 40.0;
|
||||
const OCTOPUS_SUCK_AIR_DURATION: f64 = 1.0;
|
||||
const OCTOPUS_SUCK_AIR_AMOUNT: f32 = 0.1;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
struct OctopusAirBubble {
|
||||
position: Vector2,
|
||||
speed: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct Octopus {
|
||||
pub position_a: Vector2,
|
||||
pub position_b: Vector2,
|
||||
|
||||
#[serde(skip)]
|
||||
pub current_position: Vector2,
|
||||
|
||||
#[serde(skip)]
|
||||
pub stunned_timer: f64,
|
||||
#[serde(skip)]
|
||||
pub max_stunned_time: f64,
|
||||
|
||||
#[serde(skip)]
|
||||
pub suck_air_time_remaining: f64,
|
||||
#[serde(skip)]
|
||||
suck_air_bubbles: Vec<OctopusAirBubble>,
|
||||
#[serde(skip)]
|
||||
has_taken_air_from_player: bool,
|
||||
}
|
||||
|
||||
impl Octopus {}
|
||||
|
||||
impl EnemyBase for Octopus {
|
||||
fn render(
|
||||
&mut self,
|
||||
context_2d: &mut raylib::prelude::RaylibMode2D<raylib::prelude::RaylibDrawHandle>,
|
||||
player: &mut Player,
|
||||
resources: &mut crate::resources::GlobalResources,
|
||||
dt: f64,
|
||||
) {
|
||||
let is_octopus_stunned = self.stunned_timer > 0.0;
|
||||
|
||||
// Simple sine position
|
||||
let h_trans = (context_2d.get_time() / 8.0).sin().abs() as f32;
|
||||
|
||||
// Modify the current pose
|
||||
let dist_a_to_b = self.position_b - self.position_a;
|
||||
self.current_position = (dist_a_to_b * h_trans) + self.position_a;
|
||||
|
||||
// Render the stun ring
|
||||
if self.max_stunned_time > 0.0 && self.stunned_timer > 0.0 {
|
||||
let stun_ring_alpha =
|
||||
calculate_linear_slide(self.stunned_timer / self.max_stunned_time);
|
||||
context_2d.draw_circle_v(
|
||||
self.current_position,
|
||||
20.0,
|
||||
TRANSLUCENT_RED_64.fade(0.55 * stun_ring_alpha as f32),
|
||||
);
|
||||
self.stunned_timer -= dt;
|
||||
}
|
||||
|
||||
// Every once in a while, start sucking air
|
||||
if (context_2d.get_time() % OCTOPUS_SUCK_AIR_DELAY) < 0.1
|
||||
&& self.suck_air_time_remaining == 0.0
|
||||
&& !is_octopus_stunned
|
||||
{
|
||||
self.suck_air_time_remaining = OCTOPUS_SUCK_AIR_DURATION;
|
||||
self.has_taken_air_from_player = false;
|
||||
|
||||
// Spawn a few air bubbles if the player is in range
|
||||
if player.position.distance_to(self.current_position).abs() <= OCTOPUS_SUCK_AIR_RANGE {
|
||||
for _ in 0..5 {
|
||||
self.suck_air_bubbles.push(OctopusAirBubble {
|
||||
position: player.position,
|
||||
speed: rand::thread_rng().gen_range(0.8..1.3),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle sucking air bubble animation
|
||||
if self.suck_air_time_remaining > 0.0 {
|
||||
// Render and update all bubbles
|
||||
for bubble in self.suck_air_bubbles.iter_mut() {
|
||||
// Get the direction from the bubble to the octopus
|
||||
let direction = (self.current_position - bubble.position).normalized();
|
||||
|
||||
// Render the bubble
|
||||
context_2d.draw_circle_v(bubble.position, 2.0, TRANSLUCENT_WHITE_128);
|
||||
|
||||
// Move the bubble
|
||||
bubble.position += direction * bubble.speed;
|
||||
}
|
||||
|
||||
// Reduce time
|
||||
self.suck_air_time_remaining = (self.suck_air_time_remaining - dt).max(0.0);
|
||||
} else {
|
||||
self.suck_air_bubbles.clear();
|
||||
}
|
||||
|
||||
// Render animation
|
||||
if self.suck_air_time_remaining > 0.0 {
|
||||
resources
|
||||
.octopus_animation_attack
|
||||
.draw(context_2d, self.current_position, 0.0);
|
||||
} else {
|
||||
resources
|
||||
.octopus_animation_regular
|
||||
.draw(context_2d, self.current_position, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_logic(&mut self, player: &mut crate::player::Player, _dt: f64) -> u8 {
|
||||
if self.suck_air_time_remaining > 0.0 && !self.has_taken_air_from_player {
|
||||
if player.position.distance_to(self.current_position).abs() <= OCTOPUS_SUCK_AIR_RANGE {
|
||||
// Take air from the player
|
||||
player.breath_percent -= OCTOPUS_SUCK_AIR_AMOUNT;
|
||||
|
||||
// Set the flag
|
||||
self.has_taken_air_from_player = true;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn handle_getting_attacked(&mut self, stun_duration: f64, _current_time: f64) {
|
||||
self.stunned_timer = stun_duration;
|
||||
self.max_stunned_time = stun_duration;
|
||||
}
|
||||
}
|
156
src/entities/enemy/pufferfish.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use raylib::prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{lib::utils::calculate_linear_slide, pallette::TRANSLUCENT_RED_64};
|
||||
|
||||
use super::base::EnemyBase;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub enum PufferState {
|
||||
SmallIdle,
|
||||
Growing,
|
||||
LargeIdle,
|
||||
Blowing,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Pufferfish {
|
||||
pub position: Vector2,
|
||||
pub is_knocking_back: bool,
|
||||
pub time_knocking_back: f64,
|
||||
|
||||
pub inflate_timer: f64,
|
||||
pub is_large: bool,
|
||||
pub stun_timer: f64,
|
||||
pub puffer_state: PufferState,
|
||||
}
|
||||
|
||||
impl EnemyBase for Pufferfish {
|
||||
fn render(
|
||||
&mut self,
|
||||
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||
player: &mut crate::player::Player,
|
||||
resources: &mut crate::resources::GlobalResources,
|
||||
dt: f64,
|
||||
) {
|
||||
|
||||
let is_stunned = self.stun_timer > 0.0;
|
||||
|
||||
// Render the stun ring
|
||||
if is_stunned {
|
||||
// println!("Stunned");
|
||||
let stun_ring_alpha =
|
||||
calculate_linear_slide(self.stun_timer / 1.0);
|
||||
context_2d.draw_circle_v(
|
||||
self.position,
|
||||
12.0,
|
||||
TRANSLUCENT_RED_64.fade(0.55 * stun_ring_alpha as f32),
|
||||
);
|
||||
|
||||
self.stun_timer -= dt;
|
||||
}
|
||||
|
||||
|
||||
let angle = player.position.angle_to(self.position).to_degrees();
|
||||
|
||||
|
||||
match self.puffer_state {
|
||||
PufferState::SmallIdle => {
|
||||
resources.pufferfish_small.draw(
|
||||
context_2d,
|
||||
Vector2 {
|
||||
x: self.position.x,
|
||||
y: self.position.y,
|
||||
},
|
||||
angle,
|
||||
);
|
||||
|
||||
if self.position.distance_to(player.position).abs() <= 100.0 && self.inflate_timer > 2.0{
|
||||
self.puffer_state = PufferState::Growing;
|
||||
}
|
||||
self.is_large = false;
|
||||
},
|
||||
PufferState::Growing => {
|
||||
self.inflate_timer = 0.0;
|
||||
resources.pufferfish_expand.draw(
|
||||
context_2d,
|
||||
Vector2 {
|
||||
x: self.position.x,
|
||||
y: self.position.y,
|
||||
},
|
||||
angle,
|
||||
);
|
||||
|
||||
if resources.pufferfish_expand.get_current_frame_id(context_2d) == 3 {
|
||||
self.puffer_state = PufferState::LargeIdle;
|
||||
}
|
||||
self.is_large = true;
|
||||
|
||||
},
|
||||
PufferState::LargeIdle => {
|
||||
self.inflate_timer = 0.0;
|
||||
resources.pufferfish_big.draw(
|
||||
context_2d,
|
||||
Vector2 {
|
||||
x: self.position.x,
|
||||
y: self.position.y,
|
||||
},
|
||||
angle,
|
||||
);
|
||||
|
||||
if self.position.distance_to(player.position).abs() <= 65.0{
|
||||
self.puffer_state = PufferState::Blowing;
|
||||
self.is_knocking_back = true;
|
||||
self.time_knocking_back = 0.0;
|
||||
}
|
||||
|
||||
self.is_large = true;
|
||||
},
|
||||
PufferState::Blowing => {
|
||||
|
||||
resources.pufferfish_attack.draw(
|
||||
context_2d,
|
||||
Vector2 {
|
||||
x: self.position.x,
|
||||
y: self.position.y,
|
||||
},
|
||||
angle,
|
||||
);
|
||||
|
||||
|
||||
if resources.pufferfish_expand.get_current_frame_id(context_2d) == 3 && self.inflate_timer > 1.0{
|
||||
self.puffer_state = PufferState::SmallIdle;
|
||||
self.inflate_timer = 0.0;
|
||||
}
|
||||
self.is_large = false;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_logic(&mut self, player: &mut crate::player::Player, dt: f64) -> u8 {
|
||||
|
||||
|
||||
|
||||
self.inflate_timer += dt;
|
||||
self.time_knocking_back += dt;
|
||||
|
||||
if self.time_knocking_back >= 0.5{
|
||||
self.is_knocking_back = false;
|
||||
}
|
||||
|
||||
if self.position.distance_to(player.position).abs() > 120.0 && self.is_large {
|
||||
self.puffer_state = PufferState::Blowing;
|
||||
self.inflate_timer = 0.0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
fn handle_getting_attacked(&mut self, stun_duration: f64, current_time: f64) {
|
||||
|
||||
self.stun_timer = stun_duration;
|
||||
|
||||
}
|
||||
}
|
52
src/entities/enemy/whirlpool.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use raylib::prelude::*;
|
||||
|
||||
use super::base::EnemyBase;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct Whirlpool{
|
||||
pub position: Vector2,
|
||||
|
||||
// Track if it needs removing
|
||||
pub should_remove: bool,
|
||||
|
||||
// variable for tracking rotation
|
||||
pub rotation: f32,
|
||||
}
|
||||
|
||||
impl Whirlpool{
|
||||
|
||||
// hook to see if item needs removing
|
||||
pub fn should_remove(&self) -> bool{
|
||||
return self.should_remove;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl EnemyBase for Whirlpool{
|
||||
fn render(
|
||||
&mut self,
|
||||
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||
player: &mut crate::player::Player,
|
||||
resources: &mut crate::resources::GlobalResources,
|
||||
dt: f64,
|
||||
) {
|
||||
|
||||
resources.whirlpool.draw(context_2d, Vector2{x: self.position.x, y: self.position.y}, self.rotation);
|
||||
self.rotation += 1.0;
|
||||
}
|
||||
|
||||
fn handle_logic(&mut self, player: &mut crate::player::Player, dt: f64) -> u8 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Whirlpool removed if shoot
|
||||
fn handle_getting_attacked(&mut self, stun_duration: f64, current_time: f64) {
|
||||
self.should_remove = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,14 +1,7 @@
|
||||
use rand::{prelude::ThreadRng, Rng};
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{
|
||||
gamecore::GameCore, lib::utils::triangles::rotate_vector, player::Player, world::World,
|
||||
};
|
||||
|
||||
const FISH_FOLLOW_PLAYER_DISTANCE: f32 = 30.0;
|
||||
const FISH_FOLLOW_PLAYER_SPEED: f32 = 1.8;
|
||||
const FISH_FOLLOW_PLAYER_SPEED_FAST: f32 = FISH_FOLLOW_PLAYER_SPEED * 3.0;
|
||||
const FISH_ATTACH_RADIUS: f32 = 20.0;
|
||||
use crate::{gamecore::{self, GameCore}, lib::wrappers::audio::player::AudioPlayer, player::Player, resources::GlobalResources};
|
||||
|
||||
const FISH_VISION: f32 = 25.0;
|
||||
const FISH_MAX_SPEED: f32 = 2.0;
|
||||
@ -19,15 +12,18 @@ const FISH_FACTOR_COHESION: f32 = 0.1;
|
||||
const FISH_SEPARATION_DISTANCE: f32 = 15.0;
|
||||
const FISH_FACTOR_SEPARATION: f32 = 1.5;
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FishEntity {
|
||||
position: Vector2,
|
||||
direction: Vector2,
|
||||
velocity: Vector2,
|
||||
pub following_player: bool,
|
||||
current_frame: u8,
|
||||
animation_counter: u32,
|
||||
size: Vector2,
|
||||
color: u8,
|
||||
rng: ThreadRng,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl FishEntity {
|
||||
@ -35,16 +31,16 @@ impl FishEntity {
|
||||
let mut rng = rand::thread_rng();
|
||||
Self {
|
||||
position: position,
|
||||
direction: Vector2::zero(),
|
||||
direction: Vector2 {
|
||||
x: rng.gen_range(0.0..1.0),
|
||||
y: rng.gen_range(0.0..1.0),
|
||||
},
|
||||
velocity: Vector2::zero(),
|
||||
following_player: false,
|
||||
animation_counter: 0,
|
||||
current_frame: 0,
|
||||
size: Vector2 { x: 5.0, y: 8.0 },
|
||||
color: Color {
|
||||
r: rng.gen_range(128..225),
|
||||
g: rng.gen_range(128..225),
|
||||
b: rng.gen_range(128..225),
|
||||
a: 140,
|
||||
},
|
||||
color: rng.gen_range(0..6),
|
||||
rng,
|
||||
}
|
||||
}
|
||||
@ -57,7 +53,12 @@ impl FishEntity {
|
||||
return output;
|
||||
}
|
||||
|
||||
pub fn handle_follow_player(&mut self, player: &Player, dt: f64, other_fish: &Vec<FishEntity>) {
|
||||
pub fn handle_follow_player(
|
||||
&mut self,
|
||||
player: &Player,
|
||||
_dt: f64,
|
||||
other_fish: &Vec<FishEntity>,
|
||||
) {
|
||||
let mut acceleration: Vector2 = Vector2::zero();
|
||||
|
||||
let mut steer: Vector2 = Vector2::zero();
|
||||
@ -134,69 +135,72 @@ impl FishEntity {
|
||||
self.position += self.velocity;
|
||||
}
|
||||
|
||||
pub fn handle_free_movement(&mut self, player: &mut Player, dt: f64) {
|
||||
// Distance and direction to player
|
||||
let dist_to_player = player.position - self.position;
|
||||
let dist_to_player_lin = self.position.distance_to(player.position);
|
||||
let mut direction_to_player = dist_to_player;
|
||||
direction_to_player.normalize();
|
||||
|
||||
pub fn handle_free_movement(&mut self, player: &mut Player, _dt: f64) -> bool {
|
||||
// Handle player picking up fish
|
||||
if player.position.distance_to(self.position).abs() <= player.size.y * 2.2 {
|
||||
self.following_player = true;
|
||||
self.velocity = self.direction.normalized();
|
||||
self.current_frame = 0;
|
||||
self.animation_counter = 0;
|
||||
|
||||
// Add currency to the player
|
||||
player.coins += 1;
|
||||
}
|
||||
|
||||
// Look at the player;
|
||||
self.position = self.position;
|
||||
self.direction = direction_to_player;
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn update_position(&mut self, player: &mut Player, dt: f64, other_fish: &Vec<FishEntity>) {
|
||||
pub fn update_position(&mut self, player: &mut Player, dt: f64, other_fish: &Vec<FishEntity>) -> bool{
|
||||
if self.following_player {
|
||||
self.handle_follow_player(player, dt, other_fish);
|
||||
} else {
|
||||
self.handle_free_movement(player, dt);
|
||||
if self.handle_free_movement(player, dt) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||
resources: &mut GlobalResources,
|
||||
) {
|
||||
// Direction
|
||||
let direction = (Vector2::zero().angle_to(self.direction.normalized())).to_degrees();
|
||||
|
||||
self.animation_counter += 1;
|
||||
|
||||
// swimming
|
||||
if self.following_player {
|
||||
if self.animation_counter % 3 == 0 {
|
||||
self.current_frame += 1;
|
||||
if self.current_frame == 8 {
|
||||
self.current_frame = 0;
|
||||
}
|
||||
}
|
||||
resources.fish_animation_swim.draw_frame(
|
||||
context_2d,
|
||||
self.position,
|
||||
direction,
|
||||
(self.current_frame + self.color * 9) as u32,
|
||||
);
|
||||
// idle
|
||||
} else {
|
||||
if self.animation_counter % 10 == 0 {
|
||||
if self.current_frame == 0 {
|
||||
self.current_frame = 1;
|
||||
} else {
|
||||
self.current_frame = 0;
|
||||
}
|
||||
}
|
||||
resources.fish_animation_idle.draw_frame(
|
||||
context_2d,
|
||||
self.position,
|
||||
direction,
|
||||
(self.current_frame + self.color * 2) as u32,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, context_2d: &mut RaylibMode2D<RaylibDrawHandle>) {
|
||||
// Direction
|
||||
let direction =
|
||||
Vector2::zero().angle_to(self.direction.normalized()) + (90.0 as f32).to_radians();
|
||||
|
||||
// Get the corners of the fish
|
||||
let fish_front = rotate_vector(
|
||||
Vector2 {
|
||||
x: 0.0,
|
||||
y: (self.size.y / 2.0) * -1.0,
|
||||
},
|
||||
direction,
|
||||
);
|
||||
let fish_bl = rotate_vector(
|
||||
Vector2 {
|
||||
x: (self.size.x / 2.0) * -1.0,
|
||||
y: (self.size.y / 2.0),
|
||||
},
|
||||
direction,
|
||||
);
|
||||
let fish_br = rotate_vector(
|
||||
Vector2 {
|
||||
x: (self.size.x / 2.0),
|
||||
y: (self.size.y / 2.0),
|
||||
},
|
||||
direction,
|
||||
);
|
||||
|
||||
// Draw the fish as a triangle with rotation
|
||||
context_2d.draw_triangle(
|
||||
self.position + fish_front,
|
||||
self.position + fish_bl,
|
||||
self.position + fish_br,
|
||||
self.color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,13 @@ use std::{fmt, fs::File, io::BufReader};
|
||||
use raylib::{
|
||||
camera::Camera2D, math::Vector2, prelude::RaylibDrawHandle, RaylibHandle, RaylibThread,
|
||||
};
|
||||
|
||||
use failure::Error;
|
||||
use crate::{
|
||||
player::{Player, PlayerInventory},
|
||||
resources::GlobalResources,
|
||||
world::World,
|
||||
};
|
||||
|
||||
use failure::Error;
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Overall states for the game
|
||||
@ -25,6 +23,8 @@ pub enum GameState {
|
||||
GameQuit,
|
||||
InGame,
|
||||
GameEnd,
|
||||
InShop,
|
||||
WinGame,
|
||||
}
|
||||
|
||||
impl fmt::Display for GameState {
|
||||
@ -76,6 +76,20 @@ impl GameProgress {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(&mut self, new_progress: &GameProgress) {
|
||||
// Bring in new data
|
||||
self.coins = new_progress.coins;
|
||||
self.inventory = new_progress.inventory.clone();
|
||||
self.max_depth = self.max_depth.max(new_progress.max_depth);
|
||||
// self.fastest_time = self.fastest_time.min(new_progress.fastest_time);
|
||||
|
||||
// Write to file
|
||||
let result = self.to_file("./savestate.json".to_string());
|
||||
if result.is_err() {
|
||||
println!("Could not save game state. Holding in RAM");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure contains the entire game state, and should be passed around to various logic functions.
|
||||
@ -134,8 +148,6 @@ impl GameCore {
|
||||
}
|
||||
|
||||
pub fn switch_state(&mut self, new_state: GameState, draw_handle: Option<&RaylibDrawHandle>) {
|
||||
debug!("Switching global state to: {}", new_state);
|
||||
|
||||
self.last_state = self.state;
|
||||
self.state = new_state;
|
||||
|
||||
|
293
src/items.rs
@ -1,46 +1,325 @@
|
||||
use raylib::{
|
||||
color::Color,
|
||||
math::{Rectangle, Vector2},
|
||||
prelude::{RaylibDraw, RaylibDrawHandle},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::resources::GlobalResources;
|
||||
|
||||
pub trait ItemBase {
|
||||
fn get_cost(&self) -> u32;
|
||||
fn get_level(&self) -> u8;
|
||||
fn get_name(&self) -> String;
|
||||
fn get_description(&self) -> String;
|
||||
fn get_texture(
|
||||
&self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
resources: &GlobalResources,
|
||||
dest: Rectangle,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct StunGun {
|
||||
pub range: f32,
|
||||
pub duration: f64,
|
||||
pub level: u8,
|
||||
cost: u32,
|
||||
}
|
||||
|
||||
impl StunGun {
|
||||
pub fn lvl1() -> Self {
|
||||
Self {
|
||||
range: 30.0,
|
||||
duration: 0.75,
|
||||
duration: 2.0,
|
||||
level: 1,
|
||||
cost: 15,
|
||||
}
|
||||
}
|
||||
pub fn lvl2() -> Self {
|
||||
Self {
|
||||
range: 60.0,
|
||||
duration: 1.25,
|
||||
duration: 2.5,
|
||||
level: 2,
|
||||
cost: 25,
|
||||
}
|
||||
}
|
||||
pub fn lvl3() -> Self {
|
||||
Self {
|
||||
range: 80.0,
|
||||
duration: 3.0,
|
||||
level: 3,
|
||||
cost: 40,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct AirBag;
|
||||
impl ItemBase for StunGun {
|
||||
fn get_cost(&self) -> u32 {
|
||||
self.cost
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
return "Stun Bomb".to_string();
|
||||
}
|
||||
|
||||
fn get_description(&self) -> String {
|
||||
return "Right click to stun enemies\n one use per dive".to_string();
|
||||
}
|
||||
|
||||
fn get_texture(
|
||||
&self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
resources: &GlobalResources,
|
||||
dest: Rectangle,
|
||||
) {
|
||||
let texture = match self.get_level() {
|
||||
1 => (&resources.stun_gun_one),
|
||||
2 => (&resources.stun_gun_two),
|
||||
3 | _ => (&resources.stun_gun_three),
|
||||
};
|
||||
|
||||
draw_handle.draw_texture_pro(
|
||||
texture,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: texture.width as f32,
|
||||
height: texture.height as f32,
|
||||
},
|
||||
dest,
|
||||
Vector2 { x: 0.0, y: 0.0 },
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
fn get_level(&self) -> u8 {
|
||||
self.level
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Flashlight;
|
||||
pub struct AirBag {
|
||||
pub extra_oxygen: f32,
|
||||
pub level: u8,
|
||||
cost: u32,
|
||||
}
|
||||
|
||||
impl AirBag {
|
||||
pub fn lvl1() -> Self {
|
||||
Self {
|
||||
extra_oxygen: 0.15,
|
||||
level: 1,
|
||||
cost: 25,
|
||||
}
|
||||
}
|
||||
pub fn lvl2() -> Self {
|
||||
Self {
|
||||
extra_oxygen: 0.30,
|
||||
level: 2,
|
||||
cost: 35,
|
||||
}
|
||||
}
|
||||
pub fn lvl3() -> Self {
|
||||
Self {
|
||||
extra_oxygen: 0.45,
|
||||
level: 3,
|
||||
cost: 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBase for AirBag {
|
||||
fn get_cost(&self) -> u32 {
|
||||
self.cost
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
return "Bag of Air".to_string();
|
||||
}
|
||||
|
||||
fn get_description(&self) -> String {
|
||||
return "Its.. a bag.\nFilled with air. Duh".to_string();
|
||||
}
|
||||
|
||||
fn get_texture(
|
||||
&self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
resources: &GlobalResources,
|
||||
dest: Rectangle,
|
||||
) {
|
||||
let texture = match self.get_level() {
|
||||
1 => (&resources.air_one),
|
||||
2 => (&resources.air_two),
|
||||
3 | _ => (&resources.air_three),
|
||||
};
|
||||
|
||||
draw_handle.draw_texture_pro(
|
||||
texture,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: texture.width as f32,
|
||||
height: texture.height as f32,
|
||||
},
|
||||
dest,
|
||||
Vector2 { x: 0.0, y: 0.0 },
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
fn get_level(&self) -> u8 {
|
||||
self.level
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Flashlight {
|
||||
pub radius: f32,
|
||||
pub level: u8,
|
||||
cost: u32,
|
||||
}
|
||||
|
||||
impl Flashlight {
|
||||
pub fn lvl1() -> Self {
|
||||
Self {
|
||||
radius: 0.25,
|
||||
level: 1,
|
||||
cost: 20,
|
||||
}
|
||||
}
|
||||
pub fn lvl2() -> Self {
|
||||
Self {
|
||||
radius: 0.5,
|
||||
level: 2,
|
||||
cost: 30,
|
||||
}
|
||||
}
|
||||
pub fn lvl3() -> Self {
|
||||
Self {
|
||||
radius: 1.0,
|
||||
level: 3,
|
||||
cost: 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBase for Flashlight {
|
||||
fn get_cost(&self) -> u32 {
|
||||
self.cost
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
return "Flashlight".to_string();
|
||||
}
|
||||
|
||||
fn get_description(&self) -> String {
|
||||
return "See better for longer".to_string();
|
||||
}
|
||||
|
||||
fn get_texture(
|
||||
&self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
resources: &GlobalResources,
|
||||
dest: Rectangle,
|
||||
) {
|
||||
let texture = match self.get_level() {
|
||||
1 => (&resources.flashlight_one),
|
||||
2 => (&resources.flashlight_two),
|
||||
3 | _ => (&resources.flashlight_three),
|
||||
};
|
||||
|
||||
draw_handle.draw_texture_pro(
|
||||
texture,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: texture.width as f32,
|
||||
height: texture.height as f32,
|
||||
},
|
||||
dest,
|
||||
Vector2 { x: 0.0, y: 0.0 },
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
fn get_level(&self) -> u8 {
|
||||
self.level
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct Flippers {
|
||||
pub speed_increase: f32,
|
||||
pub level: u8,
|
||||
cost: u32,
|
||||
}
|
||||
|
||||
impl Flippers {
|
||||
pub fn lvl1() -> Self {
|
||||
Self {
|
||||
speed_increase: 1.2
|
||||
speed_increase: 1.1,
|
||||
level: 1,
|
||||
cost: 30,
|
||||
}
|
||||
}
|
||||
pub fn lvl2() -> Self {
|
||||
Self {
|
||||
speed_increase: 1.5
|
||||
speed_increase: 1.2,
|
||||
level: 2,
|
||||
cost: 40,
|
||||
}
|
||||
}
|
||||
pub fn lvl3() -> Self {
|
||||
Self {
|
||||
speed_increase: 1.3,
|
||||
level: 3,
|
||||
cost: 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBase for Flippers {
|
||||
fn get_cost(&self) -> u32 {
|
||||
self.cost
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
return "Flippers".to_string();
|
||||
}
|
||||
|
||||
fn get_description(&self) -> String {
|
||||
return "Swim faster, and look stupid\nat the same time!".to_string();
|
||||
}
|
||||
|
||||
fn get_texture(
|
||||
&self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
resources: &GlobalResources,
|
||||
dest: Rectangle,
|
||||
) {
|
||||
let texture = match self.get_level() {
|
||||
1 => (&resources.flippers_one),
|
||||
2 => (&resources.flippers_two),
|
||||
3 | _ => (&resources.flippers_three),
|
||||
};
|
||||
|
||||
draw_handle.draw_texture_pro(
|
||||
texture,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: texture.width as f32,
|
||||
height: texture.height as f32,
|
||||
},
|
||||
dest,
|
||||
Vector2 { x: 0.0, y: 0.0 },
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
fn get_level(&self) -> u8 {
|
||||
self.level
|
||||
}
|
||||
}
|
||||
|
71
src/lib/utils/button.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use raylib::prelude::*;
|
||||
|
||||
pub struct OnScreenButton {
|
||||
bounds: Rectangle,
|
||||
text: String,
|
||||
background: Color,
|
||||
border: Color,
|
||||
border_hover: Color,
|
||||
font_px: i32,
|
||||
draw_border: bool,
|
||||
}
|
||||
|
||||
impl OnScreenButton {
|
||||
pub fn new(
|
||||
text: String,
|
||||
bounds: Rectangle,
|
||||
background: Color,
|
||||
border: Color,
|
||||
border_hover: Color,
|
||||
font_px: i32,
|
||||
draw_border: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
text,
|
||||
background,
|
||||
border,
|
||||
border_hover,
|
||||
font_px,
|
||||
draw_border,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_hovered(&self, draw_handle: &RaylibDrawHandle) -> bool {
|
||||
return self
|
||||
.bounds
|
||||
.check_collision_point_rec(draw_handle.get_mouse_position());
|
||||
}
|
||||
|
||||
pub fn render(&self, draw_handle: &mut RaylibDrawHandle) {
|
||||
// Draw the button background
|
||||
draw_handle.draw_rectangle_rec(self.bounds, self.background);
|
||||
|
||||
// Check mouse info
|
||||
let is_being_hovered = self.is_hovered(draw_handle);
|
||||
|
||||
// Render the border
|
||||
if self.draw_border {
|
||||
draw_handle.draw_rectangle_lines_ex(
|
||||
self.bounds,
|
||||
3,
|
||||
match is_being_hovered {
|
||||
true => self.border_hover,
|
||||
false => self.border,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Render the text
|
||||
draw_handle.draw_text(
|
||||
&self.text,
|
||||
self.bounds.x as i32 + 10,
|
||||
self.bounds.y as i32 + ((self.bounds.height as i32 - self.font_px) / 2),
|
||||
self.font_px,
|
||||
match is_being_hovered && !self.draw_border {
|
||||
true => self.border_hover,
|
||||
false => self.border,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod profiler;
|
||||
pub mod triangles;
|
||||
pub mod button;
|
||||
|
||||
pub fn calculate_linear_slide(playthrough_percent: f64) -> f64 {
|
||||
if playthrough_percent < 0.25 {
|
||||
|
@ -39,3 +39,10 @@ impl std::ops::Deref for AudioPlayer {
|
||||
&self.backend
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl std::ops::DerefMut for AudioPlayer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.backend
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
use std::usize;
|
||||
|
||||
use raylib::prelude::*;
|
||||
|
||||
pub struct FrameRange {
|
||||
pub min: usize,
|
||||
pub max: usize,
|
||||
}
|
||||
|
||||
pub struct ComplexAnimationTool {
|
||||
sprite_sheet: Texture2D,
|
||||
frames_per_second: f32,
|
||||
frame_size: Vector2,
|
||||
sprite_sheet_size_frames: Vector2
|
||||
}
|
||||
|
||||
impl ComplexAnimationTool {
|
||||
pub fn render_loop(&self, context_2d: &mut RaylibMode2D<RaylibDrawHandle>, bounds: Rectangle, rotation: f32, range: &FrameRange) {
|
||||
|
||||
}
|
||||
|
||||
pub fn render_frame(&self, context_2d: &mut RaylibMode2D<RaylibDrawHandle>, bounds: Rectangle, rotation: f32, id: usize) {
|
||||
|
||||
// Convert the ID to an xy
|
||||
let col_id = id % self.sprite_sheet_size_frames.x as usize;
|
||||
let row_id = id / self.sprite_sheet_size_frames.y as usize;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,3 +1,2 @@
|
||||
pub mod audio;
|
||||
pub mod animation;
|
||||
pub mod complexanimation;
|
@ -2,7 +2,7 @@ use raylib::prelude::*;
|
||||
|
||||
use crate::{
|
||||
gamecore::{GameCore, GameState},
|
||||
lib::wrappers::audio::player::AudioPlayer,
|
||||
lib::{utils::button::OnScreenButton, wrappers::audio::player::AudioPlayer},
|
||||
};
|
||||
|
||||
use super::screen::Screen;
|
||||
@ -22,13 +22,14 @@ impl Screen for GameEndScreen {
|
||||
&mut self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
_thread: &RaylibThread,
|
||||
audio_system: &mut AudioPlayer,
|
||||
_audio_system: &mut AudioPlayer,
|
||||
game_core: &mut GameCore,
|
||||
) -> Option<GameState> {
|
||||
let mouse_position = draw_handle.get_mouse_position();
|
||||
draw_handle.clear_background(Color::GRAY);
|
||||
// TODO: Maybe we can stick some art here?
|
||||
|
||||
// Render the background
|
||||
draw_handle.draw_texture(&game_core.resources.shop_background, 0, 0, Color::WHITE);
|
||||
|
||||
// Window dimensions
|
||||
let win_height = draw_handle.get_screen_height();
|
||||
@ -53,75 +54,48 @@ impl Screen for GameEndScreen {
|
||||
// Render heading text
|
||||
draw_handle.draw_text(
|
||||
"OUT OF BREATH",
|
||||
(win_width / 2) - 80,
|
||||
(win_width / 2) - ((SCREEN_PANEL_SIZE.x as i32 + 6) / 2) + 25,
|
||||
(win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 10,
|
||||
40,
|
||||
30,
|
||||
Color::BLACK,
|
||||
);
|
||||
|
||||
// TODO: Save game progress
|
||||
// Render message
|
||||
draw_handle.draw_text(
|
||||
"Your clone can now buy items ",
|
||||
((win_width / 2) - ((SCREEN_PANEL_SIZE.x as i32 + 6) / 2))
|
||||
+ (0.15 * SCREEN_PANEL_SIZE.x) as i32,
|
||||
(win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 50,
|
||||
15,
|
||||
Color::BLACK,
|
||||
);
|
||||
|
||||
// Creates
|
||||
let go_to_menu_button = OnScreenButton::new(
|
||||
String::from("Return to shop"),
|
||||
Rectangle {
|
||||
x: (((win_width / 2) - ((SCREEN_PANEL_SIZE.x as i32 + 6) / 2) + 5)
|
||||
+ (0.15 * SCREEN_PANEL_SIZE.x) as i32) as f32,
|
||||
y: (((win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 90) as f32) + 100.0,
|
||||
width: 210.0,
|
||||
height: 50.0,
|
||||
},
|
||||
Color::WHITE,
|
||||
Color::BLACK,
|
||||
Color::GRAY,
|
||||
25,
|
||||
true,
|
||||
);
|
||||
|
||||
// // Close and quit buttons
|
||||
// let bottom_left_button_dimensions = Rectangle {
|
||||
// x: (win_width as f32 / 2.0) - (SCREEN_PANEL_SIZE.x / 2.0) + 5.0,
|
||||
// y: (win_height as f32 / 2.0) + (SCREEN_PANEL_SIZE.y / 2.0) - 50.0,
|
||||
// width: (SCREEN_PANEL_SIZE.x / 2.0) - 15.0,
|
||||
// height: 40.0,
|
||||
// };
|
||||
// let bottom_right_button_dimensions = Rectangle {
|
||||
// x: (win_width as f32 / 2.0) + 5.0,
|
||||
// y: bottom_left_button_dimensions.y,
|
||||
// width: bottom_left_button_dimensions.width,
|
||||
// height: bottom_left_button_dimensions.height,
|
||||
// };
|
||||
// render button
|
||||
go_to_menu_button.render(draw_handle);
|
||||
|
||||
// // Check if the mouse is over either button
|
||||
// let mouse_over_bottom_left_button =
|
||||
// bottom_left_button_dimensions.check_collision_point_rec(mouse_position);
|
||||
// let mouse_over_bottom_right_button =
|
||||
// bottom_right_button_dimensions.check_collision_point_rec(mouse_position);
|
||||
|
||||
// // Render buttons
|
||||
// draw_handle.draw_rectangle_lines_ex(
|
||||
// bottom_left_button_dimensions,
|
||||
// 3,
|
||||
// match mouse_over_bottom_left_button {
|
||||
// true => Color::GRAY,
|
||||
// false => Color::BLACK,
|
||||
// },
|
||||
// );
|
||||
// draw_handle.draw_text(
|
||||
// "Quit",
|
||||
// bottom_left_button_dimensions.x as i32 + 15,
|
||||
// bottom_left_button_dimensions.y as i32 + 5,
|
||||
// 30,
|
||||
// Color::BLACK,
|
||||
// );
|
||||
// draw_handle.draw_rectangle_lines_ex(
|
||||
// bottom_right_button_dimensions,
|
||||
// 3,
|
||||
// match mouse_over_bottom_right_button {
|
||||
// true => Color::GRAY,
|
||||
// false => Color::BLACK,
|
||||
// },
|
||||
// );
|
||||
// draw_handle.draw_text(
|
||||
// "Close",
|
||||
// bottom_right_button_dimensions.x as i32 + 15,
|
||||
// bottom_right_button_dimensions.y as i32 + 5,
|
||||
// 30,
|
||||
// Color::BLACK,
|
||||
// );
|
||||
|
||||
// // Handle click actions on the buttons
|
||||
// if draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) {
|
||||
// if mouse_over_bottom_left_button {
|
||||
// return Some(GameState::GameQuit);
|
||||
// } else if mouse_over_bottom_right_button {
|
||||
// return Some(game_core.last_state);
|
||||
// }
|
||||
// }
|
||||
// If the player clicks on the button send them to shop
|
||||
if go_to_menu_button.is_hovered(draw_handle)
|
||||
&& draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON)
|
||||
{
|
||||
return Some(GameState::InShop);
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ pub fn render_hud(
|
||||
+ slider_bound_height,
|
||||
};
|
||||
|
||||
draw_handle.draw_rectangle(10, 10, 20, 700, Color::new(0, 50, 255, 50));
|
||||
draw_handle.draw_rectangle( 10, 710 - (game_core.player.breath_percent * 450.0) as i32, 20, (game_core.player.breath_percent * 450.0) as i32, Color::new(0, 50, 255, 125));
|
||||
|
||||
// Render the base of the slider
|
||||
draw_handle.draw_rectangle(
|
||||
(progress_slider_position.x - slider_bound_height) as i32,
|
||||
|
@ -2,29 +2,24 @@ mod hud;
|
||||
mod playerlogic;
|
||||
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{
|
||||
entities::enemy::base::EnemyBase,
|
||||
gamecore::{GameCore, GameState},
|
||||
lib::wrappers::audio::player::AudioPlayer,
|
||||
pallette::{SKY, WATER},
|
||||
};
|
||||
use crate::{entities::enemy::{base::EnemyBase, whirlpool::Whirlpool}, gamecore::{self, GameCore, GameState}, lib::wrappers::audio::player::AudioPlayer};
|
||||
|
||||
use super::screen::Screen;
|
||||
|
||||
pub enum InGameState {
|
||||
BUYING,
|
||||
SWIMMING,
|
||||
}
|
||||
use crate::entities::fish::FishEntity;
|
||||
|
||||
pub struct InGameScreen {
|
||||
current_state: InGameState,
|
||||
shader_time_var_location: i32,
|
||||
swim_playing: bool,
|
||||
}
|
||||
|
||||
impl InGameScreen {
|
||||
pub fn new() -> Self {
|
||||
pub unsafe fn new(game_core: &GameCore) -> Self {
|
||||
Self {
|
||||
current_state: InGameState::SWIMMING,
|
||||
shader_time_var_location: raylib::ffi::GetShaderLocation(
|
||||
*game_core.resources.pixel_shader,
|
||||
rstr!("time").as_ptr(),
|
||||
),
|
||||
swim_playing: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +27,8 @@ impl InGameScreen {
|
||||
&mut self,
|
||||
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||
game_core: &mut GameCore,
|
||||
dt: f64
|
||||
dt: f64,
|
||||
audio_system: &mut AudioPlayer,
|
||||
) {
|
||||
// Build source bounds
|
||||
let source_bounds = Rectangle {
|
||||
@ -49,14 +45,101 @@ impl InGameScreen {
|
||||
};
|
||||
|
||||
// Clear the background
|
||||
context_2d.draw_rectangle_rec(world_bounds, WATER);
|
||||
// context_2d.draw_rectangle_gradient_v(
|
||||
// world_bounds.x as i32,
|
||||
// world_bounds.y as i32,
|
||||
// world_bounds.width as i32,
|
||||
// world_bounds.height as i32,
|
||||
// WATER,
|
||||
// WATER_DARK,
|
||||
// );
|
||||
context_2d.draw_texture_pro(
|
||||
&game_core.resources.background_back,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: game_core.resources.background_back.width as f32,
|
||||
height: game_core.resources.background_back.height as f32,
|
||||
},
|
||||
Rectangle::new(
|
||||
0.0,
|
||||
0.0,
|
||||
(game_core.resources.background_back.width * 2) as f32,
|
||||
(game_core.resources.background_back.height * 2) as f32,
|
||||
),
|
||||
Vector2 { x: 0.0, y: 0.0 },
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
context_2d.draw_texture_pro(
|
||||
&game_core.resources.background_front,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: game_core.resources.background_front.width as f32,
|
||||
height: game_core.resources.background_front.height as f32,
|
||||
},
|
||||
Rectangle::new(
|
||||
0.0,
|
||||
0.0,
|
||||
(game_core.resources.background_front.width * 2) as f32,
|
||||
(game_core.resources.background_front.height * 2) as f32,
|
||||
),
|
||||
Vector2 { x: 0.0, y: 0.0 },
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
|
||||
// Render fish
|
||||
let fish_clone = game_core.world.fish.clone();
|
||||
for fish in game_core.world.fish.iter_mut() {
|
||||
fish.update_position(&mut game_core.player, dt, &fish_clone);
|
||||
fish.render(context_2d);
|
||||
if fish.update_position(&mut game_core.player, dt, &fish_clone) {
|
||||
audio_system.play_sound(&game_core.resources.fish_pickup);
|
||||
}
|
||||
fish.render(context_2d, &mut game_core.resources);
|
||||
}
|
||||
context_2d.draw_texture_pro(
|
||||
&game_core.resources.tut1,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: 44.0,
|
||||
height: 41.0,
|
||||
},
|
||||
Rectangle {
|
||||
x: 110.0,
|
||||
y: 100.0,
|
||||
width: 44.0,
|
||||
height: 41.0,
|
||||
},
|
||||
Vector2 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
},
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
context_2d.draw_texture_pro(
|
||||
&game_core.resources.tut2,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: 44.0,
|
||||
height: 41.0,
|
||||
},
|
||||
Rectangle {
|
||||
x: 160.0,
|
||||
y: 110.0,
|
||||
width: 44.0,
|
||||
height: 41.0,
|
||||
},
|
||||
Vector2 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
},
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
|
||||
// Render the world texture
|
||||
context_2d.draw_texture_rec(
|
||||
@ -80,27 +163,83 @@ impl InGameScreen {
|
||||
context_2d.draw_rectangle_lines_ex(collider, 1, Color::RED);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_darkness(&mut self, draw_handle: &mut RaylibDrawHandle, game_core: &mut GameCore) {
|
||||
// Calculate the min view radius based on the current flashlight
|
||||
let mut min_radius = 0.0;
|
||||
if game_core.player.inventory.flashlight.is_some() {
|
||||
min_radius = game_core
|
||||
.player
|
||||
.inventory
|
||||
.flashlight
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.radius;
|
||||
}
|
||||
|
||||
// Get the window center
|
||||
let win_height = draw_handle.get_screen_height();
|
||||
let win_width = draw_handle.get_screen_width();
|
||||
|
||||
// Calculate the occusion radius based on depth
|
||||
let radius = (1.0
|
||||
- (game_core.player.calculate_depth_percent(&game_core.world) * 1.3).clamp(0.0, 1.0))
|
||||
.max(min_radius);
|
||||
|
||||
// Determine width and height scales
|
||||
// This is clamped to make the rendering logic below easier by removing the need to overdraw
|
||||
let width_scale = (5.0 * radius).max(0.5);
|
||||
let height_scale = (5.0 * radius).max(0.5);
|
||||
|
||||
// Get the base sizes of everything
|
||||
let texture_width = game_core.resources.darkness_overlay.width as f32;
|
||||
let texture_height = game_core.resources.darkness_overlay.height as f32;
|
||||
let texture_width_scaled = texture_width * width_scale;
|
||||
let texture_height_scaled = texture_height * height_scale;
|
||||
|
||||
// Render the overlay
|
||||
draw_handle.draw_texture_pro(
|
||||
&game_core.resources.darkness_overlay,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: texture_width,
|
||||
height: texture_height,
|
||||
},
|
||||
Rectangle {
|
||||
x: (win_width as f32 - texture_width_scaled) / 2.0,
|
||||
y: (win_height as f32 - texture_height_scaled) / 2.0,
|
||||
width: texture_width_scaled,
|
||||
height: texture_height_scaled,
|
||||
},
|
||||
Vector2 { x: 0.0, y: 0.0 },
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Screen for InGameScreen {
|
||||
fn render(
|
||||
&mut self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
thread: &RaylibThread,
|
||||
audio_system: &mut AudioPlayer,
|
||||
_thread: &RaylibThread,
|
||||
_audio_system: &mut AudioPlayer,
|
||||
game_core: &mut GameCore,
|
||||
) -> Option<GameState> {
|
||||
// Calculate DT
|
||||
let dt = draw_handle.get_time() - game_core.last_frame_time;
|
||||
|
||||
// Clear frame
|
||||
draw_handle.clear_background(Color::BLACK);
|
||||
|
||||
// Handle the pause menu being opened
|
||||
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||
return Some(GameState::PauseMenu);
|
||||
}
|
||||
|
||||
// music
|
||||
if !_audio_system.is_sound_playing(&game_core.resources.song_swim) {
|
||||
_audio_system.play_sound(&game_core.resources.song_swim);
|
||||
}
|
||||
|
||||
// Window dimensions
|
||||
let win_height = draw_handle.get_screen_height();
|
||||
let win_width = draw_handle.get_screen_width();
|
||||
@ -108,33 +247,138 @@ impl Screen for InGameScreen {
|
||||
x: (win_width as f32 / 2.0),
|
||||
y: (win_height as f32 / 2.0),
|
||||
};
|
||||
let camera_window_center = window_center * (1.0 / game_core.master_camera.zoom);
|
||||
|
||||
// Update player movement
|
||||
playerlogic::update_player_movement(draw_handle, game_core, window_center);
|
||||
playerlogic::update_player_movement(draw_handle, game_core, window_center, _audio_system);
|
||||
|
||||
if draw_handle.get_time() % 10.0 <= 0.1 && !_audio_system.is_sound_playing(&game_core.resources.breath){
|
||||
_audio_system.set_sound_volume(&game_core.resources.breath, 0.5);
|
||||
_audio_system.play_sound(&game_core.resources.breath);
|
||||
}
|
||||
|
||||
// Open a 2D context
|
||||
{
|
||||
let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera);
|
||||
|
||||
// Render the world
|
||||
self.render_world(&mut context_2d, game_core, dt);
|
||||
if game_core.show_simple_debug_info {
|
||||
self.render_colliders(&mut context_2d, game_core);
|
||||
unsafe {
|
||||
raylib::ffi::BeginTextureMode(*game_core.resources.shader_texture);
|
||||
}
|
||||
{
|
||||
let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera);
|
||||
|
||||
// Render entities
|
||||
for jellyfish in game_core.world.jellyfish.iter_mut() {
|
||||
jellyfish.handle_logic(&mut game_core.player, dt);
|
||||
jellyfish.render(&mut context_2d, &mut game_core.resources, dt);
|
||||
// Clear frame
|
||||
context_2d.clear_background(Color::BLACK);
|
||||
|
||||
// Render the world
|
||||
self.render_world(&mut context_2d, game_core, dt, _audio_system);
|
||||
if game_core.show_simple_debug_info {
|
||||
self.render_colliders(&mut context_2d, game_core);
|
||||
}
|
||||
|
||||
// Render entities
|
||||
for jellyfish in game_core.world.jellyfish.iter_mut() {
|
||||
jellyfish.handle_logic(&mut game_core.player, dt);
|
||||
jellyfish.render(
|
||||
&mut context_2d,
|
||||
&mut game_core.player,
|
||||
&mut game_core.resources,
|
||||
dt,
|
||||
);
|
||||
}
|
||||
for octopus in game_core.world.octopus.iter_mut() {
|
||||
if octopus.handle_logic(&mut game_core.player, dt) == 1 {
|
||||
_audio_system.play_sound(&game_core.resources.succ);
|
||||
}
|
||||
octopus.render(
|
||||
&mut context_2d,
|
||||
&mut game_core.player,
|
||||
&mut game_core.resources,
|
||||
dt,
|
||||
);
|
||||
}
|
||||
|
||||
// Iterates over whirlpools and runs render and logic funcs
|
||||
for whirlpool_mob in game_core.world.whirlpool.iter_mut(){
|
||||
whirlpool_mob.handle_logic(&mut game_core.player, dt);
|
||||
whirlpool_mob.render(&mut context_2d, &mut game_core.player, &mut game_core.resources, dt);
|
||||
|
||||
// Spawns 10 fish on spawn
|
||||
if whirlpool_mob.should_remove(){
|
||||
for _ in 0..10{
|
||||
game_core.world.fish.push(FishEntity::new(whirlpool_mob.position));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Iterates over pufferfish
|
||||
for pufferfish in game_core.world.pufferfish.iter_mut(){
|
||||
|
||||
pufferfish.handle_logic(&mut game_core.player, dt);
|
||||
pufferfish.render(&mut context_2d, &mut game_core.player, &mut game_core.resources, dt);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Removes whirlpools set for removal
|
||||
game_core.world.whirlpool.retain(|x| !x.should_remove());
|
||||
|
||||
|
||||
|
||||
|
||||
// Render transponder
|
||||
game_core.resources.transponder.draw(
|
||||
&mut context_2d,
|
||||
game_core.world.end_position,
|
||||
0.0,
|
||||
);
|
||||
|
||||
// Render Player
|
||||
game_core
|
||||
.player
|
||||
.render(&mut context_2d, &mut game_core.resources, dt);
|
||||
}
|
||||
unsafe {
|
||||
raylib::ffi::EndTextureMode();
|
||||
}
|
||||
|
||||
// Render Player
|
||||
game_core
|
||||
.player
|
||||
.render(&mut context_2d, &mut game_core.resources, dt);
|
||||
}
|
||||
|
||||
// Update the shader's internal time
|
||||
game_core
|
||||
.resources
|
||||
.pixel_shader
|
||||
.set_shader_value(self.shader_time_var_location, draw_handle.get_time() as f32);
|
||||
|
||||
// Render the 2D context via the ripple shader
|
||||
{
|
||||
let mut shader_context =
|
||||
draw_handle.begin_shader_mode(&game_core.resources.pixel_shader);
|
||||
|
||||
// Blit the texture
|
||||
shader_context.draw_texture_pro(
|
||||
&game_core.resources.shader_texture,
|
||||
Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: game_core.resources.shader_texture.width() as f32,
|
||||
height: (game_core.resources.shader_texture.height() as f32) * -1.0,
|
||||
},
|
||||
Rectangle {
|
||||
x: -10.0,
|
||||
y: -10.0,
|
||||
width: win_width as f32 + 20.0,
|
||||
height: win_height as f32 + 20.0,
|
||||
},
|
||||
Vector2::zero(),
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
|
||||
// Render the darkness layer
|
||||
self.render_darkness(draw_handle, game_core);
|
||||
|
||||
// Render the hud
|
||||
hud::render_hud(draw_handle, game_core, window_center);
|
||||
|
||||
@ -143,6 +387,15 @@ impl Screen for InGameScreen {
|
||||
return Some(GameState::GameEnd);
|
||||
}
|
||||
|
||||
if game_core
|
||||
.world
|
||||
.end_position
|
||||
.distance_to(game_core.player.position)
|
||||
<= 40.0
|
||||
{
|
||||
return Some(GameState::WinGame);
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,33 @@
|
||||
use raylib::core::audio::RaylibAudio;
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{
|
||||
gamecore::GameCore,
|
||||
pallette::{TRANSLUCENT_WHITE_128, TRANSLUCENT_WHITE_64, TRANSLUCENT_WHITE_96},
|
||||
};
|
||||
use rand::{prelude::ThreadRng, Rng};
|
||||
use crate::{gamecore::GameCore, lib::wrappers::audio::player::AudioPlayer};
|
||||
|
||||
const NORMAL_PLAYER_SPEED: i32 = 1;
|
||||
const BOOST_PLAYER_SPEED: i32 = NORMAL_PLAYER_SPEED * 2;
|
||||
const CAMERA_FOLLOW_SPEED: f32 = 0.7;
|
||||
const PLAYER_FRICTION: f32 = 1.05;
|
||||
const WHIRLPOOL_PULL: f32 = 3.0;
|
||||
const TURN_SPEED: f32 = 0.15;
|
||||
const BOOST_DECREASE_PER_SECOND: f32 = 0.65;
|
||||
const BOOST_REGEN_PER_SECOND: f32 = 0.25;
|
||||
const BREATH_DECREASE_PER_SECOND: f32 = 0.02;
|
||||
|
||||
|
||||
|
||||
pub fn update_player_movement(
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
game_core: &mut GameCore,
|
||||
window_center: Vector2,
|
||||
audio_system: &mut AudioPlayer
|
||||
) {
|
||||
|
||||
// Calculate DT
|
||||
let dt = draw_handle.get_time() - game_core.last_frame_time;
|
||||
|
||||
// Handle player movement
|
||||
let mouse_pose = draw_handle.get_mouse_position();
|
||||
let mouse_world_pose = draw_handle.get_screen_to_world2D(mouse_pose, game_core.master_camera);
|
||||
let raw_movement_direction = mouse_world_pose - game_core.player.position;
|
||||
let mut raw_movement_direction = mouse_world_pose - game_core.player.position;
|
||||
let mut normalized_movement_direction = raw_movement_direction;
|
||||
normalized_movement_direction.normalize();
|
||||
|
||||
@ -78,8 +81,26 @@ pub fn update_player_movement(
|
||||
let user_request_boost = draw_handle.is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON);
|
||||
let user_request_action = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_RIGHT_BUTTON);
|
||||
|
||||
if user_request_action && !game_core.player.has_stunned {
|
||||
game_core.player.has_stunned = true;
|
||||
game_core
|
||||
.player
|
||||
.begin_attack(&mut game_core.world, draw_handle.get_time());
|
||||
}
|
||||
|
||||
if user_request_action {
|
||||
game_core.player.begin_attack(&mut game_core.world);
|
||||
// println!("{{\"x\":{}, \"y\":{}}},",f32::round(game_core.player.position.x),f32::round(game_core.player.position.y));
|
||||
}
|
||||
|
||||
// die sound
|
||||
if game_core.player.breath_percent <= 0.06 {
|
||||
if !audio_system.is_sound_playing(&game_core.resources.die) {
|
||||
audio_system.play_sound(&game_core.resources.die);
|
||||
}
|
||||
}
|
||||
|
||||
if (game_core.player.stun_timer > 0.0 || game_core.player.is_stun_gun_active()) && !audio_system.is_sound_playing(&game_core.resources.zap) {
|
||||
audio_system.play_sound(&game_core.resources.zap);
|
||||
}
|
||||
|
||||
// Move the player in their direction
|
||||
@ -88,6 +109,21 @@ pub fn update_player_movement(
|
||||
// Set the speed multiplier
|
||||
speed_multiplier = BOOST_PLAYER_SPEED as f32;
|
||||
|
||||
// swim sound
|
||||
if !audio_system.is_sound_playing(&game_core.resources.swim1) && !audio_system.is_sound_playing(&game_core.resources.swim2) && !audio_system.is_sound_playing(&game_core.resources.swim3) && !audio_system.is_sound_playing(&game_core.resources.swim4) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let num = rng.gen_range(0..3);
|
||||
if num == 0 {
|
||||
audio_system.play_sound(&game_core.resources.swim1);
|
||||
} else if num == 1 {
|
||||
audio_system.play_sound(&game_core.resources.swim2);
|
||||
} else if num == 2 {
|
||||
audio_system.play_sound(&game_core.resources.swim3);
|
||||
} else {
|
||||
audio_system.play_sound(&game_core.resources.swim4);
|
||||
}
|
||||
}
|
||||
|
||||
// Decrease the boost
|
||||
game_core.player.boost_percent -= BOOST_DECREASE_PER_SECOND * dt as f32;
|
||||
game_core.player.is_boosting = true;
|
||||
@ -142,35 +178,144 @@ pub fn update_player_movement(
|
||||
|
||||
// Update the player's breath
|
||||
game_core.player.breath_percent =
|
||||
(game_core.player.breath_percent - BREATH_DECREASE_PER_SECOND * dt as f32).clamp(0.0, 1.0);
|
||||
(game_core.player.breath_percent - BREATH_DECREASE_PER_SECOND * dt as f32).max(0.0);
|
||||
|
||||
// Only do this if the mouse is far enough away
|
||||
let player_stunned = game_core.player.stun_timer > 0.0;
|
||||
let mut player_real_movement = game_core.player.direction * speed_multiplier;
|
||||
if raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0
|
||||
&& !game_core.player.is_stunned()
|
||||
{
|
||||
|
||||
// Handle the player wearing flippers
|
||||
if game_core.player.inventory.flippers.is_some() {
|
||||
player_real_movement *= game_core
|
||||
.player
|
||||
.inventory
|
||||
.flippers
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.speed_increase;
|
||||
}
|
||||
|
||||
let mut should_apply_friction: bool = true;
|
||||
|
||||
// Check each whirlpool for effects
|
||||
for whirlpool in game_core.world.whirlpool.iter_mut(){
|
||||
|
||||
|
||||
// check if its in range and not to close
|
||||
if game_core.player.position.distance_to(whirlpool.position) <= 50.0 && game_core.player.position.distance_to(whirlpool.position) >= 10.0{
|
||||
|
||||
// Calculates info for formulas
|
||||
|
||||
// Deltas between positions
|
||||
let net_pose = game_core.player.position - whirlpool.position;
|
||||
|
||||
// Angle between: UNITS: RADIANS
|
||||
let angle = net_pose.y.atan2(net_pose.x);
|
||||
|
||||
|
||||
// Calculates force
|
||||
let force = WHIRLPOOL_PULL / game_core.player.position.distance_to(whirlpool.position);
|
||||
|
||||
// Calculates componets of force
|
||||
let mut force_x = (force as f32 * angle.cos()).clamp(-5.0, 5.0);
|
||||
let mut force_y = (force as f32 * angle.sin()).clamp(-5.0, 5.0);
|
||||
|
||||
// Prevents Nan erros
|
||||
if force_x.is_nan(){
|
||||
force_x = 5.0 * net_pose.x;
|
||||
}
|
||||
|
||||
if force_y.is_nan(){
|
||||
force_y = 5.0 * net_pose.y;
|
||||
}
|
||||
|
||||
// Adds values to drift tracker
|
||||
game_core.player.additional_vel.x -= force_x;
|
||||
game_core.player.additional_vel.y -= force_y;
|
||||
|
||||
should_apply_friction = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for pufferfish in game_core.world.pufferfish.iter_mut(){
|
||||
|
||||
if pufferfish.is_knocking_back{
|
||||
// Calculates info for formulas
|
||||
|
||||
// Deltas between positions
|
||||
let net_pose = game_core.player.position - pufferfish.position;
|
||||
|
||||
// Angle between: UNITS: RADIANS
|
||||
let angle = net_pose.y.atan2(net_pose.x);
|
||||
|
||||
// Calculates force
|
||||
let force = 0.2;
|
||||
|
||||
// Calculates componets of force
|
||||
let mut force_x = (force as f32 * angle.cos()).clamp(-1.0, 1.0);
|
||||
let mut force_y = (force as f32 * angle.sin()).clamp(-1.0, 1.0);
|
||||
|
||||
// Prevents Nan erros
|
||||
if force_x.is_nan(){
|
||||
force_x = 1.0 * net_pose.x;
|
||||
}
|
||||
|
||||
if force_y.is_nan(){
|
||||
force_y = 1.0 * net_pose.y;
|
||||
}
|
||||
|
||||
game_core.player.additional_vel.x += force_x;
|
||||
game_core.player.additional_vel.y += force_y;
|
||||
|
||||
should_apply_friction = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if should_apply_friction {
|
||||
game_core.player.additional_vel.x /= PLAYER_FRICTION;
|
||||
game_core.player.additional_vel.y /= PLAYER_FRICTION;
|
||||
if f32::round(game_core.player.additional_vel.x * 10.0) == 0.0 {
|
||||
game_core.player.additional_vel.x = 0.0;
|
||||
}
|
||||
if f32::round(game_core.player.additional_vel.y * 10.0) == 0.0 {
|
||||
game_core.player.additional_vel.y = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
if !(raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0) {
|
||||
player_real_movement = Vector2::zero();
|
||||
}
|
||||
|
||||
// Handle movement and collisions
|
||||
if !game_core.player.is_stunned() {
|
||||
if game_core.player.is_moving {
|
||||
// move in x
|
||||
game_core.player.position.x += player_real_movement.x;
|
||||
game_core.player.position.x += player_real_movement.x + game_core.player.additional_vel.x;
|
||||
|
||||
// Check for any collisions
|
||||
for collider in game_core.world.colliders.iter() {
|
||||
if game_core.player.collides_with_rec(collider) {
|
||||
game_core.player.position.x -= player_real_movement.x;
|
||||
game_core.player.position.x -= player_real_movement.x + game_core.player.additional_vel.x;
|
||||
player_real_movement.x = 0.0;
|
||||
game_core.player.additional_vel.x = 0.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// move in y
|
||||
game_core.player.position.y += player_real_movement.y;
|
||||
game_core.player.position.y += player_real_movement.y + game_core.player.additional_vel.y;
|
||||
|
||||
// Check for any collisions
|
||||
for collider in game_core.world.colliders.iter() {
|
||||
if game_core.player.collides_with_rec(collider) {
|
||||
game_core.player.position.y -= player_real_movement.y;
|
||||
game_core.player.position.y -= player_real_movement.y + game_core.player.additional_vel.y;
|
||||
player_real_movement.y = 0.0;
|
||||
game_core.player.additional_vel.y = 0.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -180,17 +325,20 @@ pub fn update_player_movement(
|
||||
// Handle updating the stun timer
|
||||
if player_stunned {
|
||||
game_core.player.stun_timer -= dt;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Move the camera to follow the player
|
||||
let direction_from_cam_to_player =
|
||||
(game_core.player.position - window_center) - game_core.master_camera.target;
|
||||
let player_screen_position =
|
||||
draw_handle.get_world_to_screen2D(game_core.player.position, game_core.master_camera);
|
||||
|
||||
// Camera only moves if you get close to the edge of the screen
|
||||
if player_screen_position.distance_to(window_center).abs() > 100.0 {
|
||||
game_core.master_camera.target += player_real_movement;
|
||||
game_core.master_camera.target += player_real_movement + game_core.player.additional_vel;
|
||||
}
|
||||
|
||||
// If the player is not on screen, snap the camera to them
|
||||
|
@ -1,11 +1,13 @@
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{gamecore::{GameCore, GameState}, lib::{utils::calculate_linear_slide, wrappers::audio::player::AudioPlayer}};
|
||||
use crate::{
|
||||
gamecore::{GameCore, GameState},
|
||||
lib::{utils::calculate_linear_slide, wrappers::audio::player::AudioPlayer},
|
||||
};
|
||||
|
||||
use super::screen::Screen;
|
||||
|
||||
const SECONDS_PER_LOGO: f64 = 4.0;
|
||||
const RUST_ORANGE: Color = Color::new(222, 165, 132, 255);
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum LoadingScreenState {
|
||||
@ -47,30 +49,30 @@ impl LoadingScreen {
|
||||
win_height: i32,
|
||||
win_width: i32,
|
||||
) {
|
||||
// Determine how far through rendering this logo we are
|
||||
// This value is used to determine the logo alpha
|
||||
let playthrough_percent =
|
||||
(draw_handle.get_time() - self.last_state_switch_time) / SECONDS_PER_LOGO;
|
||||
// // Determine how far through rendering this logo we are
|
||||
// // This value is used to determine the logo alpha
|
||||
// let playthrough_percent =
|
||||
// (draw_handle.get_time() - self.last_state_switch_time) / SECONDS_PER_LOGO;
|
||||
|
||||
// Build a color mask
|
||||
let mask = self.get_logo_mask(playthrough_percent);
|
||||
// // Build a color mask
|
||||
// let mask = self.get_logo_mask(playthrough_percent);
|
||||
|
||||
// Get the logo
|
||||
let logo = &game_core.resources.game_logo;
|
||||
// // Get the logo
|
||||
// let logo = &game_core.resources.game_logo;
|
||||
|
||||
// Render the logo
|
||||
draw_handle.draw_texture(
|
||||
logo,
|
||||
(win_width / 2) - (logo.width / 2),
|
||||
(win_height / 2) - (logo.height / 2),
|
||||
mask,
|
||||
);
|
||||
// // Render the logo
|
||||
// draw_handle.draw_texture(
|
||||
// logo,
|
||||
// (win_width / 2) - (logo.width / 2),
|
||||
// (win_height / 2) - (logo.height / 2),
|
||||
// mask,
|
||||
// );
|
||||
|
||||
// Move on to next logo if needed
|
||||
if playthrough_percent >= 1.0 {
|
||||
// if playthrough_percent >= 1.0 {
|
||||
self.state = LoadingScreenState::RaylibLogo;
|
||||
self.last_state_switch_time = draw_handle.get_time();
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
fn show_raylib_logo(
|
||||
@ -89,10 +91,10 @@ impl LoadingScreen {
|
||||
let mask = self.get_logo_mask(playthrough_percent);
|
||||
|
||||
// Create modified colors
|
||||
let alpha_orange = Color {
|
||||
r: RUST_ORANGE.r,
|
||||
g: RUST_ORANGE.g,
|
||||
b: RUST_ORANGE.b,
|
||||
let alpha_black = Color {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: mask.a,
|
||||
};
|
||||
|
||||
@ -102,7 +104,7 @@ impl LoadingScreen {
|
||||
win_height / 2 - 128,
|
||||
256,
|
||||
256,
|
||||
alpha_orange,
|
||||
alpha_black,
|
||||
);
|
||||
draw_handle.draw_rectangle(
|
||||
win_width / 2 - 112,
|
||||
@ -111,19 +113,19 @@ impl LoadingScreen {
|
||||
224,
|
||||
Color::WHITE,
|
||||
);
|
||||
draw_handle.draw_text(
|
||||
"rust",
|
||||
win_width / 2 - 69,
|
||||
win_height / 2 + 18,
|
||||
50,
|
||||
alpha_orange,
|
||||
);
|
||||
// draw_handle.draw_text(
|
||||
// "rust",
|
||||
// win_width / 2 - 69,
|
||||
// win_height / 2 + 18,
|
||||
// 50,
|
||||
// alpha_orange,
|
||||
// );
|
||||
draw_handle.draw_text(
|
||||
"raylib",
|
||||
win_width / 2 - 44,
|
||||
win_height / 2 + 48,
|
||||
50,
|
||||
alpha_orange,
|
||||
alpha_black,
|
||||
);
|
||||
|
||||
// Move on to next logo if needed
|
||||
@ -138,7 +140,7 @@ impl Screen for LoadingScreen {
|
||||
fn render(
|
||||
&mut self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
thread: &RaylibThread,
|
||||
_thread: &RaylibThread,
|
||||
_audio_system: &mut AudioPlayer,
|
||||
game_core: &mut GameCore,
|
||||
) -> Option<GameState> {
|
||||
|
@ -19,55 +19,84 @@ impl Screen for MainMenuScreen {
|
||||
fn render(
|
||||
&mut self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
thread: &RaylibThread,
|
||||
audio_system: &mut AudioPlayer,
|
||||
_thread: &RaylibThread,
|
||||
_audio_system: &mut AudioPlayer,
|
||||
game_core: &mut GameCore,
|
||||
) -> Option<GameState> {
|
||||
// Window dimensions
|
||||
let win_height = draw_handle.get_screen_height();
|
||||
let win_width = draw_handle.get_screen_width();
|
||||
|
||||
// Clear frame
|
||||
draw_handle.clear_background(Color::BLUE);
|
||||
// // Clear frame
|
||||
// draw_handle.clear_background(Color::BLUE);
|
||||
// Render the background
|
||||
draw_handle.draw_texture(&game_core.resources.shop_background, 0, 0, Color::WHITE);
|
||||
|
||||
// Render title
|
||||
draw_handle.draw_text(
|
||||
"ONE BREATH",
|
||||
(win_height / 2) - 80,
|
||||
win_width / 4,
|
||||
40,
|
||||
"DEEP BREATH",
|
||||
(win_height / 2) - 100,
|
||||
win_width / 8,
|
||||
80,
|
||||
Color::BLACK,
|
||||
);
|
||||
|
||||
// Get mouse position data
|
||||
let mouse_position = draw_handle.get_mouse_position();
|
||||
let hovering_play_button = mouse_position.y > (win_width as f32 / 4.0)
|
||||
&& mouse_position.y < (win_width as f32 / 4.0) + 60.0;
|
||||
let hovering_shop_button = mouse_position.y > (win_width as f32 / 4.0) + 100.0
|
||||
&& mouse_position.y < (win_width as f32 / 4.0) + 160.0;
|
||||
let hovering_quit_button = mouse_position.y > (win_width as f32 / 4.0) + 200.0
|
||||
&& mouse_position.y < (win_width as f32 / 4.0) + 260.0;
|
||||
|
||||
// Play and quit
|
||||
draw_handle.draw_text(
|
||||
"Play",
|
||||
(win_height / 2) - 80,
|
||||
(win_height / 2) + 120,
|
||||
win_width / 4,
|
||||
60,
|
||||
match hovering_play_button {
|
||||
true => Color::BLUE,
|
||||
false => Color::BLACK,
|
||||
},
|
||||
);
|
||||
draw_handle.draw_text(
|
||||
"Shop",
|
||||
(win_height / 2) + 120,
|
||||
(win_width / 4) + 100,
|
||||
20,
|
||||
Color::BLACK,
|
||||
60,
|
||||
match hovering_shop_button {
|
||||
true => Color::BLUE,
|
||||
false => Color::BLACK,
|
||||
},
|
||||
);
|
||||
draw_handle.draw_text(
|
||||
"Quit",
|
||||
(win_height / 2) - 80,
|
||||
(win_width / 4) + 140,
|
||||
20,
|
||||
Color::BLACK,
|
||||
(win_height / 2) + 130,
|
||||
(win_width / 4) + 200,
|
||||
60,
|
||||
match hovering_quit_button {
|
||||
true => Color::RED,
|
||||
false => Color::BLACK,
|
||||
},
|
||||
);
|
||||
|
||||
// Handle button presses
|
||||
let mouse_position = draw_handle.get_mouse_position();
|
||||
let mouse_clicked = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON);
|
||||
|
||||
// Check clicks
|
||||
if mouse_clicked {
|
||||
if mouse_position.y > (win_width as f32 / 4.0) + 100.0
|
||||
&& mouse_position.y < (win_width as f32 / 4.0) + 120.0
|
||||
{
|
||||
if hovering_play_button {
|
||||
// Reset the world
|
||||
game_core.world.reset(&mut game_core.player);
|
||||
_audio_system.play_sound(&game_core.resources.ui_click);
|
||||
// Start playing
|
||||
return Some(GameState::InGame);
|
||||
} else if mouse_position.y > (win_width as f32 / 4.0) + 140.0
|
||||
&& mouse_position.y < (win_width as f32 / 4.0) + 180.0
|
||||
{
|
||||
} else if hovering_shop_button {
|
||||
_audio_system.play_sound(&game_core.resources.ui_click);
|
||||
return Some(GameState::InShop);
|
||||
} else if hovering_quit_button {
|
||||
return Some(GameState::GameQuit);
|
||||
}
|
||||
}
|
||||
|
@ -4,3 +4,5 @@ pub mod mainmenu;
|
||||
pub mod pausemenu;
|
||||
pub mod ingame;
|
||||
pub mod gameend;
|
||||
pub mod shop;
|
||||
pub mod winscreen;
|
@ -2,7 +2,7 @@ use raylib::prelude::*;
|
||||
|
||||
use crate::{
|
||||
gamecore::{GameCore, GameState},
|
||||
lib::wrappers::audio::player::AudioPlayer,
|
||||
lib::{utils::button::OnScreenButton, wrappers::audio::player::AudioPlayer},
|
||||
};
|
||||
|
||||
use super::screen::Screen;
|
||||
@ -26,8 +26,11 @@ impl Screen for PauseMenuScreen {
|
||||
game_core: &mut GameCore,
|
||||
) -> Option<GameState> {
|
||||
let mouse_position = draw_handle.get_mouse_position();
|
||||
draw_handle.clear_background(Color::GRAY);
|
||||
// TODO: Maybe we can stick some art here?
|
||||
// draw_handle.clear_background(Color::GRAY);
|
||||
// // TODO: Maybe we can stick some art here?
|
||||
|
||||
// Render the background
|
||||
draw_handle.draw_texture(&game_core.resources.shop_background, 0, 0, Color::WHITE);
|
||||
|
||||
// If escape is pressed again, return to the previous render state
|
||||
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||
@ -120,14 +123,15 @@ impl Screen for PauseMenuScreen {
|
||||
|
||||
// Render credits
|
||||
draw_handle.draw_text(
|
||||
"Credits:\n\t- @ewpratten\n\t- @rsninja722\n\t- @wm-c\n\t- @catarinaburghi",
|
||||
"Credits:\n\t- @ewpratten\n\t- @rsninja722\n\t- @wm-c\n\t- @catarinaburghi\n\t- @kondroel",
|
||||
(win_width / 2) - (SCREEN_PANEL_SIZE.x as i32 / 2) + 10,
|
||||
(win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 120,
|
||||
(win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 150,
|
||||
20,
|
||||
Color::BLACK,
|
||||
);
|
||||
|
||||
// Close and quit buttons
|
||||
// Bottom buttons
|
||||
|
||||
let bottom_left_button_dimensions = Rectangle {
|
||||
x: (win_width as f32 / 2.0) - (SCREEN_PANEL_SIZE.x / 2.0) + 5.0,
|
||||
y: (win_height as f32 / 2.0) + (SCREEN_PANEL_SIZE.y / 2.0) - 50.0,
|
||||
@ -141,49 +145,36 @@ impl Screen for PauseMenuScreen {
|
||||
height: bottom_left_button_dimensions.height,
|
||||
};
|
||||
|
||||
// Check if the mouse is over either button
|
||||
let mouse_over_bottom_left_button =
|
||||
bottom_left_button_dimensions.check_collision_point_rec(mouse_position);
|
||||
let mouse_over_bottom_right_button =
|
||||
bottom_right_button_dimensions.check_collision_point_rec(mouse_position);
|
||||
|
||||
// Render buttons
|
||||
draw_handle.draw_rectangle_lines_ex(
|
||||
let menu_button = OnScreenButton::new(
|
||||
"Menu".to_string(),
|
||||
bottom_left_button_dimensions,
|
||||
3,
|
||||
match mouse_over_bottom_left_button {
|
||||
true => Color::GRAY,
|
||||
false => Color::BLACK,
|
||||
},
|
||||
);
|
||||
draw_handle.draw_text(
|
||||
"Quit",
|
||||
bottom_left_button_dimensions.x as i32 + 15,
|
||||
bottom_left_button_dimensions.y as i32 + 5,
|
||||
30,
|
||||
Color::WHITE,
|
||||
Color::BLACK,
|
||||
Color::GRAY,
|
||||
30,
|
||||
true,
|
||||
);
|
||||
draw_handle.draw_rectangle_lines_ex(
|
||||
let close_button = OnScreenButton::new(
|
||||
"Close".to_string(),
|
||||
bottom_right_button_dimensions,
|
||||
3,
|
||||
match mouse_over_bottom_right_button {
|
||||
true => Color::GRAY,
|
||||
false => Color::BLACK,
|
||||
},
|
||||
);
|
||||
draw_handle.draw_text(
|
||||
"Close",
|
||||
bottom_right_button_dimensions.x as i32 + 15,
|
||||
bottom_right_button_dimensions.y as i32 + 5,
|
||||
30,
|
||||
Color::WHITE,
|
||||
Color::BLACK,
|
||||
Color::GRAY,
|
||||
30,
|
||||
true,
|
||||
);
|
||||
|
||||
// Render both
|
||||
menu_button.render(draw_handle);
|
||||
close_button.render(draw_handle);
|
||||
|
||||
// Handle click actions on the buttons
|
||||
if draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) {
|
||||
if mouse_over_bottom_left_button {
|
||||
return Some(GameState::GameQuit);
|
||||
} else if mouse_over_bottom_right_button {
|
||||
if menu_button.is_hovered(draw_handle) {
|
||||
audio_system.play_sound(&game_core.resources.ui_click);
|
||||
return Some(GameState::MainMenu);
|
||||
} else if close_button.is_hovered(draw_handle) {
|
||||
audio_system.play_sound(&game_core.resources.ui_click);
|
||||
return Some(game_core.last_state);
|
||||
}
|
||||
}
|
||||
|
82
src/logic/shop/item.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use super::itemui::ShopItemUi;
|
||||
use crate::{items::ItemBase, player::Player};
|
||||
use raylib::prelude::*;
|
||||
|
||||
pub struct ShopItemWrapper<T: ItemBase + Clone> {
|
||||
bounds: Rectangle,
|
||||
ui: ShopItemUi,
|
||||
item: T,
|
||||
}
|
||||
|
||||
impl<T: ItemBase + Clone> ShopItemWrapper<T> {
|
||||
pub fn new(
|
||||
item: T,
|
||||
from_inventory: &Option<T>,
|
||||
first_item_bounds: Rectangle,
|
||||
index: u8,
|
||||
) -> Self {
|
||||
// Build new bounds for the UI row
|
||||
let new_bounds = Rectangle {
|
||||
x: first_item_bounds.x,
|
||||
y: first_item_bounds.y + ((first_item_bounds.height + 5.0) * index as f32),
|
||||
width: first_item_bounds.width,
|
||||
height: first_item_bounds.height,
|
||||
};
|
||||
|
||||
Self {
|
||||
bounds: new_bounds,
|
||||
ui: ShopItemUi::new(
|
||||
item.get_name(),
|
||||
match from_inventory {
|
||||
Some(x) => x.get_level(),
|
||||
None => 0,
|
||||
},
|
||||
3,
|
||||
item.get_cost(),
|
||||
),
|
||||
item,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_item(&self) -> &T {
|
||||
&self.item
|
||||
}
|
||||
|
||||
pub fn can_player_afford(&self, player: &Player, players_item: &Option<T>) -> bool {
|
||||
return player.coins >= self.item.get_cost()
|
||||
&& ((players_item.is_some() && players_item.as_ref().unwrap().get_level() < 3)
|
||||
|| players_item.is_none());
|
||||
}
|
||||
|
||||
pub fn purchase(&self, player: &mut Player) -> T {
|
||||
// Take the currency from the player
|
||||
player.coins -= self.item.get_cost();
|
||||
|
||||
// Return a clone of the item
|
||||
return self.item.clone();
|
||||
}
|
||||
|
||||
pub fn user_clicked_buy(&self, draw_handle: &mut RaylibDrawHandle) -> bool {
|
||||
return self.ui.buy_button_hovered
|
||||
&& draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON);
|
||||
}
|
||||
|
||||
pub fn user_hovering_row(&self, draw_handle: &mut RaylibDrawHandle) -> bool {
|
||||
return self
|
||||
.bounds
|
||||
.check_collision_point_rec(draw_handle.get_mouse_position());
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
player: &Player,
|
||||
players_item: &Option<T>,
|
||||
) {
|
||||
self.ui.render(
|
||||
draw_handle,
|
||||
self.bounds,
|
||||
self.can_player_afford(player, players_item),
|
||||
);
|
||||
}
|
||||
}
|
63
src/logic/shop/itemui.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use crate::lib::utils::button::OnScreenButton;
|
||||
use raylib::prelude::*;
|
||||
|
||||
pub struct ShopItemUi {
|
||||
name: String,
|
||||
current_level: u8,
|
||||
max_level: u8,
|
||||
pub cost: u32,
|
||||
pub buy_button_hovered: bool,
|
||||
}
|
||||
|
||||
impl ShopItemUi {
|
||||
pub fn new(name: String, current_level: u8, max_level: u8, cost: u32) -> Self {
|
||||
Self {
|
||||
name,
|
||||
current_level,
|
||||
max_level,
|
||||
cost,
|
||||
buy_button_hovered: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
bounds: Rectangle,
|
||||
can_be_bought: bool,
|
||||
) {
|
||||
// Render the background box
|
||||
draw_handle.draw_rectangle_rec(bounds, Color::WHITE);
|
||||
draw_handle.draw_rectangle_lines_ex(bounds, 3, Color::BLACK);
|
||||
|
||||
// Render the name
|
||||
draw_handle.draw_text(
|
||||
&format!("{}: {}/{}", self.name, self.current_level, self.max_level),
|
||||
bounds.x as i32 + 10,
|
||||
bounds.y as i32 + ((bounds.height as i32 - 20) / 2),
|
||||
20,
|
||||
Color::BLACK,
|
||||
);
|
||||
|
||||
// Render the buy button
|
||||
let buy_button = OnScreenButton::new(
|
||||
format!("Buy - {}f", self.cost),
|
||||
Rectangle {
|
||||
x: bounds.x + bounds.width - 150.0,
|
||||
y: bounds.y + 5.0,
|
||||
width: 145.0,
|
||||
height: bounds.height - 10.0,
|
||||
},
|
||||
match can_be_bought {
|
||||
true => Color::WHITE,
|
||||
false => Color::GRAY,
|
||||
},
|
||||
Color::BLACK,
|
||||
Color::GRAY,
|
||||
20,
|
||||
true,
|
||||
);
|
||||
buy_button.render(draw_handle);
|
||||
self.buy_button_hovered = buy_button.is_hovered(draw_handle);
|
||||
}
|
||||
}
|