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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions