├── src ├── game_state.rs ├── sky_box │ ├── components.rs │ └── systems.rs ├── debug_fps │ ├── resources.rs │ └── systems.rs ├── wind │ └── resources.rs ├── focal_point │ ├── resources.rs │ └── systems.rs ├── stats │ ├── resources.rs │ └── systems.rs ├── camera │ ├── resources.rs │ └── systems.rs ├── ocean │ ├── components.rs │ ├── materials.rs │ ├── resources.rs │ └── systems.rs ├── orbiting_camera │ ├── events.rs │ ├── resources.rs │ └── systems.rs ├── menu │ ├── components.rs │ └── systems.rs ├── utils.rs ├── game_state │ └── states.rs ├── args │ ├── run_conditions.rs │ └── resources.rs ├── light.rs ├── wind.rs ├── player │ ├── components.rs │ └── systems.rs ├── widget_debug.rs ├── artillery │ ├── resources.rs │ ├── components.rs │ └── systems.rs ├── instructions.rs ├── assets │ ├── resources.rs │ └── systems.rs ├── args.rs ├── controls.rs ├── orbiting_camera.rs ├── sync_test.rs ├── utils │ ├── f32_extensions.rs │ ├── linear_algebra.rs │ ├── vec2_extensions.rs │ ├── aerodynamics.rs │ ├── hash.rs │ └── water_mechanics.rs ├── debug_fps.rs ├── focal_point.rs ├── sky_box.rs ├── controls │ └── components.rs ├── light │ └── systems.rs ├── physics │ ├── resources.rs │ ├── bundles.rs │ ├── components.rs │ └── systems.rs ├── camera.rs ├── connection.rs ├── stats.rs ├── menu.rs ├── widget_debug │ └── systems.rs ├── assets.rs ├── inputs.rs ├── instructions │ └── systems.rs ├── inputs │ └── systems.rs ├── artillery.rs ├── sync_test │ └── systems.rs ├── player.rs ├── connection │ └── systems.rs ├── physics.rs ├── main.rs └── ocean.rs ├── .vscode └── settings.json ├── assets ├── DISCLAIMER ├── models │ ├── cannon_ball.glb │ ├── medium_flag.glb │ ├── medium_helm.glb │ ├── medium_hull.glb │ ├── medium_canon.glb │ ├── raft_with_mast.glb │ └── medium_pirate_sail.glb ├── fonts │ └── the-bomb-regular.otf └── shaders │ ├── water_dynamics.wgsl │ ├── ocean_material_bindings.wgsl │ ├── ocean_material_prepass.wgsl │ ├── ocean_material.wgsl │ ├── not_working_merged_ocean_material_shader.wgsl │ └── utils.wgsl ├── wasm ├── models │ ├── cannon_ball.glb.meta │ ├── medium_canon.glb.meta │ ├── medium_flag.glb.meta │ ├── medium_helm.glb.meta │ ├── medium_hull.glb.meta │ ├── pirate_flag.glb.meta │ ├── medium_pirate_sail.glb.meta │ └── raft_with_mast.glb.meta ├── fonts │ └── the-bomb-regular.otf.meta ├── manifest.json ├── skyboxes │ └── basic.png.meta ├── index.html └── restart-audio-context.js ├── .idea └── vcs.xml ├── LICENSE ├── justfile ├── Cargo.toml ├── README.md ├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore └── ABOUT_TEMPLATE.md /src/game_state.rs: -------------------------------------------------------------------------------- 1 | pub mod states; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.showUnlinkedFileNotification": false 3 | } -------------------------------------------------------------------------------- /assets/DISCLAIMER: -------------------------------------------------------------------------------- 1 | Asset files come with their own licenses and may not be used outside this project. -------------------------------------------------------------------------------- /src/sky_box/components.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component)] 4 | pub struct Sky; 5 | -------------------------------------------------------------------------------- /src/debug_fps/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component)] 4 | pub struct DebugFps; 5 | -------------------------------------------------------------------------------- /src/wind/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Resource)] 4 | pub struct Wind(pub Vec3); 5 | -------------------------------------------------------------------------------- /assets/models/cannon_ball.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudijo/pirate-sea-jam/HEAD/assets/models/cannon_ball.glb -------------------------------------------------------------------------------- /assets/models/medium_flag.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudijo/pirate-sea-jam/HEAD/assets/models/medium_flag.glb -------------------------------------------------------------------------------- /assets/models/medium_helm.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudijo/pirate-sea-jam/HEAD/assets/models/medium_helm.glb -------------------------------------------------------------------------------- /assets/models/medium_hull.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudijo/pirate-sea-jam/HEAD/assets/models/medium_hull.glb -------------------------------------------------------------------------------- /assets/models/medium_canon.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudijo/pirate-sea-jam/HEAD/assets/models/medium_canon.glb -------------------------------------------------------------------------------- /assets/models/raft_with_mast.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudijo/pirate-sea-jam/HEAD/assets/models/raft_with_mast.glb -------------------------------------------------------------------------------- /src/focal_point/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Resource)] 4 | pub struct FocalPoint(pub Vec3); 5 | -------------------------------------------------------------------------------- /src/stats/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Resource)] 4 | pub struct NetworkStatsTimer(pub Timer); 5 | -------------------------------------------------------------------------------- /assets/fonts/the-bomb-regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudijo/pirate-sea-jam/HEAD/assets/fonts/the-bomb-regular.otf -------------------------------------------------------------------------------- /assets/models/medium_pirate_sail.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claudijo/pirate-sea-jam/HEAD/assets/models/medium_pirate_sail.glb -------------------------------------------------------------------------------- /src/camera/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Resource)] 4 | pub struct MainCamera { 5 | pub id: Entity, 6 | } 7 | -------------------------------------------------------------------------------- /src/ocean/components.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component)] 4 | pub struct OceanTile { 5 | pub offset: Vec3, 6 | } 7 | -------------------------------------------------------------------------------- /src/orbiting_camera/events.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Event)] 4 | pub struct OrbitMotion { 5 | pub delta: Vec2, 6 | } 7 | -------------------------------------------------------------------------------- /src/menu/components.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component)] 4 | pub struct StartGameButton; 5 | 6 | #[derive(Component)] 7 | pub struct StartMenuLayout; 8 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | pub mod aerodynamics; 2 | pub mod f32_extensions; 3 | pub mod hash; 4 | pub mod linear_algebra; 5 | pub mod vec2_extensions; 6 | pub mod water_mechanics; 7 | -------------------------------------------------------------------------------- /wasm/models/cannon_ball.glb.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_gltf::loader::GltfLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /wasm/models/medium_canon.glb.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_gltf::loader::GltfLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /wasm/models/medium_flag.glb.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_gltf::loader::GltfLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /wasm/models/medium_helm.glb.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_gltf::loader::GltfLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /wasm/models/medium_hull.glb.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_gltf::loader::GltfLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /wasm/models/pirate_flag.glb.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_gltf::loader::GltfLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /wasm/models/medium_pirate_sail.glb.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_gltf::loader::GltfLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /wasm/models/raft_with_mast.glb.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_gltf::loader::GltfLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /wasm/fonts/the-bomb-regular.otf.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_text::font_loader::FontLoader", 5 | settings: (), 6 | ), 7 | ) -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Bevy is dual-licensed under either 2 | 3 | * MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT) 4 | * Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 5 | 6 | at your option. -------------------------------------------------------------------------------- /src/game_state/states.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Clone, Debug, Default, Hash, Eq, States, PartialEq)] 4 | pub enum GameState { 5 | #[default] 6 | LoadingAssets, 7 | SplashScreen, 8 | Matchmaking, 9 | InGame, 10 | } 11 | -------------------------------------------------------------------------------- /src/args/run_conditions.rs: -------------------------------------------------------------------------------- 1 | use crate::args::resources::Args; 2 | use bevy::prelude::*; 3 | 4 | pub fn sync_test_mode(args: Res) -> bool { 5 | args.sync_test 6 | } 7 | 8 | pub fn p2p_mode(args: Res) -> bool { 9 | !args.sync_test 10 | } 11 | -------------------------------------------------------------------------------- /src/light.rs: -------------------------------------------------------------------------------- 1 | use crate::light::systems::spawn_light; 2 | use bevy::prelude::*; 3 | 4 | mod systems; 5 | 6 | pub struct LightPlugin; 7 | 8 | impl Plugin for LightPlugin { 9 | fn build(&self, app: &mut App) { 10 | app.add_systems(Startup, spawn_light); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/wind.rs: -------------------------------------------------------------------------------- 1 | use crate::wind::resources::Wind; 2 | use bevy::prelude::*; 3 | 4 | pub mod resources; 5 | 6 | pub struct WindPlugin; 7 | 8 | impl Plugin for WindPlugin { 9 | fn build(&self, app: &mut App) { 10 | app.insert_resource(Wind(Vec3::new(6., 0., 0.))); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/player/components.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Default, Reflect, Component, Clone, Copy)] 4 | #[reflect(Component)] 5 | pub struct Player { 6 | pub handle: usize, 7 | } 8 | 9 | #[derive(Component)] 10 | pub struct Wheel; 11 | 12 | #[derive(Component)] 13 | pub struct Flag; 14 | -------------------------------------------------------------------------------- /wasm/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pirate Sea Jam", 3 | "short_name": "Pirate Sea Jam", 4 | "start_url": ".", 5 | "display": "fullscreen", 6 | "orientation": "landscape", 7 | "background_color": "#ccc", 8 | "description": "Jam-sized pirate game prototype in the making. Written in Rust and Bevy. Made with ❤" 9 | } -------------------------------------------------------------------------------- /wasm/skyboxes/basic.png.meta: -------------------------------------------------------------------------------- 1 | ( 2 | meta_format_version: "1.0", 3 | asset: Load( 4 | loader: "bevy_render::texture::image_loader::ImageLoader", 5 | settings: ( 6 | format: FromExtension, 7 | is_srgb: true, 8 | sampler: Default, 9 | ), 10 | ), 11 | ) -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | default: run 2 | 3 | build-wasm: 4 | cargo build --release --target wasm32-unknown-unknown --no-default-features 5 | wasm-bindgen --no-typescript --out-name bevy_game --out-dir wasm --target web target/wasm32-unknown-unknown/release/pirate-sea-jam.wasm 6 | cp -r assets wasm/ 7 | 8 | run: build-wasm 9 | sfz -b 0.0.0.0 ./wasm -------------------------------------------------------------------------------- /src/widget_debug.rs: -------------------------------------------------------------------------------- 1 | mod systems; 2 | 3 | use crate::widget_debug::systems::{debug_buoys, debug_particle}; 4 | use bevy::prelude::*; 5 | 6 | pub struct WidgetDebugPlugin; 7 | 8 | impl Plugin for WidgetDebugPlugin { 9 | fn build(&self, app: &mut App) { 10 | app.add_systems(Update, (debug_buoys, debug_particle)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/artillery/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Resource)] 5 | pub struct StartAimArtilleryAnimationClips { 6 | pub handles: HashMap<&'static str, Handle>, 7 | } 8 | 9 | #[derive(Resource)] 10 | pub struct EndAimArtilleryAnimationClips { 11 | pub handles: HashMap<&'static str, Handle>, 12 | } 13 | -------------------------------------------------------------------------------- /src/instructions.rs: -------------------------------------------------------------------------------- 1 | use crate::camera::systems::spawn_camera; 2 | use crate::instructions::systems::display_control_keys; 3 | use bevy::prelude::*; 4 | 5 | mod systems; 6 | 7 | pub struct InstructionsPlugin; 8 | 9 | impl Plugin for InstructionsPlugin { 10 | fn build(&self, app: &mut App) { 11 | app.add_systems(Startup, display_control_keys.after(spawn_camera)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/assets/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Resource, Default)] 5 | pub struct ModelAssets { 6 | pub scene_handles: HashMap<&'static str, Handle>, 7 | pub mesh_handles: HashMap<&'static str, Handle>, 8 | } 9 | 10 | #[derive(Resource, Default)] 11 | pub struct FontAssets { 12 | pub font_handles: HashMap<&'static str, Handle>, 13 | } 14 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use crate::args::resources::Args; 2 | use bevy::app::{App, Plugin}; 3 | use bevy::log::info; 4 | use clap::Parser; 5 | 6 | pub mod resources; 7 | pub mod run_conditions; 8 | 9 | pub struct ArgsPlugin; 10 | 11 | impl Plugin for ArgsPlugin { 12 | fn build(&self, app: &mut App) { 13 | let args = Args::parse(); 14 | info!("{args:?}"); 15 | 16 | app.insert_resource(args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/artillery/components.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Component, Clone, Copy, Default)] 4 | pub struct ArtilleryReady(pub bool); 5 | 6 | #[derive(Component, Clone, Copy, Default)] 7 | pub struct ArtilleryAiming(pub bool); 8 | 9 | #[derive(Component, Clone, Copy, Default)] 10 | pub struct Projectile; 11 | 12 | #[derive(Component, Clone, Copy, Default)] 13 | pub struct Artillery { 14 | pub muzzle_velocity: f32, 15 | pub is_aiming: bool, 16 | } 17 | -------------------------------------------------------------------------------- /src/controls.rs: -------------------------------------------------------------------------------- 1 | use crate::controls::components::{checksum_wheel_turn_ratio, WheelTurnRatio}; 2 | use bevy::prelude::*; 3 | use bevy_ggrs::GgrsApp; 4 | 5 | pub mod components; 6 | 7 | pub struct ShipPlugin; 8 | 9 | impl Plugin for ShipPlugin { 10 | fn build(&self, app: &mut App) { 11 | // Component candidates for roll back 12 | app.rollback_component_with_copy::(); 13 | 14 | app.checksum_component::(checksum_wheel_turn_ratio); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/args/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use clap::Parser; 4 | 5 | #[derive(Parser, Resource, Debug, Clone)] 6 | pub struct Args { 7 | // runs the game in sync test mode / local player mode 8 | #[clap(long, default_value = "true")] 9 | pub sync_test: bool, 10 | 11 | // Set to 2 for doing sync tests, 0 for optimized single player game 12 | #[clap(long, default_value = "0")] 13 | pub check_distance: usize, 14 | 15 | #[clap(long, default_value = "1")] 16 | pub num_players: usize, 17 | } 18 | -------------------------------------------------------------------------------- /src/orbiting_camera.rs: -------------------------------------------------------------------------------- 1 | pub mod events; 2 | pub mod resources; 3 | mod systems; 4 | 5 | use crate::game_state::states::GameState; 6 | use crate::orbiting_camera::events::OrbitMotion; 7 | use crate::orbiting_camera::systems::orbit; 8 | use bevy::prelude::*; 9 | 10 | pub struct OrbitingCameraPlugin; 11 | 12 | impl Plugin for OrbitingCameraPlugin { 13 | fn build(&self, app: &mut App) { 14 | app.add_event::(); 15 | 16 | app.add_systems(Update, orbit.run_if(in_state(GameState::InGame))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/sync_test.rs: -------------------------------------------------------------------------------- 1 | mod systems; 2 | 3 | use crate::args::run_conditions::sync_test_mode; 4 | use crate::game_state::states::GameState; 5 | use crate::sync_test::systems::start_sync_test_session; 6 | use bevy::prelude::*; 7 | 8 | pub struct SyncTestPlugin; 9 | 10 | impl Plugin for SyncTestPlugin { 11 | fn build(&self, app: &mut App) { 12 | app.add_systems( 13 | Update, 14 | start_sync_test_session 15 | .run_if(in_state(GameState::Matchmaking).and_then(sync_test_mode)), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/f32_extensions.rs: -------------------------------------------------------------------------------- 1 | use bevy::math::FloatExt; 2 | use std::f32::consts::E; 3 | 4 | pub trait F32Ext { 5 | #[allow(dead_code)] 6 | fn damp(self, rhs: Self, lambda: f32, delta_time: f32) -> Self; 7 | } 8 | impl F32Ext for f32 { 9 | // lambda has range between `0` and infinity, will approach rhs 10 | // See https://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ 11 | fn damp(self, rhs: Self, lambda: f32, delta_time: f32) -> Self { 12 | self.lerp(rhs, 1. - E.powf(-lambda * delta_time)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/debug_fps.rs: -------------------------------------------------------------------------------- 1 | use crate::camera::systems::spawn_camera; 2 | use crate::debug_fps::systems::{spawn_debug_fps, update_debug_fps}; 3 | use bevy::diagnostic::FrameTimeDiagnosticsPlugin; 4 | use bevy::prelude::*; 5 | 6 | mod resources; 7 | mod systems; 8 | 9 | pub struct DebugFpsPlugin; 10 | 11 | impl Plugin for DebugFpsPlugin { 12 | fn build(&self, app: &mut App) { 13 | app.add_plugins(FrameTimeDiagnosticsPlugin); 14 | 15 | app.add_systems(Startup, spawn_debug_fps.after(spawn_camera)); 16 | 17 | app.add_systems(Update, update_debug_fps); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/focal_point.rs: -------------------------------------------------------------------------------- 1 | pub mod resources; 2 | mod systems; 3 | 4 | use crate::focal_point::resources::FocalPoint; 5 | use crate::focal_point::systems::update_focal_point; 6 | use crate::game_state::states::GameState; 7 | use bevy::prelude::*; 8 | 9 | pub struct FocalPointPlugin; 10 | 11 | impl Plugin for FocalPointPlugin { 12 | fn build(&self, app: &mut App) { 13 | app.insert_resource(FocalPoint(Vec3::ZERO)); 14 | 15 | app.add_systems( 16 | Update, 17 | update_focal_point.run_if(in_state(GameState::InGame)), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/sky_box.rs: -------------------------------------------------------------------------------- 1 | use crate::focal_point::resources::FocalPoint; 2 | use crate::sky_box::systems::{spawn_sky_box, sync_sky_box_center_offset}; 3 | use bevy::app::{App, Plugin, Startup, Update}; 4 | use bevy::prelude::*; 5 | 6 | mod components; 7 | mod systems; 8 | 9 | pub struct SkyBoxPlugin; 10 | 11 | impl Plugin for SkyBoxPlugin { 12 | fn build(&self, app: &mut App) { 13 | app.add_systems(Startup, spawn_sky_box); 14 | 15 | app.add_systems( 16 | Update, 17 | sync_sky_box_center_offset.run_if(resource_changed::), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/orbiting_camera/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use std::f32::consts::FRAC_PI_2; 3 | 4 | #[derive(Component)] 5 | pub struct OrbitingCamera { 6 | pub radius: f32, 7 | pub pitch: f32, 8 | pub yaw: f32, 9 | pub min_pitch: f32, 10 | pub max_pitch: f32, 11 | } 12 | 13 | impl Default for OrbitingCamera { 14 | fn default() -> Self { 15 | OrbitingCamera { 16 | radius: 10., 17 | pitch: 30_f32.to_radians(), 18 | yaw: 0., 19 | min_pitch: 10_f32.to_radians(), 20 | max_pitch: FRAC_PI_2, 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /wasm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/controls/components.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::hash::hash_f32_number; 2 | use bevy::prelude::*; 3 | 4 | #[derive(Component, Reflect, Clone, Copy, Default)] 5 | #[reflect(Component)] 6 | pub struct WheelTurnRatio(pub f32); 7 | 8 | #[derive(Component, Reflect, Clone, Copy, Default)] 9 | #[reflect(Component)] 10 | pub struct SailTrimRatio(pub f32); 11 | 12 | #[derive(Component, Reflect, Clone, Copy, Default)] 13 | #[reflect(Component)] 14 | pub struct Controls { 15 | pub turn_action: i32, 16 | pub accelerate_action: i32, 17 | } 18 | 19 | pub fn checksum_wheel_turn_ratio(value: &WheelTurnRatio) -> u64 { 20 | hash_f32_number(value.0) 21 | } 22 | -------------------------------------------------------------------------------- /src/focal_point/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::focal_point::resources::FocalPoint; 2 | use crate::player::components::Player; 3 | use bevy::prelude::*; 4 | use bevy_ggrs::LocalPlayers; 5 | 6 | pub fn update_focal_point( 7 | player_query: Query<(&Player, &Transform)>, 8 | local_players: Res, 9 | mut focal_point: ResMut, 10 | ) { 11 | for (player, transform) in &player_query { 12 | // Ignore non-local players 13 | if !local_players.0.contains(&player.handle) { 14 | continue; 15 | } 16 | 17 | focal_point.0 = transform.translation; 18 | focal_point.0.y = 0.; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/light/systems.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | pub fn spawn_light(mut commands: Commands) { 4 | // Sun 5 | commands.spawn(DirectionalLightBundle { 6 | directional_light: DirectionalLight { 7 | color: Color::rgb(0.98, 0.95, 0.82), 8 | shadows_enabled: true, 9 | ..default() 10 | }, 11 | transform: Transform::from_xyz(0.0, 0.0, 0.0) 12 | .with_rotation(Quat::from_rotation_x(-40_f32.to_radians())), 13 | ..default() 14 | }); 15 | 16 | // ambient light 17 | commands.insert_resource(AmbientLight { 18 | color: Color::rgb_u8(210, 220, 240), 19 | brightness: 160., 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/physics/resources.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | #[derive(Resource)] 4 | pub struct Gravity(pub Vec3); 5 | 6 | impl Default for Gravity { 7 | fn default() -> Self { 8 | Gravity(Vec3::NEG_Y * 15.) 9 | } 10 | } 11 | 12 | #[derive(Resource)] 13 | pub struct WaterDensity(pub f32); 14 | 15 | impl Default for WaterDensity { 16 | fn default() -> Self { 17 | // 1000 kg per cubic meter. 18 | WaterDensity(1000.) 19 | } 20 | } 21 | 22 | #[derive(Resource)] 23 | pub struct AirDensity(pub f32); 24 | 25 | impl Default for crate::physics::resources::AirDensity { 26 | fn default() -> Self { 27 | // 1.2 kg per cubic meter. 28 | AirDensity(1.2) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/linear_algebra.rs: -------------------------------------------------------------------------------- 1 | use bevy::math::Vec3; 2 | 3 | pub fn face_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] { 4 | let (a, b, c) = (Vec3::from(a), Vec3::from(b), Vec3::from(c)); 5 | (b - a).cross(c - a).normalize().into() 6 | } 7 | 8 | pub fn is_facing(source_direction: Vec3, source_position: Vec3, target_position: Vec3) -> bool { 9 | (target_position - source_position).dot(source_direction) > 0. 10 | } 11 | 12 | pub fn angle_between_perpendicular(vector: Vec3, normal: Vec3) -> f32 { 13 | (vector.dot(normal) / vector.length()).asin() 14 | } 15 | 16 | pub fn perpendicular_to_projection_direction(vector: Vec3, normal: Vec3) -> Vec3 { 17 | normal - normal.project_onto(vector) 18 | } 19 | -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | use crate::camera::systems::{ 2 | grab_pointer, release_pointer, release_pointer_on_escape, spawn_camera, 3 | }; 4 | use crate::game_state::states::GameState; 5 | use bevy::prelude::*; 6 | 7 | pub mod resources; 8 | pub mod systems; 9 | 10 | pub struct CameraPlugin; 11 | 12 | impl Plugin for CameraPlugin { 13 | fn build(&self, app: &mut App) { 14 | app.add_systems(Startup, spawn_camera); 15 | 16 | app.add_systems(OnEnter(GameState::InGame), grab_pointer); 17 | app.add_systems(OnExit(GameState::InGame), release_pointer); 18 | 19 | app.add_systems( 20 | Update, 21 | release_pointer_on_escape.run_if(in_state(GameState::InGame)), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/vec2_extensions.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use std::f32::consts::E; 3 | 4 | pub trait Vec2Ext { 5 | #[allow(dead_code)] 6 | fn extend_with_y(self, y: f32) -> Vec3; 7 | #[allow(dead_code)] 8 | fn damp(self, rhs: Self, lambda: f32, delta_time: f32) -> Self; 9 | } 10 | 11 | impl Vec2Ext for Vec2 { 12 | fn extend_with_y(self, y: f32) -> Vec3 { 13 | Vec3::new(self.x, y, self.y) 14 | } 15 | 16 | // lambda has range between `0` and infinity, will approach rhs 17 | // See https://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ 18 | fn damp(self, rhs: Self, lambda: f32, delta_time: f32) -> Self { 19 | self.lerp(rhs, 1. - E.powf(-lambda * delta_time)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /assets/shaders/water_dynamics.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path pirate_sea_jam::water_dynamics 2 | 3 | const PI: f32 = 3.14159265358979323846264338327950288; 4 | const GRAVITY: f32 = 10.; 5 | 6 | // `wave`: Vec4 containing direction x, direction z, steepness, wave_length 7 | fn gerstner_wave(wave: vec4, p: vec3, time: f32) -> vec3 { 8 | let steepness = wave.z; 9 | let wave_length = wave.w; 10 | 11 | let k: f32 = 2. * PI / wave_length; 12 | let c: f32 = sqrt(GRAVITY / k); 13 | let d: vec2 = normalize(wave.xy); 14 | let f: f32 = k * (dot(d, p.xz) - c * time); 15 | let a: f32 = steepness / k; 16 | 17 | return vec3( 18 | d.x * (a * cos(f)), 19 | a * sin(f), 20 | d.y * (a * cos(f)) 21 | ); 22 | } -------------------------------------------------------------------------------- /assets/shaders/ocean_material_bindings.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path pirate_sea_jam::ocean_material_bindings 2 | 3 | const WAVES_COUNT: i32 = 4; 4 | 5 | struct OceanTilelSettings { 6 | tile_offset: vec3, 7 | tile_size: f32, 8 | quad_cell_size: f32, 9 | tier: u32, 10 | time_scale: f32, 11 | waves: array, WAVES_COUNT>, 12 | subdivision_count: u32, 13 | } 14 | 15 | struct OceanPosition { 16 | center_offset: vec3, 17 | } 18 | 19 | struct RollbackTime { 20 | elapsed_seconds: f32, 21 | padding: vec3, 22 | } 23 | 24 | @group(2) @binding(100) 25 | var settings: OceanTilelSettings; 26 | 27 | @group(2) @binding(101) 28 | var position: OceanPosition; 29 | 30 | @group(2) @binding(102) 31 | var time: RollbackTime; -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pirate-sea-jam" 3 | version = "0.10.1" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | 7 | [dependencies] 8 | bevy = "0.13.2" 9 | bevy_editor_pls = "0.8.1" 10 | bevy_ggrs = { version = "0.15.0", features = ["wasm-bindgen"] } 11 | bevy_matchbox = { version = "0.9.0", features = ["ggrs"] } 12 | clap = { version = "4.5.4", features = ["derive"] } 13 | 14 | # Enable a small amount of optimization in debug mode 15 | [profile.dev] 16 | opt-level = 1 17 | 18 | # Enable high optimizations for dependencies (incl. Bevy), but not for our code: 19 | [profile.dev.package."*"] 20 | opt-level = 3 21 | 22 | # Optimize for WASM builds for size (see https://bevy-cheatbook.github.io/platforms/wasm/size-opt.html) 23 | [profile.release] 24 | opt-level = 's' 25 | lto = "thin" 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pirate Sea Jam 2 | 3 | Jam-sized pirate game prototype in the making. Written in Rust and Bevy. Made with ❤️ 4 | 5 | Demo and devlog available at [https://claudijo.itch.io/pirate-sea-jam](https://claudijo.itch.io/pirate-sea-jam) 6 | 7 | ## Changelog and Feature List 8 | The following brief changelog includes main concepts and features 9 | * Sailing simulation and custom physics (v0.10) (Reintroduce "Firing cannons") 10 | * Render ocean using WebGPU shaders (v0.9) (Temporarily invalidate "Mobile friendly" and "Firing cannons") 11 | * Infinite ocean (v0.8) 12 | * Dynamic third-person camera (v0.7) 13 | * ~~Mobile friendly (v0.6)~~ 14 | * Firing cannons (v0.5) 15 | * Player control (v0.4) 16 | * 3D models with textures (v0.3) 17 | * Buoyancy (v0.2) 18 | * Basic ocean tile with dynamic water waves (v0.1) 19 | -------------------------------------------------------------------------------- /src/connection.rs: -------------------------------------------------------------------------------- 1 | use crate::args::run_conditions::p2p_mode; 2 | use crate::connection::systems::{start_matchbox_socket, wait_for_players}; 3 | use crate::game_state::states::GameState; 4 | use bevy::prelude::*; 5 | 6 | pub mod systems; 7 | 8 | pub const MAX_PREDICTION: usize = 12; 9 | pub const FPS: usize = 60; 10 | pub const INPUT_DELAY: usize = 2; 11 | 12 | pub struct ConnectionPlugin; 13 | 14 | impl Plugin for ConnectionPlugin { 15 | fn build(&self, app: &mut App) { 16 | app.add_systems( 17 | OnEnter(GameState::Matchmaking), 18 | start_matchbox_socket.run_if(p2p_mode), 19 | ); 20 | 21 | app.add_systems( 22 | Update, 23 | wait_for_players.run_if(in_state(GameState::Matchmaking).and_then(p2p_mode)), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::args::run_conditions::p2p_mode; 2 | use crate::game_state::states::GameState; 3 | use crate::stats::resources::NetworkStatsTimer; 4 | use crate::stats::systems::{print_events, print_network_stats}; 5 | use bevy::prelude::*; 6 | 7 | mod resources; 8 | mod systems; 9 | 10 | pub struct StatsPlugin; 11 | 12 | impl Plugin for StatsPlugin { 13 | fn build(&self, app: &mut App) { 14 | app.insert_resource(NetworkStatsTimer(Timer::from_seconds( 15 | 2.0, 16 | TimerMode::Repeating, 17 | ))); 18 | app.add_systems(Update, print_events.run_if(in_state(GameState::InGame))); 19 | 20 | app.add_systems( 21 | Update, 22 | print_network_stats.run_if(in_state(GameState::InGame).and_then(p2p_mode)), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/menu.rs: -------------------------------------------------------------------------------- 1 | mod components; 2 | mod systems; 3 | 4 | use crate::game_state::states::GameState; 5 | use crate::menu::systems::{despawn_main_menu, handle_main_menu_interactions, spawn_main_menu}; 6 | use bevy::prelude::*; 7 | 8 | pub struct MenuPlugin; 9 | 10 | pub const START_BUTTON_NORMAL: Color = Color::rgb(0.9, 0.45, 0.21); 11 | pub const START_BUTTON_HOVER: Color = Color::rgb(0.87, 0.36, 0.18); 12 | 13 | impl Plugin for MenuPlugin { 14 | fn build(&self, app: &mut App) { 15 | app.add_systems(OnEnter(GameState::SplashScreen), spawn_main_menu) 16 | .add_systems(OnExit(GameState::SplashScreen), despawn_main_menu) 17 | .add_systems( 18 | Update, 19 | handle_main_menu_interactions.run_if(in_state(GameState::SplashScreen)), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/widget_debug/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::physics::components::{Buoy, Mass}; 2 | use bevy::prelude::*; 3 | 4 | pub fn debug_buoys(buoy_query: Query<(&Buoy, &GlobalTransform)>, mut gizmos: Gizmos) { 5 | for (buoy, global_transform) in &buoy_query { 6 | gizmos.cuboid( 7 | Transform::from_matrix(global_transform.compute_matrix()) 8 | .with_scale(Vec3::splat(buoy.max_depth * 2.)), 9 | Color::RED, 10 | ) 11 | } 12 | } 13 | 14 | pub fn debug_particle(particle_query: Query<(&Mass, &GlobalTransform)>, mut gizmos: Gizmos) { 15 | for (mass, global_transform) in &particle_query { 16 | let (_, rotation, translation) = global_transform.to_scale_rotation_translation(); 17 | gizmos.sphere(translation, rotation, mass.0 * 0.1, Color::PURPLE); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/physics/bundles.rs: -------------------------------------------------------------------------------- 1 | use crate::physics::components::{ 2 | AngularDamping, AngularVelocity, ExternalForce, ExternalImpulse, ExternalTorque, 3 | ExternalTorqueImpulse, Inertia, LinearDamping, LinearVelocity, Mass, 4 | }; 5 | use bevy::prelude::*; 6 | 7 | #[derive(Bundle, Default)] 8 | pub struct ParticleBundle { 9 | pub linear_velocity: LinearVelocity, 10 | pub external_force: ExternalForce, 11 | pub linear_damping: LinearDamping, 12 | pub external_impulse: ExternalImpulse, 13 | pub mass: Mass, 14 | } 15 | 16 | #[derive(Bundle, Default)] 17 | pub struct SpindleBundle { 18 | pub angular_velocity: AngularVelocity, 19 | pub external_torque: ExternalTorque, 20 | pub angular_damping: AngularDamping, 21 | pub external_torque_impulse: ExternalTorqueImpulse, 22 | pub inertia: Inertia, 23 | } 24 | -------------------------------------------------------------------------------- /src/assets.rs: -------------------------------------------------------------------------------- 1 | pub mod resources; 2 | mod systems; 3 | 4 | use crate::assets::systems::{add_assets, check_assets_ready}; 5 | use crate::game_state::states::GameState; 6 | use bevy::prelude::*; 7 | 8 | const MODEL_FILE_NAMES: [&str; 7] = [ 9 | "medium_flag.glb", 10 | "medium_helm.glb", 11 | "medium_hull.glb", 12 | "medium_pirate_sail.glb", 13 | "medium_canon.glb", 14 | "raft_with_mast.glb", 15 | "cannon_ball.glb", 16 | ]; 17 | 18 | const MESH_FILE_NAMES: [&str; 1] = ["medium_flag.glb"]; 19 | 20 | const FONT_FILE_NAMES: [&str; 1] = ["the-bomb-regular.otf"]; 21 | 22 | pub struct AssetsPlugin; 23 | 24 | impl Plugin for AssetsPlugin { 25 | fn build(&self, app: &mut App) { 26 | app.add_systems(OnEnter(GameState::LoadingAssets), add_assets); 27 | 28 | app.add_systems( 29 | Update, 30 | check_assets_ready.run_if(in_state(GameState::LoadingAssets)), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/inputs.rs: -------------------------------------------------------------------------------- 1 | use crate::inputs::systems::{read_local_inputs, read_mouse_input}; 2 | use bevy::prelude::*; 3 | use bevy_ggrs::ggrs::InputStatus; 4 | use bevy_ggrs::ReadInputs; 5 | 6 | mod systems; 7 | 8 | // pub const INPUT_UP: u8 = 1 << 0; 9 | // pub const INPUT_DOWN: u8 = 1 << 1; 10 | pub const INPUT_LEFT: u8 = 1 << 2; 11 | pub const INPUT_RIGHT: u8 = 1 << 3; 12 | pub const INPUT_FIRE: u8 = 1 << 4; 13 | 14 | pub fn turn_action_from_input(input_and_status: (u8, InputStatus)) -> i32 { 15 | let input = match input_and_status.1 { 16 | InputStatus::Confirmed => input_and_status.0, 17 | InputStatus::Predicted => input_and_status.0, 18 | InputStatus::Disconnected => 0, // disconnected players do nothing 19 | }; 20 | 21 | let mut turn: i32 = 0; 22 | 23 | if input & INPUT_RIGHT != 0 { 24 | turn += 1; 25 | } 26 | if input & INPUT_LEFT != 0 { 27 | turn -= 1; 28 | } 29 | 30 | turn 31 | } 32 | 33 | pub fn fire(input: u8) -> bool { 34 | input & INPUT_FIRE != 0 35 | } 36 | 37 | pub struct InputsPlugin; 38 | 39 | impl Plugin for InputsPlugin { 40 | fn build(&self, app: &mut App) { 41 | app.add_systems(ReadInputs, read_local_inputs); 42 | 43 | app.add_systems(Update, read_mouse_input); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/utils/aerodynamics.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::linear_algebra::{ 2 | angle_between_perpendicular, perpendicular_to_projection_direction, 3 | }; 4 | use bevy::math::Vec3; 5 | use std::ops::Neg; 6 | 7 | // https://aviation.stackexchange.com/a/64637 8 | pub fn simple_lift_coefficient(angle_of_attack: f32) -> f32 { 9 | (angle_of_attack * 2.).sin() 10 | } 11 | 12 | // https://aviation.stackexchange.com/a/64637 13 | pub fn simple_drag_coefficient(angle_of_attack: f32) -> f32 { 14 | 1. - (angle_of_attack * 2.).cos() 15 | } 16 | 17 | // https://math.stackexchange.com/a/4890320/1306679 18 | pub fn scaled_lift_drag(relative_velocity: Vec3, surface_normal: Vec3) -> (Vec3, Vec3) { 19 | if relative_velocity.length().abs() <= f32::EPSILON { 20 | return (Vec3::ZERO, Vec3::ZERO); 21 | } 22 | 23 | let normal = if relative_velocity.dot(surface_normal) > 0. { 24 | surface_normal 25 | } else { 26 | surface_normal.neg() 27 | }; 28 | 29 | let angle = angle_between_perpendicular(relative_velocity, normal); 30 | 31 | let lift = perpendicular_to_projection_direction(relative_velocity, normal).normalize() 32 | * simple_lift_coefficient(angle); 33 | let drag = relative_velocity.normalize() * simple_drag_coefficient(angle); 34 | 35 | (lift, drag) 36 | } 37 | -------------------------------------------------------------------------------- /src/sky_box/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::focal_point::resources::FocalPoint; 2 | use crate::ocean::OCEAN_TILE_SIZE; 3 | use crate::sky_box::components::Sky; 4 | use bevy::asset::Assets; 5 | use bevy::math::Vec3; 6 | use bevy::pbr::{NotShadowCaster, PbrBundle, StandardMaterial}; 7 | use bevy::prelude::*; 8 | 9 | pub fn spawn_sky_box( 10 | mut commands: Commands, 11 | mut meshes: ResMut>, 12 | mut materials: ResMut>, 13 | ) { 14 | // Sky box 15 | commands.spawn(( 16 | PbrBundle { 17 | mesh: meshes.add(Mesh::from(Cuboid::new(1., 1., 1.))), 18 | material: materials.add(StandardMaterial { 19 | base_color: Color::hex("a5cddf").unwrap(), 20 | unlit: true, 21 | cull_mode: None, 22 | ..default() 23 | }), 24 | transform: Transform::from_scale(Vec3::splat(OCEAN_TILE_SIZE * 9.)), 25 | ..default() 26 | }, 27 | Sky, 28 | NotShadowCaster, 29 | )); 30 | } 31 | 32 | pub fn sync_sky_box_center_offset( 33 | focal_point: Res, 34 | mut sky_box_query: Query<&mut Transform, With>, 35 | ) { 36 | for mut transform in &mut sky_box_query { 37 | transform.translation = focal_point.0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/debug_fps/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::camera::resources::MainCamera; 2 | use crate::debug_fps::resources::DebugFps; 3 | use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; 4 | use bevy::prelude::*; 5 | 6 | pub fn spawn_debug_fps(mut commands: Commands, main_camera: Res) { 7 | commands.spawn(( 8 | // Seems to be required in dev builds since using editor plugin results in multiple 9 | // cameras 10 | TargetCamera(main_camera.id), 11 | TextBundle::from_section( 12 | "FPS: ??", 13 | TextStyle { 14 | font_size: 14.0, 15 | ..default() 16 | }, 17 | ) 18 | .with_style(Style { 19 | position_type: PositionType::Absolute, 20 | top: Val::Px(2.), 21 | left: Val::Px(2.), 22 | ..default() 23 | }), 24 | DebugFps, 25 | )); 26 | } 27 | 28 | pub fn update_debug_fps( 29 | diagnostics: Res, 30 | mut fps_text_query: Query<&mut Text, With>, 31 | ) { 32 | for mut text in &mut fps_text_query { 33 | if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) { 34 | if let Some(value) = fps.average() { 35 | text.sections[0].value = format!("FPS: {value:.2}"); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /assets/shaders/ocean_material_prepass.wgsl: -------------------------------------------------------------------------------- 1 | // Mainly copied from default StandardMaterial prepass shader and removed unused stuff 2 | // See https://github.com/bevyengine/bevy/blob/main/crates/bevy_pbr/src/prepass/prepass.wgsl 3 | // Possibly merge with corresponding main shader. Been tried using `#ifdef PREPASS_PIPELINE` directive without success. 4 | 5 | #import bevy_pbr::{ 6 | mesh_functions::{mesh_position_local_to_clip, get_model_matrix}, 7 | prepass_io::{Vertex, VertexOutput}, 8 | } 9 | 10 | #import pirate_sea_jam::{ 11 | water_dynamics::gerstner_wave, 12 | ocean_material_bindings, 13 | } 14 | 15 | @vertex 16 | fn vertex(in: Vertex) -> VertexOutput { 17 | let time = ocean_material_bindings::time.elapsed_seconds * ocean_material_bindings::settings.time_scale; 18 | 19 | var out: VertexOutput; 20 | var next_position = in.position; 21 | 22 | for (var i = 0; i < ocean_material_bindings::WAVES_COUNT; i += 1) { 23 | next_position += gerstner_wave( 24 | ocean_material_bindings::settings.waves[i], 25 | in.position + ocean_material_bindings::position.center_offset + ocean_material_bindings::settings.tile_offset, 26 | time 27 | ); 28 | } 29 | 30 | var position = vec4(next_position, 1.); 31 | 32 | out.position = mesh_position_local_to_clip( 33 | get_model_matrix(in.instance_index), 34 | position 35 | ); 36 | 37 | return out; 38 | } -------------------------------------------------------------------------------- /src/instructions/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::camera::resources::MainCamera; 2 | use bevy::prelude::*; 3 | 4 | pub fn display_control_keys(mut commands: Commands, main_camera: Res) { 5 | commands 6 | .spawn(( 7 | // Seems to be required in dev builds since using editor plugin results in multiple 8 | // cameras 9 | TargetCamera(main_camera.id), 10 | NodeBundle { 11 | style: Style { 12 | width: Val::Percent(100.0), 13 | height: Val::Percent(100.0), 14 | align_items: AlignItems::Start, 15 | justify_content: JustifyContent::Center, 16 | padding: UiRect { 17 | left: Val::Px(16.), 18 | right: Val::Px(16.), 19 | top: Val::Px(16.), 20 | bottom: Val::Px(16.), 21 | }, 22 | ..default() 23 | }, 24 | ..default() 25 | }, 26 | )) 27 | .with_children(|child_builder| { 28 | child_builder.spawn(TextBundle::from_section( 29 | "[A] turn port | [D] turn starboard | [Space] fire cannons | [Mouse] orbit camera", 30 | TextStyle { 31 | font_size: 18.0, 32 | color: Color::WHITE, 33 | ..default() 34 | }, 35 | )); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/inputs/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::connection::systems::RollbackConfig; 2 | use crate::inputs::{INPUT_FIRE, INPUT_LEFT, INPUT_RIGHT}; 3 | use crate::orbiting_camera::events::OrbitMotion; 4 | use bevy::input::mouse::MouseMotion; 5 | use bevy::prelude::*; 6 | use bevy::utils::HashMap; 7 | use bevy_ggrs::{LocalInputs, LocalPlayers}; 8 | 9 | pub fn read_local_inputs( 10 | mut commands: Commands, 11 | keys: Res>, 12 | local_players: Res, 13 | ) { 14 | let mut local_inputs = HashMap::new(); 15 | 16 | for handle in &local_players.0 { 17 | let mut input = 0u8; 18 | if keys.any_pressed([KeyCode::ArrowLeft, KeyCode::KeyA]) { 19 | input |= INPUT_LEFT 20 | } 21 | if keys.any_pressed([KeyCode::ArrowRight, KeyCode::KeyD]) { 22 | input |= INPUT_RIGHT; 23 | } 24 | if keys.any_pressed([KeyCode::Space, KeyCode::Enter]) { 25 | input |= INPUT_FIRE; 26 | } 27 | 28 | local_inputs.insert(*handle, input); 29 | } 30 | 31 | commands.insert_resource(LocalInputs::(local_inputs)); 32 | } 33 | 34 | pub fn read_mouse_input( 35 | mut mouse_motion_event_reader: EventReader, 36 | mut orbit_motion_event_writer: EventWriter, 37 | ) { 38 | for mouse_motion_event in mouse_motion_event_reader.read() { 39 | orbit_motion_event_writer.send(OrbitMotion { 40 | delta: mouse_motion_event.delta, 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/artillery.rs: -------------------------------------------------------------------------------- 1 | use crate::artillery::components::{ArtilleryAiming, ArtilleryReady}; 2 | use crate::artillery::systems::{ 3 | despawn_projectile, register_start_aim_artillery_animations, 4 | register_stop_aim_artillery_animations, start_aim_artillery, stop_aim_and_fire_artillery, 5 | }; 6 | use crate::physics::systems::update_orientation; 7 | use bevy::prelude::*; 8 | use bevy_ggrs::{GgrsApp, GgrsSchedule}; 9 | 10 | pub mod components; 11 | mod resources; 12 | mod systems; 13 | 14 | pub const PORT_BACK_CANNON_TAG: &str = "Port back cannon"; 15 | pub const PORT_FRONT_CANNON_TAG: &str = "Port front cannon"; 16 | pub const STARBOARD_BACK_CANNON_TAG: &str = "Starboard back cannon"; 17 | pub const STARBOARD_FRONT_CANNON_TAG: &str = "Starboard front cannon"; 18 | 19 | pub struct ArtilleryPlugin; 20 | 21 | impl Plugin for ArtilleryPlugin { 22 | fn build(&self, app: &mut App) { 23 | // Component candidates for roll back 24 | app.rollback_component_with_copy::(); 25 | app.rollback_component_with_copy::(); 26 | 27 | app.add_systems( 28 | Startup, 29 | ( 30 | register_start_aim_artillery_animations, 31 | register_stop_aim_artillery_animations, 32 | ), 33 | ); 34 | 35 | app.add_systems( 36 | GgrsSchedule, 37 | ( 38 | start_aim_artillery.after(update_orientation), 39 | stop_aim_and_fire_artillery.after(start_aim_artillery), 40 | despawn_projectile, 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/sync_test/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::args::resources::Args; 2 | use crate::connection::systems::RollbackConfig; 3 | use crate::connection::{FPS, INPUT_DELAY, MAX_PREDICTION}; 4 | use crate::game_state::states::GameState; 5 | use bevy::prelude::*; 6 | use bevy_ggrs::ggrs; 7 | 8 | pub fn start_sync_test_session( 9 | mut commands: Commands, 10 | mut next_state: ResMut>, 11 | args: Res, 12 | ) { 13 | info!("Starting synctest session"); 14 | let mut session_builder = ggrs::SessionBuilder::::new() 15 | .with_num_players(args.num_players) 16 | .with_max_prediction_window(MAX_PREDICTION) 17 | .expect("prediction window can't be 0") 18 | .with_fps(FPS) 19 | .expect("FPS can't be 0") 20 | .with_input_delay(INPUT_DELAY) 21 | // GGRS will simulate a rollback every frame and re-simulate the last n states, where n is the given 22 | // check_distance. All expensive operations are skipped if the check distance is 0, enabling use of synctest 23 | // mode for general local play. 24 | .with_check_distance(args.check_distance); 25 | 26 | for i in 0..args.num_players { 27 | session_builder = session_builder 28 | .add_player(ggrs::PlayerType::Local, i) 29 | .expect("failed to add player"); 30 | } 31 | 32 | let ggrs_session = session_builder 33 | .start_synctest_session() 34 | .expect("failed to start session"); 35 | 36 | commands.insert_resource(bevy_ggrs::Session::SyncTest(ggrs_session)); 37 | next_state.set(GameState::InGame); 38 | } 39 | -------------------------------------------------------------------------------- /src/stats/systems.rs: -------------------------------------------------------------------------------- 1 | use crate::connection::systems::RollbackConfig; 2 | use crate::stats::resources::NetworkStatsTimer; 3 | use bevy::prelude::*; 4 | use bevy_ggrs::prelude::*; 5 | 6 | pub fn print_network_stats( 7 | time: Res