added yarn spinner package
This commit is contained in:
+63
@@ -0,0 +1,63 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
#nullable enable
|
||||
namespace Yarn.Unity.Addons.Snaaake
|
||||
{
|
||||
/// <summary>
|
||||
/// Randomly varies the <see cref="Image.fillAmount"/> property of an <see
|
||||
/// cref="Image"/> over time, at a fixed frame rate.
|
||||
/// </summary>
|
||||
public class RandomImageFill : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum amount to fill.
|
||||
/// </summary>
|
||||
public float MinFill = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount to fill.
|
||||
/// </summary>
|
||||
public float MaxFill = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The degree to which the fill amount will change each frame.
|
||||
/// </summary>
|
||||
public float Speed = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// The rate at which the fill amount will change.
|
||||
/// </summary>
|
||||
public float FrameRate = 8f;
|
||||
|
||||
/// <summary>
|
||||
/// The image to change the fill amount of.
|
||||
/// </summary>
|
||||
public Image? Image;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (Image == null)
|
||||
{
|
||||
// No image, so nothing to update.
|
||||
return;
|
||||
}
|
||||
|
||||
var t = Time.time;
|
||||
|
||||
// Snap time to framerate
|
||||
t *= FrameRate;
|
||||
t = Mathf.FloorToInt(t);
|
||||
t /= FrameRate;
|
||||
|
||||
// Increase speed
|
||||
t *= Speed;
|
||||
|
||||
// Evaluate the noise at our calculated point and update the image's
|
||||
// fill amount
|
||||
var noise = Mathf.PerlinNoise1D(t);
|
||||
var fill = Mathf.Lerp(MinFill, MaxFill, noise);
|
||||
Image.fillAmount = fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d110ca3d7bebb426d8cb287ecaac4c99
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267061
|
||||
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
|
||||
packageVersion: 3.2.8
|
||||
assetPath: Packages/dev.yarnspinner.unity.addons.snaaake/Runtime/Scripts/RandomImageFill.cs
|
||||
uploadId: 968678
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Yarn;
|
||||
using Yarn.Unity;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity.Addons.Snaaake
|
||||
{
|
||||
/// <summary>
|
||||
/// A basic dialogue presenter that shows the line text in a TextMeshPro
|
||||
/// text view.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This presenter waits until the line has been externally cancelled before
|
||||
/// completing. It expects to be used alongside other presenters that signal
|
||||
/// that the line has been cancelled, like <see cref="VoiceOverPresenter"/>.
|
||||
/// </remarks>
|
||||
public class SubtitlePresenter : DialoguePresenterBase
|
||||
{
|
||||
|
||||
public TMPro.TMP_Text? text;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (text != null)
|
||||
{
|
||||
text.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override async YarnTask OnDialogueStartedAsync() { }
|
||||
|
||||
public override async YarnTask OnDialogueCompleteAsync() { }
|
||||
|
||||
public override async YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
text.enabled = true;
|
||||
|
||||
text.text = line.TextWithoutCharacterName.Text;
|
||||
|
||||
// Wait until the line has been ended by someone else
|
||||
await YarnTask.WaitUntilCanceled(token.NextContentToken);
|
||||
|
||||
text.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 040745bbc9c4a429d911037da4556ee9
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267061
|
||||
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
|
||||
packageVersion: 3.2.8
|
||||
assetPath: Packages/dev.yarnspinner.unity.addons.snaaake/Runtime/Scripts/SubtitlePresenter.cs
|
||||
uploadId: 968678
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Yarn.Unity.Addons.Snaaake
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores a list of sprites associated with a character in the Snaaake
|
||||
/// dialogue presenter.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Yarn Spinner/Snaaake/Talking Head Character")]
|
||||
public class TalkingHeadCharacter : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of sprites for the character. The first sprite will be used
|
||||
/// as the 'idle' pose, when the character is not talking.
|
||||
/// </summary>
|
||||
public List<Sprite> sprites = new();
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d31af3bd6f764e14af50289644aa9ac
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267061
|
||||
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
|
||||
packageVersion: 3.2.8
|
||||
assetPath: Packages/dev.yarnspinner.unity.addons.snaaake/Runtime/Scripts/TalkingHeadCharacter.cs
|
||||
uploadId: 968678
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity.Addons.Snaaake
|
||||
{
|
||||
/// <summary>
|
||||
/// A view that presents an animated talking head in an <see cref="Image"/>,
|
||||
/// driven by an <see cref="Animator"/>.
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
public class TalkingHeadCharacterView : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Image"/> to show the sprite in.
|
||||
/// </summary>
|
||||
[SerializeField] Image? image;
|
||||
|
||||
/// <summary>
|
||||
/// The character that this view is presenting.
|
||||
/// </summary>
|
||||
private TalkingHeadCharacter? currentCharacter;
|
||||
|
||||
[Min(0.001f)]
|
||||
public float FramesPerSecond = 4;
|
||||
|
||||
public bool Talking { get; set; }
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (this.image == null && TryGetComponent<Image>(out var image))
|
||||
{
|
||||
this.image = image;
|
||||
}
|
||||
}
|
||||
|
||||
public TalkingHeadCharacter? CurrentCharacter
|
||||
{
|
||||
get => currentCharacter;
|
||||
set
|
||||
{
|
||||
currentCharacter = value;
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentCharacter != null && currentCharacter.sprites.Count > 0)
|
||||
{
|
||||
image.sprite = currentCharacter.sprites[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
image.sprite = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _currentFrame = 0;
|
||||
private int CurrentFrame
|
||||
{
|
||||
get => _currentFrame;
|
||||
set
|
||||
{
|
||||
if (this.currentCharacter == null || this.currentCharacter.sprites.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentFrame = value;
|
||||
|
||||
if (this.image == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ping-pong the actual index of the sprite to use based on our
|
||||
// frame count
|
||||
var i = PingPong(CurrentFrame, currentCharacter.sprites.Count - 1);
|
||||
var sprite = this.currentCharacter.sprites[i];
|
||||
|
||||
this.image.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
private static int PingPong(int input, int max)
|
||||
{
|
||||
|
||||
return Mathf.Abs(((input + max) % (max * 2)) - max);
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
RunAnimation().Forget();
|
||||
}
|
||||
|
||||
public async YarnTask RunAnimation()
|
||||
{
|
||||
var destroyToken = this.destroyCancellationToken;
|
||||
while (destroyToken.IsCancellationRequested == false)
|
||||
{
|
||||
if (!Talking)
|
||||
{
|
||||
await YarnTask.Yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start the talking animation. Stop when we're no longer
|
||||
// talking AND we've reached the end of a cycle.
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
if (currentCharacter == null || currentCharacter.sprites.Count == 0)
|
||||
{
|
||||
// No character to show; it must have changed.
|
||||
// Stop looping.
|
||||
break;
|
||||
}
|
||||
|
||||
// Show the next frame, and wait until it's time to
|
||||
// show the next.
|
||||
CurrentFrame += 1;
|
||||
|
||||
await YarnTask.Delay(System.TimeSpan.FromSeconds(1.0f / FramesPerSecond), destroyToken);
|
||||
|
||||
} while (Talking || CurrentFrame % (2 * (currentCharacter.sprites.Count - 1)) != 0);
|
||||
}
|
||||
catch (System.OperationCanceledException)
|
||||
{
|
||||
// We must have been destroyed, so stop immediately
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5189d1ee67a4c4eb8a261f0838a93343
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267061
|
||||
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
|
||||
packageVersion: 3.2.8
|
||||
assetPath: Packages/dev.yarnspinner.unity.addons.snaaake/Runtime/Scripts/TalkingHeadCharacterView.cs
|
||||
uploadId: 968678
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Yarn.Unity.Addons.Snaaake
|
||||
{
|
||||
public class TalkingHeadDialoguePresenter : Yarn.Unity.DialoguePresenterBase
|
||||
{
|
||||
[Header("Audio")]
|
||||
/// <summary>
|
||||
/// The audio source to monitor.
|
||||
/// </summary>
|
||||
public AudioSource? audioSource;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The channel in <see cref="audioSource"/> to monitor.
|
||||
/// </summary>
|
||||
public int sampleChannel = 0;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary for mapping names to animators that present the characters,
|
||||
/// </summary>
|
||||
public Yarn.Unity.SerializableDictionary<string, TalkingHeadCharacterView> characterSlots = new();
|
||||
|
||||
public Yarn.Unity.SerializableDictionary<string, TalkingHeadCharacter> characters = new();
|
||||
|
||||
[Range(0f, 1f)]
|
||||
public float threshold = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// The current animator whose parameters we are changing.
|
||||
/// </summary>
|
||||
private TalkingHeadCharacterView? activeView = null;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A buffer containing audio samples collected from <see
|
||||
/// cref="audioSource"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// (This is kept in the class because we want to reuse the same buffer,
|
||||
/// rather than allocate every time we want to sample it.)
|
||||
/// </remarks>
|
||||
private float[] sampleBuffer = new float[512];
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time, in milliseconds, between samples of the audio source.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We don't need to be super precise with monitoring the audio source, so
|
||||
/// there's no need to measure every frame. Instead, we'll measure at a fix
|
||||
/// number of milliseconds between samples.
|
||||
/// </remarks>
|
||||
private readonly int timeBetweenSamplesMilliseconds = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time remaining until we next sample.
|
||||
/// </summary>
|
||||
private float timeUntilNextSample = 0;
|
||||
|
||||
protected void Start()
|
||||
{
|
||||
if (audioSource)
|
||||
{
|
||||
// Unity creates the internal buffers and starts recording the first
|
||||
// time this is called, so call it immediately on start
|
||||
audioSource.GetOutputData(sampleBuffer, sampleChannel);
|
||||
}
|
||||
}
|
||||
|
||||
protected void Update()
|
||||
{
|
||||
|
||||
// Ensure that we have the components that we need
|
||||
if (audioSource == null || activeView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Are we due for another audio source sample? If not, wait.
|
||||
if (timeUntilNextSample > 0)
|
||||
{
|
||||
timeUntilNextSample -= Time.deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// Read sample data out of the audio source.
|
||||
|
||||
audioSource.GetOutputData(sampleBuffer, sampleChannel);
|
||||
|
||||
// Determine the highest peak level that we hit.
|
||||
var maxPower = 0f;
|
||||
|
||||
for (int i = 0; i < sampleBuffer.Length; i++)
|
||||
{
|
||||
maxPower = Mathf.Max(Mathf.Abs(maxPower), sampleBuffer[i]);
|
||||
}
|
||||
|
||||
// Set the parameter to whether or not we have a loud enough signal!
|
||||
activeView.Talking = maxPower > threshold;
|
||||
|
||||
|
||||
// Reset our clock.
|
||||
timeUntilNextSample = timeBetweenSamplesMilliseconds / 1000f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Associates a character name with a slot.
|
||||
/// </summary>
|
||||
/// <param name="characterName">The character name to associate.</param>
|
||||
/// <param name="slotName">The name of the slot to associate the character
|
||||
/// with.</param>
|
||||
[YarnCommand("set_slot")]
|
||||
public static void SetCharacterToSlot(string characterName, string slotName)
|
||||
{
|
||||
var presenter = FindAnyObjectByType<TalkingHeadDialoguePresenter?>();
|
||||
if (presenter == null)
|
||||
{
|
||||
Debug.LogWarning($"Can't associate {characterName} with slot {slotName}: no {nameof(TalkingHeadDialoguePresenter)}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (presenter.characters.TryGetValue(characterName, out var characterData) == false)
|
||||
{
|
||||
Debug.LogWarning($"Can't associate {characterName} with slot {slotName}: no known character {characterName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (presenter.characterSlots.TryGetValue(slotName, out var slot) == false)
|
||||
{
|
||||
Debug.LogWarning($"Can't associate {characterName} with slot {slotName}: no slot named {slotName}");
|
||||
return;
|
||||
}
|
||||
|
||||
slot.CurrentCharacter = characterData;
|
||||
}
|
||||
|
||||
public override async YarnTask RunLineAsync(LocalizedLine line, LineCancellationToken token)
|
||||
{
|
||||
// Ensure that we have a character, a slot name for that character, and
|
||||
// an animator for that slot name.
|
||||
var characterName = line.CharacterName;
|
||||
if (characterName == null)
|
||||
{
|
||||
Debug.LogWarning($"Can't run talking animation for line {line.TextID} '{line.Text}': line has no character name");
|
||||
return;
|
||||
}
|
||||
|
||||
if (characters.TryGetValue(characterName, out var character) == false)
|
||||
{
|
||||
Debug.LogWarning($"Can't run talking animation for line {line.TextID} '{line.Text}': {characterName} is not a known character");
|
||||
return;
|
||||
}
|
||||
|
||||
TalkingHeadCharacterView? slot = null;
|
||||
|
||||
// Find the slot that contains this character
|
||||
foreach (var s in characterSlots.Values)
|
||||
{
|
||||
if (s.CurrentCharacter == character)
|
||||
{
|
||||
slot = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot == null)
|
||||
{
|
||||
// We don't know which slot is showing this character
|
||||
Debug.LogWarning($"Can't run talking animation for line {line.TextID} '{line.Text}': {characterName} is not associated with a slot");
|
||||
return;
|
||||
}
|
||||
|
||||
// The animator we're working with is this one we've arrived at
|
||||
activeView = slot;
|
||||
|
||||
// Wait until the line is finished
|
||||
await YarnTask.WaitUntilCanceled(token.NextContentToken);
|
||||
|
||||
if (slot != null)
|
||||
{
|
||||
// Turn off the animation just in case the audio level is currently
|
||||
// over the threshold
|
||||
slot.Talking = false;
|
||||
}
|
||||
|
||||
// Tidy up
|
||||
activeView = null;
|
||||
}
|
||||
|
||||
public override YarnTask OnDialogueStartedAsync()
|
||||
{
|
||||
// Clear all characters from their slots
|
||||
foreach (var kv in characterSlots)
|
||||
{
|
||||
kv.Value.CurrentCharacter = null;
|
||||
}
|
||||
|
||||
return YarnTask.CompletedTask;
|
||||
}
|
||||
|
||||
public override YarnTask OnDialogueCompleteAsync()
|
||||
{
|
||||
return YarnTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7948f050ae2d45bca291d4149dd41b3
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 267061
|
||||
packageName: Yarn Spinner+ for Unity, the friendly dialogue and narrative tool
|
||||
packageVersion: 3.2.8
|
||||
assetPath: Packages/dev.yarnspinner.unity.addons.snaaake/Runtime/Scripts/TalkingHeadDialoguePresenter.cs
|
||||
uploadId: 968678
|
||||
Reference in New Issue
Block a user