63 lines
2.6 KiB
GDScript3
63 lines
2.6 KiB
GDScript3
|
|
class_name Music
|
||
|
|
extends RefCounted
|
||
|
|
|
||
|
|
# Forgejo #148: "the space_4 file in the folder is the main theme of the
|
||
|
|
# game, make it play all the time."
|
||
|
|
#
|
||
|
|
# "All the time" is the whole of the requirement, and it is what decides the
|
||
|
|
# shape of this. The theme must survive the main menu becoming the game, the
|
||
|
|
# game becoming a planet surface, and the surface becoming the game again -
|
||
|
|
# so the player cannot live in any of those scenes, because
|
||
|
|
# change_scene_to_file() frees the one that is current.
|
||
|
|
#
|
||
|
|
# It is parented to the TREE ROOT instead, alongside the GameState autoload,
|
||
|
|
# which is the same trick for the same reason. Doing it this way rather than
|
||
|
|
# by adding an autoload keeps project.godot out of it - that file carries
|
||
|
|
# the developer's own uncommitted window settings, and a change here should
|
||
|
|
# not drag those into a commit.
|
||
|
|
#
|
||
|
|
# ensure() is idempotent by NAME: whatever scene loads next asks for the
|
||
|
|
# music, and gets the one already playing rather than a second copy of it.
|
||
|
|
|
||
|
|
const THEME_PATH: String = "res://audio/space_4.mp3"
|
||
|
|
const NODE_NAME: String = "MusicPlayer"
|
||
|
|
# Under everything else. It is a bed, not an event.
|
||
|
|
const VOLUME_DB: float = -14.0
|
||
|
|
|
||
|
|
|
||
|
|
# Starts the theme if nothing is playing it yet, and returns the player
|
||
|
|
# either way. Safe to call from any scene's _ready.
|
||
|
|
static func ensure(tree: SceneTree) -> AudioStreamPlayer:
|
||
|
|
if tree == null:
|
||
|
|
return null
|
||
|
|
var existing: Node = tree.root.get_node_or_null(NODE_NAME)
|
||
|
|
if existing != null:
|
||
|
|
return existing as AudioStreamPlayer
|
||
|
|
|
||
|
|
var stream: AudioStream = load(THEME_PATH)
|
||
|
|
if stream == null:
|
||
|
|
return null
|
||
|
|
# Looping is a property of the STREAM, not of the player, so it is set
|
||
|
|
# here rather than relied on from the .import file - which Godot
|
||
|
|
# regenerates, and which would silently take the loop away with it.
|
||
|
|
if "loop" in stream:
|
||
|
|
stream.loop = true
|
||
|
|
|
||
|
|
var player := AudioStreamPlayer.new()
|
||
|
|
player.name = NODE_NAME
|
||
|
|
player.stream = stream
|
||
|
|
player.volume_db = VOLUME_DB
|
||
|
|
player.autoplay = false
|
||
|
|
# #128 stops the tree whenever a dialog is up. Music is the one thing
|
||
|
|
# that should not stop with it - a soundtrack cutting out every time
|
||
|
|
# someone reads a line is worse than no soundtrack.
|
||
|
|
player.process_mode = Node.PROCESS_MODE_ALWAYS
|
||
|
|
# A backstop for the loop above: if a stream format ever turns up that
|
||
|
|
# cannot loop itself, it still comes round again rather than falling
|
||
|
|
# silent for the rest of the session.
|
||
|
|
player.finished.connect(player.play)
|
||
|
|
# Deferred: this is called from a scene's _ready, and the root is busy
|
||
|
|
# adding that scene's own children at the time.
|
||
|
|
tree.root.add_child.call_deferred(player)
|
||
|
|
player.play.call_deferred()
|
||
|
|
return player
|