generated from 2ndbeam/bevy-template
feat: Basic player implementation
This commit is contained in:
parent
62199a1817
commit
e4b1475c48
9 changed files with 830 additions and 54 deletions
62
src/input.rs
Normal file
62
src/input.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
//! Input module
|
||||
|
||||
use bevy::prelude::*;
|
||||
use leafwing_input_manager::prelude::*;
|
||||
|
||||
/// This trait is used to provide uniform way of getting predefined [InputMap]
|
||||
pub trait DefaultInputMap: Actionlike {
|
||||
/// Get default input map for the Actionlike
|
||||
fn input_map() -> InputMap<Self>;
|
||||
}
|
||||
|
||||
/// Input used to control player character
|
||||
#[derive(Actionlike, Debug, PartialEq, Eq, Hash, Clone, Copy, Reflect)]
|
||||
#[reflect(Debug, PartialEq, Hash, Clone)]
|
||||
pub enum PlayerInput {
|
||||
/// Move character
|
||||
#[actionlike(Axis)] Move,
|
||||
/// Dodge with light weapon or block with heavy weapon
|
||||
DodgeBlock,
|
||||
/// Perform light attack
|
||||
LightAttack,
|
||||
/// Perform heavy attack
|
||||
HeavyAttack,
|
||||
}
|
||||
|
||||
impl DefaultInputMap for PlayerInput {
|
||||
fn input_map() -> InputMap<Self> {
|
||||
InputMap::default()
|
||||
.with_axis(Self::Move, VirtualAxis::ad())
|
||||
.with_axis(Self::Move, GamepadAxis::LeftStickX)
|
||||
|
||||
.with(Self::DodgeBlock, KeyCode::ShiftLeft)
|
||||
.with(Self::DodgeBlock, GamepadButton::South)
|
||||
|
||||
.with(Self::LightAttack, MouseButton::Left)
|
||||
.with(Self::HeavyAttack, GamepadButton::North)
|
||||
|
||||
.with(Self::LightAttack, MouseButton::Right)
|
||||
.with(Self::HeavyAttack, GamepadButton::East)
|
||||
}
|
||||
}
|
||||
|
||||
/// Input used to debug this prototype
|
||||
#[derive(Actionlike, Debug, PartialEq, Eq, Hash, Clone, Copy, Reflect)]
|
||||
#[reflect(Debug, PartialEq, Hash, Clone)]
|
||||
pub enum DebugInput {
|
||||
/// Reset game state
|
||||
Reset,
|
||||
/// Spawn enemy in predefined position
|
||||
SpawnEnemy,
|
||||
}
|
||||
|
||||
impl DefaultInputMap for DebugInput {
|
||||
fn input_map() -> InputMap<Self> {
|
||||
InputMap::default()
|
||||
.with(Self::Reset, KeyCode::KeyR)
|
||||
.with(Self::Reset, GamepadButton::Select)
|
||||
|
||||
.with(Self::SpawnEnemy, KeyCode::KeyT)
|
||||
.with(Self::SpawnEnemy, GamepadButton::LeftTrigger)
|
||||
}
|
||||
}
|
||||
18
src/lib.rs
18
src/lib.rs
|
|
@ -6,3 +6,21 @@
|
|||
mod tests;
|
||||
|
||||
pub mod graph;
|
||||
pub mod input;
|
||||
pub mod player;
|
||||
pub mod plugin;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::player::Player;
|
||||
|
||||
const PIXELS_PER_METER: f32 = 16.;
|
||||
|
||||
/// Returns pixel measurement for given length in meters
|
||||
#[inline(always)] pub const fn meters(length: f32) -> f32 { PIXELS_PER_METER * length }
|
||||
|
||||
// TODO: Replace with proper setting up
|
||||
/// Temporary function to setup world
|
||||
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn(Player::bundle(&asset_server, Vec2::ZERO));
|
||||
}
|
||||
|
|
|
|||
63
src/main.rs
63
src/main.rs
|
|
@ -1,54 +1,19 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
struct Person;
|
||||
|
||||
#[derive(Component)]
|
||||
struct Name(String);
|
||||
|
||||
#[derive(Resource)]
|
||||
struct GreetTimer(Timer);
|
||||
|
||||
#[derive(Resource)]
|
||||
struct UpdateTimer(Timer);
|
||||
|
||||
pub struct HelloPlugin;
|
||||
|
||||
impl Plugin for HelloPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(GreetTimer(Timer::from_seconds(2.0, TimerMode::Repeating)));
|
||||
app.insert_resource(UpdateTimer(Timer::from_seconds(10.0, TimerMode::Once)));
|
||||
app.add_systems(Startup, add_people);
|
||||
app.add_systems(Update, (update_people, greet_people).chain());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn add_people(mut commands: Commands) {
|
||||
commands.spawn((Person, Name("Alkesey Mirnekov".to_string())));
|
||||
commands.spawn((Person, Name("Alkesey Mirnekov 2".to_string())));
|
||||
commands.spawn((Person, Name("Alkesey Mirnekov 3".to_string())));
|
||||
}
|
||||
|
||||
fn greet_people(time: Res<Time>, mut timer: ResMut<GreetTimer>, query: Query<&Name, With<Person>>) {
|
||||
if timer.0.tick(time.delta()).just_finished() {
|
||||
for name in &query {
|
||||
println!("hello {}!", name.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_people(time: Res<Time>, mut timer: ResMut<UpdateTimer>, mut query: Query<&mut Name, With<Person>>) {
|
||||
if timer.0.tick(time.delta()).just_finished() {
|
||||
for mut name in &mut query {
|
||||
name.0 = format!("{} II", name.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
remote::{
|
||||
RemotePlugin,
|
||||
http::RemoteHttpPlugin,
|
||||
},
|
||||
};
|
||||
use bevy_combat_proto::plugin::GamePlugin;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugins(HelloPlugin)
|
||||
.add_plugins((
|
||||
DefaultPlugins.set(ImagePlugin::default_nearest()),
|
||||
GamePlugin,
|
||||
RemotePlugin::default(),
|
||||
RemoteHttpPlugin::default(),
|
||||
))
|
||||
.run();
|
||||
}
|
||||
|
|
|
|||
76
src/player/mod.rs
Normal file
76
src/player/mod.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
//! Player module
|
||||
|
||||
use bevy::{camera::ScalingMode, prelude::*};
|
||||
use bevy_rapier2d::prelude::*;
|
||||
use leafwing_input_manager::prelude::*;
|
||||
|
||||
use crate::{
|
||||
meters,
|
||||
input::{
|
||||
DefaultInputMap,
|
||||
PlayerInput,
|
||||
}
|
||||
};
|
||||
|
||||
pub mod systems;
|
||||
|
||||
/// Player component
|
||||
#[derive(Component, Clone, Copy, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Component, Clone, PartialEq, Debug, Default)]
|
||||
#[require(Transform, InputMap<PlayerInput>)]
|
||||
pub struct Player {
|
||||
/// Movement speed in pixels/s
|
||||
pub speed: f32,
|
||||
}
|
||||
|
||||
impl Default for Player {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
speed: meters(1.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Player {
|
||||
/// Returns player bundle with everything needed
|
||||
pub fn bundle(asset_server: &Res<AssetServer>, position: Vec2) -> impl Bundle {
|
||||
let image = asset_server.load("sprites/player/placeholder.png");
|
||||
(
|
||||
// Basic
|
||||
Name::new("Player"),
|
||||
Player::default(),
|
||||
|
||||
// Visible
|
||||
Sprite::from_image(image),
|
||||
Transform::from_xyz(position.x, position.y, 1.),
|
||||
|
||||
// Input
|
||||
PlayerInput::input_map(),
|
||||
|
||||
// Collision
|
||||
RigidBody::KinematicPositionBased,
|
||||
KinematicCharacterController::default(),
|
||||
ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_STATIC,
|
||||
Collider::cuboid(meters(0.3), meters(0.9)),
|
||||
ActiveEvents::COLLISION_EVENTS,
|
||||
Sleeping::disabled(),
|
||||
|
||||
Children::spawn((
|
||||
Spawn((
|
||||
Name::new("Player camera"),
|
||||
Camera2d,
|
||||
Camera {
|
||||
clear_color: ClearColorConfig::Custom(Color::hsl(0.02, 0.67, 0.65)),
|
||||
..default()
|
||||
},
|
||||
Projection::Orthographic(OrthographicProjection {
|
||||
scaling_mode: ScalingMode::FixedVertical {
|
||||
viewport_height: meters(24.),
|
||||
},
|
||||
..OrthographicProjection::default_2d()
|
||||
}),
|
||||
)),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
25
src/player/systems.rs
Normal file
25
src/player/systems.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//! Player systems
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Do something based on [PlayerInput]
|
||||
pub fn handle_input(
|
||||
time: Res<Time>,
|
||||
|
||||
player_query: Query<(
|
||||
&Player,
|
||||
&ActionState<PlayerInput>,
|
||||
&mut KinematicCharacterController,
|
||||
&mut Sprite,
|
||||
)>,
|
||||
) {
|
||||
for (player, action_state, mut controller, mut sprite) in player_query {
|
||||
let direction = action_state.clamped_value(&PlayerInput::Move);
|
||||
|
||||
controller.translation = Some(vec2(direction * player.speed * time.delta_secs(), 0.));
|
||||
|
||||
if direction != 0. {
|
||||
sprite.flip_x = direction < 0.;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/plugin.rs
Normal file
25
src/plugin.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//! Plugin module where everything is connected
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_rapier2d::prelude::*;
|
||||
use leafwing_input_manager::prelude::*;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// Plugin that connects everything needed for this prototype
|
||||
pub struct GamePlugin;
|
||||
|
||||
impl Plugin for GamePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((
|
||||
RapierDebugRenderPlugin::default(),
|
||||
RapierPhysicsPlugin::<()>::default()
|
||||
.with_length_unit(meters(1.)),
|
||||
|
||||
InputManagerPlugin::<input::PlayerInput>::default(),
|
||||
InputManagerPlugin::<input::DebugInput>::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, player::systems::handle_input);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue