Files
experiment-adventure-ai/action_types_design.md
2025-07-30 21:52:27 -07:00

8.7 KiB

Action Types Design

Overview

This document details the specific action types that will be implemented for the cutscene system. Each action type handles a specific kind of operation in a cutscene.

Core Action Types

1. MoveAction

Moves a character to a specific position over time.

class_name MoveAction
extends Action

# Properties
var character: Node2D        # The character to move
var target_position: Vector2 # Target position
var speed: float = 100.0     # Movement speed (pixels/second)
var threshold: float = 5.0   # Distance threshold for completion

# Internal
var start_position: Vector2
var distance: float
var traveled: float = 0.0

func _init(character_node: Node2D, target: Vector2, move_speed: float = 100.0):
    character = character_node
    target_position = target
    speed = move_speed
    name = "MoveAction"

func start() -> void:
    if character == null:
        ._set_failed("Character is null")
        return
    
    start_position = character.position
    distance = start_position.distance_to(target_position)
    traveled = 0.0
    ._set_running()

func update(delta: float) -> void:
    if state != State.RUNNING:
        return
    
    if character == null:
        ._set_failed("Character was destroyed during action")
        return
    
    # Calculate movement for this frame
    var frame_distance = speed * delta
    traveled += frame_distance
    
    # If we've reached or overshot the target
    if traveled >= distance:
        character.position = target_position
        ._set_completed()
        return
    
    # Move character along the path
    var direction = (target_position - start_position).normalized()
    character.position = start_position + direction * traveled

func is_completed() -> bool:
    return state == State.COMPLETED

2. TurnAction

Makes a character turn to face a direction or another character.

class_name TurnAction
extends Action

# Properties
var character: Node2D     # The character to turn
var target: Variant       # Either a Vector2 position or another Node2D
var turn_speed: float = 2.0  # Rotation speed (radians/second)

func _init(character_node: Node2D, turn_target: Variant, speed: float = 2.0):
    character = character_node
    target = turn_target
    turn_speed = speed
    name = "TurnAction"

func start() -> void:
    if character == null:
        ._set_failed("Character is null")
        return
    ._set_running()

func update(delta: float) -> void:
    if state != State.RUNNING:
        return
    
    if character == null:
        ._set_failed("Character was destroyed during action")
        return
    
    # Calculate target rotation
    var target_position: Vector2
    if target is Vector2:
        target_position = target
    elif target is Node2D:
        target_position = target.position
    else:
        ._set_failed("Invalid target type")
        return
    
    var direction = target_position - character.position
    var target_rotation = atan2(direction.y, direction.x)
    
    # Rotate toward target
    var angle_diff = angle_difference(character.rotation, target_rotation)
    
    # If we're close enough, complete the action
    if abs(angle_diff) < 0.01:
        character.rotation = target_rotation
        ._set_completed()
        return
    
    # Rotate based on turn speed
    var rotation_amount = sign(angle_diff) * turn_speed * delta
    if abs(rotation_amount) > abs(angle_diff):
        character.rotation = target_rotation
        ._set_completed()
    else:
        character.rotation += rotation_amount

func is_completed() -> bool:
    return state == State.COMPLETED

3. DialogueAction

Displays dialogue text, potentially with character-specific formatting.

class_name DialogueAction
extends Action

# Properties
var character: Node2D     # The speaking character (optional)
var text: String          # Dialogue text
var duration: float = 0.0 # Duration to display (0 = manual advance)
var auto_advance: bool = true  # Whether to auto-advance after duration

func _init(speaking_character: Node2D, dialogue_text: String, display_duration: float = 0.0):
    character = speaking_character
    text = dialogue_text
    duration = display_duration
    name = "DialogueAction"

func start() -> void:
    # In a real implementation, this would interface with a dialogue system
    # For now, we'll simulate by printing to console
    print("%s: %s" % [character.name if character else "Narrator", text])
    ._set_running()
    
    # If duration is 0, this is a manual advance action
    # In a real game, this would wait for player input
    if duration <= 0:
        # For demo purposes, we'll complete immediately
        # In a real game, this would wait for input
        ._set_completed()
    else:
        # Start a timer for automatic completion
        var timer = Timer.new()
        timer.wait_time = duration
        timer.one_shot = true
        timer.connect("timeout", _on_timer_timeout)
        # In a real implementation, we'd add this to the scene tree
        # add_child(timer)
        timer.start()

func _on_timer_timeout():
    ._set_completed()

func is_completed() -> bool:
    return state == State.COMPLETED

4. AnimationAction

Plays a specific animation on a character.

class_name AnimationAction
extends Action

# Properties
var character: Node2D     # The character to animate
var animation_name: String # Name of the animation to play
var loop: bool = false     # Whether to loop the animation

func _init(character_node: Node2D, anim_name: String, should_loop: bool = false):
    character = character_node
    animation_name = anim_name
    loop = should_loop
    name = "AnimationAction"

func start() -> void:
    if character == null:
        ._set_failed("Character is null")
        return
    
    # In a real implementation, this would interface with an AnimationPlayer
    # For now, we'll simulate by printing to console
    print("Playing animation '%s' on %s" % [animation_name, character.name])
    
    # Check if character has an AnimationPlayer
    var anim_player = _get_animation_player()
    if anim_player == null:
        ._set_failed("Character has no AnimationPlayer")
        return
    
    # Connect to animation finished signal for non-looping animations
    if not loop:
        if not anim_player.is_connected("animation_finished", _on_animation_finished):
            anim_player.connect("animation_finished", _on_animation_finished)
    
    # Play the animation
    anim_player.play(animation_name, loop)
    ._set_running()
    
    # For looping animations, we complete immediately
    # (they would be stopped by another action)
    if loop:
        ._set_completed()

func _get_animation_player() -> AnimationPlayer:
    # Try to find AnimationPlayer as a child
    for child in character.get_children():
        if child is AnimationPlayer:
            return child
    
    # Try to find AnimationPlayer in the scene tree
    return character.get_node_or_null("AnimationPlayer") as AnimationPlayer

func _on_animation_finished(anim_name: String):
    if anim_name == animation_name:
        ._set_completed()

func is_completed() -> bool:
    return state == State.COMPLETED

5. WaitAction

Pauses execution for a specified time.

class_name WaitAction
extends Action

# Properties
var duration: float        # Time to wait in seconds
var elapsed_time: float = 0.0

func _init(wait_duration: float):
    duration = wait_duration
    name = "WaitAction"

func start() -> void:
    elapsed_time = 0.0
    ._set_running()

func update(delta: float) -> void:
    if state != State.RUNNING:
        return
    
    elapsed_time += delta
    if elapsed_time >= duration:
        ._set_completed()

func is_completed() -> bool:
    return state == State.COMPLETED

Extensibility

To create new action types:

  1. Create a new class that extends Action
  2. Implement the required methods:
    • start() - Initialize and begin the action
    • update(delta) - Update the action each frame (if needed)
    • is_completed() - Return true when the action is finished
  3. Optionally implement stop() for cleanup
  4. Use appropriate signals to notify completion

Example of a custom action:

class_name CustomAction
extends Action

# Custom properties for this action
var custom_parameter: String

func _init(param: String):
    custom_parameter = param
    name = "CustomAction"

func start() -> void:
    # Perform the action
    print("Executing custom action with parameter: %s" % custom_parameter)
    
    # For immediate actions, complete right away
    ._set_completed()

func is_completed() -> bool:
    return state == State.COMPLETED

This design provides a solid set of core action types while maintaining the flexibility to add new types as needed.