use glam::Vec2; use rapier2d::{ control::{CharacterAutostep, CharacterLength, KinematicCharacterController}, pipeline::QueryFilter, }; use crate::{ config::GRAVITY, input::Input, sim::{ entity::{EntityBehaviour, EntityDef, EntityUpdateCtx}, rb_manager::character_impulses::solve_character_collision_impulses, sim_manager::SimCtx, }, }; struct WizardEntityBehaviour { movement_input_x: f32, jump: f32, grounded: bool, acc_vel: Vec2, kinematic_controller: KinematicCharacterController, } const JUMP_VELOCITY: f32 = 100.0; const ACCEL: f32 = 1000.0; const AIR_ACCEL: f32 = 350.0; const MAX_SPEED: f32 = 75.0; impl EntityBehaviour for WizardEntityBehaviour { fn update(&mut self, _: &mut EntityUpdateCtx, ctx: &mut SimCtx, _: f32) { if ctx.input_manager.held(Input::Left) { self.movement_input_x = -1.0; } else if ctx.input_manager.held(Input::Right) { self.movement_input_x = 1.0; } else { self.movement_input_x = 0.0; } if ctx.input_manager.pressed(Input::Jump) { self.jump = 0.06; } } fn physics_update( &mut self, update_ctx: &mut EntityUpdateCtx, ctx: &mut SimCtx, delta_time: f32, ) { if self.grounded { // TODO delta_time if self.movement_input_x.abs() < 0.2 { self.acc_vel.x = self.acc_vel.x * 0.7; } // if we're moving up, keep moving up self.acc_vel.y = self.acc_vel.y.min(0.0); if self.jump > 0.0 { self.acc_vel.y -= JUMP_VELOCITY; self.jump = 0.0; } } else { self.acc_vel.x = self.acc_vel.x * 0.95; self.acc_vel.y += GRAVITY * delta_time; if self.jump > 0.0 { self.jump -= delta_time; } } if self.acc_vel.x.abs() < 1.0 { self.acc_vel.x = 0.0; } let flip_adj = if self.acc_vel.x.signum() != self.movement_input_x.signum() { 3.0 } else { 1.0 }; self.acc_vel.x += self.movement_input_x * if self.grounded { ACCEL } else { AIR_ACCEL } * delta_time * flip_adj; self.acc_vel.x = self.acc_vel.x.clamp(-MAX_SPEED, MAX_SPEED); let query_pipeline = ctx .rb_manager .physics_manager .world .broad_phase .as_query_pipeline( ctx.rb_manager .physics_manager .world .narrow_phase .query_dispatcher(), &ctx.rb_manager.physics_manager.world.bodies, &ctx.rb_manager.physics_manager.world.colliders, QueryFilter::default().exclude_rigid_body(update_ctx.entity_data.rb_h.unwrap()), ); let rb = ctx .rb_manager .physics_manager .world .bodies .get(update_ctx.entity_data.rb_h.unwrap()) .unwrap(); let collider = ctx .rb_manager .physics_manager .world .colliders .get(update_ctx.entity_data.collider_h.unwrap()) .unwrap() .clone(); // move_shape works in translations, not velocities: feed it the distance we // want to cover this step, and convert the allowed distance back to a velocity. let mut collisions = Vec::new(); let movement = self.kinematic_controller.move_shape( delta_time, &query_pipeline, collider.shape(), rb.position(), self.acc_vel * delta_time, |col| collisions.push(col), ); let mut query_pipeline_mut = ctx .rb_manager .physics_manager .world .broad_phase .as_query_pipeline_mut( ctx.rb_manager .physics_manager .world .narrow_phase .query_dispatcher(), &mut ctx.rb_manager.physics_manager.world.bodies, &mut ctx.rb_manager.physics_manager.world.colliders, QueryFilter::default().exclude_rigid_body(update_ctx.entity_data.rb_h.unwrap()), ); // apply collisions to other scene entities. Local port: rapier's own // solve_character_collision_impulses panics with 2+ nearby dynamic bodies. solve_character_collision_impulses( &self.kinematic_controller, delta_time, &mut query_pipeline_mut, collider.shape(), update_ctx.entity_data.cells.as_mut().unwrap().mass(), &collisions, ); // update the entity's position let rb = ctx .rb_manager .physics_manager .world .bodies .get_mut(update_ctx.entity_data.rb_h.unwrap()) .unwrap(); rb.set_linvel(movement.translation / delta_time, true); self.grounded = movement.grounded; } } pub fn entity_wizard_def(position: Vec2) -> EntityDef { let mut kinematic_controller = KinematicCharacterController::default(); kinematic_controller.up = -Vec2::Y; kinematic_controller.autostep = Some(CharacterAutostep { max_height: CharacterLength::Absolute(4.0), min_width: CharacterLength::Absolute(8.0), include_dynamic_bodies: false, }); kinematic_controller.offset = CharacterLength::Absolute(0.1); kinematic_controller.normal_nudge_factor = 1.0e-3; // slightly greater than 45 since 45 deg piles are very common for any kind of sand or dirt etc kinematic_controller.max_slope_climb_angle = 48f32.to_radians(); EntityDef::kinematic_from_sprite( position, "assets/sprites/wizard", Some(Box::new(WizardEntityBehaviour { movement_input_x: 0.0, jump: 0.0, grounded: false, acc_vel: Vec2::ZERO, kinematic_controller, })), ) }