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