├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── resources ├── kenny_fontpackage │ ├── Fonts │ │ ├── Kenney Blocks.ttf │ │ ├── Kenney Future Narrow.ttf │ │ ├── Kenney Future.ttf │ │ ├── Kenney High Square.ttf │ │ ├── Kenney High.ttf │ │ ├── Kenney Mini Square.ttf │ │ ├── Kenney Mini.ttf │ │ ├── Kenney Pixel Square.ttf │ │ ├── Kenney Pixel.ttf │ │ ├── Kenney Rocket Square.ttf │ │ └── Kenney Rocket.ttf │ └── License.txt ├── maps │ ├── map_1skeleton.txt │ ├── map_2skeleton.txt │ ├── map_2skeleton_backtrack.txt │ ├── map_2skeleton_intro.txt │ ├── map_3skeleton.txt │ ├── map_3skeleton_3holes.txt │ ├── map_3skeleton_3holes_harder.txt │ ├── map_easy1.txt │ ├── map_easy2.txt │ ├── map_easy3.txt │ ├── map_easy_accidental_fall.txt │ ├── map_fall_trap.txt │ ├── map_first.txt │ ├── map_follow.txt │ ├── map_follow_2.txt │ ├── map_force_stand.txt │ ├── map_gravity.txt │ ├── map_hard1.txt │ ├── map_hard2.txt │ ├── map_hard3.txt │ ├── map_maze1.txt │ ├── map_medium2.txt │ ├── map_middleclash.txt │ ├── map_middleclash_2.txt │ ├── map_simple_backtrack.txt │ ├── map_skeleton_hole.txt │ ├── map_skeleton_platform.txt │ └── map_teleport.txt ├── sounds │ ├── door_locked.wav │ ├── level_completed.wav │ ├── level_restarted.wav │ ├── player_dig.wav │ ├── player_fall.wav │ ├── player_hit.wav │ ├── player_land.wav │ ├── player_teleport.wav │ ├── player_walk.wav │ └── skeleton_attack.wav └── textures │ ├── blue_door.png │ ├── buried.png │ ├── cloud_1.png │ ├── cloud_2.png │ ├── cloud_3.png │ ├── foilage_1.png │ ├── foilage_2.png │ ├── foilage_3.png │ ├── foilage_4.png │ ├── ground.png │ ├── ground_below.png │ ├── player.png │ ├── player_dead.png │ ├── player_dig.png │ ├── player_fall.png │ ├── player_neutral.png │ ├── red_door.png │ ├── skeleton_attack.png │ ├── skeleton_neutral.png │ ├── sound_off.png │ ├── sound_on.png │ └── unburied.png ├── src ├── constantes.rs ├── entities.rs ├── entities │ ├── ai.rs │ ├── cloud.rs │ ├── foilage.rs │ ├── player.rs │ ├── skeleton.rs │ └── teleporter.rs ├── main.rs ├── map.rs ├── particle_system.rs ├── sound_collection.rs ├── sprite.rs ├── states.rs ├── states │ ├── game_state.rs │ └── main_state.rs ├── transform_compontent.rs └── util.rs └── utils └── wasm ├── audio.js ├── build.sh ├── gl.js ├── index.html └── zip_resources.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Currently generated by build.sh 13 | /static/ 14 | 15 | #vim generated 16 | *.swp 17 | 18 | *.tar -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dig_escape" 3 | version = "0.1.0" 4 | authors = ["TanTanDev "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | gwg = {git = "https://github.com/TanTanDev/good-web-game", package = "good-web-game", branch = "audio"} 9 | nalgebra = { version = "0.20", features = ["mint"] } 10 | rand = "0.7.3" 11 | mint = "0.5" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dig Escape 2 | Dig Escape is a simple puzzle game written in [Rust](https://www.rust-lang.org/) 3 | 4 | The game is playable here: https://tantandev.itch.io/digescape 5 | 6 | ### Background 7 | What started out as a small project with the goal of learning [Rust](https://www.rust-lang.org/) 8 | , ended up being released playable on the [web!](https://tantandev.itch.io/digescape) 9 | 10 | The progress was recorded on my [Youtube Channel](https://www.youtube.com/channel/UChl_NKOs1qqh_x7yJfaDpDw) 11 | 12 | ### Building 13 | before you can run using cargo, 14 | The game assets need to be zipped as a .tar and put into the src/ folder 15 | 16 | To automatically zip the /resources there is a script in utils/wasm/zip_resources.sh you can run using git bash: 17 | ``` 18 | # first cd into the utils/wasm/ folder 19 | ./zip_resources.sh 20 | ``` 21 | Then we can use cargo to run the project on windows 22 | ```bash 23 | cargo run 24 | ``` 25 | ## WebAssembly 26 | There is a script in utils/wasm/build.sh you can run using git bash. 27 | ```bash 28 | ./build.sh 29 | ``` 30 | This script compiles the program with cargo, takes the generated dig_escape.wasm file, and the files in utils/wasm/ and 31 | moves them into a new folder called static/. 32 | To run it in the browser I'm, using [basic-http-server](https://crates.io/crates/basic-http-server). 33 | ```bash 34 | cargo install basic-http-server 35 | ``` 36 | start the server by using the correct path 37 | ```bash 38 | basic-http-server . # starts server based on current directory 39 | basic-http-server static # start server in the folder /static 40 | ``` 41 | 42 | ### External assets with license 43 | in this project I'm using Kenny assets fonts which is using: 44 | License: (Creative Commons Zero, CC0) 45 | 46 | ### Dependencies 47 | Forked Game framework [Good-web-game](https://github.com/TanTanDev/good-web-game) 48 | Note: I'm using the audio branch which is a work in progress 49 | -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Blocks.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Blocks.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Future Narrow.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Future Narrow.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Future.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Future.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney High Square.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney High Square.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney High.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney High.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Mini Square.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Mini Square.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Mini.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Mini.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Pixel Square.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Pixel Square.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Pixel.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Pixel.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Rocket Square.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Rocket Square.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/Fonts/Kenney Rocket.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/kenny_fontpackage/Fonts/Kenney Rocket.ttf -------------------------------------------------------------------------------- /resources/kenny_fontpackage/License.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Font package (free) 4 | 5 | Created/distributed by Kenney (www.kenney.nl) 6 | 7 | ------------------------------ 8 | 9 | License: (Creative Commons Zero, CC0) 10 | http://creativecommons.org/publicdomain/zero/1.0/ 11 | 12 | This content is free to use in personal, educational and commercial projects. 13 | Support us by crediting (Kenney or www.kenney.nl), this is not mandatory. 14 | 15 | ------------------------------ 16 | 17 | Donate: http://support.kenney.nl 18 | Request: http://request.kenney.nl 19 | Patreon: http://patreon.com/kenney/ 20 | 21 | Follow on Twitter for updates: 22 | @KenneyNL -------------------------------------------------------------------------------- /resources/maps/map_1skeleton.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | ---------- 3 | ---------- 4 | -0--4----- 5 | 1111111211 6 | 5555555555 7 | 5555555555 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_2skeleton.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | ---------- 3 | ---------- 4 | ---------- 5 | -4-0------ 6 | 1111112121 7 | 5555555555 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_2skeleton_backtrack.txt: -------------------------------------------------------------------------------- 1 | 3-----1111 2 | 11114----- 3 | ----1----- 4 | ---0------ 5 | ---221---- 6 | ---------3 7 | ------1111 8 | 1111115555 9 | -------------------------------------------------------------------------------- /resources/maps/map_2skeleton_intro.txt: -------------------------------------------------------------------------------- 1 | ---------5 2 | -----4---5 3 | ---1111115 4 | ---------5 5 | -0-------5 6 | 112211---5 7 | 555555---5 8 | 555555---5 -------------------------------------------------------------------------------- /resources/maps/map_3skeleton.txt: -------------------------------------------------------------------------------- 1 | 3-----1111 2 | 11114----- 3 | ---51----- 4 | ---0------ 5 | ---222---- 6 | ---------- 7 | ---------3 8 | 11111-1111 9 | -------------------------------------------------------------------------------- /resources/maps/map_3skeleton_3holes.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | -----3---- 3 | 1111112--- 4 | ---------- 5 | -4-----0-3 6 | 111---2211 7 | 5555555555 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_3skeleton_3holes_harder.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | 3--------- 3 | 111---1111 4 | ----215--- 5 | -4----03-- 6 | 11---221-1 7 | 55555555-5 8 | 55555555-5 -------------------------------------------------------------------------------- /resources/maps/map_easy1.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | -11-----3- 3 | -----0-112 4 | ----12255- 5 | 1----555-- 6 | 511-----4- 7 | 555-31111- 8 | 55511555-- -------------------------------------------------------------------------------- /resources/maps/map_easy2.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | -3-------- 3 | -12-----3- 4 | -55-11-111 5 | -5------55 6 | --0------- 7 | 121---4--1 8 | 55511111-5 -------------------------------------------------------------------------------- /resources/maps/map_easy3.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | 1--------- 3 | 5-3------1 4 | --11-121-5 5 | -40---5--5 6 | 111------5 7 | 555------3 8 | 555121--11 -------------------------------------------------------------------------------- /resources/maps/map_easy_accidental_fall.txt: -------------------------------------------------------------------------------- 1 | --51------ 2 | ---51----- 3 | 1-------03 4 | 511----211 5 | 55-------- 6 | 5---4----- 7 | ---111--3- 8 | --15551211 -------------------------------------------------------------------------------- /resources/maps/map_fall_trap.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | --3-1----- 3 | 111-51111- 4 | 55-------- 5 | 5--0--34-- 6 | --212-11-- 7 | -1555----1 8 | -5555----5 -------------------------------------------------------------------------------- /resources/maps/map_first.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | ---------- 3 | ---------- 4 | ---------- 5 | --0----4-- 6 | 1111111111 7 | 5555555555 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_follow.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | --111----- 3 | ---------1 4 | 1-3--0---5 5 | 5111111215 6 | 5-3----4-5 7 | 5111111115 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_follow_2.txt: -------------------------------------------------------------------------------- 1 | 1111111--1 2 | 5--------5 3 | 5-11111215 4 | 53---0---5 5 | 51111112-5 6 | 53----4--5 7 | 5111111--5 8 | 5555555--5 -------------------------------------------------------------------------------- /resources/maps/map_force_stand.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | 11-----3-- 3 | 551--111-- 4 | ---------1 5 | ---------- 6 | -4---1111- 7 | 1111-0-35- 8 | 555512115- -------------------------------------------------------------------------------- /resources/maps/map_gravity.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | ---------- 3 | ------4--- 4 | ----11111- 5 | ---0------ 6 | 1111------ 7 | 5555------ 8 | 5555----11 -------------------------------------------------------------------------------- /resources/maps/map_hard1.txt: -------------------------------------------------------------------------------- 1 | -3-------- 2 | 11-----3-- 3 | 552--111-- 4 | ---------1 5 | ---------5 6 | -4-0-11125 7 | 1111-----5 8 | 555512---5 -------------------------------------------------------------------------------- /resources/maps/map_hard2.txt: -------------------------------------------------------------------------------- 1 | --------- 2 | ----3---21 3 | --1112115- 4 | ---55555- 5 | 0-3-5----- 6 | 121-----4- 7 | 55511--11- 8 | 55555115-- -------------------------------------------------------------------------------- /resources/maps/map_hard3.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | -1111---11 3 | -555-3---5 4 | -----111-- 5 | 1--------- 6 | 5--21---21 7 | 5-154-0-35 8 | 5-55111215 -------------------------------------------------------------------------------- /resources/maps/map_maze1.txt: -------------------------------------------------------------------------------- 1 | ---1------ 2 | ---5-11111 3 | ---5---555 4 | ---511--55 5 | -----3--55 6 | -4---11255 7 | 1111-0-3-- 8 | 55551111-1 -------------------------------------------------------------------------------- /resources/maps/map_medium2.txt: -------------------------------------------------------------------------------- 1 | --------- 2 | --3-0211-- 3 | -1121555-- 4 | -55555---4 5 | --------11 6 | 11------55 7 | 3--1111-55 8 | 1125555-55 -------------------------------------------------------------------------------- /resources/maps/map_middleclash.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | ---------- 3 | ---------- 4 | 3-04-----3 5 | 1211111121 6 | 5555555555 7 | 5555555555 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_middleclash_2.txt: -------------------------------------------------------------------------------- 1 | 5555555555 2 | 5555555555 3 | ---------- 4 | 3-4-0----3 5 | 1211111221 6 | 5555555555 7 | 5555555555 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_simple_backtrack.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | ---34----- 3 | --11111--- 4 | ---------- 5 | ---0------ 6 | 111111-2-- 7 | 555555--3- 8 | 5555551111 -------------------------------------------------------------------------------- /resources/maps/map_skeleton_hole.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | -----3---- 3 | 1111112--- 4 | ---------- 5 | -4-----0-3 6 | 11111-1211 7 | 5555555555 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_skeleton_platform.txt: -------------------------------------------------------------------------------- 1 | ---------- 2 | -----3---- 3 | 1111112--- 4 | ---------- 5 | -4-111-0-3 6 | 1115551211 7 | 5555555555 8 | 5555555555 -------------------------------------------------------------------------------- /resources/maps/map_teleport.txt: -------------------------------------------------------------------------------- 1 | -----1---- 2 | -43--1---- 3 | 111--1--3- 4 | -----51111 5 | ---0------ 6 | 111111---- 7 | 555555---- 8 | 555555---- -------------------------------------------------------------------------------- /resources/sounds/door_locked.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/door_locked.wav -------------------------------------------------------------------------------- /resources/sounds/level_completed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/level_completed.wav -------------------------------------------------------------------------------- /resources/sounds/level_restarted.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/level_restarted.wav -------------------------------------------------------------------------------- /resources/sounds/player_dig.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/player_dig.wav -------------------------------------------------------------------------------- /resources/sounds/player_fall.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/player_fall.wav -------------------------------------------------------------------------------- /resources/sounds/player_hit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/player_hit.wav -------------------------------------------------------------------------------- /resources/sounds/player_land.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/player_land.wav -------------------------------------------------------------------------------- /resources/sounds/player_teleport.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/player_teleport.wav -------------------------------------------------------------------------------- /resources/sounds/player_walk.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/player_walk.wav -------------------------------------------------------------------------------- /resources/sounds/skeleton_attack.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/sounds/skeleton_attack.wav -------------------------------------------------------------------------------- /resources/textures/blue_door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/blue_door.png -------------------------------------------------------------------------------- /resources/textures/buried.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/buried.png -------------------------------------------------------------------------------- /resources/textures/cloud_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/cloud_1.png -------------------------------------------------------------------------------- /resources/textures/cloud_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/cloud_2.png -------------------------------------------------------------------------------- /resources/textures/cloud_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/cloud_3.png -------------------------------------------------------------------------------- /resources/textures/foilage_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/foilage_1.png -------------------------------------------------------------------------------- /resources/textures/foilage_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/foilage_2.png -------------------------------------------------------------------------------- /resources/textures/foilage_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/foilage_3.png -------------------------------------------------------------------------------- /resources/textures/foilage_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/foilage_4.png -------------------------------------------------------------------------------- /resources/textures/ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/ground.png -------------------------------------------------------------------------------- /resources/textures/ground_below.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/ground_below.png -------------------------------------------------------------------------------- /resources/textures/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/player.png -------------------------------------------------------------------------------- /resources/textures/player_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/player_dead.png -------------------------------------------------------------------------------- /resources/textures/player_dig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/player_dig.png -------------------------------------------------------------------------------- /resources/textures/player_fall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/player_fall.png -------------------------------------------------------------------------------- /resources/textures/player_neutral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/player_neutral.png -------------------------------------------------------------------------------- /resources/textures/red_door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/red_door.png -------------------------------------------------------------------------------- /resources/textures/skeleton_attack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/skeleton_attack.png -------------------------------------------------------------------------------- /resources/textures/skeleton_neutral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/skeleton_neutral.png -------------------------------------------------------------------------------- /resources/textures/sound_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/sound_off.png -------------------------------------------------------------------------------- /resources/textures/sound_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/sound_on.png -------------------------------------------------------------------------------- /resources/textures/unburied.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/DigEscape/3ebbf665263c8103c7ea43d779f567c972ddbe12/resources/textures/unburied.png -------------------------------------------------------------------------------- /src/constantes.rs: -------------------------------------------------------------------------------- 1 | use ggez::graphics::Color; 2 | use gwg as ggez; 3 | 4 | pub const GAME_BOUNDS_Y: i32 = 7; 5 | pub const GAME_BOUNDS_X: i32 = 9; 6 | pub const GAME_BOUNDS_PADDING: f32 = 5.0; // Warp clouds 7 | 8 | pub const MAX_CLOUDS: i32 = 8; 9 | pub const MIN_CLOUDS: i32 = 2; 10 | pub const CLOUD_MIN_SPEED: f32 = 0.1; 11 | pub const CLOUD_MAX_SPEED: f32 = 0.6; 12 | pub const CLOUD_MAX_SCALE: f32 = 2.0; 13 | 14 | pub const FOILAGE_BUSH_CHANCE: f32 = 1.0 / 4.0; // 25% chance to spawn bush, otherwise straw 15 | pub const FOILAGE_SPAWN_CHANCE: f32 = 0.6; 16 | pub const TIME_FOILAGE_SPEED: f32 = 3.0; 17 | pub const SIZE_FOILAGE_DELTA: f32 = 0.2; 18 | pub const ROTATION_FOILAGE_MAX: f32 = 1.0; 19 | 20 | pub const CLEAR_COLOR: Color = Color::new(0.0, 0.0, 0.0, 1.0); 21 | pub const BACKGROUND_GAME: Color = Color::new(56.0 / 255.0, 82.0 / 255.0, 119.0 / 255.0, 1.0); 22 | pub const COLOR_BLINK: Color = Color::new(2.0, 2.0, 2.0, 1.0); 23 | pub const COLOR_BLOOD: Color = Color::new(171.0 / 255.0, 34.0 / 255.0, 44.0 / 255.0, 1.0); 24 | 25 | pub const TIME_BLINK: f32 = 0.4; 26 | pub const TIME_AUTO_STEP: f32 = 0.2; 27 | pub const TIME_VISUAL_LERP: f32 = 1.0 / 0.2 * 2.0; 28 | 29 | pub const GAME_SCALE: f32 = 5.0; 30 | 31 | pub const TOUCH_MIN_DELTA: f32 = 10.0; 32 | pub const TEXT_PADDING_SIZE: f32 = 0.3; // fits all text inside screen with this padding in procentage 33 | pub const PI: f32 = std::f32::consts::PI; 34 | -------------------------------------------------------------------------------- /src/entities.rs: -------------------------------------------------------------------------------- 1 | pub mod ai; 2 | pub mod cloud; 3 | pub mod foilage; 4 | pub mod player; 5 | pub mod skeleton; 6 | pub mod teleporter; 7 | -------------------------------------------------------------------------------- /src/entities/ai.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq)] 2 | pub enum AiState { 3 | Walk, 4 | Attack, 5 | } 6 | 7 | pub struct AiComponent { 8 | pub state: AiState, 9 | pub turn_taken: bool, 10 | } 11 | 12 | impl Default for AiComponent { 13 | fn default() -> Self { 14 | AiComponent { 15 | state: AiState::Attack, 16 | turn_taken: false, 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/entities/cloud.rs: -------------------------------------------------------------------------------- 1 | use crate::constantes; 2 | use crate::sprite; 3 | use crate::states::game_state; 4 | use game_state::GameState; 5 | use nalgebra as na; 6 | use sprite::{SpriteCollection, SpriteComponent}; 7 | 8 | use ggez::graphics; 9 | use ggez::{rand, Context, GameResult}; 10 | use graphics::DrawParam; 11 | use gwg as ggez; 12 | 13 | pub struct Cloud { 14 | pub sprite: SpriteComponent, 15 | pub position: na::Point2, 16 | pub speed: f32, 17 | } 18 | 19 | impl Cloud { 20 | pub fn new() -> Self { 21 | let speed = rand::gen_range(constantes::CLOUD_MIN_SPEED, constantes::CLOUD_MAX_SPEED); 22 | let scale_x = rand::gen_range(1.0, constantes::CLOUD_MAX_SCALE); 23 | let scale_y = rand::gen_range(1.0, constantes::CLOUD_MAX_SCALE); 24 | let scale = na::Vector2::new(scale_x, scale_y); 25 | let texture_index = rand::gen_range(18, 20 + 1); 26 | let position_x = rand::gen_range( 27 | -constantes::GAME_BOUNDS_PADDING, 28 | constantes::GAME_BOUNDS_X as f32 + constantes::GAME_BOUNDS_PADDING, 29 | ); 30 | let position_y = rand::gen_range(0.0, constantes::GAME_BOUNDS_Y as f32); 31 | let position = na::Point2::new(position_x, position_y); 32 | let sprite = SpriteComponent { 33 | texture_index, 34 | scale, 35 | ..Default::default() 36 | }; 37 | Cloud { 38 | sprite, 39 | position, 40 | speed, 41 | } 42 | } 43 | } 44 | 45 | pub fn render( 46 | game_state: &mut GameState, 47 | sprite_collection: &SpriteCollection, 48 | ctx: &mut Context, 49 | screen_size: &na::Point2, 50 | ) -> GameResult { 51 | let mut params = DrawParam::default().offset(mint::Point2 { x: 0.0, y: 0.0 }); 52 | 53 | for cloud in game_state.clouds.iter() { 54 | let scale = (cloud.sprite.scale * screen_size.x) / 16.0; 55 | params = params.scale(scale); 56 | params = params.dest(cloud.position * screen_size.x); 57 | let image = sprite_collection 58 | .images 59 | .get(cloud.sprite.texture_index) 60 | .expect("No cloud image..."); 61 | graphics::draw(ctx, image, params)?; 62 | } 63 | Ok(()) 64 | } 65 | 66 | pub fn spawn(game_state: &mut GameState) { 67 | let amount = rand::gen_range(constantes::MIN_CLOUDS, constantes::MAX_CLOUDS + 1); 68 | for _i in 0..amount { 69 | game_state.clouds.push(Cloud::new()); 70 | } 71 | } 72 | 73 | pub fn update(game_state: &mut GameState, ctx: &mut Context) { 74 | let delta = ggez::timer::delta(ctx).as_secs_f32(); 75 | for cloud in game_state.clouds.iter_mut() { 76 | cloud.position.x += delta * cloud.speed; 77 | if cloud.position.x > constantes::GAME_BOUNDS_X as f32 + constantes::GAME_BOUNDS_PADDING { 78 | cloud.position.x = -constantes::GAME_BOUNDS_PADDING; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/entities/foilage.rs: -------------------------------------------------------------------------------- 1 | use crate::constantes; 2 | use crate::sprite; 3 | use crate::states::game_state::GameState; 4 | use crate::transform_compontent::TransformComponent; 5 | use nalgebra as na; 6 | use sprite::{SpriteCollection, SpriteComponent}; 7 | 8 | use ggez::graphics; 9 | use ggez::{rand, Context, GameResult}; 10 | use graphics::DrawParam; 11 | use gwg as ggez; 12 | #[derive(Default)] 13 | pub struct Grass { 14 | pub transform: TransformComponent, 15 | pub sprite: SpriteComponent, 16 | } 17 | pub enum FoilageType { 18 | Straw, // Rotates 19 | Bush, // Stretches 20 | } 21 | 22 | pub struct Foilage { 23 | pub position: na::Point2, 24 | pub pos_i32: na::Point2, // belongs to this grasss, position 25 | pub sprite: SpriteComponent, 26 | pub foilage_type: FoilageType, 27 | pub time_offset: f32, 28 | } 29 | 30 | impl Foilage { 31 | pub fn new(position: na::Point2, pos_i32: na::Point2) -> Self { 32 | let is_bush = rand::gen_range(0.0, 1.0) < constantes::FOILAGE_BUSH_CHANCE; 33 | let foilage_type = if is_bush { 34 | FoilageType::Bush 35 | } else { 36 | FoilageType::Straw 37 | }; 38 | let texture_index = match foilage_type { 39 | FoilageType::Straw => rand::gen_range(14, 16 + 1), 40 | FoilageType::Bush => 17, 41 | }; 42 | 43 | Foilage { 44 | position, 45 | pos_i32, 46 | sprite: SpriteComponent { 47 | texture_index, 48 | scale: na::Vector2::new(1.0, 1.0), 49 | is_flipped: rand::gen_range(0, 2) == 0, 50 | ..Default::default() 51 | }, 52 | foilage_type, 53 | time_offset: rand::gen_range(0.0, 1.0), 54 | } 55 | } 56 | } 57 | pub fn generate(game_state: &mut GameState) { 58 | // foilage time! 59 | // chance to spawn foilage on any grass block 60 | for grass in game_state 61 | .grasses 62 | .iter() 63 | .filter(|g| g.sprite.texture_index == 1) 64 | { 65 | if rand::gen_range(0.0, 1.0) > constantes::FOILAGE_SPAWN_CHANCE { 66 | continue; 67 | } 68 | let foilage_count = rand::gen_range(1, 2 + 1); 69 | let mut position = na::Point2::new( 70 | grass.transform.position.x as f32, 71 | grass.transform.position.y as f32, 72 | ); 73 | 74 | for _i in 0..foilage_count { 75 | // Put in middle of block 76 | if foilage_count == 0 { 77 | position.x += 0.5; 78 | } else { 79 | position.x += 0.33; 80 | } 81 | game_state 82 | .foilages 83 | .push(Foilage::new(position, grass.transform.position)); 84 | } 85 | } 86 | } 87 | pub fn render( 88 | game_state: &mut GameState, 89 | sprite_collection: &SpriteCollection, 90 | ctx: &mut Context, 91 | screen_size: &na::Point2, 92 | ) -> GameResult { 93 | for foilage in game_state.foilages.iter_mut() { 94 | let offset = mint::Point2 { x: 0.5, y: 1.0 }; 95 | let mut flip_scale = 1.0; 96 | if foilage.sprite.is_flipped { 97 | flip_scale = -1.0; 98 | } 99 | let dest = foilage.position * screen_size.x; 100 | let mut time = ggez::timer::time_since_start(ctx).as_secs_f32(); 101 | time *= constantes::TIME_FOILAGE_SPEED; 102 | time += foilage.time_offset; 103 | let mut scale_x: f32; 104 | let mut scale_y: f32; 105 | 106 | match foilage.foilage_type { 107 | FoilageType::Straw => { 108 | scale_x = 1.0; 109 | scale_y = 1.0; 110 | } 111 | FoilageType::Bush => { 112 | scale_x = 1.0 + (time.sin() * 0.5 + 0.5) * constantes::SIZE_FOILAGE_DELTA; 113 | scale_y = 1.0 + (time.cos() * 0.5 + 0.5) * constantes::SIZE_FOILAGE_DELTA; 114 | } 115 | }; 116 | scale_x *= screen_size.x; 117 | scale_y *= screen_size.y; 118 | 119 | let rotation = match foilage.foilage_type { 120 | FoilageType::Straw => (time.sin() * 0.8) * constantes::ROTATION_FOILAGE_MAX, 121 | FoilageType::Bush => 0.0, 122 | }; 123 | 124 | let params = DrawParam::default() 125 | .offset(offset) 126 | .scale(na::Vector2::::new( 127 | flip_scale * scale_x / 16.0, 128 | scale_y / 16.0, 129 | )) 130 | .rotation(rotation) 131 | .dest(dest); 132 | let image = sprite_collection 133 | .images 134 | .get(foilage.sprite.texture_index) 135 | .expect("No image with id..."); 136 | graphics::draw(ctx, image, params)?; 137 | } 138 | Ok(()) 139 | } 140 | -------------------------------------------------------------------------------- /src/entities/player.rs: -------------------------------------------------------------------------------- 1 | use crate::constantes; 2 | use crate::entities::{ 3 | foilage::Grass, 4 | skeleton::{Skeleton, SkeletonBlock}, 5 | }; 6 | use crate::particle_system; 7 | use crate::particle_system::ParticleSystemCollection; 8 | use crate::sound_collection::SoundCollection; 9 | use crate::sprite::SpriteComponent; 10 | use crate::{map, states::game_state::GameState, transform_compontent::TransformComponent}; 11 | use gwg::Context; 12 | use nalgebra as na; 13 | #[derive(PartialEq)] 14 | pub enum PlayerInputIntent { 15 | None, 16 | Up, 17 | Left, 18 | Right, 19 | Down, 20 | } 21 | 22 | impl Default for PlayerInputIntent { 23 | fn default() -> Self { 24 | PlayerInputIntent::None 25 | } 26 | } 27 | 28 | //#[derive(Default)] 29 | pub struct Player { 30 | pub transform: TransformComponent, 31 | pub sprite: SpriteComponent, 32 | pub input_intent: PlayerInputIntent, 33 | pub time_since_step: f32, 34 | pub is_alive: bool, 35 | pub is_on_skeleton: bool, // Used for other to look at 36 | pub prev_grounded: bool, 37 | } 38 | 39 | impl Default for Player { 40 | fn default() -> Self { 41 | Player { 42 | prev_grounded: true, 43 | is_on_skeleton: false, 44 | is_alive: true, 45 | time_since_step: 0.0, 46 | input_intent: PlayerInputIntent::None, 47 | sprite: SpriteComponent::default(), 48 | transform: TransformComponent::default(), 49 | } 50 | } 51 | } 52 | 53 | impl Player { 54 | pub fn should_step( 55 | &mut self, 56 | dt: f32, 57 | grasses: &Vec, 58 | skeletons: &Vec, 59 | skeleton_blocks: &Vec, 60 | sound_collection: &mut SoundCollection, 61 | particle_collection: &mut ParticleSystemCollection, 62 | land_id: &u32, 63 | screen_size: &na::Point2, 64 | ) -> bool { 65 | if !self.is_alive { 66 | return false; 67 | } 68 | 69 | self.time_since_step += dt; 70 | let pos_below = self.transform.position + na::Vector2::new(0, 1); 71 | let mut is_grounded = skeletons.iter().any(|s| s.transform.position == pos_below); 72 | self.is_on_skeleton = is_grounded; 73 | is_grounded |= grasses.iter().any(|g| g.transform.position == pos_below); 74 | is_grounded |= skeleton_blocks 75 | .iter() 76 | .any(|s| s.transform.position == pos_below); 77 | 78 | if self.input_intent != PlayerInputIntent::None && is_grounded { 79 | self.time_since_step = 0.0; 80 | return true; 81 | } 82 | 83 | if self.time_since_step > constantes::TIME_AUTO_STEP { 84 | if !is_grounded { 85 | self.time_since_step = 0.0; 86 | return true; 87 | } 88 | 89 | if is_grounded && !self.prev_grounded { 90 | self.sprite.texture_index = 0; 91 | self.prev_grounded = true; 92 | sound_collection.play(8); 93 | 94 | let mut land_particles = particle_collection.get_mut(*land_id).unwrap(); 95 | land_particles.scale = screen_size.x / 16.0; 96 | let pos_player_visual = self.sprite.visual_position; 97 | let mut pos_particle = na::Vector2::new( 98 | pos_player_visual.x / screen_size.x * 16.0, 99 | pos_player_visual.y / screen_size.x * 16.0, 100 | ); 101 | pos_particle += na::Vector2::new(16.0 * 0.5, 16.0); 102 | 103 | land_particles.position = pos_particle; 104 | land_particles.emit(15); 105 | } 106 | } 107 | false 108 | } 109 | } 110 | 111 | pub fn system( 112 | game_state: &mut GameState, 113 | ctx: &mut Context, 114 | current_map: &mut usize, 115 | sound_collection: &mut SoundCollection, 116 | screen_size: &na::Point2, 117 | particle_collection: &mut ParticleSystemCollection, 118 | grass_id: &u32, 119 | step_id: &u32, 120 | foilage_1_id: &u32, 121 | foilage_2_id: &u32, 122 | foilage_3_id: &u32, 123 | foilage_4_id: &u32, 124 | ) { 125 | let mut should_exit = false; 126 | 127 | let player = &mut game_state.player; 128 | let pos_below = player.transform.position + na::Vector2::new(0, 1); 129 | let mut is_grounded = game_state 130 | .grasses 131 | .iter() 132 | .any(|g| g.transform.position == pos_below); 133 | is_grounded |= game_state 134 | .skeletons 135 | .iter() 136 | .any(|s| s.transform.position == pos_below); 137 | is_grounded |= game_state 138 | .skeleton_blocks 139 | .iter() 140 | .any(|s| s.transform.position == pos_below); 141 | 142 | if player.prev_grounded && !is_grounded { 143 | sound_collection.play(7); 144 | player.sprite.texture_index = 13; 145 | } 146 | 147 | player.prev_grounded = is_grounded; 148 | if !is_grounded { 149 | player.transform.position = pos_below; 150 | 151 | if player.transform.position.y > constantes::GAME_BOUNDS_Y { 152 | player.transform.position.y = 0; 153 | // Force visual insta jump 154 | let pos_player_unscaled = 155 | na::convert::, na::Point2>(player.transform.position); 156 | player.sprite.visual_position = pos_player_unscaled * screen_size.x; 157 | player.sprite.blink_timer = constantes::TIME_BLINK; 158 | } 159 | return; 160 | } 161 | player.sprite.texture_index = 0; 162 | 163 | match player.input_intent { 164 | PlayerInputIntent::Left => { 165 | player.sprite.is_flipped = true; 166 | let new_position = player.transform.position - na::Vector2::new(1, 0); 167 | let occupied_by_grass = game_state 168 | .grasses 169 | .iter() 170 | .any(|g| g.transform.position == new_position); 171 | let occupied_by_skeleton = game_state 172 | .skeletons 173 | .iter() 174 | .any(|s| s.transform.position == new_position); 175 | let occupied_by_skeleton_block = game_state 176 | .skeleton_blocks 177 | .iter() 178 | .any(|s| s.transform.position == new_position); 179 | let is_occupied = 180 | occupied_by_grass || occupied_by_skeleton || occupied_by_skeleton_block; 181 | if !is_occupied { 182 | player.transform.position = new_position; 183 | sound_collection.play(0); 184 | 185 | particle_system::emit_step_particle( 186 | particle_collection, 187 | step_id, 188 | 9, 189 | false, 190 | &player.sprite.visual_position, 191 | screen_size, 192 | ); 193 | } 194 | } 195 | PlayerInputIntent::Right => { 196 | player.sprite.is_flipped = false; 197 | let new_position = player.transform.position + na::Vector2::new(1, 0); 198 | let occupied_by_grass = game_state 199 | .grasses 200 | .iter() 201 | .any(|g| g.transform.position == new_position); 202 | let occupied_by_skeleton = game_state 203 | .skeletons 204 | .iter() 205 | .any(|s| s.transform.position == new_position); 206 | let occupied_by_skeleton_block = game_state 207 | .skeleton_blocks 208 | .iter() 209 | .any(|s| s.transform.position == new_position); 210 | let is_occupied = 211 | occupied_by_grass || occupied_by_skeleton || occupied_by_skeleton_block; 212 | if !is_occupied { 213 | player.transform.position = new_position; 214 | sound_collection.play(0); 215 | particle_system::emit_step_particle( 216 | particle_collection, 217 | step_id, 218 | 8, 219 | true, 220 | &player.sprite.visual_position, 221 | screen_size, 222 | ); 223 | } 224 | } 225 | PlayerInputIntent::Up => { 226 | // Teleporter 227 | let teleporter_tuple_option = game_state 228 | .teleporters 229 | .iter() 230 | .map(|t| t.as_ref()) 231 | .enumerate() 232 | .find(|(_i, t)| { 233 | if let Some(teleporter) = t { 234 | return teleporter.transform.position == player.transform.position; 235 | } 236 | false 237 | }); 238 | if let Some((index, _teleporter)) = teleporter_tuple_option { 239 | let other_teleporter_index = 1 - index; 240 | let other_teleporter_option = game_state 241 | .teleporters 242 | .get_mut(other_teleporter_index) 243 | .unwrap(); 244 | if let Some(other_teleporter) = other_teleporter_option { 245 | player.transform.position = other_teleporter.transform.position; 246 | sound_collection.play(3); 247 | player.sprite.blink_timer = constantes::TIME_BLINK; 248 | other_teleporter.sprite.blink_timer = constantes::TIME_BLINK; 249 | } 250 | } 251 | // Exit 252 | let is_on_exit = game_state.exit.transform.position == player.transform.position; 253 | let all_skeletons_freed = game_state 254 | .skeleton_blocks 255 | .iter() 256 | .all(|s| s.buried.is_released); 257 | if is_on_exit { 258 | if all_skeletons_freed { 259 | should_exit = true; 260 | sound_collection.play(4); 261 | } else { 262 | sound_collection.play(6); 263 | for skeleton_block in game_state.skeleton_blocks.iter_mut() { 264 | skeleton_block.sprite.blink_timer = constantes::TIME_BLINK; 265 | } 266 | } 267 | } 268 | } 269 | PlayerInputIntent::Down => { 270 | let pos_below = player.transform.position + na::Vector2::new(0, 1); 271 | let skeleton_block_option = game_state 272 | .skeleton_blocks 273 | .iter_mut() 274 | .find(|s| s.transform.position == pos_below); 275 | if let Some(skeleton_block) = skeleton_block_option { 276 | skeleton_block.dig(); 277 | } 278 | 279 | let grass_particle_system = particle_collection.get_mut(*grass_id).unwrap(); 280 | let mut pos_particle = na::Vector2::new( 281 | player.sprite.visual_position.x / screen_size.x * 16.0, 282 | player.sprite.visual_position.y / screen_size.x * 16.0, 283 | ); 284 | 285 | grass_particle_system.scale = screen_size.x / 16.0; 286 | // offset to under player 287 | pos_particle.x += 16.0 * 0.5; 288 | pos_particle.y += 16.0; 289 | grass_particle_system.position = pos_particle; 290 | grass_particle_system.emit(20); 291 | 292 | sound_collection.play(1); 293 | player.sprite.texture_index = 8; 294 | 295 | // Foilage fly! 296 | let foilage_index_option = game_state 297 | .foilages 298 | .iter() 299 | .position(|f| f.pos_i32 == pos_below); 300 | if let Some(foilage_index) = foilage_index_option { 301 | let foilage = game_state.foilages.remove(foilage_index); 302 | let foilage_texture_index = foilage.sprite.texture_index; 303 | let particle_system_index = match foilage_texture_index { 304 | 14 => *foilage_1_id, 305 | 15 => *foilage_2_id, 306 | 16 => *foilage_3_id, 307 | 17 => *foilage_4_id, 308 | _ => *foilage_1_id, 309 | }; 310 | let foilage_particle_system = 311 | particle_collection.get_mut(particle_system_index).unwrap(); 312 | foilage_particle_system.scale = screen_size.x / 16.0; 313 | foilage_particle_system.position = pos_particle; 314 | foilage_particle_system.emit(1); 315 | } 316 | } 317 | PlayerInputIntent::None => {} 318 | } 319 | 320 | let player = &mut game_state.player; 321 | // bounds check 322 | if player.transform.position.x < 0 { 323 | player.transform.position.x = 0; 324 | } else if player.transform.position.x > constantes::GAME_BOUNDS_X { 325 | player.transform.position.x = constantes::GAME_BOUNDS_X; 326 | } 327 | 328 | player.input_intent = PlayerInputIntent::None; 329 | if should_exit { 330 | map::clear_map(game_state); 331 | *current_map += 1; 332 | if *current_map >= map::MAP_COUNT { 333 | game_state.is_all_levels_completed = true; 334 | } else { 335 | map::load_map(ctx, game_state, *current_map, screen_size); 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/entities/skeleton.rs: -------------------------------------------------------------------------------- 1 | use crate::constantes; 2 | use crate::entities::ai::{AiComponent, AiState}; 3 | use crate::particle_system::ParticleSystemCollection; 4 | use crate::sound_collection::SoundCollection; 5 | use crate::sprite::SpriteComponent; 6 | use crate::states::game_state::GameState; 7 | use crate::transform_compontent::TransformComponent; 8 | use ggez::Context; 9 | use gwg as ggez; 10 | use nalgebra as na; 11 | use std::collections::HashMap; 12 | #[derive(Default)] 13 | pub struct Skeleton { 14 | pub transform: TransformComponent, 15 | pub sprite: SpriteComponent, 16 | pub ai: AiComponent, 17 | } 18 | 19 | #[derive(Default)] 20 | pub struct SkeletonBlock { 21 | pub transform: TransformComponent, 22 | pub sprite: SpriteComponent, 23 | pub buried: BuriedComponent, 24 | } 25 | 26 | impl SkeletonBlock { 27 | pub fn dig(&mut self) { 28 | self.buried.is_dug = true; 29 | self.sprite.texture_index = 2; 30 | } 31 | 32 | pub fn try_release(&mut self) -> bool { 33 | if self.buried.is_released { 34 | return false; 35 | } 36 | if !self.buried.is_dug { 37 | return false; 38 | } 39 | self.buried.is_released = true; 40 | true 41 | } 42 | } 43 | 44 | pub fn block_system(game_state: &mut GameState, sound_collection: &mut SoundCollection) { 45 | for block in game_state.skeleton_blocks.iter_mut() { 46 | let pos_above = block.transform.position - na::Vector2::new(0, 1); 47 | let mut is_occupied = game_state.player.transform.position == pos_above; 48 | is_occupied |= game_state 49 | .skeletons 50 | .iter() 51 | .any(|s| s.transform.position == pos_above); 52 | if is_occupied { 53 | continue; 54 | } 55 | if block.try_release() { 56 | let transform = TransformComponent { 57 | position: pos_above, 58 | }; 59 | let sprite = SpriteComponent { 60 | texture_index: 7, 61 | ..Default::default() 62 | }; 63 | let mut new_skeleton = Skeleton { 64 | transform, 65 | sprite, 66 | ..Default::default() 67 | }; 68 | let delta_player_x = 69 | game_state.player.transform.position.x - new_skeleton.transform.position.x; 70 | if delta_player_x > 0 { 71 | new_skeleton.sprite.is_flipped = false; 72 | } else { 73 | new_skeleton.sprite.is_flipped = true; 74 | } 75 | game_state.skeletons.push(new_skeleton); 76 | sound_collection.play(5); 77 | } 78 | } 79 | } 80 | 81 | pub fn reset_turns(game_state: &mut GameState) { 82 | for skeleton in game_state.skeletons.iter_mut() { 83 | skeleton.ai.turn_taken = false; 84 | } 85 | } 86 | 87 | pub fn walk( 88 | game_state: &mut GameState, 89 | sound_collection: &mut SoundCollection, 90 | screen_size: &na::Point2, 91 | ) { 92 | let pos_player = game_state.player.transform.position; 93 | let mut new_positions = HashMap::new(); 94 | let mut wants_attack: Vec = vec![]; 95 | let mut skeleton_warped_y: Vec = vec![]; 96 | let mut flip_dirs = HashMap::new(); 97 | 98 | for (index, skeleton) in game_state 99 | .skeletons 100 | .iter() 101 | .enumerate() 102 | .filter(|(_i, s)| s.ai.state == AiState::Walk && !s.ai.turn_taken) 103 | { 104 | let mut new_position = skeleton.transform.position; 105 | let pos_below = skeleton.transform.position + na::Vector2::new(0, 1); 106 | let mut is_grounded = pos_player == pos_below; 107 | is_grounded |= game_state 108 | .grasses 109 | .iter() 110 | .any(|g| g.transform.position == pos_below); 111 | is_grounded |= game_state 112 | .skeletons 113 | .iter() 114 | .any(|s| s.transform.position == pos_below); 115 | is_grounded |= game_state 116 | .skeleton_blocks 117 | .iter() 118 | .any(|s| s.transform.position == pos_below); 119 | 120 | // walk towards player 121 | if is_grounded { 122 | let mut pos_skele = skeleton.transform.position; 123 | if pos_skele.x < pos_player.x { 124 | pos_skele.x += 1; 125 | flip_dirs.insert(index, false); 126 | } else if pos_skele.x > pos_player.x { 127 | pos_skele.x -= 1; 128 | flip_dirs.insert(index, true); 129 | } 130 | let mut is_occupied = pos_skele == pos_player; 131 | if is_occupied { 132 | wants_attack.push(index); 133 | sound_collection.play(5); 134 | continue; 135 | } 136 | is_occupied |= game_state 137 | .grasses 138 | .iter() 139 | .any(|g| g.transform.position == pos_skele); 140 | is_occupied |= game_state 141 | .skeleton_blocks 142 | .iter() 143 | .any(|s| s.transform.position == pos_skele); 144 | is_occupied |= game_state 145 | .skeletons 146 | .iter() 147 | .any(|s| s.transform.position == pos_skele); 148 | let up_vector = na::Vector2::new(0, -1); 149 | let is_other_falling = game_state 150 | .skeletons 151 | .iter() 152 | .any(|s| s.transform.position == pos_skele + up_vector); 153 | 154 | let pos_above_skeleton = skeleton.transform.position + up_vector; 155 | let is_above = game_state 156 | .skeletons 157 | .iter() 158 | .any(|s| s.transform.position == pos_above_skeleton); 159 | 160 | if !is_occupied && !game_state.player.is_on_skeleton && !is_above && !is_other_falling { 161 | new_position = pos_skele; 162 | } 163 | } else { 164 | // handle gravity 165 | new_position = pos_below; 166 | } 167 | 168 | let warped_y = in_bounds(&mut new_position); 169 | if warped_y { 170 | skeleton_warped_y.push(index); 171 | } 172 | let is_occupied = new_positions.iter().any(|(_i, p)| *p == new_position); 173 | if is_occupied { 174 | //new_positions.insert(index, skeleton.transform.position); 175 | } else { 176 | new_positions.insert(index, new_position); 177 | } 178 | } 179 | for (i, skeleton) in game_state.skeletons.iter_mut().enumerate() { 180 | match new_positions.get(&i) { 181 | Some(p) => { 182 | skeleton.transform.position = *p; 183 | } 184 | None => {} 185 | } 186 | } 187 | for i in wants_attack.iter() { 188 | match game_state.skeletons.get_mut(*i) { 189 | Some(skeleton) => { 190 | skeleton.sprite.texture_index = 7; 191 | skeleton.ai.state = AiState::Attack 192 | } 193 | None => {} 194 | } 195 | } 196 | for (i, flipped) in flip_dirs.iter() { 197 | if let Some(skeleton) = game_state.skeletons.get_mut(*i) { 198 | skeleton.sprite.is_flipped = *flipped; 199 | } 200 | } 201 | for i in skeleton_warped_y.iter() { 202 | match game_state.skeletons.get_mut(*i) { 203 | Some(skeleton) => { 204 | skeleton.sprite.blink_timer = constantes::TIME_BLINK; 205 | let position = 206 | na::convert::, na::Point2>(skeleton.transform.position); 207 | skeleton.sprite.visual_position = position * screen_size.x; 208 | } 209 | None => {} 210 | } 211 | } 212 | } 213 | 214 | pub fn attack( 215 | game_state: &mut GameState, 216 | _ctx: &mut Context, 217 | sound_collection: &mut SoundCollection, 218 | particle_collection: &mut ParticleSystemCollection, 219 | blood_id: &u32, 220 | screen_size: &na::Point2, 221 | ) { 222 | let player = &mut game_state.player; 223 | let pos_player = &player.transform.position; 224 | for skeleton in game_state 225 | .skeletons 226 | .iter_mut() 227 | .filter(|s| s.ai.state == AiState::Attack) 228 | { 229 | let pos_skele = skeleton.transform.position; 230 | let mut pos_skele_to_player = pos_skele; 231 | if pos_skele.x < pos_player.x { 232 | pos_skele_to_player.x += 1; 233 | } else if pos_skele.x > pos_player.x { 234 | pos_skele_to_player.x -= 1; 235 | } 236 | let attack_player = pos_skele_to_player == *pos_player; 237 | match attack_player { 238 | true => { 239 | player.is_alive = false; 240 | player.sprite.texture_index = 9; 241 | skeleton.ai.state = AiState::Walk; 242 | skeleton.sprite.texture_index = 4; 243 | sound_collection.play(2); 244 | 245 | let blood_particles = particle_collection.get_mut(*blood_id).unwrap(); 246 | blood_particles.scale = screen_size.x / 16.0; 247 | let pos_player_visual = player.sprite.visual_position; 248 | let mut pos_particle = na::Vector2::new( 249 | pos_player_visual.x / screen_size.x * 16.0, 250 | pos_player_visual.y / screen_size.x * 16.0, 251 | ); 252 | pos_particle += na::Vector2::new(16.0 * 0.5, 16.0 * 0.5); 253 | 254 | blood_particles.position = pos_particle; 255 | blood_particles.emit(20); 256 | } 257 | false => { 258 | skeleton.ai.state = AiState::Walk; 259 | skeleton.sprite.texture_index = 4; 260 | } 261 | } 262 | skeleton.ai.turn_taken = true; 263 | } 264 | } 265 | 266 | pub fn system( 267 | game_state: &mut GameState, 268 | ctx: &mut Context, 269 | sound_collection: &mut SoundCollection, 270 | particle_collection: &mut ParticleSystemCollection, 271 | blood_id: &u32, 272 | screen_size: &na::Point2, 273 | ) { 274 | attack( 275 | game_state, 276 | ctx, 277 | sound_collection, 278 | particle_collection, 279 | blood_id, 280 | screen_size, 281 | ); 282 | walk(game_state, sound_collection, screen_size); 283 | reset_turns(game_state); 284 | } 285 | 286 | // Returns if the skeleton warped y 287 | pub fn in_bounds(position: &mut na::Point2) -> bool { 288 | if position.x < 0 { 289 | position.x = 0; 290 | } else if position.x > constantes::GAME_BOUNDS_X { 291 | position.x = constantes::GAME_BOUNDS_X; 292 | } 293 | if position.y > constantes::GAME_BOUNDS_Y { 294 | position.y = 0; 295 | return true; 296 | } 297 | false 298 | } 299 | 300 | #[derive(Default)] 301 | pub struct BuriedComponent { 302 | pub is_dug: bool, 303 | pub is_released: bool, 304 | } 305 | -------------------------------------------------------------------------------- /src/entities/teleporter.rs: -------------------------------------------------------------------------------- 1 | use crate::{sprite::SpriteComponent, transform_compontent::TransformComponent}; 2 | #[derive(Default)] 3 | pub struct Teleporter { 4 | pub transform: TransformComponent, 5 | pub sprite: SpriteComponent, 6 | } 7 | 8 | #[derive(Default)] 9 | pub struct Exit { 10 | pub transform: TransformComponent, 11 | pub sprite: SpriteComponent, 12 | } 13 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod constantes; 2 | mod sprite; 3 | mod transform_compontent; 4 | 5 | mod entities; 6 | mod map; 7 | mod particle_system; 8 | mod sound_collection; 9 | mod util; 10 | 11 | mod states; 12 | use states::main_state::MainState; 13 | 14 | // Magic! 15 | use gwg as ggez; 16 | 17 | use ggez::GameResult; 18 | 19 | fn main() -> GameResult { 20 | ggez::start( 21 | ggez::conf::Conf { 22 | cache: ggez::conf::Cache::Tar(include_bytes!("resources.tar").to_vec()), 23 | loading: ggez::conf::Loading::Embedded, 24 | ..Default::default() 25 | }, // conf 26 | |mut context| Box::new(MainState::new(&mut context).unwrap()), 27 | ) // ggez::start 28 | } 29 | -------------------------------------------------------------------------------- /src/map.rs: -------------------------------------------------------------------------------- 1 | use ggez::Context; 2 | use gwg as ggez; 3 | 4 | use crate::entities::teleporter::{Exit, Teleporter}; 5 | use crate::entities::{cloud, foilage, player, skeleton}; 6 | use crate::sprite::SpriteComponent; 7 | use crate::states::game_state::GameState; 8 | use crate::transform_compontent::TransformComponent; 9 | use crate::util; 10 | use foilage::Grass; 11 | use nalgebra as na; 12 | use player::Player; 13 | use skeleton::SkeletonBlock; 14 | use std::io::Read; 15 | 16 | const MAP_NAMES: &[&str] = &[ 17 | "/maps/map_first.txt", 18 | "/maps/map_1skeleton.txt", 19 | "/maps/map_2skeleton.txt", 20 | "/maps/map_gravity.txt", 21 | "/maps/map_teleport.txt", 22 | "/maps/map_simple_backtrack.txt", 23 | "/maps/map_follow.txt", 24 | "/maps/map_middleclash.txt", 25 | "/maps/map_2skeleton_intro.txt", 26 | "/maps/map_2skeleton_backtrack.txt", 27 | "/maps/map_force_stand.txt", 28 | "maps/map_fall_trap.txt", 29 | "/maps/map_skeleton_hole.txt", 30 | "/maps/map_easy_accidental_fall.txt", 31 | "/maps/map_easy3.txt", 32 | "/maps/map_easy1.txt", 33 | "/maps/map_maze1.txt", 34 | "/maps/map_follow_2.txt", 35 | "/maps/map_easy2.txt", 36 | "/maps/map_3skeleton.txt", 37 | "/maps/map_3skeleton_3holes.txt", 38 | "/maps/map_skeleton_platform.txt", 39 | "/maps/map_middleclash_2.txt", 40 | "/maps/map_3skeleton_3holes_harder.txt", 41 | "/maps/map_hard1.txt", 42 | "/maps/map_hard3.txt", 43 | "/maps/map_hard2.txt", 44 | ]; 45 | 46 | pub const MAP_COUNT: usize = MAP_NAMES.len(); 47 | 48 | fn get_map_name(index: usize) -> &'static str { 49 | MAP_NAMES[index] 50 | } 51 | 52 | pub fn clear_map(game_state: &mut GameState) { 53 | game_state.grasses.clear(); 54 | game_state.skeletons.clear(); 55 | game_state.skeleton_blocks.clear(); 56 | game_state.foilages.clear(); 57 | game_state.clouds.clear(); 58 | game_state.teleporters[0] = None; 59 | game_state.teleporters[1] = None; 60 | } 61 | 62 | pub fn load_map( 63 | ctx: &mut Context, 64 | game_state: &mut GameState, 65 | map_index: usize, 66 | screen_size: &na::Point2, 67 | ) { 68 | let map_filename = get_map_name(map_index); 69 | let mut file = ggez::filesystem::open(ctx, map_filename).expect("no map file"); 70 | let mut buffer = String::new(); 71 | file.read_to_string(&mut buffer).unwrap(); 72 | let mut x: i32 = 0; 73 | let mut y: i32 = 0; 74 | for char in buffer.chars() { 75 | // almost every case have a transform, create here to avoid redundant code 76 | let transform = TransformComponent { 77 | position: na::Point2::new(x, y), 78 | }; 79 | 80 | match char { 81 | '0' => { 82 | game_state.player = Player { 83 | transform, 84 | is_alive: true, 85 | ..Default::default() 86 | }; 87 | } 88 | '1' => { 89 | game_state.grasses.push(Grass { 90 | transform, 91 | sprite: SpriteComponent { 92 | texture_index: 1, 93 | ..Default::default() 94 | }, 95 | ..Default::default() 96 | }); 97 | } 98 | '2' => { 99 | game_state.skeleton_blocks.push(SkeletonBlock { 100 | transform, 101 | sprite: SpriteComponent { 102 | texture_index: 3, 103 | ..Default::default() 104 | }, 105 | ..Default::default() 106 | }); 107 | } 108 | '3' => { 109 | let sprite = SpriteComponent { 110 | texture_index: 5, 111 | ..Default::default() 112 | }; 113 | if game_state.teleporters[0].is_none() { 114 | game_state.teleporters[0] = Some(Teleporter { 115 | transform, 116 | sprite, 117 | ..Default::default() 118 | }); 119 | } else { 120 | game_state.teleporters[1] = Some(Teleporter { 121 | transform, 122 | sprite, 123 | ..Default::default() 124 | }); 125 | } 126 | 127 | } 128 | '4' => { 129 | game_state.exit = Exit { 130 | transform, 131 | sprite: SpriteComponent { 132 | texture_index: 6, 133 | ..Default::default() 134 | }, 135 | ..Default::default() 136 | }; 137 | } 138 | '5' => { 139 | game_state.grasses.push(Grass { 140 | transform, 141 | sprite: SpriteComponent { 142 | texture_index: 10, 143 | ..Default::default() 144 | }, 145 | ..Default::default() 146 | }); 147 | } 148 | '\n' => { 149 | // -1 because it will increment right after to 0 150 | x = -1; 151 | y += 1; 152 | } 153 | _ => {} 154 | } 155 | x += 1; 156 | } 157 | // visual position starts at 0,0 158 | util::force_visual_positions(game_state, screen_size); 159 | 160 | // Clouds generation 161 | cloud::spawn(game_state); 162 | 163 | foilage::generate(game_state); 164 | } 165 | -------------------------------------------------------------------------------- /src/particle_system.rs: -------------------------------------------------------------------------------- 1 | use crate::transform_compontent::TransformComponent; 2 | use ggez::graphics::spritebatch::SpriteBatch; 3 | use ggez::graphics::{DrawParam, Image}; 4 | use ggez::Context; 5 | use ggez::GameResult; 6 | use gwg as ggez; 7 | use gwg::rand; 8 | use nalgebra as na; 9 | use std::collections::HashMap; 10 | use std::collections::VecDeque; 11 | 12 | static DEFAULT_CAPACITY: usize = 8; 13 | static PI: f32 = std::f32::consts::PI; 14 | static TAU: f32 = PI * 2.0; 15 | 16 | use na::Point2; 17 | use na::Vector2; 18 | 19 | // Todo, since start_lifetime can be randomized, the scaling will not start at start_scale 20 | // because the lifetime fraction also then is randomized 21 | 22 | // helper funcitons 23 | // in radians 24 | fn vec_from_angle(angle: f32) -> na::Vector2 { 25 | na::Vector2::new(angle.sin(), angle.cos()) 26 | } 27 | 28 | fn lerp(from: f32, to: f32, delta: f32) -> f32 { 29 | (1.0 - delta) * from + delta * to 30 | } 31 | 32 | #[derive(Clone, Copy)] 33 | pub enum TransformSpace { 34 | Local, 35 | World, 36 | } 37 | 38 | pub struct ParticleSystem { 39 | // Particle data 40 | positions: Vec>, 41 | velocities: Vec>, 42 | angular_velocities: Vec, 43 | scales: Vec, 44 | rotations: Vec, 45 | lifetimes: Vec, 46 | colors: Vec, 47 | particle_indexes: VecDeque, 48 | available_indexes: VecDeque, 49 | 50 | // System data 51 | pub emit_shape: EmitShape, 52 | pub velocity_type: VelocityType, 53 | pub gravity: f32, 54 | pub transform_space: TransformSpace, 55 | 56 | pub scale: f32, 57 | pub position: Vector2, 58 | pub start_lifetime: ValueGetter, 59 | pub start_speed: ValueGetter, 60 | pub start_rotation: ValueGetter, 61 | pub start_scale: ValueGetter, 62 | pub start_angular_velocity: ValueGetter, 63 | pub start_color: ValueGetter, 64 | pub end_scale: f32, 65 | 66 | sprite_batch_dirty: bool, 67 | pub sprite_batch: SpriteBatch, 68 | } 69 | 70 | fn make_image(ctx: &mut Context) -> Image { 71 | // 1 pixel texture with 1.0 in every color 72 | let bytes = [u8::MAX; 4]; 73 | Image::from_rgba8(ctx, 1, 1, &bytes).unwrap() 74 | } 75 | 76 | impl ParticleSystem { 77 | pub fn copy_settings(&mut self, other: &ParticleSystem) { 78 | self.emit_shape = other.emit_shape; 79 | self.velocity_type = other.velocity_type; 80 | self.gravity = other.gravity; 81 | self.transform_space = other.transform_space; 82 | self.scale = other.scale; 83 | self.position = other.position; 84 | self.start_lifetime = other.start_lifetime; 85 | self.start_speed = other.start_speed; 86 | self.start_rotation = other.start_rotation; 87 | self.start_scale = other.start_scale; 88 | self.start_angular_velocity = other.start_angular_velocity; 89 | self.start_color = other.start_color; 90 | self.end_scale = other.end_scale; 91 | } 92 | 93 | pub fn new(ctx: &mut Context, image_option: Option) -> Self { 94 | let mut available_indexes = VecDeque::with_capacity(DEFAULT_CAPACITY); 95 | for i in 0..available_indexes.capacity() { 96 | available_indexes.push_back(i); 97 | } 98 | 99 | let final_image; 100 | if let Some(image) = image_option { 101 | final_image = image; 102 | } else { 103 | final_image = make_image(ctx); 104 | } 105 | 106 | let sprite_batch = SpriteBatch::new(final_image); 107 | 108 | let mut particle_system = ParticleSystem { 109 | positions: Vec::with_capacity(DEFAULT_CAPACITY), 110 | velocities: Vec::with_capacity(DEFAULT_CAPACITY), 111 | scales: Vec::with_capacity(DEFAULT_CAPACITY), 112 | lifetimes: Vec::with_capacity(DEFAULT_CAPACITY), 113 | rotations: Vec::with_capacity(DEFAULT_CAPACITY), 114 | colors: Vec::with_capacity(DEFAULT_CAPACITY), 115 | angular_velocities: Vec::with_capacity(DEFAULT_CAPACITY), 116 | particle_indexes: VecDeque::with_capacity(DEFAULT_CAPACITY), 117 | available_indexes, 118 | 119 | emit_shape: EmitShape::Point, 120 | velocity_type: VelocityType::Angle(AngleData::new(PI, Some(0.5))), 121 | gravity: -9.0, 122 | transform_space: TransformSpace::World, 123 | 124 | scale: 1.0, 125 | position: Vector2::new(200.0, 300.0), 126 | start_lifetime: ValueGetter::Single(1.4), 127 | start_speed: ValueGetter::Range(0.0, 3.0), 128 | start_rotation: ValueGetter::Single(0.0), 129 | start_scale: ValueGetter::Single(16.0), 130 | start_angular_velocity: ValueGetter::Range(-1.0, 1.0), 131 | start_color: ValueGetter::Range( 132 | ggez::graphics::Color::new(0.5, 0.2, 0.2, 1.0), 133 | ggez::graphics::Color::new(1.0, 1.0, 0.2, 1.0), 134 | ), 135 | 136 | end_scale: 0.0, 137 | 138 | sprite_batch_dirty: true, 139 | sprite_batch, 140 | }; 141 | let available_indexes = particle_system.available_indexes.len(); 142 | for _i in 0..available_indexes { 143 | particle_system.positions.push(Point2::new(0.0, 0.0)); 144 | } 145 | for _i in 0..available_indexes { 146 | particle_system.velocities.push(Vector2::new(0.0, 0.0)); 147 | } 148 | for _i in 0..available_indexes { 149 | particle_system.scales.push(1.0); 150 | } 151 | for _i in 0..available_indexes { 152 | particle_system.lifetimes.push(0.0); 153 | } 154 | for _i in 0..available_indexes { 155 | particle_system.rotations.push(0.0); 156 | } 157 | for _i in 0..available_indexes { 158 | particle_system.angular_velocities.push(0.0); 159 | } 160 | for _i in 0..available_indexes { 161 | particle_system.colors.push(ggez::graphics::WHITE); 162 | } 163 | particle_system 164 | } 165 | 166 | // Draw delegate, recieves 167 | pub fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { 168 | if !self.sprite_batch_dirty { 169 | return Ok(()); 170 | } 171 | if self.particle_indexes.len() == 0 { 172 | return Ok(()); 173 | } 174 | self.sprite_batch.clear(); 175 | for i in self.particle_indexes.iter() { 176 | let scale = self.scales[*i]; 177 | let mut dest = self.positions[*i]; 178 | if let TransformSpace::Local = self.transform_space { 179 | dest += self.position; 180 | } 181 | 182 | let drawparam = DrawParam { 183 | offset: Point2::new(0.5, 0.5).into(), 184 | dest: (dest * self.scale).into(), 185 | scale: mint::Vector2 { 186 | x: scale * self.scale, 187 | y: scale * self.scale, 188 | }, 189 | rotation: self.rotations[*i], 190 | color: self.colors[*i], 191 | ..Default::default() 192 | }; 193 | self.sprite_batch.add(drawparam); 194 | } 195 | ggez::graphics::draw(ctx, &self.sprite_batch, DrawParam::default())?; 196 | 197 | self.sprite_batch_dirty = true; 198 | Ok(()) 199 | } 200 | 201 | pub fn update(&mut self, dt: f32) { 202 | // Borrowing here to make iterator borrowing easier 203 | let particle_indexes = &mut self.particle_indexes; 204 | let lifetimes = &mut self.lifetimes; 205 | let available_indexes = &mut self.available_indexes; 206 | 207 | // remove particles with lifetime less than 0 208 | // to those, removed, add that index to available_indexes 209 | particle_indexes.retain(|i| { 210 | lifetimes[*i] -= dt; 211 | if lifetimes[*i] < 0.0 { 212 | available_indexes.push_back(*i); 213 | return false; 214 | } 215 | true 216 | }); 217 | 218 | for i in self.particle_indexes.iter() { 219 | self.velocities[*i].y -= self.gravity * dt; 220 | self.positions[*i] += self.velocities[*i]; 221 | self.rotations[*i] += self.angular_velocities[*i] * dt; 222 | let normalized_life = 223 | (self.start_lifetime.max() - lifetimes[*i]) / self.start_lifetime.max(); 224 | self.scales[*i] = lerp(self.start_scale.max(), self.end_scale, normalized_life); 225 | } 226 | self.sprite_batch_dirty = true; 227 | } 228 | 229 | pub fn emit(&mut self, amount: i32) { 230 | let mut amount = amount; 231 | //for i in 0..amount { 232 | while amount > 0 { 233 | let index_option = self.available_indexes.pop_front(); 234 | match index_option { 235 | Some(index) => { 236 | // make unused particle come alive 237 | self.particle_setup(index); 238 | } 239 | None => { 240 | // Resize vectors and spawn a new particle 241 | let left_to_create = amount; 242 | self.grow(left_to_create as usize); 243 | // we still have a particle to spawn 244 | amount += 1; 245 | } 246 | } 247 | amount -= 1; 248 | } 249 | } 250 | 251 | // Returns the first available index 252 | fn grow(&mut self, additional: usize) { 253 | self.lifetimes.reserve(additional); 254 | self.positions.reserve(additional); 255 | self.velocities.reserve(additional); 256 | self.rotations.reserve(additional); 257 | self.scales.reserve(additional); 258 | self.angular_velocities.reserve(additional); 259 | self.particle_indexes.reserve(additional); 260 | self.available_indexes.reserve(additional); 261 | self.colors.reserve(additional); 262 | 263 | let next_available_index = self.lifetimes.len(); 264 | 265 | for _i in self.positions.len()..self.positions.capacity() { 266 | self.positions.push(Point2::new(0.0, 0.0)); 267 | } 268 | for _i in self.velocities.len()..self.velocities.capacity() { 269 | self.velocities.push(Vector2::new(0.0, 0.0)); 270 | } 271 | for _i in self.scales.len()..self.scales.capacity() { 272 | self.scales.push(0.0); 273 | } 274 | for _i in self.lifetimes.len()..self.lifetimes.capacity() { 275 | self.lifetimes.push(0.0); 276 | } 277 | for _i in self.rotations.len()..self.rotations.capacity() { 278 | self.rotations.push(0.0); 279 | } 280 | for _i in self.angular_velocities.len()..self.angular_velocities.capacity() { 281 | self.angular_velocities.push(0.0); 282 | } 283 | for _i in self.colors.len()..self.colors.capacity() { 284 | self.colors.push(ggez::graphics::WHITE); 285 | } 286 | 287 | let newly_added = self.lifetimes.len() - next_available_index; 288 | for i in 0..newly_added { 289 | self.available_indexes.push_back(next_available_index + i); 290 | } 291 | } 292 | 293 | // Setup the data for a newly created particle 294 | // index is assumed to be in bounds 295 | fn particle_setup(&mut self, index: usize) { 296 | let mut pos = self.emit_shape.get_position(); 297 | if let TransformSpace::World = self.transform_space { 298 | pos += self.position; 299 | } 300 | let rotation = self.start_rotation.get(); 301 | let angular_velocity = self.start_angular_velocity.get(); 302 | let scale = self.start_scale.get(); 303 | let speed = self.start_speed.get(); 304 | let direction = self.emit_shape.get_direction(&self.velocity_type, &pos); 305 | let velocity = direction * speed; 306 | let lifetime = self.start_lifetime.get(); 307 | let color = self.start_color.get(); 308 | 309 | self.lifetimes[index] = lifetime; 310 | self.positions[index] = pos; 311 | self.velocities[index] = velocity; 312 | self.rotations[index] = rotation; 313 | self.scales[index] = scale; 314 | self.angular_velocities[index] = angular_velocity; 315 | self.colors[index] = color; 316 | self.particle_indexes.push_back(index); 317 | } 318 | } 319 | 320 | #[derive(Clone, Copy)] 321 | pub enum EmitShape { 322 | Point, // The position of the particle system 323 | //Line(Vector2), 324 | //Rect(RectData), 325 | //Cone(ConeData), 326 | Circle(CircleData), 327 | } 328 | 329 | #[derive(Clone, Copy)] 330 | struct RectData { 331 | size: Vector2, 332 | spawn_type: SpawnType, 333 | } 334 | 335 | #[derive(Clone, Copy)] 336 | struct ConeData { 337 | radius: f32, 338 | angle: f32, 339 | spawn_type: SpawnType, 340 | } 341 | 342 | #[derive(Clone, Copy)] 343 | struct CircleData { 344 | radius: f32, 345 | spawn_type: SpawnType, 346 | } 347 | 348 | #[derive(Clone, Copy)] 349 | enum SpawnType { 350 | Volume, 351 | Edge, 352 | } 353 | 354 | // decides how velocity should be calculated 355 | #[derive(Clone, Copy)] 356 | pub enum VelocityType { 357 | //AlignToDirection(AlignToDirectionData), 358 | Angle(AngleData), 359 | Random, 360 | } 361 | 362 | #[derive(Clone, Copy)] 363 | pub struct AlignToDirectionData { 364 | pub max_delta: Option, 365 | } 366 | 367 | #[derive(Clone, Copy)] 368 | pub struct AngleData { 369 | pub angle: f32, 370 | pub max_delta: Option, 371 | } 372 | 373 | impl AngleData { 374 | pub fn new(angle: f32, max_delta: Option) -> Self { 375 | AngleData { angle, max_delta } 376 | } 377 | } 378 | 379 | impl EmitShape { 380 | // Todo: Implement other shapes other than point and cirle 381 | pub fn get_position(&self) -> Point2 { 382 | match self { 383 | EmitShape::Point => Point2::new(0.0, 0.0), 384 | EmitShape::Circle(c) => { 385 | let mut dir = vec_from_angle(TAU); 386 | if let SpawnType::Volume = c.spawn_type { 387 | dir *= rand::gen_range(0.0, 1.0); 388 | } 389 | dir.into() 390 | } 391 | //EmitShape::Line(v) => {Point2{x: 0.0, y: 0.0}}, 392 | //EmitShape::Rect(r) => {Point2{x: 0.0, y: 0.0}}, 393 | //EmitShape::Cone(c, a) => {Point2{x: 0.0, y: 0.0}}, 394 | } 395 | } 396 | 397 | pub fn get_direction( 398 | &self, 399 | velocity_type: &VelocityType, 400 | _position: &Point2, 401 | ) -> Vector2 { 402 | match velocity_type { 403 | VelocityType::Random => vec_from_angle(rand::gen_range(0.0, TAU)), 404 | VelocityType::Angle(a) => { 405 | let delta = match a.max_delta { 406 | Some(d) => rand::gen_range(-d, d), 407 | None => 0.0, 408 | }; 409 | vec_from_angle(a.angle + delta) 410 | } 411 | // VelocityType::AlignToDirection(a) => { 412 | // match self { 413 | // EmitShape::Point => { 414 | // vec_from_angle(TAU); 415 | // }, 416 | // EmitShape::Circle(c) => { 417 | // // From 0.0 out to where it will spawn 418 | 419 | // }, 420 | 421 | // } 422 | // }, 423 | } 424 | } 425 | } 426 | 427 | #[derive(Clone, Copy)] 428 | pub enum ValueGetter { 429 | Single(T), 430 | Range(T, T), 431 | } 432 | 433 | // Todo: Implement range, randomization 434 | impl ValueGetter { 435 | pub fn get(&self) -> ggez::graphics::Color { 436 | match *self { 437 | ValueGetter::Single(v) => v, 438 | ValueGetter::Range(v1, v2) => { 439 | let (low_r, low_g, low_b) = v1.into(); 440 | let (high_r, high_g, high_b) = v2.into(); 441 | // gen_range doesn't support u8 in good-web-easy 442 | let r = rand::gen_range(low_r as i32, high_r as i32) as u8; 443 | let g = rand::gen_range(low_g as i32, high_g as i32) as u8; 444 | let b = rand::gen_range(low_b as i32, high_b as i32) as u8; 445 | (r, g, b).into() 446 | } 447 | } 448 | } 449 | } 450 | 451 | impl ValueGetter { 452 | pub fn get(&self) -> f32 { 453 | match *self { 454 | ValueGetter::Single(v) => v, 455 | ValueGetter::Range(v1, v2) => rand::gen_range(v1, v2), 456 | } 457 | } 458 | pub fn max(&self) -> f32 { 459 | match *self { 460 | ValueGetter::Single(v) => v, 461 | ValueGetter::Range(_v1, v2) => v2, 462 | } 463 | } 464 | } 465 | 466 | // Manage multiple systems 467 | pub struct ParticleSystemCollection { 468 | particle_systems: HashMap, 469 | last_identifier: u32, 470 | } 471 | 472 | impl ParticleSystemCollection { 473 | pub fn new() -> Self { 474 | ParticleSystemCollection { 475 | particle_systems: HashMap::new(), 476 | last_identifier: 0, 477 | } 478 | } 479 | 480 | pub fn update(&mut self, delta: f32) { 481 | for (_identifier, system) in self.particle_systems.iter_mut() { 482 | system.update(delta); 483 | } 484 | } 485 | 486 | pub fn add_system(&mut self, system: ParticleSystem) -> u32 { 487 | self.last_identifier += 1; 488 | self.particle_systems.insert(self.last_identifier, system); 489 | self.last_identifier 490 | } 491 | 492 | pub fn get_mut(&mut self, identifier: u32) -> Option<&mut ParticleSystem> { 493 | if let Some(system) = self.particle_systems.get_mut(&identifier) { 494 | return Some(system); 495 | } 496 | None 497 | } 498 | 499 | // returns if system is still valid 500 | pub fn emit(&mut self, system_identifier: u32, amount: i32) -> bool { 501 | if let Some(system) = self.particle_systems.get_mut(&system_identifier) { 502 | system.emit(amount); 503 | return true; 504 | } 505 | false 506 | } 507 | 508 | pub fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { 509 | for (_id, system) in self.particle_systems.iter_mut() { 510 | system.draw(ctx)?; 511 | } 512 | Ok(()) 513 | } 514 | } 515 | 516 | pub fn emit_step_particle( 517 | particle_collection: &mut ParticleSystemCollection, 518 | step_id: &u32, 519 | amount: i32, 520 | is_right_dir: bool, 521 | position: &na::Point2, 522 | screen_size: &na::Point2, 523 | ) { 524 | let step_particle = particle_collection.get_mut(*step_id).unwrap(); 525 | let mut pos_particle = na::Vector2::new( 526 | position.x as f32 / screen_size.x * 16.0, 527 | position.y as f32 / screen_size.x * 16.0, 528 | ); 529 | 530 | if is_right_dir { 531 | step_particle.velocity_type = VelocityType::Angle(AngleData::new(-PI * 0.8, Some(0.2))); 532 | } else { 533 | step_particle.velocity_type = VelocityType::Angle(AngleData::new(PI * 0.8, Some(0.2))); 534 | } 535 | step_particle.scale = screen_size.x / 16.0; 536 | // offset to under player 537 | pos_particle.x += 16.0 * 0.5; 538 | pos_particle.y += 16.0; 539 | step_particle.position = pos_particle; 540 | 541 | step_particle.emit(amount); 542 | } 543 | -------------------------------------------------------------------------------- /src/sound_collection.rs: -------------------------------------------------------------------------------- 1 | use gwg as ggez; 2 | 3 | use ggez::{audio, GameResult}; 4 | 5 | pub struct SoundCollection { 6 | pub sounds: [audio::Source; 10], 7 | pub is_on: bool, 8 | } 9 | 10 | impl SoundCollection { 11 | pub fn play(&mut self, index: usize) -> GameResult<()> { 12 | if !self.is_on { 13 | return Ok(()); 14 | } 15 | if let Some(source) = self.sounds.get_mut(index) { 16 | source.play()?; 17 | } 18 | Err(ggez::error::GameError::SoundError) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/sprite.rs: -------------------------------------------------------------------------------- 1 | use crate::transform_compontent::TransformComponent; 2 | use gwg as ggez; 3 | use nalgebra as na; 4 | 5 | use crate::constantes; 6 | use crate::util; 7 | use ggez::{graphics, Context, GameResult}; 8 | use graphics::DrawParam; 9 | pub struct SpriteComponent { 10 | pub texture_index: usize, 11 | pub scale: na::Vector2, 12 | pub is_flipped: bool, 13 | pub visual_position: na::Point2, 14 | pub blink_timer: f32, 15 | } 16 | 17 | impl Default for SpriteComponent { 18 | fn default() -> Self { 19 | SpriteComponent { 20 | texture_index: 0, 21 | scale: na::Vector2::new(1.0, 1.0), 22 | is_flipped: false, 23 | visual_position: na::Point2::new(0.0, 0.0), 24 | blink_timer: 0.0, 25 | } 26 | } 27 | } 28 | pub struct SpriteCollection { 29 | pub images: [graphics::Image; 21], 30 | } 31 | 32 | impl SpriteCollection { 33 | pub fn get_sprite<'a, 'b: 'a>(&'a self, index: usize) -> Option<&'a graphics::Image> { 34 | self.images.get(index) 35 | } 36 | } 37 | 38 | pub fn render( 39 | sprite_collection: &SpriteCollection, 40 | ctx: &mut Context, 41 | transform_component: &TransformComponent, 42 | sprite: &mut SpriteComponent, 43 | screen_size: &na::Point2, 44 | ) -> GameResult { 45 | let mut offset = mint::Point2 { x: 0.0, y: 0.0 }; 46 | let final_scale = sprite.scale.x * screen_size.x; 47 | let mut flip_scale: f32 = 1.0; 48 | if sprite.is_flipped { 49 | flip_scale = -1.0; 50 | offset.x = 1.0; 51 | } 52 | let delta_time = ggez::timer::delta(ctx).as_secs_f32(); 53 | let target_position = 54 | na::convert::, na::Point2>(transform_component.position) * final_scale; 55 | sprite.visual_position.x = util::lerp( 56 | sprite.visual_position.x, 57 | target_position.x, 58 | delta_time * constantes::TIME_VISUAL_LERP, 59 | ); 60 | sprite.visual_position.y = util::lerp( 61 | sprite.visual_position.y, 62 | target_position.y, 63 | delta_time * constantes::TIME_VISUAL_LERP, 64 | ); 65 | 66 | //let dest = na::convert::, na::Point2::>(transform_component.position) * final_scale; 67 | let dest = sprite.visual_position; 68 | let mut params = DrawParam::default() 69 | .offset(offset) 70 | .scale(na::Vector2::::new( 71 | flip_scale * final_scale / 16.0, 72 | final_scale / 16.0, 73 | )) 74 | .dest(dest); 75 | 76 | if sprite.blink_timer > 0.0 { 77 | sprite.blink_timer -= delta_time; 78 | let fraction = (sprite.blink_timer / constantes::TIME_BLINK).sin() * 0.5 + 0.5; 79 | let new_color = graphics::Color::new( 80 | constantes::COLOR_BLINK.r * fraction, 81 | constantes::COLOR_BLINK.g * fraction, 82 | constantes::COLOR_BLINK.b * fraction, 83 | 1.0, 84 | ); 85 | params = params.color(new_color); 86 | } 87 | 88 | let image = sprite_collection 89 | .images 90 | .get(sprite.texture_index) 91 | .expect("No image with id..."); 92 | graphics::draw(ctx, image, params)?; 93 | Ok(()) 94 | } 95 | -------------------------------------------------------------------------------- /src/states.rs: -------------------------------------------------------------------------------- 1 | pub mod game_state; 2 | pub mod main_state; 3 | -------------------------------------------------------------------------------- /src/states/game_state.rs: -------------------------------------------------------------------------------- 1 | use crate::entities::player::Player; 2 | use crate::entities::{ 3 | cloud, foilage, skeleton, 4 | teleporter::{Exit, Teleporter}, 5 | }; 6 | use cloud::Cloud; 7 | use foilage::{Foilage, Grass}; 8 | use ggez::{graphics, Context}; 9 | use gwg as ggez; 10 | use nalgebra as na; 11 | use skeleton::{Skeleton, SkeletonBlock}; 12 | 13 | pub struct GameState { 14 | pub player: Player, 15 | pub grasses: Vec, 16 | pub skeleton_blocks: Vec, 17 | pub skeletons: Vec, 18 | pub foilages: Vec, 19 | pub clouds: Vec, 20 | pub teleporters: [Option; 2], 21 | pub exit: Exit, 22 | pub map_size: na::Point2, 23 | pub game_over_text: ggez::graphics::Text, 24 | pub all_levels_completed_text: ggez::graphics::Text, 25 | pub is_all_levels_completed: bool, 26 | } 27 | 28 | impl GameState { 29 | pub fn new(ctx: &mut Context) -> GameState { 30 | let font = graphics::Font::new(ctx, "kenny_fontpackage/Fonts/Kenney Mini.ttf").unwrap(); 31 | let game_over_text = graphics::Text::new(("PRESS (R) to restart!", font, 60.0)); 32 | let all_levels_completed_text = graphics::Text::new(( 33 | "You completed ALL LEVELS! Press R to play again", 34 | font, 35 | 30.0, 36 | )); 37 | 38 | GameState { 39 | game_over_text, 40 | all_levels_completed_text, 41 | map_size: na::Point2::new(0.0, 0.0), 42 | player: Player::default(), 43 | grasses: vec![], 44 | skeleton_blocks: vec![], 45 | skeletons: vec![], 46 | foilages: vec![], 47 | clouds: vec![], 48 | teleporters: [None, None], 49 | exit: Exit::default(), 50 | is_all_levels_completed: false, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/states/main_state.rs: -------------------------------------------------------------------------------- 1 | use crate::constantes; 2 | use crate::entities::skeleton; 3 | use crate::particle_system::{ 4 | AngleData, ParticleSystem, ParticleSystemCollection, ValueGetter, VelocityType, 5 | }; 6 | use crate::sound_collection::SoundCollection; 7 | use crate::sprite::{self, SpriteCollection}; 8 | use crate::states::game_state::GameState; 9 | use crate::util; 10 | use crate::{ 11 | entities::{cloud, foilage, player}, 12 | map, 13 | }; 14 | use event::KeyCode; 15 | use ggez::{audio, event, graphics, Context, GameResult}; 16 | use graphics::{DrawParam, FilterMode, draw}; 17 | use gwg as ggez; 18 | use gwg::input::keyboard::KeyMods; 19 | use nalgebra as na; 20 | use player::PlayerInputIntent; 21 | 22 | pub struct MainState { 23 | pub game_state: GameState, 24 | pub sprite_collection: SpriteCollection, 25 | pub sound_collection: SoundCollection, 26 | pub current_map: usize, 27 | pub screen_size: na::Point2, 28 | pub black_border_left: Option, 29 | pub black_border_right: Option, 30 | pub particle_systems: ParticleSystemCollection, 31 | // Particle system ids 32 | pub grass_id: u32, 33 | pub step_id: u32, 34 | pub blood_id: u32, 35 | pub land_id: u32, 36 | pub foilage_1_id: u32, 37 | pub foilage_2_id: u32, 38 | pub foilage_3_id: u32, 39 | pub foilage_4_id: u32, 40 | pub mouse_pos_down: na::Vector2, 41 | } 42 | 43 | impl MainState { 44 | pub fn new(ctx: &mut Context) -> GameResult { 45 | let mut images = [ 46 | graphics::Image::new(ctx, "textures/player.png")?, 47 | graphics::Image::new(ctx, "textures/ground.png")?, 48 | graphics::Image::new(ctx, "textures/unburied.png")?, 49 | graphics::Image::new(ctx, "textures/buried.png")?, 50 | graphics::Image::new(ctx, "textures/skeleton_neutral.png")?, 51 | graphics::Image::new(ctx, "textures/blue_door.png")?, 52 | graphics::Image::new(ctx, "textures/red_door.png")?, 53 | graphics::Image::new(ctx, "textures/skeleton_attack.png")?, 54 | graphics::Image::new(ctx, "textures/player_dig.png")?, 55 | graphics::Image::new(ctx, "textures/player_dead.png")?, 56 | graphics::Image::new(ctx, "textures/ground_below.png")?, 57 | graphics::Image::new(ctx, "textures/sound_on.png")?, 58 | graphics::Image::new(ctx, "textures/sound_off.png")?, 59 | graphics::Image::new(ctx, "textures/player_fall.png")?, 60 | graphics::Image::new(ctx, "textures/foilage_1.png")?, 61 | graphics::Image::new(ctx, "textures/foilage_2.png")?, 62 | graphics::Image::new(ctx, "textures/foilage_3.png")?, 63 | graphics::Image::new(ctx, "textures/foilage_4.png")?, 64 | graphics::Image::new(ctx, "textures/cloud_1.png")?, 65 | graphics::Image::new(ctx, "textures/cloud_2.png")?, 66 | graphics::Image::new(ctx, "textures/cloud_3.png")?, 67 | ]; 68 | 69 | for img in &mut images { 70 | img.set_filter(FilterMode::Nearest); 71 | } 72 | 73 | let sprite_collection = SpriteCollection { images }; 74 | 75 | let sounds = [ 76 | audio::Source::new(ctx, "sounds/player_walk.wav")?, 77 | audio::Source::new(ctx, "sounds/player_dig.wav")?, 78 | audio::Source::new(ctx, "sounds/player_hit.wav")?, 79 | audio::Source::new(ctx, "sounds/player_teleport.wav")?, 80 | audio::Source::new(ctx, "sounds/level_completed.wav")?, 81 | audio::Source::new(ctx, "sounds/skeleton_attack.wav")?, 82 | audio::Source::new(ctx, "sounds/door_locked.wav")?, 83 | audio::Source::new(ctx, "sounds/player_fall.wav")?, 84 | audio::Source::new(ctx, "sounds/player_land.wav")?, 85 | audio::Source::new(ctx, "sounds/level_restarted.wav")?, 86 | ]; 87 | let sound_collection = SoundCollection { 88 | is_on: true, 89 | sounds, 90 | }; 91 | let mut particle_systems = ParticleSystemCollection::new(); 92 | let mut grass_particle_system = ParticleSystem::new(ctx, None); 93 | grass_particle_system.start_color = ValueGetter::Range( 94 | (82.0 / 255.0, 166.0 / 255.0, 32.0 / 255.0).into(), 95 | (66.0 / 255.0, 54.0 / 255.0, 39.0 / 255.0).into(), 96 | ); 97 | 98 | grass_particle_system.velocity_type = 99 | VelocityType::Angle(AngleData::new(constantes::PI, Some(0.4))); 100 | grass_particle_system.start_speed = ValueGetter::Range(2.0, 3.0); 101 | grass_particle_system.start_lifetime = ValueGetter::Range(0.4, 0.5); 102 | grass_particle_system.start_scale = ValueGetter::Range(2.0, 3.4); 103 | grass_particle_system.start_angular_velocity = ValueGetter::Range(2.0, 30.4); 104 | 105 | let mut step_particle_system = ParticleSystem::new(ctx, None); 106 | step_particle_system.start_lifetime = ValueGetter::Range(0.2, 0.3); 107 | step_particle_system.start_scale = ValueGetter::Range(1.0, 2.4); 108 | step_particle_system.start_color = 109 | ValueGetter::Single((82.0 / 255.0, 166.0 / 255.0, 32.0 / 255.0).into()); 110 | 111 | let mut blood_particle_system = ParticleSystem::new(ctx, None); 112 | blood_particle_system.start_lifetime = ValueGetter::Range(0.3, 0.5); 113 | blood_particle_system.start_scale = ValueGetter::Range(1.0, 10.4); 114 | blood_particle_system.start_color = ValueGetter::Single(constantes::COLOR_BLOOD); 115 | 116 | let mut land_particle_system = ParticleSystem::new(ctx, None); 117 | land_particle_system.start_lifetime = ValueGetter::Range(0.3, 0.5); 118 | land_particle_system.start_speed = ValueGetter::Range(0.3, 1.5); 119 | land_particle_system.start_scale = ValueGetter::Range(1.0, 4.4); 120 | land_particle_system.gravity = -1.0; 121 | land_particle_system.start_color = ValueGetter::Single(ggez::graphics::WHITE); 122 | 123 | // Foilage particles 124 | let foilage_1_image = sprite_collection 125 | .images 126 | .get(14) 127 | .expect("no foilage 1 image"); 128 | let foilage_2_image = sprite_collection 129 | .images 130 | .get(15) 131 | .expect("no foilage 2 image"); 132 | let foilage_3_image = sprite_collection 133 | .images 134 | .get(16) 135 | .expect("no foilage 3 image"); 136 | let foilage_4_image = sprite_collection 137 | .images 138 | .get(17) 139 | .expect("no foilage 4 image"); 140 | 141 | let mut foilage_1_particle_system = ParticleSystem::new(ctx, Some(foilage_1_image.clone())); 142 | foilage_1_particle_system.start_color = ValueGetter::Single(graphics::WHITE); 143 | foilage_1_particle_system.velocity_type = 144 | VelocityType::Angle(AngleData::new(constantes::PI, Some(0.1))); 145 | foilage_1_particle_system.start_speed = ValueGetter::Range(2.0, 7.0); 146 | foilage_1_particle_system.end_scale = 3.0; 147 | foilage_1_particle_system.start_lifetime = ValueGetter::Single(6.0); 148 | foilage_1_particle_system.start_scale = ValueGetter::Single(1.0); 149 | 150 | let mut foilage_2_particle_system = ParticleSystem::new(ctx, Some(foilage_2_image.clone())); 151 | let mut foilage_3_particle_system = ParticleSystem::new(ctx, Some(foilage_3_image.clone())); 152 | let mut foilage_4_particle_system = ParticleSystem::new(ctx, Some(foilage_4_image.clone())); 153 | foilage_2_particle_system.copy_settings(&foilage_1_particle_system); 154 | foilage_3_particle_system.copy_settings(&foilage_1_particle_system); 155 | foilage_4_particle_system.copy_settings(&foilage_1_particle_system); 156 | 157 | let grass_id = particle_systems.add_system(grass_particle_system); 158 | let step_id = particle_systems.add_system(step_particle_system); 159 | let blood_id = particle_systems.add_system(blood_particle_system); 160 | let land_id = particle_systems.add_system(land_particle_system); 161 | let foilage_1_id = particle_systems.add_system(foilage_1_particle_system); 162 | let foilage_2_id = particle_systems.add_system(foilage_2_particle_system); 163 | let foilage_3_id = particle_systems.add_system(foilage_3_particle_system); 164 | let foilage_4_id = particle_systems.add_system(foilage_4_particle_system); 165 | 166 | let game_state = GameState::new(ctx); 167 | let mut main_state = MainState { 168 | sprite_collection, 169 | sound_collection, 170 | game_state, 171 | current_map: 0, 172 | screen_size: na::Point2::new(0.0, 0.0), 173 | black_border_left: None, 174 | black_border_right: None, 175 | particle_systems, 176 | grass_id, 177 | step_id, 178 | blood_id, 179 | land_id, 180 | foilage_1_id, 181 | foilage_2_id, 182 | foilage_3_id, 183 | foilage_4_id, 184 | mouse_pos_down: na::Vector2::new(0.0, 0.0), 185 | }; 186 | 187 | use ggez::event::EventHandler; 188 | let (w, h) = ggez::graphics::size(ctx); 189 | main_state.resize_event(ctx, w, h); 190 | audio::maybe_create_soundmixer(ctx); 191 | 192 | map::load_map(ctx, &mut main_state.game_state, 0, &main_state.screen_size); 193 | Ok(main_state) 194 | } 195 | pub fn restart_current_map(&mut self, ctx: &mut Context) { 196 | if self.game_state.is_all_levels_completed { 197 | self.current_map = 0; 198 | self.game_state.is_all_levels_completed = false; 199 | } 200 | map::clear_map(&mut self.game_state); 201 | map::load_map( 202 | ctx, 203 | &mut self.game_state, 204 | self.current_map, 205 | &self.screen_size, 206 | ); 207 | self.sound_collection.play(9); 208 | } 209 | } 210 | 211 | impl event::EventHandler for MainState { 212 | fn update(&mut self, ctx: &mut Context) -> GameResult { 213 | let delta = ggez::timer::delta(ctx).as_secs_f32(); 214 | if self.game_state.is_all_levels_completed { 215 | return Ok(()); 216 | } 217 | self.particle_systems.update(delta); 218 | 219 | cloud::update(&mut self.game_state, ctx); 220 | 221 | let player = &mut self.game_state.player; 222 | let should_step = player.should_step( 223 | delta, 224 | &self.game_state.grasses, 225 | &self.game_state.skeletons, 226 | &self.game_state.skeleton_blocks, 227 | &mut self.sound_collection, 228 | &mut self.particle_systems, 229 | &self.land_id, 230 | &self.screen_size, 231 | ); 232 | 233 | if should_step { 234 | player::system( 235 | &mut self.game_state, 236 | ctx, 237 | &mut self.current_map, 238 | &mut self.sound_collection, 239 | &self.screen_size, 240 | &mut self.particle_systems, 241 | &self.grass_id, 242 | &self.step_id, 243 | &self.foilage_1_id, 244 | &self.foilage_2_id, 245 | &self.foilage_3_id, 246 | &self.foilage_4_id, 247 | ); 248 | 249 | skeleton::system( 250 | &mut self.game_state, 251 | ctx, 252 | &mut self.sound_collection, 253 | &mut self.particle_systems, 254 | &self.blood_id, 255 | &self.screen_size, 256 | ); 257 | 258 | skeleton::block_system(&mut self.game_state, &mut self.sound_collection); 259 | } 260 | Ok(()) 261 | } 262 | 263 | fn draw(&mut self, ctx: &mut Context) -> GameResult { 264 | graphics::clear(ctx, constantes::CLEAR_COLOR); 265 | render_system( 266 | &mut self.game_state, 267 | &self.sprite_collection, 268 | ctx, 269 | &self.screen_size, 270 | &self.sound_collection, 271 | &self.black_border_left, 272 | &self.black_border_right, 273 | ); 274 | self.particle_systems.draw(ctx).unwrap(); 275 | graphics::present(ctx)?; 276 | Ok(()) 277 | } 278 | 279 | fn key_down_event( 280 | &mut self, 281 | ctx: &mut Context, 282 | keycode: KeyCode, 283 | _keymod: KeyMods, 284 | repeat: bool, 285 | ) { 286 | if repeat { 287 | return; 288 | } 289 | 290 | let intent = match keycode { 291 | KeyCode::Right | KeyCode::D => PlayerInputIntent::Right, 292 | KeyCode::Left | KeyCode::A => PlayerInputIntent::Left, 293 | KeyCode::Down | KeyCode::S => PlayerInputIntent::Down, 294 | KeyCode::Up | KeyCode::W => PlayerInputIntent::Up, 295 | _ => PlayerInputIntent::None, 296 | }; 297 | 298 | self.game_state.player.input_intent = intent; 299 | match keycode { 300 | KeyCode::R => { 301 | self.restart_current_map(ctx); 302 | } 303 | KeyCode::M => { 304 | self.sound_collection.is_on = !self.sound_collection.is_on; 305 | } 306 | _ => {} 307 | } 308 | } 309 | 310 | fn resize_event(&mut self, ctx: &mut Context, w: f32, h: f32) { 311 | // This scaling code is a mess, send halp 312 | let map_w = 10.0; 313 | let map_h = 8.0; 314 | let sprite_scale = (h / map_h).min(w / map_w); 315 | self.screen_size.x = sprite_scale; 316 | self.screen_size.y = sprite_scale; 317 | let offset_x = (w - map_w * sprite_scale) * 0.5; 318 | let offset_y = (h - map_h * sprite_scale) * 0.5; 319 | graphics::set_screen_coordinates( 320 | ctx, 321 | ggez::graphics::Rect::new(-offset_x, -offset_y, w, h), 322 | ).unwrap(); 323 | util::force_visual_positions(&mut self.game_state, &self.screen_size); 324 | 325 | let border_width = (w - (map_w * sprite_scale)) * 0.5; 326 | let border_height = h; 327 | let border_y = 0.0; 328 | let left_pos = -border_width; 329 | let right_pos = w - border_width * 2.0; 330 | util::update_borders( 331 | ctx, 332 | &mut self.black_border_left, 333 | &mut self.black_border_right, 334 | border_width, 335 | border_height, 336 | left_pos, 337 | right_pos, 338 | border_y, 339 | ); 340 | } 341 | 342 | fn mouse_button_down_event( 343 | &mut self, 344 | _ctx: &mut Context, 345 | _button: ggez::event::MouseButton, 346 | x: f32, 347 | y: f32, 348 | ) { 349 | self.mouse_pos_down = na::Vector2::new(x, y); 350 | } 351 | 352 | fn mouse_button_up_event( 353 | &mut self, 354 | ctx: &mut Context, 355 | _button: ggez::event::MouseButton, 356 | x: f32, 357 | y: f32, 358 | ) { 359 | let current_pos = na::Point2::new(x, y); 360 | // Mute button 361 | let screen_rect = ggez::graphics::screen_coordinates(ctx); 362 | let volume_rect = ggez::graphics::Rect::new(-screen_rect.x, -screen_rect.y, 64.0, 64.0); 363 | if volume_rect.contains(current_pos) { 364 | self.sound_collection.is_on = !self.sound_collection.is_on; 365 | } 366 | 367 | let current_pos: na::Vector2 = na::Vector2::new(current_pos.x, current_pos.y); 368 | let delta = current_pos - self.mouse_pos_down; 369 | 370 | // Restart input Currently tap anywhere on screen if delta is below move action 371 | if !self.game_state.player.is_alive && delta.norm() < constantes::TOUCH_MIN_DELTA { 372 | self.restart_current_map(ctx); 373 | } 374 | 375 | // touch input 376 | let mut input_intent = PlayerInputIntent::None; 377 | if delta.norm() > constantes::TOUCH_MIN_DELTA { 378 | let x_diff = delta.x.abs(); 379 | let y_diff = delta.y.abs(); 380 | if x_diff > y_diff { 381 | if delta.x > 0.0 { 382 | input_intent = PlayerInputIntent::Right; 383 | } else { 384 | input_intent = PlayerInputIntent::Left; 385 | } 386 | } else { 387 | if delta.y > 0.0 { 388 | input_intent = PlayerInputIntent::Down; 389 | } else { 390 | input_intent = PlayerInputIntent::Up; 391 | } 392 | } 393 | } 394 | self.game_state.player.input_intent = input_intent; 395 | } 396 | } 397 | fn render_game( 398 | game_state: &mut GameState, 399 | sprite_collection: &SpriteCollection, 400 | ctx: &mut Context, 401 | screen_size: &na::Point2, 402 | _sound_collection: &SoundCollection, 403 | ) { 404 | cloud::render(game_state, sprite_collection, ctx, screen_size).unwrap(); 405 | sprite::render( 406 | sprite_collection, 407 | ctx, 408 | &game_state.exit.transform, 409 | &mut game_state.exit.sprite, 410 | screen_size, 411 | ).unwrap(); 412 | for grass in &mut game_state.grasses { 413 | sprite::render( 414 | sprite_collection, 415 | ctx, 416 | &grass.transform, 417 | &mut grass.sprite, 418 | screen_size, 419 | ).unwrap(); 420 | } 421 | for skeleton_block in &mut game_state.skeleton_blocks { 422 | sprite::render( 423 | sprite_collection, 424 | ctx, 425 | &skeleton_block.transform, 426 | &mut skeleton_block.sprite, 427 | screen_size, 428 | ).unwrap(); 429 | } 430 | for teleporter_option in game_state.teleporters.iter_mut().map(|t| t.as_mut()) { 431 | if let Some(teleporter) = teleporter_option { 432 | sprite::render( 433 | sprite_collection, 434 | ctx, 435 | &teleporter.transform, 436 | &mut teleporter.sprite, 437 | screen_size, 438 | ).unwrap(); 439 | } 440 | } 441 | for skeleton in game_state.skeletons.iter_mut() { 442 | sprite::render( 443 | sprite_collection, 444 | ctx, 445 | &skeleton.transform, 446 | &mut skeleton.sprite, 447 | screen_size, 448 | ).unwrap(); 449 | } 450 | sprite::render( 451 | sprite_collection, 452 | ctx, 453 | &game_state.player.transform, 454 | &mut game_state.player.sprite, 455 | screen_size, 456 | ).unwrap(); 457 | foilage::render(game_state, sprite_collection, ctx, screen_size).unwrap(); 458 | } 459 | 460 | fn render_system( 461 | game_state: &mut GameState, 462 | sprite_collection: &SpriteCollection, 463 | ctx: &mut Context, 464 | screen_size: &na::Point2, 465 | sound_collection: &SoundCollection, 466 | left_border: &Option, 467 | right_border: &Option, 468 | ) { 469 | render_background(ctx, screen_size); 470 | 471 | if game_state.is_all_levels_completed { 472 | render_all_levels_completed(game_state, ctx, screen_size).unwrap(); 473 | } else { 474 | render_game( 475 | game_state, 476 | sprite_collection, 477 | ctx, 478 | screen_size, 479 | sound_collection, 480 | ); 481 | render_game_over(game_state, ctx, screen_size).unwrap(); 482 | } 483 | util::render_border(ctx, left_border).unwrap(); 484 | util::render_border(ctx, right_border).unwrap(); 485 | render_sound_button(ctx, sprite_collection, sound_collection); 486 | } 487 | 488 | fn render_background(ctx: &mut Context, screen_size: &na::Point2) { 489 | let screen_coordinates = ggez::graphics::screen_coordinates(ctx); 490 | let rect = graphics::Rect::new( 491 | -screen_coordinates.x * 0.0, 492 | 0.0, 493 | screen_size.x * 10.0, 494 | screen_size.x * 8.0, 495 | ); 496 | let rect_mesh = graphics::Mesh::new_rectangle( 497 | ctx, 498 | graphics::DrawMode::fill(), 499 | rect, 500 | constantes::BACKGROUND_GAME, 501 | ) 502 | .unwrap(); 503 | draw(ctx, &rect_mesh, DrawParam::default()).unwrap(); 504 | } 505 | 506 | fn render_sound_button( 507 | ctx: &mut Context, 508 | sprite_collection: &SpriteCollection, 509 | sound_collection: &SoundCollection, 510 | ) { 511 | let params = DrawParam::default() 512 | //.scale(na::Vector2::::new(flip_scale * final_scale / 16.0, final_scale / 16.0)) 513 | .dest(na::Point2::new(0.0, 0.0)); 514 | let image_index = match sound_collection.is_on { 515 | true => 11, 516 | false => 12, 517 | }; 518 | let image = sprite_collection 519 | .images 520 | .get(image_index) 521 | .expect("No image with id..."); 522 | draw(ctx, image, params).unwrap(); 523 | } 524 | 525 | fn render_game_over( 526 | game_state: &mut GameState, 527 | ctx: &mut Context, 528 | screen_size: &na::Point2, 529 | ) -> GameResult { 530 | if !game_state.player.is_alive { 531 | let time_since_start = ggez::timer::time_since_start(ctx).as_secs_f32(); 532 | let distance = 20.0; 533 | let speed = 5.0; 534 | let offset_y = (time_since_start * speed).sin() * distance; 535 | let offset_x = (time_since_start * speed).cos() * distance; 536 | util::render_text( 537 | &game_state.game_over_text, 538 | ctx, 539 | screen_size, 540 | na::Vector2::new(offset_x, offset_y), 541 | )?; 542 | } 543 | Ok(()) 544 | } 545 | 546 | fn render_all_levels_completed( 547 | game_state: &mut GameState, 548 | ctx: &mut Context, 549 | screen_size: &na::Point2, 550 | ) -> GameResult { 551 | if true { 552 | util::render_text( 553 | &game_state.all_levels_completed_text, 554 | ctx, 555 | screen_size, 556 | na::Vector2::new(0.0, 0.0), 557 | )?; 558 | } 559 | Ok(()) 560 | } 561 | -------------------------------------------------------------------------------- /src/transform_compontent.rs: -------------------------------------------------------------------------------- 1 | use nalgebra as na; 2 | pub struct TransformComponent { 3 | pub position: na::Point2, 4 | } 5 | 6 | impl Default for TransformComponent { 7 | fn default() -> Self { 8 | TransformComponent { 9 | position: na::Point2::new(0, 0), 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::constantes; 2 | use crate::states::game_state::GameState; 3 | use ggez::{graphics, Context, GameResult}; 4 | use graphics::DrawParam; 5 | use gwg as ggez; 6 | use nalgebra as na; 7 | 8 | pub fn lerp(from: f32, to: f32, dt: f32) -> f32 { 9 | return from + dt * (to - from); 10 | } 11 | 12 | pub fn render_text( 13 | text: &graphics::Text, 14 | ctx: &mut Context, 15 | screen_size: &na::Point2, 16 | offset: na::Vector2, 17 | ) -> GameResult { 18 | let size_x = screen_size.x * 10.0; 19 | let size_y = screen_size.x * 8.0; 20 | let mut pos_centered = na::Point2::new(size_x * 0.5, size_y * 0.5); 21 | let (text_w, text_h) = text.dimensions(ctx); 22 | let padding_scale = 1.0 - constantes::TEXT_PADDING_SIZE; 23 | let scale = (size_x / text_w) * padding_scale; 24 | pos_centered.x -= text_w as f32 * 0.5 * scale; 25 | pos_centered.y -= text_h as f32 * 0.5 * scale; 26 | 27 | let draw_param = DrawParam { 28 | dest: (pos_centered + offset).into(), 29 | offset: mint::Point2 { x: 0.0, y: 0.0 }, 30 | color: graphics::WHITE, 31 | scale: mint::Vector2 { x: scale, y: scale }, 32 | ..Default::default() 33 | }; 34 | graphics::draw(ctx, text, draw_param)?; 35 | Ok(()) 36 | } 37 | 38 | pub fn force_visual_positions(game_state: &mut GameState, screen_size: &na::Point2) { 39 | let mut position: na::Point2; 40 | for grasses in game_state.grasses.iter_mut() { 41 | position = na::convert::, na::Point2>(grasses.transform.position); 42 | grasses.sprite.visual_position = position * screen_size.x; 43 | } 44 | for skeleton_block in game_state.skeleton_blocks.iter_mut() { 45 | position = 46 | na::convert::, na::Point2>(skeleton_block.transform.position); 47 | skeleton_block.sprite.visual_position = position * screen_size.x; 48 | } 49 | for teleporter_option in game_state.teleporters.iter_mut() { 50 | if let Some(teleporter) = teleporter_option { 51 | position = 52 | na::convert::, na::Point2>(teleporter.transform.position); 53 | teleporter.sprite.visual_position = position * screen_size.x; 54 | } 55 | } 56 | { 57 | position = 58 | na::convert::, na::Point2>(game_state.exit.transform.position); 59 | game_state.exit.sprite.visual_position = position * screen_size.x; 60 | } 61 | } 62 | 63 | pub struct BlackBorder { 64 | mesh: graphics::Mesh, 65 | draw_param: graphics::DrawParam, 66 | } 67 | 68 | pub fn render_border(ctx: &mut Context, border: &Option) -> GameResult { 69 | match border { 70 | Some(b) => graphics::draw(ctx, &b.mesh, b.draw_param)?, 71 | None => {} 72 | } 73 | Ok(()) 74 | } 75 | 76 | pub fn update_borders( 77 | ctx: &mut Context, 78 | left: &mut Option, 79 | right: &mut Option, 80 | w: f32, 81 | h: f32, 82 | left_x: f32, 83 | right_x: f32, 84 | border_y: f32, 85 | ) { 86 | let left_rect = graphics::Rect::new(left_x, border_y, w, h); 87 | let right_rect = graphics::Rect::new(right_x, border_y, w, h); 88 | let left_mesh_result = graphics::Mesh::new_rectangle( 89 | ctx, 90 | graphics::DrawMode::fill(), 91 | left_rect, 92 | constantes::CLEAR_COLOR, 93 | ); 94 | 95 | let right_mesh_result = graphics::Mesh::new_rectangle( 96 | ctx, 97 | graphics::DrawMode::fill(), 98 | right_rect, 99 | constantes::CLEAR_COLOR, 100 | ); 101 | 102 | if let Ok(left_mesh) = left_mesh_result { 103 | *left = Some(BlackBorder { 104 | mesh: left_mesh, 105 | draw_param: DrawParam::default(), 106 | }); 107 | } 108 | if let Ok(right_mesh) = right_mesh_result { 109 | *right = Some(BlackBorder { 110 | mesh: right_mesh, 111 | draw_param: DrawParam::default(), 112 | }); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /utils/wasm/audio.js: -------------------------------------------------------------------------------- 1 | var ctx = null; 2 | var buffer_size = 0; 3 | 4 | register_plugin = function (importObject) { 5 | importObject.env.audio_init = function (audio_buffer_size) { 6 | if (ctx != null) { 7 | return; 8 | } 9 | window.startPause = 0; 10 | window.endPause = 0; 11 | document.addEventListener("visibilitychange", (e) => { 12 | if (document.hidden) { 13 | window.startPause = performance.now() / 1000.0; 14 | } else { 15 | window.endPause = performance.now() / 1000.0; 16 | } 17 | }, false); 18 | 19 | // https://gist.github.com/kus/3f01d60569eeadefe3a1 20 | { 21 | audioContext = window.AudioContext || window.webkitAudioContext; 22 | ctx = new audioContext(); 23 | var fixAudioContext = function (e) { 24 | console.log("fix"); 25 | 26 | // Create empty buffer 27 | var buffer = ctx.createBuffer(1, 1, 22050); 28 | var source = ctx.createBufferSource(); 29 | source.buffer = buffer; 30 | // Connect to output (speakers) 31 | source.connect(ctx.destination); 32 | // Play sound 33 | if (source.start) { 34 | source.start(0); 35 | } else if (source.play) { 36 | source.play(0); 37 | } else if (source.noteOn) { 38 | source.noteOn(0); 39 | } 40 | 41 | // Remove events 42 | document.removeEventListener('touchstart', fixAudioContext); 43 | document.removeEventListener('touchend', fixAudioContext); 44 | document.removeEventListener('mousedown', fixAudioContext); 45 | }; 46 | // iOS 6-8 47 | document.addEventListener('touchstart', fixAudioContext); 48 | // iOS 9 49 | document.addEventListener('touchend', fixAudioContext); 50 | 51 | document.addEventListener('mousedown', fixAudioContext); 52 | } 53 | 54 | buffer_size = audio_buffer_size; 55 | 56 | if (ctx) { 57 | return true; 58 | } else { 59 | return false; 60 | } 61 | } 62 | importObject.env.audio_current_time = function () { 63 | return ctx.currentTime; 64 | } 65 | 66 | importObject.env.audio_samples = function (buffer_ptr, start_audio) { 67 | var buffer = ctx.createBuffer(2, buffer_size, ctx.sampleRate); 68 | var channel0 = buffer.getChannelData(0); 69 | var channel1 = buffer.getChannelData(1); 70 | var obuf = new Float32Array(wasm_memory.buffer, buffer_ptr, buffer_size * 2); 71 | for (var i = 0, j = 0; i < buffer_size * 2; i++) { 72 | 73 | channel0[i] = obuf[j++]; 74 | channel1[i] = obuf[j++]; 75 | } 76 | var bufferSource = ctx.createBufferSource(); 77 | bufferSource.buffer = buffer; 78 | bufferSource.connect(ctx.destination); 79 | bufferSource.start(start_audio); 80 | var bufferSec = buffer_size / ctx.sampleRate; 81 | return bufferSec; 82 | } 83 | 84 | importObject.env.audio_sample_rate = function () { 85 | return ctx.sampleRate; 86 | } 87 | 88 | importObject.env.audio_pause_state = function () { 89 | var duration = window.endPause - window.startPause; 90 | if (duration > 0) { 91 | window.endPause = 0; 92 | window.startPause = 0; 93 | return duration; 94 | } else if (window.startPause > 0) { 95 | return -1; 96 | } else { 97 | return 0.0; 98 | } 99 | } 100 | } 101 | 102 | miniquad_add_plugin({ register_plugin }); 103 | -------------------------------------------------------------------------------- /utils/wasm/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | if [ ! -e resources.tar ]; then 6 | cd ../../resources && tar cf resources.tar * && cd .. && mv resources/resources.tar utils/wasm/ 7 | fi 8 | 9 | cargo build --target wasm32-unknown-unknown --release 10 | cd ../../ 11 | rm -rf static 12 | mkdir static 13 | cp target/wasm32-unknown-unknown/release/dig_escape.wasm static/ 14 | cp utils/wasm/index.html static/ 15 | cp utils/wasm/gl.js static/ 16 | cp utils/wasm/audio.js static/ 17 | cp utils/wasm/resources.tar static/ 18 | ls -lh static 19 | -------------------------------------------------------------------------------- /utils/wasm/gl.js: -------------------------------------------------------------------------------- 1 | // load wasm module and link with gl functions 2 | // 3 | // this file was made by tons of hacks from emscripten's parseTools and library_webgl 4 | // https://github.com/emscripten-core/emscripten/blob/master/src/parseTools.js 5 | // https://github.com/emscripten-core/emscripten/blob/master/src/library_webgl.js 6 | // 7 | // TODO: split to gl.js and loader.js 8 | 9 | const canvas = document.querySelector("#glcanvas"); 10 | const gl = canvas.getContext("webgl"); 11 | if (gl === null) { 12 | alert("Unable to initialize WebGL. Your browser or machine may not support it."); 13 | } 14 | 15 | var plugins = []; 16 | 17 | canvas.focus(); 18 | 19 | canvas.requestPointerLock = canvas.requestPointerLock || 20 | canvas.mozRequestPointerLock; 21 | document.exitPointerLock = document.exitPointerLock || 22 | document.mozExitPointerLock; 23 | 24 | function assert(flag, message) { 25 | if (flag == false) { 26 | alert(message) 27 | } 28 | } 29 | 30 | function acquireVertexArrayObjectExtension(ctx) { 31 | // Extension available in WebGL 1 from Firefox 25 and WebKit 536.28/desktop Safari 6.0.3 onwards. Core feature in WebGL 2. 32 | var ext = ctx.getExtension('OES_vertex_array_object'); 33 | if (ext) { 34 | ctx['createVertexArray'] = function () { return ext['createVertexArrayOES'](); }; 35 | ctx['deleteVertexArray'] = function (vao) { ext['deleteVertexArrayOES'](vao); }; 36 | ctx['bindVertexArray'] = function (vao) { ext['bindVertexArrayOES'](vao); }; 37 | ctx['isVertexArray'] = function (vao) { return ext['isVertexArrayOES'](vao); }; 38 | } 39 | else { 40 | alert("Unable to get OES_vertex_array_object extension"); 41 | } 42 | } 43 | 44 | 45 | function acquireInstancedArraysExtension(ctx) { 46 | // Extension available in WebGL 1 from Firefox 26 and Google Chrome 30 onwards. Core feature in WebGL 2. 47 | var ext = ctx.getExtension('ANGLE_instanced_arrays'); 48 | if (ext) { 49 | ctx['vertexAttribDivisor'] = function (index, divisor) { ext['vertexAttribDivisorANGLE'](index, divisor); }; 50 | ctx['drawArraysInstanced'] = function (mode, first, count, primcount) { ext['drawArraysInstancedANGLE'](mode, first, count, primcount); }; 51 | ctx['drawElementsInstanced'] = function (mode, count, type, indices, primcount) { ext['drawElementsInstancedANGLE'](mode, count, type, indices, primcount); }; 52 | } 53 | } 54 | 55 | acquireVertexArrayObjectExtension(gl); 56 | acquireInstancedArraysExtension(gl); 57 | 58 | // https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_depth_texture 59 | if (gl.getExtension('WEBGL_depth_texture') == null) { 60 | alert("Cant initialize WEBGL_depth_texture extension"); 61 | } 62 | 63 | function getArray(ptr, arr, n) { 64 | return new arr(wasm_memory.buffer, ptr, n); 65 | } 66 | 67 | function UTF8ToString(ptr, maxBytesToRead) { 68 | let u8Array = new Uint8Array(wasm_memory.buffer, ptr); 69 | 70 | var idx = 0; 71 | var endIdx = idx + maxBytesToRead; 72 | 73 | var str = ''; 74 | while (!(idx >= endIdx)) { 75 | // For UTF8 byte structure, see: 76 | // http://en.wikipedia.org/wiki/UTF-8#Description 77 | // https://www.ietf.org/rfc/rfc2279.txt 78 | // https://tools.ietf.org/html/rfc3629 79 | var u0 = u8Array[idx++]; 80 | 81 | // If not building with TextDecoder enabled, we don't know the string length, so scan for \0 byte. 82 | // If building with TextDecoder, we know exactly at what byte index the string ends, so checking for nulls here would be redundant. 83 | if (!u0) return str; 84 | 85 | if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } 86 | var u1 = u8Array[idx++] & 63; 87 | if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } 88 | var u2 = u8Array[idx++] & 63; 89 | if ((u0 & 0xF0) == 0xE0) { 90 | u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; 91 | } else { 92 | 93 | if ((u0 & 0xF8) != 0xF0) console.warn('Invalid UTF-8 leading byte 0x' + u0.toString(16) + ' encountered when deserializing a UTF-8 string on the asm.js/wasm heap to a JS string!'); 94 | 95 | u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (u8Array[idx++] & 63); 96 | } 97 | 98 | if (u0 < 0x10000) { 99 | str += String.fromCharCode(u0); 100 | } else { 101 | var ch = u0 - 0x10000; 102 | str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); 103 | } 104 | } 105 | 106 | return str; 107 | } 108 | 109 | var FS = { 110 | loaded_files: [], 111 | unique_id: 0 112 | }; 113 | 114 | var GL = { 115 | counter: 1, 116 | buffers: [], 117 | mappedBuffers: {}, 118 | programs: [], 119 | framebuffers: [], 120 | renderbuffers: [], 121 | textures: [], 122 | uniforms: [], 123 | shaders: [], 124 | vaos: [], 125 | contexts: {}, 126 | programInfos: {}, 127 | 128 | getNewId: function (table) { 129 | var ret = GL.counter++; 130 | for (var i = table.length; i < ret; i++) { 131 | table[i] = null; 132 | } 133 | return ret; 134 | }, 135 | 136 | validateGLObjectID: function (objectHandleArray, objectID, callerFunctionName, objectReadableType) { 137 | if (objectID != 0) { 138 | if (objectHandleArray[objectID] === null) { 139 | console.error(callerFunctionName + ' called with an already deleted ' + objectReadableType + ' ID ' + objectID + '!'); 140 | } else if (!objectHandleArray[objectID]) { 141 | console.error(callerFunctionName + ' called with an invalid ' + objectReadableType + ' ID ' + objectID + '!'); 142 | } 143 | } 144 | }, 145 | getSource: function (shader, count, string, length) { 146 | var source = ''; 147 | for (var i = 0; i < count; ++i) { 148 | var len = length == 0 ? undefined : getArray(length + i * 4, Uint32Array, 1)[0]; 149 | source += UTF8ToString(getArray(string + i * 4, Uint32Array, 1)[0], len); 150 | } 151 | return source; 152 | }, 153 | populateUniformTable: function (program) { 154 | GL.validateGLObjectID(GL.programs, program, 'populateUniformTable', 'program'); 155 | var p = GL.programs[program]; 156 | var ptable = GL.programInfos[program] = { 157 | uniforms: {}, 158 | maxUniformLength: 0, // This is eagerly computed below, since we already enumerate all uniforms anyway. 159 | maxAttributeLength: -1, // This is lazily computed and cached, computed when/if first asked, "-1" meaning not computed yet. 160 | maxUniformBlockNameLength: -1 // Lazily computed as well 161 | }; 162 | 163 | var utable = ptable.uniforms; 164 | // A program's uniform table maps the string name of an uniform to an integer location of that uniform. 165 | // The global GL.uniforms map maps integer locations to WebGLUniformLocations. 166 | var numUniforms = gl.getProgramParameter(p, 0x8B86/*GL_ACTIVE_UNIFORMS*/); 167 | for (var i = 0; i < numUniforms; ++i) { 168 | var u = gl.getActiveUniform(p, i); 169 | 170 | var name = u.name; 171 | ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length + 1); 172 | 173 | // If we are dealing with an array, e.g. vec4 foo[3], strip off the array index part to canonicalize that "foo", "foo[]", 174 | // and "foo[0]" will mean the same. Loop below will populate foo[1] and foo[2]. 175 | if (name.slice(-1) == ']') { 176 | name = name.slice(0, name.lastIndexOf('[')); 177 | } 178 | 179 | // Optimize memory usage slightly: If we have an array of uniforms, e.g. 'vec3 colors[3];', then 180 | // only store the string 'colors' in utable, and 'colors[0]', 'colors[1]' and 'colors[2]' will be parsed as 'colors'+i. 181 | // Note that for the GL.uniforms table, we still need to fetch the all WebGLUniformLocations for all the indices. 182 | var loc = gl.getUniformLocation(p, name); 183 | if (loc) { 184 | var id = GL.getNewId(GL.uniforms); 185 | utable[name] = [u.size, id]; 186 | GL.uniforms[id] = loc; 187 | 188 | for (var j = 1; j < u.size; ++j) { 189 | var n = name + '[' + j + ']'; 190 | loc = gl.getUniformLocation(p, n); 191 | id = GL.getNewId(GL.uniforms); 192 | 193 | GL.uniforms[id] = loc; 194 | } 195 | } 196 | } 197 | } 198 | } 199 | 200 | _glGenObject = function (n, buffers, createFunction, objectTable, functionName) { 201 | for (var i = 0; i < n; i++) { 202 | var buffer = gl[createFunction](); 203 | var id = buffer && GL.getNewId(objectTable); 204 | if (buffer) { 205 | buffer.name = id; 206 | objectTable[id] = buffer; 207 | } else { 208 | console.error("GL_INVALID_OPERATION"); 209 | GL.recordError(0x0502 /* GL_INVALID_OPERATION */); 210 | 211 | alert('GL_INVALID_OPERATION in ' + functionName + ': GLctx.' + createFunction + ' returned null - most likely GL context is lost!'); 212 | } 213 | getArray(buffers + i * 4, Int32Array, 1)[0] = id; 214 | } 215 | } 216 | 217 | _webglGet = function (name_, p, type) { 218 | // Guard against user passing a null pointer. 219 | // Note that GLES2 spec does not say anything about how passing a null pointer should be treated. 220 | // Testing on desktop core GL 3, the application crashes on glGetIntegerv to a null pointer, but 221 | // better to report an error instead of doing anything random. 222 | if (!p) { 223 | console.error('GL_INVALID_VALUE in glGet' + type + 'v(name=' + name_ + ': Function called with null out pointer!'); 224 | GL.recordError(0x501 /* GL_INVALID_VALUE */); 225 | return; 226 | } 227 | var ret = undefined; 228 | switch (name_) { // Handle a few trivial GLES values 229 | case 0x8DFA: // GL_SHADER_COMPILER 230 | ret = 1; 231 | break; 232 | case 0x8DF8: // GL_SHADER_BINARY_FORMATS 233 | if (type != 'EM_FUNC_SIG_PARAM_I' && type != 'EM_FUNC_SIG_PARAM_I64') { 234 | GL.recordError(0x500); // GL_INVALID_ENUM 235 | 236 | err('GL_INVALID_ENUM in glGet' + type + 'v(GL_SHADER_BINARY_FORMATS): Invalid parameter type!'); 237 | } 238 | return; // Do not write anything to the out pointer, since no binary formats are supported. 239 | case 0x87FE: // GL_NUM_PROGRAM_BINARY_FORMATS 240 | case 0x8DF9: // GL_NUM_SHADER_BINARY_FORMATS 241 | ret = 0; 242 | break; 243 | case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS 244 | // WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length), 245 | // so implement it ourselves to allow C++ GLES2 code get the length. 246 | var formats = gl.getParameter(0x86A3 /*GL_COMPRESSED_TEXTURE_FORMATS*/); 247 | ret = formats ? formats.length : 0; 248 | break; 249 | case 0x821D: // GL_NUM_EXTENSIONS 250 | assert(false, "unimplemented"); 251 | break; 252 | case 0x821B: // GL_MAJOR_VERSION 253 | case 0x821C: // GL_MINOR_VERSION 254 | assert(false, "unimplemented"); 255 | break; 256 | } 257 | 258 | if (ret === undefined) { 259 | var result = gl.getParameter(name_); 260 | switch (typeof (result)) { 261 | case "number": 262 | ret = result; 263 | break; 264 | case "boolean": 265 | ret = result ? 1 : 0; 266 | break; 267 | case "string": 268 | GL.recordError(0x500); // GL_INVALID_ENUM 269 | console.error('GL_INVALID_ENUM in glGet' + type + 'v(' + name_ + ') on a name which returns a string!'); 270 | return; 271 | case "object": 272 | if (result === null) { 273 | // null is a valid result for some (e.g., which buffer is bound - perhaps nothing is bound), but otherwise 274 | // can mean an invalid name_, which we need to report as an error 275 | switch (name_) { 276 | case 0x8894: // ARRAY_BUFFER_BINDING 277 | case 0x8B8D: // CURRENT_PROGRAM 278 | case 0x8895: // ELEMENT_ARRAY_BUFFER_BINDING 279 | case 0x8CA6: // FRAMEBUFFER_BINDING 280 | case 0x8CA7: // RENDERBUFFER_BINDING 281 | case 0x8069: // TEXTURE_BINDING_2D 282 | case 0x85B5: // WebGL 2 GL_VERTEX_ARRAY_BINDING, or WebGL 1 extension OES_vertex_array_object GL_VERTEX_ARRAY_BINDING_OES 283 | case 0x8919: // GL_SAMPLER_BINDING 284 | case 0x8E25: // GL_TRANSFORM_FEEDBACK_BINDING 285 | case 0x8514: { // TEXTURE_BINDING_CUBE_MAP 286 | ret = 0; 287 | break; 288 | } 289 | default: { 290 | GL.recordError(0x500); // GL_INVALID_ENUM 291 | console.error('GL_INVALID_ENUM in glGet' + type + 'v(' + name_ + ') and it returns null!'); 292 | return; 293 | } 294 | } 295 | } else if (result instanceof Float32Array || 296 | result instanceof Uint32Array || 297 | result instanceof Int32Array || 298 | result instanceof Array) { 299 | for (var i = 0; i < result.length; ++i) { 300 | assert(false, "unimplemented") 301 | } 302 | return; 303 | } else { 304 | try { 305 | ret = result.name | 0; 306 | } catch (e) { 307 | GL.recordError(0x500); // GL_INVALID_ENUM 308 | console.error('GL_INVALID_ENUM in glGet' + type + 'v: Unknown object returned from WebGL getParameter(' + name_ + ')! (error: ' + e + ')'); 309 | return; 310 | } 311 | } 312 | break; 313 | default: 314 | GL.recordError(0x500); // GL_INVALID_ENUM 315 | console.error('GL_INVALID_ENUM in glGet' + type + 'v: Native code calling glGet' + type + 'v(' + name_ + ') and it returns ' + result + ' of type ' + typeof (result) + '!'); 316 | return; 317 | } 318 | } 319 | 320 | switch (type) { 321 | case 'EM_FUNC_SIG_PARAM_I64': getArray(p, Int32Array, 1)[0] = ret; 322 | case 'EM_FUNC_SIG_PARAM_I': getArray(p, Int32Array, 1)[0] = ret; break; 323 | case 'EM_FUNC_SIG_PARAM_F': getArray(p, Float32Array, 1)[0] = ret; break; 324 | case 'EM_FUNC_SIG_PARAM_B': getArray(p, Int8Array, 1)[0] = ret ? 1 : 0; break; 325 | default: throw 'internal glGet error, bad type: ' + type; 326 | } 327 | } 328 | 329 | var Module; 330 | var wasm_exports; 331 | 332 | function resize(canvas, on_resize) { 333 | var displayWidth = canvas.clientWidth; 334 | var displayHeight = canvas.clientHeight; 335 | 336 | if (canvas.width != displayWidth || 337 | canvas.height != displayHeight) { 338 | canvas.width = displayWidth; 339 | canvas.height = displayHeight; 340 | if (on_resize != undefined) 341 | on_resize(Math.floor(displayWidth), Math.floor(displayHeight)) 342 | } 343 | } 344 | 345 | animation = function () { 346 | wasm_exports.frame(); 347 | window.requestAnimationFrame(animation); 348 | } 349 | 350 | const SAPP_EVENTTYPE_TOUCHES_BEGAN = 10; 351 | const SAPP_EVENTTYPE_TOUCHES_MOVED = 11; 352 | const SAPP_EVENTTYPE_TOUCHES_ENDED = 12; 353 | const SAPP_EVENTTYPE_TOUCHES_CANCELLED = 13; 354 | 355 | into_sapp_mousebutton = function (btn) { 356 | switch (btn) { 357 | case 0: return 0; 358 | case 1: return 2; 359 | case 2: return 1; 360 | default: return btn; 361 | } 362 | } 363 | 364 | into_sapp_keycode = function (key_code) { 365 | switch (key_code) { 366 | case "KeyM": return 77; 367 | case "KeyR": return 82; 368 | case "Digit0": return 48; 369 | case "Digit1": return 49; 370 | case "Digit2": return 50; 371 | case "Digit3": return 51; 372 | case "Digit4": return 52; 373 | case "Digit5": return 53; 374 | case "Digit6": return 54; 375 | case "Digit7": return 55; 376 | case "Digit8": return 56; 377 | case "Digit9": return 57; 378 | case "KeyA": return 65; 379 | case "KeyS": return 83; 380 | case "KeyD": return 68; 381 | case "KeyW": return 87; 382 | case "ArrowRight": return 262; 383 | case "ArrowLeft": return 263; 384 | case "ArrowDown": return 264; 385 | case "ArrowUp": return 265; 386 | case "Space": return 32; 387 | case "Home": return 268; 388 | case "End": return 269; 389 | case "Enter": return 257; 390 | case "Delete": return 261; 391 | case "Backspace": return 259; 392 | } 393 | 394 | console.log("Unsupported keyboard key") 395 | } 396 | 397 | texture_size = function (internalFormat, width, height) { 398 | if (internalFormat == gl.ALPHA) { 399 | return width * height; 400 | } 401 | else if (internalFormat == gl.RGB) { 402 | return width * height * 3; 403 | } else if (internalFormat == gl.RGBA) { 404 | return width * height * 4; 405 | } else { // TextureFormat::RGB565 | TextureFormat::RGBA4 | TextureFormat::RGBA5551 406 | return width * height * 3; 407 | } 408 | } 409 | 410 | mouse_relative_position = function (clientX, clientY) { 411 | var targetRect = canvas.getBoundingClientRect(); 412 | 413 | var x = clientX - targetRect.left; 414 | var y = clientY - targetRect.top; 415 | 416 | return { x, y }; 417 | } 418 | 419 | var emscripten_shaders_hack = false; 420 | 421 | var importObject = { 422 | env: { 423 | console_debug: function (ptr) { 424 | console.debug(UTF8ToString(ptr)); 425 | }, 426 | console_log: function (ptr) { 427 | console.log(UTF8ToString(ptr)); 428 | }, 429 | console_info: function (ptr) { 430 | console.info(UTF8ToString(ptr)); 431 | }, 432 | console_warn: function (ptr) { 433 | console.warn(UTF8ToString(ptr)); 434 | }, 435 | console_error: function (ptr) { 436 | console.error(UTF8ToString(ptr)); 437 | }, 438 | set_emscripten_shader_hack: function (flag) { 439 | emscripten_shaders_hack = flag; 440 | }, 441 | rand: function () { 442 | return Math.floor(Math.random() * 2147483647); 443 | }, 444 | time: function () { 445 | return Date.now() / 1000.0; 446 | }, 447 | canvas_width: function () { 448 | return Math.floor(canvas.clientWidth); 449 | }, 450 | canvas_height: function () { 451 | return Math.floor(canvas.clientHeight); 452 | }, 453 | glClearDepthf: function (depth) { 454 | gl.clearDepth(depth); 455 | }, 456 | glClearColor: function (r, g, b, a) { 457 | gl.clearColor(r, g, b, a); 458 | }, 459 | glClearStencil: function (s) { 460 | gl.clearColorStencil(s); 461 | }, 462 | glColorMask: function (red, green, blue, alpha) { 463 | gl.colorMask(red, green, blue, alpha); 464 | }, 465 | glScissor: function (x, y, w, h) { 466 | gl.scissor(x, y, w, h); 467 | }, 468 | glClear: function (mask) { 469 | gl.clear(mask); 470 | }, 471 | glGenTextures: function (n, textures) { 472 | _glGenObject(n, textures, "createTexture", GL.textures, "glGenTextures") 473 | }, 474 | glActiveTexture: function (texture) { 475 | gl.activeTexture(texture) 476 | }, 477 | glBindTexture: function (target, texture) { 478 | GL.validateGLObjectID(GL.textures, texture, 'glBindTexture', 'texture'); 479 | gl.bindTexture(target, GL.textures[texture]); 480 | }, 481 | glTexImage2D: function (target, level, internalFormat, width, height, border, format, type, pixels) { 482 | gl.texImage2D(target, level, internalFormat, width, height, border, format, type, 483 | pixels ? getArray(pixels, Uint8Array, texture_size(internalFormat, width, height)) : null); 484 | }, 485 | glTexSubImage2D: function (target, level, xoffset, yoffset, width, height, format, type, pixels) { 486 | gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, 487 | pixels ? getArray(pixels, Uint8Array, texture_size(format, width, height)) : null); 488 | }, 489 | glTexParameteri: function (target, pname, param) { 490 | gl.texParameteri(target, pname, param); 491 | }, 492 | glUniform1fv: function (location, count, value) { 493 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform1fv', 'location'); 494 | assert((value & 3) == 0, 'Pointer to float data passed to glUniform1fv must be aligned to four bytes!'); 495 | var view = getArray(value, Float32Array, 1 * count); 496 | gl.uniform1fv(GL.uniforms[location], view); 497 | }, 498 | glUniform2fv: function (location, count, value) { 499 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform2fv', 'location'); 500 | assert((value & 3) == 0, 'Pointer to float data passed to glUniform2fv must be aligned to four bytes!'); 501 | var view = getArray(value, Float32Array, 2 * count); 502 | gl.uniform2fv(GL.uniforms[location], view); 503 | }, 504 | glUniform3fv: function (location, count, value) { 505 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform3fv', 'location'); 506 | assert((value & 3) == 0, 'Pointer to float data passed to glUniform3fv must be aligned to four bytes!'); 507 | var view = getArray(value, Float32Array, 4 * count); 508 | gl.uniform3fv(GL.uniforms[location], view); 509 | }, 510 | glUniform4fv: function (location, count, value) { 511 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform4fv', 'location'); 512 | assert((value & 3) == 0, 'Pointer to float data passed to glUniform4fv must be aligned to four bytes!'); 513 | var view = getArray(value, Float32Array, 4 * count); 514 | gl.uniform4fv(GL.uniforms[location], view); 515 | }, 516 | glUniform1iv: function (location, count, value) { 517 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform1fv', 'location'); 518 | assert((value & 3) == 0, 'Pointer to i32 data passed to glUniform1iv must be aligned to four bytes!'); 519 | var view = getArray(value, Int32Array, 1 * count); 520 | gl.uniform1iv(GL.uniforms[location], view); 521 | }, 522 | glUniform2iv: function (location, count, value) { 523 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform2fv', 'location'); 524 | assert((value & 3) == 0, 'Pointer to i32 data passed to glUniform2iv must be aligned to four bytes!'); 525 | var view = getArray(value, Int32Array, 2 * count); 526 | gl.uniform2iv(GL.uniforms[location], view); 527 | }, 528 | glUniform3iv: function (location, count, value) { 529 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform3fv', 'location'); 530 | assert((value & 3) == 0, 'Pointer to i32 data passed to glUniform3iv must be aligned to four bytes!'); 531 | var view = getArray(value, Int32Array, 3 * count); 532 | gl.uniform3iv(GL.uniforms[location], view); 533 | }, 534 | glUniform4iv: function (location, count, value) { 535 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform4fv', 'location'); 536 | assert((value & 3) == 0, 'Pointer to i32 data passed to glUniform4iv must be aligned to four bytes!'); 537 | var view = getArray(value, Int32Array, 4 * count); 538 | gl.uniform4iv(GL.uniforms[location], view); 539 | }, 540 | glBlendFunc: function (sfactor, dfactor) { 541 | gl.blendFunc(sfactor, dfactor); 542 | }, 543 | glBlendEquationSeparate: function (modeRGB, modeAlpha) { 544 | gl.blendEquationSeparate(modeRGB, modeAlpha); 545 | }, 546 | glDisable: function (cap) { 547 | gl.disable(cap); 548 | }, 549 | glDrawElements: function (mode, count, type, indices) { 550 | gl.drawElements(mode, count, type, indices); 551 | }, 552 | glGetIntegerv: function (name_, p) { 553 | _webglGet(name_, p, 'EM_FUNC_SIG_PARAM_I'); 554 | }, 555 | glUniform1f: function (location, v0) { 556 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform1f', 'location'); 557 | gl.uniform1f(GL.uniforms[location], v0); 558 | }, 559 | glUniform1i: function (location, v0) { 560 | GL.validateGLObjectID(GL.uniforms, location, 'glUniform1i', 'location'); 561 | gl.uniform1i(GL.uniforms[location], v0); 562 | }, 563 | glGetAttribLocation: function (program, name) { 564 | return gl.getAttribLocation(GL.programs[program], UTF8ToString(name)); 565 | }, 566 | glEnableVertexAttribArray: function (index) { 567 | gl.enableVertexAttribArray(index); 568 | }, 569 | glDisableVertexAttribArray: function (index) { 570 | gl.disableVertexAttribArray(index); 571 | }, 572 | glVertexAttribPointer: function (index, size, type, normalized, stride, ptr) { 573 | gl.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); 574 | }, 575 | glGetUniformLocation: function (program, name) { 576 | GL.validateGLObjectID(GL.programs, program, 'glGetUniformLocation', 'program'); 577 | name = UTF8ToString(name); 578 | var arrayIndex = 0; 579 | // If user passed an array accessor "[index]", parse the array index off the accessor. 580 | if (name[name.length - 1] == ']') { 581 | var leftBrace = name.lastIndexOf('['); 582 | arrayIndex = name[leftBrace + 1] != ']' ? parseInt(name.slice(leftBrace + 1)) : 0; // "index]", parseInt will ignore the ']' at the end; but treat "foo[]" as "foo[0]" 583 | name = name.slice(0, leftBrace); 584 | } 585 | 586 | var uniformInfo = GL.programInfos[program] && GL.programInfos[program].uniforms[name]; // returns pair [ dimension_of_uniform_array, uniform_location ] 587 | if (uniformInfo && arrayIndex >= 0 && arrayIndex < uniformInfo[0]) { // Check if user asked for an out-of-bounds element, i.e. for 'vec4 colors[3];' user could ask for 'colors[10]' which should return -1. 588 | return uniformInfo[1] + arrayIndex; 589 | } else { 590 | return -1; 591 | } 592 | }, 593 | glUniformMatrix4fv: function (location, count, transpose, value) { 594 | GL.validateGLObjectID(GL.uniforms, location, 'glUniformMatrix4fv', 'location'); 595 | assert((value & 3) == 0, 'Pointer to float data passed to glUniformMatrix4fv must be aligned to four bytes!'); 596 | var view = getArray(value, Float32Array, 16); 597 | gl.uniformMatrix4fv(GL.uniforms[location], !!transpose, view); 598 | }, 599 | glUseProgram: function (program) { 600 | GL.validateGLObjectID(GL.programs, program, 'glUseProgram', 'program'); 601 | gl.useProgram(GL.programs[program]); 602 | }, 603 | glGenVertexArrays: function (n, arrays) { 604 | _glGenObject(n, arrays, 'createVertexArray', GL.vaos, 'glGenVertexArrays'); 605 | }, 606 | glGenFramebuffers: function (n, ids) { 607 | _glGenObject(n, ids, 'createFramebuffer', GL.framebuffers, 'glGenFramebuffers'); 608 | }, 609 | glBindVertexArray: function (vao) { 610 | gl.bindVertexArray(GL.vaos[vao]); 611 | }, 612 | glBindFramebuffer: function (target, framebuffer) { 613 | GL.validateGLObjectID(GL.framebuffers, framebuffer, 'glBindFramebuffer', 'framebuffer'); 614 | 615 | gl.bindFramebuffer(target, GL.framebuffers[framebuffer]); 616 | }, 617 | 618 | glGenBuffers: function (n, buffers) { 619 | _glGenObject(n, buffers, 'createBuffer', GL.buffers, 'glGenBuffers'); 620 | }, 621 | glBindBuffer: function (target, buffer) { 622 | GL.validateGLObjectID(GL.buffers, buffer, 'glBindBuffer', 'buffer'); 623 | gl.bindBuffer(target, GL.buffers[buffer]); 624 | }, 625 | glBufferData: function (target, size, data, usage) { 626 | gl.bufferData(target, data ? getArray(data, Uint8Array, size) : size, usage); 627 | }, 628 | glBufferSubData: function (target, offset, size, data) { 629 | gl.bufferSubData(target, offset, data ? getArray(data, Uint8Array, size) : size); 630 | }, 631 | glEnable: function (cap) { 632 | gl.enable(cap); 633 | }, 634 | glDepthFunc: function (func) { 635 | gl.depthFunc(func); 636 | }, 637 | glBlendFuncSeparate: function (sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha) { 638 | gl.blendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); 639 | }, 640 | glViewport: function (x, y, width, height) { 641 | gl.viewport(x, y, width, height); 642 | }, 643 | glDrawArrays: function (mode, first, count) { 644 | gl.drawArrays(mode, first, count); 645 | }, 646 | glCreateProgram: function () { 647 | var id = GL.getNewId(GL.programs); 648 | var program = gl.createProgram(); 649 | program.name = id; 650 | GL.programs[id] = program; 651 | return id; 652 | }, 653 | glAttachShader: function (program, shader) { 654 | GL.validateGLObjectID(GL.programs, program, 'glAttachShader', 'program'); 655 | GL.validateGLObjectID(GL.shaders, shader, 'glAttachShader', 'shader'); 656 | gl.attachShader(GL.programs[program], GL.shaders[shader]); 657 | }, 658 | glLinkProgram: function (program) { 659 | GL.validateGLObjectID(GL.programs, program, 'glLinkProgram', 'program'); 660 | gl.linkProgram(GL.programs[program]); 661 | GL.populateUniformTable(program); 662 | }, 663 | glFramebufferTexture2D: function (target, attachment, textarget, texture, level) { 664 | GL.validateGLObjectID(GL.textures, texture, 'glFramebufferTexture2D', 'texture'); 665 | gl.framebufferTexture2D(target, attachment, textarget, GL.textures[texture], level); 666 | }, 667 | glGetProgramiv: function (program, pname, p) { 668 | assert(p); 669 | GL.validateGLObjectID(GL.programs, program, 'glGetProgramiv', 'program'); 670 | if (program >= GL.counter) { 671 | console.error("GL_INVALID_VALUE in glGetProgramiv"); 672 | return; 673 | } 674 | var ptable = GL.programInfos[program]; 675 | if (!ptable) { 676 | console.error('GL_INVALID_OPERATION in glGetProgramiv(program=' + program + ', pname=' + pname + ', p=0x' + p.toString(16) + '): The specified GL object name does not refer to a program object!'); 677 | return; 678 | } 679 | if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH 680 | var log = gl.getProgramInfoLog(GL.programs[program]); 681 | assert(log !== null); 682 | 683 | getArray(p, Int32Array, 1)[0] = log.length + 1; 684 | } else if (pname == 0x8B87 /* GL_ACTIVE_UNIFORM_MAX_LENGTH */) { 685 | console.error("unsupported operation"); 686 | return; 687 | } else if (pname == 0x8B8A /* GL_ACTIVE_ATTRIBUTE_MAX_LENGTH */) { 688 | console.error("unsupported operation"); 689 | return; 690 | } else if (pname == 0x8A35 /* GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH */) { 691 | console.error("unsupported operation"); 692 | return; 693 | } else { 694 | getArray(p, Int32Array, 1)[0] = gl.getProgramParameter(GL.programs[program], pname); 695 | } 696 | }, 697 | glCreateShader: function (shaderType) { 698 | var id = GL.getNewId(GL.shaders); 699 | GL.shaders[id] = gl.createShader(shaderType); 700 | return id; 701 | }, 702 | glStencilFuncSeparate: function (face, func, ref_, mask) { 703 | gl.stencilFuncSeparate(face, func, ref_, mask); 704 | }, 705 | glStencilMaskSeparate: function (face, mask) { 706 | gl.stencilMaskSeparate(face, mask); 707 | }, 708 | glStencilOpSeparate: function (face, fail, zfail, zpass) { 709 | gl.stencilOpSeparate(face, fail, zfail, zpass); 710 | }, 711 | glFrontFace: function (mode) { 712 | gl.frontFace(mode); 713 | }, 714 | glCullFace: function (mode) { 715 | gl.cullFace(mode); 716 | }, 717 | glShaderSource: function (shader, count, string, length) { 718 | GL.validateGLObjectID(GL.shaders, shader, 'glShaderSource', 'shader'); 719 | var source = GL.getSource(shader, count, string, length); 720 | 721 | // https://github.com/emscripten-core/emscripten/blob/incoming/src/library_webgl.js#L2708 722 | if (emscripten_shaders_hack) { 723 | source = source.replace(/#extension GL_OES_standard_derivatives : enable/g, ""); 724 | source = source.replace(/#extension GL_EXT_shader_texture_lod : enable/g, ''); 725 | var prelude = ''; 726 | if (source.indexOf('gl_FragColor') != -1) { 727 | prelude += 'out mediump vec4 GL_FragColor;\n'; 728 | source = source.replace(/gl_FragColor/g, 'GL_FragColor'); 729 | } 730 | if (source.indexOf('attribute') != -1) { 731 | source = source.replace(/attribute/g, 'in'); 732 | source = source.replace(/varying/g, 'out'); 733 | } else { 734 | source = source.replace(/varying/g, 'in'); 735 | } 736 | 737 | source = source.replace(/textureCubeLodEXT/g, 'textureCubeLod'); 738 | source = source.replace(/texture2DLodEXT/g, 'texture2DLod'); 739 | source = source.replace(/texture2DProjLodEXT/g, 'texture2DProjLod'); 740 | source = source.replace(/texture2DGradEXT/g, 'texture2DGrad'); 741 | source = source.replace(/texture2DProjGradEXT/g, 'texture2DProjGrad'); 742 | source = source.replace(/textureCubeGradEXT/g, 'textureCubeGrad'); 743 | 744 | source = source.replace(/textureCube/g, 'texture'); 745 | source = source.replace(/texture1D/g, 'texture'); 746 | source = source.replace(/texture2D/g, 'texture'); 747 | source = source.replace(/texture3D/g, 'texture'); 748 | source = source.replace(/#version 100/g, '#version 300 es\n' + prelude); 749 | } 750 | 751 | gl.shaderSource(GL.shaders[shader], source); 752 | }, 753 | glGetProgramInfoLog: function (program, maxLength, length, infoLog) { 754 | GL.validateGLObjectID(GL.programs, program, 'glGetProgramInfoLog', 'program'); 755 | var log = gl.getProgramInfoLog(GL.programs[program]); 756 | assert(log !== null); 757 | let array = getArray(infoLog, Uint8Array, maxLength); 758 | for (var i = 0; i < maxLength; i++) { 759 | array[i] = log.charCodeAt(i); 760 | } 761 | }, 762 | glCompileShader: function (shader, count, string, length) { 763 | GL.validateGLObjectID(GL.shaders, shader, 'glCompileShader', 'shader'); 764 | gl.compileShader(GL.shaders[shader]); 765 | }, 766 | glGetShaderiv: function (shader, pname, p) { 767 | assert(p); 768 | GL.validateGLObjectID(GL.shaders, shader, 'glGetShaderiv', 'shader'); 769 | if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH 770 | var log = gl.getShaderInfoLog(GL.shaders[shader]); 771 | assert(log !== null); 772 | 773 | getArray(p, Int32Array, 1)[0] = log.length + 1; 774 | 775 | } else if (pname == 0x8B88) { // GL_SHADER_SOURCE_LENGTH 776 | var source = gl.getShaderSource(GL.shaders[shader]); 777 | var sourceLength = (source === null || source.length == 0) ? 0 : source.length + 1; 778 | getArray(p, Int32Array, 1)[0] = sourceLength; 779 | } else { 780 | getArray(p, Int32Array, 1)[0] = gl.getShaderParameter(GL.shaders[shader], pname); 781 | } 782 | }, 783 | glGetShaderInfoLog: function (shader, maxLength, length, infoLog) { 784 | GL.validateGLObjectID(GL.shaders, shader, 'glGetShaderInfoLog', 'shader'); 785 | var log = gl.getShaderInfoLog(GL.shaders[shader]); 786 | assert(log !== null); 787 | let array = getArray(infoLog, Uint8Array, maxLength); 788 | for (var i = 0; i < maxLength; i++) { 789 | array[i] = log.charCodeAt(i); 790 | } 791 | }, 792 | glVertexAttribDivisor: function (index, divisor) { 793 | gl.vertexAttribDivisor(index, divisor); 794 | }, 795 | glDrawArraysInstanced: function (mode, first, count, primcount) { 796 | gl.drawArraysInstanced(mode, first, count, primcount); 797 | }, 798 | glDrawElementsInstanced: function (mode, count, type, indices, primcount) { 799 | gl.drawElementsInstanced(mode, count, type, indices, primcount); 800 | }, 801 | glDeleteShader: function (shader) { gl.deleteShader(shader) }, 802 | glDeleteBuffers: function (n, buffers) { 803 | for (var i = 0; i < n; i++) { 804 | var id = getArray(buffers + i * 4, Uint32Array, 1)[0]; 805 | var buffer = GL.buffers[id]; 806 | 807 | // From spec: "glDeleteBuffers silently ignores 0's and names that do not 808 | // correspond to existing buffer objects." 809 | if (!buffer) continue; 810 | 811 | gl.deleteBuffer(buffer); 812 | buffer.name = 0; 813 | GL.buffers[id] = null; 814 | } 815 | }, 816 | glDeleteTextures: function (n, textures) { 817 | for (var i = 0; i < n; i++) { 818 | var id = getArray(textures + i * 4, Uint32Array, 1)[0]; 819 | var texture = GL.textures[id]; 820 | if (!texture) continue; // GL spec: "glDeleteTextures silently ignores 0s and names that do not correspond to existing textures". 821 | gl.deleteTexture(texture); 822 | texture.name = 0; 823 | GL.textures[id] = null; 824 | } 825 | }, 826 | init_opengl: function (ptr) { 827 | canvas.onmousemove = function (event) { 828 | var relative_position = mouse_relative_position(event.clientX, event.clientY); 829 | var x = relative_position.x; 830 | var y = relative_position.y; 831 | 832 | // TODO: do not send mouse_move when cursor is captured 833 | wasm_exports.mouse_move(Math.floor(x), Math.floor(y)); 834 | 835 | // TODO: check that mouse is captured? 836 | if (event.movementX != 0 || event.movementY != 0) { 837 | wasm_exports.raw_mouse_move(Math.floor(event.movementX), Math.floor(event.movementY)); 838 | } 839 | }; 840 | canvas.onmousedown = function (event) { 841 | var relative_position = mouse_relative_position(event.clientX, event.clientY); 842 | var x = relative_position.x; 843 | var y = relative_position.y; 844 | 845 | var btn = into_sapp_mousebutton(event.button); 846 | wasm_exports.mouse_down(x, y, btn); 847 | }; 848 | // SO WEB SO CONSISTENT 849 | canvas.addEventListener('wheel', 850 | function (event) { 851 | wasm_exports.mouse_wheel(-event.deltaX, -event.deltaY); 852 | }); 853 | canvas.onmouseup = function (event) { 854 | var relative_position = mouse_relative_position(event.clientX, event.clientY); 855 | var x = relative_position.x; 856 | var y = relative_position.y; 857 | 858 | var btn = into_sapp_mousebutton(event.button); 859 | wasm_exports.mouse_up(x, y, btn); 860 | }; 861 | canvas.onkeydown = function (event) { 862 | var sapp_key_code = into_sapp_keycode(event.code); 863 | wasm_exports.key_down(sapp_key_code); 864 | }; 865 | canvas.onkeyup = function (event) { 866 | var sapp_key_code = into_sapp_keycode(event.code); 867 | wasm_exports.key_up(sapp_key_code); 868 | }; 869 | canvas.onkeypress = function (event) { 870 | wasm_exports.key_press(event.charCode); 871 | }; 872 | 873 | canvas.addEventListener("touchstart", function (event) { 874 | event.preventDefault(); 875 | 876 | for (touch of event.changedTouches) { 877 | wasm_exports.touch(SAPP_EVENTTYPE_TOUCHES_BEGAN, touch.identifier, Math.floor(touch.clientX), Math.floor(touch.clientY)); 878 | } 879 | }); 880 | canvas.addEventListener("touchend", function (event) { 881 | event.preventDefault(); 882 | 883 | for (touch of event.changedTouches) { 884 | wasm_exports.touch(SAPP_EVENTTYPE_TOUCHES_ENDED, touch.identifier, Math.floor(touch.clientX), Math.floor(touch.clientY)); 885 | } 886 | }); 887 | canvas.addEventListener("touchcancel", function (event) { 888 | event.preventDefault(); 889 | 890 | for (touch of event.changedTouches) { 891 | wasm_exports.touch(SAPP_EVENTTYPE_TOUCHES_CANCELED, touch.identifier, Math.floor(touch.clientX), Math.floor(touch.clientY)); 892 | } 893 | }); 894 | canvas.addEventListener("touchmove", function (event) { 895 | event.preventDefault(); 896 | 897 | for (touch of event.changedTouches) { 898 | wasm_exports.touch(SAPP_EVENTTYPE_TOUCHES_MOVED, touch.identifier, Math.floor(touch.clientX), Math.floor(touch.clientY)); 899 | } 900 | }); 901 | 902 | window.onresize = function () { 903 | resize(canvas, wasm_exports.resize); 904 | }; 905 | window.requestAnimationFrame(animation); 906 | }, 907 | 908 | fs_load_file: function (ptr, len) { 909 | var url = UTF8ToString(ptr, len); 910 | var file_id = FS.unique_id; 911 | FS.unique_id += 1; 912 | var xhr = new XMLHttpRequest(); 913 | xhr.open('GET', url, true); 914 | xhr.responseType = 'arraybuffer'; 915 | xhr.onload = function (e) { 916 | if (this.status == 200) { 917 | var uInt8Array = new Uint8Array(this.response); 918 | 919 | FS.loaded_files[file_id] = uInt8Array; 920 | wasm_exports.file_loaded(file_id); 921 | } 922 | } 923 | xhr.onerror = function (e) { 924 | FS.loaded_files[file_id] = null; 925 | wasm_exports.file_loaded(file_id); 926 | }; 927 | 928 | xhr.send(); 929 | 930 | return file_id; 931 | }, 932 | 933 | fs_get_buffer_size: function (file_id) { 934 | if (FS.loaded_files[file_id] == null) { 935 | return -1; 936 | } else { 937 | return FS.loaded_files[file_id].length; 938 | } 939 | }, 940 | fs_take_buffer: function (file_id, ptr, max_length) { 941 | var file = FS.loaded_files[file_id]; 942 | console.assert(file.length <= max_length); 943 | var dest = new Uint8Array(wasm_memory.buffer, ptr, max_length); 944 | for (var i = 0; i < file.length; i++) { 945 | dest[i] = file[i]; 946 | } 947 | delete FS.loaded_files[file_id]; 948 | }, 949 | sapp_set_cursor_grab: function (grab) { 950 | if (grab) { 951 | canvas.requestPointerLock(); 952 | } else { 953 | document.exitPointerLock(); 954 | } 955 | } 956 | } 957 | }; 958 | 959 | 960 | function register_plugins(plugins) { 961 | if (plugins == undefined) 962 | return; 963 | 964 | for (var i = 0; i < plugins.length; i++) { 965 | if (plugins[i].register_plugin != undefined && plugins[i].register_plugin != null) { 966 | plugins[i].register_plugin(importObject); 967 | } 968 | } 969 | } 970 | 971 | function init_plugins(plugins) { 972 | if (plugins == undefined) 973 | return; 974 | 975 | for (var i = 0; i < plugins.length; i++) { 976 | if (plugins[i].on_init != undefined && plugins[i].on_init != null) { 977 | plugins[i].on_init(); 978 | } 979 | } 980 | } 981 | 982 | 983 | function miniquad_add_plugin(plugin) { 984 | plugins.push(plugin); 985 | } 986 | 987 | function load(wasm_path) { 988 | var req = fetch(wasm_path); 989 | 990 | register_plugins(plugins); 991 | 992 | if (typeof WebAssembly.instantiateStreaming === 'function') { 993 | WebAssembly.instantiateStreaming(req, importObject) 994 | .then(obj => { 995 | wasm_memory = obj.instance.exports.memory; 996 | wasm_exports = obj.instance.exports; 997 | 998 | init_plugins(plugins); 999 | obj.instance.exports.main(); 1000 | }); 1001 | } else { 1002 | req 1003 | .then(function (x) { return x.arrayBuffer(); }) 1004 | .then(function (bytes) { return WebAssembly.instantiate(bytes, importObject); }) 1005 | .then(function (obj) { 1006 | wasm_memory = obj.instance.exports.memory; 1007 | wasm_exports = obj.instance.exports; 1008 | 1009 | init_plugins(plugins); 1010 | obj.instance.exports.main(); 1011 | }); 1012 | } 1013 | } 1014 | 1015 | resize(canvas); 1016 | -------------------------------------------------------------------------------- /utils/wasm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | TITLE 4 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /utils/wasm/zip_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | cd ../../resources && tar cf resources.tar * && cd .. && mv resources/resources.tar utils/wasm/ 6 | cp utils/wasm/resources.tar src/ 7 | ls -lh static 8 | --------------------------------------------------------------------------------