88 lines
3.2 KiB
GDScript
88 lines
3.2 KiB
GDScript
@tool
|
|
class_name SpineDialogicPortrait
|
|
extends DialogicPortrait
|
|
|
|
#region EXPORTS
|
|
|
|
@export_group("Main")
|
|
## Path to the .tres SpineSkeletonDataResource file.
|
|
@export_file var spine_skeleton_data_file := ""
|
|
|
|
func _ready() -> void:
|
|
$SpineSprite.animation_completed.connect(_on_animation_completed)
|
|
|
|
# Just a signal connection example
|
|
func _on_animation_completed(spine_sprite: Object, animation_state: SpineAnimationState, track_entry: SpineTrackEntry) -> void:
|
|
print(track_entry)
|
|
pass
|
|
|
|
#region DIALOGIC PORTRAIT OVERRIDES
|
|
|
|
## If the next portrait uses the same scene, reuse this instance rather than
|
|
## creating a new one (allows smooth animation transitions).
|
|
func _should_do_portrait_update(_character: DialogicCharacter, _portrait: String) -> bool:
|
|
return true
|
|
|
|
|
|
## Called by Dialogic when the portrait is first shown or changed.
|
|
## [param passed_character] — the DialogicCharacter resource.
|
|
## [param passed_portrait] — the portrait name (e.g. "Idle", "Walk").
|
|
##
|
|
## Export overrides (like spine_skeleton_data_file) are applied by Dialogic
|
|
## *before* this method is called.
|
|
func _update_portrait(passed_character: DialogicCharacter, passed_portrait: String) -> void:
|
|
apply_character_and_portrait(passed_character, passed_portrait)
|
|
|
|
# Load the skeleton data file (export override from character portrait config).
|
|
if not spine_skeleton_data_file.is_empty() and ResourceLoader.exists(spine_skeleton_data_file):
|
|
var skel_res := load(spine_skeleton_data_file)
|
|
if skel_res:
|
|
$SpineSprite.skeleton_data_res = skel_res
|
|
|
|
# Anchor the SpineSprite at the root origin. Dialogic treats the portrait
|
|
# root's (0,0) as the bottom-center pivot. Scale is applied by the portrait
|
|
# container, so we reset the SpineSprite's own scale to 1.
|
|
$SpineSprite.position = Vector2.ZERO
|
|
$SpineSprite.scale = Vector2.ONE
|
|
|
|
# Play the animation matching the portrait name (loop on track 0).
|
|
var anim_name := passed_portrait
|
|
var loop := anim_name == "walk"
|
|
$SpineSprite.get_animation_state().set_animation(anim_name, loop, 0)
|
|
|
|
|
|
## Returns the bounding rectangle of this portrait relative to the root node.
|
|
## The root node's origin (0,0) is the spine skeleton's root bone.
|
|
## Dialogic uses this rect to compute the scale/offset for the portrait.
|
|
func _get_covered_rect() -> Rect2:
|
|
if not is_instance_valid($SpineSprite):
|
|
return Rect2()
|
|
|
|
# Use the skeleton's own bounds — the rect is in the skeleton's local
|
|
# space, relative to the root bone at (0,0).
|
|
return $SpineSprite.get_skeleton().get_bounds()
|
|
|
|
|
|
## Called by Dialogic when the character should be mirrored (e.g. facing left
|
|
## on a right-side position).
|
|
func _set_mirror(mirror: bool) -> void:
|
|
if not is_instance_valid($SpineSprite):
|
|
return
|
|
# Mirror by flipping the skeleton horizontally.
|
|
$SpineSprite.get_skeleton().set_scale_x(-1.0 if mirror else 1.0)
|
|
|
|
|
|
## Called when this character becomes the active speaker.
|
|
func _highlight() -> void:
|
|
if not is_instance_valid($SpineSprite):
|
|
return
|
|
# Visual feedback: brighten slightly when speaking.
|
|
$SpineSprite.modulate = Color(1.2, 1.2, 1.2, 1.0)
|
|
|
|
|
|
## Called when this character stops being the active speaker.
|
|
func _unhighlight() -> void:
|
|
if not is_instance_valid($SpineSprite):
|
|
return
|
|
$SpineSprite.modulate = Color(1.0, 1.0, 1.0, 1.0)
|