Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Character Controller and basic stuff #46

Merged
merged 18 commits into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ bevy = { version = "0.12.0", default-features = false, features = [
"vorbis",
"x11",
] }

[profile.dev.package."*"]
opt-level = 3
Binary file added assets/textures/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/textures/dragon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions src/animation.rs
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
};
}
}
}
28 changes: 28 additions & 0 deletions src/camera.rs
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;
}
5 changes: 5 additions & 0 deletions src/game/mod.rs
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;
26 changes: 26 additions & 0 deletions src/game/player.rs
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),
));
}
23 changes: 23 additions & 0 deletions src/game/plugin.rs
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()
});
}
72 changes: 72 additions & 0 deletions src/input.rs
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);
}
}
}
}
65 changes: 61 additions & 4 deletions src/main.rs
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())
}