-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Character Controller and basic stuff (#46)
Issue: ============== Closes #6 What was done: ============== * Added camera, animation and game related modules. * Makes the camera follow the player. * Added a dragon spritesheet for the player. * Added input management for player movement via mouse. * Added a placeholder background image. * Fixed some noisy WGPU console warnings. * Added build optimizations for third party crates. * Fixed a few minor issues. * Added a few convenience functions. * Updated all dependencies in Cargo.lock.
- Loading branch information
Showing
11 changed files
with
269 additions
and
16 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use bevy::prelude::*; | ||
|
||
pub struct AnimationPlugin; | ||
|
||
impl Plugin for AnimationPlugin { | ||
fn build(&self, app: &mut App) { | ||
app.add_systems(Update, animate_sprite); | ||
} | ||
} | ||
|
||
#[derive(Component)] | ||
pub struct AnimationIndices { | ||
first: usize, | ||
last: usize, | ||
} | ||
|
||
impl AnimationIndices { | ||
pub fn new(first: usize, last: usize) -> Self { | ||
Self { first, last } | ||
} | ||
} | ||
|
||
#[derive(Component, Deref, DerefMut)] | ||
pub struct AnimationTimer(Timer); | ||
|
||
impl AnimationTimer { | ||
pub fn from_seconds(secs: f32) -> Self { | ||
Self(Timer::from_seconds(secs, TimerMode::Repeating)) | ||
} | ||
} | ||
|
||
fn animate_sprite( | ||
time: Res<Time>, | ||
mut query: Query<( | ||
&AnimationIndices, | ||
&mut AnimationTimer, | ||
&mut TextureAtlasSprite, | ||
)>, | ||
) { | ||
for (indices, mut timer, mut sprite) in &mut query { | ||
if timer.tick(time.delta()).just_finished() { | ||
sprite.index = if sprite.index == indices.last { | ||
indices.first | ||
} else { | ||
sprite.index + 1 | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
use bevy::prelude::*; | ||
|
||
use crate::game::Player; | ||
|
||
pub struct CameraPlugin; | ||
|
||
impl Plugin for CameraPlugin { | ||
fn build(&self, app: &mut App) { | ||
app.add_systems(Startup, setup_camera); | ||
|
||
app.add_systems(Update, update_camera.run_if(any_with_component::<Player>())); | ||
} | ||
} | ||
|
||
fn setup_camera(mut commands: Commands) { | ||
commands.spawn(Camera2dBundle::default()); | ||
} | ||
|
||
fn update_camera( | ||
mut camera_query: Query<&mut Transform, With<Camera2d>>, | ||
player_query: Query<&Transform, (With<Player>, Without<Camera2d>)>, | ||
) { | ||
let player_transform = player_query.single(); | ||
let mut camera_transform = camera_query.single_mut(); | ||
|
||
camera_transform.translation.x = player_transform.translation.x; | ||
camera_transform.translation.y = player_transform.translation.y; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
mod player; | ||
mod plugin; | ||
|
||
pub use player::Player; | ||
pub use plugin::GamePlugin; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
use bevy::prelude::*; | ||
|
||
use crate::animation::{AnimationIndices, AnimationTimer}; | ||
|
||
#[derive(Component)] | ||
pub struct Player; | ||
|
||
pub(super) fn spawn_player(mut commands: Commands, asset_server: Res<AssetServer>) { | ||
let texture = asset_server | ||
.get_handle("textures/dragon.png") | ||
.unwrap_or_default(); | ||
let texture_atlas = TextureAtlas::from_grid(texture, Vec2::new(191., 161.), 12, 1, None, None); | ||
let texture_atlas_handle = asset_server.add(texture_atlas); | ||
|
||
commands.spawn(( | ||
Player, | ||
SpriteSheetBundle { | ||
sprite: TextureAtlasSprite::new(0), | ||
texture_atlas: texture_atlas_handle.clone(), | ||
transform: Transform::from_translation(Vec2::ZERO.extend(1.)), | ||
..default() | ||
}, | ||
AnimationIndices::new(0, 2), | ||
AnimationTimer::from_seconds(0.2), | ||
)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use bevy::prelude::*; | ||
|
||
use crate::AppState; | ||
|
||
use super::player::spawn_player; | ||
|
||
pub struct GamePlugin; | ||
|
||
impl Plugin for GamePlugin { | ||
fn build(&self, app: &mut App) { | ||
app.add_systems(OnEnter(AppState::InGame), (draw_background, spawn_player)); | ||
} | ||
} | ||
|
||
fn draw_background(mut commands: Commands, asset_server: Res<AssetServer>) { | ||
let texture = asset_server | ||
.get_handle("textures/background.png") | ||
.unwrap_or_default(); | ||
commands.spawn(SpriteBundle { | ||
texture, | ||
..default() | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use std::f32::consts::FRAC_PI_2; | ||
|
||
use bevy::{ecs::system::SystemParam, prelude::*, window::PrimaryWindow}; | ||
|
||
use crate::{game::Player, playing}; | ||
|
||
pub struct InputPlugin; | ||
|
||
impl Plugin for InputPlugin { | ||
fn build(&self, app: &mut App) { | ||
app.add_systems(Update, mouse_input.run_if(playing())); | ||
} | ||
} | ||
|
||
#[derive(Resource)] | ||
struct CursorWorldPosition(Option<Vec2>); | ||
|
||
#[derive(SystemParam)] | ||
struct CursorWorldPositionChecker<'w, 's> { | ||
window_query: Query<'w, 's, &'static Window, With<PrimaryWindow>>, | ||
camera_query: Query<'w, 's, (&'static Camera, &'static GlobalTransform), With<Camera2d>>, | ||
} | ||
|
||
impl CursorWorldPositionChecker<'_, '_> { | ||
pub fn cursor_world_position(&self) -> Option<Vec2> { | ||
let window = self.window_query.single(); | ||
|
||
window.cursor_position().and_then(|cursor_position| { | ||
let (camera, camera_transform) = self.camera_query.single(); | ||
camera.viewport_to_world_2d(camera_transform, cursor_position) | ||
}) | ||
} | ||
} | ||
|
||
fn mouse_input( | ||
mouse_input: ResMut<Input<MouseButton>>, | ||
cursor_world_position_checker: CursorWorldPositionChecker, | ||
mut query: Query<&mut Transform, With<Player>>, | ||
mut gizmos: Gizmos, | ||
) { | ||
if mouse_input.pressed(MouseButton::Right) { | ||
if let Some(cursor_position) = cursor_world_position_checker.cursor_world_position() { | ||
let mut player_transform = query.single_mut(); | ||
let player_position = player_transform.translation.truncate(); | ||
let cursor_to_player_vector = cursor_position - player_position; | ||
let cursor_distance_to_player = cursor_position.distance(player_position); | ||
let velocity_rate = cursor_distance_to_player.min(300.) / 300.; | ||
|
||
if cursor_to_player_vector != Vec2::ZERO { | ||
let direction = cursor_to_player_vector.normalize(); | ||
|
||
player_transform.translation.x += direction.x * 15. * velocity_rate; | ||
player_transform.translation.y += direction.y * 15. * velocity_rate; | ||
|
||
if direction != Vec2::ZERO { | ||
let angle = (direction).angle_between(Vec2::X); | ||
|
||
if angle.is_finite() { | ||
// FIXME: Rotate the image sprite to always face right? | ||
// FRAC_PI_2 is subtracted to offset the 90 degree rotation from the X axis the sprite has. | ||
player_transform.rotation = Quat::from_rotation_z(-angle - FRAC_PI_2); | ||
} | ||
} | ||
} | ||
|
||
#[cfg(debug_assertions)] | ||
{ | ||
gizmos.line_2d(player_position, cursor_position, Color::YELLOW); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,71 @@ | ||
use audio::AudioPlugin; | ||
use bevy::prelude::*; | ||
use textures::TexturesPlugin; | ||
use animation::AnimationPlugin; | ||
use audio::{audio_assets_loaded, AudioPlugin}; | ||
use bevy::{ | ||
prelude::*, | ||
render::{ | ||
settings::{Backends, RenderCreation, WgpuSettings}, | ||
RenderPlugin, | ||
}, | ||
}; | ||
use camera::CameraPlugin; | ||
use game::GamePlugin; | ||
use input::InputPlugin; | ||
use textures::{texture_assets_loaded, TexturesPlugin}; | ||
|
||
mod animation; | ||
mod audio; | ||
mod camera; | ||
mod game; | ||
mod input; | ||
mod textures; | ||
|
||
fn main() { | ||
let mut app = App::new(); | ||
|
||
app.add_plugins((DefaultPlugins, AudioPlugin, TexturesPlugin)); | ||
app.add_plugins(( | ||
// FIXME: Remove setting the backend explicitly to avoid noisy warnings | ||
// when https://github.com/gfx-rs/wgpu/issues/3959 gets fixed. | ||
DefaultPlugins.set(RenderPlugin { | ||
render_creation: RenderCreation::Automatic(WgpuSettings { | ||
backends: Some(Backends::DX12), | ||
..default() | ||
}), | ||
}), | ||
AnimationPlugin, | ||
AudioPlugin, | ||
CameraPlugin, | ||
GamePlugin, | ||
InputPlugin, | ||
TexturesPlugin, | ||
)); | ||
|
||
app.add_state::<AppState>(); | ||
|
||
app.add_systems( | ||
Update, | ||
handle_asset_load.run_if(assets_loaded().and_then(run_once())), | ||
); | ||
|
||
app.run(); | ||
} | ||
|
||
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, States)] | ||
pub enum AppState { | ||
#[default] | ||
Setup, | ||
InGame, | ||
} | ||
|
||
pub fn playing() -> impl Condition<()> { | ||
IntoSystem::into_system(in_state(AppState::InGame)) | ||
} | ||
|
||
fn handle_asset_load(mut state: ResMut<NextState<AppState>>) { | ||
#[cfg(debug_assertions)] | ||
info!("Assets loaded successfully."); | ||
state.set(AppState::InGame); | ||
} | ||
|
||
fn assets_loaded() -> impl Condition<()> { | ||
texture_assets_loaded().and_then(audio_assets_loaded()) | ||
} |