@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)