Carregamento em segundo plano¶
Ao trocar a cena atual do seu jogo (ex. ir para uma nova fase), você pode querer mostrar uma tela de carregamento com alguma indicação do progresso a ser feito. O principal método de carregamento (ResourceLoader::load
ou só load
em GDScript) bloqueia a thread, fazendo com que seu jogo pareça estar congelado e sem resposta enquanto o recurso está sendo carregado. Este documento discute a alternativa de usar a classe ResourceInteractiveLoader
para telas de carregamento mais suaves.
ResourceInteractiveLoader¶
A classe ResourceInteractiveLoader
permite que você carregue um recurso em etapas. A cada vez que o método poll
é chamado, uma nova etapa é carregada, e o controle é retornado ao chamador. Cada etapa é geralmente um sub-recurso que é carregado pelo recurso principal. Por exemplo, se você está carregando uma cena que tem 10 imagens, cada imagem será uma etapa.
Uso¶
O uso é geralmente o seguinte
Obtendo um ResourceInteractiveLoader¶
Ref<ResourceInteractiveLoader> ResourceLoader::load_interactive(String p_path);
Este método te dará um ResourceInteractiveLoader que você pode usar para gerenciar a operação de carregamento.
Polling¶
Error ResourceInteractiveLoader::poll();
Use este método para avançar o progresso do carregamento. Cada chamada para poll
carregará o próximo estágio do seu recurso. Lembre-se de que cada estágio é um recurso "atômico" inteiro, como uma imagem ou uma malha, portanto, serão necessários vários quadros para carregar.
Retorna OK
se não tem erros, ERR_FILE_EOF
quando o carregamento é concluído. Qualquer outro valor de retorno significa que houve um erro e o carregamento foi interrompido.
Progresso da carga (opcional)¶
Para consultar o andamento do carregamento, use os seguintes métodos:
int ResourceInteractiveLoader::get_stage_count() const;
int ResourceInteractiveLoader::get_stage() const;
get_stage_count
retorna o número total de estágios para carregar. get_stage
retorna o estágio atual que está sendo carregado.
Forçando a conclusão (opcional)¶
Error ResourceInteractiveLoader::wait();
Use este método se precisar carregar todo o recurso no quadro atual, sem mais etapas.
Obtenção de recurso¶
Ref<Resource> ResourceInteractiveLoader::get_resource();
Se tudo correr bem, use esse método para recuperar o recurso carregado.
Exemplo¶
Este exemplo demonstra como carregar uma nova cena. Considere-o no contexto do exemplo Singletons (Carregamento Automático).
Primeiro, configuramos algumas variáveis e inicializamos a current_scene
com a cena principal do jogo:
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)
A função goto_scene
é chamada do jogo quando a cena precisa ser trocada. Ele solicita um carregador interativo, e chama set_process(true)
para iniciar o polling do carregador na chamada de retorno _process
. Ele também inicia uma animação de "carregamento", que pode mostrar uma barra de progresso ou tela de carregamento.
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
é onde o carregador é pesquisado. poll
é chamado, e então lidamos com o valor de retorno dessa chamada. OK
significa continuar pesquisando, ERR_FILE_EOF
significa que o carregamento foi concluído, qualquer outra coisa significa que houve um erro. Observe também que pulamos um quadro (via wait_frames
, definido na função goto_scene
) para permitir que a tela de carregamento apareça.
Note how we use OS.get_ticks_msec
to control how long we block the
thread. Some stages might load fast, which means we might be able
to cram more than one call to poll
in one frame; some might take way
more than your value for time_max
, so keep in mind we won't have
precise control over the timings.
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
Some extra helper functions. update_progress
updates a progress bar,
or can also update a paused animation (the animation represents the
entire load process from beginning to end). set_new_scene
puts the
newly loaded scene on the tree. Because it's a scene being loaded,
instance()
needs to be called on the resource obtained from the
loader.
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)
Usando múltiplas threads¶
O ResourceInteractiveLoader pode ser usado em múltiplas threads. Umas coisinhas para ter em mente caso tente fazer isso:
Use a semaphore¶
While your thread waits for the main thread to request a new resource,
use a Semaphore
to sleep (instead of a busy loop or anything similar).
Not blocking main thread during the polling¶
If you have a mutex to allow calls from the main thread to your loader
class, don't lock the main thread while you call poll
on your loader class. When a
resource is done loading, it might require some resources from the
low-level APIs (VisualServer, etc), which might need to lock the main
thread to acquire them. This might cause a deadlock if the main thread
is waiting for your mutex while your thread is waiting to load a
resource.
Exemplo de classe¶
Você pode encontrar uma classe de exemplo para carregar recursos em threads aqui: resource_queue.gd
. O uso é o seguinte:
func start()
Chame depois de instanciar a classe para iniciar o thread.
func queue_resource(path, p_in_front = false)
Queue a resource. Use optional argument "p_in_front" to put it in front of the queue.
func cancel_resource(path)
Remove a resource from the queue, discarding any loading done.
func is_ready(path)
Retorna true
se um recurso estiver totalmente carregado e pronto para ser recuperado.
func get_progress(path)
Get the progress of a resource. Returns -1 if there was an error (for example if the
resource is not in the queue), or a number between 0.0 and 1.0 with the
progress of the load. Use mostly for cosmetic purposes (updating
progress bars, etc), use is_ready
to find out if a resource is
actually ready.
func get_resource(path)
Returns the fully loaded resource, or null
on error. If the resource is
not fully loaded (is_ready
returns false
), it will block your thread
and finish the load. If the resource is not on the queue, it will call
ResourceLoader::load
to load it normally and return it.
Exemplo:¶
# 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")
Nota: este código, em sua forma atual, não foi testado em cenários do mundo real. Se você tiver algum problema, peça ajuda em um dos canais da comunidade Godot.