feat: Introduce Tekton character model, materials, and associated control scripts.
This commit is contained in:
+4
-1
@@ -1874,7 +1874,10 @@ func sync_throw_tekton(target_pos: Vector2i):
|
|||||||
var tekton = carried_tekton
|
var tekton = carried_tekton
|
||||||
carried_tekton = null
|
carried_tekton = null
|
||||||
is_carrying_tekton = false
|
is_carrying_tekton = false
|
||||||
tekton.set_carried(false)
|
if tekton.has_method("set_thrown"):
|
||||||
|
tekton.set_thrown(true)
|
||||||
|
else:
|
||||||
|
tekton.set_carried(false)
|
||||||
|
|
||||||
# Visual Arc Tween
|
# Visual Arc Tween
|
||||||
var start_pos = tekton.global_position
|
var start_pos = tekton.global_position
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ blend_shape_mode = 0
|
|||||||
[sub_resource type="Animation" id="Animation_0jd8m"]
|
[sub_resource type="Animation" id="Animation_0jd8m"]
|
||||||
resource_name = "tekton_idle"
|
resource_name = "tekton_idle"
|
||||||
length = 0.875
|
length = 0.875
|
||||||
|
loop_mode = 1
|
||||||
tracks/0/type = "position_3d"
|
tracks/0/type = "position_3d"
|
||||||
tracks/0/imported = true
|
tracks/0/imported = true
|
||||||
tracks/0/enabled = true
|
tracks/0/enabled = true
|
||||||
@@ -569,6 +570,7 @@ _data = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[node name="tekton" type="Node3D" unique_id=2052742928]
|
[node name="tekton" type="Node3D" unique_id=2052742928]
|
||||||
|
transform = Transform3D(0.15, 0, 0, 0, 0.15, 0, 0, 0, 0.15, 0, 0, 0)
|
||||||
|
|
||||||
[node name="Armature" type="Node3D" parent="." unique_id=1122137922]
|
[node name="Armature" type="Node3D" parent="." unique_id=1122137922]
|
||||||
transform = Transform3D(1.1701986, 0, 0, 0, 1.1701986, 0, 0, 0, 1.1701986, 0, 0, 0)
|
transform = Transform3D(1.1701986, 0, 0, 0, 1.1701986, 0, 0, 0, 1.1701986, 0, 0, 0)
|
||||||
@@ -586,7 +588,7 @@ bones/1/parent = 0
|
|||||||
bones/1/rest = Transform3D(0.99999994, -6.3734255e-08, -2.29742e-07, -1.065814e-14, 0.9636076, -0.26732078, 2.384186e-07, 0.2673208, 0.96360755, -1.4760682e-14, 1.0773813, 0)
|
bones/1/rest = Transform3D(0.99999994, -6.3734255e-08, -2.29742e-07, -1.065814e-14, 0.9636076, -0.26732078, 2.384186e-07, 0.2673208, 0.96360755, -1.4760682e-14, 1.0773813, 0)
|
||||||
bones/1/enabled = true
|
bones/1/enabled = true
|
||||||
bones/1/position = Vector3(-1.4760682e-14, 1.0773813, 0)
|
bones/1/position = Vector3(-1.4760682e-14, 1.0773813, 0)
|
||||||
bones/1/rotation = Quaternion(0.13007852, 0.02469786, 0.04730596, 0.9900666)
|
bones/1/rotation = Quaternion(0.13489334, -1.18119765e-07, 1.6080534e-08, 0.9908601)
|
||||||
bones/1/scale = Vector3(0.99999994, 1, 0.99999994)
|
bones/1/scale = Vector3(0.99999994, 1, 0.99999994)
|
||||||
bones/2/name = "Bone.002"
|
bones/2/name = "Bone.002"
|
||||||
bones/2/parent = 1
|
bones/2/parent = 1
|
||||||
|
|||||||
+75
-21
@@ -7,11 +7,12 @@ signal movement_finished
|
|||||||
|
|
||||||
@export var current_position: Vector2i = Vector2i(0, 0)
|
@export var current_position: Vector2i = Vector2i(0, 0)
|
||||||
@export var health: int = 3
|
@export var health: int = 3
|
||||||
@export var movement_speed: float = 0.4 # Seconds per step
|
@export var movement_speed: float = 2.0 # Seconds per step (Slower walk)
|
||||||
|
|
||||||
var enhanced_gridmap: Node
|
var enhanced_gridmap: Node
|
||||||
var is_moving: bool = false
|
var is_moving: bool = false
|
||||||
var is_carried: bool = false
|
var is_carried: bool = false
|
||||||
|
var is_thrown: bool = false
|
||||||
var carrier: Node3D = null
|
var carrier: Node3D = null
|
||||||
var tween: Tween
|
var tween: Tween
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ func sync_position(pos: Vector2i):
|
|||||||
update_visual_position()
|
update_visual_position()
|
||||||
|
|
||||||
func update_visual_position():
|
func update_visual_position():
|
||||||
if is_carried: return
|
if is_carried or is_thrown: return
|
||||||
|
|
||||||
# Align with floor height (matching player's typical grounded height)
|
# Align with floor height (matching player's typical grounded height)
|
||||||
var floor_y = 0.05
|
var floor_y = 0.05
|
||||||
@@ -43,7 +44,7 @@ func update_visual_position():
|
|||||||
position = Vector3(current_position.x + 0.2, floor_y, current_position.y + 0.2)
|
position = Vector3(current_position.x + 0.2, floor_y, current_position.y + 0.2)
|
||||||
|
|
||||||
func move_to(target_pos: Vector2i):
|
func move_to(target_pos: Vector2i):
|
||||||
if is_moving or is_carried: return
|
if is_moving or is_carried or is_thrown: return
|
||||||
|
|
||||||
# Validate
|
# Validate
|
||||||
if not enhanced_gridmap.is_position_valid(target_pos):
|
if not enhanced_gridmap.is_position_valid(target_pos):
|
||||||
@@ -63,11 +64,14 @@ func move_to(target_pos: Vector2i):
|
|||||||
var target_rot = atan2(dir.x, dir.z)
|
var target_rot = atan2(dir.x, dir.z)
|
||||||
rotation.y = target_rot
|
rotation.y = target_rot
|
||||||
|
|
||||||
|
play_animation("tekton_move")
|
||||||
|
|
||||||
tween = create_tween()
|
tween = create_tween()
|
||||||
tween.tween_property(self , "position", target_world_pos, movement_speed).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
tween.tween_property(self , "position", target_world_pos, movement_speed).set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN_OUT)
|
||||||
tween.tween_callback(func():
|
tween.tween_callback(func():
|
||||||
current_position = target_pos
|
current_position = target_pos
|
||||||
is_moving = false
|
is_moving = false
|
||||||
|
play_animation("tekton_idle")
|
||||||
emit_signal("movement_finished")
|
emit_signal("movement_finished")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,7 +80,6 @@ func move_to(target_pos: Vector2i):
|
|||||||
|
|
||||||
@rpc("any_peer", "call_remote", "reliable")
|
@rpc("any_peer", "call_remote", "reliable")
|
||||||
func sync_movement(target_pos: Vector2i):
|
func sync_movement(target_pos: Vector2i):
|
||||||
# Clients execute the move visually
|
|
||||||
move_to(target_pos)
|
move_to(target_pos)
|
||||||
|
|
||||||
# --- COMBAT / INTERACTION ---
|
# --- COMBAT / INTERACTION ---
|
||||||
@@ -98,6 +101,10 @@ func on_hit(attacker: Node = null, intensity: float = 1.0):
|
|||||||
func set_carried(state: bool, p_carrier: Node3D = null):
|
func set_carried(state: bool, p_carrier: Node3D = null):
|
||||||
is_carried = state
|
is_carried = state
|
||||||
carrier = p_carrier
|
carrier = p_carrier
|
||||||
|
|
||||||
|
if is_carried:
|
||||||
|
play_animation("tekton_idle")
|
||||||
|
|
||||||
is_moving = false
|
is_moving = false
|
||||||
if tween: tween.kill()
|
if tween: tween.kill()
|
||||||
|
|
||||||
@@ -111,9 +118,33 @@ func set_carried(state: bool, p_carrier: Node3D = null):
|
|||||||
else:
|
else:
|
||||||
controller.call("_start_timer")
|
controller.call("_start_timer")
|
||||||
|
|
||||||
if not state:
|
if not state and not is_thrown:
|
||||||
update_visual_position()
|
update_visual_position()
|
||||||
|
|
||||||
|
@rpc("any_peer", "call_local")
|
||||||
|
func set_thrown(state: bool):
|
||||||
|
is_thrown = state
|
||||||
|
|
||||||
|
if is_thrown:
|
||||||
|
# If we are thrown, we are not carried anymore, but we must stay disabled
|
||||||
|
if is_carried:
|
||||||
|
set_carried(false) # This will re-enable controller, so we must disable it again below
|
||||||
|
|
||||||
|
var controller = get_node_or_null("TektonController")
|
||||||
|
if controller:
|
||||||
|
controller.set_physics_process(false)
|
||||||
|
if controller.get("timer"):
|
||||||
|
controller.timer.stop()
|
||||||
|
|
||||||
|
# play_animation("RESET") # Optional: ensure no animation plays? Or just idle.
|
||||||
|
# User said "don't play ANY animation".
|
||||||
|
# If we play nothing, it freezes on last frame.
|
||||||
|
# Maybe just don't call anything new, and let previous (idle) stick?
|
||||||
|
# Or stop?
|
||||||
|
var anim_player = get_node_or_null("Visuals/tekton/Armature/AnimationPlayer")
|
||||||
|
if not anim_player: anim_player = find_child("AnimationPlayer", true, false)
|
||||||
|
if anim_player: anim_player.stop()
|
||||||
|
|
||||||
func _process(delta):
|
func _process(delta):
|
||||||
if is_carried and is_instance_valid(carrier):
|
if is_carried and is_instance_valid(carrier):
|
||||||
# Carry on head: offset Y by approx carrier height (e.g. 1.25)
|
# Carry on head: offset Y by approx carrier height (e.g. 1.25)
|
||||||
@@ -125,31 +156,36 @@ var original_scales: Array[Vector3] = []
|
|||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
# Cache meshes and their initial scales
|
# Cache meshes and their initial scales
|
||||||
# We wait a frame to ensure all children are ready and transforms applied
|
_cache_meshes(self)
|
||||||
await get_tree().process_frame
|
|
||||||
var meshes = find_children("*", "MeshInstance3D", true)
|
func _cache_meshes(node: Node):
|
||||||
for mesh in meshes:
|
if node is MeshInstance3D:
|
||||||
mesh_cache.append(mesh)
|
mesh_cache.append(node)
|
||||||
original_scales.append(mesh.scale)
|
original_scales.append(node.scale)
|
||||||
|
|
||||||
|
for child in node.get_children():
|
||||||
|
_cache_meshes(child)
|
||||||
|
|
||||||
func _flash_damage():
|
func _flash_damage():
|
||||||
# If cache empty (e.g. called before ready), try to populate or just skip custom scaling
|
if mesh_cache.is_empty(): return
|
||||||
if mesh_cache.is_empty():
|
|
||||||
return
|
var tween_flash = create_tween()
|
||||||
|
|
||||||
for i in range(mesh_cache.size()):
|
for i in range(mesh_cache.size()):
|
||||||
var mesh = mesh_cache[i]
|
var mesh = mesh_cache[i]
|
||||||
if is_instance_valid(mesh):
|
var original_scale = original_scales[i]
|
||||||
var base_scale = original_scales[i]
|
|
||||||
var t = create_tween()
|
# Pop effect
|
||||||
t.tween_property(mesh, "scale", base_scale * 1.2, 0.1)
|
tween_flash.parallel().tween_property(mesh, "scale", original_scale * 1.3, 0.1).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
|
||||||
t.tween_property(mesh, "scale", base_scale, 0.1)
|
tween_flash.parallel().tween_property(mesh, "scale", original_scale, 0.2).set_delay(0.1)
|
||||||
|
|
||||||
var is_recovering: bool = false # True when shrunk/waiting
|
var is_recovering: bool = false # True when shrunk/waiting
|
||||||
|
var recovery_timer: Timer
|
||||||
|
|
||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
func on_thrown_landing(attacker: Node = null, intensity: float = 1.0):
|
func on_thrown_landing(attacker: Node = null, intensity: float = 1.0):
|
||||||
"""Called when Tekton lands after being thrown or knocked."""
|
"""Called when Tekton lands after being thrown or knocked."""
|
||||||
|
is_thrown = false
|
||||||
|
|
||||||
# Prevent double-triggering (re-entrancy)
|
# Prevent double-triggering (re-entrancy)
|
||||||
if is_recovering:
|
if is_recovering:
|
||||||
return
|
return
|
||||||
@@ -302,3 +338,21 @@ func spawn_tiles_around(count: int = 4):
|
|||||||
if main:
|
if main:
|
||||||
main.rpc("sync_grid_item", pos.x, 1, pos.y, item_id)
|
main.rpc("sync_grid_item", pos.x, 1, pos.y, item_id)
|
||||||
spawned += 1
|
spawned += 1
|
||||||
|
|
||||||
|
func play_animation(anim_name: String):
|
||||||
|
# Try specific user path first
|
||||||
|
var anim_player = get_node_or_null("Visuals/tekton/Armature/AnimationPlayer")
|
||||||
|
|
||||||
|
# If not found, try finding recursive
|
||||||
|
if not anim_player:
|
||||||
|
anim_player = find_child("AnimationPlayer", true, false)
|
||||||
|
|
||||||
|
if anim_player and anim_player.has_animation(anim_name):
|
||||||
|
anim_player.play(anim_name)
|
||||||
|
# print("[Tekton] Playing animation: %s" % anim_name)
|
||||||
|
else:
|
||||||
|
if not anim_player:
|
||||||
|
print("[Tekton] AnimationPlayer NOT FOUND! Scene tree:")
|
||||||
|
print_tree_pretty()
|
||||||
|
else:
|
||||||
|
print("[Tekton] Animation '%s' NOT FOUND in AnimationPlayer!" % anim_name)
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ extends Node
|
|||||||
# TektonController - AI for Tekton NPC
|
# TektonController - AI for Tekton NPC
|
||||||
# Handles random movement logic.
|
# Handles random movement logic.
|
||||||
|
|
||||||
@export var move_interval_min: float = 2.0
|
@export var move_interval_min: float = 0.1
|
||||||
@export var move_interval_max: float = 5.0
|
@export var move_interval_max: float = 0.1
|
||||||
|
|
||||||
var tekton: Node3D
|
var tekton: Node3D
|
||||||
var enhanced_gridmap: Node
|
var enhanced_gridmap: Node
|
||||||
|
|||||||
Reference in New Issue
Block a user