Last active
August 31, 2025 20:25
-
-
Save TellonUK/eed3224e1b3890b1d57dcf1374a8293a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| extends CharacterBody3D | |
| # Movement constants - based on Quake/Half-Life physics | |
| const MAX_VELOCITY_AIR = 0.6 | |
| const MAX_VELOCITY_GROUND = 6.0 | |
| const MAX_ACCELERATION = 10 * MAX_VELOCITY_GROUND | |
| const STOP_SPEED = 1.5 | |
| const FRICTION = 8.0 # Increased for less sliding, more Quake-like stopping | |
| # Exported settings | |
| @export var jump_height: float = 1.0 # Multiplier for jump height calculation | |
| @export var bhop_frames: int = 3 # Frames of tolerance for bunny hopping | |
| @export var additive_bhop: bool = false # More forgiving bhop mode | |
| @export var sensitivity: float = 0.002 # Mouse sensitivity | |
| @export var joypad_sensitivity: float = 2.0 # Mouse sensitivity | |
| @export var joypad_deadzone: float = 0.1 # Deadzone for analog sticks | |
| # Walking behavior | |
| @export var walk_speed_multiplier: float = 0.5 # Half speed when walking | |
| @export var walk_threshold: float = 0.5 # Stick magnitude below this => walk | |
| @export var use_analog_scaling: bool = true # Scale speed by stick tilt (on top of walk) | |
| # Camera and input | |
| var mouse_motion := Vector2.ZERO | |
| @onready var camera_pivot: Node3D = $CameraPivot | |
| # Movement variables | |
| var direction = Vector3() | |
| var wish_jump = false | |
| var bhop_frame_count: int = 0 | |
| # Cached per-frame input strength (0..1) and walking flag | |
| var move_strength: float = 0.0 | |
| var is_walking: bool = false | |
| func _ready() -> void: | |
| Input.mouse_mode = Input.MOUSE_MODE_CAPTURED | |
| func _physics_process(delta: float) -> void: | |
| handle_camera_rotation(delta) | |
| process_input() | |
| process_movement(delta) | |
| func _input(event: InputEvent) -> void: | |
| if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: | |
| mouse_motion = -event.relative * sensitivity | |
| if event.is_action_pressed("ui_cancel"): | |
| Input.mouse_mode = Input.MOUSE_MODE_VISIBLE | |
| func handle_camera_rotation(delta: float) -> void: | |
| rotate_y(mouse_motion.x) | |
| camera_pivot.rotate_x(mouse_motion.y) | |
| camera_pivot.rotation_degrees.x = clampf( | |
| camera_pivot.rotation_degrees.x, -90.0, 90.0 | |
| ) | |
| mouse_motion = Vector2.ZERO | |
| var joypad_look = Vector2.ZERO | |
| joypad_look.x = Input.get_axis("look_left", "look_right") | |
| joypad_look.y = Input.get_axis("look_up", "look_down") | |
| if joypad_look.length() > joypad_deadzone: | |
| rotate_y(-joypad_look.x * joypad_sensitivity * delta) | |
| camera_pivot.rotate_x(-joypad_look.y * joypad_sensitivity * delta) | |
| func process_input(): | |
| # Read analog move vector (x = left/right, y = forward/back) | |
| var mv2 := Input.get_vector("move_left", "move_right", "move_forward", "move_back") | |
| # Apply deadzone | |
| if mv2.length() < joypad_deadzone: | |
| mv2 = Vector2.ZERO | |
| # Build world-space desired direction (don’t normalize yet; we need strength) | |
| direction = (transform.basis.z * mv2.y) + (transform.basis.x * mv2.x) | |
| # Store analog strength (0..1) | |
| move_strength = clamp(mv2.length(), 0.0, 1.0) | |
| # Keyboard jump button | |
| wish_jump = Input.is_action_just_pressed("jump") | |
| # Determine walking: | |
| # - if 'walk' is held on keyboard OR | |
| # - if stick is slightly pressed (below threshold but above deadzone) | |
| var walk_from_stick := move_strength > 0.0 and move_strength < walk_threshold | |
| is_walking = Input.is_action_pressed("walk") or walk_from_stick | |
| func process_movement(delta: float): | |
| # Handle basic jump first (like original script) | |
| if Input.is_action_just_pressed("jump") and is_on_floor(): | |
| velocity.y = sqrt(jump_height * 2.0 * abs(get_gravity().y)) | |
| # Apply gravity | |
| if not is_on_floor(): | |
| velocity += get_gravity() * delta | |
| # Normalized wish direction for acceleration math | |
| var wish_dir = direction.normalized() | |
| # Compute effective speeds/accel based on walking & analog strength | |
| var base_ground_speed := MAX_VELOCITY_GROUND | |
| var base_air_speed := MAX_VELOCITY_AIR | |
| var base_accel := MAX_ACCELERATION | |
| # Optional continuous analog scaling of top speed | |
| var analog_scale := move_strength if (use_analog_scaling and move_strength > 0.0) else 1.0 | |
| # Walk halves both speed and acceleration | |
| var walk_scale := walk_speed_multiplier if is_walking else 1.0 | |
| var eff_ground_speed := base_ground_speed * analog_scale * walk_scale | |
| var eff_air_speed := base_air_speed * analog_scale * walk_scale | |
| var eff_accel := base_accel * walk_scale # acceleration halves when walking | |
| # Handle bhop frame tolerance for advanced users | |
| if is_on_floor(): | |
| bhop_frame_count = 0 | |
| else: | |
| bhop_frame_count += 1 | |
| # Movement physics (pass effective max accel into accelerate) | |
| if is_on_floor(): | |
| # Special case: if jumping, apply air physics for bunny hop | |
| if wish_jump and bhop_frame_count == 0: | |
| velocity = update_velocity_air(wish_dir, eff_air_speed, eff_accel, delta) | |
| wish_jump = false | |
| else: | |
| velocity = update_velocity_ground(wish_dir, eff_ground_speed, eff_accel, delta) | |
| else: | |
| velocity = update_velocity_air(wish_dir, eff_air_speed, eff_accel, delta) | |
| # Move the player once velocity has been calculated | |
| move_and_slide() | |
| # Accelerate now takes max_accel as a parameter | |
| func accelerate(wish_dir: Vector3, max_velocity: float, max_accel: float, delta: float): | |
| # Get our current speed as a projection of velocity onto the wish_dir | |
| var current_speed = velocity.dot(wish_dir) | |
| # Amount to add is limited by remaining speed budget and accel cap | |
| var add_speed = clamp(max_velocity - current_speed, 0, max_accel * delta) | |
| # Apply acceleration | |
| var new_velocity = velocity + add_speed * wish_dir | |
| # Additive bhop mode: more forgiving, velocity converges to input direction | |
| if additive_bhop and not is_on_floor() and wish_dir.length() > 0: | |
| var horizontal_vel = Vector3(velocity.x, 0, velocity.z) | |
| var desired_vel = wish_dir * min(horizontal_vel.length() + add_speed, max_velocity * 5) | |
| new_velocity.x = lerp(velocity.x, desired_vel.x, delta * 8.0) | |
| new_velocity.z = lerp(velocity.z, desired_vel.z, delta * 8.0) | |
| return new_velocity | |
| # Pass effective speeds/accel down | |
| func update_velocity_ground(wish_dir: Vector3, eff_ground_speed: float, eff_accel: float, delta: float): | |
| # Apply more aggressive friction for less sliding | |
| var speed = velocity.length() | |
| if speed > 0.0: | |
| var control = max(STOP_SPEED, speed) | |
| var drop = control * FRICTION * delta | |
| # More aggressive friction application | |
| var new_speed = max(speed - drop, 0.0) | |
| velocity *= new_speed / speed | |
| # Additional stopping help when no input (like original script) | |
| if wish_dir.length() == 0: | |
| var horizontal_vel = Vector3(velocity.x, 0, velocity.z) | |
| var decel_rate = FRICTION * 2.0 * delta # Extra deceleration | |
| velocity.x = move_toward(velocity.x, 0, decel_rate * horizontal_vel.length()) | |
| velocity.z = move_toward(velocity.z, 0, decel_rate * horizontal_vel.length()) | |
| return accelerate(wish_dir, eff_ground_speed, eff_accel, delta) | |
| func update_velocity_air(wish_dir: Vector3, eff_air_speed: float, eff_accel: float, delta: float): | |
| # Do not apply any friction in the air | |
| return accelerate(wish_dir, eff_air_speed, eff_accel, delta) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment