Created
July 14, 2024 02:01
-
-
Save adammyhre/71327c363bdb4f282b94f95dfe39ff13 to your computer and use it in GitHub Desktop.
AnimationSystem with Audio and Awaitables
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine.Animations; | |
using UnityEngine.Playables; | |
using UnityEngine; | |
[Serializable] | |
public class AnimationConfig { | |
public Animator animator; | |
public AnimationClip idleClip; | |
public AnimationClip walkClip; | |
} | |
[Serializable] | |
public class AudioConfig { | |
public AudioSource audioSource; | |
public AudioClip[] footstepSounds; | |
} | |
public class AnimationSystem { | |
PlayableGraph playableGraph; | |
AnimationMixerPlayable topLevelMixer; | |
AnimationMixerPlayable locomotionMixer; | |
AnimationClipPlayable oneShotPlayable; | |
ScriptPlayable<FootstepPlayableBehaviour> footstepPlayable; | |
Awaitable blendInOut; | |
public AnimationSystem(AnimationConfig animationConfig, AudioConfig audioConfig) { | |
playableGraph = PlayableGraph.Create("AnimationSystem"); | |
SetupAnimations(animationConfig); | |
SetupFootsteps(audioConfig); | |
playableGraph.Play(); | |
} | |
void SetupAnimations(AnimationConfig animationConfig) { | |
var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", animationConfig.animator); | |
topLevelMixer = AnimationMixerPlayable.Create(playableGraph, 2); | |
playableOutput.SetSourcePlayable(topLevelMixer); | |
locomotionMixer = AnimationMixerPlayable.Create(playableGraph, 2); | |
topLevelMixer.ConnectInput(0, locomotionMixer, 0); | |
playableGraph.GetRootPlayable(0).SetInputWeight(0, 1f); | |
var idlePlayable = AnimationClipPlayable.Create(playableGraph, animationConfig.idleClip); | |
var walkPlayable = AnimationClipPlayable.Create(playableGraph, animationConfig.walkClip); | |
idlePlayable.GetAnimationClip().wrapMode = WrapMode.Loop; | |
walkPlayable.GetAnimationClip().wrapMode = WrapMode.Loop; | |
locomotionMixer.ConnectInput(0, idlePlayable, 0); | |
locomotionMixer.ConnectInput(1, walkPlayable, 0); | |
} | |
void SetupFootsteps(AudioConfig audioConfig) { | |
var scriptOutput = ScriptPlayableOutput.Create(playableGraph, "Footsteps"); | |
footstepPlayable = ScriptPlayable<FootstepPlayableBehaviour>.Create(playableGraph); | |
var behaviour = footstepPlayable.GetBehaviour(); | |
behaviour.footstepSounds = audioConfig.footstepSounds; | |
behaviour.audioSource = new ExposedReference<AudioSource> { defaultValue = audioConfig.audioSource }; | |
scriptOutput.SetSourcePlayable(footstepPlayable); | |
} | |
public void OnFootstep() => footstepPlayable.GetBehaviour().PlayFootstepSound(); | |
public void UpdateLocomotion(Vector3 velocity, float maxSpeed) { | |
var weight = Mathf.InverseLerp(0f, maxSpeed, velocity.magnitude); | |
locomotionMixer.SetInputWeight(0, 1f - weight); | |
locomotionMixer.SetInputWeight(1, weight); | |
} | |
public void PlayOneShot(AnimationClip oneShotClip) { | |
if (oneShotPlayable.IsValid() && oneShotPlayable.GetAnimationClip() == oneShotClip) return; | |
InterruptOneShot(); | |
oneShotPlayable = AnimationClipPlayable.Create(playableGraph, oneShotClip); | |
topLevelMixer.ConnectInput(1, oneShotPlayable, 0); | |
topLevelMixer.SetInputWeight(1, 1f); | |
var blendDuration = Mathf.Clamp(oneShotClip.length * 0.1f, 0.1f, oneShotClip.length * 0.5f); | |
blendInOut = BlendInOut(blendDuration, oneShotClip.length - blendDuration); | |
} | |
async Awaitable BlendInOut(float blendDuration, float delay) { | |
await Blend(blendDuration, blendTime => { | |
var weight = Mathf.Lerp(1f, 0f, blendTime); | |
topLevelMixer.SetInputWeight(0, weight); | |
topLevelMixer.SetInputWeight(1, 1f - weight); | |
}); | |
await Awaitable.WaitForSecondsAsync(delay); | |
await Blend(blendDuration, blendTime => { | |
var weight = Mathf.Lerp(0f, 1f, blendTime); | |
topLevelMixer.SetInputWeight(0, weight); | |
topLevelMixer.SetInputWeight(1, 1f - weight); | |
}); | |
DisconnectOneShot(); | |
} | |
static async Awaitable Blend(float duration, Action<float> blendCallback) { | |
var blendTime = 0f; | |
while (blendTime < 1f) { | |
blendTime += Time.deltaTime / duration; | |
blendCallback(blendTime); | |
await Awaitable.EndOfFrameAsync(); | |
} | |
blendCallback(1f); | |
} | |
void InterruptOneShot() { | |
blendInOut?.Cancel(); | |
topLevelMixer.SetInputWeight(0, 1f); | |
topLevelMixer.SetInputWeight(1, 0f); | |
if (oneShotPlayable.IsValid()) { | |
DisconnectOneShot(); | |
} | |
} | |
void DisconnectOneShot() { | |
topLevelMixer.DisconnectInput(1); | |
playableGraph.DestroyPlayable(oneShotPlayable); | |
} | |
public void Destroy() { | |
if (playableGraph.IsValid()) { | |
playableGraph.Destroy(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using UnityEngine.Audio; | |
using UnityEngine.Playables; | |
public class FootstepPlayableBehaviour : PlayableBehaviour { | |
public AudioClip[] footstepSounds; | |
public ExposedReference<AudioSource> audioSource; | |
AudioSource resolvedAudioSource; | |
PlayableGraph playableGraph; | |
AudioMixerPlayable audioMixerPlayable; | |
AudioClipPlayable currentClipPlayable; | |
public override void OnGraphStart(Playable playable) { | |
resolvedAudioSource = audioSource.Resolve(playable.GetGraph().GetResolver()); | |
playableGraph = playable.GetGraph(); | |
audioMixerPlayable = AudioMixerPlayable.Create(playableGraph, 1); | |
var audioOutput = AudioPlayableOutput.Create(playableGraph, "FootstepAudio", resolvedAudioSource); | |
audioOutput.SetSourcePlayable(audioMixerPlayable); | |
} | |
public void PlayFootstepSound() { | |
if (footstepSounds == null || footstepSounds.Length == 0 || resolvedAudioSource == null) return; | |
var selectedClip = footstepSounds[Random.Range(0, footstepSounds.Length)]; | |
var newClipPlayable = AudioClipPlayable.Create(playableGraph, selectedClip, false); | |
if (currentClipPlayable.IsValid()) { | |
audioMixerPlayable.DisconnectInput(0); | |
playableGraph.DestroyPlayable(currentClipPlayable); | |
} | |
playableGraph.Connect(newClipPlayable, 0, audioMixerPlayable, 0); | |
audioMixerPlayable.SetInputWeight(0, 1f); | |
currentClipPlayable = newClipPlayable; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment