Улучшил Мувсет

This commit is contained in:
2026-10-13 21:57:26 +05:00
parent 1faa641bc9
commit 747d5a6239
2 changed files with 290 additions and 28 deletions
+288 -26
View File
@@ -1,23 +1,122 @@
extends CharacterBody3D extends CharacterBody3D
# Настройки скорости # --- Скорость и инерция (паркур-ощущение) ---
const WALK_SPEED = 5.0 const WALK_SPEED = 6.0
const SPRINT_SPEED = 8.5 const SPRINT_SPEED = 13.0
const JUMP_VELOCITY = 4.5 const GROUND_ACCEL = 55.0 # Разгон на земле
const GROUND_FRICTION = 45.0 # Торможение на земле без ввода
const OVERSPEED_DECEL = 14.0 # Как быстро гасится "лишняя" скорость (после слайда/прыжка) при беге
const AIR_ACCEL = 18.0 # Управление в воздухе
const MAX_SPEED = 22.0 # Потолок скорости
# --- Прыжок ---
const JUMP_VELOCITY = 6.0
const FALL_GRAVITY_MULT = 1.4 # Быстрее падаем — прыжок «злее»
const JUMP_CUT_GRAVITY_MULT = 2.5 # Отпустил пробел — прыжок ниже
const COYOTE_TIME = 0.12 # Можно прыгнуть сразу после схода с края
const JUMP_BUFFER_TIME = 0.12 # Нажал пробел чуть раньше приземления — прыжок сработает
const SLIDE_JUMP_BOOST = 1.15 # Бонус скорости при прыжке из слайда
# --- Подкат ---
const SLIDE_START_SPEED = 15.0 # Минимальная стартовая скорость слайда
const SLIDE_START_MULT = 1.25 # Множитель от скорости бега
const SLIDE_FRICTION = 6.0 # Чем меньше — тем дальше слайд
const SLIDE_END_SPEED = 6.5 # При какой скорости слайд заканчивается
const SLIDE_STEER = 1.8 # Насколько можно рулить во время слайда
const SLIDE_COOLDOWN = 0.3
const SLIDE_CAMERA_DROP = 0.6
const SLIDE_HEIGHT_FACTOR = 0.5
const CROUCH_SPEED = 2.5 # Ползём, если над головой низкий потолок
const MIN_SLIDE_SPEED = 7.0 # Минимальная скорость для начала слайда
# --- Камера ---
const ZOOM_FOV = 35.0
const ZOOM_SPEED = 10.0
const SPRINT_FOV_BONUS = 7.0 # Расширение FOV на скорости
const SLIDE_FOV_BONUS = 13.0
const STRAFE_ROLL = 3.0 # Наклон камеры при стрейфе (градусы)
# --- Виньетка (затемнение по краям) ---
const ZOOM_VIGNETTE = 0.85
const SLIDE_VIGNETTE = 0.45
const SPRINT_VIGNETTE = 0.2
const VIGNETTE_SPEED = 8.0
const VIGNETTE_SHADER = """
shader_type canvas_item;
uniform float intensity : hint_range(0.0, 1.0) = 0.0;
void fragment() {
float d = distance(UV, vec2(0.5));
float v = smoothstep(0.25, 0.75, d);
COLOR = vec4(0.0, 0.0, 0.0, v * intensity);
}
"""
# Настройки чувствительности мыши # Настройки чувствительности мыши
@export var MOUSE_SENSITIVITY: float = 0.003 @export var MOUSE_SENSITIVITY: float = 0.003
# --- Настройки тряски камеры (Head Bobbing) --- # --- Настройки тряски камеры (Head Bobbing) ---
const BOB_FREQ = 2.4 # Частота покачивания const BOB_FREQ = 2.4
const BOB_AMP = 0.08 # Амплитуда покачивания const BOB_AMP = 0.06
var bob_time: float = 0.0 var bob_time: float = 0.0
# Ссылки на узлы # Состояние подката
@onready var head: Node3D = $Camera3D var is_sliding: bool = false
var slide_cooldown_timer: float = 0.0
var slide_dir: Vector3 = Vector3.ZERO
var slide_speed: float = 0.0
# Прыжок
var coyote_timer: float = 0.0
var jump_buffer_timer: float = 0.0
var space_was_down: bool = false
# Камера
var head_base_pos: Vector3 = Vector3.ZERO
var head_offset_y: float = 0.0
var base_fov: float = 75.0
var is_zooming: bool = false
# Виньетка
var vignette_material: ShaderMaterial = null
var vignette_value: float = 0.0
# Коллайдер (опционально, ищется по имени CollisionShape3D)
var collision_shape: CollisionShape3D = null
var capsule: CapsuleShape3D = null
var default_capsule_height: float = 0.0
var default_collision_y: float = 0.0
@onready var head: Camera3D = $Camera3D
func _ready() -> void: func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
head_base_pos = head.position
base_fov = head.fov
collision_shape = get_node_or_null("CollisionShape3D")
if collision_shape and collision_shape.shape is CapsuleShape3D:
capsule = collision_shape.shape
default_capsule_height = capsule.height
default_collision_y = collision_shape.position.y
_setup_vignette()
func _setup_vignette() -> void:
var layer := CanvasLayer.new()
layer.layer = 10
add_child(layer)
var rect := ColorRect.new()
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE
layer.add_child(rect)
rect.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var shader := Shader.new()
shader.code = VIGNETTE_SHADER
vignette_material = ShaderMaterial.new()
vignette_material.shader = shader
rect.material = vignette_material
func _unhandled_input(event: InputEvent) -> void: func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_cancel"): if event.is_action_pressed("ui_cancel"):
@@ -27,20 +126,54 @@ func _unhandled_input(event: InputEvent) -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
rotate_y(-event.relative.x * MOUSE_SENSITIVITY) var zoom_factor := head.fov / base_fov
head.rotate_x(-event.relative.y * MOUSE_SENSITIVITY) rotate_y(-event.relative.x * MOUSE_SENSITIVITY * zoom_factor)
head.rotate_x(-event.relative.y * MOUSE_SENSITIVITY * zoom_factor)
head.rotation.x = clamp(head.rotation.x, deg_to_rad(-80), deg_to_rad(80)) head.rotation.x = clamp(head.rotation.x, deg_to_rad(-80), deg_to_rad(80))
# Подкат: бег (Shift) + C
if event is InputEventKey and event.pressed and not event.echo and event.keycode == KEY_C:
_try_start_slide()
func _physics_process(delta: float) -> void: func _physics_process(delta: float) -> void:
# 1. Гравитация slide_cooldown_timer = max(slide_cooldown_timer - delta, 0.0)
var space_down := Input.is_key_pressed(KEY_SPACE)
# --- Таймеры прыжка (coyote time + jump buffer) ---
if is_on_floor():
coyote_timer = COYOTE_TIME
else:
coyote_timer = max(coyote_timer - delta, 0.0)
if space_down and not space_was_down:
jump_buffer_timer = JUMP_BUFFER_TIME
else:
jump_buffer_timer = max(jump_buffer_timer - delta, 0.0)
space_was_down = space_down
# 1. Гравитация (падение быстрее, короткий прыжок при отпускании пробела)
if not is_on_floor(): if not is_on_floor():
velocity += get_gravity() * delta var g := get_gravity()
if velocity.y < 0.0:
g *= FALL_GRAVITY_MULT
elif not space_down:
g *= JUMP_CUT_GRAVITY_MULT
velocity += g * delta
# 2. Прыжок на Пробел # 2. Прыжок
if Input.is_key_pressed(KEY_SPACE) and is_on_floor(): if jump_buffer_timer > 0.0 and coyote_timer > 0.0:
if not is_sliding or _can_stand():
velocity.y = JUMP_VELOCITY velocity.y = JUMP_VELOCITY
jump_buffer_timer = 0.0
coyote_timer = 0.0
if is_sliding:
_end_slide()
# Прыжок из слайда сохраняет и усиливает скорость
velocity.x *= SLIDE_JUMP_BOOST
velocity.z *= SLIDE_JUMP_BOOST
# 3. Определение скорости (Спринт) # 3. Скорость (Спринт)
var current_speed := WALK_SPEED var current_speed := WALK_SPEED
if Input.is_key_pressed(KEY_SHIFT): if Input.is_key_pressed(KEY_SHIFT):
current_speed = SPRINT_SPEED current_speed = SPRINT_SPEED
@@ -58,22 +191,151 @@ func _physics_process(delta: float) -> void:
input_dir = input_dir.normalized() input_dir = input_dir.normalized()
# 5. Движение относительно взгляда персонажа # 5. Движение с инерцией
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction: var horizontal := Vector3(velocity.x, 0, velocity.z)
velocity.x = direction.x * current_speed var has_input := direction != Vector3.ZERO
velocity.z = direction.z * current_speed
else:
velocity.x = move_toward(velocity.x, 0, current_speed)
velocity.z = move_toward(velocity.z, 0, current_speed)
# 6. Эффект тряски / раскачивания камеры (Head Bobbing) if is_sliding:
bob_time += delta * velocity.length() * float(is_on_floor()) horizontal = _process_slide(delta, direction, horizontal)
head.transform.origin = _head_bob(bob_time) elif is_on_floor():
var target := direction * current_speed
var rate := GROUND_ACCEL
if not has_input:
rate = GROUND_FRICTION
elif horizontal.length() > current_speed:
rate = OVERSPEED_DECEL # плавно гасим лишнюю скорость, не рубим резко
horizontal = horizontal.move_toward(target, rate * delta)
else:
# В воздухе импульс сохраняется, можно только подруливать
if has_input:
var speed_cap: float = max(horizontal.length(), current_speed)
horizontal += direction * AIR_ACCEL * delta
if horizontal.length() > speed_cap:
horizontal = horizontal.normalized() * speed_cap
if horizontal.length() > MAX_SPEED:
horizontal = horizontal.normalized() * MAX_SPEED
velocity.x = horizontal.x
velocity.z = horizontal.z
# 6. Камера
_update_camera(delta, input_dir)
move_and_slide() move_and_slide()
# Функция расчета позиционирования камеры при шагах # --- ПОДКАТ ---
func _try_start_slide() -> void:
if is_sliding or slide_cooldown_timer > 0.0:
return
if not is_on_floor():
return
if not Input.is_key_pressed(KEY_SHIFT):
return
var horizontal := Vector3(velocity.x, 0, velocity.z)
if horizontal.length() < MIN_SLIDE_SPEED:
return
is_sliding = true
slide_dir = horizontal.normalized()
slide_speed = clamp(max(horizontal.length() * SLIDE_START_MULT, SLIDE_START_SPEED), 0.0, MAX_SPEED)
_set_collider_crouched(true)
func _process_slide(delta: float, direction: Vector3, horizontal: Vector3) -> Vector3:
# Сорвались с края во время слайда — выходим, если есть место
if not is_on_floor() and _can_stand():
_end_slide()
return horizontal
if slide_speed > SLIDE_END_SPEED:
# Лёгкое рулёжка во время слайда
if direction != Vector3.ZERO:
var new_dir := slide_dir.lerp(direction, SLIDE_STEER * delta)
if new_dir.length() > 0.01:
slide_dir = new_dir.normalized()
slide_speed = move_toward(slide_speed, 0.0, SLIDE_FRICTION * delta)
return slide_dir * slide_speed
# Слайд выдохся
if _can_stand():
_end_slide()
return slide_dir * slide_speed
# Под низким потолком — ползём, пока не выберемся
if direction != Vector3.ZERO:
return direction * CROUCH_SPEED
return horizontal.move_toward(Vector3.ZERO, CROUCH_SPEED)
func _end_slide() -> void:
is_sliding = false
slide_cooldown_timer = SLIDE_COOLDOWN
_set_collider_crouched(false)
func _set_collider_crouched(crouched: bool) -> void:
if capsule == null:
return
if crouched:
var new_height := default_capsule_height * SLIDE_HEIGHT_FACTOR
capsule.height = new_height
collision_shape.position.y = default_collision_y - (default_capsule_height - new_height) * 0.5
else:
capsule.height = default_capsule_height
collision_shape.position.y = default_collision_y
func _can_stand() -> bool:
if capsule == null:
return true
var extra := default_capsule_height * (1.0 - SLIDE_HEIGHT_FACTOR)
return not test_move(global_transform, Vector3.UP * extra)
# --- КАМЕРА И ЭФФЕКТЫ ---
func _update_camera(delta: float, input_dir: Vector2) -> void:
var h_speed := Vector2(velocity.x, velocity.z).length()
var sprint_factor: float = clamp((h_speed - WALK_SPEED) / (SPRINT_SPEED - WALK_SPEED), 0.0, 1.0)
# Покачивание только при обычном беге/ходьбе
if is_sliding or not is_on_floor():
bob_time = 0.0
else:
bob_time += delta * h_speed
var bob := Vector3.ZERO if (is_sliding or not is_on_floor()) else _head_bob(bob_time)
# Опускание камеры при слайде
var target_offset := -SLIDE_CAMERA_DROP if is_sliding else 0.0
head_offset_y = lerp(head_offset_y, target_offset, 12.0 * delta)
head.position = head_base_pos + bob + Vector3(0, head_offset_y, 0)
# Наклон камеры при стрейфе
var target_roll := -input_dir.x * deg_to_rad(STRAFE_ROLL) * sprint_factor
head.rotation.z = lerp(head.rotation.z, target_roll, 8.0 * delta)
# Зум на ЛКМ
is_zooming = Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) \
and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED
# FOV: зум > слайд > скорость бега
var target_fov := base_fov + sprint_factor * SPRINT_FOV_BONUS
if is_zooming:
target_fov = ZOOM_FOV
elif is_sliding:
target_fov = base_fov + SLIDE_FOV_BONUS
head.fov = lerp(head.fov, target_fov, ZOOM_SPEED * delta)
# Виньетка: зум > слайд > бег
var target_vignette := sprint_factor * SPRINT_VIGNETTE
if is_zooming:
target_vignette = ZOOM_VIGNETTE
elif is_sliding:
target_vignette = SLIDE_VIGNETTE
vignette_value = lerp(vignette_value, target_vignette, VIGNETTE_SPEED * delta)
if vignette_material:
vignette_material.set_shader_parameter("intensity", vignette_value)
func _head_bob(time: float) -> Vector3: func _head_bob(time: float) -> Vector3:
var pos := Vector3.ZERO var pos := Vector3.ZERO
pos.y = sin(time * BOB_FREQ) * BOB_AMP pos.y = sin(time * BOB_FREQ) * BOB_AMP
+1 -1
View File
@@ -29,7 +29,7 @@ script_export_mode=2
custom_template/debug="" custom_template/debug=""
custom_template/release="" custom_template/release=""
variant/extensions_support=false variant/extensions_support=true
variant/thread_support=false variant/thread_support=false
vram_texture_compression/for_desktop=true vram_texture_compression/for_desktop=true
vram_texture_compression/for_mobile=false vram_texture_compression/for_mobile=false