123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
- using System;
- using System.Collections.Generic;
- using UnityEngine;
- #if UNITY_UGUI
- using UnityEngine.UI;
- #endif
- namespace Animancer.Samples.FineControl
- {
- /// <summary>Tracks user progress through a tutorial and displays appropriate instructions.</summary>
- /// <remarks>
- /// <strong>Sample:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/samples/fine-control/live-inspector">
- /// Live Inspector</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer.Samples.FineControl/LiveInspectorTutorial
- [AddComponentMenu(Strings.SamplesMenuPrefix + "Fine Control - Live Inspector Tutorial")]
- [AnimancerHelpUrl(typeof(LiveInspectorTutorial))]
- public class LiveInspectorTutorial : MonoBehaviour
- {
- /************************************************************************************************************************/
- #if UNITY_UGUI
- /************************************************************************************************************************/
- [SerializeField] private AnimancerComponent _Animancer;
- [SerializeField] private Text _Text;
- /************************************************************************************************************************/
- #if !UNITY_EDITOR
- /************************************************************************************************************************/
- protected virtual void Awake()
- {
- _Text.text = "This Sample only works in the Unity Editor";
- }
- /************************************************************************************************************************/
- #else
- /************************************************************************************************************************/
- private class TutorialStep
- {
- /************************************************************************************************************************/
- /// <summary>Instructions to display while waiting for the user to complete this step.</summary>
- public readonly string Instructions;
- /// <summary>Has the user completed this step?</summary>
- public readonly Func<bool> IsComplete;
- /************************************************************************************************************************/
- public TutorialStep(string instructions, Func<bool> isComplete)
- {
- Instructions = instructions;
- IsComplete = isComplete;
- }
- /************************************************************************************************************************/
- }
- /************************************************************************************************************************/
- private readonly List<TutorialStep> Steps = new();
- private int _CurrentStep;
- private bool _WasShowingAddAnimationField;
- private const string
- AutoNormalizeWeightsFunction = "Display Options/Auto Normalize Weights",
- ShowAddAnimationFieldFunction = "Display Options/Show 'Add Animation' Field",
- ShowAddAnimationFieldPref = "Animancer/Inspector/" + ShowAddAnimationFieldFunction,
- IdleName = "Humanoid-Idle",
- WalkName = "Humanoid-WalkForwards",
- DragAndDropAnimations =
- "\n\nYou can also Drag and Drop animations from the Project window into the preview area to add them.";
- /************************************************************************************************************************/
- protected virtual void Awake()
- {
- _WasShowingAddAnimationField = UnityEditor.EditorPrefs.GetBool(ShowAddAnimationFieldPref);
- // Select AnimancerComponent.
- Steps.Add(new(
- $"Select the {_Animancer.name} object in the Hierarchy",
- () => UnityEditor.Selection.activeGameObject == _Animancer.gameObject));
- // Initialize graph.
- Steps.Add(new(
- $"Right Click on the header of the {nameof(AnimancerComponent)} in the Inspector" +
- $" and select the 'Initialize Graph' function from the context menu.",
- () => _Animancer.IsGraphInitialized));
- // Show 'Add Animation' field.
- Steps.Add(new(
- $"Note how the character is now in a hunched over pose with their legs under the ground." +
- $" That's the default pose for a Humanoid Rig when it has no animations so let's add some." +
- $"\n" +
- $"\nMake sure the preview area down the bottom of the Inspector is expanded." +
- $"\n" +
- $"\nRight Click on the word 'States' in the preview area" +
- $" and select the '{ShowAddAnimationFieldFunction}' function from the context menu.",
- () => UnityEditor.EditorPrefs.GetBool(ShowAddAnimationFieldPref)));
- // Add idle animation.
- Steps.Add(new(
- $"Use the 'Add Animation' field at the top of the preview area" +
- $" to select the '{IdleName}' animation." +
- DragAndDropAnimations,
- () => IsCurrentState(IdleName)));
- // Add walk animation.
- Steps.Add(new(
- $"Use the 'Add Animation' field at the top of the preview area" +
- $" to select the '{WalkName}' animation." +
- DragAndDropAnimations,
- () => IsCurrentState(WalkName)));
- // Play idle animation.
- Steps.Add(new(
- $"Ctrl + Left Click on the '{IdleName}' animation to play it.",
- () => IsCurrentState(IdleName)));
- // Play walk animation as well.
- Steps.Add(new(
- $"With the '{IdleName}' animation still playing," +
- $" Left Click on the '{WalkName}' animation to expand its details." +
- $"\n" +
- $"\nClick the Play button in that state's details.",
- () => IsCurrentState(IdleName)
- && TryGetState(WalkName, out AnimancerState walk)
- && walk.Weight == 0
- && walk.IsPlaying));
- // Set weight.
- Steps.Add(new(
- $"Note how the preview now shows both animations playing," +
- $" but you can still only see the '{IdleName}' pose in the Game window." +
- $" That's because interacting directly with the details of a state only affects those specific details" +
- $" and the Weight of '{WalkName}' is still 0." +
- $"\n" +
- $"\nSet the Weight of '{WalkName}' to 0.4.",
- () => IsCurrentState(IdleName)
- && TryGetState(IdleName, out AnimancerState idle)
- && TryGetState(WalkName, out AnimancerState walk)
- && idle.Weight == 0.6f
- && walk.Weight == 0.4f));
- // Pause graph.
- Steps.Add(new(
- $"Now we have a blend of both animations playing at the same time." +
- $" This sort of thing is normally achieved using Mixers." +
- $"\n" +
- $"\nNote how setting the Weight of '{WalkName}' to 0.4" +
- $" automatically changed the Weight of '{IdleName}' to 0.6 so that they add up to a total of 1." +
- $" If you had set its Weight in code it would only affect that state" +
- $" so everything can have whatever values you want," +
- $" but that's usually inconvenient when fiddling with them in the Inspector." +
- $" This feature can be disabled via '{AutoNormalizeWeightsFunction}' if you don't want it." +
- $"\n" +
- $"\nClick the Pause button at the top of the preview area to pause the character.",
- () => !_Animancer.Graph.IsGraphPlaying));
- // Unpause graph.
- Steps.Add(new(
- $"The character is now paused, but nothing else is." +
- $" You can still Right Click in the Game window and drag to move the camera around." +
- $"\n" +
- $"\nClick the Play button at the top of the preview area to resume playing.",
- () => _Animancer.Graph.IsGraphPlaying));
- // Set speed.
- Steps.Add(new(
- $"Click the '1.0x' button at the top of the preview area next to the Play/Pause button" +
- $" to show the 'Speed' slider." +
- $"\n" +
- $"\nUse that slider to set the speed to 0.5.",
- () => _Animancer.Graph.Speed == 0.5f));
- ApplyCurrentStep();
- }
- /************************************************************************************************************************/
- protected virtual void Update()
- {
- if (_CurrentStep >= Steps.Count)
- return;
- if (!Steps[_CurrentStep].IsComplete())
- return;
- _CurrentStep++;
- ApplyCurrentStep();
- }
- /************************************************************************************************************************/
- protected virtual void OnDestroy()
- {
- if (!_WasShowingAddAnimationField &&
- UnityEditor.EditorPrefs.GetBool(ShowAddAnimationFieldPref))
- {
- UnityEditor.EditorPrefs.SetBool(ShowAddAnimationFieldPref, false);
- Debug.Log(
- ShowAddAnimationFieldFunction +
- " has been disabled as it was before starting this tutorial." +
- " Normally it would remember the value you set.");
- }
- }
- /************************************************************************************************************************/
- private void ApplyCurrentStep()
- {
- if (_CurrentStep < Steps.Count)
- {
- _Text.text = $"{_CurrentStep}/{Steps.Count} {Steps[_CurrentStep].Instructions}";
- }
- else
- {
- _Text.text = $"{Steps.Count}/{Steps.Count} Congratulations for completing this tutorial.";
- enabled = false;
- }
- }
- /************************************************************************************************************************/
- private bool IsCurrentState(string clipName)
- {
- AnimancerState state = _Animancer.States.Current;
- if (state == null)
- return false;
- AnimationClip clip = state.Clip;
- if (clip == null)
- return false;
- return clip.name == clipName;
- }
- /************************************************************************************************************************/
- private bool TryGetState(string clipName, out AnimancerState state)
- {
- foreach (AnimancerState otherState in _Animancer.States)
- {
- if (otherState.Clip.name == clipName)
- {
- state = otherState;
- return true;
- }
- }
- state = null;
- return false;
- }
- /************************************************************************************************************************/
- #endif
- /************************************************************************************************************************/
- #else
- /************************************************************************************************************************/
- protected virtual void Awake()
- {
- SampleReadMe.LogMissingUnityUIModuleError(this);
- }
- /************************************************************************************************************************/
- #endif
- /************************************************************************************************************************/
- }
- }
|