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