478 lines
22 KiB
C#
478 lines
22 KiB
C#
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
|
|
|
using System;
|
|
using UnityEngine;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace Animancer
|
|
{
|
|
/// <summary>
|
|
/// Bitwise flags used to determine which warnings Animancer should give.
|
|
/// <para></para>
|
|
/// <strong>These warnings are all optional</strong>.
|
|
/// Feel free to disable any of them if you understand the <em>potential</em> issues they're referring to.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// All warnings are enabled by default, but are compiled out of runtime builds (except development builds).
|
|
/// <para></para>
|
|
/// You can manually disable warnings using the <c>AnimancerSettings</c> asset
|
|
/// or the Animancer Settings panel in the Animancer Tools Window (<c>Window/Animation/Animancer Tools</c>).
|
|
/// <para></para>
|
|
/// <strong>Example:</strong>
|
|
/// You can put a method like this in any class to disable whatever warnings you don't want on startup:
|
|
/// <para></para><code>
|
|
/// #if UNITY_ASSERTIONS
|
|
/// [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
/// private static void DisableAnimancerWarnings()
|
|
/// {
|
|
/// Animancer.OptionalWarning.ProOnly.Disable();
|
|
///
|
|
/// // You could disable OptionalWarning.All, but that's not recommended for obvious reasons.
|
|
/// }
|
|
/// #endif
|
|
/// </code></remarks>
|
|
///
|
|
/// https://kybernetik.com.au/animancer/api/Animancer/OptionalWarning
|
|
///
|
|
[Flags]
|
|
public enum OptionalWarning
|
|
{
|
|
/// <summary><c>default</c></summary>
|
|
None = 0,
|
|
|
|
/// <summary>
|
|
/// A <see href="https://kybernetik.com.au/animancer/docs/introduction/features">Pro-Only Feature</see>
|
|
/// has been used in <see href="https://kybernetik.com.au/animancer/redirect/lite">Animancer Lite</see>.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// Some <see href="https://kybernetik.com.au/animancer/docs/introduction/features">Features</see>
|
|
/// are only available in <see href="https://kybernetik.com.au/animancer/redirect/pro">Animancer Pro</see>.
|
|
/// <para></para>
|
|
/// <see href="https://kybernetik.com.au/animancer/redirect/lite">Animancer Lite</see>
|
|
/// allows you to try out those features in the Unity Editor and gives this warning the
|
|
/// first time each one is used to inform you that they will not work in runtime builds.
|
|
/// </remarks>
|
|
ProOnly = 1 << 0,
|
|
|
|
/// <summary>
|
|
/// An <see cref="AnimancerComponent.Graph"/> is being initialized
|
|
/// during a type of GUI event that isn't supposed to cause side effects.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <see cref="EventType.Layout"/> and <see cref="EventType.Repaint"/>
|
|
/// should display the current details of things, but they should not modify things.
|
|
/// </remarks>
|
|
CreateGraphDuringGuiEvent = 1 << 1,
|
|
|
|
/// <summary>
|
|
/// The <see cref="AnimancerComponent.Animator"/> is disabled so Animancer won't be able to play animations.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// The <see cref="Animator"/> doesn't need an Animator Controller,
|
|
/// it just needs to be enabled via the checkbox in the Inspector
|
|
/// or by setting <c>animancerComponent.Animator.enabled = true;</c> in code.
|
|
/// </remarks>
|
|
AnimatorDisabled = 1 << 2,
|
|
|
|
/// <summary>
|
|
/// An <see cref="Animator.runtimeAnimatorController"/> is assigned
|
|
/// but the Rig is Humanoid so it can't be blended with Animancer.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/animator-controllers#native">Native</see>
|
|
/// Animator Controllers can blend with Animancer on Generic Rigs, but not on Humanoid Rigs
|
|
/// (you can swap back and forth between the Animator Controller and Animancer,
|
|
/// but it won't smoothly blend between them).
|
|
/// <para></para>
|
|
/// If you don't intend to blend between them, you can just disable this warning.
|
|
/// </remarks>
|
|
NativeControllerHumanoid = 1 << 3,
|
|
|
|
/// <summary>
|
|
/// An <see cref="Animator.runtimeAnimatorController"/> is assigned while also using a
|
|
/// <see cref="HybridAnimancerComponent"/>.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// Either assign the <see cref="Animator.runtimeAnimatorController"/> to use it as a Native Animator
|
|
/// Controller or assign the <see cref="HybridAnimancerComponent.Controller"/> to use it as a Hybrid Animator
|
|
/// Controller. The differences are explained on the
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/animator-controllers">Animator Controllers</see>
|
|
/// page.
|
|
/// <para></para>
|
|
/// It is possible to use both, but it usually only happens when misunderstanding how the system works.
|
|
/// If you do want both, just disable this warning.
|
|
/// </remarks>
|
|
NativeControllerHybrid = 1 << 4,
|
|
|
|
/// <summary>
|
|
/// An <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">End Event</see>
|
|
/// didn't actually end the animation.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// Animancer doesn't automatically do anything during an End Event
|
|
/// so it's up to you to end the animation (usually by playing something else).
|
|
/// <para></para>
|
|
/// This warning is given when the event isn't used to stop the animation that caused it
|
|
/// (usually by playing something else). This often indicates that the event hasn't been
|
|
/// configured correctly, however it is sometimes intentional such as if the event doesn't
|
|
/// immediately stop the animation but sets a flag to indicate the animation has ended for
|
|
/// another system to act on at a later time. In that case this warning should be disabled.
|
|
/// <para></para>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">End Events</see>
|
|
/// are triggered every frame after their time has passed, so in this case it might be
|
|
/// necessary to clear the event or simply use a regular
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Event</see>.
|
|
/// </remarks>
|
|
EndEventInterrupt = 1 << 5,
|
|
|
|
/// <summary>
|
|
/// An <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Event</see>
|
|
/// triggered by one character was used to play an animation on a different character.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// This most commonly happens when a Transition is shared by multiple characters and they
|
|
/// all register their own callbacks to its events which leads to those events controlling
|
|
/// the wrong character. The
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer/shared">Shared Events</see>
|
|
/// page explains various ways this issue can be avoided.
|
|
/// </remarks>
|
|
EventPlayMismatch = 1 << 6,
|
|
|
|
/// <summary>
|
|
/// An <see cref="AnimancerEvent"/> that does nothing was invoked.
|
|
/// Most likely it was not configured correctly.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// Unused events should be removed to avoid wasting performance checking and invoking them.
|
|
/// </remarks>
|
|
UselessEvent = 1 << 7,
|
|
|
|
/// <summary>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Events</see>
|
|
/// are being used on a state which does not properly support them so they might not work as intended.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Events</see> on a
|
|
/// <see cref="ControllerState"/> will be triggered based on its <see cref="AnimancerState.NormalizedTime"/>,
|
|
/// which comes from the current state of its Animator Controller regardless of which state that may be.
|
|
/// <para></para>
|
|
/// If you intend for the event to be associated with a specific state inside the Animator Controller,
|
|
/// you need to use Unity's regular
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animation">Animation Events</see>
|
|
/// instead.
|
|
/// <para></para>
|
|
/// But if you intend the event to be triggered by any state inside the Animator Controller,
|
|
/// then you can simply disable this warning.
|
|
/// </remarks>
|
|
UnsupportedEvents = 1 << 8,
|
|
|
|
/// <summary>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/ik">Inverse Kinematics</see>
|
|
/// cannot be dynamically enabled on some
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/states">State</see> types.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// To use IK on a <see cref="ControllerState"/>
|
|
/// you must instead enable it on the desired layer inside the Animator Controller.
|
|
/// <para></para>
|
|
/// IK is not supported by <see cref="PlayableAssetState"/>.
|
|
/// <para></para>
|
|
/// Setting <see cref="AnimancerNode.ApplyAnimatorIK"/> on such a state will simply do nothing,
|
|
/// so feel free to disable this warning if you are enabling IK on states without checking their type.
|
|
/// </remarks>
|
|
UnsupportedIK = 1 << 9,
|
|
|
|
/// <summary>
|
|
/// A Mixer State is being initialized with its <see cref="AnimancerNode.ChildCount"/> <= 1.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// The purpose of a mixer is to mix multiple child states
|
|
/// so you are probably initializing it with incorrect parameters.
|
|
/// <para></para>
|
|
/// A mixer with only one child will simply play that child,
|
|
/// so feel free to disable this warning if that's what you intend to do.
|
|
/// </remarks>
|
|
MixerMinChildren = 1 << 10,
|
|
|
|
/// <summary>
|
|
/// A Mixer State is synchronizing a child with <see cref="AnimancerState.Length"/> = 0.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// Synchronization is based on the <see cref="AnimancerState.NormalizedTime"/>
|
|
/// which can't be calculated if the <see cref="AnimancerState.Length"/> is 0.
|
|
/// <para></para>
|
|
/// Some state types can change their <see cref="AnimancerState.Length"/>,
|
|
/// in which case you can just disable this warning.
|
|
/// But otherwise, the indicated state should not be added to the synchronization list.
|
|
/// </remarks>
|
|
MixerSynchronizeZeroLength = 1 << 11,
|
|
|
|
/// <summary>
|
|
/// When a transition with a non-zero <see cref="ITransition.FadeDuration"/>
|
|
/// creates a state, that state will log this warning if it's ever played
|
|
/// without a fade duration.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This helps identify situations where a state is accidentally played directly
|
|
/// where the transition should be played instead to allow it to apply its fade
|
|
/// and any other details.
|
|
/// </remarks>
|
|
ExpectFade = 1 << 12,
|
|
|
|
/// <summary>
|
|
/// A <see href="https://kybernetik.com.au/animancer/docs/manual/blending/fading/custom">Custom Easing</see>
|
|
/// is being started but its weight calculation does not go from 0 to 1.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// The <see cref="FadeGroup.Easing"/> method is expected to return 0 when the parameter is 0 and
|
|
/// 1 when the parameter is 1. It can do anything you want with other values,
|
|
/// but starting or ending at different values will likely lead to undesirable results.
|
|
/// <para></para>
|
|
/// If your <see cref="FadeGroup.Easing"/> method is expensive you could disable this warning to save
|
|
/// some performance, but violating the above guidelines is not recommended.
|
|
/// </remarks>
|
|
FadeEasingBounds = 1 << 13,
|
|
|
|
/// <summary>
|
|
/// The <see cref="Animator.speed"/> property does not affect Animancer.
|
|
/// Use <see cref="AnimancerGraph.Speed"/> instead.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// The <see cref="Animator.speed"/> property only works with Animator Controllers but does not affect the
|
|
/// Playables API so Animancer has its own <see cref="AnimancerGraph.Speed"/> property.
|
|
/// </remarks>
|
|
AnimatorSpeed = 1 << 14,
|
|
|
|
/// <summary>
|
|
/// An <see cref="AnimancerNodeBase.Graph"/> is null during finalization (garbage collection).
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// This probably means that node was never used for anything and should not have been created.
|
|
/// <para></para>
|
|
/// This warning can be prevented for a specific node by calling <see cref="AnimancerNodeBase.MarkAsUsed"/>.
|
|
/// <para></para>
|
|
/// To minimise the performance cost of checking this warning, it does not capture the stack trace of the
|
|
/// node's creation by default. However, you can enable <see cref="AnimancerNode.TraceConstructor"/> on startup
|
|
/// so that it can include the stack trace in the warning message for any nodes that end up being unused.
|
|
/// </remarks>
|
|
UnusedNode = 1 << 15,
|
|
|
|
/// <summary>
|
|
/// <see cref="PlayableAssetState.InitializeBindings"/> is trying to bind to the same <see cref="Animator"/>
|
|
/// that is being used by Animancer.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Doing this will replace Animancer's output so its animations would not work anymore.
|
|
/// </remarks>
|
|
PlayableAssetAnimatorBinding = 1 << 16,
|
|
|
|
/// <summary>
|
|
/// <see cref="AnimancerLayer.GetOrCreateWeightlessState"/> is cloning a complex state such as a
|
|
/// <see cref="ManualMixerState"/> or <see cref="ControllerState"/>.
|
|
/// This has a larger performance cost than cloning a <see cref="ClipState"/> and these states
|
|
/// generally have parameters that need to be controlled which may result in undesired behaviour
|
|
/// if your scripts are only expecting to have one state to control.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The <see href="https://kybernetik.com.au/animancer/docs/manual/blending/fading/modes">Fade Modes</see>
|
|
/// page explains why clones are created.
|
|
/// </remarks>
|
|
CloneComplexState = 1 << 17,
|
|
|
|
/// <summary>
|
|
/// Unity doesn't suppport dynamically creating animations for Animancer in runtime builds
|
|
/// so this warning is given when attempting to use an animation which isn't saved as an
|
|
/// asset to explain this limitation as early as possible.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// This warning should be disabled if you only intend to use the animation in the
|
|
/// Unity Editor and not create it in a runtime build.
|
|
/// </remarks>
|
|
DynamicAnimation = 1 << 18,
|
|
|
|
/// <summary>
|
|
/// <see cref="Animancer.StringReference"/>s are generally more efficient for comparisons
|
|
/// than raw <see cref="string"/>s and are not interchangeable so references should be preferred.
|
|
/// </summary>
|
|
StringReference = 1 << 19,
|
|
|
|
/// <summary>All warning types.</summary>
|
|
All = ~0,
|
|
}
|
|
|
|
/// https://kybernetik.com.au/animancer/api/Animancer/Validate
|
|
public static partial class Validate
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
#if UNITY_ASSERTIONS
|
|
/// <summary>[Assert-Only]
|
|
/// The <see cref="OptionalWarning"/> flags that are currently disabled (default none).
|
|
/// </summary>
|
|
private static OptionalWarning _DisabledWarnings;
|
|
#endif
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Animancer Extension] [Assert-Conditional]
|
|
/// Disables the specified warning type. Supports bitwise combinations.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <strong>Example:</strong>
|
|
/// You can put a method like this in any class to disable whatever warnings you don't want on startup:
|
|
/// <para></para><code>
|
|
/// #if UNITY_ASSERTIONS
|
|
/// [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
/// private static void DisableAnimancerWarnings()
|
|
/// {
|
|
/// Animancer.OptionalWarning.ProOnly.Disable();
|
|
///
|
|
/// // You could disable OptionalWarning.All, but that's not recommended for obvious reasons.
|
|
/// }
|
|
/// #endif
|
|
/// </code></remarks>
|
|
[System.Diagnostics.Conditional(Strings.Assertions)]
|
|
public static void Disable(this OptionalWarning type)
|
|
{
|
|
#if UNITY_ASSERTIONS
|
|
_DisabledWarnings |= type;
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Animancer Extension] [Assert-Conditional]
|
|
/// Enables the specified warning type. Supports bitwise combinations.
|
|
/// </summary>
|
|
[System.Diagnostics.Conditional(Strings.Assertions)]
|
|
public static void Enable(this OptionalWarning type)
|
|
{
|
|
#if UNITY_ASSERTIONS
|
|
_DisabledWarnings &= ~type;
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Animancer Extension] [Assert-Conditional]
|
|
/// Enables or disables the specified warning type. Supports bitwise combinations.
|
|
/// </summary>
|
|
[System.Diagnostics.Conditional(Strings.Assertions)]
|
|
public static void SetEnabled(this OptionalWarning type, bool enable)
|
|
{
|
|
#if UNITY_ASSERTIONS
|
|
if (enable)
|
|
type.Enable();
|
|
else
|
|
type.Disable();
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Animancer Extension] [Assert-Conditional]
|
|
/// Logs the `message` as a warning if the `type` is enabled.
|
|
/// </summary>
|
|
/// <remarks>Does nothing if the `message` is <c>null</c>.</remarks>
|
|
[System.Diagnostics.Conditional(Strings.Assertions)]
|
|
[HideInCallstack]
|
|
public static void Log(this OptionalWarning type, string message, object context = null)
|
|
{
|
|
#if UNITY_ASSERTIONS
|
|
if (message == null || type.IsDisabled())
|
|
return;
|
|
|
|
Debug.LogWarning($"Possible Issue Detected: {message}" +
|
|
$"\n\nThis warning can be disabled via '{Strings.AnimancerSettingsPath}'" +
|
|
$" or by calling {nameof(Animancer)}.{nameof(OptionalWarning)}.{type}.{nameof(Disable)}()" +
|
|
" and it will automatically be compiled out of Runtime Builds (except for Development Builds)." +
|
|
$" More information can be found at {Strings.DocsURLs.OptionalWarning}\n",
|
|
context as Object);
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#if UNITY_ASSERTIONS
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Animancer Extension] [Assert-Only] Are none of the specified warning types disabled?</summary>
|
|
public static bool IsEnabled(this OptionalWarning type) => (_DisabledWarnings & type) == 0;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Animancer Extension] [Assert-Only] Are all of the specified warning types disabled?</summary>
|
|
public static bool IsDisabled(this OptionalWarning type) => (_DisabledWarnings & type) == type;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Animancer Extension] [Assert-Only]
|
|
/// Disables the specified warnings and returns those that were previously enabled.
|
|
/// </summary>
|
|
/// <remarks>Call <see cref="Enable"/> on the returned value to re-enable it.</remarks>
|
|
public static OptionalWarning DisableTemporarily(this OptionalWarning type)
|
|
{
|
|
var previous = _DisabledWarnings;
|
|
type.Disable();
|
|
return ~previous & type;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private const string PermanentlyDisabledWarningsKey = nameof(Animancer) + "." + nameof(PermanentlyDisabledWarnings);
|
|
|
|
/// <summary>[Assert-Only] Warnings that are automatically disabled</summary>
|
|
/// <remarks>
|
|
/// This value is stored in <see cref="PlayerPrefs"/>
|
|
/// and can be manually edited via <see cref="Strings.AnimancerSettingsPath"/>.
|
|
/// </remarks>
|
|
public static OptionalWarning PermanentlyDisabledWarnings
|
|
{
|
|
#if NO_RUNTIME_PLAYER_PREFS && ! UNITY_EDITOR
|
|
get => default;
|
|
set
|
|
{
|
|
_DisabledWarnings = value;
|
|
}
|
|
#else
|
|
get => (OptionalWarning)PlayerPrefs.GetInt(PermanentlyDisabledWarningsKey);
|
|
set
|
|
{
|
|
_DisabledWarnings = value;
|
|
PlayerPrefs.SetInt(PermanentlyDisabledWarningsKey, (int)value);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
[UnityEditor.InitializeOnLoadMethod]
|
|
#endif
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
|
private static void InitializePermanentlyDisabledWarnings()
|
|
{
|
|
_DisabledWarnings = PermanentlyDisabledWarnings;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endif
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|