Skip to content

Commit b7f0ee5

Browse files
committed
added background shader
1 parent aa5e87f commit b7f0ee5

File tree

4 files changed

+224
-0
lines changed

4 files changed

+224
-0
lines changed

assets/shaders/ground.wgsl

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#import bevy_sprite::mesh2d_vertex_output::VertexOutput
2+
3+
struct GroundShader {
4+
camera_x: f32,
5+
camera_y: f32,
6+
random: f32,
7+
time: f32,
8+
};
9+
10+
@group(2) @binding(100)
11+
var<uniform> input: GroundShader;
12+
13+
const ZOOM:f32 = 180.0;
14+
const USE_BEAT_INPUT: bool = false;
15+
const ANIMATE_SHADER: bool = false;
16+
const GRID_LINE_COLOR: vec4f = vec4f(0.0);
17+
const SPACE_COLOR_ALPHA: f32 = 0.3;
18+
const CAMERA_OFFSET: f32 = 0.001953 / 2.0; // This number works well for tiling but I haven't figured out why yet, DON'T CHANGE IT
19+
const GRID_RATIO:f32 = 10.;
20+
21+
const CONTRAST_FACTOR: f32 = 1.5;
22+
const SATURATION_FACTOR: f32 = 0.5;
23+
const BRIGHTNESS: f32 = 0.5;
24+
const BACKGROUND_COLOR: vec3f = vec3<f32>(0.2, 0.0, 0.4);
25+
const COLOR_1: vec3f = vec3<f32>(0.0, 0.5, 0.0); // GREEN
26+
const COLOR_2: vec3f = vec3<f32>(0.0, 0.5, 1.0); // BLUE
27+
const COLOR_3: vec3f = vec3<f32>(1.0, 1.0, 0.0); // YELLOW
28+
const COLOR_4: vec3f = vec3<f32>(1.0, 0.0, 0.0); // RED
29+
30+
@fragment
31+
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
32+
let rand = max(select(1.0, input.random, USE_BEAT_INPUT), 0.0001);
33+
var camera_x = input.camera_x * CAMERA_OFFSET;
34+
var camera_y = input.camera_y * -CAMERA_OFFSET;
35+
36+
let uv = (in.uv.xy + vec2(camera_x, camera_y)) * ZOOM;
37+
38+
if is_line(uv) {
39+
return GRID_LINE_COLOR;
40+
}
41+
42+
let square = floor(uv * GRID_RATIO * 0.1);
43+
var tile_color = get_color_from_vec2f(square / rand);
44+
45+
let average_color = average_rgb(tile_color, BACKGROUND_COLOR);
46+
let contrasted_color = blend_towards(tile_color, average_color, CONTRAST_FACTOR);
47+
let saturated_color = blend_towards(contrasted_color, rgb_to_grayscale(contrasted_color), SATURATION_FACTOR);
48+
let brightened_color = blend_towards(saturated_color, vec3f(0.0), BRIGHTNESS);
49+
50+
let final_color = brightened_color * select(1.0, cos(input.time), ANIMATE_SHADER);
51+
52+
return vec4<f32>(final_color, SPACE_COLOR_ALPHA);
53+
}
54+
55+
fn average_rgb(color1: vec3<f32>, color2: vec3<f32>) -> vec3<f32> {
56+
return (color1 + color2) / 2.0;
57+
}
58+
59+
fn blend_towards(color: vec3<f32>, toward_color: vec3<f32>, factor: f32) -> vec3<f32> {
60+
return mix(color, toward_color, factor);
61+
}
62+
63+
fn rgb_to_grayscale(color: vec3<f32>) -> vec3<f32> {
64+
let grayscale_value = dot(color, vec3<f32>(0.299, 0.587, 0.114));
65+
return vec3<f32>(grayscale_value, grayscale_value, grayscale_value);
66+
}
67+
68+
fn get_color_from_vec2f(v: vec2<f32>) -> vec3<f32> {
69+
let index = hash_vec2f(v);
70+
return get_color(index);
71+
}
72+
73+
fn get_color(index: u32) -> vec3<f32> {
74+
switch (index) {
75+
case 1u: { return COLOR_1; }
76+
case 2u: { return COLOR_2; }
77+
case 3u: { return COLOR_3; }
78+
case 4u: { return COLOR_4; }
79+
default: { return BACKGROUND_COLOR; }
80+
}
81+
}
82+
83+
fn hash_vec2f(v: vec2<f32>) -> u32 {
84+
let pattern_size = 1000.0;
85+
return u32(sin(cos(v.x) * tan(v.y)) * pattern_size) % 10;
86+
}
87+
88+
fn is_line(uv: vec2<f32>)-> bool {
89+
let i = step(fract(uv), vec2(1.0/GRID_RATIO));
90+
return ((1.0-i.x) * (1.0-i.y)) < 0.5;
91+
}

