initial commit
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
result
|
51
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug executable 'bevy_test'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin=bevy_test",
|
||||||
|
"--package=bevy_test"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "bevy_test",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"env": {
|
||||||
|
"CARGO_MANIFEST_DIR": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'bevy_test'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--bin=bevy_test",
|
||||||
|
"--package=bevy_test"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "bevy_test",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"env": {
|
||||||
|
"CARGO_MANIFEST_DIR": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix",
|
||||||
|
"rust-analyzer.linkedProjects": [
|
||||||
|
"./Cargo.toml"
|
||||||
|
]
|
||||||
|
}
|
6179
Cargo.lock
generated
Normal file
37
Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["Zuckerberg <zuckerberg@neet.dev>"]
|
||||||
|
edition = "2021"
|
||||||
|
name = "bevy_test"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
# Enable max optimizations for dependencies, but not for our code:
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
|
# Enable only a small amount of optimization in debug mode
|
||||||
|
#[profile.dev]
|
||||||
|
#opt-level = 1
|
||||||
|
|
||||||
|
# Allow debugging in release mode
|
||||||
|
#[profile.release]
|
||||||
|
#debug = 1
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "*"
|
||||||
|
serialport = "*"
|
||||||
|
crc = "*"
|
||||||
|
|
||||||
|
[dependencies.bevy]
|
||||||
|
version = "0.14.0"
|
||||||
|
default-features = true
|
||||||
|
features = ["wayland"]
|
||||||
|
|
||||||
|
[dependencies.bevy_rapier3d]
|
||||||
|
version = "0.26.0"
|
||||||
|
features = ["enhanced-determinism", "serde-serialize"]
|
||||||
|
|
||||||
|
[dependencies.rapier3d]
|
||||||
|
version = "0.21.0"
|
||||||
|
|
||||||
|
[dependencies.noise]
|
||||||
|
version = "*"
|
BIN
assets/textures/Letters/letterA.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/textures/Letters/letterB.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/textures/Letters/letterC.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/textures/Letters/letterD.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/textures/Letters/letterE.png
Normal file
After Width: | Height: | Size: 634 B |
BIN
assets/textures/Letters/letterF.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
assets/textures/Letters/letterG.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/textures/Letters/letterH.png
Normal file
After Width: | Height: | Size: 637 B |
BIN
assets/textures/Letters/letterI.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
assets/textures/Letters/letterJ.png
Normal file
After Width: | Height: | Size: 920 B |
BIN
assets/textures/Letters/letterK.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/textures/Letters/letterL.png
Normal file
After Width: | Height: | Size: 482 B |
BIN
assets/textures/Letters/letterM.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/textures/Letters/letterN.png
Normal file
After Width: | Height: | Size: 1000 B |
BIN
assets/textures/Letters/letterO.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/textures/Letters/letterP.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/textures/Letters/letterQ.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/textures/Letters/letterR.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/textures/Letters/letterS.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/textures/Letters/letterT.png
Normal file
After Width: | Height: | Size: 604 B |
BIN
assets/textures/Letters/letterU.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/textures/Letters/letterV.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/textures/Letters/letterW.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/textures/Letters/letterX.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/textures/Letters/letterY.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/textures/Letters/letterZ.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/textures/Numbers/number0.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/textures/Numbers/number1.png
Normal file
After Width: | Height: | Size: 819 B |
BIN
assets/textures/Numbers/number2.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/textures/Numbers/number3.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/textures/Numbers/number4.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/textures/Numbers/number5.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/textures/Numbers/number6.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/textures/Numbers/number7.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/textures/Numbers/number8.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/textures/Numbers/number9.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/textures/Planes/planeBlue1.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeBlue2.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeBlue3.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeGreen1.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeGreen2.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeGreen3.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeRed1.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeRed2.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeRed3.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeYellow1.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeYellow2.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/Planes/planeYellow3.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/textures/UI/UIbg.png
Normal file
After Width: | Height: | Size: 639 B |
BIN
assets/textures/UI/buttonLarge.png
Normal file
After Width: | Height: | Size: 477 B |
BIN
assets/textures/UI/buttonSmall.png
Normal file
After Width: | Height: | Size: 468 B |
BIN
assets/textures/UI/medalBronze.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/textures/UI/medalGold.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/textures/UI/medalSilver.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/textures/UI/tap.png
Normal file
After Width: | Height: | Size: 849 B |
BIN
assets/textures/UI/tapLeft.png
Normal file
After Width: | Height: | Size: 671 B |
BIN
assets/textures/UI/tapRight.png
Normal file
After Width: | Height: | Size: 646 B |
BIN
assets/textures/UI/tapTick.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/textures/UI/textGameOver.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
assets/textures/UI/textGetReady.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
assets/textures/background.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
assets/textures/groundDirt.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/textures/groundGrass.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/textures/groundIce.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/textures/groundRock.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/textures/groundSnow.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/textures/puffLarge.png
Normal file
After Width: | Height: | Size: 357 B |
BIN
assets/textures/puffSmall.png
Normal file
After Width: | Height: | Size: 285 B |
BIN
assets/textures/rock.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/textures/rockDown.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/textures/rockGrass.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/textures/rockGrassDown.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/textures/rockIce.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/textures/rockIceDown.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/textures/rockSnow.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/textures/rockSnowDown.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/textures/starBronze.png
Normal file
After Width: | Height: | Size: 734 B |
BIN
assets/textures/starGold.png
Normal file
After Width: | Height: | Size: 734 B |
BIN
assets/textures/starSilver.png
Normal file
After Width: | Height: | Size: 725 B |
96
flake.lock
generated
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710146030,
|
||||||
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1721138476,
|
||||||
|
"narHash": "sha256-+W5eZOhhemLQxelojLxETfbFbc19NWawsXBlapYpqIA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "ad0b5eed1b6031efaed382844806550c3dcb4206",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1718428119,
|
||||||
|
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1721355572,
|
||||||
|
"narHash": "sha256-I4TQ2guV9jTmZsXeWt5HMojcaqNZHII4zu0xIKZEovM=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "d5bc7b1b21cf937fb8ff108ae006f6776bdb163d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
105
flake.nix
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, rust-overlay, flake-utils }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
overlays = [
|
||||||
|
(import rust-overlay)
|
||||||
|
];
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system overlays;
|
||||||
|
};
|
||||||
|
|
||||||
|
app = "bevy_test";
|
||||||
|
|
||||||
|
rust = pkgs.rust-bin.stable.latest.default.override { extensions = [ "rust-src" ]; };
|
||||||
|
rustPlatform = pkgs.makeRustPlatform {
|
||||||
|
cargo = rust;
|
||||||
|
rustc = rust;
|
||||||
|
};
|
||||||
|
|
||||||
|
shellInputs = [
|
||||||
|
rust
|
||||||
|
pkgs.clang
|
||||||
|
];
|
||||||
|
appNativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
appBuildInputs = appRuntimeInputs ++ (with pkgs; [
|
||||||
|
udev
|
||||||
|
alsaLib
|
||||||
|
wayland
|
||||||
|
vulkan-tools
|
||||||
|
vulkan-headers
|
||||||
|
vulkan-validation-layers
|
||||||
|
]);
|
||||||
|
appRuntimeInputs = with pkgs; [
|
||||||
|
vulkan-loader
|
||||||
|
libxkbcommon
|
||||||
|
];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells =
|
||||||
|
let
|
||||||
|
game = pkgs.mkShell {
|
||||||
|
nativeBuildInputs = appNativeBuildInputs;
|
||||||
|
buildInputs = shellInputs ++ appBuildInputs;
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath appRuntimeInputs}"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
${app} = game;
|
||||||
|
default = game;
|
||||||
|
};
|
||||||
|
|
||||||
|
packages =
|
||||||
|
let
|
||||||
|
game = rustPlatform.buildRustPackage {
|
||||||
|
pname = app;
|
||||||
|
version = "0.1.0";
|
||||||
|
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
cargoLock = {
|
||||||
|
lockFile = ./Cargo.lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = appNativeBuildInputs;
|
||||||
|
buildInputs = appBuildInputs;
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
cp -r assets $out/bin/
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
${app} = game;
|
||||||
|
default = game;
|
||||||
|
};
|
||||||
|
|
||||||
|
apps =
|
||||||
|
let
|
||||||
|
game = {
|
||||||
|
type = "app";
|
||||||
|
program = "${self.packages.${app}}/bin/${app}";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
${app} = game;
|
||||||
|
default = game;
|
||||||
|
};
|
||||||
|
|
||||||
|
checks.build = self.packages.${system}.${app};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
1
shell.nix
Normal file
@ -0,0 +1 @@
|
|||||||
|
(builtins.getFlake (toString ./.)).devShells.${builtins.currentSystem}.default
|
44
src/asset_loader.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use bevy::{asset::LoadedFolder, prelude::*};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, States)]
|
||||||
|
pub enum AssetLoadState {
|
||||||
|
#[default]
|
||||||
|
AssetsLoading,
|
||||||
|
AssetsLoaded,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AssetLoaderPlugin;
|
||||||
|
|
||||||
|
impl Plugin for AssetLoaderPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.init_state::<AssetLoadState>()
|
||||||
|
.add_systems(OnEnter(AssetLoadState::AssetsLoading), load_assets)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
check_assets.run_if(in_state(AssetLoadState::AssetsLoading)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct AssetFolder(Handle<LoadedFolder>);
|
||||||
|
|
||||||
|
fn load_assets(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
println!("Loading assets.");
|
||||||
|
// load multiple, assets from a folder
|
||||||
|
commands.insert_resource(AssetFolder(asset_server.load_folder(".")));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_assets(
|
||||||
|
mut next_state: ResMut<NextState<AssetLoadState>>,
|
||||||
|
rpg_sprite_folder: Res<AssetFolder>,
|
||||||
|
mut events: EventReader<AssetEvent<LoadedFolder>>,
|
||||||
|
) {
|
||||||
|
// Advance the `AppState` once all sprite handles have been loaded by the `AssetServer`
|
||||||
|
for event in events.read() {
|
||||||
|
if event.is_loaded_with_dependencies(&rpg_sprite_folder.0) {
|
||||||
|
next_state.set(AssetLoadState::AssetsLoaded);
|
||||||
|
println!("Assets loaded.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
113
src/background.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use crate::{asset_loader::AssetLoadState::AssetsLoaded, system_set::MovementSet};
|
||||||
|
use bevy::{prelude::*, sprite::Anchor, window::PrimaryWindow};
|
||||||
|
|
||||||
|
pub struct BackgroundPlugin;
|
||||||
|
|
||||||
|
impl Plugin for BackgroundPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(OnEnter(AssetsLoaded), setup).add_systems(
|
||||||
|
Update,
|
||||||
|
update_parallax.in_set(MovementSet::PostCameraMovement),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Parallax {
|
||||||
|
speed: f32,
|
||||||
|
tile_width: u32,
|
||||||
|
start_offset: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
assets: Res<Assets<Image>>,
|
||||||
|
window: Query<&Window, With<PrimaryWindow>>,
|
||||||
|
) {
|
||||||
|
let window = window.single();
|
||||||
|
|
||||||
|
let ground = asset_server.load("textures/groundSnow.png");
|
||||||
|
let ground_size = assets.get(&ground).unwrap().size();
|
||||||
|
let ground_tiles_needed = (window.width() * 4.0) as u32 / ground_size.x;
|
||||||
|
let ground_tile_start_offset = -1.5 * window.width();
|
||||||
|
commands.spawn((
|
||||||
|
SpriteBundle {
|
||||||
|
texture: ground,
|
||||||
|
sprite: Sprite {
|
||||||
|
custom_size: Some(Vec2::new(
|
||||||
|
(ground_size.x * ground_tiles_needed) as f32,
|
||||||
|
ground_size.y as f32,
|
||||||
|
)),
|
||||||
|
anchor: Anchor::BottomLeft,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_xyz(ground_tile_start_offset, -0.5 * window.height(), 10.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ImageScaleMode::Tiled {
|
||||||
|
tile_x: true,
|
||||||
|
tile_y: true,
|
||||||
|
stretch_value: 1.0,
|
||||||
|
},
|
||||||
|
Parallax {
|
||||||
|
speed: 0.0, // same speed as the camera
|
||||||
|
tile_width: ground_size.x,
|
||||||
|
start_offset: ground_tile_start_offset,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let background = asset_server.load("textures/background.png");
|
||||||
|
let background_size = assets.get(&background).unwrap().size();
|
||||||
|
let background_tiles_needed = (window.width() * 4.0) as u32 / background_size.x;
|
||||||
|
let background_tile_start_offset = -1.5 * window.width();
|
||||||
|
commands.spawn((
|
||||||
|
SpriteBundle {
|
||||||
|
texture: background,
|
||||||
|
sprite: Sprite {
|
||||||
|
custom_size: Some(Vec2::new(
|
||||||
|
(background_size.x * background_tiles_needed) as f32,
|
||||||
|
background_size.y as f32,
|
||||||
|
)),
|
||||||
|
anchor: Anchor::BottomLeft,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_xyz(
|
||||||
|
background_tile_start_offset,
|
||||||
|
-0.5 * window.height(),
|
||||||
|
-1.0,
|
||||||
|
),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ImageScaleMode::Tiled {
|
||||||
|
tile_x: true,
|
||||||
|
tile_y: true,
|
||||||
|
stretch_value: 1.0,
|
||||||
|
},
|
||||||
|
Parallax {
|
||||||
|
speed: 0.5, // same speed as the camera
|
||||||
|
tile_width: background_size.x,
|
||||||
|
start_offset: background_tile_start_offset,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_parallax(
|
||||||
|
mut parallax: Query<(&Parallax, &mut Transform), Without<Camera2d>>,
|
||||||
|
camera: Query<&Transform, With<Camera2d>>,
|
||||||
|
) {
|
||||||
|
let Ok(camera) = camera.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (parallax, mut transform) in &mut parallax {
|
||||||
|
// + parallax.start_offset
|
||||||
|
let mut new_offset = camera.translation.x * parallax.speed;
|
||||||
|
let tiling_undershoot =
|
||||||
|
((camera.translation.x - new_offset) / parallax.tile_width as f32) as i32;
|
||||||
|
if tiling_undershoot != 0 {
|
||||||
|
new_offset += parallax.tile_width as f32 * tiling_undershoot as f32;
|
||||||
|
}
|
||||||
|
transform.translation.x = new_offset + parallax.start_offset;
|
||||||
|
}
|
||||||
|
}
|
62
src/camera.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use bevy::{prelude::*, window::PrimaryWindow};
|
||||||
|
|
||||||
|
use crate::{player::Player, system_set::MovementSet};
|
||||||
|
|
||||||
|
const CAM_LERP_FACTOR: f32 = 1000.;
|
||||||
|
|
||||||
|
pub struct CameraPlugin;
|
||||||
|
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Startup, setup).add_systems(
|
||||||
|
Update,
|
||||||
|
(move_camera, follow_player).in_set(MovementSet::PostPlayerMovement),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn(Camera2dBundle::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_camera(
|
||||||
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut camera: Query<&mut Transform, With<Camera2d>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
let Ok(mut camera) = camera.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if keys.pressed(KeyCode::KeyW) {
|
||||||
|
camera.translation.y += time.delta_seconds() * CAM_LERP_FACTOR
|
||||||
|
}
|
||||||
|
if keys.pressed(KeyCode::KeyS) {
|
||||||
|
camera.translation.y -= time.delta_seconds() * CAM_LERP_FACTOR
|
||||||
|
}
|
||||||
|
// if keys.pressed(KeyCode::KeyA) {
|
||||||
|
// camera.translation.x -= time.delta_seconds() * CAM_LERP_FACTOR
|
||||||
|
// }
|
||||||
|
// if keys.pressed(KeyCode::KeyD) {
|
||||||
|
// camera.translation.x += time.delta_seconds() * CAM_LERP_FACTOR
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn follow_player(
|
||||||
|
mut camera: Query<&mut Transform, With<Camera2d>>,
|
||||||
|
player: Query<&mut Transform, (With<Player>, Without<Camera2d>)>,
|
||||||
|
window: Query<&Window, With<PrimaryWindow>>,
|
||||||
|
) {
|
||||||
|
let window = window.single();
|
||||||
|
|
||||||
|
let Ok(mut camera) = camera.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(player) = player.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Always position the camera so the player is in the first 1/4th of the screen
|
||||||
|
camera.translation.x = player.translation.x + window.width() / 4.0;
|
||||||
|
}
|
41
src/main.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
mod asset_loader;
|
||||||
|
mod background;
|
||||||
|
mod camera;
|
||||||
|
mod obstacle;
|
||||||
|
mod player;
|
||||||
|
mod system_set;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
|
primary_window: Some(Window {
|
||||||
|
title: "Tappy Plane".into(),
|
||||||
|
name: Some("TappyPlane.app".into()),
|
||||||
|
resolution: (800., 480.).into(),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
}))
|
||||||
|
// .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
|
||||||
|
.add_plugins((
|
||||||
|
asset_loader::AssetLoaderPlugin,
|
||||||
|
camera::CameraPlugin,
|
||||||
|
background::BackgroundPlugin,
|
||||||
|
player::PlayerPlugin,
|
||||||
|
obstacle::ObstaclePlugin,
|
||||||
|
system_set::SystemSetPlugin,
|
||||||
|
))
|
||||||
|
.add_systems(Update, quit_on_escape_system)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn quit_on_escape_system(
|
||||||
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut app_exit_events: ResMut<Events<AppExit>>,
|
||||||
|
) {
|
||||||
|
if keys.just_pressed(KeyCode::Escape) {
|
||||||
|
app_exit_events.send(AppExit::Success);
|
||||||
|
}
|
||||||
|
}
|
202
src/obstacle.rs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
use crate::{
|
||||||
|
asset_loader::AssetLoadState::AssetsLoaded,
|
||||||
|
player::Player,
|
||||||
|
system_set::{DespawnSet, MovementSet},
|
||||||
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::{prelude::*, sprite::Anchor, window::PrimaryWindow};
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
const OBSTACLE_SPAWN_DELAY: f32 = 2.0;
|
||||||
|
|
||||||
|
pub struct ObstaclePlugin;
|
||||||
|
|
||||||
|
impl Plugin for ObstaclePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(OnEnter(AssetsLoaded), setup)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
add_obstacles.in_set(MovementSet::PostPlayerMovement),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
despawn_offscreen.in_set(DespawnSet::DespawnOffscreen),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct ObstacleTimer {
|
||||||
|
timer: Timer,
|
||||||
|
obstacles_placed: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Obstacle {
|
||||||
|
pub p1: Vec2,
|
||||||
|
pub p2: Vec2,
|
||||||
|
pub p3: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Star {
|
||||||
|
pub radius: f32,
|
||||||
|
pub points: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn(ObstacleTimer {
|
||||||
|
timer: Timer::new(
|
||||||
|
Duration::from_secs_f32(OBSTACLE_SPAWN_DELAY),
|
||||||
|
TimerMode::Repeating,
|
||||||
|
),
|
||||||
|
obstacles_placed: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_obstacles(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut timer: Query<&mut ObstacleTimer>,
|
||||||
|
window: Query<&Window, With<PrimaryWindow>>,
|
||||||
|
player: Query<&Transform, With<Player>>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
assets: Res<Assets<Image>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
let window = window.single();
|
||||||
|
|
||||||
|
let Ok(mut timer) = timer.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(player) = player.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
timer.timer.tick(time.delta());
|
||||||
|
|
||||||
|
if timer.timer.just_finished() {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let is_roof_obstacle = rng.next_u32() % 2 == 0;
|
||||||
|
let obstacle_scale = if rng.next_u32() % 2 == 0 { 1.0 } else { 0.75 };
|
||||||
|
|
||||||
|
let texture: Handle<Image> = match rng.next_u32() % 4 {
|
||||||
|
0 => asset_server.load("textures/rock.png"),
|
||||||
|
1 => asset_server.load("textures/rockGrass.png"),
|
||||||
|
2 => asset_server.load("textures/rockIce.png"),
|
||||||
|
3 => asset_server.load("textures/rockSnow.png"),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let transform = Transform::from_xyz(
|
||||||
|
window.width() + player.translation.x,
|
||||||
|
(if is_roof_obstacle { 1.0 } else { -1.0 }) * window.height() / 2.0,
|
||||||
|
5.0,
|
||||||
|
) * Transform::from_scale(Vec3::splat(obstacle_scale));
|
||||||
|
|
||||||
|
let obstacle_texture = assets.get(&texture).unwrap();
|
||||||
|
let obstacle_width = (obstacle_texture.width() as f32 * obstacle_scale) as f32;
|
||||||
|
let obstacle_height = (obstacle_texture.height() as f32 * obstacle_scale) as f32;
|
||||||
|
|
||||||
|
let obstacle_tip = Vec2::new(
|
||||||
|
transform.translation.x,
|
||||||
|
transform.translation.y
|
||||||
|
+ (if is_roof_obstacle {
|
||||||
|
-1.0 * obstacle_height
|
||||||
|
} else {
|
||||||
|
obstacle_height
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let obstacle_left = Vec2::new(
|
||||||
|
transform.translation.x - obstacle_width / 2.0,
|
||||||
|
transform.translation.y,
|
||||||
|
);
|
||||||
|
let obstacle_right = Vec2::new(
|
||||||
|
transform.translation.x + obstacle_width / 2.0,
|
||||||
|
transform.translation.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
SpriteBundle {
|
||||||
|
sprite: Sprite {
|
||||||
|
anchor: if is_roof_obstacle {
|
||||||
|
Anchor::TopCenter
|
||||||
|
} else {
|
||||||
|
Anchor::BottomCenter
|
||||||
|
},
|
||||||
|
flip_y: is_roof_obstacle,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
texture: texture,
|
||||||
|
transform: transform,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Obstacle {
|
||||||
|
p1: obstacle_tip,
|
||||||
|
p2: obstacle_left,
|
||||||
|
p3: obstacle_right,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
timer.obstacles_placed += 1;
|
||||||
|
|
||||||
|
// Place star at tip of obstacle
|
||||||
|
let star_value = rng.next_u32() % 4;
|
||||||
|
if star_value != 0 {
|
||||||
|
let texture: Handle<Image> = match star_value {
|
||||||
|
1 => asset_server.load("textures/starBronze.png"),
|
||||||
|
2 => asset_server.load("textures/starSilver.png"),
|
||||||
|
3 => asset_server.load("textures/starGold.png"),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let star_texture = assets.get(&texture).unwrap();
|
||||||
|
let star_width = star_texture.width() as f32;
|
||||||
|
|
||||||
|
let transform = Transform::from_xyz(
|
||||||
|
transform.translation.x + star_width / 4.0,
|
||||||
|
transform.translation.y
|
||||||
|
+ (obstacle_height + star_width * 2.0)
|
||||||
|
* (if is_roof_obstacle { -1.0 } else { 1.0 }),
|
||||||
|
6.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
SpriteBundle {
|
||||||
|
sprite: Sprite {
|
||||||
|
anchor: Anchor::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
texture: texture,
|
||||||
|
transform: transform,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Star {
|
||||||
|
radius: star_width,
|
||||||
|
points: star_value,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn despawn_offscreen(
|
||||||
|
mut commands: Commands,
|
||||||
|
window: Query<&Window, With<PrimaryWindow>>,
|
||||||
|
player: Query<&Transform, With<Player>>,
|
||||||
|
obstacles: Query<(Entity, &Transform), Or<(With<Obstacle>, With<Star>)>>,
|
||||||
|
) {
|
||||||
|
let window = window.single();
|
||||||
|
|
||||||
|
let Ok(player) = player.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (entity, transform) in &obstacles {
|
||||||
|
if transform.translation.x < player.translation.x - window.width() {
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
235
src/player.rs
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
asset_loader::AssetLoadState::AssetsLoaded,
|
||||||
|
obstacle::{Obstacle, Star},
|
||||||
|
system_set::{DespawnSet, MovementSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
const PLANE_SPEED: f32 = 200.0;
|
||||||
|
const TAP_ACCELERATION: f32 = 50000.0;
|
||||||
|
const GRAVITY_ACCERATION_RATE: f32 = -1000.0;
|
||||||
|
// const GRAVITY_ACCERATION_RATE: f32 = 0.0;
|
||||||
|
const MAX_VELOCITY: f32 = 1000.0;
|
||||||
|
const MAX_TILT_DEGREES: f32 = 45.0;
|
||||||
|
|
||||||
|
pub struct PlayerPlugin;
|
||||||
|
|
||||||
|
impl Plugin for PlayerPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(OnEnter(AssetsLoaded), setup).add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
move_plane.in_set(MovementSet::PlayerMovement),
|
||||||
|
animate_plane,
|
||||||
|
obstacle_collider.before(DespawnSet::DespawnCollectedStars),
|
||||||
|
collect_stars.in_set(DespawnSet::DespawnCollectedStars),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Player {
|
||||||
|
acceleration: f32,
|
||||||
|
velocity: f32,
|
||||||
|
animation_index: usize,
|
||||||
|
animation_timer: Timer,
|
||||||
|
textures: Vec<Handle<Image>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct PlayerCollider {
|
||||||
|
radius: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Score {
|
||||||
|
points: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
fn new(fps: u8, textures: Vec<Handle<Image>>) -> Self {
|
||||||
|
Self {
|
||||||
|
acceleration: 0.0,
|
||||||
|
velocity: 0.0,
|
||||||
|
animation_index: 0,
|
||||||
|
animation_timer: Timer::new(
|
||||||
|
Duration::from_secs_f32(1.0 / (fps as f32)),
|
||||||
|
TimerMode::Repeating,
|
||||||
|
),
|
||||||
|
textures: textures,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, assets: Res<Assets<Image>>) {
|
||||||
|
let texture1: Handle<Image> = asset_server.load("textures/Planes/planeGreen1.png");
|
||||||
|
let texture2: Handle<Image> = asset_server.load("textures/Planes/planeGreen2.png");
|
||||||
|
let texture3: Handle<Image> = asset_server.load("textures/Planes/planeGreen3.png");
|
||||||
|
|
||||||
|
let player_radius = assets.get(&texture1).unwrap().width() / 2;
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
SpriteBundle {
|
||||||
|
texture: texture1.clone(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Player::new(20, vec![texture1, texture2.clone(), texture3, texture2]),
|
||||||
|
PlayerCollider {
|
||||||
|
// Add a little wiggle room for collision detection
|
||||||
|
radius: (player_radius as f32 * 0.8) as u32,
|
||||||
|
},
|
||||||
|
Score { points: 0 },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn animate_plane(mut player: Query<(&mut Player, &mut Handle<Image>)>, time: Res<Time>) {
|
||||||
|
let Ok((mut player, mut handle)) = player.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
player.animation_timer.tick(time.delta());
|
||||||
|
|
||||||
|
if player.animation_timer.just_finished() {
|
||||||
|
player.animation_index += 1;
|
||||||
|
if player.animation_index == player.textures.len() {
|
||||||
|
player.animation_index = 0;
|
||||||
|
}
|
||||||
|
*handle = player.textures[player.animation_index].clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_plane(
|
||||||
|
mut player: Query<(&mut Player, &mut Transform)>,
|
||||||
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
let Ok((mut player, mut transform)) = player.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
transform.translation.x += PLANE_SPEED * time.delta_seconds();
|
||||||
|
|
||||||
|
if keys.just_pressed(KeyCode::Space) {
|
||||||
|
player.acceleration = TAP_ACCELERATION;
|
||||||
|
} else {
|
||||||
|
player.acceleration = GRAVITY_ACCERATION_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.velocity += player.acceleration * time.delta_seconds();
|
||||||
|
if player.velocity > MAX_VELOCITY {
|
||||||
|
player.velocity = MAX_VELOCITY;
|
||||||
|
}
|
||||||
|
if player.velocity < -MAX_VELOCITY {
|
||||||
|
player.velocity = -MAX_VELOCITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// println!("a {}, v {}", player.acceleration, player.velocity);
|
||||||
|
|
||||||
|
transform.translation.y += player.velocity * time.delta_seconds();
|
||||||
|
|
||||||
|
let degrees: f32 = player.velocity / MAX_VELOCITY * MAX_TILT_DEGREES;
|
||||||
|
transform.rotation = Quat::from_rotation_z(degrees.to_radians());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn obstacle_collider(player: Query<(&Transform, &PlayerCollider)>, obstacles: Query<&Obstacle>) {
|
||||||
|
let Ok((player_transform, player_collider)) = player.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let player_position = Vec2::new(
|
||||||
|
player_transform.translation.x,
|
||||||
|
player_transform.translation.y,
|
||||||
|
);
|
||||||
|
let player_radius = player_collider.radius as f32;
|
||||||
|
|
||||||
|
let mut collision_detected = false;
|
||||||
|
|
||||||
|
for obstacle in &obstacles {
|
||||||
|
if circle_collides_with_line_segment(
|
||||||
|
obstacle.p1,
|
||||||
|
obstacle.p2,
|
||||||
|
player_position,
|
||||||
|
player_radius,
|
||||||
|
) || circle_collides_with_line_segment(
|
||||||
|
obstacle.p1,
|
||||||
|
obstacle.p3,
|
||||||
|
player_position,
|
||||||
|
player_radius,
|
||||||
|
) || circle_collides_with_line_segment(
|
||||||
|
obstacle.p2,
|
||||||
|
obstacle.p3,
|
||||||
|
player_position,
|
||||||
|
player_radius,
|
||||||
|
) {
|
||||||
|
collision_detected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if collision_detected {
|
||||||
|
println!("Obstacle Collision detected");
|
||||||
|
} else {
|
||||||
|
// println!("No Collision detected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn circle_collides_with_line_segment(a: Vec2, b: Vec2, c: Vec2, r: f32) -> bool {
|
||||||
|
// Based on https://stackoverflow.com/a/1079478
|
||||||
|
|
||||||
|
let ac = c - a;
|
||||||
|
let ab = b - a;
|
||||||
|
|
||||||
|
let d = ac.project_onto(ab) + a;
|
||||||
|
|
||||||
|
let ad = d - a;
|
||||||
|
|
||||||
|
let k = if ab.x.abs() > ab.y.abs() {
|
||||||
|
ad.x / ab.x
|
||||||
|
} else {
|
||||||
|
ad.y / ab.y
|
||||||
|
};
|
||||||
|
|
||||||
|
let len_squared = if k <= 0.0 {
|
||||||
|
(c - a).length_squared()
|
||||||
|
} else if k >= 1.0 {
|
||||||
|
(c - b).length_squared()
|
||||||
|
} else {
|
||||||
|
(c - d).length_squared()
|
||||||
|
};
|
||||||
|
|
||||||
|
return r * r > len_squared;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_stars(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut player: Query<(&Transform, &PlayerCollider, &mut Score)>,
|
||||||
|
stars: Query<(Entity, &Transform, &Star)>,
|
||||||
|
) {
|
||||||
|
let Ok((player_transform, player_collider, mut score)) = player.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let player_position = Vec2::new(
|
||||||
|
player_transform.translation.x,
|
||||||
|
player_transform.translation.y,
|
||||||
|
);
|
||||||
|
let player_radius = player_collider.radius as f32;
|
||||||
|
|
||||||
|
for (entity, star_transform, star) in &stars {
|
||||||
|
let star_position = Vec2::new(star_transform.translation.x, star_transform.translation.y);
|
||||||
|
let star_radius = star.radius as f32;
|
||||||
|
if circle_collides_with_circle(player_position, player_radius, star_position, star_radius) {
|
||||||
|
score.points += star.points;
|
||||||
|
println!("Player points: {}", score.points);
|
||||||
|
commands.entity(entity).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn circle_collides_with_circle(c1: Vec2, r1: f32, c2: Vec2, r2: f32) -> bool {
|
||||||
|
return r1 * r1 + r2 * r2 > (c1 - c2).length_squared();
|
||||||
|
}
|
50
src/system_set.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct SystemSetPlugin;
|
||||||
|
|
||||||
|
impl Plugin for SystemSetPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.configure_sets(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
MovementSet::PlayerMovement,
|
||||||
|
MovementSet::PostPlayerMovement,
|
||||||
|
MovementSet::PostCameraMovement,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
(
|
||||||
|
DespawnSet::DespawnCollectedStars,
|
||||||
|
// TODO flush cmd buffer here
|
||||||
|
DespawnSet::DespawnOffscreen,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
);
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
apply_deferred
|
||||||
|
.after(DespawnSet::DespawnCollectedStars)
|
||||||
|
.before(DespawnSet::DespawnOffscreen),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||||
|
pub enum MovementSet {
|
||||||
|
// Player position is updated here
|
||||||
|
PlayerMovement,
|
||||||
|
|
||||||
|
// Systems that check the player position are updated here (such as the camera)
|
||||||
|
PostPlayerMovement,
|
||||||
|
|
||||||
|
// Systems that check the camera position are updated here
|
||||||
|
PostCameraMovement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||||
|
pub enum DespawnSet {
|
||||||
|
DespawnCollectedStars,
|
||||||
|
DespawnOffscreen,
|
||||||
|
}
|