Yarn Spinner for Unity

repository·main·Indexed 21 days ago

https://github.com/yarnspinnertool/yarnspinner-unity

A dialogue system for Unity (package dev.yarnspinner.unity) that allows developers to write interactive, screenplay-style conversations. It provides a framework for handling dialogue lines, player options, and custom C# commands and functions to trigger game logic. The system supports various return types for asynchronous flow, including Coroutines, Tasks, and YarnTask, and includes a Roslyn source generator for action registration.

Tokens
6.4K
Snippets
15
Records
24
Agent score
73%

What's inside Yarn Spinner for Unity

  1. What is Yarn Spinner?

    main

    Yarn Spinner is a dialogue system for games that allows you to write interactive conversations in a screenplay-like format. During runtime, the system provides your game with three main types of data:

    1. Lines: The actual dialogue text to be displayed.
    2. Options: Choices presented to the player to drive the conversation.
    3. Commands: Instructions sent to your game to trigger specific logic or events in your scene.

    Note: This repository is the Unity integration. The core compiler logic resides in the Yarn Spinner compiler repository.

  2. Install Yarn Spinner for Unity

    main

    You can install Yarn Spinner for Unity through several methods depending on your preference for support or package management:

    Supporting the developers via purchase helps maintain the tool.

  3. Build the Actions Registration Generator source code generator

    main

    The Actions Registration Generator is a Roslyn source generator that produces registration code for Yarn Spinner actions (such as YarnCommand and YarnAction).

    Because the source folder is suffixed with a tilde ~ to prevent Unity from attempting to import it, you must build the generator manually using the .NET SDK. The resulting DLL will be located in the SourceGenerator directory of the Yarn Spinner installation.

    dotnet build
  4. Supported return types for Yarn commands

    main

    When registering a method as a command, Yarn Spinner can handle several return types to manage asynchronous flow or coroutines:

    • void: Standard synchronous command.
    • IEnumerator: A Unity Coroutine. Yarn Spinner will automatically start this using MonoBehaviour.StartCoroutine.
    • Coroutine: A Unity Coroutine object. Yarn Spinner will wait for this to complete.
    • Task: A standard C# Task. Yarn Spinner will wait for the task to complete.
    • YarnTask: A specialized task type for Yarn Spinner.
    • Awaitable: (Unity 2023.1+) Supported and converted to YarnTask.
    • UniTask: If USE_UNITASK is defined, these are supported and converted to YarnTask.

    If a method returns any other type, it is treated as a void command (the return value is ignored).

  5. How component-based commands find targets

    main

    If you register an instance method belonging to a Component class, the command becomes a 'dynamic target' command.

    When the command is called in Yarn, the first argument provided must be the name of a GameObject currently in the scene.

    Workflow:

    1. Yarn calls <<command_name TargetName Arg1 Arg2>>.
    2. Yarn Spinner calls GameObject.Find("TargetName").
    3. It then calls GetComponent<DeclaringType>() on that GameObject.
    4. If the component is found, the method is invoked with the remaining arguments (Arg1, Arg2).

    If the GameObject is not found, or the component is missing, the command dispatch will fail with a TargetMissingComponent status.

  6. Register custom commands in Yarn Spinner

    main

    You can register C# methods as commands that can be called directly from Yarn scripts. Yarn Spinner supports several types of method signatures:

    1. Static Methods: Registered as global commands.
    2. Instance Methods on Components: If the method is an instance method on a class inheriting from UnityEngine.Component, the first argument of the command in the Yarn script must be the name of a GameObject. Yarn Spinner will then find that GameObject and call the method on the component of the correct type attached to it.
    3. Delegates/Funcs: You can pass a Delegate or Func<object> to register a handler.

    Constraints:

    • Command names cannot contain spaces.
    • For instance methods on Components, the method must be declared on a class that inherits from Component.
    • If a method uses an array as a parameter, that array must be the last parameter in the method signature.
    // Example: Registering a static command
    // Yarn: <<add_score 10>>
    public static void AddScore(int amount) { ... }
    actions.AddCommandHandler("add_score", (Action)AddScore);
    
    // Example: Registering a component command
    // Yarn: <<play_sound MyPlayer SoundEffect>>
    // (Where 'MyPlayer' is a GameObject name and 'SoundEffect' is a parameter)
    public void PlaySound(string effectName) { ... }
    actions.AddCommandHandler("play_sound", (Action)PlaySound);
  7. Register custom functions in Yarn Spinner

    main

    In addition to commands (which are typically used for side effects), you can register functions that return values to be used within Yarn expressions. Use AddFunction to register a function by name. Function names also cannot contain spaces.

    Constraints:

    • Function names must be unique within the Library.
    • If a name already exists, an error will be logged.
    // Example: Registering a function
    // Yarn: <<set $score to add_one($score)>>
    public int AddOne(int value) => value + 1;
    
    actions.AddFunction("add_one", (Func<int, int>)AddOne);
  8. Expose C# methods to Yarn Spinner using YarnActionAttribute

    main

    To make a C# method callable from a Yarn dialogue script as a command, decorate the method with a class that inherits from YarnActionAttribute.

    By default, the command name used in Yarn will match the C# method name. However, you can provide a custom name via the constructor to allow for different naming conventions (e.g., using snake_case in Yarn while keeping PascalCase in C#).

    using Yarn.Unity;
    
    public class MyGameCommands
    {
        // This will be callable in Yarn as 'walk_to_point'
        [YarnAction("walk_to_point")]
        public void WalkToPoint(float x, float y)
        {
            // Implementation
        }
    
        // This will be callable in Yarn as 'say_hello'
        [YarnAction("say_hello")]
        public void SayHello()
        {
            // Implementation
        }
    
        // This will be callable in Yarn as 'Jump' (uses method name)
        [YarnAction]
        public void Jump()
        {
            // Implementation
        }
    }
  9. Mark methods as Yarn Spinner commands using [YarnCommand]

    main

    To allow a DialogueRunner to execute a C# method as a command from your Yarn dialogue, decorate the method with the [YarnCommand] attribute.

    Command Resolution Logic

    When a DialogueRunner encounters a command (e.g., <<move player fast>>):

    1. It splits the command by spaces.
    2. It checks if the second word is the name of an active GameObject in the scene.
    3. If a GameObject is found, it searches its attached MonoBehaviour components for a method marked with [YarnCommand] where the Name matches the first word of the command.
    4. If the method is static, it does not attempt to resolve a GameObject and is called directly.

    Parameter Mapping Rules

    Once a method is identified, the DialogueRunner maps the remaining words in the command to the method's parameters:

    Parameter TypeMapping Behavior
    string[]Receives an array containing all words in the command after the first two.
    Fixed number of parametersIf the number of words matches the parameter count, each word is passed as an individual parameter.
    GameObjectUses GameObject.Find(string) to locate the object (must be active).
    ComponentLocates the component on the GameObject found via GameObject.Find(string) (must be active).
    boolConverts the string "true" or "false" to a boolean. Special Case: If the string matches the parameter name (case-insensitive), it is treated as true (e.g., <<move wait>> for a Move(bool wait) method).
    Other typesUses Convert.ChangeType with CultureInfo.InvariantCulture. You can implement IConvertible to support custom types.

    Note: If parameters cannot be matched or converted, the method will not be called and a warning will be issued.

    Async and Coroutines

    You can attach [YarnCommand] to IEnumerator (coroutines), Coroutine returning methods, or Task returning methods. The DialogueRunner will automatically pause dialogue execution until the coroutine or task completes.

    using UnityEngine;
    using Yarn.Unity;
    using System.Collections;
    
    public class PlayerController : MonoBehaviour
    {
        // Simple command: <<move player fast>>
        [YarnCommand("move")]
        public void MovePlayer(GameObject target, string speed)
        {
            Debug.Log($"Moving {target.name} at speed {speed}");
        }
    
        // Boolean command with self-documenting parameter: <<wait true>> or <<wait>>
        [YarnCommand("wait")]
        public void Wait(bool wait)
        {
            if (wait) StartCoroutine(WaitRoutine());
        }
    
        private IEnumerator WaitRoutine()
        {
            yield return new WaitForSeconds(1f);
        }
    
        // Static command (no GameObject required): <<set_score 10>>
        [YarnCommand("set_score")]
        public static void SetScore(int score)
        {
            Debug.Log($"Score set to: {score}");
        }
    }