src/game.rs

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod audio;
55
pub mod card;
66
pub mod cleanup;
77
pub mod combat;
8+
pub mod ground;
89
pub mod spotlight;
910
pub mod sprite;
1011
pub mod wave;
@@ -28,6 +29,7 @@ pub(super) fn plugin(app: &mut App) {
2829
card::plugin,
2930
cleanup::plugin,
3031
combat::plugin,
32+
ground::plugin,
3133
spotlight::plugin,
3234
sprite::plugin,
3335
wave::plugin,
@@ -41,6 +43,7 @@ pub struct GameRoot {
4143
pub enemies: Entity,
4244
pub projectiles: Entity,
4345
pub vfx: Entity,
46+
pub background: Entity,
4447
}
4548

4649
impl Configure for GameRoot {
@@ -57,12 +60,14 @@ impl FromWorld for GameRoot {
5760
let enemies = world.spawn_with(root("Enemies")).id();
5861
let projectiles = world.spawn_with(root("Projectiles")).id();
5962
let vfx = world.spawn_with(root("Vfx")).id();
63+
let background = world.spawn_with(root("Background")).id();
6064

6165
Self {
6266
players,
6367
enemies,
6468
projectiles,
6569
vfx,
70+
background,
6671
}
6772
}
6873
}
@@ -72,6 +77,7 @@ fn clear_game_root(mut commands: Commands, game_root: Res<GameRoot>) {
7277
commands.entity(game_root.enemies).despawn_descendants();
7378
commands.entity(game_root.projectiles).despawn_descendants();
7479
commands.entity(game_root.vfx).despawn_descendants();
80+
commands.entity(game_root.background).despawn_descendants();
7581
}
7682

7783
fn root(name: impl Into<Cow<'static, str>>) -> impl EntityCommand<World> {

src/game/ground.rs

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use bevy::prelude::*;
2+
use bevy::reflect::TypePath;
3+
use bevy::render::render_resource::AsBindGroup;
4+
use bevy::render::render_resource::ShaderRef;
5+
use bevy::render::render_resource::ShaderType;
6+
use bevy::sprite::Material2d;
7+
use bevy::sprite::Material2dPlugin;
8+
use bevy::sprite::MaterialMesh2dBundle;
9+
use pyri_state::prelude::*;
10+
11+
use crate::core::UpdateSet;
12+
use crate::game::audio::music::on_full_beat;
13+
use crate::screen::Screen;
14+
use crate::util::prelude::*;
15+
16+
pub(super) fn plugin(app: &mut App) {
17+
app.configure::<Ground>();
18+
}
19+
20+
const GROUND_Z_INDEX: f32 = -10.0;
21+
const GROUND_MESH_SIZE: f32 = 1024.0;
22+
const GROUND_SNAP_INTERVAL: f32 = GROUND_MESH_SIZE / 4.0;
23+
const GROUND_SNAP: Vec2 = Vec2::splat(GROUND_SNAP_INTERVAL);
24+
25+
#[derive(Resource)]
26+
pub struct Ground {
27+
material: Handle<GroundMaterial>,
28+
mesh: Handle<Mesh>,
29+
}
30+
31+
impl Configure for Ground {
32+
fn configure(app: &mut App) {
33+
app.add_plugins(Material2dPlugin::<GroundMaterial>::default());
34+
app.init_resource::<Self>();
35+
36+
app.add_systems(
37+
Update,
38+
Screen::Playing.on_update((
39+
update_background,
40+
update_background_beat
41+
.in_set(UpdateSet::Update)
42+
.run_if(on_full_beat(8)),
43+
)),
44+
);
45+
}
46+
}
47+
48+
impl FromWorld for Ground {
49+
fn from_world(world: &mut World) -> Self {
50+
let material = world
51+
.resource_mut::<Assets<GroundMaterial>>()
52+
.add(GroundMaterial::default());
53+
let mesh = world
54+
.resource_mut::<Assets<Mesh>>()
55+
.add(Rectangle::default());
56+
57+
Self { material, mesh }
58+
}
59+
}
60+
61+
#[derive(Default, AsBindGroup, Asset, TypePath, Debug, Clone)]
62+
pub struct GroundMaterial {
63+
#[uniform(100)]
64+
uniforms: Uniforms,
65+
}
66+
67+
#[derive(ShaderType, Default, Clone, Debug)]
68+
struct Uniforms {
69+
pub camera_x: f32,
70+
pub camera_y: f32,
71+
pub random: f32,
72+
pub time: f32,
73+
}
74+
75+
impl Material2d for GroundMaterial {
76+
fn fragment_shader() -> ShaderRef {
77+
"shaders/ground.wgsl".into()
78+
}
79+
}
80+
81+
pub fn ground(mut entity: EntityWorldMut) {
82+
let ground = r!(entity.world().get_resource::<Ground>());
83+
let material = ground.material.clone();
84+
let mesh = ground.mesh.clone();
85+
86+
entity.insert((
87+
Name::new("Background"),
88+
MaterialMesh2dBundle {
89+
mesh: mesh.into(),
90+
transform: Transform::from_translation(Vec2::ZERO.extend(GROUND_Z_INDEX))
91+
.with_scale(Vec3::splat(GROUND_MESH_SIZE)),
92+
material,
93+
..default()
94+
},
95+
));
96+
}
97+
98+
fn update_background(
99+
mut ground_material: ResMut<Assets<GroundMaterial>>,
100+
camera_query: Query<&Transform, (With<IsDefaultUiCamera>, Without<Handle<GroundMaterial>>)>,
101+
mut ground_query: Query<&mut Transform, With<Handle<GroundMaterial>>>,
102+
time: Res<Time>,
103+
) {
104+
for (_, material) in ground_material.iter_mut() {
105+
for mut ground_transform in &mut ground_query {
106+
for camera_transform in &camera_query {
107+
let translation = ((camera_transform.translation.truncate() / GROUND_SNAP).floor()
108+
* GROUND_SNAP)
109+
.extend(GROUND_Z_INDEX);
110+
ground_transform.translation = translation;
111+
material.uniforms.camera_x = translation.x;
112+
material.uniforms.camera_y = translation.y;
113+
material.uniforms.time = time.elapsed_seconds();
114+
}
115+
}
116+
}
117+
}
118+
119+
fn update_background_beat(mut ground_material: ResMut<Assets<GroundMaterial>>) {
120+
for (_, material) in ground_material.iter_mut() {
121+
material.uniforms.random = rand::random();
122+
}
123+
}

src/screen/playing.rs

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::core::pause::Pause;
1414
use crate::game::actor::player::player;
1515
use crate::game::audio::music::start_music;
1616
use crate::game::audio::music::stop_music;
17+
use crate::game::ground::ground;
1718
use crate::game::spotlight::spotlight_lamp_spawner;
1819
use crate::game::wave::wave;
1920
use crate::game::GameRoot;
@@ -55,6 +56,9 @@ fn enter_playing(mut commands: Commands, game_root: Res<GameRoot>, ui_root: Res<
5556
commands
5657
.spawn_with(playing_hud(player))
5758
.set_parent(ui_root.body);
59+
60+
// Spawn Background.
61+
commands.spawn_with(ground).set_parent(game_root.background);
5862
}
5963

6064
#[derive(AssetCollection, Resource, Reflect, Default)]

0 commit comments

Comments
 (0)