Background loading - seraph526/godot-se GitHub Wiki
当切换游戏的主场景时(如开启一个新的关卡),你想要显示一个加载画面,并提示加载的进度。主要的加载方法(gdscript中的"ResourceLoader::load"或"load")当资源加载时会冻结你的进程,这不是很好。这个文档讨论了"ResourceInteractiveLoade"类,更平滑的加载方式。
"ResourceLoaderInteractive"类允许你在场景中加载一个资源。每一次方法"poll"被调用时,一个新的场景被加载时,和control被返回给调用对象时。每个场景都是加载它的场景的一个子资源。如你要加载一个场景,场景上加载10张图片,每张图片都要在场景上。
有法如下:
Ref<ResourceInteractiveLoader> ResourceLoader::load_interactive(String p_path);
这一方法使用ResourceLoaderInteractive管理你的加载操作。
Error ResourceLoaderInteractive::poll();
通过这个方法使用更高级的加载办法。每个"poll"调用都会加载你资源的下一个场景,注意每个场景都是一个"atomic"资源,如图片,面片,所以需要几帧来加载。
返回"OK"表示没有错误,加载完成时返回"ERR_FILE_EOF"。其他的返回值说明存在错误,加载停止了。
要得到加载进度,可使用下面的方法:
int ResourceLoaderInteractive::get_stage_count() const;
int ResourceLoaderInteractive::get_stage() const;
get_stage_count
返回加载场景的总数量
get_stage
返回当前被加载的场景
Error ResourceLoaderInteractive::wait();
如果想要加载在当前帧完成,请使用此方法,不需要更多的步骤。
Ref<Resource> ResourceLoaderInteractive::get_resource();
如果所有的东西都正常运行,使用此方法检索你加载的资源。
引示例描述了如何加载一个新的场景。请参考Scene Switcher示例的内容。
首先,设置一些变量,在游戏的主场景中初始化"current_scene"
var loader
var wait_frames
var time_max = 100 # msec
var current_scene
func _ready():
var root = get_scene().get_root()
current_scene = root.get_child( root.get_child_count() -1 )
函数"goto_scene"在场景需要切换时被调用。需要一个"interactive loader",并调用"set_progress(true)"在"_progress"中开始轮询loader。同时开始播放加载动画,可以显示一个进度条或一个加载画面等。
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_secne.queue_free() # get rid of the old scene
# start your "loading..." animation
get_node("animation").play("loading")
wait_frames = 1
"_process"是loader轮询的地方。"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
if wait_frames > 0: # wait for frames to let the "loading" animation to show up
wait_frames -= 1
return
var t = OS.get_ticks_msec()
while OS.get_ticks_msec() < t + time_max: # use "time_max" to control how much time we block this thread
# poll your loader
var err = loader.poll()
if err == ERR_FILE_EOF: # load finished
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 len = get_node("animation").get_current_animation_length()
# call this on a paused animation. use "true" as the second parameter to force the animation to update
get_node("animation").seek(progress * len, true)
func set_new_scene(scene_resource):
current_scene = scene_resource.instance()
get_node("/root").add_child(current_scene)
ResourceInteractiveLoader可用于多线程。如果你要尝试这个功能,有些问题要注意:
当你的线程要请求一个资源,要求主进程等待时,使用Semaphore来休眠(也可以用busy loop或其他类似的东西)。
如果你在调用类和主进程间有个互斥锁,不要在加载"poll"时锁定它.当资源加载完毕时,需要底层APIs的一些资源(VisualServer等),需要锁定主进程来访问这些功能。这可能会导致死锁问题,当你的主场景等待互斥锁的同时,你的进度在等待加载资源。
以下是加载资源进程的示例here。使用方法如下:
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()
#假设你的游戏有10S的过场动画(原词是custscene),期间用户和游戏进行交互。
#在此期间,我们知道他们不会使用暂停菜单,所以我们可以序列的方式加载过场动画。
queue.queue_resource("res://pause_menu.xml")
start_curscene()
# 之后,用户第一次按下暂停按扭:
pause_menu = queue.get_resource("res://pause_menu.xml").instance()
pause_menu.show()
# 当你需要一个新场景时:
queue.queue_resource("res://level_1.xml", true) # 如果想让加载的内容在队列的前面,将第二个参数改为"true",暂停其他场景加载以检查加载进度
if queue.is_ready("res://level_1.xml"):
show_new_level(queue.get_resource("res://level_1.xml"))
else:
update_progress(queue.get_process("res://level_1.xml"))
# 在你的漫游游戏中 (Metroidvania game),当玩家离开触发区域时:
queue.cancel_resource("res://zone_2.xml")
注意: 此代码没有在真实的游戏环境下测试过。可以通过IRC(punto on irc.freenode.net)或e-mail([email protected])联系我,以获取更多帮助。