feat: update vfx
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 854 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dhxg7146rcgnk"
|
||||
path="res://.godot/imported/freezeatas.png-bf12d086ba74eeafdcd33ce2cbe77666.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/vfx/tile_vfx_freeze/freezeatas.png"
|
||||
dest_files=["res://.godot/imported/freezeatas.png-bf12d086ba74eeafdcd33ce2cbe77666.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 MiB |
@@ -0,0 +1,42 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://byh7506oksbhb"
|
||||
path.s3tc="res://.godot/imported/freezebawah.png-6da5f32f6dc1d9aab209331638cef664.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/freezebawah.png-6da5f32f6dc1d9aab209331638cef664.etc2.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/graphics/vfx/tile_vfx_freeze/freezebawah.png"
|
||||
dest_files=["res://.godot/imported/freezebawah.png-6da5f32f6dc1d9aab209331638cef664.s3tc.ctex", "res://.godot/imported/freezebawah.png-6da5f32f6dc1d9aab209331638cef664.etc2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
@@ -0,0 +1,5 @@
|
||||
[gd_scene format=3 uid="uid://d3m0u8gl5odhw"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://b6xusldnaa288" path="res://assets/models/props/box_block.gltf" id="1_okim6"]
|
||||
|
||||
[node name="box_block" unique_id=1480078977 instance=ExtResource("1_okim6")]
|
||||
+3
-3
@@ -4,12 +4,12 @@ importer="scene"
|
||||
importer_version=1
|
||||
type="PackedScene"
|
||||
uid="uid://b6xusldnaa288"
|
||||
path="res://.godot/imported/Box_block_2.gltf-6b278c07bb52956b8b62d9b428e8f6a6.scn"
|
||||
path="res://.godot/imported/box_block.gltf-4e37264bb9b1903a1ad0284c85551176.scn"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/models/props/Box_block_2.gltf"
|
||||
dest_files=["res://.godot/imported/Box_block_2.gltf-6b278c07bb52956b8b62d9b428e8f6a6.scn"]
|
||||
source_file="res://assets/models/props/box_block.gltf"
|
||||
dest_files=["res://.godot/imported/box_block.gltf-4e37264bb9b1903a1ad0284c85551176.scn"]
|
||||
|
||||
[params]
|
||||
|
||||
@@ -140,6 +140,10 @@ var _bot_names: Dictionary = {}
|
||||
# Room list filter ("" = all, "Freemode", "Stop n Go", etc.)
|
||||
var _room_mode_filter: String = ""
|
||||
|
||||
# Re-entry guard for the async host flow (prevents double-click from starting
|
||||
# two overlapping create_match() calls).
|
||||
var _is_hosting: bool = false
|
||||
|
||||
# =============================================================================
|
||||
# Chat System
|
||||
# =============================================================================
|
||||
@@ -249,6 +253,13 @@ func _ready():
|
||||
UserProfileManager.profile_loaded.connect(func(_p): _sync_room_profile_card())
|
||||
UserProfileManager.profile_updated.connect(func(): _sync_room_profile_card())
|
||||
|
||||
# Clear the host re-entry guard whenever the host flow concludes — success,
|
||||
# failure, or leaving the room.
|
||||
LobbyManager.room_joined.connect(func(_r): _is_hosting = false)
|
||||
LobbyManager.room_left.connect(func(): _is_hosting = false)
|
||||
NakamaManager.match_join_error.connect(func(_e): _is_hosting = false)
|
||||
NakamaManager.connection_failed.connect(func(_e): _is_hosting = false)
|
||||
|
||||
# Connect Mailbox UI
|
||||
if MailManager:
|
||||
MailManager.unread_count_changed.connect(_on_mail_unread_count_changed)
|
||||
|
||||
+138
@@ -1919,6 +1919,144 @@ func sync_grid_item(x: int, y: int, z: int, item: int):
|
||||
if enhanced_gridmap.has_method("update_grid_data"):
|
||||
enhanced_gridmap.update_grid_data()
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func play_freeze_floor_vfx(center_x: int, center_z: int, radius: int, duration: float) -> void:
|
||||
# Spawns a flat freeze-floor AnimatedSprite3D that covers the (2*radius+1)^2
|
||||
# frozen area, centered on the given grid cell and sitting just above the
|
||||
# floor surface. The template under vfxController/AnimatedSprite3D (hidden) is
|
||||
# laid flat and sized for ~1 tile; we duplicate it, then size it to the area
|
||||
# from its measured footprint. Runs on every peer.
|
||||
var vfx_root := get_node_or_null("vfxController")
|
||||
if not vfx_root:
|
||||
return
|
||||
var template := vfx_root.get_node_or_null("AnimatedSprite3D") as AnimatedSprite3D
|
||||
if not template:
|
||||
return
|
||||
|
||||
var enhanced_gridmap = $EnhancedGridMap
|
||||
if not enhanced_gridmap:
|
||||
return
|
||||
var cell: Vector3 = enhanced_gridmap.cell_size
|
||||
|
||||
var vfx := template.duplicate() as AnimatedSprite3D
|
||||
|
||||
# --- Scale to cover the whole area -------------------------------------
|
||||
# The template's world footprint = its local sprite size (texture * pixel_size,
|
||||
# from the AABB) times each basis axis length. Scale each flat axis (X = width,
|
||||
# Y = depth, since the sprite is rotated to lie flat) so it spans the area.
|
||||
# AREA_SIZE_FACTOR oversizes slightly so the visible art (which has transparent
|
||||
# padding inside the frame) actually fills the (2*radius+1)-tile region — tune
|
||||
# this if it reads a touch small or large.
|
||||
const AREA_SIZE_FACTOR := 1.25
|
||||
var aabb := template.get_aabb()
|
||||
var b := template.transform.basis
|
||||
var base_w: float = absf(aabb.size.x) * b.x.length()
|
||||
var base_d: float = absf(aabb.size.y) * b.y.length()
|
||||
var want_w: float = float(2 * radius + 1) * cell.x * AREA_SIZE_FACTOR
|
||||
var want_d: float = float(2 * radius + 1) * cell.z * AREA_SIZE_FACTOR
|
||||
if base_w > 0.0001:
|
||||
b.x = b.x * (want_w / base_w)
|
||||
if base_d > 0.0001:
|
||||
b.y = b.y * (want_d / base_d)
|
||||
|
||||
# --- World center of the freeze area -----------------------------------
|
||||
# X/Z use the same convention players use (grid_to_world): cell center =
|
||||
# index*size + half a cell. Y is taken from the template's own position in the
|
||||
# scene, so the height can be tuned in the editor (move vfxController/
|
||||
# AnimatedSprite3D up/down) without touching code. vfxController/Main sit at
|
||||
# the origin, so this local point is also the world point.
|
||||
var world_center := Vector3(
|
||||
center_x * cell.x + cell.x * 0.5,
|
||||
template.position.y,
|
||||
center_z * cell.z + cell.z * 0.5
|
||||
)
|
||||
|
||||
# Assign basis + origin together in one shot so nothing overwrites the position.
|
||||
vfx.transform = Transform3D(b, world_center)
|
||||
vfx.visible = true
|
||||
vfx_root.add_child(vfx)
|
||||
vfx.play()
|
||||
|
||||
print("[FreezeVFX] center cell=(%d,%d) -> world=%s, scale=(%.2f x %.2f)" % [center_x, center_z, world_center, want_w, want_d])
|
||||
|
||||
await get_tree().create_timer(duration).timeout
|
||||
|
||||
if is_instance_valid(vfx):
|
||||
vfx.queue_free()
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func play_block_floor_vfx(cells: PackedVector2Array, duration: float) -> void:
|
||||
# Spawns one box_block VFX per blocked cell (each covers 1x1), autoplaying its
|
||||
# wall animation. The template under vfxController/box_block (hidden) is sized
|
||||
# for one cell; we duplicate it per cell, keeping its Y/scale from the scene so
|
||||
# height can be tuned in the editor. Runs on every peer.
|
||||
var vfx_root := get_node_or_null("vfxController")
|
||||
if not vfx_root:
|
||||
return
|
||||
var template := vfx_root.get_node_or_null("box_block")
|
||||
if not template:
|
||||
return
|
||||
|
||||
var enhanced_gridmap = $EnhancedGridMap
|
||||
if not enhanced_gridmap:
|
||||
return
|
||||
var cell: Vector3 = enhanced_gridmap.cell_size
|
||||
|
||||
var spawned: Array = []
|
||||
for c in cells:
|
||||
# DUPLICATE_USE_INSTANTIATION so the instanced glb subtree (incl. its
|
||||
# AnimationPlayer) is recreated in the copy rather than dropped.
|
||||
var box := template.duplicate(DUPLICATE_USE_INSTANTIATION)
|
||||
# X/Z = cell center (same convention as players), Y/scale kept from template.
|
||||
var pos := Vector3(
|
||||
int(c.x) * cell.x + cell.x * 0.5,
|
||||
template.position.y,
|
||||
int(c.y) * cell.z + cell.z * 0.5
|
||||
)
|
||||
box.transform = Transform3D(template.transform.basis, pos)
|
||||
box.visible = true
|
||||
vfx_root.add_child(box)
|
||||
_autoplay_vfx_animation(box)
|
||||
spawned.append(box)
|
||||
|
||||
await get_tree().create_timer(duration).timeout
|
||||
|
||||
for box in spawned:
|
||||
if is_instance_valid(box):
|
||||
box.queue_free()
|
||||
|
||||
func _autoplay_vfx_animation(node: Node) -> void:
|
||||
# Find the AnimationPlayer Godot creates for an imported glb/scene and play its
|
||||
# first clip once (one-shot) when the VFX is spawned.
|
||||
var anim_player := node.get_node_or_null("AnimationPlayer") as AnimationPlayer
|
||||
if not anim_player:
|
||||
for child in node.get_children():
|
||||
if child is AnimationPlayer:
|
||||
anim_player = child
|
||||
break
|
||||
if not anim_player:
|
||||
return
|
||||
var list := anim_player.get_animation_list()
|
||||
if list.is_empty():
|
||||
return
|
||||
var anim_name: String = list[0]
|
||||
var anim := anim_player.get_animation(anim_name)
|
||||
if anim:
|
||||
anim.loop_mode = Animation.LOOP_NONE
|
||||
anim_player.play(anim_name)
|
||||
|
||||
func play_playerboard_scatter_vfx() -> void:
|
||||
# One-shot "scatter" animation over the local player's board UI. Triggered for
|
||||
# the player whose tiles were scattered (caught outside the safe zone in
|
||||
# Stop n Go). The clip is already non-looping in the SpriteFrames.
|
||||
var vfx := get_node_or_null("PlayerBoardUI/AnimatedSprite2D") as AnimatedSprite2D
|
||||
if not vfx:
|
||||
return
|
||||
vfx.visible = true
|
||||
vfx.play("scatter")
|
||||
await vfx.animation_finished
|
||||
vfx.visible = false
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_grid_items_batch(data: Array):
|
||||
# data is an array of dictionaries: [{x: int, y: int, z: int, item: int}, ...]
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
[ext_resource type="MeshLibrary" uid="uid://kcv6ans86ug7" path="res://addons/enhanced_gridmap/meshlibrary/default.tres" id="1_110wo"]
|
||||
[ext_resource type="Script" uid="uid://co1ads72by6na" path="res://scenes/main.gd" id="1_xcpe3"]
|
||||
[ext_resource type="Texture2D" uid="uid://byh7506oksbhb" path="res://assets/graphics/vfx/tile_vfx_freeze/freezebawah.png" id="2_2cjbq"]
|
||||
[ext_resource type="PackedScene" uid="uid://d3m0u8gl5odhw" path="res://assets/models/meshes/box_block.tscn" id="2_chjal"]
|
||||
[ext_resource type="Script" uid="uid://bja8ixryvthu0" path="res://addons/enhanced_gridmap/enhanced_gridmap.gd" id="2_hbe1v"]
|
||||
[ext_resource type="Environment" uid="uid://jbptgqvstei3" path="res://assets/main-environment.tres" id="4_ky38j"]
|
||||
[ext_resource type="StyleBox" uid="uid://dlw1ogamn741n" path="res://assets/styles/box_flat.tres" id="5_dvx6y"]
|
||||
@@ -14,6 +16,7 @@
|
||||
[ext_resource type="Texture2D" uid="uid://68x88jj25yxg" path="res://assets/textures/Adjacent.png" id="9_6gcb6"]
|
||||
[ext_resource type="Texture2D" uid="uid://dasaeaytvhll0" path="res://assets/models/pboard/AdjacentRect.tres" id="9_aspsw"]
|
||||
[ext_resource type="FontFile" uid="uid://xnjx058n4tsw" path="res://assets/fonts/Nougat-ExtraBlack.ttf" id="13_j8jky"]
|
||||
[ext_resource type="Texture2D" uid="uid://cmoyhmh4jpep0" path="res://assets/graphics/vfx/playerboard_scatter.png" id="14_5poiv"]
|
||||
[ext_resource type="Texture2D" uid="uid://bafocgx2apwjm" path="res://assets/graphics/gui/stop_timer/Segment0_empty.png" id="14_fv21b"]
|
||||
[ext_resource type="Texture2D" uid="uid://c4xpg3j7p7g33" path="res://assets/graphics/gui/global_match_timer/timer.png" id="14_tel4y"]
|
||||
[ext_resource type="Texture2D" uid="uid://73ayhl1lqdpt" path="res://assets/graphics/gui/gauge/PowerLabel.png" id="14_vxglm"]
|
||||
@@ -41,6 +44,316 @@
|
||||
[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"]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_0loja"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(0, 0, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ids8r"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(500, 0, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_dnhof"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1000, 0, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1v6qk"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1500, 0, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_augu2"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2000, 0, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_tm6hj"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2500, 0, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kf5aa"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(0, 500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_t5ykj"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(500, 500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_e755i"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1000, 500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ro3en"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1500, 500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_od4ux"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2000, 500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_xhfw6"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2500, 500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1epgm"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(0, 1000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_152kg"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(500, 1000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_r4e17"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1000, 1000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_y57op"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1500, 1000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_mlv25"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2000, 1000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_a82wo"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2500, 1000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_gdvew"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(0, 1500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kjtvk"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(500, 1500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_xrs0c"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1000, 1500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_xljpf"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1500, 1500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_j4bwh"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2000, 1500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ksmap"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2500, 1500, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ygd8o"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(0, 2000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_2s0kr"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(500, 2000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_xaipn"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1000, 2000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ndrjj"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(1500, 2000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ih3m7"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2000, 2000, 500, 500)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_11yus"]
|
||||
atlas = ExtResource("2_2cjbq")
|
||||
region = Rect2(2500, 2000, 500, 500)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_5poiv"]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_0loja")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ids8r")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_dnhof")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_1v6qk")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_augu2")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_tm6hj")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_kf5aa")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_t5ykj")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_e755i")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ro3en")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_od4ux")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_xhfw6")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_1epgm")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_152kg")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_r4e17")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_y57op")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_mlv25")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_a82wo")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_gdvew")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_kjtvk")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_xrs0c")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_xljpf")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_j4bwh")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ksmap")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ygd8o")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_2s0kr")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_xaipn")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ndrjj")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ih3m7")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_11yus")
|
||||
}],
|
||||
"loop": false,
|
||||
"name": &"freeze_floor",
|
||||
"speed": 15.0
|
||||
}]
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_2cjbq"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(0, 0, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_chjal"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(357, 0, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_cjqg0"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(714, 0, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_vchkt"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(1071, 0, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_txyw0"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(0, 340, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_vc5cj"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(357, 340, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_nvyfr"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(714, 340, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ty1g6"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(1071, 340, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_j2jfr"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(0, 680, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_bjg73"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(357, 680, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_bu4jf"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(714, 680, 357, 340)
|
||||
|
||||
[sub_resource type="AtlasTexture" id="AtlasTexture_o7ddy"]
|
||||
atlas = ExtResource("14_5poiv")
|
||||
region = Rect2(1071, 680, 357, 340)
|
||||
|
||||
[sub_resource type="SpriteFrames" id="SpriteFrames_0loja"]
|
||||
animations = [{
|
||||
"frames": [{
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_2cjbq")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_chjal")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_cjqg0")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_vchkt")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_txyw0")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_vc5cj")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_nvyfr")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_ty1g6")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_j2jfr")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_bjg73")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_bu4jf")
|
||||
}, {
|
||||
"duration": 1.0,
|
||||
"texture": SubResource("AtlasTexture_o7ddy")
|
||||
}],
|
||||
"loop": false,
|
||||
"name": &"scatter",
|
||||
"speed": 30.0
|
||||
}]
|
||||
|
||||
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_c6pm6"]
|
||||
content_margin_bottom = 20.0
|
||||
texture = ExtResource("14_tel4y")
|
||||
@@ -104,6 +417,20 @@ texture = SubResource("CompressedTexture2D_chjal")
|
||||
[node name="Main" type="Node3D" unique_id=864552263]
|
||||
script = ExtResource("1_xcpe3")
|
||||
|
||||
[node name="vfxController" type="Node3D" parent="." unique_id=968868013]
|
||||
|
||||
[node name="box_block" parent="vfxController" unique_id=1480078977 instance=ExtResource("2_chjal")]
|
||||
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0.5, 0.35, 0.5)
|
||||
visible = false
|
||||
|
||||
[node name="AnimatedSprite3D" type="AnimatedSprite3D" parent="vfxController" unique_id=1360317189]
|
||||
transform = Transform3D(0.2, 0, 0, 0, -8.742278e-09, 0.15, 0, -0.2, -6.5567085e-09, 0.5, 0.4, 0.5)
|
||||
visible = false
|
||||
sprite_frames = SubResource("SpriteFrames_5poiv")
|
||||
animation = &"freeze_floor"
|
||||
frame = 29
|
||||
frame_progress = 1.0
|
||||
|
||||
[node name="EnhancedGridMap" type="GridMap" parent="." unique_id=1838552857]
|
||||
mesh_library = ExtResource("1_110wo")
|
||||
cell_size = Vector3(1, 0.2, 1)
|
||||
@@ -1077,6 +1404,14 @@ theme_override_fonts/font = ExtResource("13_j8jky")
|
||||
theme_override_font_sizes/font_size = 32
|
||||
text = "X0"
|
||||
|
||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="PlayerBoardUI" unique_id=390404299]
|
||||
visible = false
|
||||
position = Vector2(117.56293, 165.96373)
|
||||
sprite_frames = SubResource("SpriteFrames_0loja")
|
||||
animation = &"scatter"
|
||||
frame = 11
|
||||
frame_progress = 1.0
|
||||
|
||||
[node name="GlobalMatchTimer" type="PanelContainer" parent="." unique_id=1714357974]
|
||||
custom_minimum_size = Vector2(372, 162)
|
||||
anchors_preset = 5
|
||||
|
||||
@@ -413,6 +413,18 @@ func play_scatter_knock():
|
||||
await vfx_scatter_knock.animation_finished
|
||||
vfx_scatter_knock.visible = false
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func play_playerboard_scatter():
|
||||
"""Show the one-shot scatter VFX over the playerboard UI, but only on the
|
||||
local human player's own client (the board UI belongs to them)."""
|
||||
var is_local = name == str(multiplayer.get_unique_id())
|
||||
var is_bot_check = is_bot or is_in_group("Bots")
|
||||
if not is_local or is_bot_check:
|
||||
return
|
||||
var main = get_tree().get_root().get_node_or_null("Main")
|
||||
if main and main.has_method("play_playerboard_scatter_vfx"):
|
||||
main.play_playerboard_scatter_vfx()
|
||||
|
||||
func _init_managers():
|
||||
movement_manager = load("res://scripts/managers/player_movement_manager.gd").new()
|
||||
movement_manager.name = "MovementManager"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://dyovwailce5tf" path="res://scripts/tekton.gd" id="1_tekton"]
|
||||
[ext_resource type="Script" uid="uid://c67yq846u8y68" path="res://scripts/tekton_controller.gd" id="2_controller"]
|
||||
[ext_resource type="PackedScene" uid="uid://statictektonmesh001" path="res://scenes/static_tekton_mesh.tscn" id="3_d2kpk"]
|
||||
[ext_resource type="PackedScene" uid="uid://df7h7y7y7y7y7" path="res://scenes/static_tekton_mesh.tscn" id="3_d2kpk"]
|
||||
[ext_resource type="Texture2D" uid="uid://biun2yvglxgij" path="res://assets/graphics/touch_control/grab_tekton.png" id="4_grab_icon"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_tekton"]
|
||||
@@ -17,6 +17,7 @@ script = ExtResource("2_controller")
|
||||
[node name="Visuals" type="Node3D" parent="." unique_id=1698719440]
|
||||
|
||||
[node name="tekton" parent="Visuals" unique_id=2052742928 instance=ExtResource("3_d2kpk")]
|
||||
transform = Transform3D(-0.2, 0, -1.7484556e-08, 0, 0.2, 0, 1.7484556e-08, 0, -0.2, 0, 0, 0)
|
||||
|
||||
[node name="HitArea" type="Area3D" parent="." unique_id=2139590311]
|
||||
collision_layer = 4
|
||||
|
||||
@@ -72,6 +72,13 @@ func on_create_room_pressed() -> void:
|
||||
lobby._sync_room_profile_card()
|
||||
|
||||
func host_room(game_mode: String) -> void:
|
||||
# Guard against double-clicks: the create flow is async, and a second press
|
||||
# before it finishes re-enters create_match() while the bridge is still
|
||||
# JOINING, which throws "Cannot create match when state is JOINING".
|
||||
if lobby._is_hosting:
|
||||
return
|
||||
lobby._is_hosting = true
|
||||
|
||||
if AuthManager.is_guest:
|
||||
if LobbyManager.local_player_name.is_empty() or LobbyManager.local_player_name == "Player":
|
||||
LobbyManager.local_player_name = NameGenerator.generate_guest_name()
|
||||
@@ -92,6 +99,7 @@ func host_room(game_mode: String) -> void:
|
||||
var ok = await LobbyManager.create_room_lan(room_label)
|
||||
if not ok:
|
||||
lobby.connection_status.text = "Failed to start LAN room. Check port 7777."
|
||||
lobby._is_hosting = false
|
||||
else:
|
||||
lobby.connection_status.text = "Creating Nakama room..."
|
||||
var room_label := "%sRoom %d" % [mode_prefix, randi_range(1000, 9999)]
|
||||
|
||||
@@ -389,6 +389,14 @@ func _execute_area_freeze(target_pos: Vector2i = Vector2i(-9999, -9999)):
|
||||
|
||||
SfxManager.rpc("play_rpc", "freeze")
|
||||
|
||||
# Floor VFX: cover the whole frozen area on every peer.
|
||||
var main_vfx = get_node_or_null("/root/Main")
|
||||
if main_vfx and main_vfx.has_method("play_freeze_floor_vfx"):
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||
main_vfx.rpc("play_freeze_floor_vfx", center_pos.x, center_pos.y, radius, FREEZE_SLOW_DURATION)
|
||||
else:
|
||||
main_vfx.play_freeze_floor_vfx(center_pos.x, center_pos.y, radius, FREEZE_SLOW_DURATION)
|
||||
|
||||
if hit_count > 0 and player.is_multiplayer_authority():
|
||||
var is_sng = LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO)
|
||||
if not is_sng:
|
||||
@@ -403,44 +411,9 @@ func _execute_area_freeze(target_pos: Vector2i = Vector2i(-9999, -9999)):
|
||||
else:
|
||||
NotificationManager.send_message(player, "Hit %d Players!" % hit_count, NotificationManager.MessageType.GOAL)
|
||||
|
||||
# Visual Feedback (Layer 0 - Ground Level, matching Wall logic)
|
||||
if player.is_multiplayer_authority():
|
||||
var main_node = get_node_or_null("/root/Main")
|
||||
if main_node and main_node.has_method("sync_grid_items_batch"):
|
||||
var batch_data = []
|
||||
var restoration_data = [] # Stores {pos, item} to restore later
|
||||
|
||||
for rx in range(-radius, radius + 1):
|
||||
for rz in range(-radius, radius + 1):
|
||||
var cx = center_pos.x + rx
|
||||
var cz = center_pos.y + rz
|
||||
var pos = Vector3i(cx, 0, cz)
|
||||
|
||||
# Get original ground item to restore later
|
||||
var original_ground = enhanced_gridmap.get_cell_item(pos)
|
||||
|
||||
# Ignore if it is an immutable tile (Safe Zone 2, Wall 4, etc)
|
||||
if original_ground in [1, 2, 3, 4, 15, 16]: continue
|
||||
|
||||
restoration_data.append({"pos": pos, "item": original_ground})
|
||||
batch_data.append({"x": cx, "y": 0, "z": cz, "item": 5})
|
||||
|
||||
if not batch_data.is_empty():
|
||||
main_node.rpc("sync_grid_items_batch", batch_data)
|
||||
|
||||
# Removal timer with accurate restoration
|
||||
get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func():
|
||||
var cl_node = get_node_or_null("/root/Main")
|
||||
if not cl_node: return
|
||||
var clear_batch = []
|
||||
for entry in restoration_data:
|
||||
var p = entry.pos
|
||||
# Only restore if it is STILL our Freeze tile
|
||||
if enhanced_gridmap.get_cell_item(p) == 5:
|
||||
clear_batch.append({"x": p.x, "y": 0, "z": p.z, "item": entry.item})
|
||||
if not clear_batch.is_empty():
|
||||
cl_node.rpc("sync_grid_items_batch", clear_batch)
|
||||
)
|
||||
# Visual feedback is the freeze-floor VFX (play_freeze_floor_vfx above). The
|
||||
# blue layer-0 highlight tiles were removed so only the VFX shows; the slow
|
||||
# effect is driven by active_freeze_zones / layer-2 checks, not these tiles.
|
||||
|
||||
|
||||
func _execute_block_floor(target_pos: Vector2i = Vector2i(-999, -999)):
|
||||
@@ -474,6 +447,7 @@ func _execute_block_floor(target_pos: Vector2i = Vector2i(-999, -999)):
|
||||
var main = get_node_or_null("/root/Main")
|
||||
if main and main.has_method("sync_grid_items_batch"):
|
||||
var batch_data = []
|
||||
var vfx_cells := PackedVector2Array()
|
||||
for n in neighbors:
|
||||
var pos = n.position
|
||||
if _is_position_blocked_by_stand(pos): continue
|
||||
@@ -489,6 +463,7 @@ func _execute_block_floor(target_pos: Vector2i = Vector2i(-999, -999)):
|
||||
if original_item in [1, 2, 3, 4, 15, 16] or is_immutable: continue
|
||||
|
||||
batch_data.append({"x": block_pos.x, "y": 0, "z": block_pos.z, "item": 4})
|
||||
vfx_cells.append(Vector2(block_pos.x, block_pos.z))
|
||||
|
||||
# Record for restoration
|
||||
blocked_tiles.append({
|
||||
@@ -500,6 +475,13 @@ func _execute_block_floor(target_pos: Vector2i = Vector2i(-999, -999)):
|
||||
if not batch_data.is_empty():
|
||||
main.rpc("sync_grid_items_batch", batch_data)
|
||||
|
||||
# Block VFX: one box_block per cell, animated, on every peer.
|
||||
if not vfx_cells.is_empty() and main.has_method("play_block_floor_vfx"):
|
||||
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||
main.rpc("play_block_floor_vfx", vfx_cells, BLOCK_DURATION)
|
||||
else:
|
||||
main.play_block_floor_vfx(vfx_cells, BLOCK_DURATION)
|
||||
|
||||
# Notify
|
||||
SfxManager.rpc("play_rpc", "wall")
|
||||
NotificationManager.send_message(player, "Defensive Wall Deployed!", NotificationManager.MessageType.POWERUP)
|
||||
|
||||
@@ -1026,6 +1026,14 @@ func _scatter_player_tiles(player_node: Node):
|
||||
SfxManager.rpc("play_rpc", "tile_scatter")
|
||||
NotificationManager.send_message(player_node, "Not in Safe Zone! Tiles scattered!", NotificationManager.MessageType.WARNING)
|
||||
|
||||
# Playerboard scatter VFX: one-shot, only on the affected player's own UI.
|
||||
# Routed through the player node (same pattern as play_scatter_knock) because
|
||||
# the player-node name is not a valid network peer ID for rpc_id here.
|
||||
if can_rpc():
|
||||
player_node.rpc("play_playerboard_scatter")
|
||||
else:
|
||||
player_node.play_playerboard_scatter()
|
||||
|
||||
# Screen shake and animation
|
||||
if can_rpc():
|
||||
if player_node.has_method("trigger_screen_shake"):
|
||||
|
||||
@@ -491,6 +491,12 @@ func submit_to_leaderboard() -> void:
|
||||
|
||||
func get_display_name(fallback: String = "Guest") -> String:
|
||||
if not is_profile_loaded:
|
||||
# Profile storage (inventory/stats/fragments) may still be loading, but
|
||||
# AuthManager.current_user is populated before auth_completed fires, so the
|
||||
# real name is already available — prefer it over the guest fallback to
|
||||
# avoid a logged-in host being registered as "Guest" when acting quickly.
|
||||
if AuthManager.current_user.has("display_name"):
|
||||
return AuthManager.current_user["display_name"]
|
||||
return fallback
|
||||
return profile.get("display_name", fallback)
|
||||
|
||||
|
||||
@@ -187,11 +187,21 @@ func host_game(room_meta: Dictionary = {}):
|
||||
if not bridge:
|
||||
printerr("Cannot host: Bridge not initialized")
|
||||
return
|
||||
print("Hosting match via Nakama Bridge...")
|
||||
var result = await bridge.create_match()
|
||||
if result and result.is_exception():
|
||||
emit_signal("match_join_error", result.get_exception().message)
|
||||
|
||||
# Guard against re-entry: create_match() rejects any state other than
|
||||
# DISCONNECTED. A double-click on the mode button (or a retry after a stalled
|
||||
# attempt) can re-enter here while a prior create_match() is still JOINING.
|
||||
if bridge.match_state == NakamaMultiplayerBridge.MatchState.CONNECTED:
|
||||
print("[NakamaManager] Already hosting a match; ignoring duplicate host request.")
|
||||
return
|
||||
if bridge.match_state != NakamaMultiplayerBridge.MatchState.DISCONNECTED:
|
||||
# Stranded mid-join (e.g. previous attempt never resolved). Reset so the
|
||||
# bridge is idle before we try again.
|
||||
print("[NakamaManager] Bridge busy (%s); resetting before hosting." % NakamaMultiplayerBridge.MatchState.keys()[bridge.match_state])
|
||||
await bridge.leave()
|
||||
|
||||
print("Hosting match via Nakama Bridge...")
|
||||
await bridge.create_match()
|
||||
# Store room metadata in Nakama storage so other players can see it in listings
|
||||
if session and current_match_id and room_meta.size() > 0:
|
||||
var meta_json = JSON.stringify(room_meta)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
extends Node3D
|
||||
|
||||
## Autoplays all animations in the AnimationPlayer simultaneously and loops them.
|
||||
## Attach to any node that has an AnimationPlayer child (e.g. tekton_fishing_animation instances).
|
||||
## Merges every animation in the AnimationPlayer into a single looping clip and
|
||||
## plays it, so meshes driven by different animations (e.g. ted_bones and
|
||||
## fishing_acc) all animate simultaneously instead of overwriting each other.
|
||||
## Attach to any node that has an AnimationPlayer child.
|
||||
|
||||
@onready var anim_player: AnimationPlayer = $AnimationPlayer
|
||||
|
||||
const MERGED_LIB := "merged"
|
||||
const MERGED_NAME := "merged/all"
|
||||
|
||||
func _ready() -> void:
|
||||
if not anim_player:
|
||||
push_warning("tekton_fishing_autoplay: No AnimationPlayer found")
|
||||
@@ -13,13 +18,52 @@ func _ready() -> void:
|
||||
# Wait one frame for the scene tree to settle
|
||||
await get_tree().process_frame
|
||||
|
||||
# Play every animation simultaneously, all looping
|
||||
for anim_name in anim_player.get_animation_list():
|
||||
anim_player.play(anim_name)
|
||||
anim_player.advance(0.0) # ensure it starts
|
||||
var source_names := anim_player.get_animation_list()
|
||||
if source_names.is_empty():
|
||||
return
|
||||
|
||||
# Set all to loop
|
||||
for anim_name in anim_player.get_animation_list():
|
||||
var anim: Animation = anim_player.get_animation(anim_name)
|
||||
if anim:
|
||||
anim.loop_mode = Animation.LOOP_LINEAR
|
||||
var merged := _build_merged_animation(source_names)
|
||||
if not merged:
|
||||
return
|
||||
|
||||
var lib := AnimationLibrary.new()
|
||||
lib.add_animation("all", merged)
|
||||
if anim_player.has_animation_library(MERGED_LIB):
|
||||
anim_player.remove_animation_library(MERGED_LIB)
|
||||
anim_player.add_animation_library(MERGED_LIB, lib)
|
||||
|
||||
anim_player.play(MERGED_NAME)
|
||||
|
||||
|
||||
## Combines the tracks of every source animation into one Animation. The source
|
||||
## animations target disjoint node sets, so simply concatenating their tracks
|
||||
## drives all meshes at once.
|
||||
func _build_merged_animation(source_names: Array) -> Animation:
|
||||
var merged := Animation.new()
|
||||
merged.loop_mode = Animation.LOOP_LINEAR
|
||||
|
||||
var max_length := 0.0
|
||||
for anim_name in source_names:
|
||||
var src: Animation = anim_player.get_animation(anim_name)
|
||||
if not src:
|
||||
continue
|
||||
max_length = max(max_length, src.length)
|
||||
for t in src.get_track_count():
|
||||
_copy_track(src, t, merged)
|
||||
|
||||
merged.length = max_length
|
||||
return merged
|
||||
|
||||
|
||||
func _copy_track(src: Animation, src_track: int, dst: Animation) -> void:
|
||||
var new_track := dst.add_track(src.track_get_type(src_track))
|
||||
dst.track_set_path(new_track, src.track_get_path(src_track))
|
||||
dst.track_set_interpolation_type(new_track, src.track_get_interpolation_type(src_track))
|
||||
dst.track_set_interpolation_loop_wrap(new_track, src.track_get_interpolation_loop_wrap(src_track))
|
||||
dst.track_set_enabled(new_track, src.track_is_enabled(src_track))
|
||||
|
||||
for k in src.track_get_key_count(src_track):
|
||||
var time := src.track_get_key_time(src_track, k)
|
||||
var value = src.track_get_key_value(src_track, k)
|
||||
var transition := src.track_get_key_transition(src_track, k)
|
||||
dst.track_insert_key(new_track, time, value, transition)
|
||||
|
||||
Reference in New Issue
Block a user