Улучшил Мувсет
This commit is contained in:
+289
-27
@@ -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:
|
||||||
velocity.y = JUMP_VELOCITY
|
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
|
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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user