Тло завантаження

При зміні головної сцену вашої гри (наприклад, при переході на новий рівень), ви можете показати екран завантаження з відображенням прогресу завантаження. Метод головного завантаження (ResourceLoader::load, або просто load з GDScript) блокує ваші введення, що заморожує вашу гру та не дозволяє їй відповідати під час завантаження ресурсу. У цьому документі розглядається альтернативне використання класу ResourceInteractiveLoader для більш плавних екранів завантаження.

ResourceInteractiveLoader

Клас ResourceInteractiveLoader дозволяє завантажувати ресурс поетапно. Кожен раз, коли викликається метод poll, завантажується новий етап, і управління повертається абоненту. Кожен етап, як правило, є під-ресурсом, який завантажується основним ресурсом. Наприклад, якщо ви завантажуєте сцену, яка завантажує 10 зображень, кожне зображення буде одним етапом.

Використання

Використання, як правило, наступне

Отримання ResourceInteractiveLoader

Ref<ResourceInteractiveLoader> ResourceLoader::load_interactive(String p_path);

Цей метод дасть вам ResourceInteractiveLoader, який ви будете використовувати для управління операцією завантаження.

Опитування

Error ResourceInteractiveLoader::poll();

Використовуйте цей метод, щоб просунути прогрес навантаження. Кожен виклик poll завантажить наступний етап вашого ресурсу. Майте на увазі, що кожен етап є одним цілим "атомним" ресурсом, таким, як зображення, або меш, тому для завантаження знадобиться кілька кадрів.

За відсутності помилок повертається OK, при завершенні завантаження - ERR_FILE_EOF. Будь-яке інше значення, буде означати, що сталася помилка, і завантаження припинилося.

