Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8b5107c92 | ||
|
|
dbb87c3930 |
Generated
+620
-1462
File diff suppressed because it is too large
Load Diff
+8
-7
@@ -3,12 +3,13 @@ name = "time_travel"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["client", "server"]
|
||||||
|
client = []
|
||||||
|
server = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aeronet = "0.19.0"
|
|
||||||
aeronet_replicon = { version = "0.19.0", features = ["client", "server"] }
|
|
||||||
avian2d = "0.5.0"
|
avian2d = "0.5.0"
|
||||||
bevy = { version = "0.18.1", features = ["debug"] }
|
bevy = "0.18.0"
|
||||||
bevy-inspector-egui = "0.36.0"
|
clap = { version = "4.5.56", features = ["derive"] }
|
||||||
bevy_replicon = "0.38.2"
|
|
||||||
component = "0.1.1"
|
|
||||||
rust-analyzer = "0.0.1"
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
/home/mitchell/Pictures/time_travel_logo.png
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
struct Material {
|
|
||||||
p0: vec2<f32>,
|
|
||||||
c0: vec2<f32>,
|
|
||||||
c1: vec2<f32>,
|
|
||||||
p1: vec2<f32>,
|
|
||||||
width: f32,
|
|
||||||
color: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@group(2) @binding(0)
|
|
||||||
var<uniform> mat: Material;
|
|
||||||
|
|
||||||
fn bezier(p0: vec2<f32>, c0: vec2<f32>, c1: vec2<f32>, p1: vec2<f32>, t: f32) -> vec2<f32> {
|
|
||||||
let u = 1.0 - t;
|
|
||||||
return
|
|
||||||
u*u*u*p0 +
|
|
||||||
3.0*u*u*t*c0 +
|
|
||||||
3.0*u*t*t*c1 +
|
|
||||||
t*t*t*p1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn distance_to_bezier(p: vec2<f32>) -> f32 {
|
|
||||||
var min_d = 1e9;
|
|
||||||
let steps = 32;
|
|
||||||
|
|
||||||
var prev = mat.p0;
|
|
||||||
for (var i = 1; i <= steps; i++) {
|
|
||||||
let t = f32(i) / f32(steps);
|
|
||||||
let cur = bezier(mat.p0, mat.c0, mat.c1, mat.p1, t);
|
|
||||||
|
|
||||||
// distance to segment
|
|
||||||
let v = cur - prev;
|
|
||||||
let w = p - prev;
|
|
||||||
let t_seg = clamp(dot(w, v) / dot(v, v), 0.0, 1.0);
|
|
||||||
let proj = prev + t_seg * v;
|
|
||||||
|
|
||||||
min_d = min(min_d, distance(p, proj));
|
|
||||||
prev = cur;
|
|
||||||
}
|
|
||||||
|
|
||||||
return min_d;
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fragment(
|
|
||||||
@location(0) world_pos: vec3<f32>,
|
|
||||||
) -> @location(0) vec4<f32> {
|
|
||||||
let d = distance_to_bezier(world_pos.xy);
|
|
||||||
|
|
||||||
let half_w = mat.width * 0.5;
|
|
||||||
let aa = fwidth(d);
|
|
||||||
|
|
||||||
let alpha = smoothstep(half_w + aa, half_w - aa, d);
|
|
||||||
|
|
||||||
if (alpha <= 0.001) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
|
|
||||||
return vec4(mat.color.rgb, mat.color.a * alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
struct Material {
|
|
||||||
color_a: vec4<f32>,
|
|
||||||
color_b: vec4<f32>,
|
|
||||||
border_color: vec4<f32>,
|
|
||||||
time: f32,
|
|
||||||
speed: f32,
|
|
||||||
border_size: f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
@group(2) @binding(0)
|
|
||||||
var<uniform> mat: Material;
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
let uv = in.uv;
|
|
||||||
|
|
||||||
// animate
|
|
||||||
let x = uv.x + params.time * params.speed;
|
|
||||||
let g = fract(x);
|
|
||||||
|
|
||||||
let fill = mix(params.color_a, params.color_b, g);
|
|
||||||
|
|
||||||
// borders
|
|
||||||
let top = step(uv.y, params.border_size);
|
|
||||||
let bottom = step(1.0 - params.border_size, uv.y);
|
|
||||||
let border_mask = max(top, bottom);
|
|
||||||
|
|
||||||
return mix(fill, params.border_color, border_mask);
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
const PI: f32 = 3.14159265358979323846264338327950288;
|
|
||||||
|
|
||||||
struct Uniforms {
|
|
||||||
/// The size of the canvas
|
|
||||||
size: vec2<f32>,
|
|
||||||
|
|
||||||
/// The length of the first horizontal segment
|
|
||||||
len_start: f32,
|
|
||||||
/// The length of the last horizontal segment
|
|
||||||
len_end: f32,
|
|
||||||
|
|
||||||
/// The inner half thickness
|
|
||||||
thickness: f32,
|
|
||||||
/// The thickness of the border (added to the inner thickness for total thickness)
|
|
||||||
border: f32,
|
|
||||||
|
|
||||||
/// Current time
|
|
||||||
time: f32,
|
|
||||||
/// The period of the pulse effect (in seconds)
|
|
||||||
pulse_period: f32,
|
|
||||||
/// The speed at which the pulse travels along the path (in units per second)
|
|
||||||
pulse_speed: f32,
|
|
||||||
/// The width of the pulse effect (in units)
|
|
||||||
pulse_width: f32,
|
|
||||||
|
|
||||||
/// The color of the border
|
|
||||||
border_color: vec4<f32>,
|
|
||||||
/// The color of the base of the track
|
|
||||||
color_base: vec4<f32>,
|
|
||||||
/// The color of the pulsing
|
|
||||||
color_accent: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@group(#{MATERIAL_BIND_GROUP}) @binding(0)
|
|
||||||
var<uniform> U: Uniforms;
|
|
||||||
|
|
||||||
// Distance to segment
|
|
||||||
fn sdSegment(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>) -> f32 {
|
|
||||||
let pa = p - a;
|
|
||||||
let ba = b - a;
|
|
||||||
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
|
||||||
return length(pa - ba * h);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sdArc(
|
|
||||||
p: vec2<f32>,
|
|
||||||
c: vec2<f32>,
|
|
||||||
r: f32,
|
|
||||||
a0: f32,
|
|
||||||
a1: f32
|
|
||||||
) -> f32 {
|
|
||||||
let d = p - c;
|
|
||||||
var ang = atan2(d.y, d.x);
|
|
||||||
|
|
||||||
var s = a0;
|
|
||||||
var e = a1;
|
|
||||||
if (e < s) { e += 2.0 * PI; }
|
|
||||||
if (ang < s) { ang += 2.0 * PI; }
|
|
||||||
|
|
||||||
let on_arc = (ang >= s) && (ang <= e);
|
|
||||||
|
|
||||||
let circle = abs(length(d) - r);
|
|
||||||
|
|
||||||
let p0 = c + r * vec2<f32>(cos(s), sin(s));
|
|
||||||
let p1 = c + r * vec2<f32>(cos(e), sin(e));
|
|
||||||
let ends = min(length(p - p0), length(p - p1));
|
|
||||||
|
|
||||||
return select(ends, circle, on_arc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute SDF and normalized distance along path
|
|
||||||
fn trackSDF(p: vec2<f32>) -> vec2<f32> {
|
|
||||||
let top_offset = U.thickness + U.border;
|
|
||||||
let bottom_offset = U.size.y - top_offset;
|
|
||||||
let midline = U.size.y / 2.0;
|
|
||||||
let drop = midline - top_offset;
|
|
||||||
let radius = drop / 2.0;
|
|
||||||
|
|
||||||
// Construct points
|
|
||||||
let p0 = vec2<f32>(U.size.x - U.len_start, top_offset);
|
|
||||||
let p1 = vec2<f32>(U.size.x - radius, top_offset);
|
|
||||||
let arc0_center = vec2<f32>(p1.x, drop / 2.0 + top_offset);
|
|
||||||
let p2 = vec2<f32>(p1.x, midline);
|
|
||||||
let p3 = vec2<f32>(radius, midline);
|
|
||||||
let arc1_center = vec2<f32>(p3.x, drop / 2.0 + midline);
|
|
||||||
let p4 = vec2<f32>(radius, bottom_offset);
|
|
||||||
let p5 = vec2<f32>(U.len_end, bottom_offset);
|
|
||||||
|
|
||||||
// Total length
|
|
||||||
let L0 = length(p1 - p0);
|
|
||||||
let L1 = PI * length(p2 - p1);
|
|
||||||
let L2 = length(p3 - p2);
|
|
||||||
let L3 = PI * length(p4 - p3);
|
|
||||||
let L4 = length(p5 - p4);
|
|
||||||
let total = L0 + L1 + L2 + L3 + L4;
|
|
||||||
|
|
||||||
var best_d = 1e9;
|
|
||||||
var best_t = 0.0;
|
|
||||||
|
|
||||||
// Helper macro-like inline pattern
|
|
||||||
// segment i with accumulated offset
|
|
||||||
var accum = 0.0;
|
|
||||||
|
|
||||||
// p0->p1
|
|
||||||
{
|
|
||||||
let d = sdSegment(p, p0, p1);
|
|
||||||
let ba = p1 - p0;
|
|
||||||
let h = clamp(dot(p - p0, ba) / dot(ba, ba), 0.0, 1.0);
|
|
||||||
let t = (accum + h * L0) / total;
|
|
||||||
if (d < best_d) { best_d = d; best_t = t; }
|
|
||||||
accum = accum + L0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ARC p1->p2
|
|
||||||
{
|
|
||||||
let d = sdArc(p, arc0_center, radius, PI * 3.0 / 2.0, PI / 2.0);
|
|
||||||
let ba = p2 - p1;
|
|
||||||
let h = clamp(dot(p - p1, ba) / dot(ba, ba), 0.0, 1.0);
|
|
||||||
let t = (accum + h * L1) / total;
|
|
||||||
if (d < best_d) { best_d = d; best_t = t; }
|
|
||||||
accum = accum + L1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// p2->p3 (full width)
|
|
||||||
{
|
|
||||||
let d = sdSegment(p, p2, p3);
|
|
||||||
let ba = p3 - p2;
|
|
||||||
let h = clamp(dot(p - p2, ba) / dot(ba, ba), 0.0, 1.0);
|
|
||||||
let t = (accum + h * L2) / total;
|
|
||||||
if (d < best_d) { best_d = d; best_t = t; }
|
|
||||||
accum = accum + L2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ARC p3->p4
|
|
||||||
{
|
|
||||||
let d = sdArc(p, arc1_center, radius, PI / 2.0, PI * 3.0 / 2.0);
|
|
||||||
let ba = p4 - p3;
|
|
||||||
let h = clamp(dot(p - p3, ba) / dot(ba, ba), 0.0, 1.0);
|
|
||||||
let t = (accum + h * L3) / total;
|
|
||||||
if (d < best_d) { best_d = d; best_t = t; }
|
|
||||||
accum = accum + L3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// p4->p5
|
|
||||||
{
|
|
||||||
let d = sdSegment(p, p4, p5);
|
|
||||||
let ba = p5 - p4;
|
|
||||||
let h = clamp(dot(p - p4, ba) / dot(ba, ba), 0.0, 1.0);
|
|
||||||
let t = (accum + h * L4) / total;
|
|
||||||
if (d < best_d) { best_d = d; best_t = t; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return vec2<f32>(best_d, best_t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32> {
|
|
||||||
let p = frag_coord.xy;
|
|
||||||
|
|
||||||
let res = trackSDF(p);
|
|
||||||
let d = res.x;
|
|
||||||
let t = res.y;
|
|
||||||
// bounces back and forth between 0.0 and 1.0
|
|
||||||
let t_wrap = abs(((t + U.time) % 2.0) - 1.0);
|
|
||||||
// bounces back and forth between 0.0 and U.pulse_period
|
|
||||||
let t_wrap_pulse = abs(((t - U.pulse_speed * U.time) % (U.pulse_period * 2.0)) - U.pulse_period);
|
|
||||||
|
|
||||||
let inner = U.thickness;
|
|
||||||
let outer = U.thickness + U.border;
|
|
||||||
|
|
||||||
let aa = 1.0;
|
|
||||||
|
|
||||||
let inner_a = smoothstep(inner + aa, inner - aa, d);
|
|
||||||
let outer_a = smoothstep(outer + aa, outer - aa, d);
|
|
||||||
let border_mask = clamp(outer_a - inner_a, 0.0, 1.0);
|
|
||||||
|
|
||||||
// let color_mix = smoothstep(-0.1, 0.0, -abs(t - t_wrap_pulse));
|
|
||||||
let color_mix = smoothstep(-U.pulse_width, 0.0, -abs(t - t_wrap));
|
|
||||||
let inner_color = mix(U.color_base, U.color_accent, color_mix);
|
|
||||||
// let inner_color = mix(U.color_base, U.color_accent, d);
|
|
||||||
|
|
||||||
let color = inner_color * inner_a + U.border_color * border_mask;
|
|
||||||
let alpha = max(inner_a, outer_a);
|
|
||||||
|
|
||||||
return vec4<f32>(color.rgb, alpha);
|
|
||||||
// return vec4<f32>(d / 3000.0, t / 3000.0, 0.0, 1.0);
|
|
||||||
// return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
struct Uniforms {
|
|
||||||
resolution: vec2<f32>, // viewport size (pixels)
|
|
||||||
origin: vec2<f32>, // start point (pixels)
|
|
||||||
|
|
||||||
len0: f32, // first horizontal segment (to the right)
|
|
||||||
len2: f32, // last horizontal segment (to the right)
|
|
||||||
drop: f32, // vertical drop between segments
|
|
||||||
|
|
||||||
thickness: f32, // inner half-width
|
|
||||||
border: f32, // border thickness
|
|
||||||
|
|
||||||
border_color: vec4<f32>,
|
|
||||||
color_start: vec4<f32>,
|
|
||||||
color_end: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
|
||||||
var<uniform> U: Uniforms;
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position) position : vec4<f32>,
|
|
||||||
@location(0) uv : vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fragment(mesh: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
let u = clamp(mesh.uv.x, 0.0, 1.0);
|
|
||||||
return vec4<f32>(mesh.position.x / 1000.0, mesh.position.y / 1000.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub mode: Option<Mode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug, Default)]
|
||||||
|
pub enum Mode {
|
||||||
|
/// Run a headless server (no rendering)
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
#[cfg_attr(all(feature = "server", not(feature = "client")), default)]
|
||||||
|
Headless,
|
||||||
|
/// Run a server that renders
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Server,
|
||||||
|
/// Run the client application
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
#[cfg_attr(all(feature = "client", not(feature = "server")), default)]
|
||||||
|
Client,
|
||||||
|
/// Run both client and server in one process
|
||||||
|
#[cfg(all(feature = "client", feature = "server"))]
|
||||||
|
#[cfg_attr(all(feature = "client", feature = "server"), default)]
|
||||||
|
Host,
|
||||||
|
}
|
||||||
+55
-158
@@ -1,95 +1,75 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
avian::{CharacterControllerBundle, CharacterControllerPlugin},
|
avian::{CharacterControllerBundle, CharacterControllerPlugin},
|
||||||
shaders::BezierMaterial,
|
cli::Cli,
|
||||||
};
|
};
|
||||||
use avian2d::{
|
use avian2d::{
|
||||||
PhysicsPlugins,
|
PhysicsPlugins,
|
||||||
math::Vector,
|
math::Vector,
|
||||||
prelude::{Collider, Gravity, RigidBody},
|
prelude::{Collider, Gravity, RigidBody},
|
||||||
};
|
};
|
||||||
use bevy::{
|
use bevy::{camera::ScalingMode, color::palettes::css::GREEN, prelude::*};
|
||||||
camera::ScalingMode,
|
use clap::Parser;
|
||||||
color::palettes::css::{GREEN, RED, WHITE},
|
|
||||||
input_focus::InputFocus,
|
|
||||||
prelude::*,
|
|
||||||
sprite_render::Material2dPlugin,
|
|
||||||
};
|
|
||||||
use crate::shaders::track::{TrackMaterial, setup_track, update_track_time};
|
|
||||||
use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin};
|
|
||||||
|
|
||||||
pub mod avian;
|
pub mod avian;
|
||||||
pub mod shaders;
|
pub mod cli;
|
||||||
|
pub mod net;
|
||||||
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
|
|
||||||
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
|
|
||||||
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
let cli = match Cli::try_parse() {
|
||||||
.add_plugins((
|
Ok(cli) => cli,
|
||||||
DefaultPlugins,
|
Err(err) => {
|
||||||
// Add physics plugins and specify a units-per-meter scaling factor, 1 meter = 20 pixels.
|
err.print().expect("failed to print clap error");
|
||||||
// The unit allows the engine to tune its parameters for the scale of the world, improving stability.
|
std::process::exit(1);
|
||||||
PhysicsPlugins::default().with_length_unit(20.0),
|
}
|
||||||
Material2dPlugin::<BezierMaterial>::default(),
|
};
|
||||||
Material2dPlugin::<TrackMaterial>::default(),
|
|
||||||
CharacterControllerPlugin,
|
|
||||||
))
|
|
||||||
.add_plugins(EguiPlugin::default())
|
|
||||||
.add_plugins(WorldInspectorPlugin::new())
|
|
||||||
.init_resource::<InputFocus>()
|
|
||||||
// .add_systems(Startup, (setup, setup_ui))
|
|
||||||
.add_systems(Startup, (setup, setup_ui, setup_track))
|
|
||||||
.add_systems(Update, (debug_border, do_menu_interactions, move_bezier, update_track_time))
|
|
||||||
.insert_resource(Gravity(Vector::ZERO))
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_menu_interactions(
|
#[cfg(all(feature = "client", not(feature = "server")))]
|
||||||
mut input_focus: ResMut<InputFocus>,
|
let mode = cli.mode.unwrap_or(cli::Mode::Client);
|
||||||
mut interaction_query: Query<
|
#[cfg(all(feature = "server", not(feature = "client")))]
|
||||||
(
|
let mode = cli.mode.unwrap_or(cli::Mode::Server);
|
||||||
Entity,
|
#[cfg(all(feature = "client", feature = "server"))]
|
||||||
&Interaction,
|
let mode = cli.mode.unwrap_or(cli::Mode::Host);
|
||||||
&mut BackgroundColor,
|
#[cfg(all(not(feature = "client"), not(feature = "server")))]
|
||||||
&mut BorderColor,
|
panic!("You must enable either the client or server feature");
|
||||||
&mut Button,
|
|
||||||
&Children,
|
|
||||||
),
|
|
||||||
Changed<Interaction>,
|
|
||||||
>,
|
|
||||||
mut text_query: Query<&mut Text>,
|
|
||||||
) {
|
|
||||||
for (entity, interaction, mut color, mut border_color, mut button, children) in
|
|
||||||
&mut interaction_query
|
|
||||||
{
|
|
||||||
let mut text = text_query.get_mut(children[0]).unwrap();
|
|
||||||
|
|
||||||
match *interaction {
|
let mut app = App::new();
|
||||||
Interaction::Pressed => {
|
|
||||||
input_focus.set(entity);
|
|
||||||
**text = "Press".to_string();
|
|
||||||
*color = PRESSED_BUTTON.into();
|
|
||||||
*border_color = BorderColor::all(RED);
|
|
||||||
|
|
||||||
// The accessibility system's only update the button's state when the `Button` component is marked as changed.
|
match mode {
|
||||||
button.set_changed();
|
#[cfg(feature = "server")]
|
||||||
}
|
cli::Mode::Headless => {
|
||||||
Interaction::Hovered => {
|
app.add_plugins((MinimalPlugins, net::server::GameServerPlugin));
|
||||||
input_focus.set(entity);
|
}
|
||||||
**text = "Hover".to_string();
|
|
||||||
*color = HOVERED_BUTTON.into();
|
#[cfg(feature = "server")]
|
||||||
*border_color = BorderColor::all(Color::WHITE);
|
cli::Mode::Server => {
|
||||||
button.set_changed();
|
app.add_plugins((DefaultPlugins, net::server::GameServerPlugin));
|
||||||
}
|
}
|
||||||
Interaction::None => {
|
|
||||||
input_focus.clear();
|
#[cfg(feature = "client")]
|
||||||
**text = "Button".to_string();
|
cli::Mode::Client => {
|
||||||
*color = NORMAL_BUTTON.into();
|
app.add_plugins((DefaultPlugins, net::client::GameClientPlugin));
|
||||||
*border_color = BorderColor::all(Color::BLACK);
|
}
|
||||||
}
|
|
||||||
|
#[cfg(all(feature = "client", feature = "server"))]
|
||||||
|
cli::Mode::Host => {
|
||||||
|
app.add_plugins((
|
||||||
|
DefaultPlugins,
|
||||||
|
net::server::GameServerPlugin,
|
||||||
|
net::client::GameClientPlugin,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.add_plugins((
|
||||||
|
// Add physics plugins and specify a units-per-meter scaling factor, 1 meter = 20 pixels.
|
||||||
|
// The unit allows the engine to tune its parameters for the scale of the world, improving stability.
|
||||||
|
PhysicsPlugins::default().with_length_unit(20.0),
|
||||||
|
CharacterControllerPlugin,
|
||||||
|
))
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, debug_border)
|
||||||
|
.insert_resource(Gravity(Vector::ZERO))
|
||||||
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
@@ -120,89 +100,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_ui(
|
|
||||||
mut commands: Commands,
|
|
||||||
asset_server: Res<AssetServer>,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<BezierMaterial>>,
|
|
||||||
) {
|
|
||||||
commands.spawn((
|
|
||||||
Node {
|
|
||||||
width: percent(100),
|
|
||||||
height: percent(100),
|
|
||||||
align_items: AlignItems::Center,
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
children![(
|
|
||||||
Button,
|
|
||||||
Node {
|
|
||||||
width: px(150),
|
|
||||||
height: px(65),
|
|
||||||
border: UiRect::all(px(5)),
|
|
||||||
// horizontally center child text
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
// vertically center child text
|
|
||||||
align_items: AlignItems::Center,
|
|
||||||
border_radius: BorderRadius::MAX,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BorderColor::all(Color::WHITE),
|
|
||||||
BackgroundColor(Color::BLACK),
|
|
||||||
children![(
|
|
||||||
Text::new("Button"),
|
|
||||||
TextFont {
|
|
||||||
font: asset_server.load("FiraSans-Bold.ttf"),
|
|
||||||
font_size: 33.0,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
TextColor(Color::srgb(0.9, 0.9, 0.9)),
|
|
||||||
TextShadow::default(),
|
|
||||||
)]
|
|
||||||
)],
|
|
||||||
));
|
|
||||||
|
|
||||||
// Curve points (world-space)
|
|
||||||
let p0 = Vec2::new(-200.0, -100.0);
|
|
||||||
let p1 = Vec2::new(200.0, 100.0);
|
|
||||||
let c0 = p0 + Vec2::new(150.0, 0.0);
|
|
||||||
let c1 = p1 + Vec2::new(-150.0, 0.0);
|
|
||||||
|
|
||||||
// Bounding quad (must contain entire curve + width)
|
|
||||||
let center = (p0 + p1) * 0.5;
|
|
||||||
let size = Vec2::new(500.0, 300.0);
|
|
||||||
|
|
||||||
commands.spawn((
|
|
||||||
Mesh2d(meshes.add(Rectangle::new(size.x, size.y))),
|
|
||||||
MeshMaterial2d(materials.add(BezierMaterial {
|
|
||||||
p0,
|
|
||||||
c0,
|
|
||||||
c1,
|
|
||||||
p1,
|
|
||||||
width: 12.0,
|
|
||||||
color: WHITE.into(),
|
|
||||||
})),
|
|
||||||
Transform::from_translation(center.extend(0.0)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_bezier(mut materials: ResMut<Assets<BezierMaterial>>, windows: Query<&Window>) {
|
|
||||||
let Ok(window) = windows.single() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(mouse_pos) = window.cursor_position() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (_, mat) in materials.iter_mut() {
|
|
||||||
mat.p0 = mouse_pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pos: Vec2, in logical pixels
|
|
||||||
// (0, 0) is bottom-left of the window
|
|
||||||
// println!("Mouse window position: {:?}", mouse_pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_border(mut gizmos: Gizmos) {
|
fn debug_border(mut gizmos: Gizmos) {
|
||||||
gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::new(1920., 1080.), GREEN);
|
gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::new(1920., 1080.), GREEN);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct GameClientPlugin;
|
||||||
|
|
||||||
|
impl Plugin for GameClientPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
// app.add_systems(
|
||||||
|
// FixedPreUpdate,
|
||||||
|
// // Inputs have to be buffered in the WriteClientInputs set
|
||||||
|
// buffer_input.in_set(InputSystems::WriteClientInputs),
|
||||||
|
// );
|
||||||
|
// app.add_systems(FixedUpdate, player_movement);
|
||||||
|
|
||||||
|
// app.add_systems(Update, receive_message1);
|
||||||
|
// app.add_observer(handle_predicted_spawn);
|
||||||
|
// app.add_observer(handle_interpolated_spawn);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub mod client;
|
||||||
|
pub mod protocol;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub mod server;
|
||||||
|
pub mod shared;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ProtocolPlugin;
|
||||||
|
|
||||||
|
impl Plugin for ProtocolPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
// messages
|
||||||
|
app.register_message::<Message1>()
|
||||||
|
.add_direction(NetworkDirection::ServerToClient);
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
app.add_plugins(input::native::InputPlugin::<Inputs>::default());
|
||||||
|
// components
|
||||||
|
app.register_component::<PlayerId>();
|
||||||
|
|
||||||
|
app.register_component::<PlayerPosition>()
|
||||||
|
.add_prediction()
|
||||||
|
.add_linear_interpolation();
|
||||||
|
|
||||||
|
app.register_component::<PlayerColor>();
|
||||||
|
|
||||||
|
// channels
|
||||||
|
app.add_channel::<Channel1>(ChannelSettings {
|
||||||
|
mode: ChannelMode::OrderedReliable(ReliableSettings::default()),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.add_direction(NetworkDirection::ServerToClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct GameServerPlugin;
|
||||||
|
|
||||||
|
impl Plugin for GameServerPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
// // the physics/FixedUpdates systems that consume inputs should be run in this set.
|
||||||
|
// app.add_systems(FixedUpdate, movement);
|
||||||
|
// app.add_observer(handle_new_client);
|
||||||
|
// app.add_observer(handle_connected);
|
||||||
|
// app.add_systems(Update, send_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use bevy::render::render_resource::*;
|
|
||||||
use bevy::shader::ShaderRef;
|
|
||||||
use bevy::sprite_render::{AlphaMode2d, Material2d};
|
|
||||||
|
|
||||||
pub mod track;
|
|
||||||
|
|
||||||
// This is the struct that will be passed to your shader
|
|
||||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
|
||||||
pub struct BezierMaterial {
|
|
||||||
#[uniform(0)]
|
|
||||||
pub p0: Vec2,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub c0: Vec2,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub c1: Vec2,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub p1: Vec2,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub width: f32,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub color: LinearRgba,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Material2d trait is very configurable, but comes with sensible defaults for all methods.
|
|
||||||
/// You only need to implement functions for features that need non-default behavior. See the Material2d api docs for details!
|
|
||||||
impl Material2d for BezierMaterial {
|
|
||||||
fn fragment_shader() -> ShaderRef {
|
|
||||||
"shaders/bezier.wgsl".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alpha_mode(&self) -> AlphaMode2d {
|
|
||||||
AlphaMode2d::Blend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the struct that will be passed to your shader
|
|
||||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
|
||||||
pub struct FlowMaterial {
|
|
||||||
#[uniform(0)]
|
|
||||||
pub color_a: LinearRgba,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub color_b: LinearRgba,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub border_color: LinearRgba,
|
|
||||||
|
|
||||||
#[uniform(0)]
|
|
||||||
pub time: f32,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub speed: f32,
|
|
||||||
#[uniform(0)]
|
|
||||||
pub border_size: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Material2d trait is very configurable, but comes with sensible defaults for all methods.
|
|
||||||
/// You only need to implement functions for features that need non-default behavior. See the Material2d api docs for details!
|
|
||||||
impl Material2d for FlowMaterial {
|
|
||||||
fn fragment_shader() -> ShaderRef {
|
|
||||||
"shaders/flow.wgsl".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alpha_mode(&self) -> AlphaMode2d {
|
|
||||||
AlphaMode2d::Blend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use bevy::render::render_resource::*;
|
|
||||||
use bevy::shader::ShaderRef;
|
|
||||||
use bevy::sprite_render::{AlphaMode2d, Material2d};
|
|
||||||
|
|
||||||
/// The Uniform data passed to the track shader.
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy, ShaderType)]
|
|
||||||
pub struct TrackUniform {
|
|
||||||
/// The size of the canvas
|
|
||||||
pub size: Vec2,
|
|
||||||
|
|
||||||
/// The length of the first horizontal segment
|
|
||||||
pub len_start: f32,
|
|
||||||
/// The length of the last horizontal segment
|
|
||||||
pub len_end: f32,
|
|
||||||
|
|
||||||
/// The inner half thickness
|
|
||||||
pub thickness: f32,
|
|
||||||
/// The thickness of the border (added to the inner thickness for total thickness)
|
|
||||||
pub border: f32,
|
|
||||||
|
|
||||||
/// Current time
|
|
||||||
pub time: f32,
|
|
||||||
/// The period of the pulse effect (in seconds)
|
|
||||||
pub pulse_period: f32,
|
|
||||||
/// The speed at which the pulse travels along the path (in units per second)
|
|
||||||
pub pulse_speed: f32,
|
|
||||||
/// The width of the pulse effect (in units)
|
|
||||||
pub pulse_width: f32,
|
|
||||||
|
|
||||||
/// The color of the border
|
|
||||||
pub border_color: LinearRgba,
|
|
||||||
/// The color of the base of the track
|
|
||||||
pub color_base: LinearRgba,
|
|
||||||
/// The color of the pulsing
|
|
||||||
pub color_accent: LinearRgba,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(AsBindGroup, Asset, TypePath, Clone)]
|
|
||||||
pub struct TrackMaterial {
|
|
||||||
#[uniform(0)]
|
|
||||||
pub data: TrackUniform,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Material2d for TrackMaterial {
|
|
||||||
fn fragment_shader() -> ShaderRef {
|
|
||||||
"shaders/track.wgsl".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alpha_mode(&self) -> AlphaMode2d {
|
|
||||||
AlphaMode2d::Blend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup_track(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<TrackMaterial>>,
|
|
||||||
// windows: Query<&Window>,
|
|
||||||
) {
|
|
||||||
// let window = windows.single().unwrap();
|
|
||||||
|
|
||||||
let width = 1920.0 / 2.0;
|
|
||||||
let height = 1080.0 / 2.0;
|
|
||||||
// let width = 1920.0;
|
|
||||||
// let height = 1080.0;
|
|
||||||
|
|
||||||
let material = materials.add(TrackMaterial {
|
|
||||||
data: TrackUniform {
|
|
||||||
size: Vec2::new(width, height),
|
|
||||||
|
|
||||||
len_start: 400.0,
|
|
||||||
len_end: 300.0,
|
|
||||||
|
|
||||||
thickness: 8.0,
|
|
||||||
border: 2.0,
|
|
||||||
time: 0.0,
|
|
||||||
pulse_period: 2.0,
|
|
||||||
pulse_speed: 0.5,
|
|
||||||
pulse_width: 0.5,
|
|
||||||
|
|
||||||
border_color: LinearRgba::BLACK,
|
|
||||||
color_base: LinearRgba::rgb(0.0, 0.9, 1.0),
|
|
||||||
color_accent: LinearRgba::rgb(0.8, 0.0, 0.0),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// commands.spawn(Camera2d);
|
|
||||||
// println!("Window: {}, {}", window.width(), window.height());
|
|
||||||
|
|
||||||
commands.spawn((
|
|
||||||
Mesh2d(meshes.add(Rectangle::new(width * 2.0, height * 2.0))),
|
|
||||||
MeshMaterial2d(material),
|
|
||||||
Transform::from_translation(Vec3::new(
|
|
||||||
// window.width() * 0.5,
|
|
||||||
// window.height() * 0.5,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
)),
|
|
||||||
Name::new("Snake")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_track_time(
|
|
||||||
time: Res<Time>,
|
|
||||||
mut materials: ResMut<Assets<TrackMaterial>>,
|
|
||||||
) {
|
|
||||||
for (_, mat) in materials.iter_mut() {
|
|
||||||
mat.data.time = time.elapsed_secs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user