-
Notifications
You must be signed in to change notification settings - Fork 103
Description
Author, please don't delete it, I'm a Russian user and I rewrote the script if someone can't use it:
Sour Grape 42 Controller
Controls:
WASD - movement (CAN move while jumping!)
Shift - sprint
Ctrl - crouch (shrinks model and collider)
Space - jump (CAN move while jumping)
ESC - release mouse
LMB - capture mouse
extends CharacterBody3D
Speeds
@export var walk_speed : float = 7.0
@export var sprint_speed : float = 12.0
@export var crouch_speed : float = 3.5
@export var jump_force : float = 4.5
@export var mouse_sensitivity : float = 0.002
Crouch settings
@export var crouch_height : float = -0.5 # how much the camera lowers GLOBALLY
@export var crouch_scale : float = 0.5 # collider/mesh scale when crouching
@export var crouch_smoothness : float = 8.0 # smoothness of crouching
Node references (MUST HAVE EXACT NAMES!)
@onready var head: Node3D = $Head
@onready var collider: CollisionShape3D = $Collider
@onready var camera: Camera3D = $Head/Camera3D
@onready var mesh: MeshInstance3D = $Mesh
State variables
var mouse_captured : bool = false
var look_rotation : Vector2
var is_crouching : bool = false
var normal_head_height : float # remember where head is
var target_head_height : float
var normal_scale : float = 1.0
var target_scale : float = 1.0
func _ready():
# Check if all nodes exist
if not mesh:
push_error("NO MESH! Make sure ProtoController has a child node named 'Mesh'")
return
if not collider:
push_error("NO COLLIDER! Make sure ProtoController has a child node named 'Collider'")
return
# IMPORTANT: Camera starts at 0.0 relative to Head
# Make sure that's true
camera.position = Vector3.ZERO
# Remember normal head height (global Y position)
normal_head_height = head.position.y
target_head_height = normal_head_height
# Remember normal scale
normal_scale = mesh.scale.x
target_scale = normal_scale
func _unhandled_input(event):
# Capture mouse on LMB click
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
mouse_captured = true
# Release mouse on ESC
if Input.is_key_pressed(KEY_ESCAPE):
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
mouse_captured = false
# Look around with mouse
if mouse_captured and event is InputEventMouseMotion:
look_rotation.x -= event.relative.y * mouse_sensitivity
look_rotation.x = clamp(look_rotation.x, deg_to_rad(-85), deg_to_rad(85))
look_rotation.y -= event.relative.x * mouse_sensitivity
# Rotate body and head
rotation.y = look_rotation.y
head.rotation.x = look_rotation.x
func _input(event):
# Handle Ctrl release
if event is InputEventKey:
if event.keycode == KEY_CTRL and not event.pressed:
# Check if there's space to stand up
var space = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(head.global_position, head.global_position + Vector3.UP * 2)
var result = space.intersect_ray(query)
if not result: # If nothing above - stand up
is_crouching = false
target_head_height = normal_head_height
target_scale = normal_scale
func _physics_process(delta):
# Safety check for missing nodes
if not mesh or not collider:
return
# === CROUCHING (Ctrl) ===
if Input.is_key_pressed(KEY_CTRL):
is_crouching = true
target_head_height = normal_head_height + crouch_height
target_scale = crouch_scale
# Smoothly move HEAD down/up (GLOBAL movement)
head.position.y = move_toward(head.position.y, target_head_height, crouch_smoothness * delta)
# Smoothly scale mesh and collider (scale all axes X, Y, Z equally)
var new_scale = move_toward(mesh.scale.x, target_scale, crouch_smoothness * delta)
mesh.scale = Vector3(new_scale, new_scale, new_scale)
collider.scale = Vector3(new_scale, new_scale, new_scale)
# IMPORTANT: Camera is ALWAYS at 0.0 relative to head!
# Don't move camera separately, it follows the head
camera.position = Vector3.ZERO
# === GRAVITY ===
if not is_on_floor():
velocity += get_gravity() * delta
# === JUMP (space, only if not crouching) ===
if Input.is_key_pressed(KEY_SPACE) and is_on_floor() and not is_crouching:
velocity.y = jump_force
# === DETERMINE SPEED (can always move now) ===
var current_speed = walk_speed
# If Shift held and not crouching - sprint (EVEN IN AIR!)
if Input.is_key_pressed(KEY_SHIFT) and not is_crouching:
current_speed = sprint_speed
# If crouching - use crouch speed (EVEN IN AIR!)
if is_crouching:
current_speed = crouch_speed
# === WASD MOVEMENT (ALWAYS, even in air) ===
var direction = Vector3.ZERO
if Input.is_key_pressed(KEY_W):
direction -= transform.basis.z # forward
if Input.is_key_pressed(KEY_S):
direction += transform.basis.z # backward
if Input.is_key_pressed(KEY_A):
direction -= transform.basis.x # left
if Input.is_key_pressed(KEY_D):
direction += transform.basis.x # right
direction = direction.normalized()
# Apply speed
if direction:
velocity.x = direction.x * current_speed
velocity.z = direction.z * current_speed
else:
# Smooth stop
velocity.x = move_toward(velocity.x, 0, walk_speed)
velocity.z = move_toward(velocity.z, 0, walk_speed)
# Move character
move_and_slide()