From 747d5a6239f15b3ce6c055a9638e4459ae832733 Mon Sep 17 00:00:00 2001 From: aibrash Date: Tue, 13 Oct 2026 21:57:26 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=9C=D1=83=D0=B2=D1=81=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Player/character_body_3d.gd | 316 +++++++++++++++++++++++++++++++++--- export_presets.cfg | 2 +- 2 files changed, 290 insertions(+), 28 deletions(-) diff --git a/Player/character_body_3d.gd b/Player/character_body_3d.gd index 4f81bf2..8e04477 100644 --- a/Player/character_body_3d.gd +++ b/Player/character_body_3d.gd @@ -1,23 +1,122 @@ extends CharacterBody3D -# Настройки скорости -const WALK_SPEED = 5.0 -const SPRINT_SPEED = 8.5 -const JUMP_VELOCITY = 4.5 +# --- Скорость и инерция (паркур-ощущение) --- +const WALK_SPEED = 6.0 +const SPRINT_SPEED = 13.0 +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 # --- Настройки тряски камеры (Head Bobbing) --- -const BOB_FREQ = 2.4 # Частота покачивания -const BOB_AMP = 0.08 # Амплитуда покачивания +const BOB_FREQ = 2.4 +const BOB_AMP = 0.06 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: 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: if event.is_action_pressed("ui_cancel"): @@ -27,20 +126,54 @@ func _unhandled_input(event: InputEvent) -> void: 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) - head.rotate_x(-event.relative.y * MOUSE_SENSITIVITY) + var zoom_factor := head.fov / base_fov + 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)) + # Подкат: бег (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: - # 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(): - 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. Прыжок на Пробел - if Input.is_key_pressed(KEY_SPACE) and is_on_floor(): - velocity.y = JUMP_VELOCITY + # 2. Прыжок + if jump_buffer_timer > 0.0 and coyote_timer > 0.0: + if not is_sliding or _can_stand(): + 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 if Input.is_key_pressed(KEY_SHIFT): current_speed = SPRINT_SPEED @@ -58,22 +191,151 @@ func _physics_process(delta: float) -> void: input_dir = input_dir.normalized() - # 5. Движение относительно взгляда персонажа + # 5. Движение с инерцией var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() - if direction: - velocity.x = direction.x * current_speed - 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) + var horizontal := Vector3(velocity.x, 0, velocity.z) + var has_input := direction != Vector3.ZERO - # 6. Эффект тряски / раскачивания камеры (Head Bobbing) - bob_time += delta * velocity.length() * float(is_on_floor()) - head.transform.origin = _head_bob(bob_time) + if is_sliding: + horizontal = _process_slide(delta, direction, horizontal) + 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() -# Функция расчета позиционирования камеры при шагах +# --- ПОДКАТ --- + +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: var pos := Vector3.ZERO pos.y = sin(time * BOB_FREQ) * BOB_AMP diff --git a/export_presets.cfg b/export_presets.cfg index fe62901..f04c64b 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -29,7 +29,7 @@ script_export_mode=2 custom_template/debug="" custom_template/release="" -variant/extensions_support=false +variant/extensions_support=true variant/thread_support=false vram_texture_compression/for_desktop=true vram_texture_compression/for_mobile=false