Initial commit

This commit is contained in:
2026-08-06 18:53:46 +02:00
commit 03cbb1657d
24 changed files with 8773 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
# for Linux
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold", "-Zshare-generics=y", ]
[unstable]
codegen-backend = true
[profile.dev]
codegen-backend = "cranelift"
[profile.dev.package."*"]
codegen-backend = "llvm"
+2
View File
@@ -0,0 +1,2 @@
/target
/.jackdaw
Generated
+7856
View File
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
[package]
name = "game-test"
version = "0.1.0"
edition = "2024"
authors = [""]
# A normal Bevy project. jackdaw builds its own editor artifacts in
# .jackdaw/ and never changes this manifest; `cargo run` works with or
# without the editor installed.
# Stand this project on its own even when it is created inside another
# cargo workspace, which would otherwise adopt it and refuse to build
# ("current package believes it's in a workspace when it's not").
# Delete this to make it a member of the surrounding workspace instead.
[workspace]
[dependencies]
bevy = { version = "0.19", features = ["dynamic_linking", "free_camera"] }
# Loads the scenes you author in the editor (assets/*.bsn) and builds
# physics colliders from them. An ordinary dependency; remove it if you
# stop using authored scenes.
jackdaw_runtime = { version = "0.19", features = ["physics"], path="../jackdaw/crates/jackdaw_runtime" }
avian3d = "0.7"
arrayvec = "0.7.8"
bevy-inspector-egui = "0.37.0"
bevy_egui = "0.40.0"
# Enable a small amount of optimization in the dev profile.
[profile.dev]
opt-level = 1
# Enable a large amount of optimization in the dev profile for dependencies.
[profile.dev.package."*"]
opt-level = 3
+12
View File
@@ -0,0 +1,12 @@
# Game Test
A Bevy game, editable in the [jackdaw](https://github.com/jbuehler23/jackdaw) editor.
- `cargo run` plays the game standalone; no editor required.
- Open the folder in jackdaw to author scenes, then hit Play. The
editor builds its own artifacts into `.jackdaw/` (gitignored) and
never modifies your manifest or lockfile.
- Gameplay lives in `src/lib.rs` (`GamePlugin`); authored scenes live
in `assets/*.bsn`.
- Components you derive `Reflect` on appear in the editor's inspector
automatically; no registration code is needed.
+22
View File
@@ -0,0 +1,22 @@
# jackdaw project settings. Everything here has a working default;
# delete any line you are not using.
# The game plugin type inside your lib crate. Uncomment to override
# source detection.
# plugin = "GamePlugin"
# In a cargo workspace, the member jackdaw builds as the game.
# package = "my-game"
# Recorded when this project was set up. Jackdaw compares these on
# open so an editor upgrade is reported instead of surfacing as a
# build failure.
[jackdaw]
version = "0.19.0"
bevy = "0.19"
[[run]]
name = "Play"
# instances = 2
# env = { SERVER_ADDR = "127.0.0.1:5000" }
# args = []
+2
View File
@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"
+10
View File
@@ -0,0 +1,10 @@
use bevy::prelude::*;
#[derive(Component)]
pub struct FighterAttributes {
pub base_walk_accel: f32,
pub stick_walk_accel: f32,
pub max_walk_vel: f32,
pub ground_friction: f32,
pub dash_duration: u32
}
+100
View File
@@ -0,0 +1,100 @@
use bevy::{
color::palettes::css::{GREEN, HOT_PINK, RED, YELLOW},
ecs::relationship::Relationship,
prelude::*,
};
use crate::{
game_settings::GameSettings,
input::player::{ControlledBy, FighterInput},
stage::StagePlane,
};
#[derive(Component)]
pub struct Grounded;
#[derive(Component, Deref, DerefMut, Default, Debug)]
pub struct FighterVelocity(pub Vec2);
#[derive(Component, Deref, DerefMut, Default)]
pub struct FighterPreviousTranslation(pub Vec2);
#[derive(Component, Deref, DerefMut, Default)]
#[require(FighterPreviousTranslation)]
pub struct FighterTranslation(pub Vec2);
#[derive(Component, Debug)]
#[require(Transform)]
pub struct FighterECB {
pub vertical_half: f32,
pub horizontal_half: f32,
}
impl FighterECB {
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
let rel_down = trf.transform_point(Vec3::new(0.0, -self.vertical_half, 0.0));
let rel_left = trf.transform_point(Vec3::new(-self.horizontal_half, 0.0, 0.0));
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
[rel_up, rel_right, rel_down, rel_left]
}
}
pub fn debug_draw_ecb(ecbs: Query<(&FighterECB, &FighterTranslation)>, mut gizmos: Gizmos) {
for (ecb, trf) in ecbs {
let transformed = ecb.to_3d_lineloop(Transform::from_xyz(trf.x, trf.y, 0.0));
gizmos.lineloop(transformed, YELLOW);
}
}
pub fn integrate_gravity(
fighters: Query<(&mut FighterVelocity)>,
time: Res<Time<Fixed>>,
) {
/*for (movement, mut velocity) in fighters {
//velocity.y -= movement.gravity;
}*/
}
pub fn apply_motion(
fighters: Query<(
&FighterVelocity,
&mut FighterTranslation,
&mut FighterPreviousTranslation,
)>,
time: Res<Time<Fixed>>,
) {
for (velocity, mut translation, mut prev_translation) in fighters {
prev_translation.0 = translation.0;
translation.0 += velocity.0;
}
}
pub fn collide_fighter_with_scene(
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
stage_planes: Query<&StagePlane>,
mut gizmos: Gizmos,
) {
for (ecb, mut velocity, mut translation) in fighters {
for plane in stage_planes {
let dist_sq = ecb.vertical_half * ecb.vertical_half;
let ray = Ray2d::new(translation.0, Dir2::NEG_Y);
let intersection_result = plane
.intersect_ray(ray)
.filter(|result| result.position.distance_squared(translation.0) <= dist_sq);
if let Some(result) = intersection_result {
gizmos.sphere(
Isometry3d::from(Vec3::new(result.position.x, result.position.y, 0.0)),
0.1f32,
HOT_PINK,
);
velocity.0 = plane.project(velocity.0);
translation.0 = result.position + Vec2::new(0.0, ecb.vertical_half);
}
}
}
}
pub fn fighter_movement() {}
+8
View File
@@ -0,0 +1,8 @@
pub mod plugin;
pub mod fighter;
pub mod data;
pub mod state;
pub mod states;
pub use fighter::*;
pub use plugin::*;
pub use data::*;
+29
View File
@@ -0,0 +1,29 @@
use bevy::prelude::*;
use crate::fighter::states;
use crate::fighter::{
FighterECB, FighterTranslation, FighterVelocity, apply_motion, collide_fighter_with_scene, integrate_gravity, state::state_interrupt_system,
};
#[derive(Default)]
pub struct FighterPlugin;
impl Plugin for FighterPlugin {
fn build(&self, app: &mut App) {
app.add_systems(FixedPostUpdate, super::debug_draw_ecb)
.add_systems(
FixedUpdate,
(
state_interrupt_system::<states::wait::WaitState>,
state_interrupt_system::<states::walk::WalkState>,
state_interrupt_system::<states::dash::DashState>,
states::walk::walk_update,
states::dash::dash_update,
integrate_gravity,
apply_motion,
collide_fighter_with_scene,
)
.chain(),
)
.add_observer(states::dash::on_insert_dash);
}
}
+57
View File
@@ -0,0 +1,57 @@
use bevy::{ecs::relationship::RelationshipSourceCollection, prelude::*};
use crate::{fighter::{FighterAttributes, states}, game_settings::GameSettings, input::player::FighterInput};
pub enum FighterStateTransition {
Wait,
Walk,
Dash
}
pub trait FighterState : Component + Sized {
fn remove(&self,
state_context: &FighterStateContext,
commands: &mut Commands) {
commands.entity(state_context.entity).remove::<Self>();
}
fn check_interrupt(
&self,
state_context: &FighterStateContext,
) -> Option<FighterStateTransition>;
}
pub struct FighterStateContext<'a> {
pub input: &'a mut FighterInput,
pub fighter_attribs: &'a FighterAttributes,
pub game_settings: &'a Res<'a, GameSettings>,
pub entity: Entity,
}
fn create_state(transition: FighterStateTransition, state_context: FighterStateContext, commands: &mut Commands) {
let mut ent_cmd = commands.entity(state_context.entity);
match transition {
FighterStateTransition::Wait => ent_cmd.insert(states::wait::WaitState),
FighterStateTransition::Walk => ent_cmd.insert(states::walk::WalkState),
FighterStateTransition::Dash => ent_cmd.insert(states::dash::DashState::default()),
};
}
pub fn state_interrupt_system<T: FighterState + Component + std::fmt::Debug>(
query: Query<(Entity, &T, &mut FighterInput, &FighterAttributes)>,
game_settings: Res<GameSettings>,
mut commands: Commands,
) {
for (ent, state, mut input, attribs) in query {
info!("{:?}", state);
let state_context = FighterStateContext {
input: &mut input,
fighter_attribs: &attribs,
game_settings: &game_settings,
entity: ent,
};
if let Some(new_state) = state.check_interrupt(&state_context) {
commands.entity(ent).remove::<T>();
create_state(new_state, state_context, &mut commands);
}
}
}
+79
View File
@@ -0,0 +1,79 @@
use crate::{
fighter::state::{FighterState, FighterStateContext, FighterStateTransition},
input::player::FighterInput,
};
use bevy::prelude::*;
pub struct DashStateCreator {}
#[derive(Component, Debug)]
pub struct DashState {
frames_in_dash: u32,
}
impl Default for DashState {
fn default() -> Self {
Self { frames_in_dash: 0 }
}
}
impl FighterState for DashState {
fn check_interrupt(
&self,
state_context: &FighterStateContext,
) -> Option<FighterStateTransition> {
if self.frames_in_dash > state_context.fighter_attribs.dash_duration {
// Dash finish shenanigans
super::walk::check_input(state_context)
.or_else(|| super::wait::check_input(state_context))
} else if let Some(last_frame) = state_context.input.get_last_frame() {
check_smash_input_with_dir(state_context)
.filter(|dir| *dir != last_frame.movement.x.signum())
.and_then(|_| super::dash::check_input(state_context))
} else {
None
}
}
}
pub fn start(state_context: &mut FighterStateContext) {
state_context.input.clear_buffer();
}
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
state_context.input.has_smash_x_movement(
state_context
.game_settings
.input_common
.smash_input_reset_axis_threshold,
state_context
.game_settings
.input_common
.smash_input_axis_threshold,
state_context
.game_settings
.input_common
.smash_input_frame_threshold,
)
}
pub fn dash_update(dash_query: Query<&mut DashState>) {
for mut dash in dash_query {
dash.frames_in_dash += 1;
}
}
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
check_smash_input_with_dir(state_context).map(|_| FighterStateTransition::Dash)
}
pub fn on_insert_dash(
add: On<Insert, DashState>,
query: Query<(Entity, &mut FighterInput), With<DashState>>,
) {
for (entity, mut input) in query {
if entity == add.entity {
input.clear_buffer();
}
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod wait;
pub mod walk;
pub mod dash;
+23
View File
@@ -0,0 +1,23 @@
use bevy::prelude::*;
use crate::fighter::{state::{FighterState, FighterStateContext, FighterStateTransition}, states::walk};
#[derive(Component, Debug)]
pub struct WaitState;
impl FighterState for WaitState {
fn check_interrupt(
&self,
state_context: &FighterStateContext,
) -> Option<FighterStateTransition> {
walk::check_input(state_context)
}
}
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
if let Some(input_frame) = state_context.input.get_last_frame() {
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
return Some(FighterStateTransition::Wait);
}
}
None
}
+94
View File
@@ -0,0 +1,94 @@
use crate::{
fighter::{
FighterAttributes, FighterVelocity,
state::{FighterState, FighterStateContext, FighterStateTransition},
states::{dash, wait},
},
game_settings::GameSettings,
input::player::FighterInput,
};
use bevy::prelude::*;
#[derive(Component, Debug)]
pub struct WalkState;
impl FighterState for WalkState {
fn check_interrupt(
&self,
state_context: &FighterStateContext,
) -> Option<FighterStateTransition> {
wait::check_input(state_context).or_else(|| dash::check_input(state_context))
}
}
pub fn walk_update(
fighters: Query<(
&mut FighterVelocity,
&FighterAttributes,
&FighterInput,
), With<WalkState>>,
game_settings: Res<GameSettings>,
) {
for (mut velocity, attributes, input) in fighters {
if let Some(last_frame) = input.get_last_frame() {
let accel_mul = 1.0;
let mut accel = last_frame.movement.x * attributes.stick_walk_accel * accel_mul; // stick-proportional
accel += (last_frame.movement.x.signum() * attributes.base_walk_accel) * accel_mul; // constant
let target_vel = last_frame.movement.x * attributes.max_walk_vel * accel_mul;
if target_vel != 0.0 {
let mult = velocity.x / target_vel;
if mult > 0.0 && mult < 1.0 {
accel *= (1.0 - mult) * game_settings.figher_common.walk_speed_ease; // 0.5 — linear ease-out toward target
}
}
// Friction mode
if target_vel == 0.0 {
let mut out_friction = velocity.x.signum() * (-attributes.ground_friction);
// apply friction
if attributes.ground_friction.abs() > velocity.x.abs() {
out_friction = -velocity.x;
}
accel = out_friction;
} else {
let ground_max_horizontal_velocity =
game_settings.figher_common.ground_max_horizontal_velocity;
if !(velocity.x * accel < 0.0) {
// accelerating, not reversing
if accel > 0.0 {
if velocity.x + accel > target_vel {
accel = -attributes.ground_friction;
if velocity.x + accel < target_vel {
accel = target_vel - velocity.x;
}
if velocity.x + accel > ground_max_horizontal_velocity {
accel = ground_max_horizontal_velocity - velocity.x;
}
}
} else if velocity.x + accel < target_vel {
accel = attributes.ground_friction;
if velocity.x + accel > target_vel {
accel = target_vel - velocity.x;
}
if velocity.x + accel < -ground_max_horizontal_velocity {
accel = -ground_max_horizontal_velocity - velocity.x;
}
}
}
}
velocity.x += accel;
}
}
}
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
if let Some(input_frame) = state_context.input.get_last_frame() {
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.stick_deadzone {
return Some(FighterStateTransition::Walk);
}
}
None
}
+31
View File
@@ -0,0 +1,31 @@
use bevy::prelude::*;
pub struct FighterSettingsCommon {
pub walk_speed_ease: f32,
pub ground_max_horizontal_velocity: f32,
}
pub struct InputSettingsCommon {
pub stick_deadzone: f32,
pub trigger_deadzone: f32,
pub smash_input_reset_axis_threshold: f32,
pub smash_input_axis_threshold: f32,
pub smash_input_frame_threshold: u32
}
impl Default for InputSettingsCommon {
fn default() -> Self {
Self {
stick_deadzone: 0.28f32,
trigger_deadzone: 0.3f32,
smash_input_reset_axis_threshold: 0.25,
smash_input_axis_threshold: 0.8,
smash_input_frame_threshold: 2
}
}
}
#[derive(Resource)]
pub struct GameSettings {
pub figher_common: FighterSettingsCommon,
pub input_common: InputSettingsCommon
}
+43
View File
@@ -0,0 +1,43 @@
use bevy::prelude::*;
use crate::input::player::InputFrame;
use super::player::{FighterInput, BaseInputMap, InputActionState};
pub fn postprocess_input(input_state: Query<&mut FighterInput>) {
for mut player_input in input_state {
}
}
pub fn preprocess_keyboard_input(key: Res<ButtonInput<KeyCode>>, input_map: Res<BaseInputMap>, mut input_state: Query<&mut FighterInput>) {
// Keyboard input is only for player 0
for mut keeb_input in &mut input_state.iter_mut().take(1) {
let mut input_frame = InputFrame::default();
for keyboard_elment in &input_map.keyboard {
let action_state = if key.just_pressed(keyboard_elment.key) {
InputActionState::JustPressed
} else if key.just_released(keyboard_elment.key) {
InputActionState::JustReleased
} else if key.pressed(keyboard_elment.key) {
InputActionState::Pressed
} else {
InputActionState::Released
};
let action_strength = match action_state {
InputActionState::JustPressed | InputActionState::Pressed => 1.0f32,
InputActionState::JustReleased | InputActionState::Released => 0.0f32,
};
match keyboard_elment.action {
super::player::InputMapAction::MovementXDir(sign) => input_frame.movement.x += (sign as f32) * action_strength,
super::player::InputMapAction::MovementYDir(sign) => input_frame.movement.y += (sign as f32) * action_strength,
super::player::InputMapAction::Jump => todo!(),
}
}
input_frame.movement = input_frame.movement.normalize_or_zero();
input_frame.directional_attack = input_frame.directional_attack.normalize_or_zero();
keeb_input.push_input_frame(input_frame);
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod player;
pub mod logic;
pub mod plugin;
+93
View File
@@ -0,0 +1,93 @@
use std::collections::VecDeque;
use bevy::prelude::*;
use arrayvec::ArrayVec;
#[derive(Default, Clone)]
pub struct InputFrame {
pub movement: Vec2,
pub directional_attack: Vec2
}
#[derive(Component, Default)]
pub struct FighterInput {
input_buffer: VecDeque<InputFrame>
}
impl FighterInput {
const INPUT_BUFFER_SIZE: usize = 5;
pub fn get_last_frame(&self) -> Option<&InputFrame> {
self.input_buffer.back()
}
pub fn push_input_frame(&mut self, frame: InputFrame) {
while self.input_buffer.len() >= Self::INPUT_BUFFER_SIZE {
self.input_buffer.pop_front();
}
self.input_buffer.push_back(frame);
}
pub fn clear_buffer(&mut self) {
while self.input_buffer.len() > 1 {
self.input_buffer.pop_front();
}
}
pub fn has_smash_x_movement(&self, reset_deadzone: f32, threshold: f32, frame_threshold: u32) -> Option<f32> {
let iter = self.input_buffer.iter().zip(self.input_buffer.iter().skip(1));
let mut frames_in_deadzone = 0xFE;
for (prev, current) in iter {
let x_abs = current.movement.x.abs();
if x_abs < reset_deadzone {
frames_in_deadzone = 0xFE;
} else {
let prev_x_abs = prev.movement.x.abs();
if prev_x_abs < reset_deadzone || prev.movement.x.signum() != current.movement.x.signum() {
frames_in_deadzone = 0;
} else {
frames_in_deadzone += 1;
}
}
if x_abs >= threshold && frames_in_deadzone <= frame_threshold {
return Some(x_abs.signum());
}
}
None
}
}
#[derive(Debug)]
pub enum InputActionState {
JustPressed,
Pressed,
JustReleased,
Released
}
#[derive(Debug)]
pub enum InputMapAction {
MovementXDir(i32),
MovementYDir(i32),
Jump
}
#[derive(Debug)]
pub struct KeyboardInputMapElement {
pub key: KeyCode,
pub action: InputMapAction
}
#[derive(Resource)]
pub struct BaseInputMap {
pub keyboard: Vec<KeyboardInputMapElement>
}
#[derive(Component, Debug)]
#[relationship(relationship_target = ControlledBy)]
pub struct Controls(pub Entity);
#[derive(Component, Debug)]
#[relationship_target(relationship = Controls)]
pub struct ControlledBy(Entity);
+29
View File
@@ -0,0 +1,29 @@
use bevy::prelude::*;
use crate::fighter::*;
use crate::input::player::{ControlledBy, Controls, FighterInput};
use crate::input::{
logic::{postprocess_input, preprocess_keyboard_input}, player::{BaseInputMap, InputMapAction, KeyboardInputMapElement},
};
#[derive(Default)]
pub struct FighterInputPlugin;
impl Plugin for FighterInputPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(BaseInputMap {
keyboard: vec![
// right
KeyboardInputMapElement {
key: KeyCode::KeyD,
action: InputMapAction::MovementXDir(1),
},
// left
KeyboardInputMapElement {
key: KeyCode::KeyA,
action: InputMapAction::MovementXDir(-1),
},
],
})
.add_systems(FixedPreUpdate, (preprocess_keyboard_input, postprocess_input).chain());
}
}
+103
View File
@@ -0,0 +1,103 @@
//! game-test: gameplay shared between `cargo run` and the
//! editor's Play button.
//!
//! Scene content (entities, components, positions) lives in
//! `assets/*.bsn` files authored in the editor. Game behaviour
//! (systems, observers, resources) lives in [`GamePlugin`].
//!
//! # Try it
//!
//! 1. Open this project in jackdaw.
//! 2. Hierarchy: right-click, `Add > Cube`.
//! 3. Inspector: `Add Component > SpinningCube`. Set `speed` to `1.5`.
//! 4. `File > Save`, then click Play. The cube spins.
//! 5. `cargo run` launches the same game without the editor.
//!
//! # Adding your own components
//!
//! Write a component anywhere in this library (any module, not
//! `main.rs`), deriving `Component, Reflect, Default` with
//! `#[reflect(Component, Default)]`, like `SpinningCube` below. After you
//! save, click Rebuild in jackdaw (or run `jd build`) and it
//! appears in `Add Component`. No registration code is needed.
use bevy::prelude::*;
use jackdaw_runtime::prelude::*;
use crate::{fighter::{states::wait::WaitState, *}, game_settings::{FighterSettingsCommon, GameSettings, InputSettingsCommon}, stage::debug_draw_scene};
pub mod fighter;
pub mod input;
pub mod stage;
pub mod game_settings;
/// Your game's Bevy plugin. The editor finds it by this name (override
/// with `plugin = "..."` in jackdaw.toml) and runs it on Play; the
/// standalone binary adds it in `main.rs`.
#[derive(Default)]
pub struct GamePlugin;
pub fn setup(mut commands: Commands) {
let fighter = commands
.spawn((
Name("Fighter".into()),
FighterECB {
vertical_half: 0.5,
horizontal_half: 0.25,
},
FighterVelocity::default(),
FighterTranslation(Vec2::new(0.5, 1.25)),
FighterAttributes {
base_walk_accel: 0.01,
stick_walk_accel: 0.02,
max_walk_vel: 0.16,
ground_friction: 0.008,
dash_duration: 15
},
WaitState {},
input::player::FighterInput::default()
))
.id();
}
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, stage::test_scene.spawn())
.add_systems(Startup, setup)
.add_systems(Update, spin_cubes)
.add_systems(Update, debug_draw_scene)
.add_plugins(input::plugin::FighterInputPlugin)
.add_plugins(fighter::FighterPlugin)
.insert_resource(GameSettings {
figher_common: FighterSettingsCommon {
walk_speed_ease: 0.5,
ground_max_horizontal_velocity: 0.3
},
input_common: InputSettingsCommon::default(),
});
}
}
/// Spin-rate in radians per second. Attach it in the inspector; the
/// editor sees this component with no registration code because Bevy's
/// `reflect_auto_register` picks up the `Reflect` derive. `Default` +
/// `#[reflect(Default)]` let the editor add it and the game reconstruct
/// it with sensible starting values.
#[derive(Component, Reflect, Default)]
#[reflect(Component, Default, @EditorCategory::new("Actor"))]
pub struct SpinningCube {
pub speed: f32,
}
fn spin_cubes(time: Res<Time>, mut cubes: Query<(&SpinningCube, &mut Transform)>) {
let dt = time.delta_secs();
for (cube, mut transform) in &mut cubes {
transform.rotate_y(cube.speed * dt);
}
}
#[derive(Component, Reflect, Default)]
#[reflect(Component, Default, @EditorCategory::new("Actor"))]
pub struct TestCube {
pub frog: f32,
}
+40
View File
@@ -0,0 +1,40 @@
//! Standalone binary for game-test: `cargo run` plays the game
//! without the editor. The editor's Play button runs the same
//! [`game_test::GamePlugin`] through its own game runner instead.
use bevy::camera_controller::free_camera::FreeCameraPlugin;
use bevy::image::{ImageAddressMode, ImagePlugin, ImageSamplerDescriptor};
use bevy::prelude::*;
use bevy::remote::http::RemoteHttpPlugin;
use bevy::remote::{RemotePlugin};
use bevy_egui::EguiPlugin;
use bevy_inspector_egui::{
quick::WorldInspectorPlugin
};
fn main() -> AppExit {
// A `Repeat` sampler so the tileable materials authored in the
// editor render the same way at runtime; Bevy's default
// `ClampToEdge` smears texture edges on brush geometry.
let default_plugins = DefaultPlugins.set(ImagePlugin {
default_sampler: ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
address_mode_w: ImageAddressMode::Repeat,
..ImageSamplerDescriptor::linear()
},
});
App::new()
.add_plugins(default_plugins)
.add_plugins(avian3d::prelude::PhysicsPlugins::default())
.add_plugins(jackdaw_runtime::JackdawPlugin)
.add_plugins(game_test::GamePlugin)
.add_plugins(FreeCameraPlugin)
.add_plugins(RemotePlugin::default())
.add_plugins(RemoteHttpPlugin::default())
.add_plugins(EguiPlugin::default())
.add_plugins(WorldInspectorPlugin::default()) // adds default options and `InspectorEguiImpl`s
.insert_resource(Time::<Fixed>::from_hz(60.0))
.run()
}
+87
View File
@@ -0,0 +1,87 @@
use bevy::{camera_controller::free_camera::FreeCamera, color::palettes::css::{GREEN, RED}, prelude::*};
#[derive(Component, Reflect, Clone, Default)]
#[reflect(Component)]
pub struct StagePlane {
pub plane: Plane2d,
pub position: Vec2,
half_size: f32,
basis_x: Vec2,
}
pub struct StagePlaneIntersectResult {
pub position: Vec2
}
impl StagePlane {
pub fn new(normal: Dir2, half_size: f32, position: Vec2) -> Self {
let orthogonal = normal.perp();
let basis_x = orthogonal * half_size;
Self {
plane: Plane2d::new(*normal),
position,
half_size,
basis_x,
}
}
pub fn intersect_ray(&self, ray: Ray2d) -> Option<StagePlaneIntersectResult> {
if let Some(result) = ray.plane_intersection_point(self.position, self.plane) {
return Some(StagePlaneIntersectResult {
position: result
});
}
None
}
pub fn project(&self, vec: Vec2) -> Vec2 {
vec - vec.project_onto_normalized(self.plane.normal.as_vec2())
}
}
pub fn debug_draw_scene(mut gizmos: Gizmos, planes: Query<&StagePlane>) {
for plane in planes {
let plane_pos_3d = Vec3::new(plane.position.x, plane.position.y, 0.0);
let plane_normal_3d = Vec3::new(plane.plane.normal.x, plane.plane.normal.y, 0.0);
let plane_basis_x_3d = Vec3::new(plane.basis_x.x, plane.basis_x.y, 0.0);
gizmos.rect(
Transform::from_translation(plane_pos_3d).looking_to(plane_normal_3d, Dir3::Z).to_isometry(),
Vec2::new(plane.half_size * 2.0, 1.0),
GREEN,
);
gizmos.line(plane_pos_3d, plane_pos_3d + plane_basis_x_3d, RED);
}
}
/// set up a simple 3D scene
pub fn test_scene() -> impl SceneList {
let up = Dir2::Y;
bsn_list! [
(
#Cube
Mesh3d(asset_value(Cuboid::new(5.0, 1.0, 1.0)))
MeshMaterial3d::<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
Transform::from_xyz(0.0, 0.5, 0.0)
),
(
#StagePlane
template_value(StagePlane::new(up, 2.5, Vec2::new(0.0, 1.0)))
),
(
PointLight {
shadow_maps_enabled: true,
}
Transform::from_xyz(4.0, 8.0, 4.0)
),
(
Camera3d
template_value(Transform::from_xyz(0.0, 1.5, 9.0))
/*FreeCamera {
sensitivity: 0.2,
friction: 25.0,
walk_speed: 3.0,
run_speed: 9.0,
}*/
)
]
}