Прогрес завантаження (необов'язково)

Для відображення прогресу завантаження використовуйте такі методи:

int ResourceInteractiveLoader::get_stage_count() const;
int ResourceInteractiveLoader::get_stage() const;

get_stage_count повертає загальну кількість етапів для завантаження. get_stage повертає поточний етап, який завантажується.

Примусове завершення (необов'язково)

Error ResourceInteractiveLoader::wait();

Використовуйте цей метод, якщо вам потрібно завантажити весь ресурс в поточному кадрі, без будь-яких додаткових кроків.

Отримання ресурсу

Ref<Resource> ResourceInteractiveLoader::get_resource();

Якщо все йде добре, використовуйте цей метод, щоб отримати завантажений ресурс.

Приклад

У цьому прикладі показано, як завантажити нову сцену. Розглянемо його в контексті прикладу Синглтони (Автозавантаження).

Спочатку налаштовуємо деякі змінні і ініціалізуємо current_scene з головною сценою гри:

var loader
var wait_frames
var time_max = 100 # msec
var current_scene


func _ready():
    var root = get_tree().get_root()
    current_scene = root.get_child(root.get_child_count() -1)

Функція goto_scene викликається з гри, коли сцена повинна бути змінена. Вона запитує інтерактивний завантажувач і викликає set_process(true), щоб почати опитування завантажувача в _process. Вона також запускає "завантаження" анімації, яка може показати індикатор прогресу, або екран завантаження.

func goto_scene(path): # Game requests to switch to this scene.
    loader = ResourceLoader.load_interactive(path)
    if loader == null: # Check for errors.
        show_error()
        return
    set_process(true)

    current_scene.queue_free() # Get rid of the old scene.

    # Start your "loading..." animation.
    get_node("animation").play("loading")

    wait_frames = 1

_process це місце, звідки завантажувач викликає функції poll. Після виклику poll нам повертається значення цього виклику. OK означає продовження викликів, ERR_FILE_EOF означає, що завантаження завершено, все інше означає, що сталася помилка. Також зверніть увагу, що ми пропускаємо один кадр (через wait_frames, встановлений у функції goto_scene), щоб міг появитися екран завантаження.

Зверніть увагу, як ми використовуємо OS.get_ticks_msec, щоб контролювати, як довго ми блокуємо введення. Деякі етапи можуть швидко завантажуватися, а це означає, що ми можемо втиснути більше одного виклику poll в один кадр; завантаження інших може зайняти набагато більше часу, ніж ваше значення time_max, тому майте на увазі, що ми не матимемо точного контролю над часом завантаження.

func _process(time):
    if loader == null:
        # no need to process anymore
        set_process(false)
        return

    # Wait for frames to let the "loading" animation show up.
    if wait_frames > 0:
        wait_frames -= 1
        return

    var t = OS.get_ticks_msec()
    # Use "time_max" to control for how long we block this thread.
    while OS.get_ticks_msec() < t + time_max:
        # Poll your loader.
        var err = loader.poll()

        if err == ERR_FILE_EOF: # Finished loading.
            var resource = loader.get_resource()
            loader = null
            set_new_scene(resource)
            break
        elif err == OK:
            update_progress()
        else: # Error during loading.
            show_error()
            loader = null
            break

Деякі додаткові допоміжні функції. update_progress оновлює індикатор прогресу, або призупинену анімацію (анімація відображає весь процес завантаження від початку до кінця). set_new_scene ставить нещодавно завантажену сцену на дерево. Оскільки завантажується сцена, instance() потрібно викликати на ресурсі, отриманому від завантажувача.

func update_progress():
    var progress = float(loader.get_stage()) / loader.get_stage_count()
    # Update your progress bar?
    get_node("progress").set_progress(progress)

    # ...or update a progress animation?
    var length = get_node("animation").get_current_animation_length()

    # Call this on a paused animation. Use "true" as the second argument to
    # force the animation to update.
    get_node("animation").seek(progress * length, true)


func set_new_scene(scene_resource):
    current_scene = scene_resource.instance()
    get_node("/root").add_child(current_scene)

Використання кількох потоків

ResourceInteractiveLoader можна використовувати в декількох потоках. Слід пам'ятати кілька речей, якщо ви спробуєте це зробити:

Використовуйте семафор

Поки ваш потік чекає, поки головний потік запросить новий ресурс, використовуйте Semaphore, щоб приспати його (замість напруженого циклу, або чогось подібного).

Не блокуйте головний потік під час опитування

Якщо у вас є мютекс, щоб дозволити виклики з головного потоку до вашого класу завантажувача, не блокуйте головний потік під час виклику poll у класі завантажувача. При завантаженні ресурсу йому можуть знадобитися деякі ресурси з API низького рівня (VisualServer тощо), яким може знадобитися заблокувати головний потік, щоб придбати їх. Це може призвести до взаємоблокування, якщо головний потік чекає вашого мютекса, поки ваш потік чекає завантаження ресурсу.

Зразок класу

Ви можете знайти зразок класу для завантаження ресурсів у потоках тут: resource_queue.gd. Використання виглядає наступним чином:

func start()

Викликайте після вставки класу, щоб запустити потік.

func queue_resource(path, p_in_front = false)

Черга ресурсу. Використовуйте необов'язковий аргумент "p_in_front", щоб поставити його перед чергою.

func cancel_resource(path)

Видаліть ресурс з черги, відкинувши будь-яке виконане завантаження.

func is_ready(path)

Повертає true, якщо ресурс повністю завантажено та готово до отримання.

func get_progress(path)

Отримайте прогрес ресурсу. Повертає -1, якщо сталася помилка (наприклад, якщо ресурсу немає в черзі), або число від 0,0 до 1,0 по мірі завантаження. Використовуйте в основному в косметичних цілях (оновлення індикаторів прогресу тощо), використовуйте is_ready, щоб з'ясувати, чи дійсно ресурс готовий.

func get_resource(path)

Повертає повністю завантажений ресурс, або помилку null. Якщо ресурс не повністю завантажений (is_ready повертає false), він заблокує ваш потік і завершить завантаження. Якщо ресурсу немає в черзі, він викличе ResourceLoader::load, щоб завантажити його нормально і повернути.

Приклад:

# Initialize.
queue = preload("res://resource_queue.gd").new()
queue.start()

# Suppose your game starts with a 10 second cutscene, during which the user
# can't interact with the game.
# For that time, we know they won't use the pause menu, so we can queue it
# to load during the cutscene:
queue.queue_resource("res://pause_menu.tres")
start_cutscene()

# Later, when the user presses the pause button for the first time:
pause_menu = queue.get_resource("res://pause_menu.tres").instance()
pause_menu.show()

# When you need a new scene:
queue.queue_resource("res://level_1.tscn", true)
# Use "true" as the second argument to put it at the front of the queue,
# pausing the load of any other resource.

# To check progress.
if queue.is_ready("res://level_1.tscn"):
    show_new_level(queue.get_resource("res://level_1.tscn"))
else:
    update_progress(queue.get_progress("res://level_1.tscn"))

# When the user walks away from the trigger zone in your Metroidvania game:
queue.cancel_resource("res://zone_2.tscn")

Примітка: цей код, в його нинішньому вигляді, не тестувався в реальних сценаріях. Якщо ви зіткнулися з будь-якими проблемами, попросіть допомоги в одному з `каналів спільноти Godot<https://godotengine.org/community>`__.