Files
snow_trail/src/bundles/player.rs
2026-03-28 10:34:19 +01:00

263 lines
9.3 KiB
Rust

use std::f32::consts::PI;
use std::rc::Rc;
use glam::Vec3;
use rapier3d::control::{CharacterAutostep, KinematicCharacterController};
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
use crate::bundles::Bundle;
use crate::components::lights::spot::SpotlightComponent;
use crate::components::player_states::{
FallingState, IdleState, JumpingState, LeapingState, RollingState, WalkingState,
};
use crate::components::{
InputComponent, JumpComponent, MeshComponent, MovementComponent, PhysicsComponent,
};
use crate::entity::EntityHandle;
use crate::loaders::mesh::Mesh;
use crate::paths;
use crate::physics::PhysicsManager;
use crate::render::Pipeline;
use crate::state::StateMachine;
use crate::systems::player_states::{LEAP_DURATION, ROLL_DURATION};
use crate::world::{Transform, World};
pub struct PlayerBundle
{
pub position: Vec3,
}
impl Bundle for PlayerBundle
{
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
{
let entity = world.spawn();
let spawn_transform = Transform::from_position(self.position);
let rigidbody = RigidBodyBuilder::kinematic_position_based()
.translation(spawn_transform.position.into())
.build();
let collider = ColliderBuilder::capsule_y(0.5, 0.5).build();
let _controller = KinematicCharacterController {
slide: true,
autostep: Some(CharacterAutostep::default()),
max_slope_climb_angle: 45.0,
..Default::default()
};
let rigidbody_handle = PhysicsManager::add_rigidbody(rigidbody);
let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));
let mesh = Mesh::load_mesh(&paths::meshes::player())
.map_err(|e| format!("missing player mesh: {}", e))?;
let mut state_machine = StateMachine::new::<FallingState>();
state_machine.register_state(|w: &mut World| &mut w.falling_states);
state_machine.register_state(|w: &mut World| &mut w.idle_states);
state_machine.register_state(|w: &mut World| &mut w.walking_states);
state_machine.register_state(|w: &mut World| &mut w.jumping_states);
state_machine.register_state(|w: &mut World| &mut w.leaping_states);
state_machine.register_state(|w: &mut World| &mut w.rolling_states);
let entity_id = entity;
state_machine.add_transition::<FallingState, IdleState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && !has_input
});
state_machine.add_transition::<FallingState, WalkingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && has_input
});
state_machine.add_transition::<IdleState, WalkingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && has_input
});
state_machine.add_transition::<WalkingState, IdleState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && !has_input
});
state_machine.add_transition::<IdleState, FallingState>(move |world| {
!world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false)
});
state_machine.add_transition::<WalkingState, FallingState>(move |world| {
!world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false)
});
state_machine.add_transition::<IdleState, JumpingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false);
let jump_pressed = world
.inputs
.with(entity_id, |i| i.jump_just_pressed)
.unwrap_or(false);
is_grounded && jump_pressed
});
state_machine.add_transition::<WalkingState, JumpingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false);
let jump_pressed = world
.inputs
.with(entity_id, |i| i.jump_just_pressed)
.unwrap_or(false);
is_grounded && jump_pressed
});
state_machine.add_transition::<JumpingState, FallingState>(move |world| {
let time = world
.jumping_states
.with(entity_id, |s| s.time_in_state)
.unwrap_or(0.0);
world
.jumps
.with(entity_id, |j| time >= j.jump_duration)
.unwrap_or(true)
});
state_machine.add_transition::<IdleState, LeapingState>(move |world| {
world
.inputs
.with(entity_id, |i| i.roll_just_pressed)
.unwrap_or(false)
});
state_machine.add_transition::<WalkingState, LeapingState>(move |world| {
world
.inputs
.with(entity_id, |i| i.roll_just_pressed)
.unwrap_or(false)
});
state_machine.add_transition::<LeapingState, RollingState>(move |world| {
world
.leaping_states
.with(entity_id, |s| s.time_in_state >= LEAP_DURATION)
.unwrap_or(false)
});
state_machine.add_transition::<LeapingState, FallingState>(move |world| {
let leap_done = world
.leaping_states
.with(entity_id, |s| s.time_in_state >= LEAP_DURATION)
.unwrap_or(false);
let not_grounded = !world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(true);
leap_done && not_grounded
});
state_machine.add_transition::<RollingState, IdleState>(move |world| {
let done = world
.rolling_states
.with(entity_id, |s| s.time_in_state >= ROLL_DURATION)
.unwrap_or(false);
let grounded = world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false);
done && grounded
});
state_machine.add_transition::<RollingState, FallingState>(move |world| {
let done = world
.rolling_states
.with(entity_id, |s| s.time_in_state >= ROLL_DURATION)
.unwrap_or(false);
let grounded = world
.movements
.with(entity_id, |m| m.movement_context.is_floored)
.unwrap_or(false);
done && !grounded
});
world.falling_states.insert(entity, FallingState::default());
world.transforms.insert(entity, spawn_transform);
world.movements.insert(entity, MovementComponent::new());
world.jumps.insert(entity, JumpComponent::default());
world.inputs.insert(entity, InputComponent::default());
world.physics.insert(
entity,
PhysicsComponent {
rigidbody: rigidbody_handle,
collider: Some(collider_handle),
},
);
world.meshes.insert(
entity,
MeshComponent {
mesh: Rc::new(mesh),
pipeline: Pipeline::Standard,
instance_buffer: None,
num_instances: 1,
tile_scale: 4.0,
enable_dissolve: false,
enable_snow_light: false,
},
);
world.player_tags.insert(entity, ());
world.state_machines.insert(entity, state_machine);
world.names.insert(entity, "Player".to_string());
let outer_angle = PI / 2.0 * 0.9;
world.spotlights.insert(
entity,
SpotlightComponent::new(
Vec3::new(1.0, 2.5, 1.0),
Vec3::new(0.0, -1.0, 0.0),
100.0,
outer_angle * 0.5,
outer_angle,
),
);
Ok(entity)
}
}