原文:Making a background music player in Godot
作者:@woubuc
翻译:谷木 CW
目前有许多可以播放和管理游戏中音乐的方法,但是这些方法大多数都与场景相关联。在本教程中,我们将制作一个“自动载入”的音乐管理器,它能够独立于游戏场景播放背景音乐。(PS:即使切换场景也可以不间断播放)
我们需要什么?
音乐,Godot 3.1
自动载入:Autoload
我们将使用 Godot 自带的 Autoload 功能。这个功能能够添加静态节点到场景树中,并且保持存在,独立于我们的游戏场景。
这能够完美的保持我们的音乐播放,即使场景切换了。
如果你想要学习更多的内容,可以点击与此相关的相关官方文档链接。
开始动手做!
我们的项目将有三个场景:Scene1,Scene2 将是我们通常使用的游戏场景(比如两个关卡),并且我们将添加一些按钮到场景中以便管理音乐。
音乐控制器(MusicController)将会是自动载入(Autoload)的场景。我们将添加一些自定义函数到场景中,以便能够轻松的在游戏中任何地方播放我们的音乐。
场景 1:Scene 1
创建一个新的场景,叫做 Scene1
,然后添加一个标签(Lable
)和三个按钮(Button
),如下面的截图所示。
添加一个脚本到场景的根节点,然后将三个按钮的 pressed()
事件连接到按钮上。
# Scene1.gd extends Control func _on_GoToScene2Button_pressed(): get_tree().change_scene("res://Scene2.tscn") func _on_PlayTrack1Button_pressed(): pass # Replace with function body.func _on_StopMusicButton_pressed(): pass # Replace with function body.
场景 2:Scene 2
现在创建另一个场景,叫做 Scene2
,然后添加一个标签(Lable
)和两个按钮(Button
),如下面的截图所示。
下一步,添加一个脚本到场景的根节点,并且连接到两个按钮的 pressed()
事件上。
# Scene2.gd extends Control func _on_GoToScene1Button_pressed(): get_tree().change_scene("res://Scene1.tscn") func _on_PlayTrack2Button_pressed(): pass # Replace with function body.
音乐控制器:Music Controller
现在,我们就可以开始创建音乐控制器(Music Controller)场景了!
创建一个新的场景,命名为 MusicController
,添加一个 AudioStreamPlayer
节点以便播放音乐。
添加一个脚本到场景根节点上,代码如下:
# MusicController.gd extends Control # Load the music player node onready var _player = $AudioStreamPlayer # Calling this function will load the given track, and play it func play(track_url : String): pass # Calling this function will stop the musicfunc stop(): pass
小提示: 你可能有疑问, track_url : String
字符串语法是什么?这是 Godot 3.1 中的一个新功能,称为可选类型。则意味着我们希望这个函数参数为 “字符串” ,并且如果我们使用的是字符串以外的任何东西调用这个函数,编辑器就会警告我们。如你所见,我们定义了两个函数:play()
、stop()
,我们将使用这两个函数在游戏需要时 ”播放“ 和 ”停止“ 音乐。
停止函数:Stop()
让我们从最简单的函数开始。stop()
函数和它的名字一样,只需要音乐暂停。
查看 Godot 官方文档不难发现,AudioStreamPlayer
有一个 stop()
方法,我们可以直接调用。
func stop(): _player.stop()
播放函数:Play()
这里会涉及更多知识。当函数 play()
被调用时,我们需要做三件事情:
- 停止上一曲;
- 使用
track_url
函数参数载入新的 “音乐”(track 译为音乐/单曲); - 开始播放新的音乐;
很幸运的是,用 GDScript 这很容易做到!
停止播放:Stop
想要停止播放上一曲,就需要调用我们的 stop()
方法。
stop()
载入音乐:Load
下一步,我们需要载入新的音乐。GDScript 可以使用 load()
函数轻松实现。
var new_track = load(track_url)
更新:Update
现在,我们需要更新播放器,并且告诉它使用我们刚加载的音乐。
_player.stream = new_track
播放:Play
接着我们就要开始播放音乐。
_player.play()
汇总
我们的音乐控制器(MusicController
)脚本看起来现在是这样:
#MusicController.gd extends Control # Load the music player nodeonready var _player = $AudioStreamPlayer # Calling this function will load the given track, and play it func play(track_url : String): var track = load(track_url) _player.stream = track _player.play() # Calling this function will stop the musicfunc stop(): _player.stop()
设置自动加载:Set Up Autoload
最后一件事就是告诉 Godot,我们需要自动加载这个场景!
找到菜单栏的 Project 菜单并且打开 Project Settigns ,然后点击 AutoLoad 标签。在这里我们可以配置自动加载脚本。
自动加载脚本始终指向应该加载的资源(脚本或者场景)的路径(Path),以及可以在代码中使用的名称(Name)。
小提示: 你可以通过场景树访问自动加载节点,可以查看官方文档。
将我们的 MusicController
场景添加到自动加载设置中,选择它的路径,你可以通过单击字段旁边的小按钮使用文件浏览器查找它。
节点名称应该根据场景名称自动填充。你也可以根据需要修改,这里我们不做变动。
点击 Add
按钮将自动载入场景添加到项目中,它将会出现在下面的列表中。每个自动加载的脚本都有一些按钮,你可以使用这些按钮编辑或者删除脚本,或者改变它们的加载顺序。它们还有一个已经启用(Enable
)的复选框,因此你可以暂时禁用自动加载脚本,这样就免除了直接删除,后续还要重新添加的麻烦。
再次确认我们的音乐控制器场景是开启的(Enable
)。
像上图一样!我们的自动载入场景就启用了,它在项目中处于活跃状态!这就意味着我们可以通过全局调用 play()
和 stop()
函数。来试一下!
使用你的自动载入函数!
在场景 1 和场景 2 的脚本中,我们可以在按钮按下时调用这些函数:
# Scene1.gd extends Control # Switch to the other scene func _on_GoToScene2Button_pressed(): get_tree().change_scene("res://Scene2.tscn") # Load and play track 1 func _on_PlayTrack1Button_pressed(): MusicController.play("res://tracks/track 1.ogg") # Stop the music func _on_StopMusicButton_pressed(): MusicController.stop()
#Scene2.gd extends Control # Switch to the other scene func _on_GoToScene1Button_pressed(): get_tree().change_scene("res://Scene1.tscn") # Load and play track 2 func _on_PlayTrack2Button_pressed(): MusicController.play("res://tracks/track 2.ogg")
别忘了设置正确的音乐路径!
大功告成!
现在我们就可以运行游戏,并且享受音乐了!即使您在各种关卡与场景中自由切换,音乐也不会停下来!
当然,你还可以将这个功能运用在其他方面!
- 谷木工作室官网:https://gumu.studio/
- TapTtap:https://www.taptap.com/app/164010
- 爱发电游戏已经开启预售众筹:《似水年华》
get不到auto load 功能的实用性,因为任何游戏或者程序都有全局部分,gdscript不能使用全局变量,而理所当然的应该使用当前场景根节点的父级节点来执行全局音乐(也就是把当前场景作为全局场景的子节点),这在逻辑性,便捷性,合理性都比auto load要好。我认为autoload功能是想便于那些不能善用场景树的开发者,但是这个方向是错的,这会导致更多的入门开发者的思维限制于这种多个单一场景按互相切换的开发方式,会限制游戏的可能性。
最近由 ATom 修改于:2020-01-20 14:50:31@ATom:虽然我不知道您有没有用过GODOT以及GDSCRIPT,但是:第一,这作为一个简单好用的方法,能够轻而易举的为开发者提供全局变量和全局节点,让初学者可以很方便学会;第二,这个自动载入的节点,是作为“根节点的子节点”加进来的;第三,这篇文章是翻译的,许多GODOT开发者都在使用这个方法,GODOT官方文档也提供了这种自动载入的教学;第四,我也是一个对GODOT不是很精通的开发者,如果您愿意分享,希望您可以将您的思路和具体方法写出来,这样可以让我们切实学习到一个更加科学的方法。
@ATom:http://docs.godotengine.org/zh_CN/latest/getting_started/step_by_step/singletons_autoload.html
@谷木工作室:你所说的全局变量是通过$path语法糖 或者 get_node自己手动加载的局部变量。我想要说的是切换根场景的同时还要保证全局节点的存在是一种很不健康的开发方式,每次切换时,全局节点都会执行enter tree和exit tree(这一点我不太确定,但根本上一个需要在多个场景中保持存在的节点不应该作为这些场景的子节点),如果这其中也有代码,逻辑上增加了复杂性。
最近由 ATom 修改于:2020-01-21 00:22:34应该采用你给出的文档链接中提出的第一种方法,就拿文中的需求举个例子,只要不切换根场景,把音乐播放器放到根节点下,然后将场景1和场景2也作为根场景的子节点操作即可,这样需要保持存在的音乐就会一直存在,也不需要额外的功能去维护它的存在,即使在运行时,如果不需要它了,就直接移除,也可以随时添加回来。而文档中提到的“不能保证正常工作”说的是先后开发方式不一样导致的(主要是节点路径操作问题,如果从一开始就是用这种方法,问题是不存在的),这从开发的稳定性、自由度和逻辑合理性上都要比autoload好,autoload实际上迎合了一种不合理的开发模式(这里指的是游戏本身就是从一个个根场景之间切换的开发模式),这种模式固然好理解,但是一旦习惯了,又想要开发相对复杂一些的游戏,还想要开发效率,就必须抛弃这种模式,必须重新学习更好的开发模式,不然开发效率会呈指数式下降。而最好的方法就是合理利用场景树的作用(我认为场景树的核心作用之一就是解耦合),只要熟悉了各个回调函数的执行时机,利用场景树处理各个功能之间的关系,基本上就够用了。
@ATom:受教了,感谢解答!