Проблема
Вам нужна динамическая камера в игре на Godot, которая двигается и масштабируется, чтобы держать несколько объектов на экране одновременно.
Примером такого может быть игра для двух игроков, чтобы сохранять обоих игроков на экране по мере их удаления друг от друга и приближения, как показано на изображении:

Решение
В одиночной игре вы привыкли прикреплять камеру к игроку, чтобы она автоматически следила за ним. Мы не можем сделать это здесь, потому что у нас есть 2 (или более) игрока или другие объекты игры, которые мы хотим держать на экране в любое время.
Нам нужно, чтобы наша камера выполняла 3 вещи:
- Добавляла/удаляла любое количество целей.
- Сохраняла позицию камеры по центру между целями.
- Регулировала масштаб камеры, чтобы держать все цели на экране.
Создайте новую сцену с Camera2D и прикрепите к ней скрипт. Мы добавим эту камеру к нашей игре, когда закончим.
Давайте разберем, как работает этот скрипт. Вот как начинается скрипт:
extends Camera2D
@export var move_speed = 30 # camera position lerp speed
@export var zoom_speed = 3.0 # camera zoom lerp speed
@export var min_zoom = 5.0 # camera won't zoom closer than this
@export var max_zoom = 0.5 # camera won't zoom farther than this
@export var margin = Vector2(400, 200) # include some buffer area around targets
var targets = [] # Array of targets to be tracked.
@onready var screen_size = get_viewport_rect().size
Эти настройки позволяют вам настраивать поведение камеры. Мы будем использовать функцию lerp() для всех изменений камеры, поэтому установка низких значений для скоростей движения/масштабирования добавит некоторую задержку в изменении положении камеры.
Максимальные и минимальные значения масштаба также будут зависеть от размера объектов в вашей игре и того, насколько близко или далеко вы хотите приблизиться. Настройте по своему усмотрению.
Свойство margin добавит дополнительное пространство вокруг целей, чтобы они не оказывались прямо на краю видимой области.
Наконец, у нас есть массив целей и мы получаем размер экрана, чтобы правильно вычислить масштаб.
func add_target(t):
if not t in targets:
targets.append(t)
func remove_target(t):
if t in targets:
targets.erase(t)
Для добавления и удаления целей у нас есть две вспомогательные функции. Вы можете использовать их во время игры, чтобы изменять отслеживаемые цели («Игрок 3 вступил в игру!»). Обратите внимание, что мы не хотим отслеживать одну и ту же цель дважды, поэтому мы отклоняем её, если она уже присутствует.
Большая часть функционала происходит в _process(). Сначала двигаем камеру:
func _process(delta):
if !targets:
return
# Keep the camera centered between the targets
var p = Vector2.ZERO
for target in targets:
p += target.position
p /= targets.size()
position = lerp(position, p, move_speed * delta)
Здесь мы проходим по позициям целей и находим их общий центр. Используя lerp(), мы убеждаемся, что камера плавно перемещается туда.
Затем мы обработаем масштабирование:
# Find the zoom that will contain all targets
var r = Rect2(position, Vector2.ONE)
for target in targets:
r = r.expand(target.position)
r = r.grow_individual(margin.x, margin.y, margin.x, margin.y)
var z
if r.size.x > r.size.y * screen_size.aspect():
z = 1 / clamp(r.size.x / screen_size.x, min_zoom, max_zoom)
else:
z = 1 / clamp(r.size.y / screen_size.y, min_zoom, max_zoom)
zoom = lerp(zoom, Vector2.ONE * z, zoom_speed)
Основной функционал здесь поступает от Rect2. Мы хотим найти прямоугольник, охватывающий все цели, что мы можем получить с помощью метода expand(). Затем мы увеличиваем прямоугольник на величину margin.
Здесь вы можете видеть, как рисуется прямоугольник (нажмите «Tab» в демонстрационном проекте, чтобы включить это отображение):

Затем, в зависимости от того, является ли прямоугольник более широким или более высоким (относительно соотношения сторон экрана), мы находим масштаб и ограничиваем его в пределах установленного диапазона максимума/минимума.
Полный скрипт
extends Camera2D
@export var move_speed = 30 # camera position lerp speed
@export var zoom_speed = 3.0 # camera zoom lerp speed
@export var min_zoom = 5.0 # camera won't zoom closer than this
@export var max_zoom = 0.5 # camera won't zoom farther than this
@export var margin = Vector2(400, 200) # include some buffer area around targets
var targets = []
@onready var screen_size = get_viewport_rect().size
func _process(delta):
if !targets:
return
# Keep the camera centered among all targets
var p = Vector2.ZERO
for target in targets:
p += target.position
p /= targets.size()
position = lerp(position, p, move_speed * delta)
# Find the zoom that will contain all targets
var r = Rect2(position, Vector2.ONE)
for target in targets:
r = r.expand(target.position)
r = r.grow_individual(margin.x, margin.y, margin.x, margin.y)
var z
if r.size.x > r.size.y * screen_size.aspect():
z = 1 / clamp(r.size.x / screen_size.x, max_zoom, min_zoom)
else:
z = 1 / clamp(r.size.y / screen_size.y, max_zoom, min_zoom)
zoom = lerp(zoom, Vector2.ONE * z, zoom_speed * delta)
# For debug
get_parent().draw_cam_rect(r)
func add_target(t):
if not t in targets:
targets.append(t)
func remove_target(t):
if t in targets:
targets.remove(t)
Скачайте этот проект
Скачайте код проекта здесь: https://github.com/godotrecipes/multitarget_camera