Cleanup my asset stupidity...?

This commit is contained in:
Daniel Flanagan 2024-07-31 15:37:11 -05:00
parent 7d2e46c263
commit aaddc46c65
10 changed files with 259 additions and 320 deletions

View file

@ -1,72 +0,0 @@
use bevy::prelude::*;
#[derive(Resource, Debug, Default)]
pub struct AssetLoader {
pub layouts: Layouts,
pub images: Images,
pub sounds: Sounds,
pub fonts: Fonts,
}
#[derive(Resource, Debug, Default)]
pub struct Layouts {
pub player: Handle<TextureAtlasLayout>,
pub statue: Handle<TextureAtlasLayout>,
}
#[derive(Resource, Debug, Default)]
pub struct Images {
pub player: Handle<Image>,
pub statue: Handle<Image>,
}
#[derive(Resource, Debug, Default)]
pub struct Sounds {
pub meow: Handle<AudioSource>,
}
#[derive(Resource, Debug, Default)]
pub struct Fonts {
pub iosevkalytemin: Handle<Font>,
}
pub fn startup(
mut assets: ResMut<AssetLoader>,
asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
) {
let props = asset_server.load("img/Props.png");
let player = asset_server.load("img/Player.png");
let player_atlas = TextureAtlasLayout::from_grid(
UVec2::new(32, 64),
3,
1,
Some(UVec2 { x: 0, y: 0 }),
Some(UVec2 { x: 0, y: 0 }),
);
let statue_atlas =
TextureAtlasLayout::from_grid(UVec2::new(40, 74), 1, 1, None, Some(UVec2::new(443, 20)));
let player_atlas_handle = texture_atlases.add(player_atlas);
let statue_atlas_handle = texture_atlases.add(statue_atlas);
*assets = AssetLoader {
images: Images {
player,
statue: props.clone(),
},
layouts: Layouts {
player: player_atlas_handle,
statue: statue_atlas_handle,
},
sounds: Sounds {
meow: asset_server.load("sfx/meow.wav"),
},
fonts: Fonts {
iosevkalytemin: asset_server.load("font/iosevka-lyteterm-regular.subset.ttf"),
},
};
}

View file

@ -2,8 +2,8 @@ use bevy::prelude::*;
use bevy::render::camera::Camera; use bevy::render::camera::Camera;
pub fn startup(mut commands: Commands) { pub fn startup(mut commands: Commands) {
let bundle = (Camera2dBundle::default(), IsDefaultUiCamera); let mut bundle = (Camera2dBundle::default(), IsDefaultUiCamera);
// bundle.0.projection.scale = 1; bundle.0.projection.scale = 1.;
commands.spawn(bundle); commands.spawn(bundle);
} }

4
src/game.rs Normal file
View file

@ -0,0 +1,4 @@
use crate::prelude::*;
#[derive(Component, Default)]
pub struct Gameobject;

53
src/input.rs Normal file
View file

@ -0,0 +1,53 @@
use bevy::prelude::*;
#[derive(Resource, Default, Debug, Clone, PartialEq)]
pub struct Input {
pub movement: Vec2,
pub angle: f32,
pub action: bool,
pub cancel: bool,
}
pub fn process_input(
mut input: ResMut<Input>,
keys: Res<ButtonInput<KeyCode>>,
// mouse: Res<ButtonInput<MouseButton>>,
) {
input.movement.x = 0.;
input.movement.y = 0.;
for key in keys.get_pressed() {
match key {
KeyCode::KeyA | KeyCode::ArrowLeft => {
input.movement -= Vec2::X;
}
KeyCode::KeyD | KeyCode::ArrowRight => {
input.movement += Vec2::X;
}
KeyCode::KeyW | KeyCode::ArrowUp => {
input.movement += Vec2::Y;
}
KeyCode::KeyS | KeyCode::ArrowDown => {
input.movement -= Vec2::Y;
}
_ => {}
}
}
input.movement = input.movement.normalize_or_zero();
// input.target // not set using keyboard, see mouse
input.action = input.action || keys.any_pressed([KeyCode::Space, KeyCode::Enter]);
input.cancel = input.cancel || keys.just_pressed(KeyCode::Escape);
info!("Input: {:#?}", input);
}
// TODO: controller input: https://bevy-cheatbook.github.io/input/gamepad.html
// Iterate controllers on startup and assign to players/units?
// What logic for handling controller connection/disconnection?
// Will we have splitscreen?
// Once a controller is assigned to a player, we can handle its input
// pub fn controller_input(
// axes: Res<Axis<GamepadAxis>>,
// buttons: Res<ButtonInput<GamepadButton>>,
// my_gamepad: Option<Res<MyGamepad>>,
// ) {
// }

