Add credits
This commit is contained in:
242
scenes/ui/Credits.gd
Normal file
242
scenes/ui/Credits.gd
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
signal back_pressed
|
||||||
|
|
||||||
|
const YAML_SOURCES: Array[String] = [
|
||||||
|
"res://assets/sources.yaml",
|
||||||
|
# Future sources:
|
||||||
|
# "res://assets/audio/audio-sources.yaml",
|
||||||
|
# "res://assets/sprites/sprite-sources.yaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
@onready var scroll_container: ScrollContainer = $MarginContainer/VBoxContainer/ScrollContainer
|
||||||
|
@onready var credits_text: RichTextLabel = $MarginContainer/VBoxContainer/ScrollContainer/CreditsText
|
||||||
|
@onready var back_button: Button = $MarginContainer/VBoxContainer/BackButton
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
DebugManager.log_info("Credits scene ready", "Credits")
|
||||||
|
_load_and_display_credits()
|
||||||
|
back_button.grab_focus()
|
||||||
|
DebugManager.log_info("Back button focused for gamepad support", "Credits")
|
||||||
|
|
||||||
|
|
||||||
|
func _load_and_display_credits() -> void:
|
||||||
|
"""Load credits from multiple YAML files and display formatted output"""
|
||||||
|
var all_credits_data: Dictionary = {}
|
||||||
|
|
||||||
|
# Load all YAML source files
|
||||||
|
for yaml_path in YAML_SOURCES:
|
||||||
|
var yaml_data: Dictionary = _load_yaml_file(yaml_path)
|
||||||
|
if not yaml_data.is_empty():
|
||||||
|
_merge_credits_data(all_credits_data, yaml_data)
|
||||||
|
|
||||||
|
# Generate and display formatted credits
|
||||||
|
_display_formatted_credits(all_credits_data)
|
||||||
|
|
||||||
|
|
||||||
|
func _load_yaml_file(yaml_path: String) -> Dictionary:
|
||||||
|
"""Load and parse a YAML file into a dictionary structure"""
|
||||||
|
var file := FileAccess.open(yaml_path, FileAccess.READ)
|
||||||
|
|
||||||
|
if not file:
|
||||||
|
DebugManager.log_warn("Could not open YAML file: %s" % yaml_path, "Credits")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
var content: String = file.get_as_text()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
DebugManager.log_info("Loaded YAML file: %s" % yaml_path, "Credits")
|
||||||
|
return _parse_yaml_content(content)
|
||||||
|
|
||||||
|
|
||||||
|
func _parse_yaml_content(yaml_content: String) -> Dictionary:
|
||||||
|
"""Parse YAML content into structured dictionary"""
|
||||||
|
var result: Dictionary = {}
|
||||||
|
var lines: Array = yaml_content.split("\n")
|
||||||
|
var current_section: String = ""
|
||||||
|
var current_subsection: String = ""
|
||||||
|
var current_asset: String = ""
|
||||||
|
var current_asset_data: Dictionary = {}
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
var trimmed: String = line.strip_edges()
|
||||||
|
|
||||||
|
# Skip comments and empty lines
|
||||||
|
if trimmed.is_empty() or trimmed.begins_with("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Top-level section (audio, sprites, textures, etc.)
|
||||||
|
if not line.begins_with(" ") and not line.begins_with("\t") and trimmed.ends_with(":"):
|
||||||
|
if current_asset and not current_asset_data.is_empty():
|
||||||
|
_store_asset_data(
|
||||||
|
result, current_section, current_subsection, current_asset, current_asset_data
|
||||||
|
)
|
||||||
|
current_section = trimmed.trim_suffix(":")
|
||||||
|
current_subsection = ""
|
||||||
|
current_asset = ""
|
||||||
|
current_asset_data = {}
|
||||||
|
if not result.has(current_section):
|
||||||
|
result[current_section] = {}
|
||||||
|
|
||||||
|
# Subsection (music, sfx, characters, etc.)
|
||||||
|
elif line.begins_with(" ") and not line.begins_with(" ") and trimmed.ends_with(":"):
|
||||||
|
if current_asset and not current_asset_data.is_empty():
|
||||||
|
_store_asset_data(
|
||||||
|
result, current_section, current_subsection, current_asset, current_asset_data
|
||||||
|
)
|
||||||
|
current_subsection = trimmed.trim_suffix(":")
|
||||||
|
current_asset = ""
|
||||||
|
current_asset_data = {}
|
||||||
|
if current_section and not result[current_section].has(current_subsection):
|
||||||
|
result[current_section][current_subsection] = {}
|
||||||
|
|
||||||
|
# Asset name
|
||||||
|
elif trimmed.begins_with('"') and trimmed.contains('":'):
|
||||||
|
if current_asset and not current_asset_data.is_empty():
|
||||||
|
_store_asset_data(
|
||||||
|
result, current_section, current_subsection, current_asset, current_asset_data
|
||||||
|
)
|
||||||
|
var parts: Array = trimmed.split('"')
|
||||||
|
current_asset = parts[1] if parts.size() > 1 else ""
|
||||||
|
current_asset_data = {}
|
||||||
|
|
||||||
|
# Asset properties
|
||||||
|
elif ":" in trimmed:
|
||||||
|
var parts: Array = trimmed.split(":", false, 1)
|
||||||
|
if parts.size() == 2:
|
||||||
|
var key: String = parts[0].strip_edges()
|
||||||
|
var value: String = parts[1].strip_edges().trim_prefix('"').trim_suffix('"')
|
||||||
|
if value and value != '""':
|
||||||
|
current_asset_data[key] = value
|
||||||
|
|
||||||
|
# Store last asset
|
||||||
|
if current_asset and not current_asset_data.is_empty():
|
||||||
|
_store_asset_data(
|
||||||
|
result, current_section, current_subsection, current_asset, current_asset_data
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _store_asset_data(
|
||||||
|
result: Dictionary, section: String, subsection: String, asset: String, data: Dictionary
|
||||||
|
) -> void:
|
||||||
|
"""Store parsed asset data into result dictionary"""
|
||||||
|
if not section or not asset:
|
||||||
|
return
|
||||||
|
|
||||||
|
if subsection:
|
||||||
|
if not result[section].has(subsection):
|
||||||
|
result[section][subsection] = {}
|
||||||
|
result[section][subsection][asset] = data
|
||||||
|
else:
|
||||||
|
result[section][asset] = data
|
||||||
|
|
||||||
|
|
||||||
|
func _merge_credits_data(target: Dictionary, source: Dictionary) -> void:
|
||||||
|
"""Merge source credits data into target dictionary"""
|
||||||
|
for section in source:
|
||||||
|
if not target.has(section):
|
||||||
|
target[section] = {}
|
||||||
|
for subsection in source[section]:
|
||||||
|
if source[section][subsection] is Dictionary:
|
||||||
|
if not target[section].has(subsection):
|
||||||
|
target[section][subsection] = {}
|
||||||
|
for asset in source[section][subsection]:
|
||||||
|
target[section][subsection][asset] = source[section][subsection][asset]
|
||||||
|
|
||||||
|
|
||||||
|
func _display_formatted_credits(credits_data: Dictionary) -> void:
|
||||||
|
"""Generate BBCode formatted credits from parsed data"""
|
||||||
|
if not credits_text:
|
||||||
|
DebugManager.log_error("Credits text node is null, cannot display credits", "Credits")
|
||||||
|
return
|
||||||
|
|
||||||
|
var credits_bbcode: String = "[center][b][font_size=32]CREDITS[/font_size][/b][/center]\n\n"
|
||||||
|
|
||||||
|
# Audio section
|
||||||
|
if credits_data.has("audio"):
|
||||||
|
credits_bbcode += "[b][font_size=24]AUDIO[/font_size][/b]\n\n"
|
||||||
|
credits_bbcode += _format_section(credits_data["audio"])
|
||||||
|
|
||||||
|
# Sprites section
|
||||||
|
if credits_data.has("sprites"):
|
||||||
|
credits_bbcode += "[b][font_size=24]GRAPHICS[/font_size][/b]\n\n"
|
||||||
|
credits_bbcode += _format_section(credits_data["sprites"])
|
||||||
|
|
||||||
|
# Textures section
|
||||||
|
if credits_data.has("textures"):
|
||||||
|
if not credits_data.has("sprites"):
|
||||||
|
credits_bbcode += "[b][font_size=24]GRAPHICS[/font_size][/b]\n\n"
|
||||||
|
credits_bbcode += _format_section(credits_data["textures"])
|
||||||
|
|
||||||
|
# Game development credits
|
||||||
|
credits_bbcode += "[b][font_size=24]GAME DEVELOPMENT[/font_size][/b]\n\n"
|
||||||
|
credits_bbcode += "[i]Developed with Godot Engine 4.4[/i]\n"
|
||||||
|
credits_bbcode += "[url=https://godotengine.org]https://godotengine.org[/url]\n\n"
|
||||||
|
|
||||||
|
credits_text.bbcode_enabled = true
|
||||||
|
credits_text.text = credits_bbcode
|
||||||
|
DebugManager.log_info("Credits displayed successfully", "Credits")
|
||||||
|
|
||||||
|
|
||||||
|
func _format_section(section_data: Dictionary) -> String:
|
||||||
|
"""Format a credits section with subsections and assets"""
|
||||||
|
var result: String = ""
|
||||||
|
|
||||||
|
for subsection in section_data:
|
||||||
|
if section_data[subsection] is Dictionary:
|
||||||
|
# Add subsection header
|
||||||
|
var subsection_title: String = subsection.capitalize()
|
||||||
|
result += "[i]" + subsection_title + "[/i]\n"
|
||||||
|
|
||||||
|
# Add assets in this subsection
|
||||||
|
for asset in section_data[subsection]:
|
||||||
|
var asset_data: Dictionary = section_data[subsection][asset]
|
||||||
|
result += _format_asset(asset_data)
|
||||||
|
|
||||||
|
result += "\n"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _format_asset(asset_data: Dictionary) -> String:
|
||||||
|
"""Format a single asset's credit information"""
|
||||||
|
var result: String = ""
|
||||||
|
|
||||||
|
if asset_data.has("attribution") and asset_data["attribution"]:
|
||||||
|
result += "[b]" + asset_data["attribution"] + "[/b]\n"
|
||||||
|
|
||||||
|
if asset_data.has("license") and asset_data["license"]:
|
||||||
|
result += "License: " + asset_data["license"] + "\n"
|
||||||
|
|
||||||
|
if asset_data.has("source") and asset_data["source"]:
|
||||||
|
result += "[url=" + asset_data["source"] + "]Source[/url]\n"
|
||||||
|
|
||||||
|
if result:
|
||||||
|
result += "\n"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _on_back_button_pressed() -> void:
|
||||||
|
AudioManager.play_ui_click()
|
||||||
|
DebugManager.log_info("Back button pressed", "Credits")
|
||||||
|
GameManager.exit_to_main_menu()
|
||||||
|
|
||||||
|
|
||||||
|
func _input(event: InputEvent) -> void:
|
||||||
|
if event.is_action_pressed("ui_back") or event.is_action_pressed("action_east"):
|
||||||
|
_on_back_button_pressed()
|
||||||
|
elif event.is_action_pressed("move_up") or event.is_action_pressed("ui_up"):
|
||||||
|
_scroll_credits(-50.0)
|
||||||
|
elif event.is_action_pressed("move_down") or event.is_action_pressed("ui_down"):
|
||||||
|
_scroll_credits(50.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _scroll_credits(amount: float) -> void:
|
||||||
|
"""Scroll the credits by the specified amount"""
|
||||||
|
var current_scroll: float = scroll_container.scroll_vertical
|
||||||
|
scroll_container.scroll_vertical = int(current_scroll + amount)
|
||||||
|
DebugManager.log_debug("Scrolled credits to: %d" % scroll_container.scroll_vertical, "Credits")
|
||||||
1
scenes/ui/Credits.gd.uid
Normal file
1
scenes/ui/Credits.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cwygtx0r6gdt1
|
||||||
52
scenes/ui/Credits.tscn
Normal file
52
scenes/ui/Credits.tscn
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cspq2y7mvjxn5"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/ui/Credits.gd" id="1_credits"]
|
||||||
|
|
||||||
|
[node name="Credits" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_credits")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
theme_override_constants/margin_left = 40
|
||||||
|
theme_override_constants/margin_top = 40
|
||||||
|
theme_override_constants/margin_right = 40
|
||||||
|
theme_override_constants/margin_bottom = 40
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
follow_focus = true
|
||||||
|
|
||||||
|
[node name="CreditsText" type="RichTextLabel" parent="MarginContainer/VBoxContainer/ScrollContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
bbcode_enabled = true
|
||||||
|
text = "[center][b]CREDITS[/b][/center]
|
||||||
|
|
||||||
|
Loading credits..."
|
||||||
|
fit_content = true
|
||||||
|
scroll_active = false
|
||||||
|
|
||||||
|
[node name="BackButton" type="Button" parent="MarginContainer/VBoxContainer"]
|
||||||
|
custom_minimum_size = Vector2(150, 50)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Back"
|
||||||
|
|
||||||
|
[connection signal="pressed" from="MarginContainer/VBoxContainer/BackButton" to="." method="_on_back_button_pressed"]
|
||||||
@@ -31,6 +31,12 @@ func _on_settings_button_pressed() -> void:
|
|||||||
open_settings.emit()
|
open_settings.emit()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_credits_button_pressed() -> void:
|
||||||
|
AudioManager.play_ui_click()
|
||||||
|
DebugManager.log_info("Credits pressed", "MainMenu")
|
||||||
|
GameManager.show_credits()
|
||||||
|
|
||||||
|
|
||||||
func _on_exit_button_pressed() -> void:
|
func _on_exit_button_pressed() -> void:
|
||||||
AudioManager.play_ui_click()
|
AudioManager.play_ui_click()
|
||||||
DebugManager.log_info("Exit pressed", "MainMenu")
|
DebugManager.log_info("Exit pressed", "MainMenu")
|
||||||
@@ -43,6 +49,7 @@ func _setup_menu_navigation() -> void:
|
|||||||
|
|
||||||
menu_buttons.append($MenuContainer/NewGameButton)
|
menu_buttons.append($MenuContainer/NewGameButton)
|
||||||
menu_buttons.append($MenuContainer/SettingsButton)
|
menu_buttons.append($MenuContainer/SettingsButton)
|
||||||
|
menu_buttons.append($MenuContainer/CreditsButton)
|
||||||
menu_buttons.append($MenuContainer/ExitButton)
|
menu_buttons.append($MenuContainer/ExitButton)
|
||||||
|
|
||||||
for button in menu_buttons:
|
for button in menu_buttons:
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ text = "New Game"
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Settings"
|
text = "Settings"
|
||||||
|
|
||||||
|
[node name="CreditsButton" type="Button" parent="MenuContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Credits"
|
||||||
|
|
||||||
[node name="ExitButton" type="Button" parent="MenuContainer"]
|
[node name="ExitButton" type="Button" parent="MenuContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Exit"
|
text = "Exit"
|
||||||
@@ -120,4 +124,5 @@ layout_mode = 1
|
|||||||
|
|
||||||
[connection signal="pressed" from="MenuContainer/NewGameButton" to="." method="_on_new_game_button_pressed"]
|
[connection signal="pressed" from="MenuContainer/NewGameButton" to="." method="_on_new_game_button_pressed"]
|
||||||
[connection signal="pressed" from="MenuContainer/SettingsButton" to="." method="_on_settings_button_pressed"]
|
[connection signal="pressed" from="MenuContainer/SettingsButton" to="." method="_on_settings_button_pressed"]
|
||||||
|
[connection signal="pressed" from="MenuContainer/CreditsButton" to="." method="_on_credits_button_pressed"]
|
||||||
[connection signal="pressed" from="MenuContainer/ExitButton" to="." method="_on_exit_button_pressed"]
|
[connection signal="pressed" from="MenuContainer/ExitButton" to="." method="_on_exit_button_pressed"]
|
||||||
|
|||||||
Reference in New Issue
Block a user