Многоцельная камера

Проблема

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

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

Решение

В одиночной игре вы привыкли прикреплять камеру к игроку, чтобы она автоматически следила за ним. Мы не можем сделать это здесь, потому что у нас есть 2 (или более) игрока или другие объекты игры, которые мы хотим держать на экране в любое время.

Нам нужно, чтобы наша камера выполняла 3 вещи:

  1. Добавляла/удаляла любое количество целей.
  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

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

20 − 2 =

Прокрутить вверх