同步
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8d13ab9da89a45a0a633f08d7ff0f4e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Common Tags.asset
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Common Tags.asset
LFS
Normal file
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6df01bf68e01943908f9e625c3b0bbc6
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9d82a904b13745b4af3b2f101940be6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Packages/dev.yarnspinner.unity/Runtime/Views/Effects.cs
Normal file
50
Packages/dev.yarnspinner.unity/Runtime/Views/Effects.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/dev.yarnspinner.unity/Runtime/Views/Effects.cs.meta
Normal file
11
Packages/dev.yarnspinner.unity/Runtime/Views/Effects.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a287d97ec765440a18661f2967524339
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
790
Packages/dev.yarnspinner.unity/Runtime/Views/LineAdvancer.cs
Normal file
790
Packages/dev.yarnspinner.unity/Runtime/Views/LineAdvancer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61503eb6a998a4af68e0dcbec4aad8cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
411
Packages/dev.yarnspinner.unity/Runtime/Views/LinePresenter.cs
Normal file
411
Packages/dev.yarnspinner.unity/Runtime/Views/LinePresenter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7dd50f871a60b4bbe88a71d308b2613f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fccdb370bbd5a4312a5e44af933974be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
179
Packages/dev.yarnspinner.unity/Runtime/Views/MarkupPalette.cs
Normal file
179
Packages/dev.yarnspinner.unity/Runtime/Views/MarkupPalette.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
174
Packages/dev.yarnspinner.unity/Runtime/Views/OptionItem.cs
Normal file
174
Packages/dev.yarnspinner.unity/Runtime/Views/OptionItem.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c72af21cd06074f41a2537a712c24213
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
391
Packages/dev.yarnspinner.unity/Runtime/Views/OptionsPresenter.cs
Normal file
391
Packages/dev.yarnspinner.unity/Runtime/Views/OptionsPresenter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0cd0d0412ea34813959f8ddaf02a6eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a968ef973edc54520b248fa3113ae925
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 027fadfcefed14f5c9cc781a2bb7e12a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c71c1c5641d344603b2c23af06859f62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dbc98d85e6dc4263b6bf71fc0a6bdd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0901fdc59460943b682c5c4000c44a0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Packages/dev.yarnspinner.unity/Runtime/Views/TMPShim.cs
Normal file
45
Packages/dev.yarnspinner.unity/Runtime/Views/TMPShim.cs
Normal 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
|
||||
}
|
||||
11
Packages/dev.yarnspinner.unity/Runtime/Views/TMPShim.cs.meta
Normal file
11
Packages/dev.yarnspinner.unity/Runtime/Views/TMPShim.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecc7b5e10ccaa4c9580ec8902c2c68b8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 250a919d40760494ebcff0e3ddb596d0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/CharacterBubble.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/CharacterBubble.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/ContinueArrow.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/ContinueArrow.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/Divider.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/Divider.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/LastLineBalloon.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/LastLineBalloon.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/LastLineSeparator.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/LastLineSeparator.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/NextLineIndicator.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/NextLineIndicator.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/OptionDot.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/OptionDot.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/OptionNotSelected.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/OptionNotSelected.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/OptionSelected.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/OptionSelected.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/Selector-Off.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/Selector-Off.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/Selector-On.png
LFS
Normal file
BIN
Packages/dev.yarnspinner.unity/Runtime/Views/Textures/Selector-On.png
LFS
Normal file
Binary file not shown.
@@ -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:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dbbcbd5b018b4eca92eb51a7bf64a05
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6934c733ecff4af39599e521eb5cc90
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd0927d5964ad4be8b899682f6448a66
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cc6b37948eec40d5bd9e7e91157286c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c27d40c62a2864f38967e66e1ff3ce44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb87ae987c60e9d4f8346202d271648d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user