/
ulupapi
/
Race_game
Обзор
Документация
Войти
/
ulupapi
/
Race_game
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
scripts/FollowCamera.gd
191 строка
6 KB
ulupapi
Game
26 дек 2025, 19:34
26 дек 2025, 19:34
7574b6d
Код
Авторство
О чём код?
extends Node3D class_name FollowCamera # --- ССЫЛКИ --- @export var target_path: NodePath # узел машины (CarFree и т.п.) @export var camera_path: NodePath # FollowRig/GameCamera; можно оставить пустым var target: Node3D = null var cam: Camera3D = null # --- РЕЖИМ ВИДА --- @export var use_orthographic: bool = true @export var ortho_size: float = 75.0 @export var ortho_min: float = 20.0 @export var ortho_max: float = 220.0 # Для перспективы: @export var persp_height: float = 45.0 @export var persp_distance: float = 35.0 @export var tilt_deg: float = 70.0 # --- СГЛАЖИВАНИЕ --- @export var pos_lerp_speed: float = 6.0 # 1/с @export var rot_lerp_speed: float = 8.0 # 1/с @export var offset_topdown: Vector3 = Vector3(0.0, 60.0, 0.0) @export var keep_level: bool = true # --- ВНУТРЕННЕЕ --- var _boost_follow: float = 0.0 func _enter_tree() -> void: _ensure_action("cam_zoom_in", KEY_EQUAL) _ensure_action("cam_zoom_out", KEY_MINUS) _ensure_action("cam_reset", KEY_R) _ensure_action("cam_boost", KEY_F) func _ready() -> void: # цель if target_path != NodePath(""): target = get_node_or_null(target_path) as Node3D else: target = _find_first_node3d(get_tree().get_root()) if target == null: push_error("[FollowCamera] Target not set — укажи Target Path на машину.") # камера if camera_path != NodePath(""): cam = get_node_or_null(camera_path) as Camera3D else: cam = _find_first_camera3d(self) if cam == null: push_error("[FollowCamera] Camera3D not found — добавь GameCamera как ребёнка FollowRig.") else: cam.current = true cam.near = 0.05 cam.far = 10000.0 cam.cull_mask = 0xFFFFF if use_orthographic: cam.set_orthogonal(ortho_size, cam.near, cam.far) else: cam.set_perspective(70.0, cam.near, cam.far) if target != null: _snap_to_target() func _process(dt: float) -> void: if target == null or cam == null: return # хоткеи if Input.is_action_just_pressed("cam_reset"): _snap_to_target() if Input.is_action_just_pressed("cam_boost"): _boost_follow = 1.0 if Input.is_action_just_pressed("cam_zoom_in"): _zoom(+1) if Input.is_action_just_pressed("cam_zoom_out"): _zoom(-1) # целевая позиция var focus: Vector3 = target.global_transform.origin var desired_pos: Vector3 if use_orthographic: desired_pos = focus + offset_topdown else: var forward: Vector3 = (target.global_transform.basis * Vector3(0.0, 0.0, -1.0)).normalized() desired_pos = focus + Vector3(0.0, persp_height, 0.0) - forward * persp_distance # сглаживание позиции var speed: float = pos_lerp_speed if _boost_follow > 0.0: speed = pos_lerp_speed * 4.0 _boost_follow -= dt if _boost_follow < 0.0: _boost_follow = 0.0 var alpha_pos: float = clamp(speed * dt, 0.0, 1.0) global_position = global_position.lerp(desired_pos, alpha_pos) # поворот к цели var to_target: Vector3 = (focus - global_position).normalized() if to_target.length() > 0.0001: var basis_target: Basis = Basis.looking_at(to_target, Vector3.UP) var q_from: Quaternion = global_transform.basis.get_rotation_quaternion() var q_to: Quaternion = basis_target.get_rotation_quaternion() var q_new: Quaternion = q_from.slerp(q_to, clamp(rot_lerp_speed * dt, 0.0, 1.0)) var t: Transform3D = global_transform t.basis = Basis(q_new) global_transform = t # доп. наклон для перспективы if not use_orthographic: var e: Vector3 = rotation e.x = deg_to_rad(tilt_deg) rotation = e func _unhandled_input(event: InputEvent) -> void: # Зум колесом мыши if event is InputEventMouseButton: var mb: InputEventMouseButton = event as InputEventMouseButton if not mb.pressed: return if mb.button_index == MOUSE_BUTTON_WHEEL_UP: _zoom(+1) elif mb.button_index == MOUSE_BUTTON_WHEEL_DOWN: _zoom(-1) # =============================== УТИЛИТЫ =============================== func _zoom(dir: int) -> void: if cam == null: return if use_orthographic: var step: float = 6.0 var s: float = cam.size - float(dir) * step s = clamp(s, ortho_min, ortho_max) cam.size = s cam.set_orthogonal(cam.size, cam.near, cam.far) else: var dh: float = 4.0 * float(dir) var dd: float = 3.0 * float(dir) persp_height = clamp(persp_height - dh, 10.0, 120.0) persp_distance = clamp(persp_distance - dd, 10.0, 120.0) cam.set_perspective(70.0, cam.near, cam.far) func _snap_to_target() -> void: if target == null or cam == null: return var focus: Vector3 = target.global_transform.origin if use_orthographic: global_position = focus + offset_topdown cam.set_orthogonal(ortho_size, cam.near, cam.far) else: var forward: Vector3 = (target.global_transform.basis * Vector3(0.0, 0.0, -1.0)).normalized() global_position = focus + Vector3(0.0, persp_height, 0.0) - forward * persp_distance cam.set_perspective(70.0, cam.near, cam.far) # «смотрим» на цель безопасно _safe_look_at(focus) func _safe_look_at(focus: Vector3) -> void: var dir: Vector3 = focus - global_position if dir.length() < 0.0001: return dir = dir.normalized() var up: Vector3 = Vector3.UP if absf(dir.dot(up)) > 0.999: up = Vector3.FORWARD look_at(focus, up) func _find_first_node3d(n: Node) -> Node3D: if n is Node3D: return n as Node3D for ch in n.get_children(): var got: Node3D = _find_first_node3d(ch) if got != null: return got return null func _find_first_camera3d(n: Node) -> Camera3D: if n is Camera3D: return n as Camera3D for ch in n.get_children(): var c: Camera3D = _find_first_camera3d(ch) if c != null: return c return null func _ensure_action(action_id: String, keycode: int) -> void: if not InputMap.has_action(action_id): InputMap.add_action(action_id) var ev: InputEventKey = InputEventKey.new() # ЯВНЫЙ КАСТ int → Key (важно для Godot 4) ev.physical_keycode = (keycode as Key) InputMap.action_add_event(action_id, ev)