use crate::entity_registry::MapEntityMarker;
use crate::MapRoot;
use bevy::prelude::*;
use bevy_map_core::{InputConfig, InputProfile, MapProject};
pub struct MapEntityInputPlugin;
impl Plugin for MapEntityInputPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, spawn_entity_input)
.add_systems(
Update,
(platformer_input_system, top_down_input_system).run_if(
any_with_component::<PlatformerInput>.or(any_with_component::<TopDownInput>),
),
);
}
}
#[derive(Component, Debug, Clone)]
pub struct PlatformerInput {
pub speed: f32,
pub jump_force: f32,
pub max_fall_speed: f32,
pub grounded: bool,
}
impl Default for PlatformerInput {
fn default() -> Self {
Self {
speed: 200.0,
jump_force: 400.0,
max_fall_speed: 600.0,
grounded: false,
}
}
}
#[derive(Component, Debug, Clone)]
pub struct TopDownInput {
pub speed: f32,
}
impl Default for TopDownInput {
fn default() -> Self {
Self { speed: 200.0 }
}
}
#[derive(Component, Debug, Clone)]
pub struct TwinStickInput {
pub speed: f32,
}
impl Default for TwinStickInput {
fn default() -> Self {
Self { speed: 200.0 }
}
}
#[derive(Component, Debug, Clone)]
pub struct CustomInput {
pub profile_name: String,
pub speed: f32,
}
#[derive(Component)]
pub struct EntityInputSpawned;
fn spawn_entity_input(
mut commands: Commands,
entity_query: Query<
(Entity, &MapEntityMarker),
(Added<MapEntityMarker>, Without<EntityInputSpawned>),
>,
map_root_query: Query<&MapRoot>,
map_assets: Res<Assets<MapProject>>,
) {
let project = map_root_query
.iter()
.find_map(|root| map_assets.get(&root.handle));
let Some(project) = project else {
return;
};
for (entity, marker) in entity_query.iter() {
let Some(type_config) = project.get_entity_type_config(&marker.type_name) else {
commands.entity(entity).insert(EntityInputSpawned);
continue;
};
let Some(input) = &type_config.input else {
commands.entity(entity).insert(EntityInputSpawned);
continue;
};
spawn_input_component(&mut commands, entity, input);
commands.entity(entity).insert(EntityInputSpawned);
info!(
"Spawned input for entity '{}' (type: {}, profile: {})",
marker.instance_id,
marker.type_name,
input.profile.display_name()
);
}
}
fn spawn_input_component(commands: &mut Commands, entity: Entity, config: &InputConfig) {
match &config.profile {
InputProfile::Platformer => {
commands.entity(entity).insert(PlatformerInput {
speed: config.speed,
jump_force: config.jump_force.unwrap_or(400.0),
max_fall_speed: config.max_fall_speed.unwrap_or(600.0),
grounded: false,
});
}
InputProfile::TopDown => {
commands.entity(entity).insert(TopDownInput {
speed: config.speed,
});
}
InputProfile::TwinStick => {
commands.entity(entity).insert(TwinStickInput {
speed: config.speed,
});
}
InputProfile::Custom { name } => {
commands.entity(entity).insert(CustomInput {
profile_name: name.clone(),
speed: config.speed,
});
}
InputProfile::None => {
}
}
}
#[cfg(feature = "physics")]
fn platformer_input_system(
keyboard: Res<ButtonInput<KeyCode>>,
mut query: Query<(&PlatformerInput, &mut avian2d::prelude::LinearVelocity)>,
) {
for (input, mut velocity) in query.iter_mut() {
let mut direction = 0.0;
if keyboard.pressed(KeyCode::KeyA) || keyboard.pressed(KeyCode::ArrowLeft) {
direction -= 1.0;
}
if keyboard.pressed(KeyCode::KeyD) || keyboard.pressed(KeyCode::ArrowRight) {
direction += 1.0;
}
velocity.x = direction * input.speed;
if keyboard.just_pressed(KeyCode::Space) {
velocity.y = input.jump_force;
}
if velocity.y < -input.max_fall_speed {
velocity.y = -input.max_fall_speed;
}
}
}
#[cfg(not(feature = "physics"))]
fn platformer_input_system(
keyboard: Res<ButtonInput<KeyCode>>,
mut query: Query<(&PlatformerInput, &mut Transform)>,
time: Res<Time>,
) {
for (input, mut transform) in query.iter_mut() {
let mut direction = Vec2::ZERO;
if keyboard.pressed(KeyCode::KeyA) || keyboard.pressed(KeyCode::ArrowLeft) {
direction.x -= 1.0;
}
if keyboard.pressed(KeyCode::KeyD) || keyboard.pressed(KeyCode::ArrowRight) {
direction.x += 1.0;
}
transform.translation.x += direction.x * input.speed * time.delta_secs();
}
}
#[cfg(feature = "physics")]
fn top_down_input_system(
keyboard: Res<ButtonInput<KeyCode>>,
mut query: Query<(&TopDownInput, &mut avian2d::prelude::LinearVelocity)>,
) {
for (input, mut velocity) in query.iter_mut() {
let mut direction = Vec2::ZERO;
if keyboard.pressed(KeyCode::KeyW) || keyboard.pressed(KeyCode::ArrowUp) {
direction.y += 1.0;
}
if keyboard.pressed(KeyCode::KeyS) || keyboard.pressed(KeyCode::ArrowDown) {
direction.y -= 1.0;
}
if keyboard.pressed(KeyCode::KeyA) || keyboard.pressed(KeyCode::ArrowLeft) {
direction.x -= 1.0;
}
if keyboard.pressed(KeyCode::KeyD) || keyboard.pressed(KeyCode::ArrowRight) {
direction.x += 1.0;
}
let movement = direction.normalize_or_zero() * input.speed;
velocity.x = movement.x;
velocity.y = movement.y;
}
}
#[cfg(not(feature = "physics"))]
fn top_down_input_system(
keyboard: Res<ButtonInput<KeyCode>>,
mut query: Query<(&TopDownInput, &mut Transform)>,
time: Res<Time>,
) {
for (input, mut transform) in query.iter_mut() {
let mut direction = Vec2::ZERO;
if keyboard.pressed(KeyCode::KeyW) || keyboard.pressed(KeyCode::ArrowUp) {
direction.y += 1.0;
}
if keyboard.pressed(KeyCode::KeyS) || keyboard.pressed(KeyCode::ArrowDown) {
direction.y -= 1.0;
}
if keyboard.pressed(KeyCode::KeyA) || keyboard.pressed(KeyCode::ArrowLeft) {
direction.x -= 1.0;
}
if keyboard.pressed(KeyCode::KeyD) || keyboard.pressed(KeyCode::ArrowRight) {
direction.x += 1.0;
}
let movement = direction.normalize_or_zero() * input.speed * time.delta_secs();
transform.translation.x += movement.x;
transform.translation.y += movement.y;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_compiles() {
let _plugin = MapEntityInputPlugin;
}
#[test]
fn test_input_components() {
let platformer = PlatformerInput::default();
assert_eq!(platformer.speed, 200.0);
let top_down = TopDownInput::default();
assert_eq!(top_down.speed, 200.0);
}
}