后台加载
ResourceLoader::load 或 GDScript 中的 load)会阻塞线程,使你的游戏在资源加载时显得冻结和无响应。本文档讨论了使用 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 loadervar wait_framesvar time_max = 100 # msecvar current_scenefunc _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,然后我们处理该调用的返回值。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)
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 的社区频道中寻求帮助。
