feat: 由AI辅助进行游戏内容编写

This commit is contained in:
2026-06-22 11:40:53 +08:00
parent 2d6bf19a10
commit 70bd359f8a
9 changed files with 1992 additions and 455 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ game/
## 最佳实践
本项目目前正在使用 Godot 4.6.2 进行开发。开发用语言为 GDScript。
本项目目前正在使用 Godot 4.7 进行开发。开发用语言为 GDScript。
你的开发应当遵循 Godot 引擎提供的[最佳实践](https://docs.godotengine.org/zh-cn/4.x/tutorials/best_practices/)及[GDScript 编写风格指南](https://docs.godotengine.org/zh-cn/4.x/tutorials/scripting/gdscript/gdscript_styleguide.html)。但下面提到的除外:
-317
View File
@@ -1,317 +0,0 @@
extends Node
var peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new()
var max_player_num: int
var player_num: int
# 玩家数据字典 [peer_id: {"name": str, "hand": Array, "score": int, "is_ready": bool}]
var players_data: Dictionary = {}
var current_turn: int = 1 # 当前回合数
var current_player_id: int = 1 # 当前行动玩家的 peer_id
var game_started: bool = false
var game_phase: String = "waiting" # waiting, playing, ended
var deck: Array = [] # 游戏牌组
func create_server(playern: int) -> void:
max_player_num = playern
player_num = 1
if peer.create_server(GameManager.port, playern) != OK:
print("Failed to create server on port: ", GameManager.port)
return
setup_multiplayer()
# 服务器自己也是玩家
players_data[1] = {
"name": "Host",
"hand": [],
"score": 0,
"is_ready": false
}
print("Server created on port: ", GameManager.port)
func create_client(ip: String) -> void:
if peer.create_client(ip, GameManager.port) != OK:
print("Failed to connect to server: ", ip)
return
setup_multiplayer()
print("Connecting to server: ", ip)
func setup_multiplayer() -> void:
multiplayer.multiplayer_peer = peer
multiplayer.peer_connected.connect(_on_peer_connected)
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
multiplayer.connected_to_server.connect(_on_connected_to_server)
multiplayer.connection_failed.connect(_on_connection_failed)
func _on_connected_to_server() -> void:
print("Connected to server successfully!")
func _on_connection_failed() -> void:
print("Failed to connect to server")
func _on_peer_connected(id: int) -> void:
print("Peer connected: ", id)
add_player(id)
# 如果游戏未开始且达到最大玩家数,询问是否开始游戏
if !game_started and players_data.size() >= max_player_num:
check_all_players_ready()
func _on_peer_disconnected(id: int):
print("Peer disconnected: ", id)
remove_player(id)
# 如果当前玩家断开,切换到下一个玩家
if id == current_player_id and game_started:
next_turn()
func add_player(id: int) -> void:
# 只有服务器才能添加玩家
if multiplayer.is_server():
players_data[id] = {
"name": "Player_" + str(id),
"hand": [],
"score": 0,
"is_ready": false
}
player_num = players_data.size()
# 广播新玩家加入
broadcast_player_list()
print("Player added: ", id, " Total players: ", player_num)
func remove_player(id: int) -> void:
if multiplayer.is_server() and players_data.has(id):
players_data.erase(id)
player_num = players_data.size()
broadcast_player_list()
print("Player removed: ", id)
# RPC: 设置玩家准备状态
@rpc("any_peer", "call_remote", "reliable")
func set_player_ready(is_ready: bool) -> void:
var peer_id = multiplayer.get_remote_sender_id()
if players_data.has(peer_id):
players_data[peer_id]["is_ready"] = is_ready
broadcast_player_list()
if is_ready:
check_all_players_ready()
# RPC: 设置玩家名称
@rpc("any_peer", "call_remote", "reliable")
func set_player_name(name: String) -> void:
var peer_id = multiplayer.get_remote_sender_id()
if players_data.has(peer_id):
players_data[peer_id]["name"] = name
broadcast_player_list()
# RPC: 开始游戏(仅主机调用)
@rpc("call_remote", "reliable")
func start_game() -> void:
if multiplayer.is_server():
if players_data.size() < 2:
print("Need at least 2 players to start game")
return
game_started = true
game_phase = "playing"
current_turn = 1
initialize_deck()
deal_initial_cards()
# 随机选择第一个玩家
var player_ids = players_data.keys()
current_player_id = player_ids[randi() % player_ids.size()]
print("Game started! Current player: ", current_player_id)
broadcast_game_state()
# 初始化牌组
func initialize_deck() -> void:
deck = []
# 这里可以根据 GameManager 中的卡牌列表初始化牌组
if GameManager.card_list:
for card_id in GameManager.card_list:
# 每张卡牌加入多次(根据游戏需求调整)
for i in range(3): # 每张卡牌3份
deck.append(card_id)
# 洗牌
deck.shuffle()
print("Deck initialized with ", deck.size(), " cards")
# 发放初始手牌
func deal_initial_cards() -> void:
var cards_per_player = 5 # 每个玩家初始手牌数
for peer_id in players_data:
for i in range(cards_per_player):
if deck.size() > 0:
var card_id = deck.pop_front()
players_data[peer_id]["hand"].append(card_id)
broadcast_hands()
# RPC: 抽牌
@rpc("any_peer", "call_remote", "reliable")
func draw_card() -> void:
var peer_id = multiplayer.get_remote_sender_id()
# 检查是否是该玩家的回合
if peer_id != current_player_id:
return
# 检查牌组是否还有牌
if deck.size() == 0:
print("Deck is empty!")
return
var card_id = deck.pop_front()
players_data[peer_id]["hand"].append(card_id)
print("Player ", peer_id, " drew card: ", card_id)
broadcast_hands()
# RPC: 打出卡牌
@rpc("any_peer", "call_remote", "reliable")
func play_card(card_index: int, target_player_id: int = -1) -> void:
var peer_id = multiplayer.get_remote_sender_id()
# 检查是否是该玩家的回合
if peer_id != current_player_id:
return
# 检查卡牌索引是否有效
if card_index < 0 or card_index >= players_data[peer_id]["hand"].size():
return
var card_id = players_data[peer_id]["hand"][card_index]
# 移除手牌中的卡牌
players_data[peer_id]["hand"].remove_at(card_index)
# 执行卡牌效果
execute_card_effect(card_id, peer_id, target_player_id)
print("Player ", peer_id, " played card: ", card_id)
broadcast_hands()
broadcast_game_state()
# 执行卡牌效果
func execute_card_effect(card_id: int, player_id: int, target_id: int) -> void:
# 这里根据卡牌ID执行具体效果
# 示例:简单加分效果
if players_data.has(player_id):
players_data[player_id]["score"] += 10
# 可以添加更多复杂的卡牌效果逻辑
print("Card ", card_id, " effect executed by player ", player_id)
# RPC: 结束回合
@rpc("any_peer", "call_remote", "reliable")
func end_turn() -> void:
var peer_id = multiplayer.get_remote_sender_id()
if peer_id != current_player_id:
return
next_turn()
# 切换到下一个玩家
func next_turn() -> void:
var player_ids = players_data.keys()
var current_index = player_ids.find(current_player_id)
if current_index != -1:
current_index = (current_index + 1) % player_ids.size()
current_player_id = player_ids[current_index]
current_turn += 1
print("Turn ", current_turn, ". Current player: ", current_player_id)
broadcast_game_state()
# 检查所有玩家是否都准备好了
func check_all_players_ready() -> void:
var all_ready = true
for player_id in players_data:
if !players_data[player_id]["is_ready"]:
all_ready = false
break
if all_ready and players_data.size() >= 2:
print("All players ready! Starting game...")
start_game.rpc()
# 广播玩家列表
func broadcast_player_list() -> void:
update_players_data.rpc(players_data)
# RPC: 更新玩家数据
@rpc("call_remote", "reliable")
func update_players_data(data: Dictionary) -> void:
players_data = data
player_num = players_data.size()
emit_signal("players_updated", players_data)
# 广播游戏状态
func broadcast_game_state() -> void:
update_game_state.rpc(game_started, game_phase, current_turn, current_player_id)
# RPC: 更新游戏状态
@rpc("call_remote", "reliable")
func update_game_state(started: bool, phase: String, turn: int, current_id: int) -> void:
game_started = started
game_phase = phase
current_turn = turn
current_player_id = current_id
emit_signal("game_state_updated", game_started, game_phase, current_turn, current_player_id)
# 广播手牌信息(只发给对应玩家)
func broadcast_hands() -> void:
for peer_id in players_data:
send_hand.rpc_id(peer_id, players_data[peer_id]["hand"])
# 发送其他玩家的手牌数量
var hand_sizes = {}
for peer_id in players_data:
hand_sizes[peer_id] = players_data[peer_id]["hand"].size()
broadcast_hand_sizes.rpc(hand_sizes)
# RPC: 发送手牌
@rpc("call_remote", "reliable")
func send_hand(hand: Array) -> void:
var peer_id = multiplayer.get_unique_id()
if players_data.has(peer_id):
players_data[peer_id]["hand"] = hand
emit_signal("hand_updated", hand)
# RPC: 广播手牌数量
@rpc("call_remote", "reliable")
func broadcast_hand_sizes(sizes: Dictionary) -> void:
emit_signal("hand_sizes_updated", sizes)
# RPC: 结束游戏
@rpc("call_remote", "reliable")
func end_game() -> void:
game_started = false
game_phase = "ended"
broadcast_game_state()
# 计算并显示最终得分
var scores = {}
for player_id in players_data:
scores[player_id] = players_data[player_id]["score"]
emit_signal("game_ended", scores)
# 信号定义(需要在编辑器中连接或在其他脚本中连接)
signal players_updated(players: Dictionary)
signal game_state_updated(started: bool, phase: String, turn: int, current_player: int)
signal hand_updated(hand: Array)
signal hand_sizes_updated(sizes: Dictionary)
signal game_ended(scores: Dictionary)
-1
View File
@@ -1 +0,0 @@
uid://dkwc5qney4yee
-5
View File
@@ -1,5 +0,0 @@
# AI 生成代码文件
这个目录下的代码都是 AI 生成的,仅作参考。
不要将本目录下的代码直接复制到被使用的代码处。
+80 -2
View File
@@ -1,8 +1,86 @@
[gd_scene format=3 uid="uid://croqe86tpmsco"]
[ext_resource type="Script" uid="uid://di3cxx8th55lg" path="res://scripts/game/card.gd" id="1_3aeal"]
[ext_resource type="FontFile" uid="uid://c2bipf4tpdjj8" path="res://assets/fonts/AlibabaPuHuiTi-3-65-Medium.ttf" id="2_font"]
[node name="Card" type="Node2D" unique_id=1826213731]
[node name="Card" type="Node2D"]
script = ExtResource("1_3aeal")
[node name="Sprite" type="Sprite2D" parent="." unique_id=1627492935]
[node name="Background" type="ColorRect" parent="."]
offset_left = 0.0
offset_top = 0.0
offset_right = 120.0
offset_bottom = 180.0
color = Color(0.75, 0.75, 0.82, 1)
[node name="Sprite" type="Sprite2D" parent="."]
position = Vector2(60, 90)
[node name="NameLabel" type="Label" parent="."]
offset_left = 5.0
offset_top = 5.0
offset_right = 115.0
offset_bottom = 35.0
theme_override_fonts/font = ExtResource("2_font")
theme_override_font_sizes/font_size = 18
text = ""
horizontal_alignment = 0
[node name="DescriptionLabel" type="Label" parent="."]
offset_left = 5.0
offset_top = 140.0
offset_right = 115.0
offset_bottom = 175.0
theme_override_fonts/font = ExtResource("2_font")
theme_override_font_sizes/font_size = 12
text = ""
horizontal_alignment = 0
[node name="ElementLabel" type="Label" parent="."]
offset_left = 5.0
offset_top = 35.0
offset_right = 115.0
offset_bottom = 70.0
theme_override_fonts/font = ExtResource("2_font")
theme_override_font_sizes/font_size = 24
text = ""
horizontal_alignment = 1
vertical_alignment = 1
[node name="FormulaLabel" type="Label" parent="."]
offset_left = 5.0
offset_top = 35.0
offset_right = 115.0
offset_bottom = 70.0
theme_override_fonts/font = ExtResource("2_font")
theme_override_font_sizes/font_size = 20
text = ""
horizontal_alignment = 1
vertical_alignment = 1
[node name="EffectLabel" type="Label" parent="."]
offset_left = 5.0
offset_top = 35.0
offset_right = 115.0
offset_bottom = 70.0
theme_override_fonts/font = ExtResource("2_font")
theme_override_font_sizes/font_size = 16
text = ""
horizontal_alignment = 1
vertical_alignment = 1
[node name="SelectionHighlight" type="ColorRect" parent="."]
visible = false
offset_left = -3.0
offset_top = -3.0
offset_right = 123.0
offset_bottom = 183.0
color = Color(1, 1, 0.3, 0.5)
[node name="HighlightOverlay" type="ColorRect" parent="."]
visible = false
offset_left = 0.0
offset_top = 0.0
offset_right = 120.0
offset_bottom = 180.0
color = Color(0.3, 1, 0.3, 0.3)
+91 -45
View File
@@ -3,17 +3,17 @@
[ext_resource type="FontFile" uid="uid://c2bipf4tpdjj8" path="res://assets/fonts/AlibabaPuHuiTi-3-65-Medium.ttf" id="1_uwrxv"]
[ext_resource type="Script" uid="uid://78lmrp321x85" path="res://scripts/game/game.gd" id="1_yqjtg"]
[node name="Game" type="Node2D" unique_id=818469863]
[node name="Game" type="Node2D" unique_id=736592237]
script = ExtResource("1_yqjtg")
[node name="Background" type="ColorRect" parent="." unique_id=802635055]
[node name="Background" type="ColorRect" parent="." unique_id=1805986496]
offset_left = -29.0
offset_top = -18.0
offset_right = 2599.0
offset_bottom = 1485.0
color = Color(0.7058824, 0.6862745, 0, 1)
[node name="IsServerLabel" type="Label" parent="." unique_id=592205212]
[node name="IsServerLabel" type="Label" parent="." unique_id=1073183772]
offset_left = 58.0
offset_top = 15.0
offset_right = 255.0
@@ -22,7 +22,7 @@ theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 20
text = "GAMEUI_URNULL"
[node name="IPLabel" type="Label" parent="." unique_id=2017838611]
[node name="IPLabel" type="Label" parent="." unique_id=1989497270]
offset_left = 54.0
offset_top = 53.0
offset_right = 156.0
@@ -31,60 +31,106 @@ theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 24
text = "127.0.0.2"
[node name="Player1" type="Node2D" parent="." unique_id=1336934388]
[node name="PhaseLabel" type="Label" parent="." unique_id=1288595795]
offset_left = 525.0
offset_top = 15.0
offset_right = 825.0
offset_bottom = 63.0
theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 28
[node name="Username" type="Label" parent="Player1" unique_id=264475054]
[node name="RoundLabel" type="Label" parent="." unique_id=1408966789]
offset_left = 825.0
offset_top = 15.0
offset_right = 1025.0
offset_bottom = 63.0
theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 28
[node name="EndPhaseButton" type="Button" parent="." unique_id=1666060569]
offset_left = 2074.0
offset_top = 1332.0
offset_right = 2374.0
offset_bottom = 1382.0
theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 24
text = "结束阶段"
[node name="NotificationLabel" type="Label" parent="." unique_id=489502093]
offset_left = 525.0
offset_top = 580.0
offset_right = 2025.0
offset_bottom = 640.0
theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 40
horizontal_alignment = 1
[node name="Player1" type="Node2D" parent="." unique_id=2011511951]
[node name="Username" type="Label" parent="Player1" unique_id=29274612]
offset_left = 70.0
offset_top = 1338.0
offset_right = 552.0
offset_bottom = 1396.0
offset_top = 1300.0
offset_right = 470.0
offset_bottom = 1360.0
theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 35
text = "Player1"
[node name="CardAreaPanel" type="PanelContainer" parent="Player1" unique_id=881125453]
offset_left = 525.0
offset_top = 1194.0
offset_right = 1967.0
offset_bottom = 1400.0
[node name="ReactionAreaPanel" type="PanelContainer" parent="Player1" unique_id=1328772147]
offset_left = 530.0
offset_top = 700.0
offset_right = 1972.0
offset_bottom = 1139.0
[node name="Label" type="Label" parent="Player1" unique_id=739321767]
offset_left = 71.0
offset_top = 1292.0
offset_right = 352.0
offset_bottom = 1353.0
[node name="ElectronLabel" type="Label" parent="Player1" unique_id=7738246]
offset_left = 70.0
offset_top = 1240.0
offset_right = 370.0
offset_bottom = 1300.0
theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 35
text = "HP: 5"
text = "电子层: 3/3"
[node name="Player2" type="Node2D" parent="." unique_id=1559551502]
[node name="CardAreaPanel" type="PanelContainer" parent="Player1" unique_id=511752962]
offset_left = 525.0
offset_top = 1200.0
offset_right = 2025.0
offset_bottom = 1400.0
[node name="Username" type="Label" parent="Player2" unique_id=1130366682]
offset_left = 2121.0
offset_top = 36.0
offset_right = 2434.0
offset_bottom = 94.0
[node name="ReactionAreaPanel" type="PanelContainer" parent="Player1" unique_id=531235727]
offset_left = 525.0
offset_top = 650.0
offset_right = 2025.0
offset_bottom = 1150.0
[node name="ElementAreaPanel" type="PanelContainer" parent="Player1" unique_id=699278238]
offset_left = 70.0
offset_top = 650.0
offset_right = 520.0
offset_bottom = 1150.0
[node name="Player2" type="Node2D" parent="." unique_id=74741602]
[node name="Username" type="Label" parent="Player2" unique_id=510041133]
offset_left = 2100.0
offset_top = 40.0
offset_right = 2400.0
offset_bottom = 100.0
theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 35
text = "Player2"
[node name="ReactionAreaPanel" type="PanelContainer" parent="Player2" unique_id=470786359]
offset_left = 525.0
offset_top = 15.0
offset_right = 1967.0
offset_bottom = 425.0
[node name="Label" type="Label" parent="Player2" unique_id=1027772856]
offset_left = 2127.0
offset_top = 104.0
offset_right = 2333.0
offset_bottom = 165.0
[node name="ElectronLabel" type="Label" parent="Player2" unique_id=340501131]
offset_left = 2100.0
offset_top = 100.0
offset_right = 2400.0
offset_bottom = 160.0
theme_override_fonts/font = ExtResource("1_uwrxv")
theme_override_font_sizes/font_size = 35
text = "HP: 5"
text = "电子层: 3/3"
[node name="ReactionAreaPanel" type="PanelContainer" parent="Player2" unique_id=1230613866]
offset_left = 525.0
offset_top = 15.0
offset_right = 2025.0
offset_bottom = 515.0
[node name="ElementAreaPanel" type="PanelContainer" parent="Player2" unique_id=1343260677]
offset_left = 2100.0
offset_top = 160.0
offset_right = 2500.0
offset_bottom = 515.0
File diff suppressed because it is too large Load Diff
+405 -11
View File
@@ -1,24 +1,418 @@
extends Node2D
var description: String
var type: String
var card_name: String
## ============================================================
## 纸片化学 - 卡牌脚本
## card.gd - 单张卡牌的显示、交互和数据管理
## ============================================================
# ---- 信号 ----
signal card_clicked(card: Node2D)
signal card_hovered(card: Node2D)
signal card_unhovered(card: Node2D)
signal card_drag_started(card: Node2D)
signal card_drag_ended(card: Node2D, position: Vector2)
signal card_double_clicked(card: Node2D)
# ---- 卡牌数据 ----
var card_id: String = ""
var card_name: String = ""
var description: String = ""
var card_type: int = 0
var element_symbol: String = ""
var atom_group_formula: String = ""
var condition_effect_primary: int = -1
var condition_effect_secondary: int = -1
var card_kind: String = ""
# ---- 显示状态 ----
var is_face_up: bool = true
var is_selected: bool = false
var is_dragging: bool = false
var is_highlighted: bool = false
var is_in_hand: bool = true
var is_in_reaction_area: bool = false
var is_in_element_area: bool = false
var original_position: Vector2 = Vector2.ZERO
var card_owner_id: int = -1
# ---- 显示参数 ----
var card_width: float = 120.0
var card_height: float = 180.0
var card_scale_normal: float = 1.0
var card_scale_hover: float = 1.1
var card_scale_selected: float = 1.15
var card_scale_dragging: float = 1.2
# ---- 颜色配置 ----
var color_atom_metal: Color = Color(0.75, 0.75, 0.82, 1.0)
var color_atom_nonmetal: Color = Color(0.2, 0.6, 0.8, 1.0)
var color_atom_group: Color = Color(0.9, 0.7, 0.2, 1.0)
var color_condition: Color = Color(0.85, 0.45, 0.2, 1.0)
var color_sequence: Color = Color(0.6, 0.3, 0.7, 1.0)
var color_count: Color = Color(0.5, 0.5, 0.5, 1.0)
var color_selected: Color = Color(1.0, 1.0, 0.3, 0.5)
var color_highlight: Color = Color(0.3, 1.0, 0.3, 0.3)
var color_back: Color = Color(0.15, 0.15, 0.25, 1.0)
func set_texture(pic: String) -> void:
var image: Image = Image.new()
image.load(pic)
$Sprite.texture = ImageTexture.create_from_image(image)
func _ready() -> void:
_setup_clickable_area()
visible = false
func _setup_clickable_area() -> void:
if not has_node("ClickableArea"):
var area: Area2D = Area2D.new()
area.name = "ClickableArea"
add_child(area)
var collision: CollisionShape2D = CollisionShape2D.new()
var shape: RectangleShape2D = RectangleShape2D.new()
shape.size = Vector2(card_width, card_height)
collision.shape = shape
collision.position = Vector2(card_width / 2.0, card_height / 2.0)
area.add_child(collision)
area.input_event.connect(_on_clickable_area_input_event)
area.mouse_entered.connect(_on_mouse_entered)
area.mouse_exited.connect(_on_mouse_exited)
func set_card(cname: String) -> void:
card_id = cname
card_name = cname
set_texture("user://download/sources/%s/assets/pics/%s" % [DownloadManager.uuid, GameManager.pic_list[card_name]])
if GameManager.card_list.has(cname) and GameManager.card_list[cname] is Dictionary:
var data: Dictionary = GameManager.card_list[cname]
description = data.get("description", "")
card_type = data.get("type", GameLoopManager.CardType.ATOM)
element_symbol = data.get("element", "")
atom_group_formula = data.get("formula", "")
condition_effect_primary = data.get("condition_effect_primary", -1)
condition_effect_secondary = data.get("condition_effect_secondary", -1)
card_kind = data.get("kind", "")
else:
_infer_card_type(cname)
set_texture("user://download/sources/%s/assets/pics/%s" % [DownloadManager.uuid, GameManager.pic_list.get(cname, "")])
update_display()
visible = true
func set_pos(x: int, y: int) -> void:
$Sprite.position = Vector2(x, y)
func _infer_card_type(cname: String) -> void:
if cname.find("点燃") != -1 or cname.find("升温") != -1 or cname.find("降温") != -1 or cname.find("溶解") != -1 or cname.find("过滤") != -1 or cname.find("集气") != -1 or cname.find("提取") != -1 or cname.find("结晶") != -1:
card_type = GameLoopManager.CardType.CONDITION
card_kind = "condition"
elif cname in ["", "", "", "", "", ""]:
card_type = GameLoopManager.CardType.SEQUENCE
elif cname.find("^") != -1 or (cname.find("-") != -1 and cname.find("·") == -1):
card_type = GameLoopManager.CardType.ATOM_GROUP
card_kind = "atom_group"
else:
card_type = GameLoopManager.CardType.ATOM
element_symbol = cname
var metals: Array = ["Li", "Na", "K", "Ca", "Ba", "Mg", "Al", "Zn", "Fe", "Cu", "Ag", "Au", "Sn", "Pb", "Mn", "Cr", "Ni", "Co", "Hg", "Ti"]
if cname in metals:
card_kind = "metal"
else:
card_kind = "nonmetal"
func update_display() -> void:
_update_card_color()
_update_labels()
_update_selection_display()
func _update_card_color() -> void:
if not is_face_up:
_set_background_color(color_back)
return
match card_type:
GameLoopManager.CardType.ATOM:
if card_kind == "metal":
_set_background_color(color_atom_metal)
else:
_set_background_color(color_atom_nonmetal)
GameLoopManager.CardType.ATOM_GROUP:
_set_background_color(color_atom_group)
GameLoopManager.CardType.CONDITION:
_set_background_color(color_condition)
GameLoopManager.CardType.SEQUENCE:
_set_background_color(color_sequence)
GameLoopManager.CardType.COUNT:
_set_background_color(color_count)
_:
_set_background_color(Color.WHITE)
func _set_background_color(color: Color) -> void:
if has_node("Background"):
get_node("Background").color = color
func _update_labels() -> void:
if not is_face_up:
if has_node("NameLabel"):
get_node("NameLabel").text = ""
if has_node("DescriptionLabel"):
get_node("DescriptionLabel").text = ""
return
if has_node("NameLabel"):
var name_label: Label = get_node("NameLabel")
name_label.text = card_name
name_label.visible = true
if has_node("DescriptionLabel"):
var desc_label: Label = get_node("DescriptionLabel")
desc_label.text = description
desc_label.visible = description != ""
if has_node("ElementLabel") and card_type == GameLoopManager.CardType.ATOM:
var elem_label: Label = get_node("ElementLabel")
elem_label.text = element_symbol
elem_label.visible = element_symbol != ""
if has_node("FormulaLabel") and card_type == GameLoopManager.CardType.ATOM_GROUP:
var formula_label: Label = get_node("FormulaLabel")
formula_label.text = atom_group_formula
formula_label.visible = atom_group_formula != ""
if has_node("EffectLabel") and card_type == GameLoopManager.CardType.CONDITION:
var effect_label: Label = get_node("EffectLabel")
var effects: Array = []
if condition_effect_primary != -1:
effects.append(GameLoopManager.ConditionEffect.keys()[condition_effect_primary])
if condition_effect_secondary != -1:
effects.append(GameLoopManager.ConditionEffect.keys()[condition_effect_secondary])
effect_label.text = "·".join(effects)
effect_label.visible = effects.size() > 0
func _update_selection_display() -> void:
if is_selected:
scale = Vector2(card_scale_selected, card_scale_selected)
if has_node("SelectionHighlight"):
get_node("SelectionHighlight").visible = true
elif is_dragging:
scale = Vector2(card_scale_dragging, card_scale_dragging)
if has_node("SelectionHighlight"):
get_node("SelectionHighlight").visible = false
elif is_highlighted:
scale = Vector2(card_scale_hover, card_scale_hover)
if has_node("HighlightOverlay"):
get_node("HighlightOverlay").visible = true
else:
scale = Vector2(card_scale_normal, card_scale_normal)
if has_node("SelectionHighlight"):
get_node("SelectionHighlight").visible = false
if has_node("HighlightOverlay"):
get_node("HighlightOverlay").visible = false
func set_texture(pic: String) -> void:
if pic == "" or pic == null: return
var image: Image = Image.new()
if image.load(pic) == OK:
if has_node("Sprite"):
$Sprite.texture = ImageTexture.create_from_image(image)
$Sprite.position = Vector2(card_width / 2.0, card_height / 2.0)
else:
if has_node("NameLabel"):
get_node("NameLabel").text = card_name
func set_pos(x: float, y: float) -> void:
position = Vector2(x, y)
func get_pos() -> Vector2:
return $Sprite.position
return position
func set_center_pos(x: float, y: float) -> void:
position = Vector2(x - card_width / 2.0, y - card_height / 2.0)
func get_center_pos() -> Vector2:
return position + Vector2(card_width / 2.0, card_height / 2.0)
func set_face_up(face_up: bool) -> void:
is_face_up = face_up
update_display()
func set_selected(selected: bool) -> void:
is_selected = selected
_update_selection_display()
if selected:
card_clicked.emit(self)
func set_highlighted(highlighted: bool) -> void:
is_highlighted = highlighted
_update_selection_display()
func _on_clickable_area_input_event(_viewport: Node, event: InputEvent, _shape_idx: int) -> void:
if not visible or not is_face_up: return
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
if event.double_click:
card_double_clicked.emit(self)
else:
_start_drag(event)
set_selected(not is_selected)
else:
_end_drag(event)
elif event is InputEventMouseMotion:
if is_dragging:
_update_drag_position(event)
func _on_mouse_entered() -> void:
if not visible: return
card_hovered.emit(self)
if not is_selected and not is_dragging:
scale = Vector2(card_scale_hover, card_scale_hover)
if is_in_hand:
position.y -= 20.0
func _on_mouse_exited() -> void:
if not visible: return
card_unhovered.emit(self)
if not is_selected and not is_dragging:
scale = Vector2(card_scale_normal, card_scale_normal)
if is_in_hand and not is_dragging:
position.y += 20.0
func _start_drag(event: InputEvent) -> void:
if not is_in_hand: return
is_dragging = true
original_position = position
card_drag_started.emit(self)
z_index = 100
_update_selection_display()
func _update_drag_position(event: InputEvent) -> void:
if not is_dragging: return
var global_mouse: Vector2 = event.global_position
position = get_parent().get_global_transform().affine_inverse() * global_mouse - Vector2(card_width / 2.0, card_height / 2.0)
func _end_drag(event: InputEvent) -> void:
if not is_dragging: return
is_dragging = false
z_index = 0
_update_selection_display()
card_drag_ended.emit(self, position)
func return_to_original_position() -> void:
position = original_position
scale = Vector2(card_scale_normal, card_scale_normal)
func is_metal_atom() -> bool:
return card_type == GameLoopManager.CardType.ATOM and card_kind == "metal"
func is_nonmetal_atom() -> bool:
return card_type == GameLoopManager.CardType.ATOM and card_kind == "nonmetal"
func is_atom_group() -> bool:
return card_type == GameLoopManager.CardType.ATOM_GROUP
func is_condition() -> bool:
return card_type == GameLoopManager.CardType.CONDITION
func is_sequence() -> bool:
return card_type == GameLoopManager.CardType.SEQUENCE
func is_count() -> bool:
return card_type == GameLoopManager.CardType.COUNT
func is_illegal_attack_substance() -> bool:
var no_defend_metals: Array = ["Li", "Na", "K", "Ca", "Ba"]
if card_type == GameLoopManager.CardType.ATOM and element_symbol in no_defend_metals:
return true
return false
func get_card_kind() -> String:
return card_kind
func get_card_info() -> Dictionary:
return {
"card_id": card_id,
"card_name": card_name,
"description": description,
"card_type": card_type,
"element_symbol": element_symbol,
"atom_group_formula": atom_group_formula,
"condition_effect_primary": condition_effect_primary,
"condition_effect_secondary": condition_effect_secondary,
"card_kind": card_kind,
"is_face_up": is_face_up,
"is_selected": is_selected,
"card_owner_id": card_owner_id,
}
func get_display_name() -> String:
if not is_face_up:
return "???"
return card_name
func flip_card(to_face_up: bool, duration: float = 0.3) -> void:
var tween: Tween = create_tween()
tween.tween_property(self, "scale:x", 0.0, duration / 2.0)
tween.tween_callback(func(): set_face_up(to_face_up))
tween.tween_property(self, "scale:x", card_scale_normal, duration / 2.0)
func animate_to_position(target_pos: Vector2, duration: float = 0.3) -> void:
var tween: Tween = create_tween()
tween.tween_property(self, "position", target_pos, duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
func fade_in(duration: float = 0.3) -> void:
modulate.a = 0.0
visible = true
var tween: Tween = create_tween()
tween.tween_property(self, "modulate:a", 1.0, duration)
func fade_out(duration: float = 0.3) -> void:
var tween: Tween = create_tween()
tween.tween_property(self, "modulate:a", 0.0, duration)
tween.tween_callback(func(): visible = false)
func bounce_effect() -> void:
var tween: Tween = create_tween()
tween.tween_property(self, "scale", Vector2(card_scale_hover, card_scale_hover), 0.1)
tween.tween_property(self, "scale", Vector2(card_scale_normal, card_scale_normal), 0.15)
func shake_effect() -> void:
var original_pos: Vector2 = position
var tween: Tween = create_tween()
for i in range(3):
tween.tween_property(self, "position", original_pos + Vector2(5.0, 0.0), 0.05)
tween.tween_property(self, "position", original_pos + Vector2(-5.0, 0.0), 0.05)
tween.tween_property(self, "position", original_pos, 0.05)
+309 -9
View File
@@ -1,18 +1,34 @@
extends Node2D
var card_list: Array
## ============================================================
## 纸片化学 - 游戏场景脚本
## game.gd - 管理游戏UI、卡牌显示、玩家交互
## ============================================================
var card_list: Array = []
var hand_card_nodes: Array = []
var reaction_card_nodes: Dictionary = {}
var element_card_nodes: Dictionary = {}
var hand_card_spacing: float = 130.0
var hand_card_y: float = 1250.0
var hand_card_start_x: float = 530.0
var reaction_card_spacing: float = 130.0
var element_card_spacing: float = 130.0
var selected_cards: Array = []
var is_waiting_for_target: bool = false
var current_operation: int = -1
func _ready() -> void:
init()
GameLoopManager.start_game()
var card = create_card("Oxygen")
card.show()
card.set_pos(300, 300)
print(card.get_pos())
_connect_signals()
if GameLoopManager.game_started:
_refresh_all_ui()
func init() -> void:
# 设置 UI 展示文本
if multiplayer.is_server():
$IsServerLabel.text = "GAMEUI_URHOST"
else:
@@ -25,11 +41,295 @@ func init() -> void:
break
$IPLabel.text = ipaddress
$Player1/Username.text = GameManager.username
$EndPhaseButton.visible = false
$PhaseLabel.text = ""
$RoundLabel.text = ""
func create_card(card_name: String):
var card = SceneManager.Card.instantiate()
func _connect_signals() -> void:
GameLoopManager.game_phase_changed.connect(_on_game_phase_changed)
GameLoopManager.player_data_updated.connect(_on_player_data_updated)
GameLoopManager.player_eliminated.connect(_on_player_eliminated)
GameLoopManager.player_dying_entered.connect(_on_player_dying_entered)
GameLoopManager.player_dying_resolved.connect(_on_player_dying_resolved)
GameLoopManager.hand_updated.connect(_on_hand_updated)
GameLoopManager.reaction_area_updated.connect(_on_reaction_area_updated)
GameLoopManager.element_area_updated.connect(_on_element_area_updated)
GameLoopManager.game_ended.connect(_on_game_ended)
GameLoopManager.turn_order_determined.connect(_on_turn_order_determined)
GameLoopManager.settlement_damage_applied.connect(_on_settlement_damage_applied)
$EndPhaseButton.pressed.connect(_on_end_phase_button_pressed)
func create_card(card_name: String) -> Node2D:
var card: Node2D = SceneManager.Card.instantiate()
add_child(card)
card.set_card(card_name)
card_list.append(card)
return card
func create_card_at(card_name: String, x: float, y: float) -> Node2D:
var card: Node2D = create_card(card_name)
card.set_pos(x, y)
return card
func _refresh_all_ui() -> void:
_refresh_phase_display()
_refresh_round_display()
_refresh_all_player_data()
_refresh_hand_cards()
_refresh_all_reaction_areas()
_refresh_all_element_areas()
func _refresh_phase_display() -> void:
var phase: int = GameLoopManager.get_current_phase()
var phase_names: Dictionary = {
GameLoopManager.GamePhase.PREPARATION: "准备阶段",
GameLoopManager.GamePhase.ACTION1: "行动阶段一",
GameLoopManager.GamePhase.SETTLEMENT1: "结算阶段一",
GameLoopManager.GamePhase.DRAW: "摸牌阶段",
GameLoopManager.GamePhase.ACTION2: "行动阶段二",
GameLoopManager.GamePhase.SETTLEMENT2: "结算阶段二",
GameLoopManager.GamePhase.DYING_ENTER: "进入濒死",
GameLoopManager.GamePhase.DYING_REPLY: "濒死回复",
GameLoopManager.GamePhase.DYING_SETTLE: "濒死结算",
GameLoopManager.GamePhase.ELIMINATION: "出局流程",
GameLoopManager.GamePhase.GAME_END: "游戏结束",
}
$PhaseLabel.text = phase_names.get(phase, "未知阶段")
var show_end: bool = phase in [GameLoopManager.GamePhase.ACTION1, GameLoopManager.GamePhase.ACTION2, GameLoopManager.GamePhase.DYING_REPLY]
$EndPhaseButton.visible = show_end
if phase == GameLoopManager.GamePhase.ACTION1:
$EndPhaseButton.text = "结束行动一 → 结算"
elif phase == GameLoopManager.GamePhase.ACTION2:
$EndPhaseButton.text = "结束行动二 → 结算"
elif phase == GameLoopManager.GamePhase.DYING_REPLY:
$EndPhaseButton.text = "宣布不再出牌"
func _refresh_round_display() -> void:
$RoundLabel.text = "回合: %d" % GameLoopManager.get_game_round()
func _refresh_all_player_data() -> void:
var my_id: int = multiplayer.get_unique_id()
var turn_order: Array = GameLoopManager.get_turn_order()
var my_data: Dictionary = GameLoopManager.get_player_data(my_id)
if not my_data.is_empty():
$Player1/Username.text = my_data.get("nickname", "Player1")
var el: int = my_data.get("electron_layers", 0)
var max_el: int = my_data.get("max_electron_layers", 0)
$Player1/ElectronLabel.text = "电子层: %d/%d" % [el, max_el]
if my_id == GameLoopManager.get_current_player():
$Player1/Username.add_theme_color_override("font_color", Color.YELLOW)
else:
$Player1/Username.add_theme_color_override("font_color", Color.WHITE)
if el <= 1:
$Player1/ElectronLabel.add_theme_color_override("font_color", Color.RED)
else:
$Player1/ElectronLabel.add_theme_color_override("font_color", Color.WHITE)
var other_players: Array = []
for pid in turn_order:
if pid != my_id:
other_players.append(pid)
if other_players.size() >= 1 and has_node("Player2"):
var p2d: Dictionary = GameLoopManager.get_player_data(other_players[0])
if not p2d.is_empty():
$Player2/Username.text = p2d.get("nickname", "Player2")
$Player2/ElectronLabel.text = "电子层: %d/%d" % [p2d.get("electron_layers", 0), p2d.get("max_electron_layers", 0)]
if other_players.size() >= 2 and has_node("Player3"):
var p3d: Dictionary = GameLoopManager.get_player_data(other_players[1])
if not p3d.is_empty():
$Player3/Username.text = p3d.get("nickname", "Player3")
$Player3/ElectronLabel.text = "电子层: %d" % p3d.get("electron_layers", 0)
if other_players.size() >= 3 and has_node("Player4"):
var p4d: Dictionary = GameLoopManager.get_player_data(other_players[2])
if not p4d.is_empty():
$Player4/Username.text = p4d.get("nickname", "Player4")
$Player4/ElectronLabel.text = "电子层: %d" % p4d.get("electron_layers", 0)
func _refresh_hand_cards() -> void:
var my_id: int = multiplayer.get_unique_id()
var data: Dictionary = GameLoopManager.get_player_data(my_id)
if data.is_empty(): return
var hand: Array = data.get("hand", [])
for cn in hand_card_nodes:
if is_instance_valid(cn): cn.queue_free()
hand_card_nodes.clear()
for i in range(hand.size()):
var card_node: Node2D = create_card(hand[i])
card_node.set_pos(hand_card_start_x + i * hand_card_spacing, hand_card_y)
card_node.is_in_hand = true
card_node.card_owner_id = my_id
card_node.is_face_up = true
card_node.show()
hand_card_nodes.append(card_node)
card_node.card_clicked.connect(_on_hand_card_clicked)
card_node.card_double_clicked.connect(_on_hand_card_double_clicked)
card_node.card_drag_ended.connect(_on_hand_card_drag_ended)
func _refresh_reaction_area(player_id: int) -> void:
var data: Dictionary = GameLoopManager.get_player_data(player_id)
if data.is_empty(): return
var area: Array = data.get("reaction_area", [])
if reaction_card_nodes.has(player_id):
for cn in reaction_card_nodes[player_id]:
if is_instance_valid(cn): cn.queue_free()
reaction_card_nodes[player_id].clear()
else:
reaction_card_nodes[player_id] = []
var panel: PanelContainer = _get_reaction_panel(player_id)
if panel == null: return
var bx: float = panel.position.x + 10.0
var by: float = panel.position.y + 10.0
for i in range(area.size()):
var sub: Dictionary = area[i]
var cn: Node2D = create_card(sub.get("formula", "???"))
cn.set_pos(bx + i * reaction_card_spacing, by)
cn.is_in_reaction_area = true
cn.is_face_up = true
cn.show()
reaction_card_nodes[player_id].append(cn)
func _refresh_all_reaction_areas() -> void:
for pid in GameLoopManager.get_turn_order():
_refresh_reaction_area(pid)
func _refresh_element_area(player_id: int) -> void:
var data: Dictionary = GameLoopManager.get_player_data(player_id)
if data.is_empty(): return
var area: Array = data.get("element_area", [])
if element_card_nodes.has(player_id):
for cn in element_card_nodes[player_id]:
if is_instance_valid(cn): cn.queue_free()
element_card_nodes[player_id].clear()
else:
element_card_nodes[player_id] = []
var panel: PanelContainer = _get_element_panel(player_id)
if panel == null: return
var bx: float = panel.position.x + 10.0
var by: float = panel.position.y + 10.0
for i in range(area.size()):
var cn: Node2D = create_card(area[i])
cn.set_pos(bx + i * element_card_spacing, by)
cn.is_in_element_area = true
cn.is_face_up = true
cn.show()
element_card_nodes[player_id].append(cn)
func _refresh_all_element_areas() -> void:
for pid in GameLoopManager.get_turn_order():
_refresh_element_area(pid)
func _get_reaction_panel(player_id: int) -> PanelContainer:
var my_id: int = multiplayer.get_unique_id()
if player_id == my_id: return $Player1/ReactionAreaPanel
var turn_order: Array = GameLoopManager.get_turn_order()
var idx: int = 0
for pid in turn_order:
if pid != my_id:
idx += 1
if pid == player_id:
if idx == 1 and has_node("Player2/ReactionAreaPanel"): return $Player2/ReactionAreaPanel
if idx == 2 and has_node("Player3/ReactionAreaPanel"): return $Player3/ReactionAreaPanel
if idx == 3 and has_node("Player4/ReactionAreaPanel"): return $Player4/ReactionAreaPanel
return null
func _get_element_panel(player_id: int) -> PanelContainer:
var my_id: int = multiplayer.get_unique_id()
if player_id == my_id:
if has_node("Player1/ElementAreaPanel"): return $Player1/ElementAreaPanel
return null
var turn_order: Array = GameLoopManager.get_turn_order()
var idx: int = 0
for pid in turn_order:
if pid != my_id:
idx += 1
if pid == player_id:
if idx == 1 and has_node("Player2/ElementAreaPanel"): return $Player2/ElementAreaPanel
if idx == 2 and has_node("Player3/ElementAreaPanel"): return $Player3/ElementAreaPanel
if idx == 3 and has_node("Player4/ElementAreaPanel"): return $Player4/ElementAreaPanel
return null
func _on_game_phase_changed(phase: int, player_id: int) -> void:
_refresh_phase_display()
_refresh_round_display()
_refresh_all_player_data()
if phase == GameLoopManager.GamePhase.DRAW: _refresh_hand_cards()
if phase in [GameLoopManager.GamePhase.SETTLEMENT1, GameLoopManager.GamePhase.SETTLEMENT2]:
_refresh_all_reaction_areas()
_refresh_all_player_data()
func _on_player_data_updated(player_id: int) -> void: _refresh_all_player_data()
func _on_player_eliminated(player_id: int, _attacker_id: int) -> void:
_refresh_all_player_data()
_refresh_all_reaction_areas()
_refresh_all_element_areas()
_refresh_hand_cards()
var d: Dictionary = GameLoopManager.get_player_data(player_id)
_show_notification("%s 已出局!" % d.get("nickname", "Player"))
func _on_player_dying_entered(player_id: int) -> void:
var d: Dictionary = GameLoopManager.get_player_data(player_id)
_show_notification("%s 进入濒死!" % d.get("nickname", "Player"))
_refresh_all_player_data()
func _on_player_dying_resolved(player_id: int, survived: bool) -> void:
var d: Dictionary = GameLoopManager.get_player_data(player_id)
_show_notification("%s %s" % [d.get("nickname", "Player"), "回复成功" if survived else "回复失败"])
_refresh_all_player_data()
func _on_hand_updated(player_id: int, _cards: Array) -> void:
if player_id == multiplayer.get_unique_id(): _refresh_hand_cards()
func _on_reaction_area_updated(player_id: int, _substances: Array) -> void: _refresh_reaction_area(player_id)
func _on_element_area_updated(player_id: int, _elements: Array) -> void: _refresh_element_area(player_id)
func _on_game_ended(winner_ids: Array) -> void:
$EndPhaseButton.visible = false
if winner_ids.size() == 1:
var d: Dictionary = GameLoopManager.get_player_data(winner_ids[0])
_show_notification("游戏结束! %s 获胜!" % d.get("nickname", "Player"))
else:
_show_notification("游戏结束! 平局!")
func _on_turn_order_determined(_order: Array) -> void:
_refresh_all_player_data()
_show_notification("出牌顺序已确定!")
func _on_settlement_damage_applied(player_id: int, damage_type: int, amount: int) -> void:
var d: Dictionary = GameLoopManager.get_player_data(player_id)
var tn: Dictionary = {GameLoopManager.DamageType.CORROSION: "腐蚀", GameLoopManager.DamageType.POISONING: "中毒", GameLoopManager.DamageType.GENERAL: "一般"}
_show_notification("%s 受到%d%s伤害!" % [d.get("nickname", "Player"), amount, tn.get(damage_type, "未知")])
func _on_hand_card_clicked(card: Node2D) -> void:
if card.is_selected: selected_cards.append(card)
else: selected_cards.erase(card)
func _on_hand_card_double_clicked(card: Node2D) -> void:
print("Double clicked: ", card.card_name)
func _on_hand_card_drag_ended(card: Node2D, _position: Vector2) -> void:
card.return_to_original_position()
func _on_end_phase_button_pressed() -> void:
GameLoopManager.end_current_phase.rpc()
func _show_notification(text: String) -> void:
$NotificationLabel.text = text
$NotificationLabel.visible = true
var tween: Tween = create_tween()
tween.tween_interval(3.0)
tween.tween_callback(func(): $NotificationLabel.visible = false)