You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
469 lines
24 KiB
469 lines
24 KiB
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using UnityEngine.Serialization;
|
|
|
|
namespace UnityEditor.VFX.Block
|
|
{
|
|
[VFXHelpURL("Block-FlipbookPlayer")]
|
|
[VFXInfo(name = "Flipbook Player", category = "FlipBook", synonyms = new []{ "Animation", "Atlas", "Frame", "Rate", "Sheet", "Sprite", "SubUV", "Texture" })]
|
|
class FlipbookPlay : VFXBlock
|
|
{
|
|
public enum Mode
|
|
{
|
|
FrameRate,
|
|
Cycles
|
|
}
|
|
|
|
[VFXSetting, Tooltip("How to control the flipbook animation: setting the frame rate (speed) or the frames to be played (specifying the range and the number of cycles over the particle lifetime).")]
|
|
public Mode mode = Mode.FrameRate;
|
|
|
|
public enum FrameRateMode
|
|
{
|
|
Constant,
|
|
Random,
|
|
OverLifetime,
|
|
BySpeed,
|
|
Custom
|
|
}
|
|
[VFXSetting, Tooltip("Different modes to determine the frame rate.")]
|
|
public FrameRateMode frameRateMode = FrameRateMode.Constant;
|
|
|
|
public enum CyclesMode
|
|
{
|
|
Constant,
|
|
Random,
|
|
RandomFullCycle
|
|
}
|
|
[VFXSetting, Tooltip("How to specify the number of cycles (loops) in the animation.")]
|
|
public CyclesMode cyclesMode = CyclesMode.Constant;
|
|
|
|
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Enable range options for frame rate mode. When turned off, range is the entire flipbook.")]
|
|
public bool useCustomRange = false;
|
|
|
|
public enum AnimationRange
|
|
{
|
|
EntireFlipbook,
|
|
FlipbookRow,
|
|
FlipbookColumn,
|
|
StartEndFrames,
|
|
}
|
|
[VFXSetting, Tooltip("How to specify the range of frames used by the animation."), FormerlySerializedAs("rangeMode")]
|
|
public AnimationRange animationRange = AnimationRange.EntireFlipbook;
|
|
|
|
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Play the animation backwards.")]
|
|
public bool reverse = false;
|
|
|
|
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Clamp the animation to the last frame when the output is using blending between frames.")]
|
|
public bool clampBlending = false;
|
|
|
|
[VFXSetting(VFXSettingAttribute.VisibleFlags.InInspector), Tooltip("Remap the texIndex along each cycle of the animation using a custom curve from 0 to 1.")]
|
|
public bool customCurve = false;
|
|
|
|
private bool needsRandom =>
|
|
(mode == Mode.FrameRate && frameRateMode == FrameRateMode.Random) ||
|
|
(mode == Mode.Cycles && (cyclesMode == CyclesMode.Random || cyclesMode == CyclesMode.RandomFullCycle));
|
|
|
|
private bool needsRange => mode == Mode.Cycles || useCustomRange;
|
|
|
|
private bool needsTexIndexBlend => needsRange && animationRange != AnimationRange.EntireFlipbook;
|
|
|
|
private bool needsFrameBlending => mode == Mode.Cycles && outputHasBlending;
|
|
|
|
private bool outputHasBlending
|
|
{
|
|
get
|
|
{
|
|
var parent = GetParent();
|
|
bool hasBlendFrames = false;
|
|
foreach (var context in parent.outputContexts)
|
|
{
|
|
if (context is VFXAbstractParticleOutput output && output.usesFlipbook)
|
|
{
|
|
hasBlendFrames = output.flipbookHasInterpolation;
|
|
break;
|
|
}
|
|
}
|
|
return hasBlendFrames;
|
|
}
|
|
}
|
|
|
|
public override string name => "Flipbook Player";
|
|
|
|
public override VFXContextType compatibleContexts { get { return VFXContextType.Update; } }
|
|
|
|
public override VFXDataType compatibleData { get { return VFXDataType.Particle; } }
|
|
|
|
protected override IEnumerable<string> filteredOutSettings
|
|
{
|
|
get
|
|
{
|
|
if (mode != Mode.FrameRate)
|
|
{
|
|
yield return nameof(frameRateMode);
|
|
yield return nameof(useCustomRange);
|
|
}
|
|
|
|
if (mode != Mode.Cycles)
|
|
{
|
|
yield return nameof(cyclesMode);
|
|
yield return nameof(customCurve);
|
|
}
|
|
|
|
if (!needsRange)
|
|
{
|
|
yield return nameof(animationRange);
|
|
}
|
|
|
|
if (!needsFrameBlending)
|
|
{
|
|
yield return nameof(clampBlending);
|
|
}
|
|
|
|
foreach (var setting in base.filteredOutSettings)
|
|
yield return setting;
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<VFXAttributeInfo> attributes
|
|
{
|
|
get
|
|
{
|
|
yield return new VFXAttributeInfo(VFXAttribute.TexIndex, VFXAttributeMode.ReadWrite);
|
|
if (needsTexIndexBlend)
|
|
{
|
|
yield return new VFXAttributeInfo(VFXAttribute.TexIndexBlend, VFXAttributeMode.Write);
|
|
}
|
|
if (mode == Mode.Cycles || (mode == Mode.FrameRate && frameRateMode == FrameRateMode.OverLifetime))
|
|
{
|
|
yield return new VFXAttributeInfo(VFXAttribute.Age, VFXAttributeMode.Read);
|
|
yield return new VFXAttributeInfo(VFXAttribute.Lifetime, VFXAttributeMode.Read);
|
|
}
|
|
if (needsRandom)
|
|
{
|
|
yield return new VFXAttributeInfo(VFXAttribute.ParticleId, VFXAttributeMode.Read);
|
|
}
|
|
if (frameRateMode == FrameRateMode.BySpeed)
|
|
{
|
|
yield return new VFXAttributeInfo(VFXAttribute.Velocity, VFXAttributeMode.Read);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override IEnumerable<VFXPropertyWithValue> inputProperties
|
|
{
|
|
get
|
|
{
|
|
switch (mode)
|
|
{
|
|
case Mode.FrameRate:
|
|
switch (frameRateMode)
|
|
{
|
|
case FrameRateMode.Constant:
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(float), "frameRate", new TooltipAttribute("The frame rate of the animation, in frames per second.")), 24.0f);
|
|
break;
|
|
case FrameRateMode.Random:
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(Vector2), "frameRate", new TooltipAttribute("The frame rate of the animation, in frames per second, choosing a random value in the specified range.")), new Vector2(24.0f, 30.0f));
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(uint), "seed", new TooltipAttribute("Random seed.")));
|
|
break;
|
|
case FrameRateMode.OverLifetime:
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), "frameRate", new TooltipAttribute("The frame rate of the animation, in frames per second, from a curve over the lifetime.")), AnimationCurve.Constant(0.0f, 1.0f, 24.0f));
|
|
break;
|
|
case FrameRateMode.BySpeed:
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), "frameRate", new TooltipAttribute("The frame rate of the animation, in frames per second, from a curve depending on the speed.")), AnimationCurve.Constant(0.0f, 1.0f, 24.0f));
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(Vector2), "speedRange", new TooltipAttribute("Min and max speeds to map the current speed value to the normalized curve.")), new Vector2(0.0f, 1.0f));
|
|
break;
|
|
case FrameRateMode.Custom:
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), "frameRate", new TooltipAttribute("The frame rate of the animation, in frames per second, from a curve depending on a custom value.")), AnimationCurve.Constant(0.0f, 1.0f, 24.0f));
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(float), "sampleTime", new TooltipAttribute("Value that controls the framerate.")), 0.0f);
|
|
break;
|
|
}
|
|
break;
|
|
case Mode.Cycles:
|
|
switch (cyclesMode)
|
|
{
|
|
case CyclesMode.Constant:
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(float), "cycles", new TooltipAttribute("Fixed number of cycles of the animation.")), 1.0f);
|
|
break;
|
|
case CyclesMode.Random:
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(Vector2), "cycles", new TooltipAttribute("Number of cycles of the animation, between two values, can be a decimal value.")), new Vector2(1.0f, 3.0f));
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(uint), "seed", new TooltipAttribute("Random seed.")));
|
|
break;
|
|
case CyclesMode.RandomFullCycle:
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(Vector2), "cycles", new TooltipAttribute("Number of cycles of the animation, between two values, clamped to full cycles.")), new Vector2(1, 3));
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(uint), "seed", new TooltipAttribute("Random seed.")));
|
|
break;
|
|
}
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(float), "startOffset", new TooltipAttribute("Offset the texture index for the first frame of the animation.")), 0.0f);
|
|
if (customCurve)
|
|
{
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(AnimationCurve), "customCurve", new TooltipAttribute("Custom curve to remap the animation.")), AnimationCurve.Linear(0.0f, 0.0f, 1.0f, 1.0f));
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (needsRange)
|
|
{
|
|
if (animationRange == AnimationRange.FlipbookRow || animationRange == AnimationRange.FlipbookColumn)
|
|
{
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(int), "index", new TooltipAttribute($"Index of the selected { (animationRange == AnimationRange.FlipbookRow? "row" : "column") }, starting at 0.")), 0);
|
|
}
|
|
else if (animationRange == AnimationRange.StartEndFrames)
|
|
{
|
|
yield return new VFXPropertyWithValue(new VFXProperty(typeof(Vector2), "frameRange", new TooltipAttribute("First and last frames of the animation, both included.")), new Vector2(0, 16));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<VFXNamedExpression> parameters
|
|
{
|
|
get
|
|
{
|
|
// this function gets all the InputProperties members and sends them to the node block, except for speedRange
|
|
VFXExpression speedRangeExp = null;
|
|
VFXExpression flipBookIndexExp = null;
|
|
VFXExpression frameRangeExp = null;
|
|
foreach (var p in GetExpressionsFromSlots(this))
|
|
{
|
|
switch (p.name)
|
|
{
|
|
case "speedRange":
|
|
speedRangeExp = p.exp;
|
|
break;
|
|
case "index":
|
|
flipBookIndexExp = p.exp;
|
|
break;
|
|
case "frameRange":
|
|
frameRangeExp = p.exp;
|
|
break;
|
|
default:
|
|
yield return p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (speedRangeExp != null)
|
|
{
|
|
var speedRangeComponents = (VFXOperatorUtility.ExtractComponents(speedRangeExp) as List<VFXExpression>).ToArray();
|
|
// speedRange.y = 1 / (speedRange.y - speedRange.x)
|
|
speedRangeComponents[1] = VFXOperatorUtility.Reciprocal(speedRangeComponents[1] - speedRangeComponents[0]);
|
|
yield return new VFXNamedExpression(new VFXExpressionCombine(speedRangeComponents), "speedRange");
|
|
}
|
|
|
|
// this statement enables using deltaTime builtin expression in the nodeblock Source.
|
|
yield return new VFXNamedExpression(VFXBuiltInExpression.DeltaTime, "deltaTime");
|
|
|
|
if (needsRandom)
|
|
{
|
|
yield return new VFXNamedExpression(VFXBuiltInExpression.SystemSeed, "systemSeed");
|
|
}
|
|
|
|
if (needsRange)
|
|
{
|
|
VFXExpression flipBookSizeXExp = null;
|
|
VFXExpression flipBookSizeYExp = null;
|
|
|
|
bool needsFlipbook = animationRange == AnimationRange.EntireFlipbook || animationRange == AnimationRange.FlipbookRow || animationRange == AnimationRange.FlipbookColumn;
|
|
if (needsFlipbook)
|
|
{
|
|
var parent = GetParent();
|
|
foreach (var context in parent.outputContexts)
|
|
{
|
|
if (context is VFXAbstractParticleOutput output && output.usesFlipbook)
|
|
{
|
|
foreach (var p in GetExpressionsFromSlots(context))
|
|
{
|
|
if (p.name == "flipBookSize")
|
|
{
|
|
flipBookSizeXExp = p.exp.x;
|
|
flipBookSizeYExp = p.exp.y;
|
|
}
|
|
if (flipBookSizeXExp != null && flipBookSizeYExp != null)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flipBookSizeXExp == null || flipBookSizeYExp == null)
|
|
{
|
|
flipBookSizeXExp = new VFXValue<float>(1);
|
|
flipBookSizeYExp = new VFXValue<float>(1);
|
|
}
|
|
|
|
// FrameRange variable: (startFrame, frameCount, [stride])
|
|
switch (animationRange)
|
|
{
|
|
case AnimationRange.EntireFlipbook:
|
|
yield return new VFXNamedExpression(new VFXExpressionCombine(new VFXValue<float>(0), flipBookSizeXExp * flipBookSizeYExp, new VFXValue<float>(1)), "frameRange");
|
|
break;
|
|
case AnimationRange.FlipbookRow:
|
|
yield return new VFXNamedExpression(new VFXExpressionCombine(new VFXExpressionCastIntToFloat(flipBookIndexExp) * flipBookSizeXExp, flipBookSizeXExp, new VFXValue<float>(1)), "frameRange");
|
|
break;
|
|
case AnimationRange.FlipbookColumn:
|
|
yield return new VFXNamedExpression(new VFXExpressionCombine(new VFXExpressionCastIntToFloat(flipBookIndexExp), flipBookSizeYExp, flipBookSizeXExp), "frameRange");
|
|
break;
|
|
case AnimationRange.StartEndFrames:
|
|
yield return new VFXNamedExpression(new VFXExpressionCombine(frameRangeExp.x, frameRangeExp.y - frameRangeExp.x + new VFXValue<float>(1), new VFXValue<float>(1)), "frameRange");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Source code is the actual code of your nodeblock where you can access properties, attributes and optionally parameters.
|
|
public override string source
|
|
{
|
|
get
|
|
{
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
bool needsFrameBlendingCache = needsFrameBlending;
|
|
|
|
if (needsRange)
|
|
{
|
|
stringBuilder.AppendLine($"float stride = {(animationRange == AnimationRange.FlipbookColumn ? "frameRange.z" : "1.0f")};");
|
|
stringBuilder.AppendLine("float rangeLength = frameRange.y;");
|
|
stringBuilder.AppendLine("float firstFrame = frameRange.x;");
|
|
stringBuilder.AppendLine("float lastFrame = firstFrame + (rangeLength - 1.0f) * stride;");
|
|
if (mode == Mode.Cycles)
|
|
{
|
|
stringBuilder.AppendLine($"float startFrameOffset = fmod({(reverse ? "-" : "")}startOffset, rangeLength) * stride;");
|
|
stringBuilder.AppendLine($"float {(reverse && needsFrameBlendingCache ? "endFrame" : "startFrame")} = startFrameOffset >= 0 ? firstFrame + startFrameOffset : lastFrame + startFrameOffset + stride;");
|
|
stringBuilder.AppendLine($"float {(reverse && needsFrameBlendingCache ? "startFrame" : "endFrame")} = startFrameOffset > 0 ? firstFrame + startFrameOffset - stride : lastFrame + startFrameOffset;");
|
|
|
|
stringBuilder.AppendLine("\nif (age <= 0.0f) texIndex = startFrame;");
|
|
}
|
|
if (animationRange == AnimationRange.FlipbookColumn)
|
|
{
|
|
stringBuilder.AppendLine("\nfloat previousFrame = floor(texIndex);");
|
|
}
|
|
}
|
|
|
|
string framerate = "";
|
|
string cycleCount = "";
|
|
switch (mode)
|
|
{
|
|
case Mode.FrameRate:
|
|
switch (frameRateMode)
|
|
{
|
|
case FrameRateMode.Constant: framerate = "frameRate"; break;
|
|
case FrameRateMode.Random: framerate = "FIXED_RAND(seed) * (frameRate.y - frameRate.x) + frameRate.x"; break;
|
|
case FrameRateMode.OverLifetime: framerate = "SampleCurve(frameRate, age/lifetime)"; break;
|
|
case FrameRateMode.BySpeed: framerate = "SampleCurve(frameRate, saturate((length(velocity) - speedRange.x) * speedRange.y))"; break;
|
|
case FrameRateMode.Custom: framerate = "SampleCurve(frameRate, saturate(sampleTime))"; break;
|
|
}
|
|
break;
|
|
case Mode.Cycles:
|
|
switch (cyclesMode)
|
|
{
|
|
case CyclesMode.Constant: cycleCount = "cycles"; break;
|
|
case CyclesMode.Random: cycleCount = "FIXED_RAND(seed) * (cycles.y - cycles.x) + cycles.x"; break;
|
|
case CyclesMode.RandomFullCycle: cycleCount = "FIXED_RAND_INT(seed) % (int)(cycles.y - cycles.x + 1) + cycles.x"; break;
|
|
}
|
|
stringBuilder.AppendLine($"float cycleCount = {cycleCount};");
|
|
framerate = "cycleCount * rangeLength / lifetime";
|
|
break;
|
|
}
|
|
|
|
if (mode == Mode.Cycles && customCurve)
|
|
{
|
|
stringBuilder.AppendLine($"\nfloat framerateValue = {framerate};\ntexIndex += RemapCurve(customCurve, age * framerateValue / rangeLength, framerateValue * {(reverse ? "-" : "")}deltaTime);");
|
|
}
|
|
else
|
|
{
|
|
stringBuilder.AppendLine($"\nfloat framerateValue = {framerate};\ntexIndex += framerateValue * {(reverse ? "-" : "")}deltaTime;");
|
|
}
|
|
|
|
|
|
if (needsRange)
|
|
{
|
|
if (animationRange != AnimationRange.FlipbookColumn)
|
|
{
|
|
stringBuilder.AppendLine("\ntexIndex = fmod(texIndex - firstFrame + rangeLength, rangeLength) + firstFrame;");
|
|
if (animationRange != AnimationRange.EntireFlipbook)
|
|
{
|
|
stringBuilder.AppendLine("float currentFrame = floor(texIndex);");
|
|
stringBuilder.AppendLine("\ntexIndexBlend = texIndex > lastFrame ? firstFrame - currentFrame : 1.0f;");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
stringBuilder.AppendLine("\nfloat currentFrame = floor(texIndex);");
|
|
stringBuilder.AppendLine("float frameOffset = texIndex - currentFrame;"); // offset always 0 or positive
|
|
stringBuilder.AppendLine("float previousRow = floor(previousFrame / stride);");
|
|
stringBuilder.AppendLine("float advanceFrames = currentFrame - previousFrame;");
|
|
stringBuilder.AppendLine("texIndex = fmod((previousRow + advanceFrames) + rangeLength, rangeLength) * stride + firstFrame + frameOffset;");
|
|
stringBuilder.AppendLine("\ntexIndexBlend = stride;");
|
|
}
|
|
}
|
|
|
|
if (mode == Mode.Cycles && customCurve && (!needsFrameBlendingCache || clampBlending))
|
|
{
|
|
stringBuilder.AppendLine("\nfloat currentCycle = cycleCount * age / lifetime;");
|
|
stringBuilder.AppendLine($"if (currentCycle > cycleCount - 0.9f) texIndex = {(reverse ? "max" : "min")}(texIndex, endFrame);");
|
|
}
|
|
else if (needsFrameBlendingCache && clampBlending)
|
|
{
|
|
stringBuilder.AppendLine("\nfloat halfFrame = 0.5f / framerateValue;");
|
|
stringBuilder.AppendLine("if (age <= halfFrame) texIndex = startFrame;");
|
|
stringBuilder.AppendLine("if ((lifetime - age) <= halfFrame) texIndex = endFrame;");
|
|
}
|
|
|
|
return stringBuilder.ToString();
|
|
}
|
|
}
|
|
|
|
internal sealed override void GenerateErrors(VFXErrorReporter report)
|
|
{
|
|
base.GenerateErrors(report);
|
|
|
|
var data = GetData();// as VFXDataParticle;
|
|
if (mode == Mode.Cycles && data != null && !data.IsAttributeStored(VFXAttribute.Alive))
|
|
{
|
|
report.RegisterError("FlipbookAnimLengthUnavailable", VFXErrorType.Warning, "Cycles mode only works with particles that have the Lifetime attribute", this);
|
|
}
|
|
|
|
if (needsRange && (animationRange == AnimationRange.EntireFlipbook || animationRange == AnimationRange.FlipbookRow || animationRange == AnimationRange.FlipbookColumn))
|
|
{
|
|
var parent = GetParent();
|
|
bool usesFlipbook = false;
|
|
foreach (var context in parent.outputContexts)
|
|
{
|
|
if (context is VFXAbstractParticleOutput output && output.usesFlipbook)
|
|
{
|
|
usesFlipbook = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!usesFlipbook)
|
|
{
|
|
report.RegisterError("NoFlipbookOutput", VFXErrorType.Warning, "Flipbook mode requires an output with Uv Mode set to Flipbook", this);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Sanitize(int version)
|
|
{
|
|
if (version < 17)
|
|
{
|
|
switch ((int)mode)
|
|
{
|
|
case 0: // Constant
|
|
mode = Mode.FrameRate;
|
|
frameRateMode = FrameRateMode.Constant;
|
|
break;
|
|
case 1: // CurveOverLife
|
|
mode = Mode.FrameRate;
|
|
frameRateMode = FrameRateMode.OverLifetime;
|
|
break;
|
|
}
|
|
}
|
|
base.Sanitize(version);
|
|
}
|
|
}
|
|
}
|