diff --git a/Assets/Scripts/Games/KarateMan/KarateManJoe.cs b/Assets/Scripts/Games/KarateMan/KarateManJoe.cs index dda18bac..b7b33353 100644 --- a/Assets/Scripts/Games/KarateMan/KarateManJoe.cs +++ b/Assets/Scripts/Games/KarateMan/KarateManJoe.cs @@ -219,7 +219,7 @@ namespace HeavenStudio.Games.Scripts_KarateMan private bool _lastPunchedHeavy = false; - public bool Punch(int forceHand = 0, bool touchCharge = false, bool punchedHeavy = false) + public bool Punch(int forceHand = 0, bool touchCharge = false, bool punchedHeavy = false, double wantBeat = double.MaxValue) { if (GameManager.instance.currentGame != "karateman") return false; var cond = Conductor.instance; @@ -246,7 +246,8 @@ namespace HeavenStudio.Games.Scripts_KarateMan noNuriJabTime = cond.songPositionInBeatsAsDouble; break; default: - if (cond.songPositionInBeatsAsDouble <= cond.GetBeatFromSongPos(lastPunchTime + Minigame.NgLateTime() - 1) + 0.25) + Debug.Log($"Punching with beat {wantBeat} and lastPunchTime {lastPunchTime}"); + if (wantBeat <= cond.GetBeatFromSongPos(lastPunchTime + Minigame.NgLateTime() - 1) + 0.25) { lastPunchTime = double.MinValue; anim.DoScaledAnimationAsync("Straight", 0.5f); @@ -254,7 +255,7 @@ namespace HeavenStudio.Games.Scripts_KarateMan } else { - lastPunchTime = cond.songPositionAsDouble; + lastPunchTime = cond.GetSongPosFromBeat(wantBeat, true); anim.DoScaledAnimationAsync("Jab", 0.5f); } break; diff --git a/Assets/Scripts/Games/KarateMan/KarateManPot.cs b/Assets/Scripts/Games/KarateMan/KarateManPot.cs index 934ae397..179527b1 100644 --- a/Assets/Scripts/Games/KarateMan/KarateManPot.cs +++ b/Assets/Scripts/Games/KarateMan/KarateManPot.cs @@ -765,7 +765,7 @@ namespace HeavenStudio.Games.Scripts_KarateMan var joe = KarateMan.instance.Joe; if (state <= -1f || state >= 1f) { - bool straight = joe.Punch(ItemPunchHand(), false, ItemPunchHand() == 2); + bool straight = joe.Punch(ItemPunchHand(), false, ItemPunchHand() == 2, caller.startBeat + caller.timer); startBeat = Conductor.instance.unswungSongPositionInBeatsAsDouble; CurrentCurve = ItemCurves[6]; curveTargetBeat = 1f; @@ -798,7 +798,7 @@ namespace HeavenStudio.Games.Scripts_KarateMan return; } } - bool straight = joe.Punch(ItemPunchHand(), false, ItemPunchHand() == 2); + bool straight = joe.Punch(ItemPunchHand(), false, ItemPunchHand() == 2, caller.startBeat + caller.timer); DoHitExpression(startBeat + 1f); ItemHitEffect(straight); status = FlyStatus.Hit; diff --git a/Assets/Scripts/Util/AnimationHelpers.cs b/Assets/Scripts/Util/AnimationHelpers.cs index 03565169..743a3093 100644 --- a/Assets/Scripts/Util/AnimationHelpers.cs +++ b/Assets/Scripts/Util/AnimationHelpers.cs @@ -11,6 +11,14 @@ namespace HeavenStudio.Util var stateInfo = anim.GetCurrentAnimatorStateInfo(0); return (stateInfo.normalizedTime >= stateInfo.speed || stateInfo.loop) && !anim.IsInTransition(0); } + + public static bool IsAnimationNotPlaying(this Animator anim, int layer) + { + if (anim == null) return true; + var stateInfo = anim.GetCurrentAnimatorStateInfo(layer); + return (stateInfo.normalizedTime >= stateInfo.speed || stateInfo.loop) && !anim.IsInTransition(layer); + } + /// /// Returns true if animName is currently playing on animator /// @@ -24,6 +32,19 @@ namespace HeavenStudio.Util return (stateInfo.normalizedTime < stateInfo.speed || stateInfo.loop) && isPlaying; } + /// + /// Returns true if animName is currently playing on animator on a specific layer + /// + /// Animator to check + /// name(s) of animation to look out for + public static bool IsPlayingAnimationNames(this Animator anim, int layer, params string[] animNames) + { + if (anim == null) return false; + var stateInfo = anim.GetCurrentAnimatorStateInfo(layer); + var isPlaying = Array.Exists(animNames, animName => stateInfo.IsName(animName)); + return (stateInfo.normalizedTime < stateInfo.speed || stateInfo.loop) && isPlaying; + } + /// /// Sets animator's progress on an animation based on current song beat between startTime and length /// function must be called in actor's Update loop to update properly @@ -43,6 +64,27 @@ namespace HeavenStudio.Util anim.speed = 1f; //not 0 so these can still play their script events } + /// + /// Sets animator's progress on a state based on current song beat between startTime and length + /// function must be called in actor's Update loop to update properly + /// also sets the speed of the state 1x speed + /// Animator must have a float parameter named "{state}_Speed" for this to work, and have the state's speed multiplier mapped to that parameter in the state tree + /// + /// Animator to update + /// name of animation to play + /// reference start time of animation (progress 0.0) + /// duration of animation (progress 1.0) + /// multiplier for animation progress (smaller values make animation slower) + /// animator layer to play animation on + public static void DoScaledState(this Animator anim, string state, double startTime, double length = 1, float timeScale = 1f, int animLayer = -1, bool clamp = false, bool ignoreSwing = true) + { + if (anim == null) return; + float pos = Conductor.instance.GetPositionFromBeat(startTime, length, ignoreSwing: ignoreSwing) * timeScale; + if (clamp) pos = Mathf.Clamp01(pos); + anim.Play(state, animLayer, pos); + anim.SetFloat($"{state}_Speed", 1); + } + /// /// Sets animator progress on an animation according to pos /// @@ -85,6 +127,36 @@ namespace HeavenStudio.Util anim.DoScaledAnimationAsync(animName, timeScale, pos, animLayer); } + /// + /// Sets the speed of an animation state on an animator, scaled to the BPM, then plays it + /// Animator must have a float parameter named "{state}Speed" for this to work, and have the state's speed multiplier mapped to that parameter in the state tree + /// + /// Animator to play animation on + /// name of animation to play + /// multiplier for animation speed + /// beat that this animation would start on + /// animator layer to play animation on + public static void DoScaledStateFromBeatAsync(this Animator anim, string animName, float timeScale = 1f, double startBeat = 0, int animLayer = -1) + { + if (anim == null) return; + float pos = 0; + if (!double.IsNaN(startBeat)) { + var cond = Conductor.instance; + var animClip = Array.Find(anim.runtimeAnimatorController.animationClips, x => x.name == animName); + if (animClip == null) { + Debug.LogError("Animation clip " + animName + " not found!"); + return; + } + double animLength = cond.SecsToBeats(animClip.length, cond.GetBpmAtBeat(startBeat)); + pos = cond.GetPositionFromBeat(startBeat, animLength) * timeScale; + } else { + Debug.LogWarning("DoScaledAnimationFromBeatAsync()'s startBeat was NaN; using DoScaledAnimationAsync() instead."); + } + anim.DoScaledStateAsync(animName, timeScale, pos, animLayer); + } + + + /// /// Plays animation on animator, scaling speed to song BPM /// call this function once, when playing an animation @@ -101,6 +173,21 @@ namespace HeavenStudio.Util anim.speed = (1f / Conductor.instance.pitchedSecPerBeat) * timeScale; } + /// + /// Sets the speed of an animation state on an animator, scaled to the BPM, then plays it + /// Animator must have a float parameter named "{state}Speed" for this to work, and have the state's speed multiplier mapped to that parameter in the state tree + /// + /// Animator to play animation on + /// name of animation to play + /// multiplier for animation speed + /// starting progress of animation + /// animator layer to play animation on + public static void DoScaledStateAsync(this Animator anim, string state, float timeScale = 1f, float startPos = 0f, int animLayer = -1) + { + if (anim == null) return; + anim.PlayStateAtSpeed(state, (1f / Conductor.instance.pitchedSecPerBeat) * timeScale, startPos, animLayer); + } + public static void SetScaledAnimationSpeed(this Animator anim, float timeScale = 0.5f) { if (anim == null) return; @@ -121,5 +208,21 @@ namespace HeavenStudio.Util anim.Play(animName, animLayer, startPos); anim.speed = 1f; } + + /// + /// Sets the speed of an animation state on an animator, then plays it + /// Animator must have a float parameter named "{state}_Speed" for this to work, and have the state's speed multiplier mapped to that parameter in the state tree + /// + /// Animator to play animation on + /// name of animation to play + /// multiplier for animation speed + /// starting progress of animation + /// animator layer to play animation on + public static void PlayStateAtSpeed(this Animator anim, string state, float timeScale = 1f, float startPos = 0f, int animLayer = -1) + { + if (anim == null) return; + anim.SetFloat($"{state}_Speed", timeScale); + anim.Play(state, animLayer, startPos); + } } } \ No newline at end of file