feat: add basic UI scenes (card, hand, training room, main menu)
This commit is contained in:
37
src/ui/components/card_node.gd
Normal file
37
src/ui/components/card_node.gd
Normal file
@@ -0,0 +1,37 @@
|
||||
extends Control
|
||||
|
||||
signal card_clicked(card_node)
|
||||
signal card_double_clicked(card_node)
|
||||
|
||||
var card_data: Card = null
|
||||
var is_selected: bool = false
|
||||
|
||||
@onready var texture_rect: TextureRect = $TextureRect
|
||||
@onready var label: Label = $Label
|
||||
|
||||
func setup(card: Card) -> void:
|
||||
card_data = card
|
||||
update_display()
|
||||
|
||||
func update_display() -> void:
|
||||
if card_data == null:
|
||||
return
|
||||
var suits := ["S", "H", "C", "D", "SJ", "BJ"]
|
||||
var ranks := ["", "", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "SJ", "BJ"]
|
||||
var suit := card_data.suit()
|
||||
var rank := card_data.rank()
|
||||
if rank < ranks.size() and suit < suits.size():
|
||||
label.text = "%s %s" % [suits[suit], ranks[rank]]
|
||||
modulate = Color.WHITE if not is_selected else Color(1.2, 1.2, 0.8)
|
||||
|
||||
func set_selected(sel: bool) -> void:
|
||||
is_selected = sel
|
||||
update_display()
|
||||
|
||||
func _on_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
|
||||
if event.double_click:
|
||||
card_double_clicked.emit(self)
|
||||
else:
|
||||
card_clicked.emit(self)
|
||||
16
src/ui/components/card_node.tscn
Normal file
16
src/ui/components/card_node.tscn
Normal file
@@ -0,0 +1,16 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://card_node"]
|
||||
[ext_resource type="Script" path="res://src/ui/components/card_node.gd" id="1_script"]
|
||||
[node name="CardNode" type="Control"]
|
||||
custom_minimum_size = Vector2(80, 120)
|
||||
size = Vector2(80, 120)
|
||||
script = ExtResource("1_script")
|
||||
[node name="TextureRect" type="TextureRect" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 80.0
|
||||
offset_bottom = 120.0
|
||||
[node name="Label" type="Label" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 80.0
|
||||
offset_bottom = 120.0
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
61
src/ui/components/hand_area.gd
Normal file
61
src/ui/components/hand_area.gd
Normal file
@@ -0,0 +1,61 @@
|
||||
extends HBoxContainer
|
||||
|
||||
signal cards_selected(selected: Array)
|
||||
signal hint_requested()
|
||||
signal play_requested()
|
||||
signal pass_requested()
|
||||
|
||||
var card_nodes: Array = []
|
||||
var selected_cards: Array = []
|
||||
const CARD_SCENE := preload("res://src/ui/components/card_node.tscn")
|
||||
|
||||
var training_controller: TrainingController = null
|
||||
|
||||
func update_hand(hand: Array) -> void:
|
||||
for cn in card_nodes:
|
||||
cn.queue_free()
|
||||
card_nodes.clear()
|
||||
selected_cards.clear()
|
||||
for c in hand:
|
||||
var node: Control = CARD_SCENE.instantiate()
|
||||
node.setup(c)
|
||||
node.card_clicked.connect(_on_card_clicked)
|
||||
node.card_double_clicked.connect(_on_card_double_clicked)
|
||||
add_child(node)
|
||||
card_nodes.append(node)
|
||||
|
||||
func _on_card_clicked(card_node: Node) -> void:
|
||||
card_node.set_selected(not card_node.is_selected)
|
||||
if card_node.is_selected:
|
||||
selected_cards.append(card_node.card_data)
|
||||
else:
|
||||
selected_cards.erase(card_node.card_data)
|
||||
cards_selected.emit(selected_cards)
|
||||
|
||||
func _on_card_double_clicked(card_node: Node) -> void:
|
||||
if not card_node.is_selected:
|
||||
card_node.set_selected(true)
|
||||
selected_cards.append(card_node.card_data)
|
||||
play_requested.emit()
|
||||
|
||||
func _on_hint_pressed() -> void:
|
||||
hint_requested.emit()
|
||||
|
||||
func _on_play_pressed() -> void:
|
||||
play_requested.emit()
|
||||
|
||||
func _on_pass_pressed() -> void:
|
||||
pass_requested.emit()
|
||||
|
||||
func disable_input() -> void:
|
||||
for cn in card_nodes:
|
||||
cn.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
||||
func enable_input() -> void:
|
||||
for cn in card_nodes:
|
||||
cn.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
|
||||
func clear_selection() -> void:
|
||||
for cn in card_nodes:
|
||||
cn.set_selected(false)
|
||||
selected_cards.clear()
|
||||
6
src/ui/components/hand_area.tscn
Normal file
6
src/ui/components/hand_area.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://hand_area"]
|
||||
[ext_resource type="Script" path="res://src/ui/components/hand_area.gd" id="1_script"]
|
||||
[node name="HandArea" type="HBoxContainer"]
|
||||
offset_right = 800.0
|
||||
offset_bottom = 150.0
|
||||
script = ExtResource("1_script")
|
||||
13
src/ui/scenes/main_menu.gd
Normal file
13
src/ui/scenes/main_menu.gd
Normal file
@@ -0,0 +1,13 @@
|
||||
extends Control
|
||||
|
||||
func _ready() -> void:
|
||||
var start_btn := $VBoxContainer/StartButton as Button
|
||||
var quit_btn := $VBoxContainer/QuitButton as Button
|
||||
start_btn.pressed.connect(_on_start_pressed)
|
||||
quit_btn.pressed.connect(_on_quit_pressed)
|
||||
|
||||
func _on_start_pressed() -> void:
|
||||
get_tree().change_scene_to_file("res://src/ui/scenes/training_room.tscn")
|
||||
|
||||
func _on_quit_pressed() -> void:
|
||||
get_tree().quit()
|
||||
17
src/ui/scenes/main_menu.tscn
Normal file
17
src/ui/scenes/main_menu.tscn
Normal file
@@ -0,0 +1,17 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://main_menu"]
|
||||
[ext_resource type="Script" path="res://src/ui/scenes/main_menu.gd" id="1_script"]
|
||||
[node name="MainMenu" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
script = ExtResource("1_script")
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
offset_left = 300.0
|
||||
offset_top = 200.0
|
||||
offset_right = 500.0
|
||||
offset_bottom = 400.0
|
||||
[node name="StartButton" type="Button" parent="VBoxContainer"]
|
||||
text = "开始训练"
|
||||
[node name="QuitButton" type="Button" parent="VBoxContainer"]
|
||||
text = "退出"
|
||||
70
src/ui/scenes/training_room.gd
Normal file
70
src/ui/scenes/training_room.gd
Normal file
@@ -0,0 +1,70 @@
|
||||
extends Control
|
||||
|
||||
var controller: TrainingController
|
||||
|
||||
@onready var hand_area: Node = $HandArea
|
||||
@onready var play_button: Button = $Buttons/PlayButton
|
||||
@onready var pass_button: Button = $Buttons/PassButton
|
||||
@onready var hint_button: Button = $Buttons/HintButton
|
||||
@onready var status_label: Label = $StatusLabel
|
||||
|
||||
func _ready() -> void:
|
||||
hand_area.training_controller = controller
|
||||
play_button.pressed.connect(_on_play_pressed)
|
||||
pass_button.pressed.connect(_on_pass_pressed)
|
||||
hint_button.pressed.connect(_on_hint_pressed)
|
||||
|
||||
func start_training() -> void:
|
||||
controller = TrainingController.new()
|
||||
controller.start_game(Config.rule_config, 0)
|
||||
controller.turn_ready.connect(_on_turn_ready)
|
||||
controller.state_changed.connect(_refresh_ui)
|
||||
controller.game_ended.connect(_on_game_ended)
|
||||
_refresh_ui()
|
||||
|
||||
func _on_turn_ready(player_idx: int, is_human: bool) -> void:
|
||||
if is_human:
|
||||
hand_area.enable_input()
|
||||
status_label.text = "你的回合"
|
||||
else:
|
||||
hand_area.disable_input()
|
||||
status_label.text = "%s 思考中..." % controller.game_state.player_names[player_idx]
|
||||
|
||||
func _on_play_pressed() -> void:
|
||||
var selected := hand_area.selected_cards
|
||||
if selected.is_empty():
|
||||
return
|
||||
var result := controller.handle_human_play(selected)
|
||||
if not result.ok:
|
||||
status_label.text = "无效出牌"
|
||||
return
|
||||
hand_area.clear_selection()
|
||||
_refresh_ui()
|
||||
|
||||
func _on_pass_pressed() -> void:
|
||||
var result := controller.handle_human_pass()
|
||||
if not result.ok:
|
||||
status_label.text = "不能过牌"
|
||||
return
|
||||
_refresh_ui()
|
||||
|
||||
func _on_hint_pressed() -> void:
|
||||
var hint := controller.get_hint()
|
||||
if hint == null or hint.type == -1:
|
||||
status_label.text = "建议:过牌"
|
||||
return
|
||||
hand_area.clear_selection()
|
||||
for card in hint.cards:
|
||||
for cn in hand_area.card_nodes:
|
||||
if cn.card_data != null and cn.card_data.card_id == card.card_id:
|
||||
cn.set_selected(true)
|
||||
hand_area.selected_cards.append(card)
|
||||
status_label.text = "建议牌型: %s (rank=%d)" % [hint.type, hint.primary_rank]
|
||||
|
||||
func _on_game_ended(winner_team: int, reason: String) -> void:
|
||||
status_label.text = "游戏结束! 队伍 %d 获胜" % winner_team
|
||||
hand_area.disable_input()
|
||||
|
||||
func _refresh_ui() -> void:
|
||||
if controller and controller.game_state:
|
||||
hand_area.update_hand(controller.game_state.get_hand(0))
|
||||
28
src/ui/scenes/training_room.tscn
Normal file
28
src/ui/scenes/training_room.tscn
Normal file
@@ -0,0 +1,28 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://training_room"]
|
||||
[ext_resource type="Script" path="res://src/ui/scenes/training_room.gd" id="1_script"]
|
||||
[node name="TrainingRoom" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
script = ExtResource("1_script")
|
||||
[node name="StatusLabel" type="Label" parent="."]
|
||||
layout_mode = 0
|
||||
offset_right = 400.0
|
||||
offset_bottom = 50.0
|
||||
text = "掼蛋训练模式"
|
||||
horizontal_alignment = 1
|
||||
[node name="HandArea" type="HBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
offset_top = 500.0
|
||||
offset_right = 800.0
|
||||
offset_bottom = 650.0
|
||||
[node name="Buttons" type="HBoxContainer" parent="."]
|
||||
layout_mode = 0
|
||||
offset_top = 660.0
|
||||
offset_right = 800.0
|
||||
offset_bottom = 720.0
|
||||
[node name="PlayButton" type="Button" parent="Buttons"]
|
||||
text = "出牌"
|
||||
[node name="PassButton" type="Button" parent="Buttons"]
|
||||
text = "过牌"
|
||||
[node name="HintButton" type="Button" parent="Buttons"]
|
||||
text = "提示"
|
||||
Reference in New Issue
Block a user