init game files
This commit is contained in:
67
scripts/ui/DamageDisplay.gd
Normal file
67
scripts/ui/DamageDisplay.gd
Normal file
@@ -0,0 +1,67 @@
|
||||
class_name DamageDisplay
|
||||
extends Control
|
||||
|
||||
## DamageDisplay - Shows player damage counters
|
||||
|
||||
# Display settings
|
||||
const MAX_DAMAGE: int = 7
|
||||
const MARKER_SIZE: float = 30.0
|
||||
const MARKER_SPACING: float = 5.0
|
||||
|
||||
# UI elements
|
||||
var markers: Array[ColorRect] = []
|
||||
var damage_label: Label
|
||||
|
||||
# Current damage
|
||||
var current_damage: int = 0
|
||||
|
||||
func _ready() -> void:
|
||||
_create_display()
|
||||
|
||||
func _create_display() -> void:
|
||||
# Container for markers
|
||||
var marker_container = HBoxContainer.new()
|
||||
add_child(marker_container)
|
||||
marker_container.add_theme_constant_override("separation", int(MARKER_SPACING))
|
||||
|
||||
# Create damage markers
|
||||
for i in range(MAX_DAMAGE):
|
||||
var marker = ColorRect.new()
|
||||
marker.custom_minimum_size = Vector2(MARKER_SIZE, MARKER_SIZE)
|
||||
marker.color = Color(0.3, 0.3, 0.3) # Empty state
|
||||
marker_container.add_child(marker)
|
||||
markers.append(marker)
|
||||
|
||||
# Damage count label
|
||||
damage_label = Label.new()
|
||||
damage_label.text = "0 / 7"
|
||||
damage_label.position = Vector2(0, MARKER_SIZE + 5)
|
||||
damage_label.add_theme_font_size_override("font_size", 14)
|
||||
add_child(damage_label)
|
||||
|
||||
## Update damage display
|
||||
func set_damage(amount: int) -> void:
|
||||
current_damage = clampi(amount, 0, MAX_DAMAGE)
|
||||
|
||||
# Update markers
|
||||
for i in range(MAX_DAMAGE):
|
||||
if i < current_damage:
|
||||
markers[i].color = Color(0.8, 0.2, 0.2) # Damage taken
|
||||
else:
|
||||
markers[i].color = Color(0.3, 0.3, 0.3) # Empty
|
||||
|
||||
# Update label
|
||||
damage_label.text = str(current_damage) + " / " + str(MAX_DAMAGE)
|
||||
|
||||
# Flash on damage change
|
||||
if current_damage > 0:
|
||||
_flash_latest()
|
||||
|
||||
func _flash_latest() -> void:
|
||||
if current_damage <= 0 or current_damage > MAX_DAMAGE:
|
||||
return
|
||||
|
||||
var marker = markers[current_damage - 1]
|
||||
var tween = create_tween()
|
||||
tween.tween_property(marker, "color", Color(1.0, 0.5, 0.5), 0.1)
|
||||
tween.tween_property(marker, "color", Color(0.8, 0.2, 0.2), 0.2)
|
||||
336
scripts/ui/GameUI.gd
Normal file
336
scripts/ui/GameUI.gd
Normal file
@@ -0,0 +1,336 @@
|
||||
class_name GameUI
|
||||
extends CanvasLayer
|
||||
|
||||
## GameUI - Main UI overlay for game information and controls
|
||||
|
||||
signal end_phase_pressed
|
||||
signal pass_priority_pressed
|
||||
|
||||
# UI Components
|
||||
var turn_panel: PanelContainer
|
||||
var phase_panel: PanelContainer
|
||||
var cp_panel: PanelContainer
|
||||
var message_panel: PanelContainer
|
||||
var card_detail_panel: PanelContainer
|
||||
var action_buttons: HBoxContainer
|
||||
|
||||
# Labels
|
||||
var turn_label: Label
|
||||
var phase_label: Label
|
||||
var cp_label: Label
|
||||
var message_label: Label
|
||||
|
||||
# Card detail labels
|
||||
var detail_name_label: Label
|
||||
var detail_type_label: Label
|
||||
var detail_cost_label: Label
|
||||
var detail_power_label: Label
|
||||
var detail_element_label: Label
|
||||
var detail_ability_label: Label
|
||||
|
||||
# Buttons
|
||||
var end_phase_button: Button
|
||||
var pass_button: Button
|
||||
|
||||
# Message queue
|
||||
var message_queue: Array[String] = []
|
||||
var message_timer: Timer
|
||||
const MESSAGE_DISPLAY_TIME: float = 3.0
|
||||
|
||||
func _ready() -> void:
|
||||
_create_ui()
|
||||
_connect_signals()
|
||||
|
||||
func _create_ui() -> void:
|
||||
# Root control that fills the screen
|
||||
var root = Control.new()
|
||||
add_child(root)
|
||||
root.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
root.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
# === TOP BAR ===
|
||||
var top_bar = HBoxContainer.new()
|
||||
root.add_child(top_bar)
|
||||
top_bar.set_anchors_preset(Control.PRESET_TOP_WIDE)
|
||||
top_bar.offset_left = 10
|
||||
top_bar.offset_right = -10
|
||||
top_bar.offset_top = 10
|
||||
top_bar.offset_bottom = 70
|
||||
top_bar.add_theme_constant_override("separation", 20)
|
||||
|
||||
# Turn panel (top left)
|
||||
turn_panel = _create_panel()
|
||||
top_bar.add_child(turn_panel)
|
||||
var turn_vbox = VBoxContainer.new()
|
||||
turn_panel.add_child(turn_vbox)
|
||||
|
||||
var turn_header = Label.new()
|
||||
turn_header.text = "Turn"
|
||||
turn_header.add_theme_font_size_override("font_size", 12)
|
||||
turn_vbox.add_child(turn_header)
|
||||
|
||||
turn_label = Label.new()
|
||||
turn_label.text = "Player 1 - Turn 1"
|
||||
turn_label.add_theme_font_size_override("font_size", 16)
|
||||
turn_vbox.add_child(turn_label)
|
||||
|
||||
# Phase panel (top center-left)
|
||||
phase_panel = _create_panel()
|
||||
top_bar.add_child(phase_panel)
|
||||
var phase_vbox = VBoxContainer.new()
|
||||
phase_panel.add_child(phase_vbox)
|
||||
|
||||
var phase_header = Label.new()
|
||||
phase_header.text = "Phase"
|
||||
phase_header.add_theme_font_size_override("font_size", 12)
|
||||
phase_vbox.add_child(phase_header)
|
||||
|
||||
phase_label = Label.new()
|
||||
phase_label.text = "Active Phase"
|
||||
phase_label.add_theme_font_size_override("font_size", 16)
|
||||
phase_vbox.add_child(phase_label)
|
||||
|
||||
# CP panel
|
||||
cp_panel = _create_panel()
|
||||
top_bar.add_child(cp_panel)
|
||||
var cp_vbox = VBoxContainer.new()
|
||||
cp_panel.add_child(cp_vbox)
|
||||
|
||||
var cp_header = Label.new()
|
||||
cp_header.text = "CP Pool"
|
||||
cp_header.add_theme_font_size_override("font_size", 12)
|
||||
cp_vbox.add_child(cp_header)
|
||||
|
||||
cp_label = Label.new()
|
||||
cp_label.text = "0 CP"
|
||||
cp_label.add_theme_font_size_override("font_size", 16)
|
||||
cp_vbox.add_child(cp_label)
|
||||
|
||||
# Spacer to push buttons to the right
|
||||
var spacer = Control.new()
|
||||
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
top_bar.add_child(spacer)
|
||||
|
||||
# Action buttons in top bar (right side)
|
||||
action_buttons = HBoxContainer.new()
|
||||
top_bar.add_child(action_buttons)
|
||||
action_buttons.add_theme_constant_override("separation", 10)
|
||||
|
||||
pass_button = Button.new()
|
||||
pass_button.text = "Pass"
|
||||
pass_button.custom_minimum_size = Vector2(80, 40)
|
||||
action_buttons.add_child(pass_button)
|
||||
|
||||
end_phase_button = Button.new()
|
||||
end_phase_button.text = "End Phase"
|
||||
end_phase_button.custom_minimum_size = Vector2(100, 40)
|
||||
action_buttons.add_child(end_phase_button)
|
||||
|
||||
# === MESSAGE PANEL (center, above hand) ===
|
||||
message_panel = _create_panel()
|
||||
root.add_child(message_panel)
|
||||
message_panel.set_anchors_preset(Control.PRESET_CENTER)
|
||||
message_panel.position = Vector2(-100, 200)
|
||||
message_panel.custom_minimum_size = Vector2(200, 40)
|
||||
|
||||
message_label = Label.new()
|
||||
message_label.text = ""
|
||||
message_label.add_theme_font_size_override("font_size", 16)
|
||||
message_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
message_panel.add_child(message_label)
|
||||
message_panel.visible = false
|
||||
|
||||
# === CARD DETAIL PANEL (right side) ===
|
||||
card_detail_panel = _create_panel()
|
||||
root.add_child(card_detail_panel)
|
||||
card_detail_panel.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
|
||||
card_detail_panel.offset_left = -220
|
||||
card_detail_panel.offset_right = -10
|
||||
card_detail_panel.offset_top = -150
|
||||
card_detail_panel.offset_bottom = 150
|
||||
card_detail_panel.visible = false
|
||||
|
||||
var detail_vbox = VBoxContainer.new()
|
||||
card_detail_panel.add_child(detail_vbox)
|
||||
detail_vbox.add_theme_constant_override("separation", 5)
|
||||
|
||||
detail_name_label = Label.new()
|
||||
detail_name_label.text = "Card Name"
|
||||
detail_name_label.add_theme_font_size_override("font_size", 18)
|
||||
detail_vbox.add_child(detail_name_label)
|
||||
|
||||
var separator = HSeparator.new()
|
||||
detail_vbox.add_child(separator)
|
||||
|
||||
detail_type_label = Label.new()
|
||||
detail_type_label.text = "Type: Forward"
|
||||
detail_vbox.add_child(detail_type_label)
|
||||
|
||||
detail_element_label = Label.new()
|
||||
detail_element_label.text = "Element: Fire"
|
||||
detail_vbox.add_child(detail_element_label)
|
||||
|
||||
detail_cost_label = Label.new()
|
||||
detail_cost_label.text = "Cost: 3"
|
||||
detail_vbox.add_child(detail_cost_label)
|
||||
|
||||
detail_power_label = Label.new()
|
||||
detail_power_label.text = "Power: 7000"
|
||||
detail_vbox.add_child(detail_power_label)
|
||||
|
||||
var separator2 = HSeparator.new()
|
||||
detail_vbox.add_child(separator2)
|
||||
|
||||
var ability_header = Label.new()
|
||||
ability_header.text = "Abilities:"
|
||||
ability_header.add_theme_font_size_override("font_size", 12)
|
||||
detail_vbox.add_child(ability_header)
|
||||
|
||||
detail_ability_label = Label.new()
|
||||
detail_ability_label.text = ""
|
||||
detail_ability_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
detail_ability_label.custom_minimum_size.x = 180
|
||||
detail_vbox.add_child(detail_ability_label)
|
||||
|
||||
# Message timer
|
||||
message_timer = Timer.new()
|
||||
add_child(message_timer)
|
||||
message_timer.one_shot = true
|
||||
message_timer.timeout.connect(_on_message_timer_timeout)
|
||||
|
||||
func _create_panel() -> PanelContainer:
|
||||
var panel = PanelContainer.new()
|
||||
|
||||
# Create stylebox
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.1, 0.1, 0.15, 0.9)
|
||||
style.border_color = Color(0.3, 0.3, 0.4)
|
||||
style.set_border_width_all(2)
|
||||
style.set_corner_radius_all(5)
|
||||
style.set_content_margin_all(8)
|
||||
|
||||
panel.add_theme_stylebox_override("panel", style)
|
||||
return panel
|
||||
|
||||
func _connect_signals() -> void:
|
||||
end_phase_button.pressed.connect(_on_end_phase_pressed)
|
||||
pass_button.pressed.connect(_on_pass_priority_pressed)
|
||||
|
||||
# Connect to GameManager signals
|
||||
if GameManager:
|
||||
GameManager.turn_changed.connect(_on_turn_changed)
|
||||
GameManager.phase_changed.connect(_on_phase_changed)
|
||||
GameManager.message.connect(show_message)
|
||||
|
||||
func _on_end_phase_pressed() -> void:
|
||||
end_phase_pressed.emit()
|
||||
if GameManager:
|
||||
GameManager.pass_priority()
|
||||
|
||||
func _on_pass_priority_pressed() -> void:
|
||||
pass_priority_pressed.emit()
|
||||
if GameManager:
|
||||
GameManager.pass_priority()
|
||||
|
||||
func _on_turn_changed(player_name: String, turn_number: int) -> void:
|
||||
turn_label.text = player_name + " - Turn " + str(turn_number)
|
||||
|
||||
func _on_phase_changed(phase_name: String) -> void:
|
||||
phase_label.text = phase_name
|
||||
|
||||
## Update CP display
|
||||
func update_cp_display(cp_pool: CPPool) -> void:
|
||||
if not cp_pool:
|
||||
cp_label.text = "0 CP"
|
||||
return
|
||||
|
||||
var total = cp_pool.get_total_cp()
|
||||
var text = str(total) + " CP"
|
||||
|
||||
# Show element breakdown if any specific element CP
|
||||
var elements_text = []
|
||||
for element in Enums.Element.values():
|
||||
var amount = cp_pool.get_cp(element)
|
||||
if amount > 0:
|
||||
var elem_name = Enums.element_to_string(element)
|
||||
elements_text.append(elem_name + ":" + str(amount))
|
||||
|
||||
if elements_text.size() > 0:
|
||||
text += "\n" + ", ".join(elements_text)
|
||||
|
||||
cp_label.text = text
|
||||
|
||||
## Show a message
|
||||
func show_message(text: String) -> void:
|
||||
message_queue.append(text)
|
||||
if not message_timer.is_stopped():
|
||||
return
|
||||
_show_next_message()
|
||||
|
||||
func _show_next_message() -> void:
|
||||
if message_queue.is_empty():
|
||||
message_panel.visible = false
|
||||
return
|
||||
|
||||
var text = message_queue.pop_front()
|
||||
message_label.text = text
|
||||
message_panel.visible = true
|
||||
message_timer.start(MESSAGE_DISPLAY_TIME)
|
||||
|
||||
func _on_message_timer_timeout() -> void:
|
||||
_show_next_message()
|
||||
|
||||
## Show card detail panel
|
||||
func show_card_detail(card: CardInstance) -> void:
|
||||
if not card or not card.card_data:
|
||||
card_detail_panel.visible = false
|
||||
return
|
||||
|
||||
var data = card.card_data
|
||||
detail_name_label.text = data.name
|
||||
detail_type_label.text = "Type: " + Enums.card_type_to_string(data.type)
|
||||
detail_cost_label.text = "Cost: " + str(data.cost)
|
||||
|
||||
# Element
|
||||
var element_strs = []
|
||||
for elem in data.elements:
|
||||
element_strs.append(Enums.element_to_string(elem))
|
||||
detail_element_label.text = "Element: " + "/".join(element_strs)
|
||||
|
||||
# Power
|
||||
if data.type == Enums.CardType.FORWARD or data.power > 0:
|
||||
detail_power_label.text = "Power: " + str(card.get_power())
|
||||
detail_power_label.visible = true
|
||||
else:
|
||||
detail_power_label.visible = false
|
||||
|
||||
# Abilities
|
||||
var ability_text = ""
|
||||
for ability in data.abilities:
|
||||
if ability_text != "":
|
||||
ability_text += "\n\n"
|
||||
|
||||
var type_str = ""
|
||||
match ability.type:
|
||||
Enums.AbilityType.FIELD: type_str = "[Field]"
|
||||
Enums.AbilityType.AUTO: type_str = "[Auto]"
|
||||
Enums.AbilityType.ACTION: type_str = "[Action]"
|
||||
Enums.AbilityType.SPECIAL: type_str = "[Special]"
|
||||
|
||||
ability_text += type_str
|
||||
if ability.name != "":
|
||||
ability_text += " " + ability.name
|
||||
ability_text += "\n" + ability.effect
|
||||
|
||||
detail_ability_label.text = ability_text if ability_text != "" else "No abilities"
|
||||
|
||||
card_detail_panel.visible = true
|
||||
|
||||
## Hide card detail panel
|
||||
func hide_card_detail() -> void:
|
||||
card_detail_panel.visible = false
|
||||
|
||||
## Update button states based on game phase
|
||||
func update_button_states(can_end_phase: bool, can_pass: bool) -> void:
|
||||
end_phase_button.disabled = not can_end_phase
|
||||
pass_button.disabled = not can_pass
|
||||
238
scripts/ui/HandDisplay.gd
Normal file
238
scripts/ui/HandDisplay.gd
Normal file
@@ -0,0 +1,238 @@
|
||||
class_name HandDisplay
|
||||
extends Control
|
||||
|
||||
## HandDisplay - Shows the current player's hand in a fan layout
|
||||
|
||||
signal card_selected(card_instance: CardInstance)
|
||||
signal card_hovered(card_instance: CardInstance)
|
||||
signal card_unhovered
|
||||
|
||||
# Hand card visuals
|
||||
var hand_cards: Array[Control] = []
|
||||
var card_instances: Array[CardInstance] = []
|
||||
|
||||
# Layout settings
|
||||
const CARD_WIDTH: float = 100.0
|
||||
const CARD_HEIGHT: float = 140.0
|
||||
const CARD_OVERLAP: float = 70.0
|
||||
const FAN_ANGLE: float = 2.0 # Degrees per card from center
|
||||
const HOVER_LIFT: float = 25.0
|
||||
|
||||
# Current hover state
|
||||
var hovered_card_index: int = -1
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
# Connect to resized signal to re-layout when size changes
|
||||
resized.connect(_on_resized)
|
||||
|
||||
func _on_resized() -> void:
|
||||
_layout_cards()
|
||||
|
||||
## Update hand display with cards
|
||||
func update_hand(cards: Array) -> void:
|
||||
# Clear existing
|
||||
for card_ui in hand_cards:
|
||||
card_ui.queue_free()
|
||||
hand_cards.clear()
|
||||
card_instances.clear()
|
||||
|
||||
# Store card instances
|
||||
for card in cards:
|
||||
if card is CardInstance:
|
||||
card_instances.append(card)
|
||||
|
||||
# Create card UI elements
|
||||
_create_card_visuals()
|
||||
|
||||
# Defer layout to ensure size is set
|
||||
call_deferred("_layout_cards")
|
||||
|
||||
func _create_card_visuals() -> void:
|
||||
for i in range(card_instances.size()):
|
||||
var card = card_instances[i]
|
||||
var card_ui = _create_card_ui(card, i)
|
||||
add_child(card_ui)
|
||||
hand_cards.append(card_ui)
|
||||
|
||||
func _create_card_ui(card: CardInstance, index: int) -> Control:
|
||||
# Use Panel for better input handling
|
||||
var container = Panel.new()
|
||||
container.custom_minimum_size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
||||
container.size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
||||
container.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
# Style the panel with element color
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = _get_element_color(card.card_data.get_primary_element())
|
||||
style.border_color = Color.BLACK
|
||||
style.set_border_width_all(2)
|
||||
container.add_theme_stylebox_override("panel", style)
|
||||
|
||||
# Card background (keep for highlighting)
|
||||
var bg = ColorRect.new()
|
||||
bg.size = Vector2(CARD_WIDTH, CARD_HEIGHT)
|
||||
bg.color = _get_element_color(card.card_data.get_primary_element())
|
||||
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE # Let parent handle input
|
||||
container.add_child(bg)
|
||||
|
||||
# Card name
|
||||
var name_label = Label.new()
|
||||
name_label.text = card.card_data.name
|
||||
name_label.position = Vector2(5, 5)
|
||||
name_label.size = Vector2(CARD_WIDTH - 10, 50)
|
||||
name_label.add_theme_font_size_override("font_size", 11)
|
||||
name_label.add_theme_color_override("font_color", Color.BLACK)
|
||||
name_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
container.add_child(name_label)
|
||||
|
||||
# Cost indicator (top right)
|
||||
var cost_bg = ColorRect.new()
|
||||
cost_bg.size = Vector2(28, 28)
|
||||
cost_bg.position = Vector2(CARD_WIDTH - 32, 4)
|
||||
cost_bg.color = Color(0.2, 0.2, 0.2)
|
||||
cost_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
container.add_child(cost_bg)
|
||||
|
||||
var cost_label = Label.new()
|
||||
cost_label.text = str(card.card_data.cost)
|
||||
cost_label.position = Vector2(CARD_WIDTH - 32, 4)
|
||||
cost_label.size = Vector2(28, 28)
|
||||
cost_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
cost_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
cost_label.add_theme_font_size_override("font_size", 16)
|
||||
cost_label.add_theme_color_override("font_color", Color.WHITE)
|
||||
cost_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
container.add_child(cost_label)
|
||||
|
||||
# Type indicator (bottom left)
|
||||
var type_label = Label.new()
|
||||
type_label.text = Enums.card_type_to_string(card.card_data.type)
|
||||
type_label.position = Vector2(5, CARD_HEIGHT - 22)
|
||||
type_label.add_theme_font_size_override("font_size", 10)
|
||||
type_label.add_theme_color_override("font_color", Color.BLACK)
|
||||
type_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
container.add_child(type_label)
|
||||
|
||||
# Power for forwards (bottom right)
|
||||
if card.card_data.type == Enums.CardType.FORWARD:
|
||||
var power_bg = ColorRect.new()
|
||||
power_bg.size = Vector2(45, 20)
|
||||
power_bg.position = Vector2(CARD_WIDTH - 50, CARD_HEIGHT - 24)
|
||||
power_bg.color = Color(0.3, 0.1, 0.1, 0.8)
|
||||
power_bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
container.add_child(power_bg)
|
||||
|
||||
var power_label = Label.new()
|
||||
power_label.text = str(card.card_data.power)
|
||||
power_label.position = Vector2(CARD_WIDTH - 50, CARD_HEIGHT - 24)
|
||||
power_label.size = Vector2(45, 20)
|
||||
power_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
power_label.add_theme_font_size_override("font_size", 12)
|
||||
power_label.add_theme_color_override("font_color", Color.WHITE)
|
||||
power_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
container.add_child(power_label)
|
||||
|
||||
# Connect signals
|
||||
container.gui_input.connect(_on_card_gui_input.bind(index))
|
||||
container.mouse_entered.connect(_on_card_mouse_entered.bind(index))
|
||||
container.mouse_exited.connect(_on_card_mouse_exited.bind(index))
|
||||
|
||||
return container
|
||||
|
||||
func _get_element_color(element: Enums.Element) -> Color:
|
||||
var color = Enums.get_element_color(element)
|
||||
# Lighten for card background
|
||||
return color.lightened(0.4)
|
||||
|
||||
func _layout_cards() -> void:
|
||||
var card_count = hand_cards.size()
|
||||
if card_count == 0:
|
||||
return
|
||||
|
||||
# Get actual display width - use size if available, otherwise viewport
|
||||
var display_width = size.x
|
||||
|
||||
if display_width <= 0:
|
||||
var viewport = get_viewport()
|
||||
if viewport:
|
||||
display_width = viewport.get_visible_rect().size.x - 100 # Leave margins
|
||||
|
||||
if display_width <= 0:
|
||||
display_width = 1820 # Fallback
|
||||
|
||||
# Calculate total width needed
|
||||
var total_width = CARD_WIDTH + (card_count - 1) * CARD_OVERLAP
|
||||
var start_x = (display_width - total_width) / 2.0
|
||||
|
||||
# Position each card
|
||||
for i in range(card_count):
|
||||
var card_ui = hand_cards[i]
|
||||
|
||||
# X position with overlap
|
||||
var x = start_x + i * CARD_OVERLAP
|
||||
|
||||
# Y position (near top of container, with hover lift)
|
||||
var y = 10.0
|
||||
if i == hovered_card_index:
|
||||
y -= HOVER_LIFT
|
||||
|
||||
# Rotation based on position in fan
|
||||
var center_offset = i - (card_count - 1) / 2.0
|
||||
var angle = center_offset * FAN_ANGLE
|
||||
|
||||
card_ui.position = Vector2(x, y)
|
||||
card_ui.rotation = deg_to_rad(angle)
|
||||
card_ui.z_index = i
|
||||
if i == hovered_card_index:
|
||||
card_ui.z_index = 100
|
||||
|
||||
func _on_card_gui_input(event: InputEvent, index: int) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
print("HandDisplay: Card clicked, index=", index)
|
||||
if index >= 0 and index < card_instances.size():
|
||||
print("HandDisplay: Emitting card_selected for ", card_instances[index].card_data.name)
|
||||
card_selected.emit(card_instances[index])
|
||||
|
||||
func _on_card_mouse_entered(index: int) -> void:
|
||||
hovered_card_index = index
|
||||
_layout_cards()
|
||||
if index >= 0 and index < card_instances.size():
|
||||
card_hovered.emit(card_instances[index])
|
||||
|
||||
func _on_card_mouse_exited(index: int) -> void:
|
||||
if hovered_card_index == index:
|
||||
hovered_card_index = -1
|
||||
_layout_cards()
|
||||
card_unhovered.emit()
|
||||
|
||||
## Highlight playable cards
|
||||
func highlight_playable(predicate: Callable) -> void:
|
||||
for i in range(hand_cards.size()):
|
||||
var card_ui = hand_cards[i]
|
||||
var card = card_instances[i]
|
||||
|
||||
var bg = card_ui.get_child(0) as ColorRect
|
||||
if bg:
|
||||
if predicate.call(card):
|
||||
# Add glow effect - brighter
|
||||
bg.color = _get_element_color(card.card_data.get_primary_element()).lightened(0.2)
|
||||
else:
|
||||
# Dim non-playable
|
||||
bg.color = _get_element_color(card.card_data.get_primary_element()).darkened(0.4)
|
||||
|
||||
## Clear all highlights
|
||||
func clear_highlights() -> void:
|
||||
for i in range(hand_cards.size()):
|
||||
var card_ui = hand_cards[i]
|
||||
var card = card_instances[i]
|
||||
|
||||
var bg = card_ui.get_child(0) as ColorRect
|
||||
if bg:
|
||||
bg.color = _get_element_color(card.card_data.get_primary_element())
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_RESIZED:
|
||||
_layout_cards()
|
||||
94
scripts/ui/MainMenu.gd
Normal file
94
scripts/ui/MainMenu.gd
Normal file
@@ -0,0 +1,94 @@
|
||||
class_name MainMenu
|
||||
extends CanvasLayer
|
||||
|
||||
## MainMenu - Main menu screen
|
||||
|
||||
signal start_game
|
||||
signal quit_game
|
||||
|
||||
# UI Components
|
||||
var title_label: Label
|
||||
var start_button: Button
|
||||
var options_button: Button
|
||||
var quit_button: Button
|
||||
var version_label: Label
|
||||
|
||||
func _ready() -> void:
|
||||
_create_menu()
|
||||
|
||||
func _create_menu() -> void:
|
||||
# Background
|
||||
var bg = ColorRect.new()
|
||||
add_child(bg)
|
||||
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
bg.color = Color(0.08, 0.08, 0.12)
|
||||
|
||||
# Center container
|
||||
var center = CenterContainer.new()
|
||||
add_child(center)
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
|
||||
var vbox = VBoxContainer.new()
|
||||
center.add_child(vbox)
|
||||
vbox.add_theme_constant_override("separation", 20)
|
||||
|
||||
# Title
|
||||
title_label = Label.new()
|
||||
title_label.text = "FF-TCG Digital"
|
||||
title_label.add_theme_font_size_override("font_size", 48)
|
||||
title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(title_label)
|
||||
|
||||
# Subtitle
|
||||
var subtitle = Label.new()
|
||||
subtitle.text = "Final Fantasy Trading Card Game"
|
||||
subtitle.add_theme_font_size_override("font_size", 18)
|
||||
subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
subtitle.add_theme_color_override("font_color", Color(0.6, 0.6, 0.7))
|
||||
vbox.add_child(subtitle)
|
||||
|
||||
# Spacer
|
||||
var spacer = Control.new()
|
||||
spacer.custom_minimum_size = Vector2(0, 40)
|
||||
vbox.add_child(spacer)
|
||||
|
||||
# Start button
|
||||
start_button = _create_menu_button("Start Game")
|
||||
vbox.add_child(start_button)
|
||||
start_button.pressed.connect(_on_start_pressed)
|
||||
|
||||
# Options button (placeholder)
|
||||
options_button = _create_menu_button("Options")
|
||||
options_button.disabled = true
|
||||
vbox.add_child(options_button)
|
||||
|
||||
# Quit button
|
||||
quit_button = _create_menu_button("Quit")
|
||||
vbox.add_child(quit_button)
|
||||
quit_button.pressed.connect(_on_quit_pressed)
|
||||
|
||||
# Version label at bottom
|
||||
version_label = Label.new()
|
||||
version_label.text = "v0.1.0 - Development Build"
|
||||
version_label.add_theme_font_size_override("font_size", 12)
|
||||
version_label.add_theme_color_override("font_color", Color(0.4, 0.4, 0.5))
|
||||
add_child(version_label)
|
||||
version_label.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
version_label.offset_left = -200
|
||||
version_label.offset_top = -30
|
||||
version_label.offset_right = -10
|
||||
version_label.offset_bottom = -10
|
||||
|
||||
func _create_menu_button(text: String) -> Button:
|
||||
var button = Button.new()
|
||||
button.text = text
|
||||
button.custom_minimum_size = Vector2(200, 50)
|
||||
button.add_theme_font_size_override("font_size", 20)
|
||||
return button
|
||||
|
||||
func _on_start_pressed() -> void:
|
||||
start_game.emit()
|
||||
|
||||
func _on_quit_pressed() -> void:
|
||||
quit_game.emit()
|
||||
get_tree().quit()
|
||||
111
scripts/ui/PauseMenu.gd
Normal file
111
scripts/ui/PauseMenu.gd
Normal file
@@ -0,0 +1,111 @@
|
||||
class_name PauseMenu
|
||||
extends CanvasLayer
|
||||
|
||||
## PauseMenu - In-game pause/escape menu
|
||||
|
||||
signal resume_game
|
||||
signal restart_game
|
||||
signal return_to_menu
|
||||
|
||||
# UI Components
|
||||
var panel: PanelContainer
|
||||
var resume_button: Button
|
||||
var restart_button: Button
|
||||
var menu_button: Button
|
||||
|
||||
var menu_is_visible: bool = false
|
||||
|
||||
func _ready() -> void:
|
||||
_create_menu()
|
||||
hide_menu()
|
||||
|
||||
func _create_menu() -> void:
|
||||
# Semi-transparent background
|
||||
var bg = ColorRect.new()
|
||||
add_child(bg)
|
||||
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
bg.color = Color(0, 0, 0, 0.7)
|
||||
bg.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
# Center container
|
||||
var center = CenterContainer.new()
|
||||
add_child(center)
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
|
||||
# Panel
|
||||
panel = PanelContainer.new()
|
||||
center.add_child(panel)
|
||||
|
||||
var style = StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.15, 0.15, 0.2)
|
||||
style.border_color = Color(0.3, 0.3, 0.4)
|
||||
style.set_border_width_all(2)
|
||||
style.set_corner_radius_all(10)
|
||||
style.set_content_margin_all(30)
|
||||
panel.add_theme_stylebox_override("panel", style)
|
||||
|
||||
var vbox = VBoxContainer.new()
|
||||
panel.add_child(vbox)
|
||||
vbox.add_theme_constant_override("separation", 15)
|
||||
|
||||
# Title
|
||||
var title = Label.new()
|
||||
title.text = "Game Paused"
|
||||
title.add_theme_font_size_override("font_size", 28)
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(title)
|
||||
|
||||
# Spacer
|
||||
var spacer = Control.new()
|
||||
spacer.custom_minimum_size = Vector2(0, 10)
|
||||
vbox.add_child(spacer)
|
||||
|
||||
# Resume button
|
||||
resume_button = _create_menu_button("Resume")
|
||||
vbox.add_child(resume_button)
|
||||
resume_button.pressed.connect(_on_resume_pressed)
|
||||
|
||||
# Restart button
|
||||
restart_button = _create_menu_button("Restart Game")
|
||||
vbox.add_child(restart_button)
|
||||
restart_button.pressed.connect(_on_restart_pressed)
|
||||
|
||||
# Return to menu button
|
||||
menu_button = _create_menu_button("Main Menu")
|
||||
vbox.add_child(menu_button)
|
||||
menu_button.pressed.connect(_on_menu_pressed)
|
||||
|
||||
func _create_menu_button(text: String) -> Button:
|
||||
var button = Button.new()
|
||||
button.text = text
|
||||
button.custom_minimum_size = Vector2(180, 45)
|
||||
button.add_theme_font_size_override("font_size", 18)
|
||||
return button
|
||||
|
||||
func show_menu() -> void:
|
||||
visible = true
|
||||
menu_is_visible = true
|
||||
get_tree().paused = true
|
||||
|
||||
func hide_menu() -> void:
|
||||
visible = false
|
||||
menu_is_visible = false
|
||||
get_tree().paused = false
|
||||
|
||||
func toggle_menu() -> void:
|
||||
if menu_is_visible:
|
||||
hide_menu()
|
||||
else:
|
||||
show_menu()
|
||||
|
||||
func _on_resume_pressed() -> void:
|
||||
hide_menu()
|
||||
resume_game.emit()
|
||||
|
||||
func _on_restart_pressed() -> void:
|
||||
hide_menu()
|
||||
restart_game.emit()
|
||||
|
||||
func _on_menu_pressed() -> void:
|
||||
hide_menu()
|
||||
return_to_menu.emit()
|
||||
Reference in New Issue
Block a user