View file

@ -1,15 +1,20 @@
use bevy::audio::{AudioPlugin, SpatialScale};
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::prelude::{default, *};
use bevy::window::{PrimaryWindow, WindowMode};
mod assets;
mod camera; mod camera;
mod game;
mod input;
mod main_menu; mod main_menu;
mod movement; mod movement;
mod player; mod player;
mod prelude;
mod statue; mod statue;
use bevy::asset::load_internal_binary_asset;
use bevy::audio::{AudioPlugin, SpatialScale};
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::window::{PrimaryWindow, WindowMode};
use input::Input;
use prelude::*;
use statue::SpawnStatueEvent;
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)] #[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash)]
pub enum Game { pub enum Game {
#[default] #[default]
@ -39,7 +44,8 @@ fn main() -> AppExit {
.set(WindowPlugin { .set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
title: "Kodo Tag".into(), title: "Kodo Tag".into(),
mode: bevy::window::WindowMode::BorderlessFullscreen, // mode: bevy::window::WindowMode::BorderlessFullscreen,
mode: bevy::window::WindowMode::Windowed,
// resolution: (640. * 2., 360. * 2.).into(), // resolution: (640. * 2., 360. * 2.).into(),
..default() ..default()
}), }),
@ -52,38 +58,54 @@ fn main() -> AppExit {
}) })
.set(ImagePlugin::default_nearest()), .set(ImagePlugin::default_nearest()),
FrameTimeDiagnosticsPlugin, FrameTimeDiagnosticsPlugin,
)) ));
.init_resource::<assets::AssetLoader>()
load_internal_binary_asset!(
app,
TextStyle::default().font,
"../assets/font/iosevka-lyteterm-regular.subset.ttf",
|bytes: &[u8], _path: String| { Font::try_from_bytes(bytes.to_vec()).unwrap() }
);
app.init_resource::<input::Input>()
.init_state::<View>() .init_state::<View>()
.init_state::<Game>() .init_state::<Game>();
.add_systems(OnEnter(View::MainMenu), main_menu::startup)
app.configure_sets(
Update,
(
MainMenuSet.run_if(in_state(View::MainMenu)),
InGameSet
.run_if(in_state(View::InGame))
.after(input::process_input),
),
);
app.add_event::<SpawnStatueEvent>();
app.add_systems(OnEnter(View::MainMenu), main_menu::startup)
.add_systems(OnExit(View::MainMenu), main_menu::exit) .add_systems(OnExit(View::MainMenu), main_menu::exit)
.add_systems(OnEnter(View::InGame), (player::startup, statue::startup)) .add_systems(OnEnter(View::InGame), (player::startup, statue::startup))
.add_systems(OnExit(View::InGame), (player::exit, statue::exit)) .add_systems(OnExit(View::InGame), (player::exit, statue::exit))
.add_systems( .add_systems(OnEnter(View::LoadingScreen), (startup, camera::startup))
OnEnter(View::LoadingScreen),
(startup, assets::startup, camera::startup),
)
.add_systems( .add_systems(
Update, Update,
( (
update,
finish_setup.run_if(in_state(View::LoadingScreen)), finish_setup.run_if(in_state(View::LoadingScreen)),
input::process_input,
(update,).after(input::process_input),
( (
player::sprite_select, player::control,
player::controls, statue::spawn_statue,
movement::update_velocity_by_heading, movement::update_velocity_by_heading,
movement::resolve_velocity.after(movement::update_velocity_by_heading), movement::resolve_velocity.after(movement::update_velocity_by_heading),
movement::ysort.after(movement::update_velocity_by_heading), movement::ysort.after(movement::update_velocity_by_heading),
camera::update camera::update
.after(player::controls) .after(player::control)
.after(movement::resolve_velocity), .after(movement::resolve_velocity),
) )
.in_set(InGameSet) .in_set(InGameSet),
.run_if(in_state(View::InGame)), (main_menu::update).in_set(MainMenuSet),
(main_menu::update)
.in_set(MainMenuSet)
.run_if(in_state(View::MainMenu)),
), ),
) )
.insert_resource(ClearColor(Color::srgb(0.3, 0.1, 0.5))); .insert_resource(ClearColor(Color::srgb(0.3, 0.1, 0.5)));
@ -97,14 +119,6 @@ fn main() -> AppExit {
app.run() app.run()
} }
fn toggle_fullscreen(mut window: Mut<Window>) {
if window.mode == WindowMode::Windowed {
window.mode = WindowMode::BorderlessFullscreen
} else {
window.mode = WindowMode::Windowed
}
}
#[derive(Component)] #[derive(Component)]
struct FpsText; struct FpsText;
@ -112,22 +126,21 @@ fn finish_setup(mut app_state: ResMut<NextState<View>>) {
app_state.set(View::MainMenu); app_state.set(View::MainMenu);
} }
fn startup(mut commands: Commands, assets: Res<assets::AssetLoader>) { fn startup(mut commands: Commands, mut ui_scale: ResMut<UiScale>) {
commands.spawn(( commands.spawn((
FpsText, FpsText,
TextBundle::from_sections([ TextBundle::from_sections([
TextSection::new( TextSection::new(
"FPS: ", "FPS: ",
TextStyle { TextStyle {
font: assets.fonts.iosevkalytemin.clone(),
font_size: 60., font_size: 60.,
..default() ..default()
}, },
), ),
TextSection::from_style(TextStyle { TextSection::from_style(TextStyle {
font: assets.fonts.iosevkalytemin.clone(),
font_size: 60., font_size: 60.,
color: Color::hsla(0.5, 0.5, 0.5, 0.5), color: Color::hsla(0.5, 0.5, 0.5, 0.5),
..default()
}), }),
]) ])
.with_style(Style { .with_style(Style {
@ -137,19 +150,49 @@ fn startup(mut commands: Commands, assets: Res<assets::AssetLoader>) {
..default() ..default()
}), }),
)); ));
ui_scale.0 = 1.;
} }
fn update( fn update(
view: Res<State<View>>,
input: Res<Input>,
mut next_state: ResMut<NextState<crate::View>>,
diagnostics: Res<DiagnosticsStore>, diagnostics: Res<DiagnosticsStore>,
mut query: Query<&mut Text, With<FpsText>>, mut query: Query<&mut Text, With<FpsText>>,
keyboard: Res<ButtonInput<KeyCode>>,
mut window: Query<&mut Window, With<PrimaryWindow>>, mut window: Query<&mut Window, With<PrimaryWindow>>,
keys: Res<ButtonInput<KeyCode>>,
mut app_exit_events: ResMut<Events<bevy::app::AppExit>>,
) { ) {
if keyboard.just_pressed(KeyCode::Enter) if input.cancel {
&& (keyboard.pressed(KeyCode::AltLeft) || keyboard.pressed(KeyCode::AltRight)) match view.get() {
View::InGame => {
next_state.set(crate::View::MainMenu);
}
View::MainMenu => {
app_exit_events.send(AppExit::Success);
}
View::LoadingScreen => {}
}
} else if input.action {
match view.get() {
View::InGame => {}
View::MainMenu => {
next_state.set(crate::View::InGame);
}
View::LoadingScreen => {}
}
}
if keys.just_pressed(KeyCode::Enter)
&& (keys.pressed(KeyCode::AltLeft) || keys.pressed(KeyCode::AltRight))
{ {
if let Ok(window) = window.get_single_mut() { if let Ok(window) = window.get_single_mut() {
toggle_fullscreen(window) let mut window = window;
if window.mode == WindowMode::Windowed {
window.mode = WindowMode::BorderlessFullscreen
} else {
window.mode = WindowMode::Windowed
}
} }
} }
for mut text in &mut query { for mut text in &mut query {

View file

@ -1,11 +1,9 @@
use bevy::prelude::*; use bevy::prelude::*;
use crate::assets::AssetLoader;
#[derive(Component, Debug)] #[derive(Component, Debug)]
pub struct MainMenu; pub struct MainMenu;
pub fn startup(mut commands: Commands, assets: Res<AssetLoader>) { pub fn startup(mut commands: Commands) {
commands commands
.spawn(( .spawn((
MainMenu, MainMenu,
@ -25,7 +23,6 @@ pub fn startup(mut commands: Commands, assets: Res<AssetLoader>) {
TextBundle::from_section( TextBundle::from_section(
"Text Example\nPress ENTER to play", "Text Example\nPress ENTER to play",
TextStyle { TextStyle {
font: assets.fonts.iosevkalytemin.clone(),
font_size: 100.0, font_size: 100.0,
..default() ..default()
}, },
@ -63,20 +60,4 @@ pub fn exit(mut commands: Commands, q: Query<Entity, With<MainMenu>>) {
} }
} }
pub fn update( pub fn update() {}
keyboard: Res<ButtonInput<KeyCode>>,
mut next_state: ResMut<NextState<crate::View>>,
mut app_exit_events: ResMut<Events<bevy::app::AppExit>>,
) {
for key in keyboard.get_just_pressed() {
match key {
KeyCode::Escape => {
app_exit_events.send(AppExit::Success);
}
KeyCode::Enter => {
next_state.set(crate::View::InGame);
}
_ => {}
}
}
}

View file

@ -9,7 +9,7 @@ pub struct Heading(pub Vec2);
#[derive(Component, Deref, DerefMut, Debug, Default)] #[derive(Component, Deref, DerefMut, Debug, Default)]
pub struct Speed(pub f32); pub struct Speed(pub f32);
#[derive(Component, Debug)] #[derive(Component, Debug, Default)]
pub struct YSortable; pub struct YSortable;
pub fn resolve_velocity(mut query: Query<(&Velocity, &mut Transform)>, time: Res<Time>) { pub fn resolve_velocity(mut query: Query<(&Velocity, &mut Transform)>, time: Res<Time>) {

View file

@ -1,17 +1,19 @@
use bevy::sprite::MaterialMesh2dBundle;
use bevy::sprite::Mesh2dHandle;
use bevy::{prelude::*, window::PrimaryWindow};
use crate::camera::Watched; use crate::camera::Watched;
use crate::movement::YSortable; use crate::movement::{Heading, Mover, Speed, Velocity, YSortable};
use crate::statue; use crate::prelude::*;
use crate::{ use bevy::sprite::MaterialMesh2dBundle;
assets::AssetLoader,
movement::{Heading, Mover, Speed, Velocity},
};
const PLAYER_SPEED: f32 = 1000.; const PLAYER_SPEED: f32 = 1000.;
// #[derive(Resource, Default)]
// pub struct Layout(Handle<TextureAtlasLayout>);
// #[derive(Resource)]
// pub struct CrosshairMaterial(Handle<ColorMaterial>);
// #[derive(Resource)]
// pub struct CrosshairMesh(Mesh2dHandle);
#[derive(Component, Debug)] #[derive(Component, Debug)]
pub struct Player; pub struct Player;
@ -20,21 +22,28 @@ pub struct Crosshair;
pub fn startup( pub fn startup(
mut commands: Commands, mut commands: Commands,
assets: Res<AssetLoader>, assets: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
) { ) {
let layout = assets.add(TextureAtlasLayout::from_grid(
UVec2::new(32, 64),
3,
1,
Some(UVec2 { x: 0, y: 0 }),
Some(UVec2 { x: 0, y: 0 }),
));
commands commands
.spawn(( .spawn((
Player, Player,
YSortable, YSortable,
Watched, Watched,
SpriteBundle { SpriteBundle {
texture: assets.images.player.clone(), texture: assets.load("img/Player.png"),
..default() ..default()
}, },
TextureAtlas { TextureAtlas {
layout: assets.layouts.player.clone(), layout,
..default() ..default()
}, },
Mover { Mover {
@ -44,7 +53,7 @@ pub fn startup(
}, },
)) ))
.with_children(|player| { .with_children(|player| {
let mesh = Mesh2dHandle(meshes.add(Capsule2d::new(3.0, 25.0))); let mesh = meshes.add(Capsule2d::new(3.0, 25.0));
let material = materials.add(Color::hsl(360. * 1 as f32 / 3 as f32, 0.95, 0.7)); let material = materials.add(Color::hsl(360. * 1 as f32 / 3 as f32, 0.95, 0.7));
// let global_transform = GlobalTransform::from_xyz( // let global_transform = GlobalTransform::from_xyz(
// 0.0, 20.0, // 0.0, 20.0,
@ -62,138 +71,34 @@ pub fn startup(
player.spawn(( player.spawn((
Crosshair, Crosshair,
// SpatialBundle::default(), // TODO: crosshair transform?
// SpatialListener::new(1.), MaterialMesh2dBundle {
// MaterialMesh2dBundle { // global_transform,
// // global_transform, mesh: mesh.into(),
// mesh, material,
// material, transform,
// transform, ..default()
// ..default() },
// },
// Text2dBundle {
// text: Text::from_section(
// "You",
// TextStyle {
// font_size: 11.0,
// font: assets.fonts.iosevkalytemin.clone(),
// color: Color::WHITE,
// ..default()
// },
// ),
// transform: Transform::from_translation(Vec3::new(30.0, -30.0, 0.)),
// ..default()
// },
)); ));
player.spawn((SpatialBundle::default(), SpatialListener::new(1.)));
player.spawn((Text2dBundle {
text: Text::from_section(
"You",
TextStyle {
font_size: 18.,
color: Color::WHITE,
..default()
},
),
transform: Transform::from_translation(Vec3::new(30.0, -30.0, 0.)),
..default()
},));
}); });
} }
pub fn controls( pub fn control(input: Res<Input>, mut player: Query<&mut Heading, With<Player>>) {
mut commands: Commands, if let Ok(mut heading) = player.get_single_mut() {
mut player: Query<(&mut Sprite, &mut TextureAtlas, &mut Heading, Entity), With<Player>>, heading.0 = input.movement;
input: Res<ButtonInput<KeyCode>>,
mouse_input: Res<ButtonInput<MouseButton>>,
assets: Res<AssetLoader>,
win: Query<&Window, With<PrimaryWindow>>,
cam: Query<(&Camera, &GlobalTransform), With<Camera>>,
mut next_state: ResMut<NextState<crate::View>>,
mut crosshair: Query<&mut Transform, With<Crosshair>>,
) {
let (mut sprite, mut texture, mut heading, player_entity) =
player.get_single_mut().expect("no player_q entities");
let (camera, camera_transform) = cam.single();
let mouse_world_position = win
.single()
.cursor_position()
.and_then(|cursor| camera.viewport_to_world(&GlobalTransform::from_xyz(0., 0., 0.), cursor))
.map(|ray| ray.origin.truncate());
if let Some(pos) = mouse_world_position {
if let Ok(mut t) = crosshair.get_single_mut() {
t.look_at(Vec3::ZERO, pos.extend(0.));
t.translation = camera_transform.translation();
}
}
**heading = Vec2::ZERO;
for button in mouse_input.get_just_pressed() {
match button {
MouseButton::Left => {
if let Some(world_position) = win
.single()
.cursor_position()
.and_then(|cursor| camera.viewport_to_world(camera_transform, cursor))
.map(|ray| ray.origin.truncate())
{
let spos = world_position.extend(-world_position.y);
commands.spawn(statue::statue_at_position(&assets, spos));
}
}
_ => {}
}
}
for key in input.get_just_pressed() {
match key {
KeyCode::Escape => next_state.set(crate::View::MainMenu),
KeyCode::Enter => next_state.set(crate::View::MainMenu),
KeyCode::Space => {
let child = commands
.spawn(AudioSourceBundle {
source: assets.sounds.meow.clone(),
settings: PlaybackSettings::DESPAWN.with_spatial(false),
})
.id();
commands.entity(player_entity).push_children(&[child]);
}
_ => {}
}
}
for key in input.get_pressed() {
match key {
KeyCode::KeyA | KeyCode::ArrowLeft => {
**heading -= Vec2::X;
}
KeyCode::KeyD | KeyCode::ArrowRight => {
**heading += Vec2::X;
}
KeyCode::KeyW | KeyCode::ArrowUp => {
**heading += Vec2::Y;
}
KeyCode::KeyS | KeyCode::ArrowDown => {
**heading -= Vec2::Y;
}
_ => {}
}
}
if heading.y < 0. {
texture.index = 0;
} else if heading.y > 0. {
texture.index = 1;
} else if heading.x != 0. {
texture.index = 2;
sprite.flip_x = heading.x > 0.;
}
}
pub fn sprite_select(
mut q: Query<(&Handle<TextureAtlasLayout>, &mut TextureAtlas), With<Player>>,
keyboard_input: Res<ButtonInput<KeyCode>>,
texture_atlases: Res<Assets<TextureAtlasLayout>>,
) {
if keyboard_input.just_pressed(KeyCode::KeyN) {
info!("pressed N");
if let Ok((texture, mut sprite)) = q.get_single_mut() {
info!("next tex: {}", sprite.index);
let t = texture_atlases
.get(texture)
.expect("could not load player texture");
if sprite.index < t.textures.len() - 1 {
sprite.index += 1;
} else {
sprite.index = 0;
}
}
} }
} }

3
src/prelude.rs Normal file
View file

@ -0,0 +1,3 @@
pub use crate::game::Gameobject;
pub use crate::input::Input;
pub use bevy::prelude::*;

View file

@ -1,36 +1,58 @@
use crate::{assets::AssetLoader, movement::YSortable}; use crate::movement::YSortable;
use bevy::prelude::*; use crate::prelude::*;
#[derive(Component, Debug)] #[derive(Component, Debug, Default)]
pub struct Statue; pub struct Statue;
pub fn statue_at_position( #[derive(Bundle, Default)]
assets: &Res<AssetLoader>, pub struct StatueBundle {
translation: Vec3, gameobject: Gameobject,
) -> ( statue: Statue,
Statue, ysort: YSortable,
YSortable, sprite: SpriteBundle,
bevy::prelude::SpriteBundle, texture: TextureAtlas,
bevy::prelude::TextureAtlas,
) {
(
Statue,
YSortable,
SpriteBundle {
transform: Transform::from_translation(translation),
texture: assets.images.statue.clone(),
..default()
},
TextureAtlas {
layout: assets.layouts.statue.clone(),
..default()
},
)
} }
pub fn startup(mut commands: Commands, assets: Res<AssetLoader>) { pub fn startup(mut events: EventWriter<SpawnStatueEvent>) {
commands.spawn(statue_at_position(&assets, Vec3::new(50., 50., 0.))); events.send_batch([
commands.spawn(statue_at_position(&assets, Vec3::new(50., 100., 0.))); SpawnStatueEvent(Vec3::new(50., 50., 0.)),
SpawnStatueEvent(Vec3::new(50., 100., 0.)),
]);
}
#[derive(Event)]
pub struct SpawnStatueEvent(Vec3);
pub fn spawn_statue(
mut events: EventReader<SpawnStatueEvent>,
mut commands: Commands,
assets: Res<AssetServer>,
mut layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
// TODO: rebuilding this layout each spawn is surely not correct?
let layout = layouts.add(TextureAtlasLayout::from_grid(
UVec2::new(40, 74),
1,
1,
None,
Some(UVec2::new(443, 20)),
));
for ev in events.read() {
let bundle = StatueBundle {
sprite: SpriteBundle {
transform: Transform::from_translation(ev.0),
texture: assets.load("img/Props.png"),
..default()
},
texture: TextureAtlas {
layout: layout.clone(),
..default()
},
..default()
};
commands.spawn(bundle);
}
} }
pub fn exit(mut commands: Commands, q: Query<Entity, With<Statue>>) { pub fn exit(mut commands: Commands, q: Query<Entity, With<Statue>>) {