这个场景管理器主要是接受资源路径的输入,进行场景的加载
可以通过设置加载模式来决定,在加载某一类场景时是否保留同类场景
还可以将被卸载的场景入栈,在需要的时候出栈
extends Node
class_name SceneManager
@export var scene_root : Node
class ScenesStackItem:
var scene_name : String
var group_name : String
func _init(_scene_name : String, _group_name : String):
scene_name = _scene_name
group_name = _group_name
var scenes_stack : Array[ScenesStackItem]
enum LoadSceneMode{
Single,
Additive
}
func load_scene_with_relative_path(load_scene_name : String, group_name : String, load_scene_mode : LoadSceneMode):
if scene_root == null:
print("Have not assigned Scene Root!")
return
if load_scene_mode == LoadSceneMode.Single:
var old_scenes = get_tree().get_nodes_in_group(group_name)
for old_scene in old_scenes:
old_scene.queue_free()
_load_scene_with_relative_path(load_scene_name, group_name)
func _load_scene_with_relative_path(load_scene_name : String, group_name : String):
var dir = DirAccess.open("res://scenes")
var load_scene_path : String = "res://scenes/" + load_scene_name + ".tscn"
if dir.file_exists(load_scene_name + ".tscn"):
var scene = load(load_scene_path).instantiate()
scene_root.add_child(scene)
scene.add_to_group(group_name)
else:
print("File < " + load_scene_path + " > does not exist!")
func push_scene_with_relative_path(scene_name : String, group_name : String):
scenes_stack.push_back(ScenesStackItem.new(scene_name, group_name))
func pop_scene_with_relative_path(load_scene_mode : LoadSceneMode):
var item : ScenesStackItem = scenes_stack.pop_back()
load_scene_with_relative_path(item.scene_name, item.group_name, load_scene_mode)
func clear_scenes_stack():
scenes_stack.clear()
但是之后在试用的时候,发现一旦我修改了场景文件夹的组织方式,那么所有已经设置好的场景路径名称全部要修改一遍,这实在是太蠢了
于是我就改成了使用 PackedScene
extends Node
class_name SceneManager
@export var scene_root : Node
class ScenesStackItem:
var packed_scene : PackedScene
var group_name : String
func _init(_packed_scene : PackedScene, _group_name : String):
packed_scene = _packed_scene
group_name = _group_name
var scenes_stack : Array[ScenesStackItem]
enum LoadSceneMode{
Single,
Additive
}
func load_packed_scene(packed_scene : PackedScene, group_name : String, load_scene_mode : LoadSceneMode):
if scene_root == null:
print("Have not assigned Scene Root!")
return
if load_scene_mode == LoadSceneMode.Single:
var old_scenes = get_tree().get_nodes_in_group(group_name)
for old_scene in old_scenes:
old_scene.queue_free()
_load_packed_scene(packed_scene, group_name)
func _load_packed_scene(packed_scene : PackedScene, group_name : String):
if packed_scene != null:
var scene = packed_scene.instantiate()
scene_root.add_child(scene)
scene.add_to_group(group_name)
else:
print("Packed Scene is null!")
func push_packed_scene(packed_scene : PackedScene, group_name : String):
scenes_stack.push_back(ScenesStackItem.new(packed_scene, group_name))
func pop_packed_scene(load_scene_mode : LoadSceneMode):
var item : ScenesStackItem = scenes_stack.pop_back()
load_packed_scene(item.PackedScene, item.group_name, load_scene_mode)
func clear_scenes_stack():
scenes_stack.clear()
一个使用示例是加载场景按钮:
extends CustomButton
class_name LoadSceneButton
@export var load_packed_scene : Array[PackedScene]
@export var group_names : Array[String]
@export var load_scene_modes : Array[SceneManager.LoadSceneMode]
func _ready():
connect("button_down", Callable(self, "_load_scene"))
func _load_scene():
# load scene
for i in range(load_packed_scene.size()):
Globals.scenes().load_packed_scene(load_packed_scene[i], group_names[i], load_scene_modes[i])
但是当我写到场景堆栈的时候,假设我需要在离开这个场景的时候把这个场景加入堆栈,那么这个时候 godot 就会提示我这是循环依赖
extends Control
class_name Menu
# if you need push current scene
@export var push_curr_scene_when_exit : bool = false
@export var push_packed_scene : PackedScene
@export var push_group_name : String
@export var clear_stack_when_enter: bool = false
func _enter_tree():
if clear_stack_when_enter:
Globals.scenes().clear_scenes_stack()
func _exit_tree():
if push_curr_scene_when_exit:
Globals.scenes().push_packed_scene(push_packed_scene, push_group_name)
因此我觉得我似乎陷入了牛角尖……其实完全不用这么多
我或许不能强求所有场景都是用一个统一的函数来加载,我完全可以把某些场景捆绑在一起,或者在游戏开始时就生成
比如暂停菜单和设置菜单这种场景,可以在游戏开始时就生成并隐藏,在整个游戏过程中不销毁
比如某个关卡的 ui 和 world 节点我完全可以绑在一起
又比如从主菜单到选关菜单,虽然进入关卡后应该销毁,但是刚进入游戏时也可以都一起生成,进入关卡之后一起销毁
从主菜单切换到选关菜单就不用什么复杂的函数了,就是隐藏主菜单,显示选关菜单
因此我现在是在游戏开始时就载入所有菜单,然后通过切换可见性来切换菜单。将功能分散到一个一个按钮里面,例如:
back_to_previous_button.gd
extends CustomButton
@export var menu_root : Control
# may be override by sub class
func _ready():
super._ready()
connect("custom_button_down", Callable(self, "_return_button_down"))
func _return_button_down():
menu_root.visible = false
Globals.scenes().pop_scene()
close_menu_button.gd
extends CustomButton
@export var menu_root : Control
# may be override by sub class
func _ready():
super._ready()
connect("custom_button_down", Callable(self, "_close_menu_button_down"))
func _close_menu_button_down():
menu_root.visible = false
open_menu_button.gd
extends CustomButton
@export var menu_root : Control
@export var open_menu_root : Control
# may be override by sub class
func _ready():
super._ready()
connect("custom_button_down", Callable(self, "_open_menu_button_down"))
func _open_menu_button_down():
menu_root.visible = false
open_menu_root.visible = true
Globals.scenes().push_scene(menu_root)