├── .dockerignore ├── .github └── workflows │ ├── build-image.yaml │ ├── fmt.yaml │ ├── players-lint.yaml │ ├── publish-binary.yaml │ ├── test.yaml │ └── wake-lint.yaml ├── .gitignore ├── .vimspector.json ├── CHANGELOG_WAKE.md ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── docs ├── Architecture.md └── shump-converter.md ├── githooks └── pre-push ├── players ├── Cargo.toml └── src │ ├── cmd │ ├── mod.rs │ └── shmup.rs │ ├── main.rs │ └── shmup │ ├── components │ ├── camera.rs │ ├── enemy.rs │ ├── enemy_bullets.rs │ ├── guns.rs │ ├── mod.rs │ ├── patterns.rs │ ├── player.rs │ └── player_bullet.rs │ ├── config.rs │ ├── debug.rs │ ├── mod.rs │ ├── placer.rs │ ├── plugin.rs │ └── systems │ ├── camera.rs │ ├── enemy_bullets.rs │ ├── guns.rs │ ├── mod.rs │ ├── movements.rs │ ├── player.rs │ └── player_bullet.rs ├── rust-toolchain ├── wake ├── Cargo.toml ├── benches │ └── benches.rs ├── src │ ├── cmd │ │ ├── mod.rs │ │ ├── play.rs │ │ ├── scan.rs │ │ └── serve.rs │ └── main.rs └── tests │ ├── play_test.rs │ ├── scan_test.rs │ └── serve_test.rs └── waking-git-core ├── Cargo.toml └── src ├── config.rs ├── converters ├── mod.rs └── shmup │ └── mod.rs ├── exec └── mod.rs ├── extractor ├── code.rs ├── git.rs └── mod.rs ├── hash └── mod.rs ├── languages ├── mod.rs └── res │ └── languages.yml ├── lib.rs ├── repo └── mod.rs ├── server └── mod.rs ├── shapes └── mod.rs └── utils ├── mod.rs └── test └── mod.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | /target 2 | Dockerfile 3 | .gitignore 4 | -------------------------------------------------------------------------------- /.github/workflows/build-image.yaml: -------------------------------------------------------------------------------- 1 | name: Build server image 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - ./README.md 9 | 10 | env: 11 | REGISTRY: ghcr.io 12 | IMAGE_NAME: elhmn/wake-server 13 | 14 | jobs: 15 | build-and-push-image: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v2 24 | 25 | - name: Log in to the Container registry 26 | uses: docker/login-action@v2 27 | with: 28 | registry: ${{ env.REGISTRY }} 29 | username: ${{ github.actor }} 30 | password: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Extract metadata (tags, labels) for Docker 33 | id: meta 34 | uses: docker/metadata-action@v4 35 | with: 36 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 37 | tags: | 38 | type=sha,enable=true,priority=100,prefix=,suffix=,format=long 39 | 40 | - name: Build and push Docker image 41 | uses: docker/build-push-action@v3 42 | with: 43 | context: ./ 44 | push: true 45 | tags: ${{ steps.meta.outputs.tags }} 46 | labels: ${{ steps.meta.outputs.labels }} 47 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yaml: -------------------------------------------------------------------------------- 1 | name: Code format 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | lint: 8 | name: Format 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | - name: "check format" 16 | run: | 17 | make fmt 18 | -------------------------------------------------------------------------------- /.github/workflows/players-lint.yaml: -------------------------------------------------------------------------------- 1 | name: Players Lint 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - 'wake/**' 7 | 8 | jobs: 9 | lint: 10 | name: Lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | - name: install-dependencies 18 | run: | 19 | sudo apt-get install g++ pkg-config libx11-dev libasound2-dev libudev-dev 20 | sudo apt-get install libwayland-dev libxkbcommon-dev 21 | - name: "check lint with clippy" 22 | run: | 23 | make lint-players 24 | -------------------------------------------------------------------------------- /.github/workflows/publish-binary.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - v[0-9]+.* 10 | 11 | jobs: 12 | create-release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: taiki-e/create-gh-release-action@v1 17 | with: 18 | # (optional) Path to changelog. 19 | changelog: CHANGELOG_WAKE.md 20 | # (required) GitHub token for creating GitHub Releases. 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | upload-assets: 24 | strategy: 25 | matrix: 26 | os: 27 | - ubuntu-latest 28 | - macos-latest 29 | - windows-latest 30 | runs-on: ${{ matrix.os }} 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: taiki-e/upload-rust-binary-action@v1 34 | with: 35 | # (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload. 36 | # Note that glob pattern is not supported yet. 37 | bin: wake 38 | # (optional) On which platform to distribute the `.tar.gz` file. 39 | # [default value: unix] 40 | # [possible values: all, unix, windows, none] 41 | tar: unix 42 | # (optional) On which platform to distribute the `.zip` file. 43 | # [default value: windows] 44 | # [possible values: all, unix, windows, none] 45 | zip: windows 46 | # (required) GitHub token for uploading assets to GitHub Releases. 47 | token: ${{ secrets.GITHUB_TOKEN }} 48 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | lint: 8 | name: Test 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | toolchain: stable 15 | - name: install-dependencies 16 | run: | 17 | sudo apt-get install g++ pkg-config libx11-dev libasound2-dev libudev-dev 18 | sudo apt-get install libwayland-dev libxkbcommon-dev 19 | - name: "test" 20 | run: | 21 | make test 22 | -------------------------------------------------------------------------------- /.github/workflows/wake-lint.yaml: -------------------------------------------------------------------------------- 1 | name: Wake Lint 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - 'players/**' 7 | 8 | jobs: 9 | lint: 10 | name: Lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | - name: "check lint with clippy" 18 | run: | 19 | make lint-wake 20 | -------------------------------------------------------------------------------- /.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 | 13 | # Added by cargo 14 | 15 | /target 16 | 17 | #custom 18 | tmp 19 | -------------------------------------------------------------------------------- /.vimspector.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "launch": { 4 | "adapter": "CodeLLDB", 5 | "filetypes": ["rust"], 6 | "configuration": { 7 | "request": "launch", 8 | "program": "${workspaceRoot}/target/debug/wake", 9 | "args": ["scan", "https://github.com/osscameroon/osscameroon-website"], 10 | "breakpoints": { 11 | "exception": { 12 | "cpp_throw": "Y", 13 | "cpp_catch": "Y" 14 | } 15 | } 16 | } 17 | }, 18 | "launch-server": { 19 | "adapter": "CodeLLDB", 20 | "filetypes": ["rust"], 21 | "configuration": { 22 | "request": "launch", 23 | "program": "${workspaceRoot}/target/debug/wake", 24 | "args": ["serve", "-p", "3000"], 25 | "breakpoints": { 26 | "exception": { 27 | "cpp_throw": "Y", 28 | "cpp_catch": "Y" 29 | } 30 | } 31 | } 32 | }, 33 | "attach": { 34 | "adapter": "CodeLLDB", 35 | "filetypes": ["rust", "c", "cpp", "jai"], 36 | "configuration": { 37 | "request": "attach", 38 | "program": "${workspaceRoot}/${fileBasenameNoExtension}", 39 | "PID": "${PID}", 40 | "breakpoints": { 41 | "exception": { "all": "N", "catch": "N" } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CHANGELOG_WAKE.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | - ### Fixes 4 | - Nothing got fixed 5 | 6 | - ### Maintenance 7 | - Nothing got maintained 8 | 9 | - ### Features 10 | - Nothing got added 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "wake", 4 | "players", 5 | "waking-git-core", 6 | ] 7 | resolver="2" 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.67-slim AS base 2 | 3 | WORKDIR /build 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | g++ \ 7 | pkg-config \ 8 | libssl-dev 9 | 10 | # Create appuser 11 | ENV USER=user 12 | ENV UID=10001 13 | 14 | RUN adduser \ 15 | --disabled-password \ 16 | --gecos "" \ 17 | --home "/nonexistent" \ 18 | --shell "/sbin/nologin" \ 19 | --no-create-home \ 20 | --uid "${UID}" \ 21 | "${USER}" 22 | 23 | COPY . ./ 24 | 25 | RUN cargo build -p wake --release 26 | 27 | WORKDIR /app 28 | 29 | RUN cp /build/target/release/wake . 30 | 31 | FROM debian:bullseye 32 | 33 | COPY --from=base /etc/passwd /etc/passwd 34 | COPY --from=base /etc/group /etc/group 35 | 36 | WORKDIR /server 37 | 38 | COPY --from=base /app/wake . 39 | 40 | # set user to `user` 41 | USER user 42 | 43 | # expose api port 44 | EXPOSE 3000 45 | 46 | CMD ["./wake", "serve", "--port", "3000"] 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Boris Mbarga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | WAKE_FOLDER = .wake 4 | BIN_PATH = ~/$(WAKE_FOLDER)/bin 5 | PLAYERS_BIN_PATH = ~/$(WAKE_FOLDER)/bin/players 6 | PLAYERS_TARGET = ./target/debug/players 7 | WAKE_TARGET = ./target/debug/wake 8 | # The test server port is used when running tests on the 9 | # `wake serve -p ` command. 10 | TEST_SERVER_PORT = 4242 11 | 12 | ## build: build application binary. 13 | .PHONY: build 14 | build: install-players 15 | cargo build 16 | 17 | ## run: run an example. 18 | .PHONY: run 19 | run: install-players 20 | cargo run -p wake -- play shmup https://github.com/osscameroon/osscameroon-website 21 | 22 | ## serve: start the wake server. 23 | .PHONY: serve 24 | serve: 25 | cargo run -p wake -- serve -p 3000 26 | 27 | ## build-wake: build wake binary. 28 | .PHONY: build-wake 29 | build-wake: $(WAKE_TARGET) 30 | 31 | $(WAKE_TARGET): 32 | cargo build -p wake 33 | 34 | ## install-players: install players binary. 35 | .PHONY: install-players 36 | install-players: $(PLAYERS_BIN_PATH) 37 | 38 | $(PLAYERS_BIN_PATH): 39 | mkdir -p ~/$(WAKE_FOLDER)/bin/ 40 | cp $(PLAYERS_TARGET) $(BIN_PATH) 41 | 42 | ## build-players: build players binary. 43 | .PHONY: build-players 44 | build-players: $(PLAYERS_TARGET) 45 | 46 | $(PLAYERS_TARGET): 47 | cargo build -p players 48 | 49 | .PHONY: install-deps 50 | install-deps: 51 | ifeq (, $(shell which cargo)) 52 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 53 | endif 54 | 55 | ## test: run tests 56 | .PHONY: test 57 | test: install-deps 58 | -lsof -i :$(TEST_SERVER_PORT) | grep LISTEN | awk '{print $$2}' | xargs kill -9 59 | SERVER_PORT=$(TEST_SERVER_PORT) cargo test -- --test-threads 1 60 | 61 | ## lint: run linter over the entire code base 62 | .PHONY: lint 63 | lint: install-deps 64 | cargo clippy -- -D warnings || (echo "\nFix linting errors with \`__CARGO_FIX_YOLO=1 cargo clippy --fix\`"; exit 1) 65 | 66 | ## lint-players: run linter over the players workspace 67 | .PHONY: lint 68 | lint-players: install-deps 69 | cargo clippy -p players -- -D warnings || (echo "\nFix linting errors with \`__CARGO_FIX_YOLO=1 cargo clippy --fix\`"; exit 1) 70 | 71 | ## lint-wake: run linter over the wake workspace 72 | .PHONY: lint 73 | lint-wake: install-deps 74 | cargo clippy -p wake -- -D warnings || (echo "\nFix linting errors with \`__CARGO_FIX_YOLO=1 cargo clippy --fix\`"; exit 1) 75 | 76 | ## fmt: check your code format 77 | .PHONY: fmt 78 | fmt: install-deps 79 | cargo fmt --check || (echo "\nFix formatting errors with \`cargo fmt\`"; exit 1) 80 | 81 | ## install-hooks: install local git hooks 82 | .PHONY: install-hooks 83 | install-hooks: 84 | ln -s $(PWD)/githooks/pre-push .git/hooks/pre-push 85 | 86 | ## clean: remove generated files 87 | .PHONY: clean 88 | clean: 89 | rm -rf ~/$(WAKE_FOLDER)/bin/ 90 | rm -rf $(BIN_PATH) 91 | rm -rf $(WAKE_TARGET) 92 | rm -rf $(PLAYERS_TARGET) 93 | 94 | .PHONY: all 95 | all: help 96 | 97 | .PHONY: help 98 | help: Makefile 99 | @echo " You can build \`wake\` using \`make build\`" 100 | @echo " or run an example using \`make run\`" 101 | @echo "" 102 | @echo " Choose a command..." 103 | @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' 104 | @echo "" 105 | @echo "You could run it using cargo commands directly" 106 | @echo "" 107 | @echo "Make sure to build and install the player before running it:" 108 | @echo "\`"make build-players \; make install-players"\`" 109 | @echo "" 110 | @echo "Then run: \`"cargo run -p wake -- play shmup https://github.com/elhmn/waking-git"\`" 111 | @echo "" 112 | @echo "Scan a repo:" 113 | @echo "\`"cargo run -p wake -- scan https://github.com/elhmn/waking-git"\`" 114 | @echo "" 115 | @echo "Run the player:" 116 | @echo "\`"cargo run -p players -- shmup ~/.wake/scanner/github-com-elhmn-waking-git/shmup-converted.json"\`" 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waking-git 2 | Git world generator. `waking-git` uses the code structure, and evolution of 3 | your git repository to generate a world for you to explore. 4 | 5 | The program will scan your repository, and extract from its directory and code structure 6 | revelant data that can be used to generate a 2D/3D playable world/game. The structure 7 | of your code (Intefaces, Classes, functions etc...) can be used to create living creatures. 8 | Those creatures can be mobs/enemies depending on how well their structure match well known 9 | code smell, in the programming language the code is written on. 10 | 11 | ## Little shmup example (WIP) 12 | 13 | Here is work in progress shmup game that is currently being built on the `implement-a-shmup-demo` branch: 14 | 15 | 16 | ![Kapture 2023-06-10 at 10 09 29](https://github.com/elhmn/waking-git/assets/5704817/56a69149-4bc0-4676-8596-d15291071d76) 17 | 18 | 19 | 20 | 21 | 22 | ## Development 23 | The project is built in `rust`, and uses git as a strong dependency. 24 | 25 | ### Dependencies 26 | - [Rust](https://www.rust-lang.org/tools/install) 27 | - [Git](https://git-scm.com/downloads) 28 | - [Docker](https://docs.docker.com/engine/install/) 29 | - [GNU make](https://www.gnu.org/software/make/) 30 | - [Bevy dependencies](https://github.com/bevyengine/bevy/blob/latest/docs/linux_dependencies.md) 31 | 32 | ### Architecture 33 | 34 | Please read the draft of the [architecture](https://github.com/elhmn/waking-git/blob/main/docs/Architecture.md). 35 | 36 | ### How to run ? 37 | 38 | In order to run `waking-git` you need to [install](https://www.rust-lang.org/tools/install) the rust tool chain. 39 | 40 | **How to scan a repository ?** 41 | 42 | ```console 43 | $ cargo run -p wake -- scan https://github.com/elhmn/waking-git 44 | ``` 45 | 46 | **How to play ?** 47 | 48 | First make sure to build and install the players using the following command: 49 | 50 | ```console 51 | $ make build-players && make install-players 52 | ``` 53 | 54 | Then you can run the player using the following command: 55 | 56 | ```console 57 | $ cargo run -p wake -- play shmup https://github.com/elhmn/waking-git 58 | ``` 59 | 60 | How to run the server ? 61 | 62 | ```console 63 | $ cargo run -p wake serve -p 3000 64 | ``` 65 | 66 | You could request data from the server using the following command: 67 | ```console 68 | $ curl -X GET -vsS -d '{"repo_url": "https://github.com/elhmn/cgit"}' \ 69 | -H 'Content-Type: application/json' localhost:3000/scan/extracted | jq 70 | ``` 71 | 72 | The server supports the following routes: 73 | 74 | - `GET /scan/extracted` - Extract data from a repository 75 | - `GET /scan/converted` - Convert extracted data to a world representation 76 | - `GET /scan` - Scan a repository and return a tarball containing extracted and converted data 77 | 78 | ### How to test ? 79 | 80 | Run the entire test suite using, 81 | ```console 82 | $ make test 83 | ``` 84 | 85 | Check linting 86 | ```console 87 | $ make lint 88 | ``` 89 | 90 | Check code format 91 | ```console 92 | $ make fmt 93 | ``` 94 | 95 | Setup git local hooks 96 | ```console 97 | $ make install-hooks 98 | ``` 99 | 100 | ### More 101 | 102 | You can run `make` OR `make help` to find out more commands 103 | ```console 104 | $ make 105 | You can build `wake` using `make build` 106 | or run an example using `make run` 107 | 108 | Choose a command... 109 | build build application binary. 110 | run run an example. 111 | serve start the wake server. 112 | build-wake build wake binary. 113 | install-players install players binary. 114 | build-players build players binary. 115 | test run tests 116 | lint run linter over the entire code base 117 | lint-players run linter over the players workspace 118 | lint-wake run linter over the wake workspace 119 | fmt check your code format 120 | install-hooks install local git hooks 121 | 122 | You could run it using cargo commands directly 123 | 124 | Make sure to build and install the player before running it: 125 | `make build-players ; make install-players` 126 | 127 | Then run: `cargo run -p wake -- play shmup https://github.com/elhmn/waking-git` 128 | 129 | Scan a repo: 130 | `cargo run -p wake -- scan https://github.com/elhmn/waking-git` 131 | 132 | Run the player: 133 | `cargo run -p players -- shmup /Users/elhmn/.wake/scanner/github-com-elhmn-waking-git/shmup-converted.json` 134 | ``` 135 | 136 | ## Resources 137 | 138 | Building this project require to know what is the of state code scanning and data visualisation researches. 139 | Here is a list of research papers and code visualisers that will be useful to work on this project: 140 | 141 | - [codeology](https://demo.marpi.pl/codeology/) brings life to your source code, by generating a creature that represents your code structure 142 | - [repo-visualization](https://githubnext.com/projects/repo-visualization#explore-for-yourself) 143 | - [Gource](https://github.com/acaudwell/Gource) is a git history visualiser 144 | - [Visual softare analytics](https://home.uni-leipzig.de/svis) (VSA) is a research group that explore different ways to represent complex software systems 145 | - [getaviz](https://home.uni-leipzig.de/svis/getaviz/index.php?setup=web/RD%20C&model=RD%20C%20busybox&aframe=true) is a [tool](https://github.com/softvis-research/Getaviz) built by the VSA, to visualise code structure 146 | - [static code analysers](https://github.com/analysis-tools-dev/static-analysis) 147 | - [dynamic code analysers](https://github.com/analysis-tools-dev/dynamic-analysis) 148 | - [Gephi](https://gephi.org) 149 | - [Rust code analysis](https://github.com/mozilla/rust-code-analysis) 150 | - [emerge](https://github.com/glato/emerge) 151 | - Some research papers: 152 | - [1](https://www.researchgate.net/publication/328282991_Towards_an_Open_Source_Stack_to_Create_a_Unified_Data_Source_for_Software_Analysis_and_Visualization) 153 | - [2](https://www.researchgate.net/publication/328019593_The_Recursive_Disk_Metaphor_-_A_Glyph-based_Approach_for_Software_Visualization) 154 | - [3](https://www.researchgate.net/publication/328019663_Past_Present_and_Future_of_3D_Software_Visualization_-_A_Systematic_Literature_Analysis) 155 | - [4](https://www.researchgate.net/publication/328019394_A_Structured_Approach_for_Conducting_a_Series_of_Controlled_Experiments_in_Software_Visualization) 156 | - [5](https://www.researchgate.net/publication/320083290_GETAVIZ_Generating_Structural_Behavioral_and_Evolutionary_Views_of_Software_Systems_for_Empirical_Evaluation) 157 | - [6](https://www.researchgate.net/publication/318570435_Generative_Software_Visualization_Automatic_Generation_of_User-Specific_Visualizations) 158 | - [7](https://www.researchgate.net/publication/265428652_MSE_and_FAMIX_30_an_Interexchange_Format_and_Source_Code_Model_Family) 159 | - [8](https://www.researchgate.net/publication/281743434_How_to_Master_Challenges_in_Experimental_Evaluation_of_2D_versus_3D_Software_Visualizations) 160 | - [9](https://www.researchgate.net/publication/220818819_A_Visual_Analytics_Tool_for_Software_Project_Structure_and_Relationships_among_Classes) 161 | - [10](https://opus-htw-aalen.bsz-bw.de/frontdoor/deliver/index/docId/658/file/ICCSE16-SEE.pdf) 162 | - [11](https://blog.ndepend.com/visualize-code-with-software-architecture-diagrams/) 163 | - [12](https://www.researchgate.net/publication/347700460_rust-code-analysis_A_Rust_library_to_analyze_and_extract_maintainability_information_from_source_codes) 164 | -------------------------------------------------------------------------------- /docs/Architecture.md: -------------------------------------------------------------------------------- 1 | # Waking-git architecture (draft) 2 | 3 | This is a rough description of what the program architecture is supposed to look like. 4 | 5 | ## How does it work ? 6 | 7 | - We first pass to the program a `git` repository `http` or `https` url. 8 | The repository url can come from github.com, gitlab.com or any other source. 9 | - The repository will then be scanned and a set of relevant data will be extracted from the repository tree and source code. 10 | This data will be stored in a directory named after a slug of your repository `owner/name`. 11 | - The Player will use data extracted from your repository to create a world to explore. 12 | 13 | ### Components 14 | 15 | ```mermaid 16 | flowchart LR 17 | 18 | Repos --> |git repository| Extractor 19 | Extractor --> |git or code structure data| Converter 20 | Converter --> |game entities| Player 21 | ``` 22 | 23 | From the **Repos** we fetched and stored on disk, the **Extractor** will get data about the git tree 24 | and the code structure. This data will then be picked up and **Converted** into a list of game entities 25 | such as NPCs, enemies, bosses and decors. This list of entities will be used by the **Player** 26 | to generate the game or simulated world. 27 | 28 | #### Repos 29 | 30 | This is the git repository pulled from a webserver or present on disk, that will be used 31 | as a data source for `waking-git`. 32 | 33 | #### Extractor 34 | 35 | It will go through your repository and extract data from your git object tree and 36 | your source code architecture. 37 | 38 | From your `git object tree` will be extracted data such as: 39 | 40 | - commit objects 41 | - blob objects 42 | - lfs objects (they are blob objects but might be treated differently by the extractor) 43 | - tag objects 44 | - tree objects 45 | - refs 46 | - the size, name and permissions of each file and directory 47 | - data regarding your git repository overall health, using `git-sizer` for example 48 | 49 | In the `source code architecture` we can find these interesting artifacts: 50 | 51 | - code metrics: (we can get those from [rust_code_analysis](https://docs.rs/rust-code-analysis/latest/rust_code_analysis/index.html)) 52 | - CC: it calculates the code complexity examining the control flow of a program. 53 | - SLOC: it counts the number of lines in a source file. 54 | - PLOC: it counts the number of physical lines (instructions) contained in a source file. 55 | - LLOC: it counts the number of logical lines (statements) contained in a source file. 56 | - CLOC: it counts the number of comments in a source file. 57 | - BLANK: it counts the number of blank lines in a source file. 58 | - HALSTEAD: it is a suite that provides a series of information, such as the effort required to maintain the analyzed code, the size in bits to store the program, the difficulty to understand the code, an estimate of the number of bugs present in the codebase, and an estimate of the time needed to implement the software. 59 | - MI: it is a suite that allows to evaluate the maintainability of a software. 60 | - NOM: it counts the number of functions and closures in a file/trait/class. 61 | - NEXITS: it counts the number of possible exit points from a method/function. 62 | - NARGS: it counts the number of arguments of a function/method. 63 | - lines of code per file 64 | - file type 65 | - programming languages 66 | - code structure: 67 | - functions 68 | - classes 69 | - variables 70 | - const 71 | - structs 72 | - interfaces/traits 73 | - etc... 74 | - code smells (non idomatic ways of writing code, depends on the programming language) 75 | - linting issues (we can run standard linters on the project) 76 | - security breaches 77 | - code comments (can be used to generate stories or dialogue for NPC characters) 78 | 79 | **Output:** 80 | 81 | The extractor will spit out a list of data points, that can then be stored in a json file. 82 | 83 |
84 | Example of json data 85 | 86 | ```json 87 | { 88 | "git_health": { 89 | "commits": { 90 | "count": "723k", 91 | "total_size": "525 Mib" 92 | }, 93 | "tree": { 94 | "count": "3.40 M", 95 | "total_size": "9.00 Gib", 96 | "total_tree_entries": "264 M" 97 | } 98 | //...more entries refer to git-sizer's output -> https://github.com/github/git-sizer#usage 99 | }, 100 | "objects": [ 101 | "data_point_hash_blob": { 102 | "type": "git", 103 | "git": { 104 | "type": "blob", 105 | "sha": "eb50cab7d9c22101393e693c00aeff662e256d1b", 106 | "permissions": "100644", 107 | "name": "README.md" 108 | } 109 | }, 110 | "data_point_hash_tree": { 111 | "type": "git", 112 | "git": { 113 | "type": "tree", 114 | "sha": "01da9bd182c510aea3bf57281c9b31b5d571a730", 115 | "permissions": "", //This is the root tree so it does not have permissions (still not 100% sure about that though) 116 | "name": "", //root tree should have no name 117 | "objects": [ 118 | {"blob": "eb50cab7d9c22101393e693c00aeff662e256d1b"} , //the sha of the `README.md` blob object 119 | {"tree": "accb07f852119953193bf379799e2cafbf3505fd"}, //cmd tree object 120 | {"blob": "f2e389d48f79b0909bd7ad0c6ce5299a019f1cc7"}, //LICENCE blob object 121 | ] 122 | } 123 | }, 124 | "data_point_hash_commit": { 125 | "type": "git", 126 | "dangling": true, //the commit is unreachable 127 | "git": { 128 | "type": "commit", 129 | "sha": "01da9bd182c510aea3bf57281c9b31b5d571a730", 130 | "author": "goreleaserbot 1630490004 +0200", //root tree should have no name 131 | "commiter": "GitHub 1635941609 +0100", 132 | "message": "Brew formula update for ckp version v0.17.1", //commit message 133 | "objects": [ 134 | "tree": "accb07f852119953193bf379799e2cafbf3505fd", //cmd tree object 135 | "parents": [ 136 | "d87dddc4fed262617ee27928bb65cc45274c96ca", 137 | "" //it is possible to have more than one parent only if it is a merge commit 138 | ] 139 | ] 140 | } 141 | }, 142 | "data_point_hash_code": { 143 | type": "code" 144 | //...TODO, write objects extracted from code structure static analysis 145 | } 146 | ] 147 | //...TODO expand this example as our understanding of the structure increases 148 | } 149 | } 150 | ``` 151 | 152 |
153 | 154 | #### Converter 155 | 156 | The **Converter** receives a set of data points and transforms them into a list of entities 157 | that are understandable by the **Player** such as NPCs, enemies, bosses and decors elements. 158 | 159 |
160 | Example of json data 161 | 162 | ```json 163 | [ 164 | { 165 | "type": "npc", 166 | "name": "main.c", 167 | "entity": { 168 | "type": "pedestrian", 169 | "class": "villager", 170 | //dialogue can be extracted from file comments or file commit messages (less sure about the former) 171 | "dialogue": [ 172 | { "line": 10, "comment": "a comment someone left in the code" }, 173 | { "line": 15, "comment": "a comment found on a function" } 174 | ], 175 | //when the position is not specified the entity can be placed anywhere on screen 176 | "position": { 177 | "x": 1, 178 | "y": 10 179 | } 180 | } 181 | }, 182 | { 183 | "type": "ennemy", 184 | "name": "sha", 185 | "entity": { 186 | //ghosts can be generated from dangling commits or blobs 187 | "type": "ghost", 188 | "class": "c", 189 | "position": { 190 | "x": 90, 191 | "y": 20 192 | } 193 | } 194 | }, 195 | { 196 | "type": "decor", 197 | "name": "sha", 198 | "entity": { 199 | "type": "block", 200 | "class": "a", 201 | "position": { 202 | "x": 0, 203 | "y": 0 204 | } 205 | } 206 | }, 207 | { 208 | "type": "boss", 209 | "name": "sha", 210 | "entity": { 211 | //ghosts can be generated from dangling commits or blobs 212 | "type": "data-race", 213 | "class": "a", 214 | //dialogue can be extracted from file comments or file commit messages (less sure about the former) 215 | "dialogue": [ 216 | { "line": 10, "comment": "a comment someone left in the code" }, 217 | { "line": 15, "comment": "a comment found on a function" } 218 | //...more 219 | ], 220 | //when the position is not specified the entity can be placed anywhere on screen 221 | "position": { 222 | "x": 1, 223 | "y": 10 224 | } 225 | } 226 | } 227 | //...more 228 | ] 229 | ``` 230 | 231 |
232 | 233 | #### Player 234 | 235 | It ingests data from the converter and generates a 2D/3D playable game/simulator. 236 | 237 | ## How to run? 238 | 239 | ### Code scanner: 240 | 241 | The code scanner receives an https url and an optional commit `reference` 242 | to download the repository content, if the reference is not specified 243 | the scanner will use the main branch as default reference and download. 244 | 245 | ```console 246 | $ wake scan https://github.com/elhmn/ckp 247 | ``` 248 | 249 | The code scanner will analyse your repository tree structure and your source file, 250 | and generate a set of files that will later be used by the world generator to create 251 | a playable 2D or 3D world. 252 | 253 | ### The player: 254 | 255 | The player uses data extracted by the `code scanner` to generate a playable world. 256 | 257 | ```console 258 | $ wake play --dir 259 | ``` 260 | 261 | you can use the `--dir` flag to set the directory that should be used to generate the world 262 | use to create the world to explore. 263 | 264 | The player can also use the `https` url of your source code. This will automatically call 265 | the scanner and create the playable world created by the scanner. 266 | 267 | ```console 268 | $ wake play 269 | ``` 270 | -------------------------------------------------------------------------------- /docs/shump-converter.md: -------------------------------------------------------------------------------- 1 | # shoot 'em up (shump) converter 2 | 3 | This document layout the design of our basic shoot 'em up converter 4 | 5 | ## Inspiration 6 | 7 | To demonstrate how `waking-git` can be used to give life to worlds full of life, 8 | we decided to build a basic `shmup` that will heavily draw inspiration from 9 | [Near Automata](https://www.youtube.com/watch?v=wJxNhJ8fjFk)'s [shmup phases](https://www.youtube.com/watch?v=jT2jOeFo5HQ). 10 | 11 | `Near Automata` `shump` phases have a minimalistic design that can be easily used to create 12 | a prototype of what a `shump` built using git repository data would look like. 13 | 14 | ## Convertion 15 | 16 | This `shmup` converter will get json data from the `extractor` and translate them 17 | into a set of data that can be exploitated to generate a 2D/3D playable game. 18 | It uses the `git` and `code` structure extracted from your repository to generate a list of `scenes` 19 | that will each contain a list of `entities`. 20 | 21 | Here is an example of data generated by the converter: 22 | 23 |
24 | Shmup converter output 25 | 26 | ```json 27 | { 28 | "": { 29 | "id": "", 30 | "sub_scenes": ["sub_scene_1_id", "sub_scene_2_id"], 31 | "entities": { 32 | "entity_id": { 33 | "id": "entity_id", 34 | "scene_id": "scene_id", 35 | "name": "readme.md", 36 | "type": "circle", //more visual than anything else 37 | "movement_pattern": "pattern_1", 38 | "color": "#B9D9FF", 39 | "weapon": "weapon_type_1", 40 | "speed": 0.2, // from 0 to 1 41 | "hp": 0.1, // from 0 to 1 42 | "shield": "none", 43 | "size": 0.2, // from 0 to 1, 44 | "destructible": true // is the entity destructible 45 | }, 46 | "entity_id_2": { 47 | "id": "entity_id_2", 48 | "scene_id": "scene_id", 49 | "name": "src/main.rs", 50 | "type": "square", 51 | "movement_pattern": "pattern_1", 52 | "color": "#34EB6B", 53 | "weapon": "weapon_type_2", 54 | "speed": 0.4, // from 0 to 1 55 | "hp": 0.4, // from 0 to 1 56 | "shield": "none", 57 | "destructible": true // is the entity destructible 58 | } 59 | } 60 | } 61 | } 62 | ``` 63 | 64 |
65 | 66 | ### converter objects 67 | 68 | **Scene object:** 69 | | key | type | default | example| description | 70 | | :------------: | :----------: | :--------: | :--------: | ---------------------------------------------- | 71 | | `id` | `string` | - | - | the scene id is mapped to a git tree object id | 72 | | `sub_scenes` | `Array` | - | - | the list of git sub tree ids | 73 | | `entities` | `Array` | - | - | List of `Entities` | 74 | 75 | **Entity object:** 76 | | key | type | default | example | description | 77 | | :------------: | :----------: | :----: | :--------: | ---------------------------------------------- | 78 | | `id` | `string` | - | `""` | the entity id is mapped to a git blob object id | 79 | | `scene_id` | `string` | - | `""`| the scene the object belongs to | 80 | | `name` | `string` | - | `"src/readme.md"` | the object name, can be the blob file name | 81 | | `kind` | `string` | - | `"circle"` | the entity kind is used to know how to display the object | 82 | | `color` | `string` | - | `"red"` | the color is another visual information, mapped to the language's color | 83 | | `weapon` | `string` | - | `"weapon_t1"` | type of weapon | 84 | | `movement_pattern` | `string` | - | `"mvmt_1"` | the movement pattern followed by the entity | 85 | | `speed` | `float` | - | `0.1` | the object speed can be extracted from blob size, value between 0 and 1 | 86 | | `hp` | `float` | - | `0.2` | the entity hp, value between 0 and 1 | 87 | | `size` | `float` | - | `0.2` | the entity size, value between 0 and 1 | 88 | | `shield` | `string` | - | `"full" \| "half-guard"` | wether or not the entity has a shield | 89 | | `destructible` | `bool` | - | `true` | wether or not the entity can be destroyed | 90 | -------------------------------------------------------------------------------- /githooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | remote="$1" 4 | url="$2" 5 | 6 | make lint || exit 1 7 | make fmt || exit 1 8 | make test || exit 1 9 | 10 | exit 0 11 | -------------------------------------------------------------------------------- /players/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "players" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | core = { package="waking-git-core", path = "../waking-git-core" } 10 | 11 | clap = { version = "4.0.23", features = ["derive"] } 12 | #for fast compilation of 13 | # your program consider using the dynamic feature, 14 | # with `--features bevy/dynamic` flags. 15 | # example: cargo run --features bevy/dynamic 16 | # This does not, work on windows. 17 | # Find out more https://docs.rs/bevy_dylib/latest/bevy_dylib/#the-recommended-way 18 | # and https://docs.rs/bevy_dylib/latest/bevy_dylib/#the-recommended-way 19 | bevy = { version = "0.9.0" } 20 | bevy-inspector-egui = "0.15.0" 21 | serde = { version = "1.0", features = ["derive"] } 22 | serde_json = "1.0" 23 | 24 | rand = "0.8.5" 25 | -------------------------------------------------------------------------------- /players/src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod shmup; 2 | 3 | use clap::{Parser, Subcommand}; 4 | 5 | #[derive(Subcommand, Debug)] 6 | enum Commands { 7 | /// Shmup will start the shmup game 8 | Shmup(shmup::RunArgs), 9 | } 10 | 11 | /// `play` launches a selection of games or simulators 12 | #[derive(Parser, Debug)] 13 | #[clap(author, version, about, long_about = None)] 14 | struct Cli { 15 | #[clap(subcommand)] 16 | command: Commands, 17 | } 18 | 19 | pub fn run() { 20 | let cli = Cli::parse(); 21 | 22 | match &cli.command { 23 | Commands::Shmup(args) => { 24 | shmup::run(args); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /players/src/cmd/shmup.rs: -------------------------------------------------------------------------------- 1 | use crate::shmup; 2 | use clap::Args; 3 | use core::converters; 4 | use std::fs::File; 5 | use std::io::{Error, Read}; 6 | 7 | #[derive(Args, Debug)] 8 | pub struct RunArgs { 9 | /// The json file containing the converted data 10 | #[clap(value_name = "FILE", index = 1)] 11 | file: Option, 12 | } 13 | 14 | pub fn run(args: &RunArgs) { 15 | let file = args.file.clone().unwrap_or_default(); 16 | 17 | //load the converted.json data from the file 18 | let converted_data = match load_converted_data(&file) { 19 | Ok(d) => d, 20 | Err(err) => { 21 | println!("Error: failed to load converted data: {err}"); 22 | return; 23 | } 24 | }; 25 | 26 | shmup::run(converted_data); 27 | } 28 | 29 | fn load_converted_data(file: &str) -> Result { 30 | let mut f = File::open(file)?; 31 | let mut data = String::new(); 32 | f.read_to_string(&mut data)?; 33 | let data: converters::shmup::Data = serde_json::from_str(&data)?; 34 | Ok(data) 35 | } 36 | -------------------------------------------------------------------------------- /players/src/main.rs: -------------------------------------------------------------------------------- 1 | mod cmd; 2 | mod shmup; 3 | 4 | fn main() { 5 | cmd::run(); 6 | } 7 | -------------------------------------------------------------------------------- /players/src/shmup/components/camera.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component)] 4 | pub struct MainCamera; 5 | -------------------------------------------------------------------------------- /players/src/shmup/components/enemy.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component, Debug, Default)] 4 | pub struct Enemy { 5 | pub id: String, 6 | pub scene_id: String, 7 | pub color: String, 8 | pub hp: i32, 9 | pub kind: String, 10 | pub name: String, 11 | pub speed: f32, 12 | pub destructible: bool, 13 | pub shield: String, 14 | } 15 | 16 | #[derive(Component, Debug, Default)] 17 | pub struct EnemyCollider {} 18 | -------------------------------------------------------------------------------- /players/src/shmup/components/enemy_bullets.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | pub const COLOR_DESTRUCTIBLE: &str = "fc6c12"; 4 | pub const COLOR_UNDESTRUCTIBLE: &str = "cc4dcc"; 5 | 6 | pub enum BulletKind { 7 | Destructible, 8 | Undestructible, 9 | } 10 | 11 | #[derive(Component, Debug)] 12 | pub struct Bullet { 13 | pub life_timer: Timer, 14 | pub speed: f32, 15 | pub damage: f32, 16 | pub direction: Vec2, 17 | } 18 | 19 | #[derive(Component, Debug, Default)] 20 | // A destructible bullet is a bullet that can be destroyed by a player 21 | pub struct DestructibleBullet {} 22 | 23 | #[derive(Component, Debug, Default)] 24 | // An indesctuctible bullet is a bullet that can't be destroyed by a player 25 | pub struct UndestructibleBullet {} 26 | 27 | #[derive(Component, Debug, Default)] 28 | pub struct BulletCollider {} 29 | 30 | impl Default for Bullet { 31 | fn default() -> Self { 32 | Self { 33 | life_timer: Timer::from_seconds(5., TimerMode::Once), 34 | speed: 500., 35 | damage: 1., 36 | direction: Vec2::new(0., 0.), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /players/src/shmup/components/guns.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | /// Basic enemy gun 4 | #[derive(Component, Debug)] 5 | pub struct SimpleGun { 6 | pub cooldown_timer: Timer, 7 | } 8 | 9 | impl Default for SimpleGun { 10 | fn default() -> Self { 11 | Self { 12 | cooldown_timer: Timer::from_seconds(2., TimerMode::Once), 13 | } 14 | } 15 | } 16 | 17 | #[derive(Component, Debug)] 18 | pub struct FastGun { 19 | pub cooldown_timer: Timer, 20 | pub count_fired_bullets: u8, 21 | } 22 | 23 | impl Default for FastGun { 24 | fn default() -> Self { 25 | Self { 26 | cooldown_timer: Timer::from_seconds(0.5, TimerMode::Once), 27 | count_fired_bullets: 0, 28 | } 29 | } 30 | } 31 | 32 | #[derive(Debug)] 33 | pub enum Direction { 34 | // Towards the player 35 | North, 36 | NorthEast, 37 | NorthWest, 38 | South, 39 | SouthEast, 40 | SouthWest, 41 | East, 42 | West, 43 | } 44 | 45 | #[derive(Component, Debug)] 46 | pub struct MultiDirectionCircleGun { 47 | pub cooldown_timer: Timer, 48 | pub count_fired_bullets: u8, 49 | pub directions: Vec, 50 | } 51 | 52 | impl Default for MultiDirectionCircleGun { 53 | fn default() -> Self { 54 | Self { 55 | cooldown_timer: Timer::from_seconds(2.5, TimerMode::Once), 56 | count_fired_bullets: 0, 57 | directions: vec![ 58 | Direction::North, 59 | Direction::NorthEast, 60 | Direction::NorthWest, 61 | Direction::South, 62 | Direction::SouthEast, 63 | Direction::SouthWest, 64 | Direction::East, 65 | Direction::West, 66 | ], 67 | } 68 | } 69 | } 70 | 71 | #[derive(Component, Debug)] 72 | pub struct MultiDirectionRectangleGun { 73 | pub cooldown_timer: Timer, 74 | pub count_fired_bullets: u8, 75 | pub directions: Vec, 76 | } 77 | 78 | impl Default for MultiDirectionRectangleGun { 79 | fn default() -> Self { 80 | Self { 81 | cooldown_timer: Timer::from_seconds(2.5, TimerMode::Once), 82 | count_fired_bullets: 0, 83 | directions: vec![ 84 | Direction::North, 85 | Direction::South, 86 | Direction::East, 87 | Direction::West, 88 | ], 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /players/src/shmup/components/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod camera; 2 | pub mod enemy; 3 | pub mod enemy_bullets; 4 | pub mod guns; 5 | pub mod patterns; 6 | pub mod player; 7 | pub mod player_bullet; 8 | -------------------------------------------------------------------------------- /players/src/shmup/components/patterns.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use rand::prelude::*; 3 | 4 | /// Pattern1 defines a movement pattern 5 | #[derive(Component, Debug)] 6 | // We could define better names 7 | pub struct Pattern1 { 8 | pub speed: f32, 9 | } 10 | 11 | /// Pattern2 defines a movement pattern 12 | #[derive(Component, Debug, Default)] 13 | pub struct Pattern2 { 14 | pub speed: f32, 15 | } 16 | 17 | /// MoveTowards 18 | #[derive(Component, Debug)] 19 | pub struct MoveTowards { 20 | pub max_prediction_time: f32, 21 | 22 | pub initial_speed: f32, 23 | pub max_speed: f32, 24 | pub speed: f32, 25 | //The angular speed 26 | pub rotation: f32, 27 | } 28 | 29 | impl Default for MoveTowards { 30 | fn default() -> Self { 31 | let initial_speed = 25.; 32 | let max_speed = 50.; 33 | Self { 34 | max_prediction_time: 1000., 35 | initial_speed, 36 | max_speed, 37 | speed: initial_speed, 38 | rotation: 0.1, 39 | } 40 | } 41 | } 42 | 43 | /// Pattern3 defines a movement pattern 44 | #[derive(Component, Debug)] 45 | pub struct Pattern3 { 46 | pub speed: f32, 47 | /// Indicate when should change direction 48 | pub timer: Timer, 49 | pub dir_x: f32, 50 | pub dir_y: f32, 51 | } 52 | 53 | pub fn pattern3_random_dir(p: &mut Pattern3) { 54 | let mut r = thread_rng(); 55 | let prob = r.gen_range(0..100); 56 | 57 | if (20..40).contains(&prob) { 58 | p.dir_x = 1.; 59 | p.dir_y = 1.; 60 | } else if (40..60).contains(&prob) { 61 | p.dir_x = -1.; 62 | p.dir_y = -1.; 63 | } else if (60..80).contains(&prob) { 64 | p.dir_x = 1.; 65 | p.dir_y = -1.; 66 | } else if (80..100).contains(&prob) { 67 | p.dir_x = -1.; 68 | p.dir_y = 1.; 69 | } 70 | } 71 | 72 | pub fn pattern3_random_time(p: &mut Pattern3) { 73 | let mut r = thread_rng(); 74 | let prob = r.gen_range(0..100); 75 | 76 | let mut t = 1.; 77 | if (20..40).contains(&prob) { 78 | t = 1.5; 79 | } else if (40..60).contains(&prob) { 80 | t = 2.; 81 | } else if (60..80).contains(&prob) { 82 | t = 2.5; 83 | } else if (80..100).contains(&prob) { 84 | t = 3.; 85 | } 86 | 87 | p.timer = Timer::from_seconds(t, TimerMode::Once); 88 | } 89 | 90 | impl Default for Pattern3 { 91 | fn default() -> Self { 92 | Pattern3 { 93 | timer: Timer::from_seconds(1., TimerMode::Once), 94 | dir_x: 1., 95 | dir_y: 1., 96 | speed: 1., 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /players/src/shmup/components/player.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component, Debug, Default)] 4 | pub struct Player { 5 | pub speed: f32, 6 | pub hp: f32, 7 | } 8 | 9 | #[derive(Component, Debug, Default)] 10 | pub struct PlayerCollider {} 11 | 12 | #[derive(Component, Debug, Default)] 13 | pub struct Velocity { 14 | pub x: f32, 15 | pub y: f32, 16 | } 17 | 18 | #[derive(Component, Debug)] 19 | pub struct Gun { 20 | pub cooldown_timer: Timer, 21 | } 22 | 23 | impl Default for Gun { 24 | fn default() -> Self { 25 | Self { 26 | cooldown_timer: Timer::from_seconds(0.05, TimerMode::Once), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /players/src/shmup/components/player_bullet.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component, Debug)] 4 | pub struct Bullet { 5 | pub life_timer: Timer, 6 | pub speed: f32, 7 | pub damage: f32, 8 | pub direction: Vec2, 9 | } 10 | 11 | #[derive(Component, Debug, Default)] 12 | pub struct BulletCollider {} 13 | 14 | impl Default for Bullet { 15 | fn default() -> Self { 16 | Self { 17 | life_timer: Timer::from_seconds(2., TimerMode::Once), 18 | speed: 2000., 19 | damage: 1., 20 | direction: Vec2::new(0., 0.), 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /players/src/shmup/config.rs: -------------------------------------------------------------------------------- 1 | //collision box color 2 | pub const COL_COLOR: &str = "26a64166"; 3 | pub const DEBUG: bool = false; 4 | 5 | pub fn get_col_color() -> String { 6 | if DEBUG { 7 | COL_COLOR.to_string() 8 | } else { 9 | "00000000".to_string() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /players/src/shmup/debug.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_inspector_egui::WorldInspectorPlugin; 3 | 4 | pub struct DebugPlugin; 5 | 6 | impl Plugin for DebugPlugin { 7 | fn build(&self, app: &mut App) { 8 | if cfg!(debug_assertions) { 9 | app.add_plugin(WorldInspectorPlugin::new()); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /players/src/shmup/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod components; 2 | pub mod config; 3 | pub mod debug; 4 | pub mod placer; 5 | pub mod plugin; 6 | pub mod systems; 7 | 8 | use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}; 9 | use bevy::time::FixedTimestep; 10 | use bevy::{app::PluginGroupBuilder, prelude::*}; 11 | 12 | use core::converters; 13 | 14 | const TIMESTEP_60_FPS: f64 = 1. / 60.; 15 | 16 | #[derive(Resource, Default, Debug)] 17 | pub struct WorldData(converters::shmup::Data); 18 | 19 | fn default_plugins() -> PluginGroupBuilder { 20 | DefaultPlugins.set({ 21 | WindowPlugin { 22 | window: WindowDescriptor { 23 | title: "waking-git: shoot 'em up".to_string(), 24 | ..default() 25 | }, 26 | ..default() 27 | } 28 | }) 29 | } 30 | 31 | pub fn run(data: converters::shmup::Data) { 32 | App::new() 33 | .add_plugins(default_plugins()) 34 | .add_plugin(LogDiagnosticsPlugin::default()) 35 | .add_plugin(FrameTimeDiagnosticsPlugin) 36 | .add_system_set(SystemSet::new().with_run_criteria(FixedTimestep::step(TIMESTEP_60_FPS))) 37 | .insert_resource(WorldData(data)) 38 | .add_plugin(plugin::ShmupPlugin) 39 | .run(); 40 | } 41 | -------------------------------------------------------------------------------- /players/src/shmup/placer.rs: -------------------------------------------------------------------------------- 1 | use super::components::enemy; 2 | use super::components::guns; 3 | use super::components::player; 4 | use super::config; 5 | use super::WorldData; 6 | use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; 7 | use core::shapes; 8 | use rand::prelude::*; 9 | 10 | const BG_MAP_SIZE: u32 = 100; 11 | const BG_MAP_BLOCK_SIZE: u32 = 30; 12 | const BG_GRID_WIDTH: f32 = 1.; 13 | 14 | const AREA_BLOCK_SIZE: f32 = 70.; 15 | const AREA_BLOCK_PADDING: f32 = 20.; 16 | const AREA_BLOCK_COL: i32 = 20; 17 | const AREA_BLOCK_ROW: i32 = 17; 18 | 19 | type Area = Vec>; 20 | 21 | pub struct Placer { 22 | pub area: Area, 23 | pub number_of_placed_entities: u32, 24 | pub area_block_size: f32, 25 | pub area_block_padding: f32, 26 | pub area_block_col: i32, 27 | pub area_block_row: i32, 28 | } 29 | 30 | pub fn new() -> Placer { 31 | Placer { 32 | area: vec![vec![0; AREA_BLOCK_COL as usize]; AREA_BLOCK_ROW as usize], 33 | number_of_placed_entities: 0, 34 | area_block_size: AREA_BLOCK_SIZE, 35 | area_block_padding: AREA_BLOCK_PADDING, 36 | area_block_col: AREA_BLOCK_COL, 37 | area_block_row: AREA_BLOCK_ROW, 38 | } 39 | } 40 | 41 | #[allow(clippy::too_many_arguments)] 42 | impl Placer { 43 | pub fn spawn_entities( 44 | &mut self, 45 | windows: Res, 46 | world_data: Res, 47 | commands: &mut Commands, 48 | meshes: &mut ResMut>, 49 | materials: &mut ResMut>, 50 | ) { 51 | let _win = windows.primary(); 52 | let data = &world_data.0; 53 | 54 | self.spawn_game_area(commands); 55 | 56 | // Spawn the player before anything else 57 | self.spawn_player(commands, meshes, materials); 58 | self.number_of_placed_entities += 1; 59 | self.area[(self.area_block_row / 2) as usize][(self.area_block_col / 2) as usize] = 60 | self.number_of_placed_entities as usize; 61 | 62 | //Spawn ennemies 63 | let main_scene = &data.scenes[&data.main_scene]; 64 | for entity in main_scene.entities.values() { 65 | //get possible position 66 | let mut area_pos = self.random_position(); 67 | let mut position = self.area_pos_to_world_pos(area_pos); 68 | let mut size = random_size(); 69 | while self.is_occupied(area_pos) || self.mark_occupied_area(area_pos, position, size) { 70 | area_pos = self.random_position(); 71 | position = self.area_pos_to_world_pos(area_pos); 72 | size = random_size(); 73 | } 74 | 75 | let color = entity.color.replace('#', ""); 76 | match entity.kind.as_str() { 77 | shapes::CIRCLE => { 78 | self.spawn_circle( 79 | entity.name.to_string(), 80 | color, 81 | size, 82 | position, 83 | commands, 84 | meshes, 85 | materials, 86 | ); 87 | } 88 | shapes::RECTANGLE => { 89 | self.spawn_rectangle(entity.name.to_string(), color, size, position, commands); 90 | } 91 | shapes::TRIANGLE => { 92 | self.spawn_triangle( 93 | entity.name.to_string(), 94 | color, 95 | size, 96 | position, 97 | commands, 98 | meshes, 99 | materials, 100 | ); 101 | } 102 | shapes::HEXAGON => { 103 | self.spawn_hexagon( 104 | entity.name.to_string(), 105 | color, 106 | size, 107 | position, 108 | commands, 109 | meshes, 110 | materials, 111 | ); 112 | } 113 | _ => { 114 | //we will spawn an hexagon until we have defined 115 | //a different shape for the rest of entity's kind 116 | self.spawn_hexagon( 117 | entity.name.to_string(), 118 | color, 119 | size, 120 | position, 121 | commands, 122 | meshes, 123 | materials, 124 | ); 125 | } 126 | }; 127 | } 128 | } 129 | 130 | fn spawn_game_area(&self, commands: &mut Commands) { 131 | let color: Color = Color::hex("2d333b").unwrap_or_default(); 132 | commands.spawn(SpriteBundle { 133 | transform: Transform::from_translation(Vec3::new(0., 0., -0.2)), 134 | sprite: Sprite { 135 | color, 136 | custom_size: Some(Vec2::new( 137 | (self.area_block_size + self.area_block_padding * 2.) 138 | * self.area_block_col as f32, 139 | (self.area_block_size + self.area_block_padding * 2.) 140 | * self.area_block_row as f32, 141 | )), 142 | ..default() 143 | }, 144 | ..default() 145 | }); 146 | } 147 | 148 | #[allow(dead_code)] 149 | fn spawn_background(&self, commands: &mut Commands) { 150 | //took from that tutorial https://johanhelsing.studio/posts/extreme-bevy-2 151 | // Horizontal lines 152 | for i in 0..=BG_MAP_SIZE { 153 | commands.spawn(SpriteBundle { 154 | transform: Transform::from_translation(Vec3::new( 155 | 0., 156 | (BG_MAP_SIZE as f32 * BG_MAP_BLOCK_SIZE as f32 / 2.) 157 | - (i as f32 * BG_MAP_BLOCK_SIZE as f32), 158 | -0.1, 159 | )), 160 | sprite: Sprite { 161 | color: Color::rgb(1., 1., 1.), 162 | custom_size: Some(Vec2::new( 163 | BG_MAP_BLOCK_SIZE as f32 * BG_MAP_SIZE as f32, 164 | BG_GRID_WIDTH, 165 | )), 166 | ..default() 167 | }, 168 | ..default() 169 | }); 170 | } 171 | 172 | // Vertical lines 173 | for i in 0..=BG_MAP_SIZE { 174 | commands.spawn(SpriteBundle { 175 | transform: Transform::from_translation(Vec3::new( 176 | (BG_MAP_SIZE as f32 * BG_MAP_BLOCK_SIZE as f32 / 2.) 177 | - (i as f32 * BG_MAP_BLOCK_SIZE as f32), 178 | 0., 179 | -0.1, 180 | )), 181 | sprite: Sprite { 182 | color: Color::rgb(1., 1., 1.), 183 | custom_size: Some(Vec2::new( 184 | BG_GRID_WIDTH, 185 | BG_MAP_BLOCK_SIZE as f32 * BG_MAP_SIZE as f32, 186 | )), 187 | ..default() 188 | }, 189 | ..default() 190 | }); 191 | } 192 | } 193 | 194 | fn spawn_player( 195 | &self, 196 | commands: &mut Commands, 197 | meshes: &mut ResMut>, 198 | materials: &mut ResMut>, 199 | ) { 200 | let player_size = 50.; 201 | let col_sprite = SpriteBundle { 202 | transform: Transform::from_translation(Vec3::new(0., 0., -0.1)), 203 | sprite: Sprite { 204 | color: Color::hex(config::get_col_color()).unwrap_or_default(), 205 | custom_size: Some(Vec2::new(player_size, player_size)), 206 | ..default() 207 | }, 208 | ..default() 209 | }; 210 | 211 | let color = "26a641"; 212 | commands 213 | .spawn(MaterialMesh2dBundle { 214 | mesh: meshes.add(shape::RegularPolygon::new(20., 3).into()).into(), 215 | material: materials.add(ColorMaterial::from(Color::hex(color).unwrap_or_default())), 216 | transform: Transform::from_translation(Vec3::new(0., 0., 0.)), 217 | ..default() 218 | }) 219 | .with_children(|parent| { 220 | parent 221 | .spawn(col_sprite) 222 | .insert(player::PlayerCollider::default()); 223 | }) 224 | .insert(player::Player { 225 | speed: 25., 226 | hp: 100., 227 | }) 228 | .insert(player::Velocity { x: 0.1, y: 0.1 }) 229 | .insert(player::Gun::default()) 230 | .insert(Name::new("Player")) 231 | .insert(enemy::Enemy { 232 | name: "player".to_string(), 233 | ..Default::default() 234 | }); 235 | } 236 | 237 | fn spawn_circle( 238 | &self, 239 | name: String, 240 | color: String, 241 | size: f32, 242 | position: Vec3, 243 | commands: &mut Commands, 244 | meshes: &mut ResMut>, 245 | materials: &mut ResMut>, 246 | ) { 247 | let col_sprite = SpriteBundle { 248 | transform: Transform::from_translation(Vec3::new(0., 0., -0.1)), 249 | sprite: Sprite { 250 | color: Color::hex(config::get_col_color()).unwrap_or_default(), 251 | custom_size: Some(Vec2::new(size * 2., size * 2.)), 252 | ..default() 253 | }, 254 | ..default() 255 | }; 256 | 257 | let entity = &mut commands.spawn(MaterialMesh2dBundle { 258 | mesh: meshes.add(shape::Circle::new(size).into()).into(), 259 | material: materials.add(ColorMaterial::from(Color::hex(color).unwrap_or_default())), 260 | transform: Transform::from_translation(position), 261 | ..default() 262 | }); 263 | 264 | entity 265 | // .insert(patterns::MoveTowards::default()) 266 | .insert(Name::new(name.to_owned())) 267 | .insert(enemy::Enemy { 268 | name, 269 | ..Default::default() 270 | }) 271 | .with_children(|parent| { 272 | parent 273 | .spawn(col_sprite) 274 | .insert(enemy::EnemyCollider::default()); 275 | }); 276 | 277 | if size >= 60. { 278 | entity.insert(guns::MultiDirectionCircleGun::default()); 279 | } else { 280 | entity.insert(guns::FastGun::default()); 281 | } 282 | } 283 | 284 | fn spawn_hexagon( 285 | &self, 286 | name: String, 287 | color: String, 288 | size: f32, 289 | position: Vec3, 290 | commands: &mut Commands, 291 | meshes: &mut ResMut>, 292 | materials: &mut ResMut>, 293 | ) { 294 | let col_sprite = SpriteBundle { 295 | transform: Transform::from_translation(Vec3::new(0., 0., -0.1)), 296 | sprite: Sprite { 297 | color: Color::hex(config::get_col_color()).unwrap_or_default(), 298 | custom_size: Some(Vec2::new(size, size)), 299 | ..default() 300 | }, 301 | ..default() 302 | }; 303 | 304 | commands 305 | .spawn(MaterialMesh2dBundle { 306 | mesh: meshes 307 | .add(shape::RegularPolygon::new(size, 6).into()) 308 | .into(), 309 | material: materials.add(ColorMaterial::from(Color::hex(color).unwrap_or_default())), 310 | transform: Transform::from_translation(position), 311 | ..default() 312 | }) 313 | .with_children(|parent| { 314 | parent 315 | .spawn(col_sprite) 316 | .insert(enemy::EnemyCollider::default()); 317 | }) 318 | // .insert(patterns::Pattern3 { 319 | // speed: 150., 320 | // ..default() 321 | //}) 322 | .insert(Name::new(name.to_owned())) 323 | .insert(enemy::Enemy { 324 | name, 325 | ..Default::default() 326 | }); 327 | } 328 | 329 | fn spawn_triangle( 330 | &self, 331 | name: String, 332 | color: String, 333 | size: f32, 334 | position: Vec3, 335 | commands: &mut Commands, 336 | meshes: &mut ResMut>, 337 | materials: &mut ResMut>, 338 | ) { 339 | let col_sprite = SpriteBundle { 340 | transform: Transform::from_translation(Vec3::new(0., 0., -0.1)), 341 | sprite: Sprite { 342 | color: Color::hex(config::get_col_color()).unwrap_or_default(), 343 | custom_size: Some(Vec2::new(size * 1.5, size * 1.5)), 344 | ..default() 345 | }, 346 | ..default() 347 | }; 348 | 349 | commands 350 | .spawn(MaterialMesh2dBundle { 351 | mesh: meshes 352 | .add(shape::RegularPolygon::new(size, 3).into()) 353 | .into(), 354 | material: materials.add(ColorMaterial::from(Color::hex(color).unwrap_or_default())), 355 | transform: Transform::from_translation(position), 356 | ..default() 357 | }) 358 | .with_children(|parent| { 359 | parent 360 | .spawn(col_sprite) 361 | .insert(enemy::EnemyCollider::default()); 362 | }) 363 | .insert(guns::SimpleGun::default()) 364 | // .insert(patterns::Pattern3 { 365 | // speed: 150., 366 | // ..default() 367 | // }) 368 | .insert(Name::new(name.to_owned())) 369 | .insert(enemy::Enemy { 370 | name, 371 | ..Default::default() 372 | }); 373 | } 374 | 375 | fn spawn_rectangle( 376 | &self, 377 | name: String, 378 | color: String, 379 | size: f32, 380 | position: Vec3, 381 | commands: &mut Commands, 382 | ) { 383 | let padding = 20.; 384 | let col_sprite = SpriteBundle { 385 | transform: Transform::from_translation(Vec3::new(0., 0., -0.1)), 386 | sprite: Sprite { 387 | color: Color::hex(config::get_col_color()).unwrap_or_default(), 388 | custom_size: Some(Vec2::new(size + padding + 2., size + padding + 2.)), 389 | ..default() 390 | }, 391 | ..default() 392 | }; 393 | 394 | commands 395 | .spawn(SpriteBundle { 396 | sprite: Sprite { 397 | color: Color::hex(color).unwrap_or_default(), 398 | custom_size: Some(Vec2::new(size + padding, size + padding)), 399 | ..default() 400 | }, 401 | transform: Transform::from_translation(position), 402 | ..default() 403 | }) 404 | .with_children(|parent| { 405 | parent 406 | .spawn(col_sprite) 407 | .insert(enemy::EnemyCollider::default()); 408 | }) 409 | .insert(guns::MultiDirectionRectangleGun::default()) // Debug 410 | // .insert(patterns::Pattern3 { 411 | // speed: 100., 412 | // ..default() 413 | // }) 414 | .insert(Name::new(name.to_owned())) 415 | .insert(enemy::Enemy { 416 | name, 417 | ..Default::default() 418 | }); 419 | } 420 | 421 | fn random_position(&self) -> Vec2 { 422 | let mut r = thread_rng(); 423 | let x = r.gen_range(0..(self.area_block_col)); 424 | let y = r.gen_range(0..(self.area_block_row)); 425 | 426 | //if the there is something on that position, try again 427 | Vec2::new(x as f32, y as f32) 428 | } 429 | 430 | fn is_occupied(&self, position: Vec2) -> bool { 431 | self.area[position.y as usize][position.x as usize] != 0 432 | } 433 | 434 | //mark areas affected by an entity as occupied 435 | //it uses the size of the entity to mark the areas it 436 | //touches as occupied 437 | fn mark_occupied_area(&mut self, area_pos: Vec2, position: Vec3, size: f32) -> bool { 438 | let size = size / 2.; 439 | let _a = &mut self.area; 440 | let i = area_pos.y as usize; 441 | let j = area_pos.x as usize; 442 | 443 | let col = self.area_block_col as f32; 444 | let row = self.area_block_row as f32; 445 | let position = position 446 | + Vec3::new( 447 | (self.area_block_size + self.area_block_padding) * col / 2., 448 | (self.area_block_size + self.area_block_padding) * row / 2., 449 | 0., 450 | ); 451 | let left = position + Vec3::new(-size, 0., 0.); 452 | let right = position + Vec3::new(size, 0., 0.); 453 | let top = position + Vec3::new(0., size, 0.); 454 | let bottom = position + Vec3::new(0., -size, 0.); 455 | let top_left = position + Vec3::new(-size, size, 0.); 456 | let top_right = position + Vec3::new(size, size, 0.); 457 | let bottom_left = position + Vec3::new(-size, -size, 0.); 458 | let bottom_right = position + Vec3::new(size, -size, 0.); 459 | 460 | let multiple = self.area_block_size + self.area_block_padding; 461 | let a_size = Vec3::new(multiple, multiple, 1.); 462 | let a_left_pos = left / a_size; 463 | let a_right_pos = right / a_size; 464 | let a_top_pos = top / a_size; 465 | let a_bottom_pos = bottom / a_size; 466 | let a_top_left_pos = top_left / a_size; 467 | let a_top_right_pos = top_right / a_size; 468 | let a_bottom_left_pos = bottom_left / a_size; 469 | let a_bottom_right_pos = bottom_right / a_size; 470 | 471 | //Check that the area is not occupied 472 | if self.is_occupied(a_left_pos.truncate()) 473 | || self.is_occupied(a_right_pos.truncate()) 474 | || self.is_occupied(a_top_pos.truncate()) 475 | || self.is_occupied(a_bottom_pos.truncate()) 476 | || self.is_occupied(a_top_left_pos.truncate()) 477 | || self.is_occupied(a_top_right_pos.truncate()) 478 | || self.is_occupied(a_bottom_left_pos.truncate()) 479 | || self.is_occupied(a_bottom_right_pos.truncate()) 480 | { 481 | return true; 482 | } 483 | 484 | //Occupy areas 485 | self.number_of_placed_entities += 1; 486 | self.area[i][j] = self.number_of_placed_entities as usize; 487 | self.occupy(a_left_pos.x, a_left_pos.y); 488 | self.occupy(a_right_pos.x, a_right_pos.y); 489 | self.occupy(a_top_pos.x, a_top_pos.y); 490 | self.occupy(a_bottom_pos.x, a_bottom_pos.y); 491 | self.occupy(a_top_left_pos.x, a_top_left_pos.y); 492 | self.occupy(a_top_right_pos.x, a_top_right_pos.y); 493 | self.occupy(a_bottom_left_pos.x, a_bottom_left_pos.y); 494 | self.occupy(a_bottom_right_pos.x, a_bottom_right_pos.y); 495 | 496 | false 497 | } 498 | 499 | fn area_pos_to_world_pos(&self, position: Vec2) -> Vec3 { 500 | Vec3::new( 501 | position.x * (self.area_block_size + self.area_block_padding) 502 | - (self.area_block_size + self.area_block_padding) * self.area_block_col as f32 503 | / 2., 504 | position.y * (self.area_block_size + self.area_block_padding) 505 | - (self.area_block_size + self.area_block_padding) * self.area_block_row as f32 506 | / 2., 507 | 0., 508 | ) 509 | } 510 | 511 | fn occupy(&mut self, x: f32, y: f32) { 512 | let x = if x as usize >= self.area_block_col as usize { 513 | (self.area_block_col - 1) as usize 514 | } else { 515 | x as usize 516 | }; 517 | 518 | let y = if y as usize >= self.area_block_row as usize { 519 | (self.area_block_row - 1) as usize 520 | } else { 521 | y as usize 522 | }; 523 | 524 | self.area[y][x] = self.number_of_placed_entities as usize; 525 | } 526 | 527 | #[allow(dead_code)] 528 | fn print(&self) { 529 | for i in 0..self.area_block_row as usize { 530 | for j in 0..self.area_block_col as usize { 531 | print!("{:3}", self.area[i][j]); 532 | } 533 | println!(); 534 | } 535 | } 536 | } 537 | 538 | fn random_size() -> f32 { 539 | match rand::thread_rng().gen_range(0..10) { 540 | v if (0..1).contains(&v) => 40., 541 | v if (1..2).contains(&v) => 50., 542 | v if (2..3).contains(&v) => 60., 543 | v if (3..4).contains(&v) => 70., 544 | v if (4..5).contains(&v) => 80., 545 | _ => 30., 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /players/src/shmup/plugin.rs: -------------------------------------------------------------------------------- 1 | use super::components::camera; 2 | use super::debug; 3 | use super::placer; 4 | use super::systems::camera as camera_system; 5 | use super::systems::enemy_bullets as enemy_bullets_systems; 6 | use super::systems::guns as guns_systems; 7 | use super::systems::movements; 8 | use super::systems::player as player_systems; 9 | use super::systems::player_bullet; 10 | use super::WorldData; 11 | use bevy::{prelude::*, render::camera::ScalingMode}; 12 | 13 | pub struct ShmupPlugin; 14 | 15 | impl Plugin for ShmupPlugin { 16 | fn build(&self, app: &mut App) { 17 | // set GitHub dark mode background color 18 | let bg_color: Color = Color::hex("0e1117").unwrap_or_default(); 19 | 20 | app.insert_resource(ClearColor(bg_color)) 21 | .add_plugin(debug::DebugPlugin) 22 | .add_startup_system(setup) 23 | .add_startup_system(movements::pattern_3_init) 24 | .add_system(movements::movement_pattern_1) 25 | .add_system(movements::movement_pattern_2) 26 | .add_system(movements::movement_pattern_3) 27 | .add_system(movements::move_towards) 28 | .add_system(player_systems::movement) 29 | .add_system(player_systems::keyboad_input) 30 | .add_system(player_systems::mouse_input) 31 | .add_system(player_systems::player_enemy_bullets_collisions) 32 | .add_system(player_systems::player_bullets_enemy_bullets_collisions) 33 | .add_system(player_systems::player_bullets_enemies_collisions) 34 | .add_system(player_bullet::movement) 35 | .add_system(player_bullet::despawn) 36 | .add_system(enemy_bullets_systems::despawn) 37 | .add_system(enemy_bullets_systems::movement) 38 | .add_system(guns_systems::simple_gun) 39 | .add_system(guns_systems::fast_gun) 40 | .add_system(guns_systems::multidirection_circle_gun) 41 | .add_system(guns_systems::multidirection_rectangle_gun) 42 | .add_system(camera_system::follow_player); 43 | } 44 | } 45 | 46 | fn setup( 47 | mut commands: Commands, 48 | mut meshes: ResMut>, 49 | world_data: Res, 50 | windows: Res, 51 | mut materials: ResMut>, 52 | ) { 53 | commands 54 | .spawn(Camera2dBundle { 55 | transform: Transform { 56 | scale: Vec3::new(1., 1., 1.), 57 | ..Default::default() 58 | }, 59 | projection: OrthographicProjection { 60 | scaling_mode: ScalingMode::FixedHorizontal(4000.), 61 | ..Default::default() 62 | }, 63 | ..Default::default() 64 | }) 65 | .insert(camera::MainCamera); 66 | 67 | let mut placer = placer::new(); 68 | placer.spawn_entities( 69 | windows, 70 | world_data, 71 | &mut commands, 72 | &mut meshes, 73 | &mut materials, 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /players/src/shmup/systems/camera.rs: -------------------------------------------------------------------------------- 1 | use super::super::components::camera; 2 | use super::super::components::player; 3 | use bevy::prelude::*; 4 | 5 | pub fn follow_player( 6 | mut cam: Query<&mut Transform, (With, Without)>, 7 | player: Query<&Transform, (With, Without)>, 8 | ) { 9 | let camera_transform = cam.get_single_mut(); 10 | if let Ok(mut camera_transform) = camera_transform { 11 | if let Ok(player_transform) = player.get_single() { 12 | let player_pos = player_transform.translation; 13 | let smoothness = 0.02; 14 | let smoothed_position = camera_transform.translation.lerp(player_pos, smoothness); 15 | camera_transform.translation = smoothed_position; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /players/src/shmup/systems/enemy_bullets.rs: -------------------------------------------------------------------------------- 1 | use super::super::components::enemy_bullets::Bullet; 2 | use bevy::prelude::*; 3 | use std::time::Duration; 4 | 5 | pub fn movement(time: Res