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 |
3
.gitignore
vendored
@ -15,4 +15,5 @@ Cargo.lock
|
|||||||
|
|
||||||
/target
|
/target
|
||||||
|
|
||||||
.project
|
.project
|
||||||
|
savestate.json
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "one-breath"
|
name = "ldgame"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Evan Pratten <ewpratten@gmail.com>"]
|
authors = ["Evan Pratten <ewpratten@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@ -13,9 +13,4 @@ serialstudio = "0.1.0"
|
|||||||
serde = "1.0.125"
|
serde = "1.0.125"
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
failure = "0.1.8"
|
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"
|
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/build.yml)
|
||||||
[](https://github.com/Ewpratten/ludum-dare-48/actions/workflows/bundle.yml)
|
[](https://github.com/Ewpratten/ludum-dare-48/actions/workflows/bundle.yml)
|
||||||
[](https://ldjam.com/events/ludum-dare/48/$236526)
|
[](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
|
## Development Resources
|
||||||
|
|
||||||
@ -21,6 +32,10 @@ Core libraries:
|
|||||||
- [`serde`](https://serde.rs/)
|
- [`serde`](https://serde.rs/)
|
||||||
- [`serialstudio-rs`](https://github.com/Ewpratten/serialstudio-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
|
### 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)
|
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
|
# Make a uni-bundle
|
||||||
echo "Creating a fat bundle for all platforms"
|
echo "Creating a fat bundle for all platforms"
|
||||||
rm -rf ./bundle/release
|
rm -rf ./bundle/release
|
||||||
rm -rf ./bundle/one-breath.zip
|
rm -rf ./bundle/ldgame.zip
|
||||||
mkdir -p ./bundle/release
|
mkdir -p ./bundle/release
|
||||||
cp -r ./assets ./bundle/release
|
cp -r ./assets ./bundle/release
|
||||||
cp ./bundle/linux/release/one-breath ./bundle/release/one-breath
|
cp ./bundle/linux/release/ldgame ./bundle/release/ldgame
|
||||||
cp ./bundle/windows/release/one-breath.exe ./bundle/release/one-breath.exe
|
cp ./bundle/windows/release/ldgame.exe ./bundle/release/ldgame.exe
|
||||||
cd ./bundle/release
|
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
|
mkdir -p ./bundle/linux/release
|
||||||
|
|
||||||
echo "Copying binary"
|
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"
|
echo "Copying assets"
|
||||||
cp -r ./assets ./bundle/linux/release
|
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
|
mkdir -p ./bundle/windows/release
|
||||||
|
|
||||||
echo "Copying binary"
|
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"
|
echo "Copying assets"
|
||||||
cp -r ./assets ./bundle/windows/release
|
cp -r ./assets ./bundle/windows/release
|
||||||
|
@ -6,9 +6,10 @@ pub trait EnemyBase {
|
|||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||||
|
player: &mut Player,
|
||||||
resources: &mut GlobalResources,
|
resources: &mut GlobalResources,
|
||||||
dt: f64,
|
dt: f64,
|
||||||
);
|
);
|
||||||
fn handle_logic(&mut self, player: &mut Player, dt: f64);
|
fn handle_logic(&mut self, player: &mut Player, dt: f64) -> u8;
|
||||||
fn handle_getting_attacked(&mut self, stun_duration: f64);
|
fn handle_getting_attacked(&mut self, stun_duration: f64, current_time: f64);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
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;
|
const JELLYFISH_STUN_REACH: f32 = 20.0;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
@ -28,6 +28,7 @@ impl EnemyBase for JellyFish {
|
|||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
context_2d: &mut raylib::prelude::RaylibMode2D<raylib::prelude::RaylibDrawHandle>,
|
context_2d: &mut raylib::prelude::RaylibMode2D<raylib::prelude::RaylibDrawHandle>,
|
||||||
|
_player: &mut Player,
|
||||||
resources: &mut GlobalResources,
|
resources: &mut GlobalResources,
|
||||||
dt: f64,
|
dt: f64,
|
||||||
) {
|
) {
|
||||||
@ -76,16 +77,17 @@ impl EnemyBase for JellyFish {
|
|||||||
&& !is_jelly_stunned;
|
&& !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
|
// Handle stunning the player
|
||||||
if self.do_stun_player {
|
if self.do_stun_player {
|
||||||
if self.position.distance_to(player.position).abs() <= JELLYFISH_STUN_REACH {
|
if self.position.distance_to(player.position).abs() <= JELLYFISH_STUN_REACH {
|
||||||
player.set_stun_seconds(JELLYFISH_STUN_DURATION);
|
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.stunned_timer = stun_duration;
|
||||||
self.max_stunned_time = stun_duration;
|
self.max_stunned_time = stun_duration;
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
pub mod base;
|
pub mod base;
|
||||||
pub mod jellyfish;
|
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 rand::{prelude::ThreadRng, Rng};
|
||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{gamecore::{self, GameCore}, lib::wrappers::audio::player::AudioPlayer, player::Player, resources::GlobalResources};
|
||||||
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;
|
|
||||||
|
|
||||||
const FISH_VISION: f32 = 25.0;
|
const FISH_VISION: f32 = 25.0;
|
||||||
const FISH_MAX_SPEED: f32 = 2.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_SEPARATION_DISTANCE: f32 = 15.0;
|
||||||
const FISH_FACTOR_SEPARATION: f32 = 1.5;
|
const FISH_FACTOR_SEPARATION: f32 = 1.5;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FishEntity {
|
pub struct FishEntity {
|
||||||
position: Vector2,
|
position: Vector2,
|
||||||
direction: Vector2,
|
direction: Vector2,
|
||||||
velocity: Vector2,
|
velocity: Vector2,
|
||||||
pub following_player: bool,
|
pub following_player: bool,
|
||||||
|
current_frame: u8,
|
||||||
|
animation_counter: u32,
|
||||||
size: Vector2,
|
size: Vector2,
|
||||||
|
color: u8,
|
||||||
rng: ThreadRng,
|
rng: ThreadRng,
|
||||||
color: Color,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FishEntity {
|
impl FishEntity {
|
||||||
@ -35,16 +31,16 @@ impl FishEntity {
|
|||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
Self {
|
Self {
|
||||||
position: position,
|
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(),
|
velocity: Vector2::zero(),
|
||||||
following_player: false,
|
following_player: false,
|
||||||
|
animation_counter: 0,
|
||||||
|
current_frame: 0,
|
||||||
size: Vector2 { x: 5.0, y: 8.0 },
|
size: Vector2 { x: 5.0, y: 8.0 },
|
||||||
color: Color {
|
color: rng.gen_range(0..6),
|
||||||
r: rng.gen_range(128..225),
|
|
||||||
g: rng.gen_range(128..225),
|
|
||||||
b: rng.gen_range(128..225),
|
|
||||||
a: 140,
|
|
||||||
},
|
|
||||||
rng,
|
rng,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +53,12 @@ impl FishEntity {
|
|||||||
return output;
|
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 acceleration: Vector2 = Vector2::zero();
|
||||||
|
|
||||||
let mut steer: Vector2 = Vector2::zero();
|
let mut steer: Vector2 = Vector2::zero();
|
||||||
@ -134,69 +135,72 @@ impl FishEntity {
|
|||||||
self.position += self.velocity;
|
self.position += self.velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_free_movement(&mut self, player: &mut Player, dt: f64) {
|
pub fn handle_free_movement(&mut self, player: &mut Player, _dt: f64) -> bool {
|
||||||
// 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();
|
|
||||||
|
|
||||||
// Handle player picking up fish
|
// Handle player picking up fish
|
||||||
if player.position.distance_to(self.position).abs() <= player.size.y * 2.2 {
|
if player.position.distance_to(self.position).abs() <= player.size.y * 2.2 {
|
||||||
self.following_player = true;
|
self.following_player = true;
|
||||||
self.velocity = self.direction.normalized();
|
self.velocity = self.direction.normalized();
|
||||||
|
self.current_frame = 0;
|
||||||
|
self.animation_counter = 0;
|
||||||
|
|
||||||
// Add currency to the player
|
// Add currency to the player
|
||||||
player.coins += 1;
|
player.coins += 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
// Look at the player;
|
|
||||||
self.position = self.position;
|
|
||||||
self.direction = direction_to_player;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if self.following_player {
|
||||||
self.handle_follow_player(player, dt, other_fish);
|
self.handle_follow_player(player, dt, other_fish);
|
||||||
} else {
|
} 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::{
|
use raylib::{
|
||||||
camera::Camera2D, math::Vector2, prelude::RaylibDrawHandle, RaylibHandle, RaylibThread,
|
camera::Camera2D, math::Vector2, prelude::RaylibDrawHandle, RaylibHandle, RaylibThread,
|
||||||
};
|
};
|
||||||
|
use failure::Error;
|
||||||
use crate::{
|
use crate::{
|
||||||
player::{Player, PlayerInventory},
|
player::{Player, PlayerInventory},
|
||||||
resources::GlobalResources,
|
resources::GlobalResources,
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
use failure::Error;
|
|
||||||
use log::debug;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Overall states for the game
|
/// Overall states for the game
|
||||||
@ -25,6 +23,8 @@ pub enum GameState {
|
|||||||
GameQuit,
|
GameQuit,
|
||||||
InGame,
|
InGame,
|
||||||
GameEnd,
|
GameEnd,
|
||||||
|
InShop,
|
||||||
|
WinGame,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for GameState {
|
impl fmt::Display for GameState {
|
||||||
@ -76,6 +76,20 @@ impl GameProgress {
|
|||||||
|
|
||||||
Ok(())
|
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.
|
/// 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>) {
|
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.last_state = self.state;
|
||||||
self.state = new_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 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)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub struct StunGun {
|
pub struct StunGun {
|
||||||
pub range: f32,
|
pub range: f32,
|
||||||
pub duration: f64,
|
pub duration: f64,
|
||||||
|
pub level: u8,
|
||||||
|
cost: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StunGun {
|
impl StunGun {
|
||||||
pub fn lvl1() -> Self {
|
pub fn lvl1() -> Self {
|
||||||
Self {
|
Self {
|
||||||
range: 30.0,
|
range: 30.0,
|
||||||
duration: 0.75,
|
duration: 2.0,
|
||||||
|
level: 1,
|
||||||
|
cost: 15,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn lvl2() -> Self {
|
pub fn lvl2() -> Self {
|
||||||
Self {
|
Self {
|
||||||
range: 60.0,
|
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)]
|
impl ItemBase for StunGun {
|
||||||
pub struct AirBag;
|
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)]
|
#[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)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub struct Flippers {
|
pub struct Flippers {
|
||||||
pub speed_increase: f32,
|
pub speed_increase: f32,
|
||||||
|
pub level: u8,
|
||||||
|
cost: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Flippers {
|
impl Flippers {
|
||||||
pub fn lvl1() -> Self {
|
pub fn lvl1() -> Self {
|
||||||
Self {
|
Self {
|
||||||
speed_increase: 1.2
|
speed_increase: 1.1,
|
||||||
|
level: 1,
|
||||||
|
cost: 30,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn lvl2() -> Self {
|
pub fn lvl2() -> Self {
|
||||||
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 profiler;
|
||||||
pub mod triangles;
|
pub mod triangles;
|
||||||
|
pub mod button;
|
||||||
|
|
||||||
pub fn calculate_linear_slide(playthrough_percent: f64) -> f64 {
|
pub fn calculate_linear_slide(playthrough_percent: f64) -> f64 {
|
||||||
if playthrough_percent < 0.25 {
|
if playthrough_percent < 0.25 {
|
||||||
|
@ -39,3 +39,10 @@ impl std::ops::Deref for AudioPlayer {
|
|||||||
&self.backend
|
&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 audio;
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
pub mod complexanimation;
|
|
@ -2,7 +2,7 @@ use raylib::prelude::*;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gamecore::{GameCore, GameState},
|
gamecore::{GameCore, GameState},
|
||||||
lib::wrappers::audio::player::AudioPlayer,
|
lib::{utils::button::OnScreenButton, wrappers::audio::player::AudioPlayer},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::screen::Screen;
|
use super::screen::Screen;
|
||||||
@ -22,13 +22,14 @@ impl Screen for GameEndScreen {
|
|||||||
&mut self,
|
&mut self,
|
||||||
draw_handle: &mut RaylibDrawHandle,
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
_thread: &RaylibThread,
|
_thread: &RaylibThread,
|
||||||
audio_system: &mut AudioPlayer,
|
_audio_system: &mut AudioPlayer,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
) -> Option<GameState> {
|
) -> Option<GameState> {
|
||||||
let mouse_position = draw_handle.get_mouse_position();
|
|
||||||
draw_handle.clear_background(Color::GRAY);
|
draw_handle.clear_background(Color::GRAY);
|
||||||
// TODO: Maybe we can stick some art here?
|
// 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
|
// Window dimensions
|
||||||
let win_height = draw_handle.get_screen_height();
|
let win_height = draw_handle.get_screen_height();
|
||||||
@ -53,75 +54,48 @@ impl Screen for GameEndScreen {
|
|||||||
// Render heading text
|
// Render heading text
|
||||||
draw_handle.draw_text(
|
draw_handle.draw_text(
|
||||||
"OUT OF BREATH",
|
"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,
|
(win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 10,
|
||||||
40,
|
30,
|
||||||
Color::BLACK,
|
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
|
// render button
|
||||||
// let bottom_left_button_dimensions = Rectangle {
|
go_to_menu_button.render(draw_handle);
|
||||||
// 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,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// // Check if the mouse is over either button
|
// If the player clicks on the button send them to shop
|
||||||
// let mouse_over_bottom_left_button =
|
if go_to_menu_button.is_hovered(draw_handle)
|
||||||
// bottom_left_button_dimensions.check_collision_point_rec(mouse_position);
|
&& draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON)
|
||||||
// let mouse_over_bottom_right_button =
|
{
|
||||||
// bottom_right_button_dimensions.check_collision_point_rec(mouse_position);
|
return Some(GameState::InShop);
|
||||||
|
}
|
||||||
// // 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);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,9 @@ pub fn render_hud(
|
|||||||
+ slider_bound_height,
|
+ 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
|
// Render the base of the slider
|
||||||
draw_handle.draw_rectangle(
|
draw_handle.draw_rectangle(
|
||||||
(progress_slider_position.x - slider_bound_height) as i32,
|
(progress_slider_position.x - slider_bound_height) as i32,
|
||||||
|
@ -2,29 +2,24 @@ mod hud;
|
|||||||
mod playerlogic;
|
mod playerlogic;
|
||||||
|
|
||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
use crate::{entities::enemy::{base::EnemyBase, whirlpool::Whirlpool}, gamecore::{self, GameCore, GameState}, lib::wrappers::audio::player::AudioPlayer};
|
||||||
use crate::{
|
|
||||||
entities::enemy::base::EnemyBase,
|
|
||||||
gamecore::{GameCore, GameState},
|
|
||||||
lib::wrappers::audio::player::AudioPlayer,
|
|
||||||
pallette::{SKY, WATER},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::screen::Screen;
|
use super::screen::Screen;
|
||||||
|
use crate::entities::fish::FishEntity;
|
||||||
pub enum InGameState {
|
|
||||||
BUYING,
|
|
||||||
SWIMMING,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct InGameScreen {
|
pub struct InGameScreen {
|
||||||
current_state: InGameState,
|
shader_time_var_location: i32,
|
||||||
|
swim_playing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InGameScreen {
|
impl InGameScreen {
|
||||||
pub fn new() -> Self {
|
pub unsafe fn new(game_core: &GameCore) -> Self {
|
||||||
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,
|
&mut self,
|
||||||
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
dt: f64
|
dt: f64,
|
||||||
|
audio_system: &mut AudioPlayer,
|
||||||
) {
|
) {
|
||||||
// Build source bounds
|
// Build source bounds
|
||||||
let source_bounds = Rectangle {
|
let source_bounds = Rectangle {
|
||||||
@ -49,14 +45,101 @@ impl InGameScreen {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Clear the background
|
// 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
|
// Render fish
|
||||||
let fish_clone = game_core.world.fish.clone();
|
let fish_clone = game_core.world.fish.clone();
|
||||||
for fish in game_core.world.fish.iter_mut() {
|
for fish in game_core.world.fish.iter_mut() {
|
||||||
fish.update_position(&mut game_core.player, dt, &fish_clone);
|
if fish.update_position(&mut game_core.player, dt, &fish_clone) {
|
||||||
fish.render(context_2d);
|
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
|
// Render the world texture
|
||||||
context_2d.draw_texture_rec(
|
context_2d.draw_texture_rec(
|
||||||
@ -80,27 +163,83 @@ impl InGameScreen {
|
|||||||
context_2d.draw_rectangle_lines_ex(collider, 1, Color::RED);
|
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 {
|
impl Screen for InGameScreen {
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
draw_handle: &mut RaylibDrawHandle,
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
thread: &RaylibThread,
|
_thread: &RaylibThread,
|
||||||
audio_system: &mut AudioPlayer,
|
_audio_system: &mut AudioPlayer,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
) -> Option<GameState> {
|
) -> Option<GameState> {
|
||||||
// Calculate DT
|
// Calculate DT
|
||||||
let dt = draw_handle.get_time() - game_core.last_frame_time;
|
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
|
// Handle the pause menu being opened
|
||||||
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||||
return Some(GameState::PauseMenu);
|
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
|
// Window dimensions
|
||||||
let win_height = draw_handle.get_screen_height();
|
let win_height = draw_handle.get_screen_height();
|
||||||
let win_width = draw_handle.get_screen_width();
|
let win_width = draw_handle.get_screen_width();
|
||||||
@ -108,33 +247,138 @@ impl Screen for InGameScreen {
|
|||||||
x: (win_width as f32 / 2.0),
|
x: (win_width as f32 / 2.0),
|
||||||
y: (win_height 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
|
// 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
|
// Open a 2D context
|
||||||
{
|
{
|
||||||
let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera);
|
unsafe {
|
||||||
|
raylib::ffi::BeginTextureMode(*game_core.resources.shader_texture);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let mut context_2d = draw_handle.begin_mode2D(game_core.master_camera);
|
||||||
|
|
||||||
// Render entities
|
// Clear frame
|
||||||
for jellyfish in game_core.world.jellyfish.iter_mut() {
|
context_2d.clear_background(Color::BLACK);
|
||||||
jellyfish.handle_logic(&mut game_core.player, dt);
|
|
||||||
jellyfish.render(&mut context_2d, &mut game_core.resources, dt);
|
// 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
|
// Render the hud
|
||||||
hud::render_hud(draw_handle, game_core, window_center);
|
hud::render_hud(draw_handle, game_core, window_center);
|
||||||
|
|
||||||
@ -143,6 +387,15 @@ impl Screen for InGameScreen {
|
|||||||
return Some(GameState::GameEnd);
|
return Some(GameState::GameEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if game_core
|
||||||
|
.world
|
||||||
|
.end_position
|
||||||
|
.distance_to(game_core.player.position)
|
||||||
|
<= 40.0
|
||||||
|
{
|
||||||
|
return Some(GameState::WinGame);
|
||||||
|
}
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,33 @@
|
|||||||
|
use raylib::core::audio::RaylibAudio;
|
||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
use rand::{prelude::ThreadRng, Rng};
|
||||||
use crate::{
|
use crate::{gamecore::GameCore, lib::wrappers::audio::player::AudioPlayer};
|
||||||
gamecore::GameCore,
|
|
||||||
pallette::{TRANSLUCENT_WHITE_128, TRANSLUCENT_WHITE_64, TRANSLUCENT_WHITE_96},
|
|
||||||
};
|
|
||||||
|
|
||||||
const NORMAL_PLAYER_SPEED: i32 = 1;
|
const NORMAL_PLAYER_SPEED: i32 = 1;
|
||||||
const BOOST_PLAYER_SPEED: i32 = NORMAL_PLAYER_SPEED * 2;
|
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 TURN_SPEED: f32 = 0.15;
|
||||||
const BOOST_DECREASE_PER_SECOND: f32 = 0.65;
|
const BOOST_DECREASE_PER_SECOND: f32 = 0.65;
|
||||||
const BOOST_REGEN_PER_SECOND: f32 = 0.25;
|
const BOOST_REGEN_PER_SECOND: f32 = 0.25;
|
||||||
const BREATH_DECREASE_PER_SECOND: f32 = 0.02;
|
const BREATH_DECREASE_PER_SECOND: f32 = 0.02;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn update_player_movement(
|
pub fn update_player_movement(
|
||||||
draw_handle: &mut RaylibDrawHandle,
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
window_center: Vector2,
|
window_center: Vector2,
|
||||||
|
audio_system: &mut AudioPlayer
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Calculate DT
|
// Calculate DT
|
||||||
let dt = draw_handle.get_time() - game_core.last_frame_time;
|
let dt = draw_handle.get_time() - game_core.last_frame_time;
|
||||||
|
|
||||||
// Handle player movement
|
// Handle player movement
|
||||||
let mouse_pose = draw_handle.get_mouse_position();
|
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 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;
|
let mut normalized_movement_direction = raw_movement_direction;
|
||||||
normalized_movement_direction.normalize();
|
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_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);
|
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 {
|
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
|
// Move the player in their direction
|
||||||
@ -88,6 +109,21 @@ pub fn update_player_movement(
|
|||||||
// Set the speed multiplier
|
// Set the speed multiplier
|
||||||
speed_multiplier = BOOST_PLAYER_SPEED as f32;
|
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
|
// Decrease the boost
|
||||||
game_core.player.boost_percent -= BOOST_DECREASE_PER_SECOND * dt as f32;
|
game_core.player.boost_percent -= BOOST_DECREASE_PER_SECOND * dt as f32;
|
||||||
game_core.player.is_boosting = true;
|
game_core.player.is_boosting = true;
|
||||||
@ -142,35 +178,144 @@ pub fn update_player_movement(
|
|||||||
|
|
||||||
// Update the player's breath
|
// Update the player's breath
|
||||||
game_core.player.breath_percent =
|
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
|
// Only do this if the mouse is far enough away
|
||||||
let player_stunned = game_core.player.stun_timer > 0.0;
|
let player_stunned = game_core.player.stun_timer > 0.0;
|
||||||
let mut player_real_movement = game_core.player.direction * speed_multiplier;
|
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 {
|
if game_core.player.is_moving {
|
||||||
// move in x
|
// 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
|
// Check for any collisions
|
||||||
for collider in game_core.world.colliders.iter() {
|
for collider in game_core.world.colliders.iter() {
|
||||||
if game_core.player.collides_with_rec(collider) {
|
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;
|
player_real_movement.x = 0.0;
|
||||||
|
game_core.player.additional_vel.x = 0.0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// move in y
|
// 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
|
// Check for any collisions
|
||||||
for collider in game_core.world.colliders.iter() {
|
for collider in game_core.world.colliders.iter() {
|
||||||
if game_core.player.collides_with_rec(collider) {
|
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;
|
player_real_movement.y = 0.0;
|
||||||
|
game_core.player.additional_vel.y = 0.0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,17 +325,20 @@ pub fn update_player_movement(
|
|||||||
// Handle updating the stun timer
|
// Handle updating the stun timer
|
||||||
if player_stunned {
|
if player_stunned {
|
||||||
game_core.player.stun_timer -= dt;
|
game_core.player.stun_timer -= dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Move the camera to follow the player
|
// 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 =
|
let player_screen_position =
|
||||||
draw_handle.get_world_to_screen2D(game_core.player.position, game_core.master_camera);
|
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
|
// Camera only moves if you get close to the edge of the screen
|
||||||
if player_screen_position.distance_to(window_center).abs() > 100.0 {
|
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
|
// If the player is not on screen, snap the camera to them
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
use raylib::prelude::*;
|
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;
|
use super::screen::Screen;
|
||||||
|
|
||||||
const SECONDS_PER_LOGO: f64 = 4.0;
|
const SECONDS_PER_LOGO: f64 = 4.0;
|
||||||
const RUST_ORANGE: Color = Color::new(222, 165, 132, 255);
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum LoadingScreenState {
|
enum LoadingScreenState {
|
||||||
@ -47,30 +49,30 @@ impl LoadingScreen {
|
|||||||
win_height: i32,
|
win_height: i32,
|
||||||
win_width: i32,
|
win_width: i32,
|
||||||
) {
|
) {
|
||||||
// Determine how far through rendering this logo we are
|
// // Determine how far through rendering this logo we are
|
||||||
// This value is used to determine the logo alpha
|
// // This value is used to determine the logo alpha
|
||||||
let playthrough_percent =
|
// let playthrough_percent =
|
||||||
(draw_handle.get_time() - self.last_state_switch_time) / SECONDS_PER_LOGO;
|
// (draw_handle.get_time() - self.last_state_switch_time) / SECONDS_PER_LOGO;
|
||||||
|
|
||||||
// Build a color mask
|
// // Build a color mask
|
||||||
let mask = self.get_logo_mask(playthrough_percent);
|
// let mask = self.get_logo_mask(playthrough_percent);
|
||||||
|
|
||||||
// Get the logo
|
// // Get the logo
|
||||||
let logo = &game_core.resources.game_logo;
|
// let logo = &game_core.resources.game_logo;
|
||||||
|
|
||||||
// Render the logo
|
// // Render the logo
|
||||||
draw_handle.draw_texture(
|
// draw_handle.draw_texture(
|
||||||
logo,
|
// logo,
|
||||||
(win_width / 2) - (logo.width / 2),
|
// (win_width / 2) - (logo.width / 2),
|
||||||
(win_height / 2) - (logo.height / 2),
|
// (win_height / 2) - (logo.height / 2),
|
||||||
mask,
|
// mask,
|
||||||
);
|
// );
|
||||||
|
|
||||||
// Move on to next logo if needed
|
// Move on to next logo if needed
|
||||||
if playthrough_percent >= 1.0 {
|
// if playthrough_percent >= 1.0 {
|
||||||
self.state = LoadingScreenState::RaylibLogo;
|
self.state = LoadingScreenState::RaylibLogo;
|
||||||
self.last_state_switch_time = draw_handle.get_time();
|
self.last_state_switch_time = draw_handle.get_time();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_raylib_logo(
|
fn show_raylib_logo(
|
||||||
@ -89,10 +91,10 @@ impl LoadingScreen {
|
|||||||
let mask = self.get_logo_mask(playthrough_percent);
|
let mask = self.get_logo_mask(playthrough_percent);
|
||||||
|
|
||||||
// Create modified colors
|
// Create modified colors
|
||||||
let alpha_orange = Color {
|
let alpha_black = Color {
|
||||||
r: RUST_ORANGE.r,
|
r: 0,
|
||||||
g: RUST_ORANGE.g,
|
g: 0,
|
||||||
b: RUST_ORANGE.b,
|
b: 0,
|
||||||
a: mask.a,
|
a: mask.a,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,7 +104,7 @@ impl LoadingScreen {
|
|||||||
win_height / 2 - 128,
|
win_height / 2 - 128,
|
||||||
256,
|
256,
|
||||||
256,
|
256,
|
||||||
alpha_orange,
|
alpha_black,
|
||||||
);
|
);
|
||||||
draw_handle.draw_rectangle(
|
draw_handle.draw_rectangle(
|
||||||
win_width / 2 - 112,
|
win_width / 2 - 112,
|
||||||
@ -111,19 +113,19 @@ impl LoadingScreen {
|
|||||||
224,
|
224,
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
draw_handle.draw_text(
|
// draw_handle.draw_text(
|
||||||
"rust",
|
// "rust",
|
||||||
win_width / 2 - 69,
|
// win_width / 2 - 69,
|
||||||
win_height / 2 + 18,
|
// win_height / 2 + 18,
|
||||||
50,
|
// 50,
|
||||||
alpha_orange,
|
// alpha_orange,
|
||||||
);
|
// );
|
||||||
draw_handle.draw_text(
|
draw_handle.draw_text(
|
||||||
"raylib",
|
"raylib",
|
||||||
win_width / 2 - 44,
|
win_width / 2 - 44,
|
||||||
win_height / 2 + 48,
|
win_height / 2 + 48,
|
||||||
50,
|
50,
|
||||||
alpha_orange,
|
alpha_black,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Move on to next logo if needed
|
// Move on to next logo if needed
|
||||||
@ -138,7 +140,7 @@ impl Screen for LoadingScreen {
|
|||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
draw_handle: &mut RaylibDrawHandle,
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
thread: &RaylibThread,
|
_thread: &RaylibThread,
|
||||||
_audio_system: &mut AudioPlayer,
|
_audio_system: &mut AudioPlayer,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
) -> Option<GameState> {
|
) -> Option<GameState> {
|
||||||
|
@ -19,55 +19,84 @@ impl Screen for MainMenuScreen {
|
|||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
draw_handle: &mut RaylibDrawHandle,
|
draw_handle: &mut RaylibDrawHandle,
|
||||||
thread: &RaylibThread,
|
_thread: &RaylibThread,
|
||||||
audio_system: &mut AudioPlayer,
|
_audio_system: &mut AudioPlayer,
|
||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
) -> Option<GameState> {
|
) -> Option<GameState> {
|
||||||
// Window dimensions
|
// Window dimensions
|
||||||
let win_height = draw_handle.get_screen_height();
|
let win_height = draw_handle.get_screen_height();
|
||||||
let win_width = draw_handle.get_screen_width();
|
let win_width = draw_handle.get_screen_width();
|
||||||
|
|
||||||
// Clear frame
|
// // Clear frame
|
||||||
draw_handle.clear_background(Color::BLUE);
|
// draw_handle.clear_background(Color::BLUE);
|
||||||
|
// Render the background
|
||||||
|
draw_handle.draw_texture(&game_core.resources.shop_background, 0, 0, Color::WHITE);
|
||||||
|
|
||||||
// Render title
|
// Render title
|
||||||
draw_handle.draw_text(
|
draw_handle.draw_text(
|
||||||
"ONE BREATH",
|
"DEEP BREATH",
|
||||||
(win_height / 2) - 80,
|
(win_height / 2) - 100,
|
||||||
win_width / 4,
|
win_width / 8,
|
||||||
40,
|
80,
|
||||||
Color::BLACK,
|
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
|
// Play and quit
|
||||||
draw_handle.draw_text(
|
draw_handle.draw_text(
|
||||||
"Play",
|
"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,
|
(win_width / 4) + 100,
|
||||||
20,
|
60,
|
||||||
Color::BLACK,
|
match hovering_shop_button {
|
||||||
|
true => Color::BLUE,
|
||||||
|
false => Color::BLACK,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
draw_handle.draw_text(
|
draw_handle.draw_text(
|
||||||
"Quit",
|
"Quit",
|
||||||
(win_height / 2) - 80,
|
(win_height / 2) + 130,
|
||||||
(win_width / 4) + 140,
|
(win_width / 4) + 200,
|
||||||
20,
|
60,
|
||||||
Color::BLACK,
|
match hovering_quit_button {
|
||||||
|
true => Color::RED,
|
||||||
|
false => Color::BLACK,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle button presses
|
// Handle button presses
|
||||||
let mouse_position = draw_handle.get_mouse_position();
|
|
||||||
let mouse_clicked = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON);
|
let mouse_clicked = draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON);
|
||||||
|
|
||||||
// Check clicks
|
// Check clicks
|
||||||
if mouse_clicked {
|
if mouse_clicked {
|
||||||
if mouse_position.y > (win_width as f32 / 4.0) + 100.0
|
if hovering_play_button {
|
||||||
&& mouse_position.y < (win_width as f32 / 4.0) + 120.0
|
// 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);
|
return Some(GameState::InGame);
|
||||||
} else if mouse_position.y > (win_width as f32 / 4.0) + 140.0
|
} else if hovering_shop_button {
|
||||||
&& mouse_position.y < (win_width as f32 / 4.0) + 180.0
|
_audio_system.play_sound(&game_core.resources.ui_click);
|
||||||
{
|
return Some(GameState::InShop);
|
||||||
|
} else if hovering_quit_button {
|
||||||
return Some(GameState::GameQuit);
|
return Some(GameState::GameQuit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,6 @@ pub mod loadingscreen;
|
|||||||
pub mod mainmenu;
|
pub mod mainmenu;
|
||||||
pub mod pausemenu;
|
pub mod pausemenu;
|
||||||
pub mod ingame;
|
pub mod ingame;
|
||||||
pub mod gameend;
|
pub mod gameend;
|
||||||
|
pub mod shop;
|
||||||
|
pub mod winscreen;
|
@ -2,7 +2,7 @@ use raylib::prelude::*;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gamecore::{GameCore, GameState},
|
gamecore::{GameCore, GameState},
|
||||||
lib::wrappers::audio::player::AudioPlayer,
|
lib::{utils::button::OnScreenButton, wrappers::audio::player::AudioPlayer},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::screen::Screen;
|
use super::screen::Screen;
|
||||||
@ -26,8 +26,11 @@ impl Screen for PauseMenuScreen {
|
|||||||
game_core: &mut GameCore,
|
game_core: &mut GameCore,
|
||||||
) -> Option<GameState> {
|
) -> Option<GameState> {
|
||||||
let mouse_position = draw_handle.get_mouse_position();
|
let mouse_position = draw_handle.get_mouse_position();
|
||||||
draw_handle.clear_background(Color::GRAY);
|
// draw_handle.clear_background(Color::GRAY);
|
||||||
// TODO: Maybe we can stick some art here?
|
// // 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 escape is pressed again, return to the previous render state
|
||||||
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||||
@ -120,14 +123,15 @@ impl Screen for PauseMenuScreen {
|
|||||||
|
|
||||||
// Render credits
|
// Render credits
|
||||||
draw_handle.draw_text(
|
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_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,
|
20,
|
||||||
Color::BLACK,
|
Color::BLACK,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Close and quit buttons
|
// Bottom buttons
|
||||||
|
|
||||||
let bottom_left_button_dimensions = Rectangle {
|
let bottom_left_button_dimensions = Rectangle {
|
||||||
x: (win_width as f32 / 2.0) - (SCREEN_PANEL_SIZE.x / 2.0) + 5.0,
|
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,
|
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,
|
height: bottom_left_button_dimensions.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the mouse is over either button
|
let menu_button = OnScreenButton::new(
|
||||||
let mouse_over_bottom_left_button =
|
"Menu".to_string(),
|
||||||
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,
|
bottom_left_button_dimensions,
|
||||||
3,
|
Color::WHITE,
|
||||||
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,
|
Color::BLACK,
|
||||||
|
Color::GRAY,
|
||||||
|
30,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
draw_handle.draw_rectangle_lines_ex(
|
let close_button = OnScreenButton::new(
|
||||||
|
"Close".to_string(),
|
||||||
bottom_right_button_dimensions,
|
bottom_right_button_dimensions,
|
||||||
3,
|
Color::WHITE,
|
||||||
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,
|
Color::BLACK,
|
||||||
|
Color::GRAY,
|
||||||
|
30,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Render both
|
||||||
|
menu_button.render(draw_handle);
|
||||||
|
close_button.render(draw_handle);
|
||||||
|
|
||||||
// Handle click actions on the buttons
|
// Handle click actions on the buttons
|
||||||
if draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) {
|
if draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) {
|
||||||
if mouse_over_bottom_left_button {
|
if menu_button.is_hovered(draw_handle) {
|
||||||
return Some(GameState::GameQuit);
|
audio_system.play_sound(&game_core.resources.ui_click);
|
||||||
} else if mouse_over_bottom_right_button {
|
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);
|
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);
|
||||||
|
}
|
||||||
|
}
|