Godot Snippets

Damage Number Popup

Untitled video - Made with Clipchamp (1).gif

class_name NumberPopup extends Node2D

'''
Node2D
-> Label
-> AnimationPlayer

Have an animation that makes it scale up really fast, 
then turn transparent and scale down

If you are going to spawn a lot, create a manager with a pool of numbers!
'''

@export var speed: float = 100
@export var friction: float = 15
var shift_direction: Vector2 = Vector2.ZERO

@onready var label: Label = $Label
@onready var animation_player: AnimationPlayer = $AnimationPlayer

func popup(damage: String, popup_direction: Vector2 = Vector2.ZERO) -> void:
	label.text = str(damage)
	animation_player.play("number_popup")
	shift_direction += popup_direction

func _process(delta: float) -> void:
	global_position += speed * shift_direction * delta
	speed = max(speed - friction * delta, 0)

AudioManager

extends Node

const SOUND_FOLDER_PATH: String = "res://assets/audio/sounds/"
const SAVE_FILE_PATH: String = "user://audio_settings.cfg"

const POOL_SIZE: int = 12

var sounds_dictionary: Dictionary = {}
var supported_audio_files: Array[String] = [".wav", ".ogg", ".mp3"]

var sounds_bus_idx: int
var music_bus_idx: int

var sound_volume: float = 1.0
var music_volume: float = 1.0

var player_pool: Array[AudioStreamPlayer] = []

func _ready() -> void:
	sounds_bus_idx = AudioServer.get_bus_index("sounds")
	music_bus_idx = AudioServer.get_bus_index("music")
	
	# Pre-populate the object pool for performance.
	create_player_pool()
	
	load_sound_paths_dictionary()
	load_volumes()

func create_player_pool() -> void:
	for i in range(POOL_SIZE):
		var stream_player := AudioStreamPlayer.new()
		# Add the player to this node. It will stay in the scene tree.
		add_child(stream_player)
		# The player is now part of the pool.
		player_pool.append(stream_player)

func get_available_player() -> AudioStreamPlayer:
	for player in player_pool:
		if not player.is_playing():
			return player
	return null # All players are busy

func play_sound(sound_name: String, pitch_scale: float = -1.0) -> void:
	if not sounds_dictionary.has(sound_name):
		push_warning("Sound not found in dictionary: %s" % sound_name)
		return
	
	# Fetch an available player from the pool.
	var player = get_available_player()
	
	# If no player is available, we can't play the sound.
	if not player:
		push_warning("Audio player pool is full. Consider increasing POOL_SIZE.")
		return
	
	# Configure and play the sound.
	player.stream = sounds_dictionary[sound_name]
	player.bus = "sounds"
	
	if pitch_scale == -1.0:
		player.pitch_scale = randf_range(0.9, 1.1)
	else:
		player.pitch_scale = pitch_scale
	
	player.play()

func set_sound_volume(volume_linear: float) -> void:
	sound_volume = clampf(volume_linear, 0.0, 1.0)
	var volume_db = linear_to_db(sound_volume) if sound_volume > 0 else -80.0
	AudioServer.set_bus_volume_db(sounds_bus_idx, volume_db)
	save_volumes()

func set_music_volume(volume_linear: float) -> void:
	music_volume = clampf(volume_linear, 0.0, 1.0)
	var volume_db = linear_to_db(music_volume) if music_volume > 0 else -80.0
	AudioServer.set_bus_volume_db(music_bus_idx, volume_db)
	save_volumes()

func save_volumes() -> void:
	var config := ConfigFile.new()
	config.set_value("audio", "sound_volume", sound_volume)
	config.set_value("audio", "music_volume", music_volume)
	config.save(SAVE_FILE_PATH)

func load_volumes() -> void:
	var config := ConfigFile.new()
	if config.load(SAVE_FILE_PATH) == OK:
		var loaded_sound_vol = config.get_value("audio", "sound_volume", sound_volume)
		var loaded_music_vol = config.get_value("audio", "music_volume", music_volume)
		set_sound_volume(loaded_sound_vol)
		set_music_volume(loaded_music_vol)
	else:
		print("No audio settings file found. Creating one with default values.")
		save_volumes()

func load_sound_paths_dictionary() -> void:
	var dir := DirAccess.open(SOUND_FOLDER_PATH)
	if not dir:
		push_error("Could not open sound assets folder: %s" % SOUND_FOLDER_PATH)
		return
	
	dir.list_dir_begin()
	var file_name := dir.get_next()
	
	while file_name != "":
		if dir.current_is_dir() or not is_supported_audio_file(file_name):
			file_name = dir.get_next()
			continue
		
		if OS.has_feature("editor") and file_name.ends_with(".import"):
			file_name = dir.get_next()
			continue
		
		var sound_name_key = file_name.get_basename()
		var sound_path = "%s/%s" % [SOUND_FOLDER_PATH, file_name]
		
		sounds_dictionary[sound_name_key] = load(sound_path)
		
		file_name = dir.get_next()

func is_supported_audio_file(file_name: String) -> bool:
	for ext in supported_audio_files:
		if file_name.ends_with(ext):
			return true
	return false

extends Node

"""
How it works:
First we (automatically) load all the sounds placed inside res://assets/audio/sounds/   and	place them inside the sounds_dictionary, 
using the sound name as the dictionary key.
	
Then, from anywhere in the game, we can call play_sound(sound_name) and it
will play :)
"""

var sound_folder_path: String = "res://assets/audio/sounds/"
var sounds_dictionary: Dictionary = {}

var supported_audio_files: Array = [".wav", ".ogg", ".mp3"]

func _ready() -> void:
	load_sound_paths_dictionary()

func play_sound(sound_name: String, pitch_scale: float = -1) -> void:
	if !sounds_dictionary.has(sound_name):
		print("Sound not present in sounds dictionary: " + sound_name)
		return
	
	var sound_stream: AudioStream = sounds_dictionary[sound_name]
	
	if sound_stream:
		var stream_player: AudioStreamPlayer = AudioStreamPlayer.new()
		stream_player.stream = sound_stream
		stream_player.bus = "sound"
		
		stream_player.finished.connect(stream_player.queue_free)
		
		randomize()
		if pitch_scale == -1:
			pitch_scale = randf_range(0.9, 1.1)
		
		stream_player.pitch_scale = pitch_scale # Randomize pitch so it's not always the same!
		
		add_child(stream_player)
		
		stream_player.play()

func load_sound_paths_dictionary() -> void:
	var directory: DirAccess = DirAccess.open(sound_folder_path)
	if directory:
		directory.list_dir_begin()
		var file_name = directory.get_next()
		
		while file_name != "":
			if directory.current_is_dir():
				pass # If you want recursive file loading, do it here. I think
			else:    # it's a bit overkill :)
				for file_type in supported_audio_files:
					#print(file_name)
					if file_name.contains(file_type):
						if OS.has_feature("editor"):
							if file_name.contains(".import"):
								continue
						else:
							if file_name.ends_with(".import"):
								file_name = file_name.trim_suffix(".import")
						
						var sound_file_path: String = sound_folder_path + "/" + file_name
						var new_audio_stream = load(sound_file_path)
						if new_audio_stream is AudioStream:
							sounds_dictionary[file_name.trim_suffix(file_type)] = new_audio_stream
						# print("Successfully loaded sound: " + sound_file_path)
			
			file_name = directory.get_next()
	else:
		print("An error occurred when trying to access the sounds path: " + sound_folder_path)

Audio Resources

Music

Art

3D Models

Creative Trio – Free CCO Stylized Low Poly 3D Models, Game Assets, VR Assets, Roblox