feat: 由AI辅助进行游戏内容编写
This commit is contained in:
@@ -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)。但下面提到的除外:
|
||||
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
uid://dkwc5qney4yee
|
||||
@@ -1,5 +0,0 @@
|
||||
# AI 生成代码文件
|
||||
|
||||
这个目录下的代码都是 AI 生成的,仅作参考。
|
||||
|
||||
不要将本目录下的代码直接复制到被使用的代码处。
|
||||
+80
-2
@@ -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
@@ -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
|
||||
|
||||
+1106
-64
File diff suppressed because it is too large
Load Diff
+405
-11
@@ -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
@@ -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)
|
||||
Reference in New Issue
Block a user