feat: Add main game scene with core logic, manager initialization, UI setup, and an in-game message bar.

This commit is contained in:
Yogi Wiguna
2026-02-03 14:48:41 +08:00
parent 16a46d7e76
commit 02d13d9ff5
13 changed files with 257 additions and 279 deletions
@@ -6,51 +6,11 @@
[ext_resource type="ArrayMesh" uid="uid://brevl3ab0tdqe" path="res://assets/models/tiles/tile_wall.tres" id="4_8v5xv"]
[ext_resource type="ArrayMesh" uid="uid://b5ta7tcw0iscd" path="res://assets/models/tiles/tile_coin.tres" id="4_76xkl"]
[ext_resource type="ArrayMesh" uid="uid://d4himvyb81in8" path="res://assets/models/meshes/non-walkable.res" id="4_sx8rm"]
[ext_resource type="ArrayMesh" uid="uid://dr80txgr61irt" path="res://assets/models/tiles/tile_diamond.tres" id="5_sx8rm"]
[ext_resource type="ArrayMesh" uid="uid://dr80txgr61irt" path="res://assets/models/tiles/tile_area_freeze.tres" id="5_sx8rm"]
[ext_resource type="ArrayMesh" uid="uid://bqvqj3fhf5x51" path="res://assets/models/tiles/tile_ghost.tres" id="6_r32il"]
[ext_resource type="ArrayMesh" uid="uid://cv4bedhida00g" path="res://assets/models/tiles/tile_star.tres" id="7_p5epg"]
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_ghs0t"]
load_path = "res://.godot/imported/tile_coin_holo.png-6a443a1d36bcd3bf79f0210c252c3d26.s3tc.ctex"
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_5pajh"]
resource_name = "boost"
transparency = 1
cull_mode = 2
albedo_color = Color(0.91, 0.91, 0.91, 0.45098)
albedo_texture = SubResource("CompressedTexture2D_ghs0t")
[sub_resource type="ArrayMesh" id="ArrayMesh_ghs0t"]
_surfaces = [{
"aabb": AABB(-0.282176, -0.000324821, -0.282176, 0.564351, 0.050792, 0.564351),
"format": 34896613377,
"index_count": 36,
"index_data": PackedByteArray("BwAEAAUABwAGAAQABQACAAMABQAEAAIAAAAEAAYAAAACAAQABQABAAcABQADAAEAAgABAAMAAgAAAAEAAQAGAAcAAQAAAAYA"),
"name": "boost",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 8,
"vertex_data": PackedByteArray("AAD/////AAAAAP7/AAAAAP///////wAA///+/wAAAAD//wAA//8AAP//AAAAAAAAAAAAAP//AAAAAAAAAAAAAA==")
}]
blend_shape_mode = 0
[sub_resource type="ArrayMesh" id="ArrayMesh_cg50n"]
resource_name = "tile_diamond"
_surfaces = [{
"aabb": AABB(-0.282176, -0.000324821, -0.282176, 0.564351, 0.050792, 0.564351),
"attribute_data": PackedByteArray("sPss5W0hot47+9DosPv6720hXCE7+6/6sPvl75Heot6r9qfksPu0+pHeXCHM5KfkSvvl78X6sfqr9kDkSvu0+sX6tuTM5EDkSvss5c7ksfrU+tDoSvv6787ktuTU+q/6"),
"format": 34896613399,
"index_count": 36,
"index_data": PackedByteArray("FgANABAAFgATAA0AEQAIAAsAEQAOAAgAAAAMABIAAAAGAAwADwADABUADwAJAAMABwAEAAoABwABAAQABQAUABcABQACABQA"),
"material": SubResource("StandardMaterial3D_5pajh"),
"name": "tile_coin_holo",
"primitive": 3,
"uv_scale": Vector4(0, 0, 0, 0),
"vertex_count": 24,
"vertex_data": PackedByteArray("AAD//////78AAP//////vwAA/////6oqAAD+/wAAAAAAAP7/AAD/vwAA/v8AAKoq/////////7//////////v//////////////+/wAAAAD///7/AAD/v////v8AAP////8AAP///7///wAA////P///AAD///////8AAAAAAAD//wAAAAD/P///AAAAAP//AAAAAP///78AAAAA////PwAAAAD//6oqAAAAAAAAAAAAAAAAAAD/PwAAAAAAAKoq/////////39U1VTV/7//v////39U1VTV/////////3//v/9//7//v////3//v/9//////wAA/3//v/9//7//vwAA/3//v/9//////wAA/39U1VTV/7//vwAA/39U1VTV")
}]
blend_shape_mode = 0
shadow_mesh = SubResource("ArrayMesh_ghs0t")
[ext_resource type="ArrayMesh" uid="uid://gpnl4cjrivor" path="res://assets/models/tiles/tile_speed.tres" id="7_sx8rm"]
[ext_resource type="ArrayMesh" uid="uid://dr80txgr61irt" path="res://assets/models/tiles/tile_diamond.tres" id="10_r32il"]
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_5d0gc"]
load_path = "res://.godot/imported/tile_heart.png-deeef50755ca225f028608dfd16900e6.s3tc.ctex"
@@ -150,7 +110,7 @@ item/7/shapes = []
item/7/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/7/navigation_layers = 1
item/8/name = "tile_diamond"
item/8/mesh = ExtResource("5_sx8rm")
item/8/mesh = ExtResource("10_r32il")
item/8/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/8/mesh_cast_shadow = 1
item/8/shapes = []
@@ -170,29 +130,29 @@ item/10/mesh_cast_shadow = 1
item/10/shapes = []
item/10/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/10/navigation_layers = 1
item/11/name = "tile_heart_holo"
item/11/mesh = ExtResource("4_8v5xv")
item/11/name = "tile_speed"
item/11/mesh = ExtResource("7_sx8rm")
item/11/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/11/mesh_cast_shadow = 1
item/11/shapes = []
item/11/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/11/navigation_layers = 1
item/12/name = "tile_diamond_holo"
item/12/name = "tile_area_freeze"
item/12/mesh = ExtResource("5_sx8rm")
item/12/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/12/mesh_cast_shadow = 1
item/12/shapes = []
item/12/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/12/navigation_layers = 1
item/13/name = "tile_star_holo"
item/13/mesh = ExtResource("6_r32il")
item/13/name = "tile_wall"
item/13/mesh = ExtResource("4_8v5xv")
item/13/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/13/mesh_cast_shadow = 1
item/13/shapes = []
item/13/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/13/navigation_layers = 1
item/14/name = "tile_coin_holo"
item/14/mesh = SubResource("ArrayMesh_cg50n")
item/14/name = "tile_ghost"
item/14/mesh = ExtResource("6_r32il")
item/14/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
item/14/mesh_cast_shadow = 1
item/14/shapes = []
@@ -3,19 +3,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://umw3e8nfe3vr"
path="res://.godot/imported/attack_mode.png-c8d2e720b153f717981c069694b99c1d.ctex"
path.s3tc="res://.godot/imported/attack_mode.png-c8d2e720b153f717981c069694b99c1d.s3tc.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/graphics/touch_control/attack_mode.png"
dest_files=["res://.godot/imported/attack_mode.png-c8d2e720b153f717981c069694b99c1d.ctex"]
dest_files=["res://.godot/imported/attack_mode.png-c8d2e720b153f717981c069694b99c1d.s3tc.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
@@ -3,19 +3,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://3up2su2e0lfa"
path="res://.godot/imported/freeze_area.png-637e16813f4e334856ce1077dd0a8f60.ctex"
path.s3tc="res://.godot/imported/freeze_area.png-637e16813f4e334856ce1077dd0a8f60.s3tc.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/graphics/touch_control/freeze_area.png"
dest_files=["res://.godot/imported/freeze_area.png-637e16813f4e334856ce1077dd0a8f60.ctex"]
dest_files=["res://.godot/imported/freeze_area.png-637e16813f4e334856ce1077dd0a8f60.s3tc.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
@@ -3,19 +3,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://pwxo4lb87yi"
path="res://.godot/imported/put_tile.png-076fc15f3cb4549d9803338227d28dc3.ctex"
path.s3tc="res://.godot/imported/put_tile.png-076fc15f3cb4549d9803338227d28dc3.s3tc.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/graphics/touch_control/put_tile.png"
dest_files=["res://.godot/imported/put_tile.png-076fc15f3cb4549d9803338227d28dc3.ctex"]
dest_files=["res://.godot/imported/put_tile.png-076fc15f3cb4549d9803338227d28dc3.s3tc.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
@@ -3,19 +3,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://ckhdyxnho6sjp"
path="res://.godot/imported/spawn_tile.png-1538ef0a9fcda66388ef4cde6070b0fa.ctex"
path.s3tc="res://.godot/imported/spawn_tile.png-1538ef0a9fcda66388ef4cde6070b0fa.s3tc.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/graphics/touch_control/spawn_tile.png"
dest_files=["res://.godot/imported/spawn_tile.png-1538ef0a9fcda66388ef4cde6070b0fa.ctex"]
dest_files=["res://.godot/imported/spawn_tile.png-1538ef0a9fcda66388ef4cde6070b0fa.s3tc.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
@@ -3,19 +3,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://bsgqrjx2ity4c"
path="res://.godot/imported/speed.png-b1b011b3242f4e45ee38a2ab2d8e9378.ctex"
path.s3tc="res://.godot/imported/speed.png-b1b011b3242f4e45ee38a2ab2d8e9378.s3tc.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/graphics/touch_control/speed.png"
dest_files=["res://.godot/imported/speed.png-b1b011b3242f4e45ee38a2ab2d8e9378.ctex"]
dest_files=["res://.godot/imported/speed.png-b1b011b3242f4e45ee38a2ab2d8e9378.s3tc.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
@@ -3,19 +3,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://ba80xnybpixw2"
path="res://.godot/imported/take_tile.png-e2d53446f322555ce2a2ebc189d0edb9.ctex"
path.s3tc="res://.godot/imported/take_tile.png-e2d53446f322555ce2a2ebc189d0edb9.s3tc.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/graphics/touch_control/take_tile.png"
dest_files=["res://.godot/imported/take_tile.png-e2d53446f322555ce2a2ebc189d0edb9.ctex"]
dest_files=["res://.godot/imported/take_tile.png-e2d53446f322555ce2a2ebc189d0edb9.s3tc.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
@@ -3,19 +3,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://cupfmb5m15kmf"
path="res://.godot/imported/wall.png-3792c1a2a09d1bc9ce89d91015b9b816.ctex"
path.s3tc="res://.godot/imported/wall.png-3792c1a2a09d1bc9ce89d91015b9b816.s3tc.ctex"
metadata={
"vram_texture": false
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/graphics/touch_control/wall.png"
dest_files=["res://.godot/imported/wall.png-3792c1a2a09d1bc9ce89d91015b9b816.ctex"]
dest_files=["res://.godot/imported/wall.png-3792c1a2a09d1bc9ce89d91015b9b816.s3tc.ctex"]
[params]
compress/mode=0
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
-5
View File
@@ -1,14 +1,9 @@
[gd_resource type="ArrayMesh" format=4 uid="uid://bqvqj3fhf5x51"]
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_7uoqg"]
load_path = "res://.godot/imported/tile_star_holo.png-4aade3ef0db71672f9bd2143ab924c6e.s3tc.ctex"
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ev6sk"]
resource_name = "boost"
transparency = 1
cull_mode = 2
albedo_color = Color(0.91, 0.91, 0.91, 0.45098)
albedo_texture = SubResource("CompressedTexture2D_7uoqg")
[sub_resource type="ArrayMesh" id="ArrayMesh_w3aij"]
_surfaces = [{
+6
View File
@@ -459,6 +459,12 @@ func _setup_client_game():
touch_controls.set_player(player_character)
ui_manager.update_button_states()
print("Client: Configured local player ", my_id)
# ALWAYS setup PowerUpUI when we have the local player, just in case
var powerup_ui = get_node_or_null("PowerUpInventoryUI")
if powerup_ui:
powerup_ui.setup(player_character)
print("Client: PowerUpInventoryUI setup forced for ", my_id)
# Wait shorter time for host to be ready, then request full sync to correct positions/state
await get_tree().create_timer(1.0).timeout
+53 -61
View File
@@ -22,9 +22,13 @@
[ext_resource type="StyleBox" uid="uid://d3ruc8gytoovx" path="res://assets/styles/ribbon_selected_gui.tres" id="18_u5x6e"]
[ext_resource type="StyleBox" uid="uid://cdhnwvcklbyl8" path="res://assets/styles/ribbon_hovered_gui.tres" id="19_w1rqq"]
[ext_resource type="StyleBox" uid="uid://3yog1weaqhxb" path="res://assets/styles/ribbon_unselected_gui.tres" id="20_q6bc1"]
[ext_resource type="Texture2D" uid="uid://ba80xnybpixw2" path="res://assets/graphics/touch_control/take_tile.png" id="25_qkpxi"]
[ext_resource type="Texture2D" uid="uid://bsgqrjx2ity4c" path="res://assets/graphics/touch_control/speed.png" id="26_2f3dj"]
[ext_resource type="Texture2D" uid="uid://pwxo4lb87yi" path="res://assets/graphics/touch_control/put_tile.png" id="26_5q0nq"]
[ext_resource type="Texture2D" uid="uid://umw3e8nfe3vr" path="res://assets/graphics/touch_control/attack_mode.png" id="27_dgi5k"]
[ext_resource type="Texture2D" uid="uid://cupfmb5m15kmf" path="res://assets/graphics/touch_control/wall.png" id="27_yq6so"]
[ext_resource type="Texture2D" uid="uid://3up2su2e0lfa" path="res://assets/graphics/touch_control/freeze_area.png" id="28_fv21b"]
[ext_resource type="Texture2D" uid="uid://ckhdyxnho6sjp" path="res://assets/graphics/touch_control/spawn_tile.png" id="28_j8jky"]
[ext_resource type="Script" uid="uid://86ikh0wuqk7v" path="res://scripts/ui/powerup_inventory_ui.gd" id="powerup_ui_script"]
[ext_resource type="Script" uid="uid://b54tfa0n6kogi" path="res://scripts/managers/touch_controls.gd" id="touch_manager"]
[ext_resource type="Script" uid="uid://djiml4sh61dc1" path="res://scripts/ui/virtual_joystick.gd" id="virtual_joystick"]
@@ -9718,7 +9722,7 @@ anchor_bottom = 1.0
offset_top = 318.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 1
mouse_filter = 2
[node name="VirtualJoystick" type="Control" parent="TouchControls/TouchControls" unique_id=1983608919]
layout_mode = 1
@@ -9745,7 +9749,10 @@ offset_right = -233.0
offset_bottom = -179.0
grow_horizontal = 0
grow_vertical = 0
text = "👋"
focus_mode = 0
icon = ExtResource("25_qkpxi")
flat = true
expand_icon = true
[node name="PutBtn" type="Button" parent="TouchControls/TouchControls" unique_id=1027790362]
layout_mode = 1
@@ -9760,7 +9767,10 @@ offset_right = -153.0
offset_bottom = -99.0
grow_horizontal = 0
grow_vertical = 0
text = "📦"
focus_mode = 0
icon = ExtResource("26_5q0nq")
flat = true
expand_icon = true
[node name="SpecialBtn" type="Button" parent="TouchControls/TouchControls" unique_id=1380511463]
layout_mode = 1
@@ -9775,7 +9785,10 @@ offset_right = -153.0
offset_bottom = -253.0
grow_horizontal = 0
grow_vertical = 0
text = "⚡"
focus_mode = 0
icon = ExtResource("27_dgi5k")
flat = true
expand_icon = true
[node name="SpawnBoostBtn" type="Button" parent="TouchControls/TouchControls" unique_id=1566173505]
layout_mode = 1
@@ -9790,7 +9803,10 @@ offset_right = -73.0
offset_bottom = -171.0
grow_horizontal = 0
grow_vertical = 0
text = "🚀"
icon = ExtResource("28_j8jky")
flat = true
icon_alignment = 1
expand_icon = true
[node name="SettingsBtn" type="Button" parent="TouchControls/TouchControls" unique_id=1964422444]
layout_mode = 1
@@ -9827,72 +9843,48 @@ layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = 212.0
offset_bottom = 50.0
offset_top = 2.0
offset_right = 237.07463
offset_bottom = 58.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 15
theme_override_constants/separation = 10
[node name="CoinIcon" type="TextureRect" parent="PowerUpInventoryUI/HBoxContainer" unique_id=341904386]
[node name="SpeedBtn" type="Button" parent="PowerUpInventoryUI/HBoxContainer" unique_id=1549270030]
layout_mode = 2
texture = ExtResource("26_2f3dj")
expand_mode = 2
size_flags_horizontal = 3
focus_mode = 0
icon = ExtResource("26_2f3dj")
flat = true
icon_alignment = 1
expand_icon = true
[node name="SelectRect" type="TextureRect" parent="PowerUpInventoryUI/HBoxContainer/CoinIcon" unique_id=961231613]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("8_8meci")
[node name="HeartIcon" type="TextureRect" parent="PowerUpInventoryUI/HBoxContainer" unique_id=61328923]
[node name="WallBtn" type="Button" parent="PowerUpInventoryUI/HBoxContainer" unique_id=863365575]
layout_mode = 2
texture = ExtResource("27_yq6so")
expand_mode = 2
size_flags_horizontal = 3
focus_mode = 0
icon = ExtResource("27_yq6so")
flat = true
icon_alignment = 1
expand_icon = true
[node name="SelectRect" type="TextureRect" parent="PowerUpInventoryUI/HBoxContainer/HeartIcon" unique_id=772732397]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("8_8meci")
[node name="DiamondIcon" type="TextureRect" parent="PowerUpInventoryUI/HBoxContainer" unique_id=1830849071]
[node name="FreezeAreaBtn" type="Button" parent="PowerUpInventoryUI/HBoxContainer" unique_id=1087493560]
layout_mode = 2
texture = ExtResource("28_fv21b")
expand_mode = 2
size_flags_horizontal = 3
focus_mode = 0
icon = ExtResource("28_fv21b")
flat = true
icon_alignment = 1
expand_icon = true
[node name="SelectRect" type="TextureRect" parent="PowerUpInventoryUI/HBoxContainer/DiamondIcon" unique_id=439518389]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("8_8meci")
[node name="StarIcon" type="TextureRect" parent="PowerUpInventoryUI/HBoxContainer" unique_id=1468665065]
visible = false
[node name="GhostBtn" type="Button" parent="PowerUpInventoryUI/HBoxContainer" unique_id=2041811828]
layout_mode = 2
texture = ExtResource("9_i0gbs")
expand_mode = 2
[node name="SelectRect" type="TextureRect" parent="PowerUpInventoryUI/HBoxContainer/StarIcon" unique_id=1393132163]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("8_8meci")
size_flags_horizontal = 3
focus_mode = 0
icon = ExtResource("27_dgi5k")
flat = true
icon_alignment = 1
expand_icon = true
[connection signal="pressed" from="Menu/Host" to="." method="_on_host_pressed"]
[connection signal="pressed" from="Menu/Join" to="." method="_on_join_pressed"]
+10 -3
View File
@@ -269,10 +269,17 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true):
for y in range(-radius, radius + 1):
var pos = center + Vector2i(x, y)
if enhanced_gridmap.is_position_valid(pos):
# Random chance
if rng.randf() > 0.4: continue
# Random chance to spawn ANYTHING at this spot (keep density reasonable)
if rng.randf() > 0.5: continue
var item_id: int
# 70% Chance for PowerUp (11-14)
if rng.randf() < 0.7:
item_id = rng.randi_range(11, 14)
else:
# 30% Chance for Normal Tile (7-10)
item_id = rng.randi_range(7, 10)
var item_id = rng.randi_range(11, 14) # 11-14 are the new powerups
var cell = Vector3i(pos.x, 1, pos.y)
if player.is_multiplayer_authority():
+142 -131
View File
@@ -3,8 +3,7 @@ extends Control
# PowerUpInventoryUI - Displays stored powerups and handles selection
# UI References
var icon_containers: Dictionary = {} # { EffectEnum: TextureRect }
var selection_indicators: Dictionary = {} # { EffectEnum: Control }
var icon_containers: Dictionary = {} # { EffectEnum: Button }
# Local State
var selected_effect: int = -1
@@ -13,159 +12,171 @@ var special_manager_ref: Node = null # Reference to SpecialTilesManager
signal effect_selected(effect: int)
func _ready():
# Wait for children to be ready
await get_tree().process_frame
print("[PowerUpUI] _ready called")
# Map Effect Enum to UI Nodes (Assumes specific names in main.tscn)
# Default structure: HBoxContainer > CoinIcon, HeartIcon, etc.
# We try to get them immediately. If they are children, they should be accessible.
var container = get_node_or_null("HBoxContainer")
if not container:
print("PowerUpUI: Container not found")
print("[PowerUpUI] ERROR: HBoxContainer not found in ", get_path())
return
# ID 11 = Faster (Coin Icon?) User said: "Coin : random between two" originally,
# but now "11 (Faster)". Let's use CoinIcon for Speed? Or Star?
# User Request: "CoinIcon, HeartIcon, DiamondIcon and StarIcon"
# Let's map:
# 11 (Faster) -> CoinIcon
# 12 (Freeze) -> DiamondIcon
# 13 (Block) -> HeartIcon
# 14 (Invisible) -> StarIcon
# Mapping based on User Request
# 11: FASTER_SPEED (0) -> SpeedBtn
# 12: AREA_FREEZE (1) -> FreezeAreaBtn
# 13: BLOCK_FLOOR (2) -> WallBtn
# 14: INVISIBLE_MODE (3) -> GhostBtn
_setup_icon(11, container.get_node_or_null("CoinIcon"))
_setup_icon(12, container.get_node_or_null("DiamondIcon"))
_setup_icon(13, container.get_node_or_null("HeartIcon"))
_setup_icon(14, container.get_node_or_null("StarIcon"))
# We use 0, 1, 2, 3 to match SpecialTilesManager.SpecialEffect enum
_setup_btn(0, container.get_node_or_null("SpeedBtn"))
_setup_btn(1, container.get_node_or_null("FreezeAreaBtn"))
_setup_btn(2, container.get_node_or_null("WallBtn"))
_setup_btn(3, container.get_node_or_null("GhostBtn"))
# Note: SpecialEffect enum values from SpecialTilesManager:
# BURN_TILES = 0
# SPAWN_TILES = 1 (we map Coin to 0, merged)
# FREEZE_PLAYER = 2
# BLOCK_FLOOR = 3
# INVISIBLE_MODE = 4
print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size())
func _setup_icon(effect_id: int, node: Control):
if not node: return
func _setup_btn(effect_id: int, btn: Button):
if not btn:
print("[PowerUpUI] Warning: Button for effect %d is null" % effect_id)
return
icon_containers[effect_id] = node
icon_containers[effect_id] = btn
# Assume node has specific children for selection state?
# "SelectRect" child?
var select_rect = node.get_node_or_null("SelectRect")
if select_rect:
selection_indicators[effect_id] = select_rect
select_rect.visible = false
# Start DISABLED
btn.disabled = true
btn.modulate = Color(0.5, 0.5, 0.5, 0.5) # Grayed out
btn.focus_mode = Control.FOCUS_NONE
# Add Level Label
if not btn.has_node("LevelLabel"):
var lvl_lbl = Label.new()
lvl_lbl.name = "LevelLabel"
lvl_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
lvl_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
lvl_lbl.vertical_alignment = VERTICAL_ALIGNMENT_BOTTOM
lvl_lbl.set_anchors_preset(Control.PRESET_FULL_RECT)
lvl_lbl.add_theme_font_size_override("font_size", 16)
lvl_lbl.add_theme_color_override("font_outline_color", Color.BLACK)
lvl_lbl.add_theme_constant_override("outline_size", 4)
lvl_lbl.text = "" # Hidden initially
btn.add_child(lvl_lbl)
# Add Cooldown Label if missing
if not node.has_node("CooldownLabel"):
var lbl = Label.new()
lbl.name = "CooldownLabel"
lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE # Ensure input passes to icon
# Style the label
lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
lbl.set_anchors_preset(Control.PRESET_FULL_RECT) # Cover icon
lbl.add_theme_color_override("font_shadow_color", Color.BLACK)
lbl.add_theme_constant_override("shadow_offset_x", 1)
lbl.add_theme_constant_override("shadow_offset_y", 1)
lbl.add_theme_font_size_override("font_size", 20) # Big text
lbl.text = ""
lbl.hide()
node.add_child(lbl)
# Connect click event
if not node.gui_input.is_connected(_on_icon_input):
node.gui_input.connect(_on_icon_input.bind(effect_id))
# Add Cooldown Label
if not btn.has_node("CooldownLabel"):
var cd_lbl = Label.new()
cd_lbl.name = "CooldownLabel"
cd_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE
cd_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
cd_lbl.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
cd_lbl.set_anchors_preset(Control.PRESET_FULL_RECT)
cd_lbl.add_theme_font_size_override("font_size", 20)
cd_lbl.add_theme_color_override("font_outline_color", Color.BLACK)
cd_lbl.add_theme_constant_override("outline_size", 4)
cd_lbl.text = ""
btn.add_child(cd_lbl)
func _on_icon_input(event, effect_id: int):
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
print("[PowerUpUI] Clicked Icon %d. Manager: %s" % [effect_id, special_manager_ref])
# User Request: "Click the CoinIcon for activate"
# Instead of just selecting, we ACTIVATE it immediately.
if special_manager_ref:
special_manager_ref.activate_effect(effect_id)
else:
print("[PowerUpUI] ERROR: SpecialManagerRef is null!")
# Visual feedback (still useful)
select_effect(effect_id)
# Auto-deselect after short delay?
# For now, keep selection highlight as feedback of "last used" or "active"?
# Or just flash it?
# If it's a "selection for target" ability (like Block Floor on Target?),
# we might need selection state first, then clicking target.
# But user said "CoinIcon ... activate faster speed". Speed is self-cast.
# Freeze is 3x3 self-centered.
# Invisible is self.
# Block is "Wall Block"... maybe checks target?
# SpecialTilesManager.activate_effect defaults target to self if null.
# So direct activation is safe for IsInstant effects.
# Connect click
if not btn.pressed.is_connected(_on_btn_pressed):
btn.pressed.connect(_on_btn_pressed.bind(effect_id))
func _on_btn_pressed(effect_id: int):
print("[PowerUpUI] Clicked Button %d" % effect_id)
if special_manager_ref:
special_manager_ref.activate_effect(effect_id)
else:
print("[PowerUpUI] ERROR: special_manager_ref is null during click")
func setup(player_node):
print("[PowerUpUI] Setup called for player: ", player_node.name)
var special_manager = player_node.get_node_or_null("SpecialTilesManager")
if special_manager:
special_manager_ref = special_manager # Store reference for activation logic
if not special_manager.is_connected("inventory_updated", _on_inventory_updated):
special_manager.connect("inventory_updated", _on_inventory_updated)
if not special_manager.is_connected("cooldown_updated", _on_cooldown_updated):
special_manager.connect("cooldown_updated", _on_cooldown_updated)
_on_inventory_updated(special_manager.inventory)
_connect_special_manager(special_manager)
else:
print("[PowerUpUI] SpecialTilesManager not found on %s. Waiting for it..." % player_node.name)
if not player_node.child_entered_tree.is_connected(_on_player_child_entered):
player_node.child_entered_tree.connect(_on_player_child_entered.bind(player_node))
func _on_player_child_entered(node: Node, player_node: Node):
if node.name == "SpecialTilesManager":
print("[PowerUpUI] SpecialTilesManager appeared on %s!" % player_node.name)
_connect_special_manager(node)
# Disconnect to avoid checking every child forever
if player_node.child_entered_tree.is_connected(_on_player_child_entered):
player_node.child_entered_tree.disconnect(_on_player_child_entered)
func _connect_special_manager(special_manager):
special_manager_ref = special_manager
print("[PowerUpUI] Connected to SpecialTilesManager")
# Connect signals if not already connected
if not special_manager.is_connected("powerup_unlocked", _on_powerup_unlocked):
special_manager.connect("powerup_unlocked", _on_powerup_unlocked)
if not special_manager.is_connected("cooldown_updated", _on_cooldown_updated):
special_manager.connect("cooldown_updated", _on_cooldown_updated)
if not special_manager.is_connected("inventory_updated", _on_inventory_updated):
special_manager.connect("inventory_updated", _on_inventory_updated)
# Initial State Sync
if icon_containers.is_empty():
print("[PowerUpUI] Warning: Icon containers empty during setup. Attempting _ready logic now...")
var container = get_node_or_null("HBoxContainer")
if container:
# Fix: Use correct IDs 0-3 here too
_setup_btn(0, container.get_node_or_null("SpeedBtn"))
_setup_btn(1, container.get_node_or_null("FreezeAreaBtn"))
_setup_btn(2, container.get_node_or_null("WallBtn"))
_setup_btn(3, container.get_node_or_null("GhostBtn"))
# Sync Inventory
_on_inventory_updated(special_manager.inventory)
# Sync Levels for owned items
for effect in special_manager.inventory:
if special_manager.inventory[effect]:
var lvl = special_manager.powerup_levels.get(effect, 1)
_on_powerup_unlocked(effect, lvl)
func _on_powerup_unlocked(effect: int, level: int):
# Enable button and set level
if icon_containers.has(effect):
var btn = icon_containers[effect]
print("[PowerUpUI] Enabling button for Effect %d (Node: %s)" % [effect, btn.name])
btn.disabled = false
btn.modulate = Color.WHITE # Restore color
btn.visible = true # Ensure visible
# Update Level
var lvl_lbl = btn.get_node_or_null("LevelLabel")
if lvl_lbl:
lvl_lbl.text = "Lvl %d" % level
else:
print("[PowerUpUI] ERROR: Unlocked Effect %d but no UI button found! Keys: %s" % [effect, icon_containers.keys()])
func _on_cooldown_updated(effect: int, time_left: float, max_time: float):
if icon_containers.has(effect):
var node = icon_containers[effect]
var lbl = node.get_node_or_null("CooldownLabel")
if lbl:
var btn = icon_containers[effect]
var cd_lbl = btn.get_node_or_null("CooldownLabel")
if cd_lbl:
if time_left > 0:
lbl.text = "%.1fs" % time_left
lbl.show()
node.modulate = Color(0.5, 0.5, 0.5, 1.0) # Dimmed
cd_lbl.text = "%.1f" % time_left
btn.disabled = true
btn.modulate = Color(0.7, 0.7, 0.7, 0.8)
else:
lbl.hide()
# Check inventory to restore proper modulate
var has_item = false # Need ref to inventory... or rely on next inventory update?
# Inventory update might not fire when cooldown ends.
# Let's restore bright if we have it?
# We don't have inventory reference here easily without storing it.
node.modulate = Color.WHITE # Assume we have it if we used it?
# Or better, just restore to WHITE and let inventory logic handle "not owned" graying.
# But wait, logic in _on_inventory_updated sets GRAY if not owned.
# If we set WHITE here, we might un-gray an unowned item?
# The only way cooldown runs is if we ACTIVATED it, so we HAD it.
# And we treat it as infinite consumable now. So we still have it.
pass
# Better modulate handling:
# If cooldown > 0, DIM.
# If cooldown == 0, check owned state?
# For now, simplistic: if cooldown ends, set white.
if time_left <= 0:
node.modulate = Color.WHITE
cd_lbl.text = ""
# Re-enable if we own it
if special_manager_ref and special_manager_ref.inventory.get(effect, false):
btn.disabled = false
btn.modulate = Color.WHITE
func _on_inventory_updated(inventory: Dictionary):
# Update UI icons (Dimmed vs Lit)
# Update UI icons (Dimmed vs Lit) and Enablement
print("[PowerUpUI] Inventory Updated: ", inventory)
for effect in icon_containers:
if inventory.has(effect):
var has_item = inventory[effect]
icon_containers[effect].modulate = Color.WHITE if has_item else Color(0.3, 0.3, 0.3, 0.5)
var btn = icon_containers[effect]
if not has_item and selected_effect == effect:
deselect()
func select_effect(effect: int):
# Check if we own it first? The UI click handler should check.
selected_effect = effect
emit_signal("effect_selected", effect)
_update_selection_visuals()
func deselect():
selected_effect = -1
emit_signal("effect_selected", -1)
_update_selection_visuals()
func _update_selection_visuals():
for effect in selection_indicators:
selection_indicators[effect].visible = (effect == selected_effect)
btn.modulate = Color.WHITE if has_item else Color(0.5, 0.5, 0.5, 0.5)
btn.disabled = !has_item