use std::hash::Hash;
use std::marker::PhantomData;
use std::sync::Once;
use bevy::app::{App, Plugin};
use bevy::asset::{load_internal_asset, LoadState};
use bevy::core_pipeline::fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE;
use bevy::ecs::world::Command;
use bevy::image::{ImageAddressMode, ImageSampler, ImageSamplerDescriptor};
use bevy::prelude::*;
use bevy::render::mesh::MeshVertexBufferLayoutRef;
use bevy::render::render_resource::{
AsBindGroup, PrimitiveState, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
};
use bevy::render::view::NoFrustumCulling;
use bevy::sprite::{AlphaMode2d, Material2d, Material2dKey, Material2dPlugin};
use bevy::window::{PrimaryWindow, WindowResized};
pub const TILED_BG_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(429593476423978);
pub const BGLIB_HANDLE: Handle<Shader> = Handle::weak_from_u128(429593476423988);
pub const BG_MESH_HANDLE: Handle<Mesh> = Handle::weak_from_u128(12316584166263728426);
pub static BEVY_TILING_PLUGIN_SHADERS_LOADED: Once = Once::new();
fn load_plugin_shadercode(app: &mut App) {
load_internal_asset!(
app,
TILED_BG_SHADER_HANDLE,
"shaders/background.wgsl",
Shader::from_wgsl
);
load_internal_asset!(app, BGLIB_HANDLE, "shaders/bglib.wgsl", Shader::from_wgsl);
let mut meshes = app.world_mut().resource_mut::<Assets<Mesh>>();
meshes.insert(&BG_MESH_HANDLE, Mesh::from(Rectangle::new(1., 1.)));
}
#[derive(Default)]
pub struct TilingBackgroundPlugin<T: AsBindGroup + Send + Sync + Clone + Asset + Sized + 'static> {
_phantom: PhantomData<T>,
}
impl<T: Material2d + AsBindGroup + Clone + ScrollingBackground> Plugin for TilingBackgroundPlugin<T>
where
T::Data: Clone + Eq + Send + Sync + Clone + Sized + Hash,
{
fn build(&self, app: &mut App) {
BEVY_TILING_PLUGIN_SHADERS_LOADED.call_once(|| {
info!("Loading bevy_tiling_background shaders");
load_plugin_shadercode(app);
});
app.add_plugins(Material2dPlugin::<T>::default())
.register_type::<BackgroundMovementScale>()
.insert_resource(UpdateSamplerRepeating::default())
.add_systems(PostUpdate, Self::on_window_resize)
.add_systems(Update, Self::on_background_added)
.add_systems(Update, Self::queue_update_sampler)
.add_systems(Update, Self::update_movement_scale_system)
.add_systems(Update, update_sampler_on_loaded_system);
}
}
impl<T: Material2d + AsBindGroup + Clone + ScrollingBackground> TilingBackgroundPlugin<T>
where
<T as AsBindGroup>::Data: Clone + Eq + Send + Sync + Clone + Sized + Hash,
{
pub fn new() -> Self {
TilingBackgroundPlugin::<T> {
_phantom: PhantomData {},
}
}
pub fn on_window_resize(
mut events: EventReader<WindowResized>,
mut backgrounds: Query<&mut Transform, With<MeshMaterial2d<T>>>,
) {
events.read().for_each(|ev| {
for mut transform in backgrounds.iter_mut() {
transform.scale.x = ev.width;
transform.scale.y = ev.height;
}
});
}
pub fn on_background_added(
windows: Query<&Window, With<PrimaryWindow>>,
mut backgrounds: Query<&mut Transform, Added<MeshMaterial2d<T>>>,
) {
if let Ok(window) = windows.get_single() {
for mut transform in backgrounds.iter_mut() {
transform.scale.x = window.width();
transform.scale.y = window.height();
}
};
}
fn queue_update_sampler(
query: Query<&Sprite, Added<MeshMaterial2d<T>>>,
mut update_samplers: ResMut<UpdateSamplerRepeating>,
) {
for sprite in query.iter() {
update_samplers.0.push(sprite.image.clone());
}
}
pub fn update_movement_scale_system(
mut query: Query<
(&mut MeshMaterial2d<T>, &BackgroundMovementScale),
Changed<BackgroundMovementScale>,
>,
mut background_materials: ResMut<Assets<T>>,
) {
for (bg_material_handle, scale) in query.iter_mut() {
if let Some(background_material) = background_materials.get_mut(&*bg_material_handle) {
background_material.set_movement(scale.scale);
}
}
}
}
pub trait ScrollingBackground {
fn set_movement(&mut self, movement: f32);
}
#[derive(AsBindGroup, Debug, Clone, Asset, TypePath, Default)]
pub struct BackgroundMaterial {
#[uniform(0)]
pub movement_scale: f32,
#[uniform(0)]
pub _wasm_padding: Vec3,
#[texture(1)]
#[sampler(2)]
pub texture: Handle<Image>,
}
impl Material2d for BackgroundMaterial {
fn vertex_shader() -> ShaderRef {
FULLSCREEN_SHADER_HANDLE.into()
}
fn fragment_shader() -> ShaderRef {
TILED_BG_SHADER_HANDLE.into()
}
fn alpha_mode(&self) -> AlphaMode2d {
AlphaMode2d::Blend
}
fn specialize(
descriptor: &mut RenderPipelineDescriptor,
_: &MeshVertexBufferLayoutRef,
_: Material2dKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
descriptor.primitive = PrimitiveState::default();
descriptor.vertex.entry_point = "fullscreen_vertex_shader".into();
Ok(())
}
}
impl ScrollingBackground for BackgroundMaterial {
fn set_movement(&mut self, movement: f32) {
self.movement_scale = movement;
}
}
impl ScrollingBackground for &mut BackgroundMaterial {
fn set_movement(&mut self, movement: f32) {
self.movement_scale = movement;
}
}
#[derive(Resource, Default)]
struct UpdateSamplerRepeating(Vec<Handle<Image>>);
fn update_sampler_on_loaded_system(
asset_server: Res<AssetServer>,
mut update_sampler: ResMut<UpdateSamplerRepeating>,
mut images: ResMut<Assets<Image>>,
) {
let handles = update_sampler
.0
.iter()
.cloned()
.enumerate()
.rev()
.collect::<Vec<_>>();
for (index, handle) in handles {
match asset_server.get_load_state(&handle) {
Some(LoadState::Failed(_)) => {
update_sampler.0.remove(index);
}
Some(LoadState::Loaded) => {
let bg_texture = images
.get_mut(&handle)
.expect("the image should be loaded at this point");
if let ImageSampler::Descriptor(descriptor) = &mut bg_texture.sampler {
descriptor.address_mode_u = ImageAddressMode::Repeat;
descriptor.address_mode_v = ImageAddressMode::Repeat;
descriptor.address_mode_w = ImageAddressMode::Repeat;
} else {
bg_texture.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
address_mode_w: ImageAddressMode::Repeat,
..default()
});
}
update_sampler.0.remove(index);
debug!("Updated image sampler to be repeating");
}
_ => {
}
}
}
}
#[derive(Component, Reflect)]
#[reflect(Component, Default)]
pub struct BackgroundMovementScale {
pub scale: f32,
}
impl Default for BackgroundMovementScale {
fn default() -> Self {
Self { scale: 1.0 }
}
}
#[derive(Bundle)]
pub struct CustomBackgroundImageBundle<T: Material2d> {
pub material: MeshMaterial2d<T>,
pub mesh: Mesh2d,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub visibility: Visibility,
pub view_visibility: ViewVisibility,
pub inherited_visibility: InheritedVisibility,
pub movement_scale: BackgroundMovementScale,
pub no_frustum_culling: NoFrustumCulling,
}
impl<T: Material2d + ScrollingBackground> CustomBackgroundImageBundle<T> {
pub fn with_material(material: T, materials: &mut Assets<T>) -> Self {
Self {
material: materials.add(material).into(),
mesh: BG_MESH_HANDLE.into(),
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
view_visibility: Default::default(),
inherited_visibility: Default::default(),
movement_scale: Default::default(),
no_frustum_culling: Default::default(),
}
}
}
#[derive(Bundle)]
pub struct BackgroundImageBundle {
pub material: MeshMaterial2d<BackgroundMaterial>,
pub mesh: Mesh2d,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub visibility: Visibility,
pub view_visibility: ViewVisibility,
pub inherited_visibility: InheritedVisibility,
pub movement_scale: BackgroundMovementScale,
pub no_frustum_culling: NoFrustumCulling,
}
impl BackgroundImageBundle {
pub fn from_image(
image: Handle<Image>,
background_materials: &mut Assets<BackgroundMaterial>,
) -> Self {
Self {
material: background_materials
.add(BackgroundMaterial {
texture: image,
movement_scale: 1.0,
_wasm_padding: Vec3::ZERO,
})
.into(),
mesh: BG_MESH_HANDLE.into(),
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
view_visibility: Default::default(),
inherited_visibility: Default::default(),
movement_scale: Default::default(),
no_frustum_culling: Default::default(),
}
}
pub fn with_movement_scale(mut self, scale: f32) -> Self {
self.movement_scale.scale = scale;
self
}
pub fn at_z_layer(mut self, z: f32) -> Self {
self.transform.translation.z = z;
self
}
}
struct SetImageRepeatingCommand {
image: Handle<Image>,
}
impl Command for SetImageRepeatingCommand {
fn apply(self, world: &mut World) {
let mut samplers = world.resource_mut::<UpdateSamplerRepeating>();
samplers.0.push(self.image);
}
}
pub trait SetImageRepeatingExt {
fn set_image_repeating(&mut self, image: Handle<Image>);
}
impl SetImageRepeatingExt for Commands<'_, '_> {
fn set_image_repeating(&mut self, image: Handle<Image>) {
self.queue(SetImageRepeatingCommand { image })
}
}