├── .github
└── workflows
│ ├── deploy-page.yaml
│ └── release.yaml
├── .gitignore
├── .vscode
└── launch.json
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── assets
├── audio
│ └── sfx
│ │ ├── drone.ogg
│ │ ├── echo1.ogg
│ │ ├── echo2.ogg
│ │ ├── enemy_alert1.ogg
│ │ ├── enemy_alert2.ogg
│ │ ├── enemy_death1.ogg
│ │ ├── enemy_death2.ogg
│ │ ├── level_cleared.ogg
│ │ ├── player_death1.ogg
│ │ └── player_death2.ogg
├── fonts
│ └── Spaceport_2006.otf
├── sprites.svg
└── textures
│ ├── bevy.png
│ ├── button.png
│ ├── charge.png
│ ├── circle.png
│ ├── circle_outline.png
│ ├── controls_arrows.png
│ ├── echo_ping.png
│ ├── player.png
│ ├── portal.png
│ ├── spiky.png
│ └── wave.png
├── build.rs
├── build
├── macos
│ ├── AppIcon.iconset
│ │ ├── icon_128x128.png
│ │ ├── icon_128x128@2x.png
│ │ ├── icon_16x16.png
│ │ ├── icon_16x16@2x.png
│ │ ├── icon_256x256.png
│ │ ├── icon_256x256@2x.png
│ │ ├── icon_32x32.png
│ │ ├── icon_32x32@2x.png
│ │ ├── icon_512x512.png
│ │ └── icon_512x512@2x.png
│ ├── create_icns.sh
│ ├── icon_1024x1024.png
│ └── src
│ │ └── Game.app
│ │ └── Contents
│ │ ├── Info.plist
│ │ └── Resources
│ │ └── AppIcon.icns
├── web
│ ├── sound.js
│ └── styles.css
└── windows
│ ├── icon.ico
│ └── icon.rc
├── credits
├── CREDITS.md
└── licenses
│ └── Bevy_MIT_License.md
├── index.html
├── itch
├── cover.png
├── cover.xcf
└── echo_1.gif
├── rust-toolchain.toml
├── src
├── agent
│ ├── agent.rs
│ └── mod.rs
├── animation
│ ├── mod.rs
│ ├── tween.rs
│ ├── tween_events.rs
│ ├── tween_lenses.rs
│ └── tween_macros.rs
├── assets
│ ├── audio.rs
│ ├── fonts.rs
│ ├── mod.rs
│ ├── textures.rs
│ └── window_icon.rs
├── audio
│ ├── mod.rs
│ └── sfx.rs
├── debug
│ └── mod.rs
├── echolocation
│ ├── echolocation.rs
│ ├── mod.rs
│ └── wave.rs
├── enemy
│ └── mod.rs
├── input
│ ├── actions.rs
│ ├── cooldown.rs
│ ├── mod.rs
│ └── mouse.rs
├── io
│ ├── mod.rs
│ └── save.rs
├── level
│ ├── level.rs
│ ├── map.rs
│ └── mod.rs
├── lib.rs
├── main.rs
├── physics
│ └── mod.rs
├── player
│ ├── mod.rs
│ └── player.rs
├── render
│ ├── camera.rs
│ ├── mod.rs
│ └── palette.rs
├── state
│ ├── mod.rs
│ ├── pause.rs
│ ├── reset.rs
│ └── state.rs
├── time
│ ├── mod.rs
│ └── time.rs
├── tools
│ ├── ecs.rs
│ ├── enum_tools.rs
│ ├── math.rs
│ └── mod.rs
└── ui
│ ├── button.rs
│ ├── game_over.rs
│ ├── menu.rs
│ ├── mod.rs
│ ├── pause.rs
│ ├── splash.rs
│ └── tutorial.rs
└── todo.md
/.github/workflows/deploy-page.yaml:
--------------------------------------------------------------------------------
1 | name: deploy-github-page
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | permissions:
7 | contents: write
8 |
9 | jobs:
10 | build-web:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout repository
15 | uses: actions/checkout@v2
16 | - name: Install rust toolchain
17 | uses: actions-rs/toolchain@v1
18 | with:
19 | toolchain: nightly
20 | override: true
21 | - name: Install Dependencies
22 | run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev
23 | - name: Install trunk
24 | uses: jetli/trunk-action@v0.1.0
25 | with:
26 | version: 'latest'
27 | - name: Add wasm target
28 | run: |
29 | rustup target add wasm32-unknown-unknown
30 | - name: Build Release
31 | run: |
32 | trunk build --release --public-url "${GITHUB_REPOSITORY#*/}"
33 | - name: optimize Wasm
34 | uses: NiklasEi/wasm-opt-action@v2
35 | with:
36 | file: dist/*.wasm
37 | - name: Deploy to GitHub Pages
38 | uses: JamesIves/github-pages-deploy-action@v4.2.5
39 | with:
40 | branch: gh-pages
41 | folder: dist
42 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: release-flow
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v[0-9]+.[0-9]+.[0-9]+*"
7 |
8 | # ToDo: adapt names
9 | env:
10 | # heads-up: this value is used as a pattern in an sed command as a workaround for a trunk issue
11 | # if you use special characters, take a look at the 'Make paths relative' step in the 'build-web' job
12 | GAME_EXECUTABLE_NAME: ranos
13 | GAME_OSX_APP_NAME: ranos
14 |
15 | permissions:
16 | contents: write
17 |
18 | jobs:
19 | build-macOS:
20 | runs-on: macos-latest
21 |
22 | env:
23 | # macOS 11.0 Big Sur is the first version to support universal binaries
24 | MACOSX_DEPLOYMENT_TARGET: 11.0
25 | steps:
26 | - name: Get tag
27 | id: tag
28 | uses: dawidd6/action-get-tag@v1
29 | - name: Checkout repository
30 | uses: actions/checkout@v2
31 | - name: Remove build script
32 | run: |
33 | rm build.rs
34 | - name: Install rust toolchain for Apple Silicon
35 | uses: actions-rs/toolchain@v1
36 | with:
37 | toolchain: nightly
38 | target: aarch64-apple-darwin
39 | override: true
40 | - name: Build release for Apple Silicon
41 | run: |
42 | SDKROOT=$(xcrun -sdk macosx --show-sdk-path) cargo build --release --target=aarch64-apple-darwin
43 | - name: Install rust toolchain for Apple x86
44 | uses: actions-rs/toolchain@v1
45 | with:
46 | toolchain: nightly
47 | target: x86_64-apple-darwin
48 | override: true
49 | - name: Build release for x86 Apple
50 | run: |
51 | SDKROOT=$(xcrun -sdk macosx --show-sdk-path) cargo build --release --target=x86_64-apple-darwin
52 | - name: Create Universal Binary
53 | run: |
54 | lipo -create -output target/release/${{ env.GAME_EXECUTABLE_NAME }} target/aarch64-apple-darwin/release/${{ env.GAME_EXECUTABLE_NAME }} target/x86_64-apple-darwin/release/${{ env.GAME_EXECUTABLE_NAME }}
55 | - name: Create release
56 | run: |
57 | mkdir -p build/macos/src/Game.app/Contents/MacOS/assets
58 | cp -r assets/ build/macos/src/Game.app/Contents/MacOS/assets
59 | cp -r credits/ build/macos/src/Game.app/Contents/MacOS/credits
60 | cp target/release/${{ env.GAME_EXECUTABLE_NAME }} build/macos/src/Game.app/Contents/MacOS/
61 | strip build/macos/src/Game.app/Contents/MacOS/${{ env.GAME_EXECUTABLE_NAME }}
62 | mv build/macos/src/Game.app build/macos/src/${{ env.GAME_OSX_APP_NAME }}.app
63 | ln -s /Applications build/macos/src/
64 | hdiutil create -fs HFS+ -volname "${{ env.GAME_OSX_APP_NAME }}" -srcfolder build/macos/src ${{ env.GAME_EXECUTABLE_NAME }}.dmg
65 | - name: Upload release
66 | uses: svenstaro/upload-release-action@v2
67 | with:
68 | repo_token: ${{ secrets.GITHUB_TOKEN }}
69 | file: ${{ env.GAME_EXECUTABLE_NAME }}.dmg
70 | asset_name: ${{ env.GAME_EXECUTABLE_NAME }}_${{ steps.tag.outputs.tag }}_macOS.dmg
71 | tag: ${{ github.ref }}
72 | overwrite: true
73 |
74 | build-linux:
75 | runs-on: ubuntu-latest
76 |
77 | steps:
78 | - name: Get tag
79 | id: tag
80 | uses: dawidd6/action-get-tag@v1
81 | - name: Checkout repository
82 | uses: actions/checkout@v2
83 | - name: Install rust toolchain
84 | uses: actions-rs/toolchain@v1
85 | with:
86 | toolchain: nightly
87 | override: true
88 | - name: Install Dependencies
89 | run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev
90 | - name: Build release
91 | run: |
92 | cargo build --release
93 | - name: Prepare release
94 | run: |
95 | strip target/release/${{ env.GAME_EXECUTABLE_NAME }}
96 | chmod +x target/release/${{ env.GAME_EXECUTABLE_NAME }}
97 | mv target/release/${{ env.GAME_EXECUTABLE_NAME }} .
98 | - name: Bundle release
99 | run: |
100 | tar -czf ${{ env.GAME_EXECUTABLE_NAME }}_linux.tar.gz ${{ env.GAME_EXECUTABLE_NAME }} assets credits
101 | - name: Upload release
102 | uses: svenstaro/upload-release-action@v2
103 | with:
104 | repo_token: ${{ secrets.GITHUB_TOKEN }}
105 | file: ${{ env.GAME_EXECUTABLE_NAME }}_linux.tar.gz
106 | asset_name: ${{ env.GAME_EXECUTABLE_NAME }}_${{ steps.tag.outputs.tag }}_linux.tar.gz
107 | tag: ${{ github.ref }}
108 | overwrite: true
109 |
110 | build-windows:
111 | runs-on: windows-latest
112 |
113 | steps:
114 | - name: Get tag
115 | id: tag
116 | uses: dawidd6/action-get-tag@v1
117 | - name: Checkout repository
118 | uses: actions/checkout@v2
119 | - name: Install rust toolchain
120 | uses: actions-rs/toolchain@v1
121 | with:
122 | toolchain: nightly
123 | override: true
124 | - name: Build release
125 | run: |
126 | cargo build --release
127 | - name: Prepare release
128 | run: |
129 | mkdir target/release/assets && cp -r assets target/release/assets
130 | mkdir target/release/credits && cp -r credits target/release/credits
131 | - name: Zip release
132 | uses: vimtor/action-zip@v1
133 | with:
134 | files: target/release/assets/ target/release/credits/ target/release/${{ env.GAME_EXECUTABLE_NAME }}.exe
135 | dest: ${{ env.GAME_EXECUTABLE_NAME }}_windows.zip
136 | - name: Upload release
137 | uses: svenstaro/upload-release-action@v2
138 | with:
139 | repo_token: ${{ secrets.GITHUB_TOKEN }}
140 | file: ${{ env.GAME_EXECUTABLE_NAME }}_windows.zip
141 | asset_name: ${{ env.GAME_EXECUTABLE_NAME }}_${{ steps.tag.outputs.tag }}_windows.zip
142 | tag: ${{ github.ref }}
143 | overwrite: true
144 |
145 | build-web:
146 | runs-on: ubuntu-latest
147 |
148 | steps:
149 | - name: Get tag
150 | id: tag
151 | uses: dawidd6/action-get-tag@v1
152 | - name: Checkout repository
153 | uses: actions/checkout@v2
154 | - name: Install rust toolchain
155 | uses: actions-rs/toolchain@v1
156 | with:
157 | toolchain: nightly
158 | override: true
159 | - name: Install Dependencies
160 | run: sudo apt-get update; sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev
161 | - name: Install trunk
162 | uses: jetli/trunk-action@v0.1.0
163 | with:
164 | version: latest
165 | - name: Add wasm target
166 | run: |
167 | rustup target add wasm32-unknown-unknown
168 | - name: Build Release
169 | run: |
170 | trunk build --release
171 | - name: optimize Wasm
172 | uses: NiklasEi/wasm-opt-action@v2
173 | with:
174 | file: dist/*.wasm
175 | # Trunk cannot import assets from relative paths (see e.g. https://github.com/thedodd/trunk/issues/395)
176 | # On sites like itch.io, we don't know on which base path the game gets served, so we need to rewrite all links to be relative
177 | - name: Make paths relative
178 | run: |
179 | sed -i 's/\/index/.\/index/g' dist/index.html
180 | sed -i 's/\/${{ env.GAME_EXECUTABLE_NAME }}/.\/${{ env.GAME_EXECUTABLE_NAME }}/g' dist/index.html
181 | - name: Zip release
182 | uses: vimtor/action-zip@v1
183 | with:
184 | files: dist/
185 | dest: ${{ env.GAME_EXECUTABLE_NAME }}_web.zip
186 | - name: Upload release
187 | uses: svenstaro/upload-release-action@v2
188 | with:
189 | repo_token: ${{ secrets.GITHUB_TOKEN }}
190 | file: ${{ env.GAME_EXECUTABLE_NAME }}_web.zip
191 | asset_name: ${{ env.GAME_EXECUTABLE_NAME }}_${{ steps.tag.outputs.tag }}_web.zip
192 | tag: ${{ github.ref }}
193 | overwrite: true
194 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .idea/
3 | .DS_Store
4 |
5 | dist/
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | // this config bypasses waiting for debugger attachment, increasing speed
8 | {
9 | "name": "Quick Launch",
10 | "type": "node-terminal",
11 | "request": "launch",
12 | "command": "cargo run --features dev"
13 | },
14 | {
15 | "type": "lldb",
16 | "request": "launch",
17 | "name": "Debug",
18 | "cargo": {
19 | "args": [
20 | "build",
21 | "--bin=ranos",
22 | "--package=ranos",
23 | "--features",
24 | "dev"
25 | ],
26 | "filter": {
27 | "name": "ranos",
28 | "kind": "bin"
29 | }
30 | },
31 | "args": [],
32 | "cwd": "${workspaceFolder}",
33 | "env": {
34 | "CARGO_MANIFEST_DIR": "${workspaceFolder}"
35 | },
36 | "linux": {
37 | "env": {
38 | "LD_LIBRARY_PATH": "${env:LD_LIBRARY_PATH}:${workspaceFolder}/target/debug:${env:HOME}/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib:${env:HOME}/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib"
39 | }
40 | }
41 | },
42 | {
43 | "type": "lldb",
44 | "request": "launch",
45 | "name": "Debug unit tests",
46 | "cargo": {
47 | "args": [
48 | "test",
49 | "--no-run",
50 | "--bin=ranos",
51 | "--package=ranos",
52 | "--features",
53 | "dev"
54 | ],
55 | "filter": {
56 | "name": "ranos",
57 | "kind": "bin"
58 | }
59 | },
60 | "args": [],
61 | "cwd": "${workspaceFolder}",
62 | "env": {
63 | "CARGO_MANIFEST_DIR": "${workspaceFolder}",
64 | "LD_LIBRARY_PATH": "${env:LD_LIBRARY_PATH}:${workspaceFolder}/target/debug:${env:HOME}/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib:${env:HOME}/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib"
65 | }
66 | }
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "ranos"
3 | version = "0.1.0"
4 | publish = false
5 | edition = "2021"
6 | exclude = ["dist", "build", "assets", "credits"]
7 | rust-version = "1.70.0"
8 |
9 | [profile.dev.package."*"]
10 | opt-level = 3
11 |
12 | [profile.dev]
13 | opt-level = 1
14 |
15 | [profile.release]
16 | lto = true
17 | codegen-units = 1
18 |
19 | # The profile that 'cargo dist' will build with
20 | [profile.dist]
21 | inherits = "release"
22 | lto = "thin"
23 |
24 | [features]
25 | dev = ["bevy/bevy_dylib"]
26 |
27 | # Bevy defaults minus audio and some other not needed things
28 | # see https://github.com/bevyengine/bevy/blob/main/Cargo.toml#L31-L54
29 | default = [
30 | "bevy/animation",
31 | "bevy/bevy_asset",
32 | "bevy/bevy_scene",
33 | "bevy/bevy_winit",
34 | "bevy/bevy_core_pipeline",
35 | "bevy/bevy_render",
36 | "bevy/bevy_sprite",
37 | "bevy/bevy_text",
38 | "bevy/bevy_ui",
39 | "bevy/png",
40 | "bevy/hdr",
41 | "bevy/zstd",
42 | "bevy/x11",
43 | "bevy/ktx2",
44 | "bevy/filesystem_watcher",
45 | "bevy/tonemapping_luts",
46 | ]
47 |
48 | [dependencies]
49 | bevy = { version = "0.10", default-features = false }
50 | bevy_kira_audio = { version = "0.15", features = ["mp3"] }
51 | bevy_asset_loader = { version = "0.15", features = [
52 | "standard_dynamic_assets",
53 | "progress_tracking",
54 | ] }
55 | rand = "0.8.3"
56 | seldom_fn_plugin = "0.3.0"
57 | bevy_tweening = { git = "https://github.com/SecretPocketCat/bevy_tweening", branch = "generic_completed_payload" }
58 | bevycheck = "0.5.2"
59 | bevy_pkv = "0.7.0"
60 | serde = "1.0.157"
61 | leafwing-input-manager = "0.9.1"
62 | iyes_progress = "0.8.0"
63 | bevy_rapier2d = { version = "*", features = ["debug-render-2d"] }
64 |
65 | # keep the following in sync with Bevy's dependencies
66 | winit = { version = "0.28", default-features = false }
67 | image = { version = "0.24", default-features = false }
68 | bevy_framepace = "0.12.1"
69 | bevy_editor_pls = "0.4.0"
70 | paste = "1.0.12"
71 | bracket-geometry = "0.8.7"
72 | bracket-algorithm-traits = "0.8.7"
73 | bracket-pathfinding = "0.8.7"
74 | interpolation = "0.2.0"
75 |
76 | [build-dependencies]
77 | embed-resource = "1.4"
78 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
--------------------------------------------------------------------------------
/assets/audio/sfx/drone.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/drone.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/echo1.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/echo1.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/echo2.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/echo2.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/enemy_alert1.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/enemy_alert1.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/enemy_alert2.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/enemy_alert2.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/enemy_death1.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/enemy_death1.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/enemy_death2.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/enemy_death2.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/level_cleared.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/level_cleared.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/player_death1.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/player_death1.ogg
--------------------------------------------------------------------------------
/assets/audio/sfx/player_death2.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/audio/sfx/player_death2.ogg
--------------------------------------------------------------------------------
/assets/fonts/Spaceport_2006.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/fonts/Spaceport_2006.otf
--------------------------------------------------------------------------------
/assets/sprites.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
392 |
--------------------------------------------------------------------------------
/assets/textures/bevy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/bevy.png
--------------------------------------------------------------------------------
/assets/textures/button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/button.png
--------------------------------------------------------------------------------
/assets/textures/charge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/charge.png
--------------------------------------------------------------------------------
/assets/textures/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/circle.png
--------------------------------------------------------------------------------
/assets/textures/circle_outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/circle_outline.png
--------------------------------------------------------------------------------
/assets/textures/controls_arrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/controls_arrows.png
--------------------------------------------------------------------------------
/assets/textures/echo_ping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/echo_ping.png
--------------------------------------------------------------------------------
/assets/textures/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/player.png
--------------------------------------------------------------------------------
/assets/textures/portal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/portal.png
--------------------------------------------------------------------------------
/assets/textures/spiky.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/spiky.png
--------------------------------------------------------------------------------
/assets/textures/wave.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/assets/textures/wave.png
--------------------------------------------------------------------------------
/build.rs:
--------------------------------------------------------------------------------
1 | extern crate embed_resource;
2 | use std::env;
3 |
4 | fn main() {
5 | let target = env::var("TARGET").unwrap();
6 | if target.contains("windows") {
7 | // on windows we will set our game icon as icon for the executable
8 | embed_resource::compile("build/windows/icon.rc");
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_128x128.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_16x16.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_256x256.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_32x32.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_512x512.png
--------------------------------------------------------------------------------
/build/macos/AppIcon.iconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/AppIcon.iconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/build/macos/create_icns.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | rm -rf AppIcon.iconset/*
4 | mkdir -p AppIcon.iconset
5 | sips -z 16 16 icon_1024x1024.png --out AppIcon.iconset/icon_16x16.png
6 | sips -z 32 32 icon_1024x1024.png --out AppIcon.iconset/icon_16x16@2x.png
7 | sips -z 32 32 icon_1024x1024.png --out AppIcon.iconset/icon_32x32.png
8 | sips -z 64 64 icon_1024x1024.png --out AppIcon.iconset/icon_32x32@2x.png
9 | sips -z 128 128 icon_1024x1024.png --out AppIcon.iconset/icon_128x128.png
10 | sips -z 256 256 icon_1024x1024.png --out AppIcon.iconset/icon_128x128@2x.png
11 | sips -z 256 256 icon_1024x1024.png --out AppIcon.iconset/icon_256x256.png
12 | sips -z 512 512 icon_1024x1024.png --out AppIcon.iconset/icon_256x256@2x.png
13 | sips -z 512 512 icon_1024x1024.png --out AppIcon.iconset/icon_512x512.png
14 | cp icon_1024x1024.png AppIcon.iconset/icon_512x512@2x.png
15 | iconutil -c icns AppIcon.iconset
16 | mkdir -p src/Game.app/Contents/Resources
17 | mv AppIcon.icns src/Game.app/Contents/Resources/
18 |
--------------------------------------------------------------------------------
/build/macos/icon_1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/icon_1024x1024.png
--------------------------------------------------------------------------------
/build/macos/src/Game.app/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ranos
9 | CFBundleExecutable
10 | ranos
11 | CFBundleIconFile
12 | AppIcon.icns
13 | CFBundleIdentifier
14 | spc.ranos
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | ranos
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 |
23 | 0.1.0
24 | CFBundleSupportedPlatforms
25 |
26 | MacOSX
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/build/macos/src/Game.app/Contents/Resources/AppIcon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/macos/src/Game.app/Contents/Resources/AppIcon.icns
--------------------------------------------------------------------------------
/build/web/sound.js:
--------------------------------------------------------------------------------
1 | // Insert hack to make sound autoplay on Chrome as soon as the user interacts with the tab:
2 | // https://developers.google.com/web/updates/2018/11/web-audio-autoplay#moving-forward
3 |
4 | // the following function keeps track of all AudioContexts and resumes them on the first user
5 | // interaction with the page. If the function is called and all contexts are already running,
6 | // it will remove itself from all event listeners.
7 | (function () {
8 | // An array of all contexts to resume on the page
9 | const audioContextList = [];
10 |
11 | // An array of various user interaction events we should listen for
12 | const userInputEventNames = [
13 | "click",
14 | "contextmenu",
15 | "auxclick",
16 | "dblclick",
17 | "mousedown",
18 | "mouseup",
19 | "pointerup",
20 | "touchend",
21 | "keydown",
22 | "keyup",
23 | ];
24 |
25 | // A proxy object to intercept AudioContexts and
26 | // add them to the array for tracking and resuming later
27 | self.AudioContext = new Proxy(self.AudioContext, {
28 | construct(target, args) {
29 | const result = new target(...args);
30 | audioContextList.push(result);
31 | return result;
32 | },
33 | });
34 |
35 | // To resume all AudioContexts being tracked
36 | function resumeAllContexts(_event) {
37 | let count = 0;
38 |
39 | audioContextList.forEach((context) => {
40 | if (context.state !== "running") {
41 | context.resume();
42 | } else {
43 | count++;
44 | }
45 | });
46 |
47 | // If all the AudioContexts have now resumed then we unbind all
48 | // the event listeners from the page to prevent unnecessary resume attempts
49 | // Checking count > 0 ensures that the user interaction happens AFTER the game started up
50 | if (count > 0 && count === audioContextList.length) {
51 | userInputEventNames.forEach((eventName) => {
52 | document.removeEventListener(eventName, resumeAllContexts);
53 | });
54 | }
55 | }
56 |
57 | // We bind the resume function for each user interaction
58 | // event on the page
59 | userInputEventNames.forEach((eventName) => {
60 | document.addEventListener(eventName, resumeAllContexts);
61 | });
62 | })();
63 |
--------------------------------------------------------------------------------
/build/web/styles.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | background: repeating-linear-gradient(
7 | 135deg,
8 | black 0,
9 | black 2px,
10 | white 2px,
11 | white 20px
12 | );
13 | margin: 0;
14 | }
15 |
16 | .game-container {
17 | width: 100%;
18 | height: 100%;
19 | display: flex;
20 | justify-content: center;
21 | align-items: center;
22 | }
23 |
24 | #bevy {
25 | background-color: white;
26 | width: 800px;
27 | height: 600px;
28 | }
29 |
--------------------------------------------------------------------------------
/build/windows/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/build/windows/icon.ico
--------------------------------------------------------------------------------
/build/windows/icon.rc:
--------------------------------------------------------------------------------
1 | app_icon ICON "icon.ico"
2 |
--------------------------------------------------------------------------------
/credits/CREDITS.md:
--------------------------------------------------------------------------------
1 | # Credits
2 |
3 | ## Assets
4 |
5 | * Bevy icon: [MIT License](licenses/Bevy_MIT_License.md);
6 | * [Spaceport 2006 font](https://www.dafont.com/spaceport-2006.font);
7 | * [DEEP MAZE PALETTE](https://lospec.com/palette-list/deep-maze);
8 | * [SFX and music](https://github.com/adsick);
9 |
10 | ## Original template
11 |
12 | * [bevy_game_template](https://github.com/NiklasEi/bevy_game_template)
13 |
14 |
--------------------------------------------------------------------------------
/credits/licenses/Bevy_MIT_License.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ranos
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/itch/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/itch/cover.png
--------------------------------------------------------------------------------
/itch/cover.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/itch/cover.xcf
--------------------------------------------------------------------------------
/itch/echo_1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meepleek/echo_nomical/78777d1c501439d61055a96dca39040fda2212ce/itch/echo_1.gif
--------------------------------------------------------------------------------
/rust-toolchain.toml:
--------------------------------------------------------------------------------
1 | [toolchain]
2 | channel = "nightly-2023-03-16"
3 |
--------------------------------------------------------------------------------
/src/agent/agent.rs:
--------------------------------------------------------------------------------
1 | use std::marker::PhantomData;
2 |
3 | use bevy::prelude::*;
4 | use bevy_rapier2d::prelude::*;
5 | use bevy_tweening::*;
6 | use interpolation::*;
7 |
8 | use crate::{level::level::Wall, physics::check_collision_start_pair, time::time::*};
9 |
10 | #[derive(Component, Deref, DerefMut, Default, Reflect)]
11 | pub struct MovementDirection(pub Vec2);
12 |
13 | #[derive(Component, Reflect)]
14 | pub struct MovementDirectionEasing {
15 | time_to_ease: f32,
16 | time: f32,
17 | #[reflect(ignore)]
18 | ease: EaseFunction,
19 | eased_dir: Vec2,
20 | }
21 |
22 | impl MovementDirectionEasing {
23 | pub fn new(time_to_ease: f32) -> Self {
24 | Self::with_ease_fn(time_to_ease, EaseFunction::SineInOut)
25 | }
26 |
27 | pub fn with_ease_fn(time_to_ease: f32, ease: EaseFunction) -> Self {
28 | Self {
29 | time_to_ease,
30 | time: 0.,
31 | ease,
32 | eased_dir: Vec2::ZERO,
33 | }
34 | }
35 |
36 | pub fn reset(&mut self) {
37 | self.time = 0.
38 | }
39 | }
40 |
41 | #[derive(Component, Deref, DerefMut, Default)]
42 | pub struct Speed(pub f32);
43 |
44 | /* How long does it take to damp the movement direction */
45 | #[derive(Component, Deref, DerefMut, Default)]
46 | pub struct Damping(pub f32);
47 |
48 | #[derive(Component, Deref, DerefMut, Default)]
49 | pub struct AgentRotation(pub f32);
50 |
51 | #[derive(Component)]
52 | pub struct StopOnCollision(PhantomData);
53 |
54 | impl StopOnCollision {
55 | pub fn new() -> Self {
56 | Self(PhantomData::default())
57 | }
58 | }
59 |
60 | #[derive(Component, Deref, DerefMut)]
61 | pub struct DespawnParent(pub Entity);
62 |
63 | #[derive(Component, Deref, DerefMut, Default)]
64 | pub struct Age(pub f32);
65 |
66 | pub(super) fn move_agents(
67 | mut velocity_q: Query<(
68 | &MovementDirection,
69 | Option<&MovementDirectionEasing>,
70 | &Speed,
71 | &mut Transform,
72 | Option<&mut KinematicCharacterController>,
73 | )>,
74 | time: ScaledTime,
75 | ) {
76 | for (dir, eased, speed, mut trans, char_cont) in velocity_q.iter_mut() {
77 | let vel = eased.map_or(dir.0, |e| e.eased_dir) * speed.0 * time.scaled_delta_seconds();
78 |
79 | if let Some(mut char_cont) = char_cont {
80 | char_cont.translation = Some(vel);
81 | } else {
82 | trans.translation += vel.extend(0.);
83 | }
84 | }
85 | }
86 |
87 | pub(super) fn ease_direction(
88 | mut dir_easing_q: Query<(&MovementDirection, &mut MovementDirectionEasing)>,
89 | time: ScaledTime,
90 | ) {
91 | for (dir, mut ease_dir) in dir_easing_q.iter_mut() {
92 | let time_step = time.scaled_delta_seconds()
93 | * if ease_dir.eased_dir.length() < dir.0.length() {
94 | 1.
95 | } else {
96 | -1.
97 | };
98 |
99 | ease_dir.time = (ease_dir.time + time_step).clamp(0., ease_dir.time_to_ease);
100 | ease_dir.eased_dir = dir.0 * ((ease_dir.time / ease_dir.time_to_ease).calc(ease_dir.ease));
101 | }
102 | }
103 |
104 | pub(super) fn apply_damping(
105 | mut damping_q: Query<(&mut MovementDirection, &Damping)>,
106 | time: ScaledTime,
107 | ) {
108 | for (mut dir, damping) in damping_q.iter_mut() {
109 | let damping = dir.0 * (1. / damping.0) * time.scaled_delta_seconds();
110 | dir.0 -= damping;
111 | }
112 | }
113 |
114 | pub(super) fn rotate(mut dir_q: Query<(&mut Transform, &AgentRotation)>, time: ScaledTime) {
115 | for (mut t, rotation) in &mut dir_q {
116 | t.rotate_local_z((rotation.0 * time.scaled_delta_seconds()).to_radians());
117 | }
118 | }
119 |
120 | pub(super) fn age(mut age_q: Query<&mut Age>, time: ScaledTime) {
121 | for mut age in &mut age_q.iter_mut() {
122 | age.0 += time.scaled_delta_seconds();
123 | }
124 | }
125 |
126 | pub(super) fn stop_on_wall_collision(
127 | mut collision_events: EventReader,
128 | stoppable_q: Query<(), With>>,
129 | stop_q: Query<(), With>,
130 | mut dir_q: Query<&mut MovementDirection>,
131 | ) {
132 | for coll in collision_events
133 | .iter()
134 | .filter_map(|ev| check_collision_start_pair(ev, &stoppable_q, &stop_q))
135 | {
136 | if let Ok(mut dir) = dir_q.get_mut(coll.0) && dir.0.length() > 0. {
137 | dir.0 = Vec2::ZERO;
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/agent/mod.rs:
--------------------------------------------------------------------------------
1 | use bevy::{prelude::*, transform::TransformSystem};
2 |
3 | use crate::state::UnpausedGame;
4 |
5 | use self::agent::{
6 | age, apply_damping, ease_direction, move_agents, rotate, stop_on_wall_collision,
7 | MovementDirection, MovementDirectionEasing,
8 | };
9 |
10 | pub mod agent;
11 |
12 | pub fn agent_plugin(app: &mut App) {
13 | app.add_systems(
14 | (rotate, apply_damping, ease_direction, move_agents)
15 | .chain()
16 | .in_set(UnpausedGame)
17 | .in_base_set(CoreSet::PostUpdate)
18 | .before(TransformSystem::TransformPropagate),
19 | )
20 | .add_system(stop_on_wall_collision.in_set(UnpausedGame))
21 | .add_system(age.in_base_set(CoreSet::PreUpdate));
22 |
23 | app.register_type::()
24 | .register_type::();
25 | }
26 |
--------------------------------------------------------------------------------
/src/animation/mod.rs:
--------------------------------------------------------------------------------
1 | use bevy::prelude::*;
2 | use bevy_tweening::{component_animator_system, TweeningPlugin};
3 |
4 | mod tween;
5 | mod tween_events;
6 | pub mod tween_lenses;
7 | mod tween_macros;
8 |
9 | pub use tween::*;
10 | pub use tween_events::TweenDoneAction;
11 |
12 | pub fn animation_plugin(app: &mut App) {
13 | app.add_plugin(TweeningPlugin::::new())
14 | .add_system(tween_events::on_tween_completed)
15 | .add_system(component_animator_system::)
16 | .add_system(component_animator_system::);
17 | }
18 |
--------------------------------------------------------------------------------
/src/animation/tween.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 |
3 | use bevy::prelude::*;
4 | use bevy_tweening::*;
5 | use std::time::Duration;
6 |
7 | use super::tween_events::TweenDoneAction;
8 | use super::tween_lenses::*;
9 | use super::tween_macros::*;
10 |
11 | pub fn delay_tween(
12 | tween: Tween,
13 | delay_ms: u64,
14 | ) -> Sequence {
15 | if delay_ms > 0 {
16 | Delay::new(Duration::from_millis(delay_ms)).then(tween)
17 | } else {
18 | Sequence::new([tween])
19 | }
20 | }
21 |
22 | relative_tween_fns!(
23 | translation,
24 | Transform,
25 | TweenDoneAction,
26 | TransformRelativePositionLens,
27 | Vec3,
28 | Vec3
29 | );
30 |
31 | relative_tween_fns!(
32 | scale,
33 | Transform,
34 | TweenDoneAction,
35 | TransformRelativeScaleLens,
36 | Vec3,
37 | Vec3
38 | );
39 |
40 | relative_tween_fns!(
41 | text_color,
42 | Text,
43 | TweenDoneAction,
44 | TextRelativeColorLens,
45 | Vec,
46 | Color
47 | );
48 |
49 | relative_tween_fns!(
50 | spritesheet_color,
51 | TextureAtlasSprite,
52 | TweenDoneAction,
53 | SpriteSheetRelativeColorLens,
54 | Color,
55 | Color
56 | );
57 |
58 | relative_tween_fns!(
59 | sprite_color,
60 | Sprite,
61 | TweenDoneAction,
62 | SpriteRelativeColorLens,
63 | Color,
64 | Color
65 | );
66 |
67 | relative_tween_fns!(
68 | ui_bg_color,
69 | BackgroundColor,
70 | TweenDoneAction,
71 | UiBackgroundColorLens,
72 | Color,
73 | Color
74 | );
75 |
--------------------------------------------------------------------------------
/src/animation/tween_events.rs:
--------------------------------------------------------------------------------
1 | use bevy::{prelude::*, render::view::NoFrustumCulling};
2 | use bevy_tweening::TweenCompleted;
3 |
4 | use crate::state::{AppState, PersistReset};
5 |
6 | #[derive(Clone)]
7 | pub enum TweenDoneAction {
8 | #[allow(dead_code)]
9 | None,
10 | DespawnSelfRecursive,
11 | DespawnRecursive(Entity),
12 | ResetAndNextState(AppState),
13 | }
14 |
15 | pub fn on_tween_completed(
16 | mut cmd: Commands,
17 | mut ev_reader: EventReader>,
18 | entity_q: Query,
19 | reset_q: Query<
20 | Entity,
21 | (
22 | Without,
23 | Without,
24 | Without,
25 | // this is just to not reset rapier debug as there's no actual usable marker
26 | Without,
27 | ),
28 | >,
29 | mut next_state: ResMut>,
30 | ) {
31 | for ev in ev_reader.iter() {
32 | match &ev.user_data {
33 | TweenDoneAction::None => {}
34 | TweenDoneAction::DespawnSelfRecursive => {
35 | if entity_q.contains(ev.entity) {
36 | cmd.entity(ev.entity).despawn_recursive();
37 | }
38 | }
39 | TweenDoneAction::DespawnRecursive(e) => {
40 | if entity_q.contains(*e) {
41 | cmd.entity(*e).despawn_recursive();
42 | }
43 | }
44 | TweenDoneAction::ResetAndNextState(next) => {
45 | for e in reset_q.iter() {
46 | cmd.entity(e).despawn_recursive();
47 | }
48 |
49 | next_state.set(next.clone());
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/animation/tween_lenses.rs:
--------------------------------------------------------------------------------
1 | use bevy::prelude::*;
2 | use bevy_tweening::*;
3 |
4 | use super::tween_macros::*;
5 |
6 | relative_lens!(Transform, Vec3, TransformRelativeScaleLens, scale);
7 | relative_lens!(Transform, Vec3, TransformRelativePositionLens, translation);
8 |
9 | #[derive(Default)]
10 | pub struct TransformRelativeByPositionLens {
11 | start: Vec3,
12 | end: Vec3,
13 | move_by: Vec3,
14 | }
15 |
16 | impl TransformRelativeByPositionLens {
17 | #[allow(dead_code)]
18 | pub fn new(move_by: Vec3) -> Self {
19 | Self {
20 | move_by,
21 | start: Vec3::ZERO,
22 | end: Vec3::ZERO,
23 | }
24 | }
25 | }
26 |
27 | impl Lens for TransformRelativeByPositionLens {
28 | fn lerp(&mut self, target: &mut Transform, ratio: f32) {
29 | let value = self.start + (self.end - self.start) * ratio;
30 | target.translation = value;
31 | }
32 |
33 | fn update_on_tween_start(&mut self, target: &Transform) {
34 | self.start = target.translation;
35 | self.end = target.translation + self.move_by;
36 | }
37 | }
38 |
39 | #[derive(Default)]
40 | pub struct TextRelativeColorLens {
41 | pub start: Option>,
42 | pub end: Color,
43 | }
44 |
45 | impl TextRelativeColorLens {
46 | #[allow(dead_code)]
47 | pub fn relative(end: Color) -> Self {
48 | Self { start: None, end }
49 | }
50 | }
51 |
52 | impl Lens for TextRelativeColorLens {
53 | fn lerp(&mut self, target: &mut Text, ratio: f32) {
54 | for i in 0..target.sections.len() {
55 | if let Some(col) = self.start.as_ref().unwrap().get(i) {
56 | target.sections[i].style.color = lerp_color(*col, self.end, ratio);
57 | }
58 | }
59 | }
60 |
61 | fn update_on_tween_start(&mut self, target: &Text) {
62 | self.start
63 | .get_or_insert_with(|| target.sections.iter().map(|s| s.style.color).collect());
64 | }
65 | }
66 |
67 | color_lens!(Sprite, SpriteRelativeColorLens, color);
68 | color_lens!(TextureAtlasSprite, SpriteSheetRelativeColorLens, color);
69 | color_lens!(BackgroundColor, UiBackgroundColorLens, 0);
70 |
71 | fn lerp_color(from: Color, to: Color, ratio: f32) -> Color {
72 | let start: Vec4 = from.into();
73 | let end: Vec4 = to.into();
74 | start.lerp(end, ratio).into()
75 | }
76 |
--------------------------------------------------------------------------------
/src/animation/tween_macros.rs:
--------------------------------------------------------------------------------
1 | macro_rules! relative_lens_struct {
2 | ($lens:ident, $value:ty) => {
3 | #[derive(Default)]
4 | pub struct $lens {
5 | pub(super) start: Option<$value>,
6 | pub(super) end: $value,
7 | }
8 |
9 | impl $lens {
10 | #[allow(dead_code)]
11 | pub fn relative(end: $value) -> Self {
12 | Self { start: None, end }
13 | }
14 |
15 | #[allow(dead_code)]
16 | pub fn new(start: $value, end: $value) -> Self {
17 | Self {
18 | start: Some(start),
19 | end,
20 | }
21 | }
22 | }
23 | };
24 | }
25 |
26 | pub(super) use relative_lens_struct;
27 |
28 | macro_rules! color_lens {
29 | ($component:ty, $lens:ident, $field:tt) => {
30 | relative_lens_struct!($lens, Color);
31 |
32 | impl Lens<$component> for $lens {
33 | fn lerp(&mut self, target: &mut $component, ratio: f32) {
34 | target.$field = lerp_color(
35 | self.start
36 | .expect("Lerping has started so initial values should have been set"),
37 | self.end,
38 | ratio,
39 | );
40 | }
41 |
42 | fn update_on_tween_start(&mut self, target: &$component) {
43 | self.start.get_or_insert_with(|| target.$field);
44 | }
45 | }
46 | };
47 | }
48 |
49 | pub(super) use color_lens;
50 |
51 | macro_rules! relative_lens {
52 | ($component:ty, $value:ty, $lens:ident, $field:tt) => {
53 | relative_lens_struct!($lens, $value);
54 |
55 | impl Lens<$component> for $lens {
56 | fn lerp(&mut self, target: &mut $component, ratio: f32) {
57 | let start = self.start.unwrap();
58 | let value = start + (self.end - start) * ratio;
59 | target.scale = value;
60 | }
61 |
62 | fn update_on_tween_start(&mut self, target: &$component) {
63 | self.start.get_or_insert_with(|| target.$field);
64 | }
65 | }
66 | };
67 | }
68 |
69 | pub(super) use relative_lens;
70 |
71 | macro_rules! relative_tween_fns {
72 | ($name:ident, $component:ty, $done_action:ty, $lens:ty, $value_start:ty, $value_end:ty) => {
73 | paste::paste! {
74 | pub fn [](
75 | start: $value_start,
76 | end: $value_end,
77 | duration_ms: u64,
78 | on_completed: $done_action,
79 | ) -> Tween<$component, $done_action> {
80 | [](
81 | Some(start),
82 | end,
83 | duration_ms,
84 | EaseFunction::QuadraticInOut,
85 | on_completed,
86 | )
87 | }
88 |
89 | pub fn [](
90 | end: $value_end,
91 | duration_ms: u64,
92 | on_completed: $done_action,
93 | ) -> Tween<$component, $done_action> {
94 | [](
95 | None,
96 | end,
97 | duration_ms,
98 | EaseFunction::QuadraticInOut,
99 | on_completed,
100 | )
101 | }
102 |
103 | pub fn [](
104 | start: $value_start,
105 | end: $value_end,
106 | duration_ms: u64,
107 | on_completed: $done_action,
108 | ) -> Animator<$component, $done_action> {
109 | Animator::new([](
110 | start,
111 | end,
112 | duration_ms,
113 | on_completed,
114 | ))
115 | }
116 |
117 | pub fn [](
118 | end: $value_end,
119 | duration_ms: u64,
120 | on_completed: $done_action,
121 | ) -> Animator<$component, $done_action> {
122 | Animator::new([](
123 | end,
124 | duration_ms,
125 | on_completed,
126 | ))
127 | }
128 |
129 | pub fn [](
130 | start: Option<$value_start>,
131 | end: $value_end,
132 | duration_ms: u64,
133 | ease: EaseFunction,
134 | on_completed: $done_action,
135 | ) -> Animator<$component, $done_action> {
136 | Animator::new([](
137 | start,
138 | end,
139 | duration_ms,
140 | ease,
141 | on_completed,
142 | ))
143 |
144 | }
145 |
146 | pub fn [](
147 | start: Option<$value_start>,
148 | end: $value_end,
149 | duration_ms: u64,
150 | ease: EaseFunction,
151 | on_completed: $done_action,
152 | ) -> Tween<$component, $done_action> {
153 | Tween::new(
154 | ease,
155 | Duration::from_millis(duration_ms),
156 | $lens {
157 | start,
158 | end,
159 | },
160 | ).with_completed_event(on_completed)
161 | }
162 | }
163 | };
164 | }
165 |
166 | pub(super) use relative_tween_fns;
167 |
--------------------------------------------------------------------------------
/src/assets/audio.rs:
--------------------------------------------------------------------------------
1 | use bevy::prelude::*;
2 | use bevy_asset_loader::prelude::*;
3 | use bevy_kira_audio::AudioSource;
4 |
5 | use super::add_dynamic_assets;
6 |
7 | #[derive(AssetCollection, Resource)]
8 | pub struct SfxAssets {
9 | // todo: fix echo
10 | #[asset(key = "echo", collection(typed))]
11 | pub echo: Vec>,
12 |
13 | #[asset(key = "player_death", collection(typed))]
14 | pub player_death: Vec>,
15 |
16 | #[asset(key = "enemy_alert", collection(typed))]
17 | pub enemy_alert: Vec>,
18 |
19 | #[asset(key = "enemy_death", collection(typed))]
20 | pub enemy_death: Vec>,
21 |
22 | #[asset(path = "audio/sfx/level_cleared.ogg")]
23 | pub level_cleared: Handle,
24 |
25 | #[asset(path = "audio/sfx/drone.ogg")]
26 | pub drone: Handle,
27 | }
28 |
29 | pub(super) fn setup_sfx_assets(mut dynamic_assets: ResMut) {
30 | for key in ["echo", "player_death", "enemy_alert", "enemy_death"] {
31 | add_dynamic_assets(
32 | &mut dynamic_assets,
33 | key,
34 | &format!("audio/sfx/{}", key),
35 | "ogg",
36 | 1..=2,
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/assets/fonts.rs:
--------------------------------------------------------------------------------
1 | use bevy::prelude::*;
2 | use bevy_asset_loader::prelude::*;
3 |
4 | #[derive(AssetCollection, Resource)]
5 | pub struct FontAssets {
6 | #[asset(path = "fonts/Spaceport_2006.otf")]
7 | pub menu: Handle,
8 | }
9 |
--------------------------------------------------------------------------------
/src/assets/mod.rs:
--------------------------------------------------------------------------------
1 | use std::ops::RangeInclusive;
2 |
3 | use bevy::prelude::*;
4 | use bevy_asset_loader::prelude::*;
5 | use iyes_progress::{ProgressCounter, ProgressPlugin};
6 |
7 | use crate::AppState;
8 |
9 | pub mod audio;
10 | pub mod fonts;
11 | pub mod textures;
12 | mod window_icon;
13 |
14 | use self::window_icon::set_window_icon;
15 |
16 | pub fn assets_plugin(app: &mut App) {
17 | app.add_startup_system(set_window_icon)
18 | .add_loading_state(LoadingState::new(AppState::Loading))
19 | .add_collection_to_loading_state::<_, fonts::FontAssets>(AppState::Loading)
20 | .add_collection_to_loading_state::<_, audio::SfxAssets>(AppState::Loading)
21 | .add_collection_to_loading_state::<_, textures::TextureAssets>(AppState::Loading)
22 | .add_startup_system(audio::setup_sfx_assets)
23 | .add_plugin(ProgressPlugin::new(AppState::Loading))
24 | .add_system(fade_to_splash.in_set(OnUpdate(AppState::Loading)));
25 | }
26 |
27 | fn add_dynamic_assets(
28 | dynamic_assets: &mut DynamicAssets,
29 | key: &str,
30 | file_prefix: &str,
31 | file_ext: &str,
32 | range: RangeInclusive,
33 | ) {
34 | dynamic_assets.register_asset(
35 | key,
36 | Box::new(StandardDynamicAsset::Files {
37 | paths: range
38 | .map(|i| format!("{file_prefix}{i}.{file_ext}"))
39 | .collect(),
40 | }),
41 | );
42 | }
43 |
44 | fn fade_to_splash(progress: Res, mut fade_reset: ResMut>) {
45 | let progress = progress.progress();
46 | if progress.total > 0 && progress.done == progress.total {
47 | fade_reset.set(if cfg!(debug_assertions) {
48 | AppState::Game
49 | } else {
50 | AppState::Splash
51 | });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/assets/textures.rs:
--------------------------------------------------------------------------------
1 | use bevy::prelude::*;
2 | use bevy_asset_loader::prelude::*;
3 |
4 | #[derive(AssetCollection, Resource)]
5 | pub struct TextureAssets {
6 | #[asset(path = "textures/bevy.png")]
7 | pub bevy: Handle,
8 |
9 | #[asset(path = "textures/player.png")]
10 | pub player: Handle,
11 |
12 | #[asset(path = "textures/echo_ping.png")]
13 | pub echo_ping: Handle,
14 |
15 | #[asset(path = "textures/wave.png")]
16 | pub wave: Handle,
17 |
18 | #[asset(path = "textures/spiky.png")]
19 | pub spiky: Handle,
20 |
21 | #[asset(path = "textures/charge.png")]
22 | pub charge: Handle,
23 |
24 | #[asset(path = "textures/portal.png")]
25 | pub portal: Handle,
26 |
27 | #[asset(path = "textures/button.png")]
28 | pub button: Handle,
29 |
30 | #[asset(path = "textures/controls_arrows.png")]
31 | pub controls_arrows: Handle,
32 | }
33 |
--------------------------------------------------------------------------------
/src/assets/window_icon.rs:
--------------------------------------------------------------------------------
1 | use bevy::prelude::*;
2 | use bevy::window::PrimaryWindow;
3 | use bevy::winit::WinitWindows;
4 | use std::io::Cursor;
5 | use winit::window::Icon;
6 |
7 | // Sets the icon on windows and X11
8 | pub(super) fn set_window_icon(
9 | windows: NonSend,
10 | primary_window: Query>,
11 | ) {
12 | let primary_entity = primary_window.single();
13 | let primary = windows.get_window(primary_entity).unwrap();
14 | let icon_buf = Cursor::new(include_bytes!(
15 | "../../build/macos/AppIcon.iconset/icon_256x256.png"
16 | ));
17 |
18 | if let Ok(image) = image::load(icon_buf, image::ImageFormat::Png) {
19 | let image = image.into_rgba8();
20 | let (width, height) = image.dimensions();
21 | let rgba = image.into_raw();
22 | let icon = Icon::from_rgba(rgba, width, height).unwrap();
23 | primary.set_window_icon(Some(icon));
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/audio/mod.rs:
--------------------------------------------------------------------------------
1 | use bevy::prelude::*;
2 | use bevy_kira_audio::AudioPlugin;
3 | use seldom_fn_plugin::FnPluginExt;
4 |
5 | pub mod sfx;
6 |
7 | pub fn audio_plugin(app: &mut App) {
8 | app.add_plugin(AudioPlugin).fn_plugin(sfx::sfx_plugin);
9 | }
10 |
--------------------------------------------------------------------------------
/src/audio/sfx.rs:
--------------------------------------------------------------------------------
1 | use std::time::Duration;
2 |
3 | use bevy::input::mouse::MouseButtonInput;
4 | use bevy::{ecs::system::Resource, prelude::*};
5 | use bevy_kira_audio::prelude::*;
6 | use rand::seq::SliceRandom;
7 | use rand::*;
8 |
9 | use crate::assets::audio::SfxAssets;
10 | use crate::echolocation::echolocation::EcholocationEv;
11 | use crate::enemy::EnemyEv;
12 | use crate::io::save::VolumeSettings;
13 | use crate::player::player::PlayerEv;
14 | use crate::state::AppState;
15 |
16 | // todo: use sfx + master volume
17 | pub(super) fn sfx_plugin(app: &mut bevy::prelude::App) {
18 | app.add_audio_channel::()
19 | .add_systems(
20 | (
21 | play_sfx_on_evt::,
22 | play_sfx_on_evt::,
23 | play_sfx_on_evt::,
24 | )
25 | .in_base_set(CoreSet::PostUpdate)
26 | .distributive_run_if(resource_exists::()),
27 | )
28 | .add_system(set_sfx_volume.run_if(resource_changed::()))
29 | .add_system(play_drone.in_schedule(OnExit(AppState::Loading)));
30 | }
31 |
32 | #[derive(Resource)]
33 | struct SfxChannel;
34 |
35 | pub trait SfxEv {
36 | fn get_volume(&self) -> f64;
37 | fn get_sfx_handles(&self, sfx: &SfxAssets) -> Vec>;
38 | fn skip(&self) -> bool {
39 | false
40 | }
41 | }
42 |
43 | impl SfxEv for EcholocationEv {
44 | fn get_volume(&self) -> f64 {
45 | 1.0
46 | }
47 |
48 | fn get_sfx_handles(&self, sfx: &SfxAssets) -> Vec> {
49 | sfx.echo.clone()
50 | }
51 | }
52 |
53 | impl SfxEv for PlayerEv {
54 | fn get_volume(&self) -> f64 {
55 | match self {
56 | PlayerEv::ClearedLevel => 0.5,
57 | PlayerEv::Died => 0.9,
58 | }
59 | }
60 |
61 | fn get_sfx_handles(&self, sfx: &SfxAssets) -> Vec> {
62 | match self {
63 | PlayerEv::ClearedLevel => vec![sfx.level_cleared.clone()],
64 | PlayerEv::Died => sfx.player_death.clone(),
65 | }
66 | }
67 | }
68 |
69 | impl SfxEv for EnemyEv {
70 | fn get_volume(&self) -> f64 {
71 | match self {
72 | EnemyEv::Killed => 0.85,
73 | EnemyEv::Alarmed => 0.3,
74 | }
75 | }
76 |
77 | fn get_sfx_handles(&self, sfx: &SfxAssets) -> Vec> {
78 | match self {
79 | EnemyEv::Killed => sfx.enemy_death.clone(),
80 | EnemyEv::Alarmed => sfx.enemy_alert.clone(),
81 | }
82 | }
83 | }
84 |
85 | fn play_sfx_on_evt(
86 | mut ev_r: EventReader,
87 | audio: Res>,
88 | sfx: Res,
89 | volume: Res,
90 | ) {
91 | let mut rand = thread_rng();
92 |
93 | for ev in ev_r.iter() {
94 | if ev.skip() {
95 | continue;
96 | }
97 |
98 | if let Some(sfx_handle) = ev.get_sfx_handles(&sfx).choose(&mut rand) {
99 | audio
100 | .play(sfx_handle.clone())
101 | .with_volume(ev.get_volume() * volume.get_sfx_volume());
102 | }
103 | }
104 | }
105 |
106 | fn set_sfx_volume(audio: Res>, volume: Res) {
107 | audio.set_volume(volume.get_sfx_volume());
108 | }
109 |
110 | fn play_drone(audio: Res