This commit is contained in:
SoulliesOfficial
2026-06-09 11:21:59 -04:00
parent 7c60c40d6b
commit 021e76efe7
493 changed files with 50500 additions and 2211 deletions

View File

@@ -0,0 +1,125 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using UnityEngine;
using Yarn.Markup;
using System.Threading;
#if USE_TMP
using TMPro;
#else
using TMP_Text = Yarn.Unity.TMPShim;
#endif
#nullable enable
namespace Yarn.Unity
{
/// <summary>
/// A <see cref="IActionMarkupHandler"/> is an object that reacts to the
/// delivery of a line of dialogue, and can optionally control the timing of
/// that delivery.
/// </summary>
/// <remarks>
/// <para>
/// There are a number of cases where a line's delivery needs to have its
/// timing controlled. For example, <see cref="PauseEventProcessor"/> adds a
/// small delay between each character, creating a 'typewriter' effect as
/// each letter appears over time.
/// </para>
/// <para>
/// Another example of a <see cref="IActionMarkupHandler"/> is an in-line
/// event or animation, such as causing a character to play an animation
/// (and waiting for that animation to complete before displaying the rest
/// of the line).
/// </para>
/// </remarks>
public interface IActionMarkupHandler
{
/// <summary>
/// Called when the line view receives the line, to prepare for showing
/// the line.
/// </summary>
/// <remarks>
/// This method is called before any part of the line is visible, and is
/// an opportunity to set up any part of the <see
/// cref="ActionMarkupHandler"/>'s display before the user can see it.
/// </remarks>
/// <param name="line">The line being presented.</param>
/// <param name="text">A <see cref="TMP_Text"/> object that the line is
/// being displayed in.</param>
public void OnPrepareForLine(MarkupParseResult line, TMP_Text text);
/// <summary>
/// Called immediately before the first character in the line is
/// presented.
/// </summary>
/// <param name="line">The line being presented.</param>
/// <param name="text">A <see cref="TMP_Text"/> object that the line is
/// being displayed in.</param>
public void OnLineDisplayBegin(MarkupParseResult line, TMP_Text text);
/// <summary>
/// Called repeatedly for each visible character in the line.
/// </summary>
/// <remarks> This method is a <see cref="ActionMarkupHandler"/>
/// object's main opportunity to take action during line
/// display.</remarks>
/// <param name="currentCharacterIndex">The zero-based index of the
/// character being displayed.</param>
/// <param name="text">A <see cref="TMP_Text"/> object that the line is
/// being displayed in.</param>
/// <param name="cancellationToken">A cancellation token representing
/// whether the </param>
/// <returns>A task that completes when the <see
/// cref="ActionMarkupHandler"/> has completed presenting this
/// character. Dialogue presenters will wait until this task is complete
/// before displaying the remainder of the line.</returns>
public YarnTask OnCharacterWillAppear(int currentCharacterIndex, MarkupParseResult line, CancellationToken cancellationToken);
/// <summary>
/// Called after the last call to <see cref="PresentCharacter(int,
/// TMP_Text, CancellationToken)"/>.
/// </summary>
/// <remarks>This method is an opportunity for a <see
/// cref="ActionMarkupHandler"/> to finalise its presentation after
/// all of the characters in the line have been presented.</remarks>
public void OnLineDisplayComplete();
/// <summary>
/// Called right before the line will dismiss itself.
/// </summary>
/// <remarks>
/// This does not mean that the entirety of the view itself will have been removed, just the <see cref="DialoguePresenterBase"/> has completed displaying everything and is returning control back to the <see cref="DialogueRunner"/> to let more content flow.
/// </remarks>
public void OnLineWillDismiss();
}
/// <summary>
/// This is an abstract monobehaviour that conforms to the <see cref="IActionMarkupHandler"/> interface.
/// </summary>
/// <remarks>
/// <para>
/// Intended to be used in situations where you require a monobehaviour version of the interfaces.
/// This is used by <see cref="LinePresenter"/> to have a list of handlers that can be connected up via the inspector.
/// </para>
/// </remarks>
public abstract class ActionMarkupHandler : MonoBehaviour, IActionMarkupHandler
{
/// <inheritdoc/>
public abstract void OnPrepareForLine(MarkupParseResult line, TMP_Text text);
/// <inheritdoc/>
public abstract void OnLineDisplayBegin(MarkupParseResult line, TMP_Text text);
/// <inheritdoc/>
public abstract YarnTask OnCharacterWillAppear(int currentCharacterIndex, MarkupParseResult line, CancellationToken cancellationToken);
/// <inheritdoc/>
public abstract void OnLineDisplayComplete();
/// <inheritdoc/>
public abstract void OnLineWillDismiss();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8d13ab9da89a45a0a633f08d7ff0f4e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6df01bf68e01943908f9e625c3b0bbc6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,178 @@
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.Events;
#nullable enable
namespace Yarn.Unity
{
/// <summary>
/// A <see cref="MonoBehaviour"/> that can present lines and options to the
/// user, when it receives them from a <see cref="DialogueRunner"/>.
/// </summary>
/// <remarks>
/// <para>When the Dialogue Runner encounters content that the user should
/// see - that is, lines or options - it sends that content to all of the
/// dialogue presenters stored in <see cref="DialogueRunner.dialogueViews"/>. The
/// Dialogue Runner then waits until all Dialogue Presenters have reported that
/// they have finished presenting the content.</para>
/// <para>
/// To use this class, subclass it, and implement its required methods. Once
/// you have written your subclass, attach it as a component to a <see
/// cref="GameObject"/>, and add this game object to the list of Dialogue
/// presenters in your scene's <see cref="DialogueRunner"/>.
/// </para>
/// <para>Dialogue Presenters do not need to handle every kind of content that
/// the Dialogue Runner might produce. For example, you might have one
/// Dialogue Presenter that handles Lines, and another that handles Options. The
/// built-in <see cref="LineView"/> class is an example of this, in that it
/// only handles Lines and does nothing when it receives Options.</para>
/// <para>
/// You may also have multiple Dialogue Presenters that handle the <i>same</i>
/// kind of content. For example, you may have a Dialogue Presenter that receives
/// Lines and uses them to play voice-over audio, and a second Dialogue Presenter
/// that also receives Lines and uses them to display on-screen subtitles.
/// </para>
/// </remarks>
/// <seealso cref="LineProviderBehaviour"/>
/// <seealso cref="DialogueRunner.dialogueViews"/>
public abstract class DialoguePresenterBase : MonoBehaviour
{
/// <summary>
/// Called by the <see cref="DialogueRunner"/> to signal that a line
/// should be displayed to the user.
/// </summary>
/// <remarks>
/// <para>
/// When this method is called, the Dialogue Presenter should present the
/// line to the user. The content to present is contained within the
/// <paramref name="line"/> parameter, which contains information about
/// the line in the user's current locale.
/// </para>
/// <para style="tip">
/// It's up to the Dialogue Presenter to decide what "presenting" the line
/// may mean; for example, showing on-screen text, playing voice-over
/// audio, or updating on-screen portraits to show a picture of the
/// speaking character.
/// </para>
/// <para>
/// The <see cref="DialogueRunner"/> will wait until the tasks from all
/// of its dialogue presenters have completed before continuing to the next
/// piece of content. If your dialogue presenter does not need to handle the
/// line, it should return immediately.
/// </para>
/// <para style="info">The value of the <paramref name="line"/>
/// parameter is produced by the Dialogue Runner's <see
/// cref="LineProviderBehaviour"/>.
/// </para>
/// <para style="info">
/// The default implementation of this method takes no action and
/// returns immediately.
/// </para>
/// </remarks>
/// <param name="line">The line to present.</param>
/// <param name="token">A <see cref="LineCancellationToken"/> that
/// represents whether the dialogue presenter should hurry it its
/// presentation of the line, or stop showing the current line.</param>
/// <returns>A task that completes when the dialogue presenter has finished
/// showing the line to the user.</returns>
/// <seealso cref="RunOptionsAsync(DialogueOption[],
/// CancellationToken)"/>
public abstract YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token);
/// <summary>
/// Called by the <see cref="DialogueRunner"/> to signal that a set of
/// options should be displayed to the user.
/// </summary>
/// <remarks>
/// <para>This method is called when the Dialogue Runner wants to show a
/// collection of options that the user should choose from. Each option
/// is represented by a <see cref="DialogueOption"/> object, which
/// contains information about the option.</para>
/// <para>When this method is called, the Dialogue Presenter should display
/// appropriate user interface elements that let the user choose among
/// the options.</para>
/// <para>This method should await until an option is selected, and then
/// return the selected option. If this view doesn't handle options, or
/// is otherwise unable to select an option, it should return <see
/// cref="YarnAsync.NoOptionSelected"/>. The dialogue runner will wait
/// for all dialogue views to return, so if a dialogue presenter doesn't or
/// can't handle options, it's good practice to return as soon as
/// possible.
/// </para>
/// <para>If the <paramref name="cancellationToken"/> becomes cancelled,
/// this means that the dialogue runner no longer needs this Dialogue
/// presenter to make a selection, and this method should return as soon as
/// possible; its return value will not be used.
/// </para>
/// <para style="note">
/// The default implementation of this method returns <see
/// cref="YarnAsync.NoOptionSelected"/>.
/// </para>
/// </remarks>
/// <param name="dialogueOptions">The set of options that should be
/// displayed to the user.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/>
/// that becomes cancelled when the dialogue runner no longer needs this
/// dialogue presenter to return an option.</param>
/// <returns>A task that indicates which option was selected, or that this dialogue presenter did not select an option.</returns>
/// <seealso cref="RunLineAsync(LocalizedLine, LineCancellationToken)"/>
/// <seealso cref="YarnAsync.NoOptionSelected"/>
[System.Obsolete("The LineCancellationToken form of RunOptionsAsync allows for option cancellation and hurrying up and is prefered.")]
public virtual YarnTask<DialogueOption?> RunOptionsAsync(DialogueOption[] dialogueOptions, CancellationToken cancellationToken)
{
return YarnTask<DialogueOption?>.FromResult(null);
}
#pragma warning disable 0618
public virtual YarnTask<DialogueOption?> RunOptionsAsync(DialogueOption[] dialogueOptions, LineCancellationToken cancellationToken)
{
return RunOptionsAsync(dialogueOptions, cancellationToken.NextLineToken);
}
#pragma warning restore 0618
/// <summary>Called by the <see cref="DialogueRunner"/> to signal that
/// dialogue has started.</summary>
/// <remarks>
/// <para>This method is called before any content (that is, lines,
/// options or commands) are delivered.</para>
/// <para>This method is a good place to perform tasks like preparing
/// on-screen dialogue UI (for example, turning on a letterboxing
/// effect, or making dialogue UI elements visible.)
/// </para>
/// <para style="note">The default implementation of this method does
/// nothing.</para>
/// </remarks>
/// <returns>A task that represents any work done by this dialogue presenter in order to get ready for dialogue to run.</returns>
public abstract YarnTask OnDialogueStartedAsync();
/// <summary>
/// Called by the <see cref="DialogueRunner"/> to signal that the
/// dialogue has ended, and no more lines will be delivered.
/// </summary>
/// <remarks>
/// <para>This method is called after the last piece of content (that
/// is, lines, options or commands) finished running.</para>
/// <para>This method is a good place to perform tasks like dismissing
/// on-screen dialogue UI (for example, turning off a letterboxing
/// effect, or hiding dialogue UI elements.)
/// </para>
/// <para style="note">The default implementation of this method does
/// nothing.</para>
/// </remarks>
/// <returns>A task that represents any work done by this dialogue presenter
/// in order to clean up after running dialogue.</returns>
public abstract YarnTask OnDialogueCompleteAsync();
// these are virtual because it's quite likely you don't need them
// they are also void instead of YarnTask because currently the VM doesn't wait on node enter/exit so we can't either
public virtual void OnNodeEnter(string nodeName) { }
public virtual void OnNodeExit(string nodeName) { }
public virtual IAsyncTypewriter? Typewriter { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a9d82a904b13745b4af3b2f101940be6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
#if USE_TMP
using TMPro;
#else
using TextMeshProUGUI = Yarn.Unity.TMPShim;
#endif
using System.Threading;
#nullable enable
namespace Yarn.Unity
{
public static class Effects
{
public static IEnumerator FadeAlpha(CanvasGroup canvas, float from, float to, float duration, CancellationToken token)
{
return YarnTask.ToCoroutine(() => FadeAlphaAsync(canvas, from, to, duration, token));
}
public static async YarnTask FadeAlphaAsync(CanvasGroup canvas, float from, float to, float duration, CancellationToken token)
{
if (duration == 0)
{
canvas.alpha = to;
return;
}
canvas.alpha = from;
float accumulator = 0;
while (!token.IsCancellationRequested && accumulator < duration)
{
accumulator += Time.deltaTime;
canvas.alpha = Mathf.Lerp(from, to, accumulator / duration);
await YarnTask.Yield();
}
canvas.alpha = to;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a287d97ec765440a18661f2967524339
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,790 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Yarn.Markup;
using Yarn.Unity.Attributes;
#if USE_TMP
using TMPro;
#else
using TextMeshProUGUI = Yarn.Unity.TMPShim;
using TMP_Text = Yarn.Unity.TMPShim;
#endif
#nullable enable
namespace Yarn.Unity
{
public static class InputSystemAvailability
{
#if USE_INPUTSYSTEM
internal const bool inputSystemInstalled = true;
#else
internal const bool inputSystemInstalled = false;
#endif
#if ENABLE_INPUT_SYSTEM
internal const bool enableInputSystem = true;
#else
internal const bool enableInputSystem = false;
#endif
#if ENABLE_LEGACY_INPUT_MANAGER
internal const bool enableLegacyInput = true;
#else
internal const bool enableLegacyInput = false;
#endif
#if !ENABLE_LEGACY_INPUT_MANAGER
/// <summary>
/// A dictionary mapping legacy keycodes to Input System keys.
/// </summary>
static System.Lazy<Dictionary<KeyCode, UnityEngine.InputSystem.Key>> lookup = new(() =>
{
var result = new Dictionary<KeyCode, UnityEngine.InputSystem.Key>();
foreach (KeyCode keyCode in System.Enum.GetValues(typeof(KeyCode)))
{
// Attempt to automatically find the equivalent of keyCode by
// assuming that the string representation of a key (e.g. "Tab")
// is the same in both enums.
if (System.Enum.TryParse<UnityEngine.InputSystem.Key>(keyCode.ToString(), true, out var value))
{
result[keyCode] = value;
}
}
// Manually map some remaining keys
result[KeyCode.Return] = UnityEngine.InputSystem.Key.Enter;
result[KeyCode.KeypadEnter] = UnityEngine.InputSystem.Key.NumpadEnter;
return result;
});
#endif
/// <summary>
/// Gets a value indicating whether the key indicated by a <see
/// cref="KeyCode"/> was pressed this frame.
/// </summary>
/// <remarks>
/// If the Legacy Input Manager is enabled, this method wraps <see
/// cref="Input.GetKeyDown"/>. Otherwise, it attempts to find the <see
/// cref="UnityEngine.InputSystem.Key"/> equivalent of <paramref
/// name="key"/>, and then checks with <see
/// cref="UnityEngine.InputSystem.Keyboard.current"/> to find the key,
/// and queries its <see
/// cref="UnityEngine.InputSystem.Controls.ButtonControl.wasPressedThisFrame"/>
/// property.
/// </remarks>
/// <param name="key">The <see cref="KeyCode"/> to check for the state
/// of.</param>
/// <returns>Whether the key was pressed this frame.</returns>
public static bool GetKeyDown(KeyCode key)
{
if (key == KeyCode.None)
{
// The 'none' key is never pressed
return false;
}
#if ENABLE_LEGACY_INPUT_MANAGER
// If we're using Legacy Input, read from it directly
return Input.GetKeyDown(key);
#else
if (lookup.Value.TryGetValue(key, out var lookupKey))
{
try
{
return UnityEngine.InputSystem.Keyboard.current[lookup.Value[key]].wasPressedThisFrame;
}
catch (System.ArgumentOutOfRangeException)
{
#if DEBUG
Debug.LogWarning($"Can't check if {key} is down: found Input System mapping {lookupKey}, but this key is not present in the current keyboard");
#endif
return false;
}
}
else
{
#if DEBUG
Debug.LogWarning($"Can't check if {key} is down: can't find a mapping from legacy keycode {key} to Unity Input System");
#endif
return false;
}
#endif
}
public static bool GetButtonDown(string? buttonName)
{
if (buttonName == null)
{
return false;
}
#if ENABLE_LEGACY_INPUT_MANAGER
return Input.GetButtonUp(buttonName);
#else
return false;
#endif
}
public static float GetAxis(string? axisName)
{
if (axisName == null)
{
return 0;
}
#if ENABLE_LEGACY_INPUT_MANAGER
return Input.GetAxis(axisName);
#else
return 0;
#endif
}
}
/// <summary>
/// A dialogue presenter that listens for user input and sends requests to a <see
/// cref="DialogueRunner"/> to advance the presentation of the current line,
/// either by asking a dialogue runner to hurry up its delivery, advance to
/// the next line, or cancel the entire dialogue session.
/// </summary>
public class LineAdvancer : DialoguePresenterBase, IActionMarkupHandler
{
[MustNotBeNull("Line Advancer needs to know which Dialogue Runner should be told to tell it to show the next line.")]
[Tooltip("The dialogue runner that will receive requests to advance or cancel content.")]
[SerializeField] protected DialogueRunner? runner;
/// <summary>
/// The <see cref="DialoguePresenterBase"/> that this LineAdvancer should subscribe to for notifications that the line is fully visible.
/// </summary>
/// <remarks>When <see cref="RequestLineHurryUp"/> is called, if the line is fully visible, the <see cref="runner"/> object will have its <see cref="DialogueRunner.RequestNextLine"/> method called (instead of its <see cref="DialogueRunner.RequestHurryUpLine"/> method).
/// This behaviour is only the case when the <see cref="separateHurryUpAndAdvanceControls"/> is set to false.
///</remarks>
[SerializeField] protected DialoguePresenterBase? presenter;
/// <summary>
/// Should this line advancer use different actions for hurrying up a line and advancing a line?
/// </summary>
/// <remarks>
/// When this is false if the player requests a line to hurry up and the line is fully shown the <see cref="DialogueRunner.RequestNextLine"/> method will be called instead of the <see cref="DialogueRunner.RequestHurryUpLine"/> method.
/// This behaviour is only the case when <see cref="presenter"/> is not null and the presenter is presenting it's line content via it's <see cref="DialoguePresenter.Typewriter"/> property.
/// </remarks>
[SerializeField] protected bool separateHurryUpAndAdvanceControls = false;
/// <summary>
/// If <see langword="true"/>, repeatedly signalling that the line
/// should be hurried up will cause the line advancer to request that
/// the next line be shown.
/// </summary>
/// <seealso cref="advanceRequestsBeforeCancellingLine"/>
[Space]
[Tooltip("Does repeatedly requesting a line advance cancel the line?")]
public bool multiAdvanceIsCancel = false;
/// <summary>
/// The number of times that a 'hurry up' signal occurs before the line
/// advancer requests that the next line be shown.
/// </summary>
/// <seealso cref="multiAdvanceIsCancel"/>
[ShowIf(nameof(multiAdvanceIsCancel))]
[Indent]
[Label("Advance Count")]
[Tooltip("The number of times that a line advance occurs before the current line is cancelled.")]
public int advanceRequestsBeforeCancellingLine = 2;
/// <summary>
/// The number of times that this object has received an indication that
/// the line should be advanced.
/// </summary>
/// <remarks>
/// This value is reset to zero when a new line is run. When the line is
/// advanced, this value is incremented. If this value ever meets or
/// exceeds <see cref="advanceRequestsBeforeCancellingLine"/>, the line
/// will be cancelled.
/// </remarks>
private int numberOfAdvancesThisLine = 0;
/// <summary>
/// The type of input that this line advancer responds to.
/// </summary>
public enum InputMode
{
/// <summary>
/// The line advancer responds to Input Actions from the <a
/// href="https://docs.unity3d.com/Packages/com.unity.inputsystem@latest">Unity
/// Input System</a>.
/// </summary>
InputActions,
/// <summary>
/// The line advancer responds to keypresses on the keyboard.
/// </summary>
KeyCodes,
/// <summary>
/// The line advancer does not respond to any input.
/// </summary>
/// <remarks>When a line advancer's <see cref="UsedInputMode"/> is set
/// to <see cref="None"/>, call the <see
/// cref="RequestLineHurryUp"/>, <see cref="RequestNextLine"/> and
/// <see cref="RequestDialogueCancellation"/> methods directly from
/// your code to control line advancement.</remarks>
None,
/// <summary>
/// The line advancer responds to input from the legacy <a
/// href="https://docs.unity3d.com/Manual/class-InputManager.html">Input
/// Manager</a>.
/// </summary>
LegacyInputAxes,
}
/// <summary>
/// The type of input that this line advancer responds to.
/// </summary>
/// <seealso cref="InputMode"/>
[Tooltip("The type of input that this line advancer responds to.")]
[Space]
[MessageBox(sourceMethod: nameof(ValidateInputMode))]
[SerializeField] protected InputMode inputMode;
// when using the same input for different actions, for example using spacebar to select an option but also spacebar to hurry up lines
// the action for hurrying up the line will happen the same frame as the action for selection
// so if a line follows options (very common), that line might well get told to instantly hurry up
// which isn't ideal, so this tracks the frame that content arrives and hurry up events cannot run the same frame as their content appears
private int frameContentReceived = 0;
protected InputMode UsedInputMode
{
get
{
const bool inputSystemAvailable = InputSystemAvailability.enableInputSystem && InputSystemAvailability.inputSystemInstalled;
if (inputMode == InputMode.InputActions && !inputSystemAvailable)
{
// We're configured to use input actions, but the input
// system is not enabled. Fall back to key codes.
return InputMode.KeyCodes;
}
else
{
return inputMode;
}
}
}
/// <summary>
/// Validates the current value of <see cref="inputMode"/>, and
/// potentially returns a message box to display.
/// </summary>
protected MessageBoxAttribute.Message ValidateInputMode()
{
#pragma warning disable CS0162 // Unreachable code detected
if (this.inputMode == InputMode.None)
{
return MessageBoxAttribute.Info($"To use this component, call the following methods on it:\n\n" +
$"- {nameof(this.RequestLineHurryUp)}()\n" +
$"- {nameof(this.RequestNextLine)}()\n" +
$"- {nameof(this.RequestOptionHurryUp)}()\n" +
$"- {nameof(this.RequestDialogueCancellation)}()"
);
}
if (this.inputMode == InputMode.LegacyInputAxes && !InputSystemAvailability.enableLegacyInput)
{
return MessageBoxAttribute.Warning("The Input Manager (Old) system is not enabled.\n\nEither change this setting to Input Actions, or enable Input Manager (Old) in Project Settings > Player > Configuration > Active Input Handling.");
}
if (this.inputMode == InputMode.InputActions)
{
if (InputSystemAvailability.inputSystemInstalled == false)
{
return MessageBoxAttribute.Warning("Please install the Unity Input System package to use Input Actions.\n\nFalling back to the keyboard in the meantime.");
}
if (!InputSystemAvailability.enableInputSystem)
{
return MessageBoxAttribute.Warning("The Unity Input System is not enabled.\n\nEither change this setting, or enable Input System in Project Settings > Player > Configuration > Active Input Handling.\n\nFalling back to the keyboard in the meantime.");
}
}
return MessageBoxAttribute.NoMessage;
#pragma warning restore CS0162 // Unreachable code detected
}
#if USE_INPUTSYSTEM
/// <summary>
/// The Input Action that triggers a request to advance to the next
/// piece of content.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.InputActions)]
[Indent]
[SerializeField] protected UnityEngine.InputSystem.InputActionReference? hurryUpLineAction;
/// <summary>
/// The Input Action that triggers an instruction to cancel the current
/// line.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.InputActions)]
[ShowIf(nameof(separateHurryUpAndAdvanceControls))]
[Indent]
[SerializeField] protected UnityEngine.InputSystem.InputActionReference? nextLineAction;
/// <summary>
/// The Input Action that triggers an instruction to hurry up presenting the current options
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.InputActions)]
[Indent]
[SerializeField] protected UnityEngine.InputSystem.InputActionReference? hurryUpOptionsAction;
/// <summary>
/// The Input Action that triggers an instruction to cancel the entire
/// dialogue.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.InputActions)]
[Indent]
[SerializeField] protected UnityEngine.InputSystem.InputActionReference? cancelDialogueAction;
/// <summary>
/// If true, the <see cref="hurryUpLineAction"/>, <see
/// cref="nextLineAction"/> and <see cref="cancelDialogueAction"/> Input
/// Actions will be enabled when the the dialogue runner signals that a
/// line is running.
/// </summary>
[Tooltip("If true, the input actions above will be enabled when a line begins.")]
[ShowIf(nameof(UsedInputMode), InputMode.InputActions)]
[Indent]
[SerializeField] protected bool enableActions = true;
#endif
/// <summary>
/// The legacy Input Axis that triggers a request to advance to the next
/// piece of content.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.LegacyInputAxes)]
[Indent]
[SerializeField] protected string? hurryUpLineAxis = "Jump";
/// <summary>
/// The legacy Input Axis that triggers an instruction to cancel the
/// current line.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.LegacyInputAxes)]
[ShowIf(nameof(separateHurryUpAndAdvanceControls))]
[Indent]
[SerializeField] protected string? nextLineAxis = "Cancel";
/// <summary>
/// The legacy Input Axis that triggers an instruction to hurry up presenting the current options
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.LegacyInputAxes)]
[Indent]
[SerializeField] protected string? hurryUpOptionsAxis = "Jump";
/// <summary>
/// The legacy Input Axis that triggers an instruction to cancel the
/// entire dialogue.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.LegacyInputAxes)]
[Indent]
[SerializeField] protected string? cancelDialogueAxis = "";
/// <summary>
/// The <see cref="KeyCode"/> that triggers a request to advance to the
/// next piece of content.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.KeyCodes)]
[Indent]
[SerializeField] protected KeyCode hurryUpLineKeyCode = KeyCode.Space;
/// <summary>
/// The <see cref="KeyCode"/> that triggers an instruction to cancel the
/// current line.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.KeyCodes)]
[ShowIf(nameof(separateHurryUpAndAdvanceControls))]
[Indent]
[SerializeField] protected KeyCode nextLineKeyCode = KeyCode.Escape;
/// <summary>
/// The <see cref="KeyCode"/> that triggers an instruction to hurry up presenting options
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.KeyCodes)]
[Indent]
[SerializeField] protected KeyCode hurryUpOptionsKeyCode = KeyCode.Space;
/// <summary>
/// The <see cref="KeyCode"/> that triggers an instruction to cancel the
/// entire dialogue.
/// </summary>
[ShowIf(nameof(UsedInputMode), InputMode.KeyCodes)]
[Indent]
[SerializeField] protected KeyCode cancelDialogueKeyCode = KeyCode.None;
#if USE_INPUTSYSTEM
private void OnHurryUpLinePerformed(UnityEngine.InputSystem.InputAction.CallbackContext ctx)
{
RequestLineHurryUpInternal();
}
private void OnHurryUpOptionsPerformed(UnityEngine.InputSystem.InputAction.CallbackContext ctx)
{
RequestOptionHurryUp();
}
private void OnNextLinePerformed(UnityEngine.InputSystem.InputAction.CallbackContext ctx)
{
RequestNextLine();
}
private void OnCancelDialoguePerformed(UnityEngine.InputSystem.InputAction.CallbackContext ctx)
{
RequestDialogueCancellation();
}
#endif
// used to track the status of the presentation
// you can think of this as a variation on multiple presses to advance a line
// where if the presenter is awaiting input it is reasonable that pressing hurry up would advance to the next piece of content
// but the default presenters can't really tell that apart
// so the line advancer instead will handle this
// this only works if the line advancer is added as a processor onto the presenters typewriter
// but that is ok as that is the default
// as people replace those defaults with more complex views and presenters they will also want to replace the line advancer anyways
private enum PresentationStatus
{
Unknown, LineBegan, LineWaiting, OptionsBegan, OptionsWaiting,
}
private PresentationStatus status = PresentationStatus.Unknown;
private void Start()
{
// If we have a dialogue presenter configured, register ourselves as
// a temporal processor, so that we get notified when the line is
// fully visible. This is so that when a line is fully visible, the
// 'hurry up' action will instead trigger a 'next line' action,
// (because there's nothing left to hurry up.)
if (runner == null || presenter == null)
{
return;
}
if (!separateHurryUpAndAdvanceControls)
{
var listOfPresenters = new List<DialoguePresenterBase?>(runner.DialoguePresenters)
{
this
};
runner.DialoguePresenters = listOfPresenters;
presenter.Typewriter?.ActionMarkupHandlers.Add(this);
// last thing is to null out the inputs just in case
nextLineAxis = null;
nextLineKeyCode = KeyCode.None;
#if USE_INPUTSYSTEM
nextLineAction = null;
#endif
}
}
/// <summary>
/// Called by a dialogue runner when dialogue starts to add input action
/// handlers for advancing the line.
/// </summary>
/// <returns>A completed task.</returns>
public override YarnTask OnDialogueStartedAsync()
{
#if USE_INPUTSYSTEM
if (enableActions)
{
if (hurryUpLineAction != null) { hurryUpLineAction.action.Enable(); }
if (hurryUpOptionsAction != null) { hurryUpOptionsAction.action.Enable(); }
if (nextLineAction != null) { nextLineAction.action.Enable(); }
if (cancelDialogueAction != null) { cancelDialogueAction.action.Enable(); }
}
if (UsedInputMode == InputMode.InputActions)
{
// If we're using the input system, register callbacks to run
// when our actions are performed.
if (hurryUpLineAction != null) { hurryUpLineAction.action.performed += OnHurryUpLinePerformed; }
if (hurryUpOptionsAction != null) { hurryUpOptionsAction.action.performed += OnHurryUpOptionsPerformed; }
if (nextLineAction != null) { nextLineAction.action.performed += OnNextLinePerformed; }
if (cancelDialogueAction != null) { cancelDialogueAction.action.performed += OnCancelDialoguePerformed; }
}
#endif
ResetLineTracking();
return YarnTask.CompletedTask;
}
/// <summary>
/// Called by a dialogue runner when dialogue ends to remove the input
/// action handlers.
/// </summary>
/// <returns>A completed task.</returns>
public override YarnTask OnDialogueCompleteAsync()
{
#if USE_INPUTSYSTEM
// If we're using the input system, remove the callbacks.
if (UsedInputMode == InputMode.InputActions)
{
if (hurryUpLineAction != null) { hurryUpLineAction.action.performed -= OnHurryUpLinePerformed; }
if (hurryUpOptionsAction != null) { hurryUpOptionsAction.action.performed -= OnHurryUpOptionsPerformed; }
if (nextLineAction != null) { nextLineAction.action.performed -= OnNextLinePerformed; }
if (cancelDialogueAction != null) { cancelDialogueAction.action.performed -= OnCancelDialoguePerformed; }
Debug.Log("LineAdvancer: Unregistered input action callbacks.");
}
#endif
ResetLineTracking();
return YarnTask.CompletedTask;
}
/// <summary>
/// Called by a dialogue presenter to signal that a line is running.
/// </summary>
/// <inheritdoc cref="LinePresenter.RunLineAsync" path="/param"/>
/// <returns>A completed task.</returns>
public override YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
{
// A new line has come in, so reset the number of times we've seen a
// request to skip.
ResetLineTracking();
status = PresentationStatus.LineBegan;
frameContentReceived = Time.frameCount;
return YarnTask.CompletedTask;
}
/// <summary>
/// Called by a dialogue presenter to signal that options are running.
/// </summary>
/// <inheritdoc cref="LinePresenter.RunOptionsAsync" path="/param"/>
/// <returns>A completed task indicating that no option was selected by
/// this view.</returns>
public override YarnTask<DialogueOption?> RunOptionsAsync(DialogueOption[] dialogueOptions, LineCancellationToken cancellationToken)
{
ResetLineTracking();
status = PresentationStatus.OptionsBegan;
frameContentReceived = Time.frameCount;
return DialogueRunner.NoOptionSelected;
}
private void ResetLineTracking()
{
numberOfAdvancesThisLine = 0;
status = PresentationStatus.Unknown;
}
protected virtual void RequestLineHurryUpInternal()
{
if (frameContentReceived == Time.frameCount)
{
return;
}
// in this mode we NEED to be in a state where a line showing, regardless of it's completion state
if (!separateHurryUpAndAdvanceControls)
{
if (!(status == PresentationStatus.LineBegan || status == PresentationStatus.LineWaiting))
{
return;
}
}
// Increment our counter of line advancements, and depending on the
// new count, request that the runner 'soft-cancel' the line or
// cancel the entire line
// this is true regardless of if we are the hurry up mode or not
numberOfAdvancesThisLine += 1;
if (multiAdvanceIsCancel && numberOfAdvancesThisLine >= advanceRequestsBeforeCancellingLine)
{
RequestNextLine();
}
else
{
// at this stage we want to hurry up if we are in multiAdvanceIsCancel
// and either hurry up or skip the line depending on the state
if (separateHurryUpAndAdvanceControls)
{
if (runner != null)
{
runner.RequestHurryUpLine();
}
else
{
Debug.LogError($"{nameof(LineAdvancer)} dialogue runner is null", this);
}
}
else
{
if (status == PresentationStatus.LineWaiting)
{
RequestNextLine();
}
else
{
if (runner != null)
{
runner.RequestHurryUpLine();
}
else
{
Debug.LogError($"{nameof(LineAdvancer)} dialogue runner is null", this);
}
}
}
}
}
/// <summary>
/// Requests that the line be hurried up.
/// </summary>
/// <remarks>If this method has been called more times for a single line
/// than <see cref="numberOfAdvancesThisLine"/>, this method requests
/// that the dialogue runner proceed to the next line. Otherwise, it
/// requests that the dialogue runner instruct all line views to hurry
/// up their presentation of the current line.
/// </remarks>
public void RequestLineHurryUp()
{
// Increment our counter of line advancements, and depending on the
// new count, request that the runner 'soft-cancel' the line or
// cancel the entire line
numberOfAdvancesThisLine += 1;
if (multiAdvanceIsCancel && numberOfAdvancesThisLine >= advanceRequestsBeforeCancellingLine)
{
RequestNextLine();
}
else
{
if (runner != null)
{
runner.RequestHurryUpLine();
}
else
{
Debug.LogError($"{nameof(LineAdvancer)} dialogue runner is null", this);
}
}
}
public void RequestOptionHurryUp()
{
if (frameContentReceived == Time.frameCount)
{
return;
}
if (runner == null)
{
Debug.LogError($"Unable to hurry up options, {nameof(LineAdvancer)} dialogue runner is null", this);
return;
}
if (!separateHurryUpAndAdvanceControls)
{
if (status == PresentationStatus.OptionsBegan || status == PresentationStatus.OptionsWaiting)
{
runner.RequestHurryUpOption();
}
}
else
{
runner.RequestHurryUpOption();
}
}
/// <summary>
/// Requests that the dialogue runner proceeds to the next line.
/// </summary>
public virtual void RequestNextLine()
{
ResetLineTracking();
if (runner != null)
{
runner.RequestNextLine();
}
else
{
Debug.LogError($"{nameof(LineAdvancer)} dialogue runner is null", this);
}
}
/// <summary>
/// Requests that the dialogue runner to instruct all line views to
/// dismiss their content, and then stops the dialogue.
/// </summary>
public virtual void RequestDialogueCancellation()
{
ResetLineTracking();
// Stop the dialogue runner, which will cancel the current line as
// well as the entire dialogue.
if (runner != null)
{
runner.Stop().Forget();
}
}
/// <summary>
/// Called by Unity every frame to check to see if, depending on <see
/// cref="UsedInputMode"/>, the <see cref="LineAdvancer"/> should take
/// action.
/// </summary>
private void Update()
{
switch (UsedInputMode)
{
case InputMode.KeyCodes:
if (InputSystemAvailability.GetKeyDown(hurryUpLineKeyCode)) { this.RequestLineHurryUpInternal(); }
if (InputSystemAvailability.GetKeyDown(hurryUpOptionsKeyCode)) { this.RequestOptionHurryUp(); }
if (InputSystemAvailability.GetKeyDown(nextLineKeyCode)) { this.RequestNextLine(); }
if (InputSystemAvailability.GetKeyDown(cancelDialogueKeyCode)) { this.RequestDialogueCancellation(); }
break;
case InputMode.LegacyInputAxes:
if (InputSystemAvailability.GetButtonDown(hurryUpLineAxis)) { this.RequestLineHurryUpInternal(); }
if (InputSystemAvailability.GetButtonDown(hurryUpOptionsAxis)) { this.RequestOptionHurryUp(); }
if (InputSystemAvailability.GetButtonDown(nextLineAxis)) { this.RequestNextLine(); }
if (InputSystemAvailability.GetButtonDown(cancelDialogueAxis)) { this.RequestDialogueCancellation(); }
break;
default:
// Nothing to do; 'None' takes no action, and 'InputActions'
// doesn't poll in Update()
break;
}
}
public void OnPrepareForLine(MarkupParseResult line, TMP_Text text)
{
return;
}
public void OnLineDisplayBegin(MarkupParseResult line, TMP_Text text)
{
return;
}
public YarnTask OnCharacterWillAppear(int currentCharacterIndex, MarkupParseResult line, CancellationToken cancellationToken)
{
return YarnTask.CompletedTask;
}
public void OnLineDisplayComplete()
{
if (status == PresentationStatus.LineBegan)
{
status = PresentationStatus.LineWaiting;
}
else if (status == PresentationStatus.OptionsBegan)
{
status = PresentationStatus.OptionsWaiting;
}
}
public void OnLineWillDismiss()
{
return;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 61503eb6a998a4af68e0dcbec4aad8cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,411 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Yarn.Markup;
using Yarn.Unity.Attributes;
#nullable enable
#if USE_TMP
using TMPro;
#else
using TextMeshProUGUI = Yarn.Unity.TMPShim;
using TMP_Text = Yarn.Unity.TMPShim;
#endif
namespace Yarn.Unity
{
/// <summary>
/// A Dialogue Presenter that presents lines of dialogue, using Unity UI
/// elements.
/// </summary>
[HelpURL("https://docs.yarnspinner.dev/using-yarnspinner-with-unity/components/dialogue-view/line-view")]
public class LinePresenter : DialoguePresenterBase
{
protected internal enum TypewriterType
{
Instant, ByLetter, ByWord, Custom,
}
/// <summary>
/// The canvas group that contains the UI elements used by this Line
/// View.
/// </summary>
/// <remarks>
/// If <see cref="useFadeEffect"/> is true, then the alpha value of this
/// <see cref="CanvasGroup"/> will be animated during line presentation
/// and dismissal.
/// </remarks>
/// <seealso cref="useFadeEffect"/>
[Space]
[MustNotBeNull]
public CanvasGroup? canvasGroup;
/// <summary>
/// The <see cref="TMP_Text"/> object that displays the text of
/// dialogue lines.
/// </summary>
[MustNotBeNull]
public TMP_Text? lineText;
/// <summary>
/// Controls whether the <see cref="lineText"/> object will show the
/// character name present in the line or not.
/// </summary>
/// <remarks>
/// <para style="note">This value is only used if <see
/// cref="characterNameText"/> is <see langword="null"/>.</para>
/// <para>If this value is <see langword="true"/>, any character names
/// present in a line will be shown in the <see cref="lineText"/>
/// object.</para>
/// <para>If this value is <see langword="false"/>, character names will
/// not be shown in the <see cref="lineText"/> object.</para>
/// </remarks>
[Group("Character")]
[Label("Shows Name In Line")]
public bool showCharacterNameInLine = true;
/// <summary>
/// The <see cref="TMP_Text"/> object that displays the character
/// names found in dialogue lines.
/// </summary>
/// <remarks>
/// If the <see cref="LinePresenter"/> receives a line that does not contain
/// a character name, this object will be left blank.
/// </remarks>
[Group("Character")]
[Label("Name Field")]
public TMP_Text? characterNameText = null;
/// <summary>
/// The game object that holds the <see cref="characterNameText"/> text
/// field.
/// </summary>
/// <remarks>
/// This is needed in situations where the character name is contained
/// within an entirely different game object. Most of the time this will
/// just be the same game object as <see cref="characterNameText"/>.
/// </remarks>
[Group("Character")]
public GameObject? characterNameContainer = null;
/// <summary>
/// Controls whether the line view should fade in when lines appear, and
/// fade out when lines disappear.
/// </summary>
/// <remarks><para>If this value is <see langword="true"/>, the <see
/// cref="canvasGroup"/> object's alpha property will animate from 0 to
/// 1 over the course of <see cref="fadeUpDuration"/> seconds when lines
/// appear, and animate from 1 to zero over the course of <see
/// cref="fadeDownDuration"/> seconds when lines disappear.</para>
/// <para>If this value is <see langword="false"/>, the <see
/// cref="canvasGroup"/> object will appear instantaneously.</para>
/// </remarks>
/// <seealso cref="canvasGroup"/>
/// <seealso cref="fadeUpDuration"/>
/// <seealso cref="fadeDownDuration"/>
[Group("Fade")]
[Label("Fade UI")]
public bool useFadeEffect = true;
/// <summary>
/// The time that the fade effect will take to fade lines in.
/// </summary>
/// <remarks>This value is only used when <see cref="useFadeEffect"/> is
/// <see langword="true"/>.</remarks>
/// <seealso cref="useFadeEffect"/>
[Group("Fade")]
[ShowIf(nameof(useFadeEffect))]
public float fadeUpDuration = 0.25f;
/// <summary>
/// The time that the fade effect will take to fade lines out.
/// </summary>
/// <remarks>This value is only used when <see cref="useFadeEffect"/> is
/// <see langword="true"/>.</remarks>
/// <seealso cref="useFadeEffect"/>
[Group("Fade")]
[ShowIf(nameof(useFadeEffect))]
public float fadeDownDuration = 0.1f;
/// <summary>
/// Controls whether this Line View will automatically to the Dialogue
/// Runner that the line is complete as soon as the line has finished
/// appearing.
/// </summary>
/// <remarks>
/// <para>
/// If this value is true, the Line View will
/// </para>
/// <para style="note"><para>The <see cref="DialogueRunner"/> will not
/// proceed to the next piece of content (e.g. the next line, or the
/// next options) until all Dialogue Presenters have reported that they have
/// finished presenting their lines. If a <see cref="LinePresenter"/>
/// doesn't report that it's finished until it receives input, the <see
/// cref="DialogueRunner"/> will end up pausing.</para>
/// <para>
/// This is useful for games in which you want the player to be able to
/// read lines of dialogue at their own pace, and give them control over
/// when to advance to the next line.</para></para>
/// </remarks>
[Group("Automatically Advance Dialogue")]
public bool autoAdvance = false;
/// <summary>
/// The amount of time after the line finishes appearing before
/// automatically ending the line, in seconds.
/// </summary>
/// <remarks>This value is only used when <see cref="autoAdvance"/> is
/// <see langword="true"/>.</remarks>
[Group("Automatically Advance Dialogue")]
[ShowIf(nameof(autoAdvance))]
[Label("Delay Before Advancing")]
public float autoAdvanceDelay = 1f;
// typewriter fields
[Group("Typewriter")]
[SerializeField]
protected internal TypewriterType typewriterStyle = TypewriterType.ByLetter;
/// <summary>
/// The number of characters per second that should appear during a
/// typewriter effect.
/// </summary>
[Group("Typewriter")]
[ShowIf(nameof(typewriterStyle), TypewriterType.ByLetter)]
[Label("Letters per Second")]
[Min(0)]
public int lettersPerSecond = 60;
[Group("Typewriter")]
[ShowIf(nameof(typewriterStyle), TypewriterType.ByWord)]
[Label("Words per Second")]
[Min(0)]
public int wordsPerSecond = 10;
[Group("Typewriter")]
[ShowIf(nameof(typewriterStyle), TypewriterType.Custom)]
[UnityEngine.Serialization.FormerlySerializedAs("CustomTypewriter")]
public InterfaceContainer<IAsyncTypewriter>? customTypewriter;
/// <summary>
/// A list of <see cref="ActionMarkupHandler"/> objects that will be
/// used to handle markers in the line.
/// </summary>
[Group("Typewriter")]
[Label("Event Handlers")]
[UnityEngine.Serialization.FormerlySerializedAs("actionMarkupHandlers")]
[SerializeField] protected List<ActionMarkupHandler> eventHandlers = new List<ActionMarkupHandler>();
protected List<IActionMarkupHandler> ActionMarkupHandlers
{
get
{
var pauser = new PauseEventProcessor();
List<IActionMarkupHandler> ActionMarkupHandlers = new()
{
pauser,
};
ActionMarkupHandlers.AddRange(eventHandlers);
return ActionMarkupHandlers;
}
}
/// <inheritdoc/>
public override YarnTask OnDialogueCompleteAsync()
{
if (canvasGroup != null)
{
canvasGroup.alpha = 0;
}
return YarnTask.CompletedTask;
}
/// <inheritdoc/>
public override YarnTask OnDialogueStartedAsync()
{
if (canvasGroup != null)
{
canvasGroup.alpha = 0;
}
return YarnTask.CompletedTask;
}
/// <summary>
/// Called by <see cref="RunLineAsync(LocalizedLine, LineCancellationToken)"/>
/// after the line's text has been fully set up in the <see cref="lineText"/> field,
/// but before the typewriter effect has begun to display the line.
/// </summary>
protected virtual void PostProcessDisplayText()
{
}
/// <summary>
/// Called by Unity on first frame.
/// </summary>
private void Awake()
{
if (characterNameContainer == null && characterNameText != null)
{
characterNameContainer = characterNameText.gameObject;
}
switch (typewriterStyle)
{
case TypewriterType.Instant:
Typewriter = new InstantTypewriter()
{
ActionMarkupHandlers = ActionMarkupHandlers,
TextElement = this.lineText,
};
break;
case TypewriterType.ByLetter:
Typewriter = new LetterTypewriter()
{
ActionMarkupHandlers = ActionMarkupHandlers,
TextElement = this.lineText,
CharactersPerSecond = this.lettersPerSecond,
};
break;
case TypewriterType.ByWord:
Typewriter = new WordTypewriter()
{
ActionMarkupHandlers = ActionMarkupHandlers,
TextElement = this.lineText,
WordsPerSecond = this.wordsPerSecond,
};
break;
case TypewriterType.Custom:
Typewriter = customTypewriter?.Interface;
if (Typewriter == null)
{
Debug.LogWarning("Typewriter mode is set to custom but there is no typewriter set.");
}
else
{
Typewriter.ActionMarkupHandlers.AddRange(ActionMarkupHandlers);
Typewriter.TextElement = this.lineText;
}
break;
}
}
/// <summary>Presents a line using the configured text view.</summary>
/// <inheritdoc cref="DialoguePresenterBase.RunLineAsync(LocalizedLine, LineCancellationToken)" path="/param"/>
/// <inheritdoc cref="DialoguePresenterBase.RunLineAsync(LocalizedLine, LineCancellationToken)" path="/returns"/>
public override async YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
{
if (lineText == null)
{
Debug.LogError($"{nameof(LinePresenter)} does not have a text view. Skipping line {line.TextID} (\"{line.RawText}\")");
return;
}
MarkupParseResult text;
// configuring the text fields
if (characterNameText == null)
{
if (showCharacterNameInLine)
{
text = line.Text;
}
else
{
text = line.TextWithoutCharacterName;
}
}
else
{
text = line.TextWithoutCharacterName;
// we are configured to show character names in their own little box, but this line doesn't have one
if (characterNameContainer != null)
{
if (string.IsNullOrWhiteSpace(line.CharacterName))
{
characterNameContainer.SetActive(false);
}
else
{
characterNameContainer.SetActive(true);
characterNameText.text = line.CharacterName;
}
}
}
YarnTagParser.Parse(line.Metadata, out Dictionary<string, string>? keyValueTags, out HashSet<string>? plainTags);
if (keyValueTags.TryGetValue("mood", out string? valueTag))
{
Debug.Log($"Line has mood tag with value {valueTag}");
}
Typewriter ??= new InstantTypewriter()
{
ActionMarkupHandlers = this.ActionMarkupHandlers,
TextElement = this.lineText,
};
Typewriter.PrepareForContent(text);
PostProcessDisplayText();
if (canvasGroup != null)
{
// fading up the UI
if (useFadeEffect)
{
await Effects.FadeAlphaAsync(canvasGroup, 0, 1, fadeUpDuration, token.HurryUpToken);
}
else
{
// We're not fading up, so set the canvas group's alpha to 1 immediately.
canvasGroup.alpha = 1;
}
}
await Typewriter.RunTypewriter(text, token.HurryUpToken).SuppressCancellationThrow();
// if we are set to autoadvance how long do we hold for before continuing?
if (autoAdvance)
{
await YarnTask.Delay((int)(autoAdvanceDelay * 1000), token.NextContentToken).SuppressCancellationThrow();
}
else
{
await YarnTask.WaitUntilCanceled(token.NextContentToken).SuppressCancellationThrow();
}
Typewriter.ContentWillDismiss();
if (canvasGroup != null)
{
// we fade down the UI
if (useFadeEffect)
{
await Effects.FadeAlphaAsync(canvasGroup, 1, 0, fadeDownDuration, token.HurryUpToken).SuppressCancellationThrow();
}
else
{
canvasGroup.alpha = 0;
}
}
Typewriter.ContentDidDismiss();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7dd50f871a60b4bbe88a71d308b2613f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,89 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
using Yarn.Markup;
using Yarn.Unity.Attributes;
#if USE_TMP
using TMPro;
#else
using TMP_Text = Yarn.Unity.TMPShim;
#endif
#nullable enable
namespace Yarn.Unity
{
public class LinePresenterButtonHandler : ActionMarkupHandler
{
[MustNotBeNull, SerializeField] Button? continueButton;
[MustNotBeNullWhen(nameof(continueButton), "A " + nameof(DialogueRunner) + " must be provided for the continue button to work.")]
[SerializeField] DialogueRunner? dialogueRunner;
void Awake()
{
if (continueButton == null)
{
Debug.LogWarning($"The {nameof(continueButton)} is null, is it not connected in the inspector?", this);
return;
}
continueButton.interactable = false;
continueButton.enabled = false;
}
public override void OnPrepareForLine(MarkupParseResult line, TMP_Text text)
{
if (continueButton == null)
{
Debug.LogWarning($"The {nameof(continueButton)} is null, is it not connected in the inspector?", this);
return;
}
// enable the button
continueButton.interactable = true;
continueButton.enabled = true;
continueButton.onClick.AddListener(() =>
{
if (dialogueRunner == null)
{
Debug.LogWarning($"Continue button was clicked, but {nameof(dialogueRunner)} is null!", this);
return;
}
dialogueRunner.RequestNextLine();
});
}
public override void OnLineDisplayBegin(MarkupParseResult line, TMP_Text text)
{
return;
}
public override YarnTask OnCharacterWillAppear(int currentCharacterIndex, MarkupParseResult line, CancellationToken cancellationToken)
{
return YarnTask.CompletedTask;
}
public override void OnLineDisplayComplete()
{
return;
}
public override void OnLineWillDismiss()
{
if (continueButton == null)
{
return;
}
// disable interaction
continueButton.onClick.RemoveAllListeners();
continueButton.interactable = false;
continueButton.enabled = false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fccdb370bbd5a4312a5e44af933974be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,179 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Collections.Generic;
using UnityEngine;
namespace Yarn.Unity
{
/// <summary>
/// Represents a collection of marker names and colours.
/// </summary>
/// <remarks>
/// This is intended to be used with the <see cref="LinePresenter"/>, and
/// also be a sample of using the markup system.
/// </remarks>
[CreateAssetMenu(fileName = "NewPalette", menuName = "Yarn Spinner/Markup Palette", order = 102)]
public sealed class MarkupPalette : ScriptableObject
{
/// <summary>
/// Contains information describing the formatting style of text within
/// a named marker.
/// </summary>
[System.Serializable]
public struct BasicMarker
{
/// <summary>
/// The name of the marker which can be used in text to indicate
/// specific formatting.
/// </summary>
public string Marker;
/// <summary>
/// Indicates whethere or not the text associated with this marker should have a custom colour.
/// </summary>
public bool CustomColor;
/// <summary>
/// The color to use for text associated with this marker.
/// </summary>
public Color Color;
/// <summary>
/// Indicates whether the text associated with this marker should be
/// bolded.
/// </summary>
public bool Boldened;
/// <summary>
/// Indicates whether the text associated with this marker should be
/// italicized.
/// </summary>
public bool Italicised;
/// <summary>
/// Indicates whether the text associated with this marker should be
/// underlined.
/// </summary>
public bool Underlined;
/// <summary>
/// Indicates whether the text associated with this marker should
/// have a strikethrough effect.
/// </summary>
public bool Strikedthrough;
}
[System.Serializable]
public struct CustomMarker
{
public string Marker;
public string Start;
public string End;
public int MarkerOffset;
public int TotalVisibleCharacterCount;
}
/// <summary>
/// A list containing all the color markers defined in this palette.
/// </summary>
[UnityEngine.Serialization.FormerlySerializedAs("ColourMarkers")]
public List<BasicMarker> BasicMarkers = new List<BasicMarker>();
public List<CustomMarker> CustomMarkers = new List<CustomMarker>();
/// <summary>
/// Determines the colour for a particular marker inside this palette.
/// </summary>
/// <param name="Marker">The marker you want to get a colour
/// for.</param>
/// <param name="colour">The colour of the marker, or <see
/// cref="Color.black"/> if it doesn't exist in the <see
/// cref="MarkupPalette"/>.</param>
/// <returns><see langword="true"/> if the marker exists within this
/// palette; <see langword="false"/> otherwise.</returns>
public bool ColorForMarker(string Marker, out Color colour)
{
foreach (var item in BasicMarkers)
{
if (item.Marker == Marker)
{
colour = item.Color;
return true;
}
}
colour = Color.black;
return false;
}
public bool PaletteForMarker(string markerName, out CustomMarker palette)
{
// we first check if we have a marker of that name in the basic markers
foreach (var item in BasicMarkers)
{
if (item.Marker == markerName)
{
System.Text.StringBuilder front = new();
System.Text.StringBuilder back = new();
// do we have a custom colour set?
if (item.CustomColor)
{
front.AppendFormat("<color=#{0}>", ColorUtility.ToHtmlStringRGBA(item.Color));
back.Append("</color>");
}
// do we need to bold it?
if (item.Boldened)
{
front.Append("<b>");
back.Append("</b>");
}
// do we need to italicise it?
if (item.Italicised)
{
front.Append("<i>");
back.Append("</i>");
}
// do we need to underline it?
if (item.Underlined)
{
front.Append("<u>");
back.Append("</u>");
}
// do we need to strikethrough it?
if (item.Strikedthrough)
{
front.Append("<s>");
back.Append("</s>");
}
palette = new CustomMarker()
{
Marker = item.Marker,
Start = front.ToString(),
End = back.ToString(),
MarkerOffset = 0,
TotalVisibleCharacterCount = 0,
};
return true;
}
}
// we now check if we have one in the format markers
foreach (var item in CustomMarkers)
{
if (item.Marker == markerName)
{
palette = item;
return true;
}
}
// we don't have anything for this marker
// so we return false and a default marker
palette = new();
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 992e2b513670a4d089c2e58d4c48e8c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: fd59b44bd3514406197f8ceb53547969, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,174 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using UnityEngine;
using UnityEngine.EventSystems;
using Yarn.Unity.Attributes;
#if USE_TMP
using TMPro;
#else
using TextMeshProUGUI = Yarn.Unity.TMPShim;
#endif
#nullable enable
namespace Yarn.Unity
{
[System.Serializable]
public struct InternalAppearance
{
[SerializeField] internal Sprite sprite;
[SerializeField] internal Color colour;
}
public class OptionItem : UnityEngine.UI.Selectable, ISubmitHandler, IPointerClickHandler, IPointerEnterHandler
{
[MustNotBeNull, SerializeField] protected TextMeshProUGUI? text;
[SerializeField] protected UnityEngine.UI.Image? selectionImage;
[Group("Appearance"), SerializeField] protected InternalAppearance normal;
[Group("Appearance"), SerializeField] protected InternalAppearance selected;
[Group("Appearance"), SerializeField] protected InternalAppearance disabled;
[Group("Appearance"), SerializeField] protected bool disabledStrikeThrough = true;
public YarnTaskCompletionSource<DialogueOption?>? OnOptionSelected;
public System.Threading.CancellationToken completionToken;
protected bool hasSubmittedOptionSelection = false;
protected DialogueOption? _option;
public virtual DialogueOption Option
{
get
{
if (_option == null)
{
throw new System.NullReferenceException("Option has not been set on the option item");
}
return _option;
}
set
{
_option = value;
hasSubmittedOptionSelection = false;
// When we're given an Option, use its text and update our
// interactibility.
string line = value.Line.TextWithoutCharacterName.Text;
if (disabledStrikeThrough && !value.IsAvailable)
{
line = $"<s>{value.Line.TextWithoutCharacterName.Text}</s>";
}
if (text == null)
{
Debug.LogWarning($"The {nameof(text)} is null, is it not connected in the inspector?", this);
return;
}
text.text = line;
interactable = value.IsAvailable;
// we want to apply the default styling to the option item when they are given an option
ApplyStyle(normal);
}
}
protected virtual void ApplyStyle(InternalAppearance style)
{
Color newColour = style.colour;
Sprite newSprite = style.sprite;
if (!Option.IsAvailable)
{
newColour = disabled.colour;
newSprite = disabled.sprite;
}
if (text == null)
{
Debug.LogWarning($"The {nameof(text)} is null, is it not connected in the inspector?", this);
return;
}
text.color = newColour;
if (selectionImage != null)
{
selectionImage.color = newColour;
if (newSprite != null)
{
selectionImage.sprite = newSprite;
selectionImage.gameObject.SetActive(true);
}
else
{
selectionImage.gameObject.SetActive(false);
}
}
}
public override void OnSelect(BaseEventData eventData)
{
base.OnSelect(eventData);
ApplyStyle(selected);
}
public override void OnDeselect(BaseEventData eventData)
{
base.OnDeselect(eventData);
ApplyStyle(normal);
}
new public bool IsHighlighted
{
get
{
return EventSystem.current.currentSelectedGameObject == this.gameObject;
}
}
// If we receive a submit or click event, invoke our "we just selected this option" handler.
public void OnSubmit(BaseEventData eventData)
{
InvokeOptionSelected();
}
public void InvokeOptionSelected()
{
// turns out that Selectable subclasses aren't intrinsically interactive/non-interactive
// based on their canvasgroup, you still need to check at the moment of interaction
if (!IsInteractable())
{
return;
}
// We only want to invoke this once, because it's an error to
// submit an option when the Dialogue Runner isn't expecting it. To
// prevent this, we'll only invoke this if the flag hasn't been cleared already.
if (hasSubmittedOptionSelection == false && !completionToken.IsCancellationRequested)
{
hasSubmittedOptionSelection = true;
OnOptionSelected?.TrySetResult(this.Option);
}
}
public virtual void OnPointerClick(PointerEventData eventData)
{
InvokeOptionSelected();
}
// If we mouse-over, we're telling the UI system that this element is
// the currently 'selected' (i.e. focused) element.
public override void OnPointerEnter(PointerEventData eventData)
{
base.Select();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c72af21cd06074f41a2537a712c24213
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,391 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Collections.Generic;
using UnityEngine;
using Yarn.Unity.Attributes;
#if USE_TMP
using TMPro;
#else
using TextMeshProUGUI = Yarn.Unity.TMPShim;
#endif
#nullable enable
using System.Threading;
namespace Yarn.Unity
{
/// <summary>
/// Receives options from a <see cref="DialogueRunner"/>, and displays and
/// manages a collection of <see cref="OptionItem"/> views for the user
/// to choose from.
/// </summary>
[HelpURL("https://docs.yarnspinner.dev/using-yarnspinner-with-unity/components/dialogue-view/options-list-view")]
public class OptionsPresenter : DialoguePresenterBase
{
[SerializeField] protected CanvasGroup? canvasGroup;
[MustNotBeNull]
[SerializeField] protected OptionItem? optionViewPrefab;
// A cached pool of OptionView objects so that we can reuse them
protected List<OptionItem> optionViews = new List<OptionItem>();
[Space]
[SerializeField] protected bool showsLastLine;
[ShowIf(nameof(showsLastLine))]
[Indent]
[MustNotBeNullWhen(nameof(showsLastLine))]
[SerializeField] protected TextMeshProUGUI? lastLineText;
[ShowIf(nameof(showsLastLine))]
[Indent]
[SerializeField] protected GameObject? lastLineContainer;
[ShowIf(nameof(showsLastLine))]
[Indent]
[SerializeField] protected TextMeshProUGUI? lastLineCharacterNameText;
[ShowIf(nameof(showsLastLine))]
[Indent]
[SerializeField] protected GameObject? lastLineCharacterNameContainer;
protected LocalizedLine? lastSeenLine;
/// <summary>
/// Controls whether or not to display options whose <see
/// cref="OptionSet.Option.IsAvailable"/> value is <see
/// langword="false"/>.
/// </summary>
[Space]
public bool showUnavailableOptions = false;
[Group("Fade")]
[Label("Fade UI")]
public bool useFadeEffect = true;
[Group("Fade")]
[ShowIf(nameof(useFadeEffect))]
public float fadeUpDuration = 0.25f;
[Group("Fade")]
[ShowIf(nameof(useFadeEffect))]
public float fadeDownDuration = 0.1f;
private const string TruncateLastLineMarkupName = "lastline";
/// <summary>
/// Called by a <see cref="DialogueRunner"/> to dismiss the options view
/// when dialogue is complete.
/// </summary>
/// <returns>A completed task.</returns>
public override YarnTask OnDialogueCompleteAsync()
{
if (canvasGroup != null)
{
canvasGroup.alpha = 0;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
return YarnTask.CompletedTask;
}
/// <summary>
/// Called by Unity to set up the object.
/// </summary>
protected virtual void Start()
{
if (canvasGroup != null)
{
canvasGroup.alpha = 0;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
if (lastLineContainer == null && lastLineText != null)
{
lastLineContainer = lastLineText.gameObject;
}
if (lastLineCharacterNameContainer == null && lastLineCharacterNameText != null)
{
lastLineCharacterNameContainer = lastLineCharacterNameText.gameObject;
}
}
/// <summary>
/// Called by a <see cref="DialogueRunner"/> to set up the options view
/// when dialogue begins.
/// </summary>
/// <returns>A completed task.</returns>
public override YarnTask OnDialogueStartedAsync()
{
if (canvasGroup != null)
{
canvasGroup.alpha = 0;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
return YarnTask.CompletedTask;
}
/// <summary>
/// Called by a <see cref="DialogueRunner"/> when a line needs to be
/// presented, and stores the line as the 'last seen line' so that it
/// can be shown when options appear.
/// </summary>
/// <remarks>This view does not display lines directly, but instead
/// stores lines so that when options are run, the last line that ran
/// before the options appeared can be shown.</remarks>
/// <inheritdoc cref="DialoguePresenterBase.RunLineAsync"
/// path="/param"/>
/// <returns>A completed task.</returns>
public override YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
{
if (showsLastLine)
{
lastSeenLine = line;
}
return YarnTask.CompletedTask;
}
/// <summary>
/// Called by a <see cref="DialogueRunner"/> to display a collection of
/// options to the user.
/// </summary>
/// <inheritdoc cref="DialoguePresenterBase.RunOptionsAsync"
/// path="/param"/>
/// <inheritdoc cref="DialoguePresenterBase.RunOptionsAsync"
/// path="/returns"/>
public override async YarnTask<DialogueOption?> RunOptionsAsync(DialogueOption[] dialogueOptions, LineCancellationToken cancellationToken)
{
// if all options are unavailable then we need to return null
// it's the responsibility of the dialogue runner to handle this, not the presenter
bool anyAvailable = false;
foreach (var option in dialogueOptions)
{
if (option.IsAvailable)
{
anyAvailable = true;
break;
}
}
if (!anyAvailable)
{
return null;
}
// If we don't already have enough option views, create more
while (dialogueOptions.Length > optionViews.Count)
{
var optionView = CreateNewOptionView();
optionViews.Add(optionView);
}
// A completion source that represents the selected option.
YarnTaskCompletionSource<DialogueOption?> selectedOptionCompletionSource = new YarnTaskCompletionSource<DialogueOption?>();
// A cancellation token source that becomes cancelled when any
// option item is selected, or when this entire option view is
// cancelled
var completionCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.NextContentToken);
async YarnTask CancelSourceWhenDialogueCancelled()
{
await YarnTask.WaitUntilCanceled(completionCancellationSource.Token);
if (cancellationToken.IsNextContentRequested == true)
{
// The overall cancellation token was fired, not just our
// internal 'something was selected' cancellation token.
// This means that the dialogue view has been informed that
// any value it returns will not be used. Set a 'null'
// result on our completion source so that that we can get
// out of here as quickly as possible.
selectedOptionCompletionSource.TrySetResult(null);
}
}
// Start waiting
CancelSourceWhenDialogueCancelled().Forget();
for (int i = 0; i < dialogueOptions.Length; i++)
{
var optionView = optionViews[i];
var option = dialogueOptions[i];
if (option.IsAvailable == false && showUnavailableOptions == false)
{
// option is unavailable, skip it
continue;
}
optionView.gameObject.SetActive(true);
optionView.Option = option;
optionView.OnOptionSelected = selectedOptionCompletionSource;
optionView.completionToken = completionCancellationSource.Token;
}
// There is a bug that can happen where in-between option items being configured one can be selected
// and because the items are still being configured the others don't get the deselect message
// which means visually two items are selected.
// So instead now after configuring them we find if any are highlighted, and if so select that one
// otherwise select the first non-deactivated one
// because at this point now all of them are configured they will all get the select/deselect message
int optionIndexToSelect = -1;
for (int i = 0; i < optionViews.Count; i++)
{
var view = optionViews[i];
if (!view.isActiveAndEnabled)
{
continue;
}
if (view.IsHighlighted)
{
optionIndexToSelect = i;
break;
}
// ok at this point the view is enabled
// but not highlighted
// so if we haven't already decreed we have found one to select
// we select this one
if (optionIndexToSelect == -1)
{
optionIndexToSelect = i;
}
}
if (optionIndexToSelect > -1)
{
optionViews[optionIndexToSelect].Select();
}
// Update the last line, if one is configured
if (lastLineContainer != null)
{
if (lastSeenLine != null && showsLastLine)
{
// if we have a last line character name container
// and the last line has a character then we show the nameplate
// otherwise we turn off the nameplate
var line = lastSeenLine.Text;
if (lastLineCharacterNameContainer != null)
{
if (string.IsNullOrWhiteSpace(lastSeenLine.CharacterName))
{
lastLineCharacterNameContainer.SetActive(false);
}
else
{
line = lastSeenLine.TextWithoutCharacterName;
lastLineCharacterNameContainer.SetActive(true);
if (lastLineCharacterNameText != null)
{
lastLineCharacterNameText.text = lastSeenLine.CharacterName;
}
}
}
else
{
line = lastSeenLine.TextWithoutCharacterName;
}
var lineText = line.Text;
// if the line was tagged with the TruncateLastLineMarkupName marker we want to clean that up before display
if (line.TryGetAttributeWithName(TruncateLastLineMarkupName, out var markup))
{
// we get the substring of 0 -> markup position
// and replace that range with ...
if (markup.Position <= lineText.Length) // Bounds check
{
var end = lineText.Substring(markup.Position);
lineText = "..." + end;
}
}
if (lastLineText != null)
{
lastLineText.text = lineText;
}
lastLineContainer.SetActive(true);
}
else
{
lastLineContainer.SetActive(false);
}
}
if (useFadeEffect && canvasGroup != null)
{
// fade up the UI now
await Effects.FadeAlphaAsync(canvasGroup, 0, 1, fadeUpDuration, cancellationToken.HurryUpToken);
}
// allow interactivity and wait for an option to be selected
if (canvasGroup != null)
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
}
// Wait for a selection to be made, or for the task to be completed.
var completedTask = await selectedOptionCompletionSource.Task;
completionCancellationSource.Cancel();
// now one of the option items has been selected so we do cleanup
if (canvasGroup != null)
{
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
}
if (useFadeEffect && canvasGroup != null)
{
// fade down
await Effects.FadeAlphaAsync(canvasGroup, 1, 0, fadeDownDuration, cancellationToken.HurryUpToken);
}
// disabling ALL the options views now
foreach (var optionView in optionViews)
{
optionView.gameObject.SetActive(false);
}
await YarnTask.Yield();
// if we are cancelled we still need to return but we don't want to have a selection, so we return no selected option
if (cancellationToken.NextContentToken.IsCancellationRequested)
{
return await DialogueRunner.NoOptionSelected;
}
// finally we return the selected option
return completedTask;
}
protected virtual OptionItem CreateNewOptionView()
{
var optionView = Instantiate(optionViewPrefab);
var targetTransform = canvasGroup != null ? canvasGroup.transform : this.transform;
if (optionView == null)
{
throw new System.InvalidOperationException($"Can't create new option view: {nameof(optionView)} is null");
}
optionView.transform.SetParent(targetTransform.transform, false);
optionView.transform.SetAsLastSibling();
optionView.gameObject.SetActive(false);
return optionView;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0cd0d0412ea34813959f8ddaf02a6eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,126 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Yarn.Markup;
using Yarn.Unity;
#nullable enable
/// <summary>
/// An attribute marker processor that uses a <see cref="MarkupPalette"/> to
/// apply TextMeshPro styling tags to a line.
/// </summary>
/// <remarks>This marker processor registers itself as a handler for markers
/// whose name is equal to the name of a style in the given palette. For
/// example, if the palette defines a style named "happy", this marker processor
/// will process tags in a Yarn line named <c>[happy]</c> by inserting the
/// appropriate TextMeshProp style tags defined for the "happy" style.</remarks>
public sealed class PaletteMarkerProcessor : Yarn.Unity.ReplacementMarkupHandler
{
/// <summary>
/// The <see cref="MarkupPalette"/> to use when applying styles.
/// </summary>
[Tooltip("The MarkupPalette to use when applying styles.")]
public MarkupPalette? palette;
/// <summary>
/// The line provider to register this markup processor with.
/// </summary>
[Tooltip("The LineProviderBehaviour to register this markup processor with.")]
public LineProviderBehaviour? lineProvider;
/// <inheritdoc/>
/// <summary>
/// Processes a replacement marker by applying the style from the given
/// palette.
/// </summary>
/// <param name="marker">The marker to process.</param>
/// <param name="childBuilder">A StringBuilder to build the styled text in.</param>
/// <param name="childAttributes">An optional list of child attributes to
/// apply, but this is ignored for TextMeshPro styles.</param>
/// <param name="localeCode">The locale code to use when formatting the style.</param>
/// <returns>A list of markup diagnostics if there are any errors, otherwise an empty list.</returns>
public override ReplacementMarkerResult ProcessReplacementMarker(MarkupAttribute marker, StringBuilder childBuilder, List<MarkupAttribute> childAttributes, string localeCode)
{
if (palette == null)
{
var error = new List<LineParser.MarkupDiagnostic>() {
new LineParser.MarkupDiagnostic($"can't apply palette for marker {marker.Name}, because a palette was not set")
};
return new ReplacementMarkerResult(error, 0);
}
if (palette.PaletteForMarker(marker.Name, out var format))
{
var childrenLength = childBuilder.Length;
childBuilder.Insert(0, format.Start);
childBuilder.Append(format.End);
// finally we need to know if we have to offset the markers
// most of the time we won't have to do anything
if (format.MarkerOffset != 0)
{
// we now need to move any children attributes down by however many characters were added to the front
// this is only the case if visible glyphs were added
// as in for example adding <b> to the front doesn't add any visible glyphs so won't need to offset anything
// and because markers are all 0-offset relative to parents
for (int i = 0; i < childAttributes.Count; i++)
{
childAttributes[i] = childAttributes[i].Shift(format.MarkerOffset);
}
}
// finally we need to calculate the number of invisible characters we added
// which is the difference between the new and original string lengths - the total number of visible characters inserted
// we don't care WHERE those visible characters were added, just that they were
// we can't just use the marker offset because that only worries about visible elements added at the front of the string
// most of the time this is just gonna be 0 anyways and you don't have to think about it
return new ReplacementMarkerResult(childBuilder.Length - childrenLength - format.TotalVisibleCharacterCount);
}
var diagnostics = new List<LineParser.MarkupDiagnostic>() { new LineParser.MarkupDiagnostic($"was unable to find a matching sprite for {marker.Name}") };
return new ReplacementMarkerResult(diagnostics, 0);
}
/// <summary>
/// Called by Unity when this script is enabled to register itself with <see
/// cref="lineProvider"/>.
/// </summary>
private void Start()
{
if (palette == null)
{
return;
}
if (palette.BasicMarkers.Count == 0)
{
return;
}
if (lineProvider == null)
{
var runner = DialogueRunner.FindRunner(this);
if (runner == null)
{
Debug.LogWarning("Was unable to find a dialogue runner, unable to register the markup palettes.");
return;
}
lineProvider = (LineProviderBehaviour)runner.LineProvider;
}
foreach (var marker in palette.BasicMarkers)
{
lineProvider.RegisterMarkerProcessor(marker.Marker, this);
}
foreach (var marker in palette.CustomMarkers)
{
lineProvider.RegisterMarkerProcessor(marker.Marker, this);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a968ef973edc54520b248fa3113ae925
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,88 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Yarn.Markup;
#if USE_TMP
using TMPro;
#else
using TMP_Text = Yarn.Unity.TMPShim;
#endif
#nullable enable
namespace Yarn.Unity
{
/// <summary>
/// Allows pausing the current typewrite through [pause/] markers.
/// </summary>
public sealed class PauseEventProcessor : IActionMarkupHandler
{
private Dictionary<int, float> pauses = new();
public void OnLineDisplayComplete()
{
pauses.Clear();
}
public void OnLineDisplayBegin(MarkupParseResult line, TMP_Text text)
{
pauses = new();
// grabbing out any pauses inside the line
foreach (var attribute in line.Attributes)
{
if (attribute.Name != "pause")
{
continue;
}
if (attribute.Properties.TryGetValue("pause", out MarkupValue value))
{
// depending on the property value we need to take a different path this is because they have made it an integer or a float which are roughly the same.
// But they also might have done something weird and we need to handle that
switch (value.Type)
{
case MarkupValueType.Integer:
pauses.Add(attribute.Position, value.IntegerValue);
break;
case MarkupValueType.Float:
pauses.Add(attribute.Position, value.FloatValue * 1000);
break;
default:
Debug.LogWarning($"Pause property is of type {value.Type}, which is not allowed. Defaulting to one second.");
pauses.Add(attribute.Position, 1000);
break;
}
}
else
{
// they haven't set a duration, so we will instead use the
// default of one second
pauses.Add(attribute.Position, 1000);
}
}
}
public void OnPrepareForLine(MarkupParseResult line, TMP_Text text)
{
return;
}
public async YarnTask OnCharacterWillAppear(int currentCharacterIndex, MarkupParseResult line, CancellationToken cancellationToken)
{
if (pauses.TryGetValue(currentCharacterIndex, out var duration))
{
await YarnTask.Delay(System.TimeSpan.FromMilliseconds(duration), cancellationToken).SuppressCancellationThrow();
}
}
public void OnLineWillDismiss()
{
return;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 027fadfcefed14f5c9cc781a2bb7e12a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Yarn.Markup;
namespace Yarn.Unity
{
/// <summary>
/// An attribute marker processor receives a marker found in a Yarn line,
/// and optionally rewrites the marker and its children into a new form.
/// </summary>
/// <seealso cref="LineProviderBehaviour"/>
public abstract class ReplacementMarkupHandler : MonoBehaviour, IAttributeMarkerProcessor
{
/// <inheritdoc/>
public abstract ReplacementMarkerResult ProcessReplacementMarker(MarkupAttribute marker, StringBuilder childBuilder, List<MarkupAttribute> childAttributes, string localeCode);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c71c1c5641d344603b2c23af06859f62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,61 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using UnityEngine;
#nullable enable
namespace Yarn.Unity.Samples
{
/// <summary>
/// Detects if the render pipeline is different from the one the samples
/// were created with, and warn you that things might look odd.
/// </summary>
/// <remarks>
/// This component only exists to be added into the Yarn Spinner sample
/// scenes.
/// You are safe to delete this.
/// </remarks>
[ExecuteInEditMode]
public sealed class SampleRenderDetector : MonoBehaviour
{
void Awake()
{
// When using the built in render pipeline there is no graphics
// pipeline set, so this being null is the same as saying "using the
// built in pipeline".
//
// There are some edge cases this won't detect, but will work well
// enough.
if (Application.isEditor && UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline == null)
{
Debug.LogWarning("The samples were created using the Universal Render Pipeline, things will not appear correctly. You will need to convert the materials to be compatible.");
}
}
}
#if UNITY_EDITOR
namespace Editor
{
using UnityEditor;
[CustomEditor(typeof(SampleRenderDetector))]
public class SampleRenderDetectorEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
if (UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline == null)
{
EditorGUILayout.HelpBox("The samples were created using the Universal Render Pipeline, things will not appear correctly.\nYou are safe to delete this game object.", MessageType.Error);
}
else
{
EditorGUILayout.HelpBox("This object detects if samples were created using the URP.\nYou are safe to delete this game object.", MessageType.Info);
}
}
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6dbc98d85e6dc4263b6bf71fc0a6bdd5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Yarn.Markup;
#nullable enable
namespace Yarn.Unity
{
/// <summary>
/// An attribute marker processor that inserts TextMeshPro style tags where
/// Yarn Spinner <c>[style]</c> tags appear in a line.
/// </summary>
public sealed class StyleMarkerProcessor : ReplacementMarkupHandler
{
[SerializeField]
public LineProviderBehaviour? lineProvider;
/// <inheritdoc/>
public override ReplacementMarkerResult ProcessReplacementMarker(MarkupAttribute marker, StringBuilder childBuilder, List<MarkupAttribute> childAttributes, string localeCode)
{
// ok so we check if we have a property called style
// if not give up
if (!marker.TryGetProperty("style", out string? property))
{
var error = new List<LineParser.MarkupDiagnostic>
{
new LineParser.MarkupDiagnostic("Unable to identify a name for the style.")
};
return new ReplacementMarkerResult(error, 0);
}
var originalLength = childBuilder.Length;
childBuilder.Insert(0, $"<style=\"{property}\">");
childBuilder.Append("</style>");
// at this point we have no errors
// but it is entirely possible that style has added visible characters
// we have no way of knowing this
// so if this is the case any attributes will now be off
// unfortunately that is a downside to using the style replacement system
// most of the time this won't be a problem
return new ReplacementMarkerResult(childBuilder.Length - originalLength);
}
// Start is called before the first frame update
void Start()
{
if (lineProvider == null)
{
var runner = DialogueRunner.FindRunner(this);
if (runner == null)
{
Debug.LogWarning("Was unable to find a dialogue runner, unable to register the style markup.");
return;
}
lineProvider = (LineProviderBehaviour)runner.LineProvider;
}
lineProvider.RegisterMarkerProcessor("style", this);
// in an ideal world instead of making you write out [style = h1] you could just do [h1]
// but TMP doesn't allow access to the list of styles, and as such also can't get their names
// so we have no way of know what the names of any style to register them
// alas
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0901fdc59460943b682c5c4000c44a0d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#nullable enable
namespace Yarn.Unity
{
/*
This class exists for the purpose of resolving a VERY unlikely to occur package dependancy issue.
Previously we used to have a dependancy on Text Mesh Pro (and still do), but in Unity 2023 unity deprecated TMP and merged it into uGUI 2.0.0 but did it in a very silly way that caused a package resolution issue if you have it installed in 2023.
Because you can't easily set Unity version dependant packaging, the dependancy on either was removed and we rely on, that by default, one of either TMP or uGUI is installed.
Essentially we moved from a hard dependancy on TMP to a soft one.
This means it's possible that neither is installed (very unlikely) and we need to handle it.
That is what this class is for, it emulates the basic shape of TMP as far as dialogue views are concerned and can stand in for the proper TMP elements.
The reason we did it this way is so that any serialised elements of those dialogue views aren't lost when the user sees the error and then installs TMP.
*/
#if !USE_TMP
using UnityEngine;
[ExecuteInEditMode]
public sealed class TMPShim : MonoBehaviour
{
public Color color;
public string text;
public int maxVisibleCharacters;
public TextInfo textInfo;
void OnEnable()
{
#if UNITY_2023_2_OR_NEWER
Debug.LogWarning("Yarn Spinner requires requires uGUI 2.0.0 or above (com.unity.ugui) be installed in the Package Manager.");
#else
Debug.LogWarning("Yarn Spinner requires TextMeshPro (com.unity.textmeshpro) be installed in the Package Manager.");
#endif
}
public struct TextInfo
{
public int characterCount;
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ecc7b5e10ccaa4c9580ec8902c2c68b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 250a919d40760494ebcff0e3ddb596d0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
fileFormatVersion: 2
guid: 2b62f169ad6c04c2d9a3fa4e8c3aa694
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 150
spriteBorder: {x: 80, y: 0, z: 80, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 1537655665
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 7a184148437294a7b8d96378a700727d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 0
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 32
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,108 @@
fileFormatVersion: 2
guid: 05af27d2817f5413ea5ba715b8378151
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: 4
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: 4
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
fileFormatVersion: 2
guid: 48f24bb86f008479ab5e103ef81a0b49
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 150
spriteBorder: {x: 156, y: 171, z: 156, w: 156}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 1537655665
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
fileFormatVersion: 2
guid: 04a26af393c8742a3bdf84b43bed6c16
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 150
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
fileFormatVersion: 2
guid: a044d296f0c964ed8856be08c527238d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 150
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
fileFormatVersion: 2
guid: 997a4571b48144fd7be9c547b769c11d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 150
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
fileFormatVersion: 2
guid: 4de89c03b6297472d82b0d925ee6fcd3
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 150
spriteBorder: {x: 165, y: 165, z: 165, w: 165}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 1537655665
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
fileFormatVersion: 2
guid: 33e9636a699f04c3ab383160b1904a2d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMasterTextureLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 150
spriteBorder: {x: 165, y: 165, z: 165, w: 165}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 1537655665
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,108 @@
fileFormatVersion: 2
guid: eaa71f36e30cb45f1876a4832d45cac4
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,108 @@
fileFormatVersion: 2
guid: 7e9a80d90823e4648b6cfa68e570fad1
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: -1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6dbbcbd5b018b4eca92eb51a7bf64a05
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#nullable enable
namespace Yarn.Unity
{
using System.Threading;
using System.Collections.Generic;
#if USE_TMP
using TMPro;
#else
using TMP_Text = Yarn.Unity.TMPShim;
#endif
/// <summary>
/// An object that can handle delivery of a line's text over time.
/// </summary>
public interface IAsyncTypewriter
{
/// <summary>
/// Displays the contents of a line over time.
/// </summary>
/// <remarks>
/// <para>This method is called when a dialogue presenter wants to
/// deliver a line's text. The typewriter should present the text to the
/// user; it may take as long as it needs to do so. </para>
///
/// <para>If <paramref name="cancellationToken"/>'s <see
/// cref="CancellationToken.IsCancellationRequested"/> becomes true, the
/// typewriter effect should end early and present the entire contents
/// of <paramref name="line"/>.</para>
/// </remarks>
/// <param name="line">The line to display.</param>
/// <param name="cancellationToken">A token that indicates that the
/// typewriter effect should be cancelled.</param>
/// <returns>A task that completes when the typewriter effect has
/// finished.</returns>
public YarnTask RunTypewriter(Markup.MarkupParseResult line, CancellationToken cancellationToken);
/// <summary>
/// Called by the presenter before content has been shown.
/// This gives the typewriter it's chance to do any setup before the content is visibly shown.
/// </summary>
/// <param name="line">The content of the line or option that is about to be shown</param>
public void PrepareForContent(Markup.MarkupParseResult line);
/// <summary>
/// Called right before the content will be visibly hidden
/// </summary>
public void ContentWillDismiss();
/// <summary>
/// Called after the content has been visibly hidden.
/// </summary>
/// <remarks>
/// This is the typewriters last chance to do any cleanup that they may need to do before more content or full destruction occurs, will always be called after <see cref="ContentWillDismiss"/>.
/// It is the responsibility of the <see cref="DialoguePresenterBase"/> to only call this after hiding anything that might look weird if state is reset.
/// </remarks>
public void ContentDidDismiss();
/// <summary>
/// The list of action markup handlers that this typewriter should call out to while typewriting.
/// </summary>
public List<IActionMarkupHandler> ActionMarkupHandlers { get; }
/// <summary>
/// The main text element that the presenter intends the typewriter to work with
/// </summary>
/// <remarks>
/// Most of the time the typewriter is just going to be changing the visible characters so will need this anyways.
/// Is safe to not worry about this if your typewriter has no need of it.
/// </remarks>
public TMP_Text? TextElement { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d6934c733ecff4af39599e521eb5cc90
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,122 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#nullable enable
namespace Yarn.Unity
{
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Yarn.Markup;
#if USE_TMP
using TMPro;
#else
using TMP_Text = Yarn.Unity.TMPShim;
#endif
/// <summary>
/// An implementation of <see cref="IAsyncTypewriter"/> that delivers
/// all content instantly, and invokes any <see
/// cref="IActionMarkupHandler"/>s along the way as needed.
/// </summary>
public class InstantTypewriter : IAsyncTypewriter
{
/// <summary>
/// The <see cref="TMP_Text"/> to display the text in.
/// </summary>
public TMP_Text? TextElement { get; set; }
/// <summary>
/// A collection of <see cref="IActionMarkupHandler"/> objects that
/// should be invoked as needed during the typewriter's delivery in <see
/// cref="RunTypewriter"/>, depending upon the contents of a line.
/// </summary>
public List<IActionMarkupHandler> ActionMarkupHandlers { get; set; } = new();
/// <inheritdoc/>
public async YarnTask RunTypewriter(Markup.MarkupParseResult line, CancellationToken cancellationToken)
{
if (TextElement == null)
{
Debug.LogWarning($"Can't show text as typewriter, because {nameof(TextElement)} was not provided");
return;
}
TextElement.maxVisibleCharacters = 0;
TextElement.text = line.Text;
// Let every markup handler know that display is about to begin
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayBegin(line, TextElement);
}
var textInfo = TextElement.GetTextInfo(line.Text);
// Get the count of visible characters from TextMesh to exclude markup characters
var visibleCharacterCount = textInfo.characterCount;
// Go through each character of the line and letting the
// processors know about it
for (int i = 0; i < visibleCharacterCount; i++)
{
// Tell every markup handler that it is time to process the
// current character
foreach (var processor in ActionMarkupHandlers)
{
await processor
.OnCharacterWillAppear(i, line, cancellationToken)
.SuppressCancellationThrow();
}
TextElement.maxVisibleCharacters += 1;
}
// We've finished showing every character (or we were
// cancelled); ensure that everything is now visible.
TextElement.maxVisibleCharacters = visibleCharacterCount;
// Let each markup handler know the line has finished displaying
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayComplete();
}
}
public void PrepareForContent(MarkupParseResult line)
{
if (TextElement == null)
{
return;
}
TextElement.maxVisibleCharacters = 0;
TextElement.text = line.Text;
foreach (var processor in ActionMarkupHandlers)
{
processor.OnPrepareForLine(line, TextElement);
}
}
public void ContentWillDismiss()
{
// we tell all action processors that the line is finished and is about to go away
foreach (var processor in ActionMarkupHandlers)
{
processor.OnLineWillDismiss();
}
}
public void ContentDidDismiss()
{
if (TextElement == null)
{
return;
}
TextElement.maxVisibleCharacters = 0;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd0927d5964ad4be8b899682f6448a66
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,151 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#nullable enable
namespace Yarn.Unity
{
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
#if USE_TMP
using TMPro;
#else
using TMP_Text = Yarn.Unity.TMPShim;
#endif
/// <summary>
/// An implementation of <see cref="IAsyncTypewriter"/> that delivers
/// characters one at a time, and invokes any <see
/// cref="IActionMarkupHandler"/>s along the way as needed.
/// </summary>
public class LetterTypewriter : IAsyncTypewriter
{
/// <summary>
/// The <see cref="TMP_Text"/> to display the text in.
/// </summary>
public TMP_Text? TextElement { get; set; }
/// <summary>
/// A collection of <see cref="IActionMarkupHandler"/> objects that
/// should be invoked as needed during the typewriter's delivery in <see
/// cref="RunTypewriter"/>, depending upon the contents of a line.
/// </summary>
public List<IActionMarkupHandler> ActionMarkupHandlers { get; set; } = new();
/// <summary>
/// The number of characters per second to deliver.
/// </summary>
/// <remarks>If this value is zero, all characters are delivered at
/// once, subject to any delays added by the markup handlers in <see
/// cref="ActionMarkupHandlers"/>.</remarks>
public float CharactersPerSecond { get; set; } = 0f;
/// <inheritdoc/>
public async YarnTask RunTypewriter(Markup.MarkupParseResult line, CancellationToken cancellationToken)
{
if (TextElement == null)
{
Debug.LogWarning($"Can't show text as typewriter, because {nameof(TextElement)} was not provided");
}
else
{
TextElement.maxVisibleCharacters = 0;
TextElement.text = line.Text;
// Let every markup handler know that display is about to begin
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayBegin(line, TextElement);
}
double secondsPerCharacter = 0;
if (CharactersPerSecond > 0)
{
secondsPerCharacter = 1.0 / CharactersPerSecond;
}
// Get the count of visible characters from TextMesh to exclude markup characters
var visibleCharacterCount = TextElement.GetTextInfo(line.Text).characterCount;
// Start with a full time budget so that we immediately show the first character
double accumulatedDelay = secondsPerCharacter;
// Go through each character of the line and letting the
// processors know about it
for (int i = 0; i < visibleCharacterCount; i++)
{
// If we don't already have enough accumulated time budget
// for a character, wait until we do (or until we're
// cancelled)
while (!cancellationToken.IsCancellationRequested
&& (accumulatedDelay < secondsPerCharacter))
{
var timeBeforeYield = Time.timeAsDouble;
await YarnTask.Yield();
var timeAfterYield = Time.timeAsDouble;
accumulatedDelay += timeAfterYield - timeBeforeYield;
}
// Tell every markup handler that it is time to process the
// current character
foreach (var processor in ActionMarkupHandlers)
{
await processor
.OnCharacterWillAppear(i, line, cancellationToken)
.SuppressCancellationThrow();
}
TextElement.maxVisibleCharacters += 1;
accumulatedDelay -= secondsPerCharacter;
}
// We've finished showing every character (or we were
// cancelled); ensure that everything is now visible.
TextElement.maxVisibleCharacters = visibleCharacterCount;
}
// Let each markup handler know the line has finished displaying
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayComplete();
}
}
public void PrepareForContent(Markup.MarkupParseResult line)
{
if (TextElement == null)
{
return;
}
TextElement.maxVisibleCharacters = 0;
TextElement.text = line.Text;
foreach (var processor in ActionMarkupHandlers)
{
processor.OnPrepareForLine(line, TextElement);
}
}
public void ContentWillDismiss()
{
// we tell all action processors that the line is finished and is about to go away
foreach (var processor in ActionMarkupHandlers)
{
processor.OnLineWillDismiss();
}
}
public void ContentDidDismiss()
{
if (TextElement == null)
{
return;
}
TextElement.maxVisibleCharacters = 0;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1cc6b37948eec40d5bd9e7e91157286c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,177 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
#nullable enable
namespace Yarn.Unity
{
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
#if USE_TMP
using TMPro;
#else
using TMP_Text = Yarn.Unity.TMPShim;
#endif
/// <summary>
/// An implementation of <see cref="IAsyncTypewriter"/> that delivers
/// words one at a time, and invokes any <see
/// cref="IActionMarkupHandler"/>s along the way as needed.
/// </summary>
public class WordTypewriter : IAsyncTypewriter
{
/// <summary>
/// The <see cref="TMP_Text"/> to display the text in.
/// </summary>
public TMP_Text? TextElement { get; set; }
/// <summary>
/// A collection of <see cref="IActionMarkupHandler"/> objects that
/// should be invoked as needed during the typewriter's delivery in <see
/// cref="RunTypewriter"/>, depending upon the contents of a line.
/// </summary>
public List<IActionMarkupHandler> ActionMarkupHandlers { get; set; } = new();
/// <summary>
/// The number of words per second to deliver.
/// </summary>
/// <remarks>If this value is zero, all words are delivered at
/// once, subject to any delays added by the markup handlers in <see
/// cref="ActionMarkupHandlers"/>.</remarks>
public float WordsPerSecond { get; set; } = 0f;
/// <inheritdoc/>
public async YarnTask RunTypewriter(Markup.MarkupParseResult line, CancellationToken cancellationToken)
{
// ok so this will have to do the following:
// work out where the pauses are meant to be
// do this by finding all the breaks in the line
// then at each point in the line we move char by char
// when we hit a break point (which we know in advance)
if (TextElement == null)
{
Debug.LogWarning($"Can't show text as typewriter, because {nameof(TextElement)} was not provided");
}
else
{
TextElement.maxVisibleCharacters = 0;
TextElement.text = line.Text;
// Let every markup handler know that display is about to begin
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayBegin(line, TextElement);
}
double secondsPerWord = 0;
if (WordsPerSecond > 0)
{
secondsPerWord = 1.0 / WordsPerSecond;
}
var wordBoundaries = new SortedSet<int>();
var textInfo = TextElement.GetTextInfo(line.Text);
for (int i = 0; i < textInfo.wordCount; i++)
{
var word = textInfo.wordInfo[i];
wordBoundaries.Add(word.lastCharacterIndex + 1);
}
// Get the count of visible characters from TextMesh to exclude markup characters
var visibleCharacterCount = textInfo.characterCount;
// Start with a full time budget so that we immediately show the first character
double accumulatedDelay = secondsPerWord;
int current = wordBoundaries.Min;
// Go through each character of the line and letting the
// processors know about it
for (int i = 0; i < visibleCharacterCount; i++)
{
// if we are at the character that requires waiting we want to wait until we hit the allotted time
if (i == current)
{
// If we don't already have enough accumulated time budget for a word, wait until we do (or until we're cancelled)
while (!cancellationToken.IsCancellationRequested && (accumulatedDelay < secondsPerWord))
{
var timeBeforeYield = Time.timeAsDouble;
await YarnTask.Yield();
var timeAfterYield = Time.timeAsDouble;
accumulatedDelay += timeAfterYield - timeBeforeYield;
}
accumulatedDelay -= secondsPerWord;
wordBoundaries.Remove(current);
if (wordBoundaries.Count > 0)
{
current = wordBoundaries.Min;
}
else
{
current = int.MaxValue;
}
}
// Tell every markup handler that it is time to process the
// current character
foreach (var processor in ActionMarkupHandlers)
{
await processor
.OnCharacterWillAppear(i, line, cancellationToken)
.SuppressCancellationThrow();
}
TextElement.maxVisibleCharacters += 1;
}
// We've finished showing every character (or we were
// cancelled); ensure that everything is now visible.
TextElement.maxVisibleCharacters = visibleCharacterCount;
}
// Let each markup handler know the line has finished displaying
foreach (var markupHandler in ActionMarkupHandlers)
{
markupHandler.OnLineDisplayComplete();
}
}
public void PrepareForContent(Markup.MarkupParseResult line)
{
if (TextElement == null)
{
return;
}
TextElement.maxVisibleCharacters = 0;
TextElement.text = line.Text;
foreach (var processor in ActionMarkupHandlers)
{
processor.OnPrepareForLine(line, TextElement);
}
}
public void ContentWillDismiss()
{
// we tell all action processors that the line is finished and is about to go away
foreach (var processor in ActionMarkupHandlers)
{
processor.OnLineWillDismiss();
}
}
public void ContentDidDismiss()
{
if (TextElement == null)
{
return;
}
TextElement.maxVisibleCharacters = 0;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c27d40c62a2864f38967e66e1ff3ce44
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,251 @@
/*
Yarn Spinner is licensed to you under the terms found in the file LICENSE.md.
*/
using System;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using UnityEngine;
using Yarn.Unity.Attributes;
#nullable enable
namespace Yarn.Unity
{
/// <summary>
/// A subclass of <see cref="DialoguePresenterBase"/> that plays voice-over
/// <see cref="AudioClip"/>s for lines of dialogue.
/// </summary>
public sealed class VoiceOverPresenter : DialoguePresenterBase
{
/// <summary>
/// If <see langword="true"/>, the voice over view will request that the
/// dialogue runner proceed to the next line when audio for the line has
/// finished playing.
/// </summary>
[Group("Line Management")]
public bool endLineWhenVoiceoverComplete = true;
/// <summary>
/// The fade out time when the line is interrupted during playback.
/// </summary>
[Group("Timing")]
public float fadeOutTimeOnLineFinish = 0.05f;
/// <summary>
/// The amount of time to wait before starting playback of the line.
/// </summary>
[Group("Timing")]
public float waitTimeBeforeLineStart = 0f;
/// <summary>
/// The amount of time after playback has completed before this view
/// reports that it's finished delivering the line.
/// </summary>
[Group("Timing")]
public float waitTimeAfterLineComplete = 0f;
/// <summary>
/// The <see cref="AudioSource"/> that this voice over view will play
/// its audio from.
/// </summary>
/// <remarks>If this is <see langword="null"/>, a new <see
/// cref="AudioSource"/> will be added at runtime.</remarks>
[SerializeField]
[NotNull]
// for some reason Unity doesn't seem to respect the [NotNull] attribute
// presumably this will be fixed in a future version of Unity
#pragma warning disable CS8618
public AudioSource audioSource;
#pragma warning restore CS8618
void Awake()
{
if (audioSource == null)
{
// If we don't have an audio source, add one.
audioSource = gameObject.AddComponent<AudioSource>();
// Additionally, we'll assume that the user didn't place the
// game object that this component is attached to deliberately,
// so we'll set the spatial blend to 0 (which means the audio
// will not be positioned in 3D space.)
audioSource.spatialBlend = 0f;
}
}
void Reset()
{
if (audioSource == null)
{
audioSource = GetComponentInChildren<AudioSource>();
}
}
/// <summary>
/// Begins playing the associated audio for the specified line.
/// </summary>
/// <remarks>
/// <para style="warning">This method is not intended to be called from
/// your code. Instead, the <see cref="DialogueRunner"/> class will call
/// it at the appropriate time.</para>
/// </remarks>
/// <inheritdoc cref="DialoguePresenterBase.RunLineAsync(LocalizedLine,
/// LineCancellationToken)" path="/param"/>
/// <seealso cref="DialoguePresenterBase.RunLineAsync(LocalizedLine,
/// LineCancellationToken)"/>
public override async YarnTask RunLineAsync(LocalizedLine dialogueLine, LineCancellationToken lineCancellationToken)
{
// Get the localized voice over audio clip
AudioClip? voiceOverClip = null;
if (dialogueLine.Asset is AudioClip clip)
{
voiceOverClip = clip;
}
else if (dialogueLine.Asset is IAssetProvider provider && provider.TryGetAsset(out AudioClip? result))
{
voiceOverClip = result;
}
DialogueRunner? dialogueRunner = dialogueLine.Source as DialogueRunner;
if (voiceOverClip == null)
{
Debug.LogError($"Playing voice over failed because the localised line {dialogueLine.TextID} " +
$"either didn't have an asset, or its asset was not an {nameof(AudioClip)}.", gameObject);
if (this.endLineWhenVoiceoverComplete && dialogueRunner != null)
{
// If we didn't get a line, but we were configured to
// advance the line on end, then we should act as though
// we've reached the end of the line now and advance.
dialogueRunner.RequestNextLine();
}
return;
}
if (audioSource.isPlaying)
{
// Usually, this shouldn't happen because the DialogueRunner
// finishes and ends a line first
audioSource.Stop();
}
// If we need to wait before starting playback, do this now
if (waitTimeBeforeLineStart > 0)
{
await YarnTask.Delay(
TimeSpan.FromSeconds(waitTimeBeforeLineStart),
lineCancellationToken.NextContentToken).SuppressCancellationThrow();
}
if (!DialogueRunner.IsInPlaymode)
{
// We left play mode while waiting before starting playback
return;
}
// Start playing the audio.
audioSource.PlayOneShot(voiceOverClip);
// Playback may not begin immediately, so wait until it does (or if
// the line is interrupted.)
await YarnTask.WaitUntil(() => audioSource.isPlaying, lineCancellationToken.NextContentToken).SuppressCancellationThrow();
if (!DialogueRunner.IsInPlaymode)
{
// We left play mode before the audio started playing
return;
}
// Now wait until either the audio source finishes playing, or the
// line is interrupted.
await YarnTask.WaitUntil(() => !audioSource.isPlaying, lineCancellationToken.NextContentToken).SuppressCancellationThrow();
if (!DialogueRunner.IsInPlaymode)
{
// We left play mode while the audio was playing
return;
}
// If the line was interrupted while we were playing, we need to
// wrap up the playback as quickly as we can. We do this here with a
// fade-out to zero over fadeOutTimeOnLineFinish seconds.
if (audioSource.isPlaying && lineCancellationToken.IsNextContentRequested)
{
// Fade out voice over clip
float lerpPosition = 0f;
float volumeFadeStart = audioSource.volume;
while (audioSource.volume != 0)
{
// We'll use unscaled time here, because if time is scaled,
// we might be fading out way too slowly, and that would
// sound extremely strange.
lerpPosition += Time.unscaledDeltaTime / fadeOutTimeOnLineFinish;
audioSource.volume = Mathf.Lerp(volumeFadeStart, 0, lerpPosition);
await YarnTask.Yield();
}
// We're done fading out. Restore our audio volume to its
// original point for the next line.
audioSource.volume = volumeFadeStart;
}
audioSource.Stop();
// We've finished our playback at this point, either by waiting
// normally or by interrupting it with a fadeout. If we weren't
// interrupted, and we have additional time to wait after the audio
// finishes, wait now. (If we were interrupted, we skip this wait,
// because the user has already indicated that they're fine with
// things moving faster than sounds normal.)
if (!lineCancellationToken.IsNextContentRequested && waitTimeAfterLineComplete > 0)
{
await YarnTask.Delay(
TimeSpan.FromSeconds(waitTimeAfterLineComplete),
lineCancellationToken.NextContentToken
).SuppressCancellationThrow();
}
if (!DialogueRunner.IsInPlaymode)
{
// We left play mode while waiting after the line completed
return;
}
if (endLineWhenVoiceoverComplete)
{
if (dialogueRunner == null)
{
Debug.LogError($"Can't end line due to voice over being complete: {nameof(dialogueRunner)} is null", this);
}
else
{
dialogueRunner.RequestNextLine();
}
}
}
/// <inheritdoc />
/// <remarks>
/// Stops any audio if there is still any playing.
/// </remarks>
public override YarnTask OnDialogueCompleteAsync()
{
// just in case we are still playing audio we want it to stop
audioSource.Stop();
return YarnTask.CompletedTask;
}
/// <inheritdoc/>
public override YarnTask OnDialogueStartedAsync()
{
return YarnTask.CompletedTask;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eb87ae987c60e9d4f8346202d271648d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: