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::states::player_states::{LEAP_DURATION, ROLL_DURATION}; use crate::states::state::StateMachine; use crate::world::{Transform, World}; pub struct PlayerBundle { pub position: Vec3, } impl Bundle for PlayerBundle { fn spawn(self, world: &mut World) -> Result { 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::(); 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::(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::(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::(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::(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::(move |world| { !world .movements .with(entity_id, |m| m.movement_context.is_floored) .unwrap_or(false) }); state_machine.add_transition::(move |world| { !world .movements .with(entity_id, |m| m.movement_context.is_floored) .unwrap_or(false) }); state_machine.add_transition::(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::(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::(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::(move |world| { world .inputs .with(entity_id, |i| i.roll_just_pressed) .unwrap_or(false) }); state_machine.add_transition::(move |world| { world .inputs .with(entity_id, |i| i.roll_just_pressed) .unwrap_or(false) }); state_machine.add_transition::(move |world| { world .leaping_states .with(entity_id, |s| s.time_in_state >= LEAP_DURATION) .unwrap_or(false) }); state_machine.add_transition::(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::(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::(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) } }