Implement speech style toolbar live-edit refinements and whole speech show controls
- Added a new specification for the speech style toolbar live-edit, detailing immediate style application, scoped effect demonstration, and hover feedback. - Introduced refinements to the speech style toolbar, including improved chooser sizing, exclusive stage editing during speech composition, and removal of redundant controls. - Documented new stage controls for managing how the whole speech shows, including options for bubble arrangement, word appearance, and bubble motion. - Created a new log file for speech padding syntax to track engine versioning.
This commit is contained in:
@@ -6,7 +6,7 @@ extends Node
|
||||
|
||||
const ACTION_REGISTRY := preload("res://scripts/action_registry.gd")
|
||||
const SPEECH_BUBBLE_SCRIPT := preload("res://scripts/stickman_speech_bubble.gd")
|
||||
const SPEECH_BUBBLE_OFFSET := Vector2(0.0, -640.0)
|
||||
const SPEECH_PAYLOAD := preload("res://scripts/speech_payload.gd")
|
||||
|
||||
enum RunnerState { IDLE, EXECUTING }
|
||||
enum ActionPhase { NONE, WALKING, SPEAKING, WAITING, RAGDOLLING, RECOVERING, PROP_INTERACTION }
|
||||
@@ -40,6 +40,7 @@ var _phase_timer: float = 0.0
|
||||
var _primary_speech: Dictionary = {}
|
||||
var _overlay_speech: Dictionary = {}
|
||||
var _speech_bubble: Node2D = null
|
||||
var _speech_preview_suppressed: bool = false
|
||||
var _action_ids: Array[int] = []
|
||||
var _next_action_id: int = 1
|
||||
var _current_action_id: int = -1
|
||||
@@ -90,15 +91,33 @@ func tick(delta: float, rig_state: int, ragdoll_at_rest: bool) -> void:
|
||||
|
||||
|
||||
func speak(text: String, duration: float) -> void:
|
||||
_primary_speech = {"text": text, "time_left": maxf(duration, 0.0)}
|
||||
speak_action({"type": "speak", "text": text, "duration": maxf(duration, 0.0)})
|
||||
|
||||
|
||||
func speak_action(action: Dictionary) -> void:
|
||||
var normalized: Dictionary = SPEECH_PAYLOAD.normalize(action)
|
||||
var duration: float = SPEECH_PAYLOAD.duration_for(normalized)
|
||||
_primary_speech = {"action": normalized, "elapsed": 0.0, "duration": duration}
|
||||
_show_visible_speech()
|
||||
|
||||
|
||||
func speak_overlay(text: String, duration: float) -> void:
|
||||
_overlay_speech = {"text": text, "time_left": maxf(duration, 0.0)}
|
||||
speak_overlay_action({"type": "speak", "text": text, "duration": maxf(duration, 0.0)})
|
||||
|
||||
|
||||
func speak_overlay_action(action: Dictionary) -> void:
|
||||
var normalized: Dictionary = SPEECH_PAYLOAD.normalize(action)
|
||||
var duration: float = SPEECH_PAYLOAD.duration_for(normalized)
|
||||
_overlay_speech = {"action": normalized, "elapsed": 0.0, "duration": duration}
|
||||
_show_visible_speech()
|
||||
|
||||
|
||||
func set_speech_preview_suppressed(value: bool) -> void:
|
||||
_speech_preview_suppressed = value
|
||||
if _speech_bubble != null and is_instance_valid(_speech_bubble):
|
||||
(_speech_bubble as SpeechBubble).set_suppressed(value)
|
||||
|
||||
|
||||
func hide_speech() -> void:
|
||||
_primary_speech.clear()
|
||||
_overlay_speech.clear()
|
||||
@@ -108,7 +127,7 @@ func hide_speech() -> void:
|
||||
|
||||
func sync_facing(profile: int) -> void:
|
||||
if _speech_bubble != null and is_instance_valid(_speech_bubble):
|
||||
_speech_bubble.scale = Vector2(-1.0, 1.0) if profile == StickmanRig.FacingProfile.LEFT else Vector2.ONE
|
||||
(_speech_bubble as SpeechBubble).set_mirrored(profile == StickmanRig.FacingProfile.LEFT)
|
||||
|
||||
|
||||
func set_authored_queue(actions: Array[Dictionary]) -> bool:
|
||||
@@ -266,7 +285,7 @@ func enqueue_reactive(actions: Array[Dictionary]) -> void:
|
||||
for action: Dictionary in actions:
|
||||
var role: int = ACTION_REGISTRY.reactive_role(action)
|
||||
if role == ACTION_REGISTRY.ReactiveRole.OVERLAY:
|
||||
speak_overlay(String(action.get("text", "")), float(action.get("duration", 2.0)))
|
||||
speak_overlay_action(action)
|
||||
continue
|
||||
if role == ACTION_REGISTRY.ReactiveRole.TAKEOVER:
|
||||
if not takeover_seen:
|
||||
@@ -394,8 +413,12 @@ func _begin_action(action: Dictionary) -> void:
|
||||
if _locomotion == null or not _locomotion.walk_to(action.get("target", Vector2.ZERO), float(action.get("speed", -1.0))):
|
||||
_finish_action()
|
||||
"speak":
|
||||
_action_phase = ActionPhase.SPEAKING
|
||||
speak(String(action.get("text", "")), float(action.get("duration", 2.0)))
|
||||
if bool(action.get("keep_acting", false)):
|
||||
speak_overlay_action(action)
|
||||
_finish_action()
|
||||
else:
|
||||
_action_phase = ActionPhase.SPEAKING
|
||||
speak_action(action)
|
||||
"wait":
|
||||
_phase_timer = float(action.get("duration", 0.0))
|
||||
_action_phase = ActionPhase.WAITING
|
||||
@@ -523,22 +546,20 @@ func _is_blocked() -> bool:
|
||||
|
||||
|
||||
func _update_speech(delta: float) -> void:
|
||||
var visible_owner_changed: bool = false
|
||||
if not _primary_speech.is_empty() and not _is_blocked():
|
||||
_primary_speech["time_left"] = float(_primary_speech.get("time_left", 0.0)) - delta
|
||||
if float(_primary_speech["time_left"]) <= 0.0:
|
||||
_primary_speech.clear()
|
||||
speech_finished.emit()
|
||||
if not _overlay_speech.is_empty():
|
||||
_overlay_speech["time_left"] = float(_overlay_speech.get("time_left", 0.0)) - delta
|
||||
if float(_overlay_speech["time_left"]) <= 0.0:
|
||||
_overlay_speech["elapsed"] = float(_overlay_speech.get("elapsed", 0.0)) + delta
|
||||
if float(_overlay_speech.get("elapsed", 0.0)) >= float(_overlay_speech.get("duration", 0.0)):
|
||||
_overlay_speech.clear()
|
||||
visible_owner_changed = true
|
||||
speech_finished.emit()
|
||||
elif not _primary_speech.is_empty() and not _is_blocked():
|
||||
_primary_speech["elapsed"] = float(_primary_speech.get("elapsed", 0.0)) + delta
|
||||
if float(_primary_speech.get("elapsed", 0.0)) >= float(_primary_speech.get("duration", 0.0)):
|
||||
_primary_speech.clear()
|
||||
speech_finished.emit()
|
||||
if _overlay_speech.is_empty() and _primary_speech.is_empty():
|
||||
if _speech_bubble != null and is_instance_valid(_speech_bubble):
|
||||
_speech_bubble.hide_bubble()
|
||||
elif visible_owner_changed and _overlay_speech.is_empty():
|
||||
else:
|
||||
_show_visible_speech()
|
||||
|
||||
|
||||
@@ -546,11 +567,12 @@ func _show_visible_speech() -> void:
|
||||
if _rig == null:
|
||||
return
|
||||
if _speech_bubble == null or not is_instance_valid(_speech_bubble):
|
||||
_speech_bubble = SPEECH_BUBBLE_SCRIPT.new() as Node2D
|
||||
_speech_bubble = SPEECH_BUBBLE_SCRIPT.new() as SpeechBubble
|
||||
_speech_bubble.name = "SpeechBubble"
|
||||
_speech_bubble.position = SPEECH_BUBBLE_OFFSET
|
||||
_rig.add_child(_speech_bubble)
|
||||
sync_facing(_rig.facing_profile)
|
||||
(_speech_bubble as SpeechBubble).set_suppressed(_speech_preview_suppressed)
|
||||
var visible_speech: Dictionary = _overlay_speech if not _overlay_speech.is_empty() else _primary_speech
|
||||
if not visible_speech.is_empty():
|
||||
_speech_bubble.show_text(String(visible_speech.get("text", "")))
|
||||
var action: Dictionary = visible_speech.get("action", {})
|
||||
(_speech_bubble as SpeechBubble).render_action(action, float(visible_speech.get("elapsed", 0.0)), false)
|
||||
|
||||
Reference in New Issue
Block a user