use glam::Vec2; use rapier2d::{ control::{CharacterAutostep, CharacterLength, KinematicCharacterController}, pipeline::QueryFilter, }; use crate::{ config::GRAVITY, input::Input, sim::{ entity::{EntityBehaviour, EntityDef, EntityUpdateCtx}, 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::Up) { 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 .query_pipeline_with_filter( 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(); // 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 movement = self.kinematic_controller.move_shape( delta_time, &query_pipeline, collider.shape(), rb.position(), self.acc_vel * delta_time, |_| {}, ); 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, })), ) }