Skip to content

Script improvement #9

@VIRSY2007

Description

@VIRSY2007

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()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions