Background loading - seraph526/godot-se GitHub Wiki

后台加载 (Background loading)

当切换游戏的主场景时(如开启一个新的关卡),你想要显示一个加载画面,并提示加载的进度。主要的加载方法(gdscript中的"ResourceLoader::load"或"load")当资源加载时会冻结你的进程,这不是很好。这个文档讨论了"ResourceInteractiveLoade"类,更平滑的加载方式。

交互资源加载 (ResourceLoaderInteractive)

"ResourceLoaderInteractive"类允许你在场景中加载一个资源。每一次方法"poll"被调用时,一个新的场景被加载时,和control被返回给调用对象时。每个场景都是加载它的场景的一个子资源。如你要加载一个场景,场景上加载10张图片,每张图片都要在场景上。

用法 (Usage)

有法如下:

获取一个交互加载资源

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

这一方法使用ResourceLoaderInteractive管理你的加载操作。

轮询 (Polling)

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 返回当前被加载的场景

强制完成 (Forcing completion) (可选)

Error ResourceLoaderInteractive::wait();

如果想要加载在当前帧完成,请使用此方法,不需要更多的步骤。

获取资源 (Obtaining the resource)

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可用于多线程。如果你要尝试这个功能,有些问题要注意:

使用信号 (Use a Semaphore)

当你的线程要请求一个资源,要求主进程等待时,使用Semaphore来休眠(也可以用busy loop或其他类似的东西)。

调用"poll"期间不要冻结主进程

如果你在调用类和主进程间有个互斥锁,不要在加载"poll"时锁定它.当资源加载完毕时,需要底层APIs的一些资源(VisualServer等),需要锁定主进程来访问这些功能。这可能会导致死锁问题,当你的主场景等待互斥锁的同时,你的进度在等待加载资源。

Example class

以下是加载资源进程的示例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"加载并返回相应值。

Example:

# 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])联系我,以获取更多帮助。

⚠️ **GitHub.com Fallback** ⚠️