diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 492f628482..bd3b360577 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -7,7 +7,6 @@ using System.Linq; using Humanizer; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; @@ -76,23 +75,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { AddStep("set beatmap", () => setBeatmap()); - // the bindables need to be independent for each content cell to prevent interference, - // as if some of the skins don't implement the animation they'll immediately revert to the previous state from the clear state. - var states = new List>(); + AddStep("create mascot", () => SetContents(() => new DrawableTaikoMascot { RelativeSizeAxes = Axes.Both })); - AddStep("create mascot", () => SetContents(() => - { - var state = new Bindable(TaikoMascotAnimationState.Clear); - states.Add(state); - return new DrawableTaikoMascot { State = { BindTarget = state }, RelativeSizeAxes = Axes.Both }; - })); - - AddStep("set clear state", () => states.ForEach(state => state.Value = TaikoMascotAnimationState.Clear)); - AddStep("miss", () => mascots.ForEach(mascot => mascot.OnNewResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }))); + AddStep("set clear state", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear)); + AddStep("miss", () => mascots.ForEach(mascot => mascot.LastResult.Value = new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss })); AddAssert("skins with animations remain in clear state", () => someMascotsIn(TaikoMascotAnimationState.Clear)); AddUntilStep("state reverts to fail", () => allMascotsIn(TaikoMascotAnimationState.Fail)); - AddStep("set clear state again", () => states.ForEach(state => state.Value = TaikoMascotAnimationState.Clear)); + AddStep("set clear state again", () => mascots.ForEach(mascot => mascot.State.Value = TaikoMascotAnimationState.Clear)); AddAssert("skins with animations change to clear", () => someMascotsIn(TaikoMascotAnimationState.Clear)); } @@ -220,6 +210,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning playfield.OnNewResult(hit, judgementResult); } + + foreach (var mascot in mascots) + { + mascot.LastResult.Value = judgementResult; + } } private bool allMascotsIn(TaikoMascotAnimationState state) => mascots.All(d => d.State.Value == state); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs index 9328b607e6..105baa84cc 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs @@ -12,14 +12,15 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Taiko.UI { public class DrawableTaikoMascot : BeatSyncedContainer { - public IBindable State => state; + public readonly Bindable State; + public readonly Bindable LastResult; - private readonly Bindable state; private readonly Dictionary animations; private TaikoMascotAnimation currentAnimation; @@ -30,12 +31,14 @@ namespace osu.Game.Rulesets.Taiko.UI { Origin = Anchor = Anchor.BottomLeft; - state = new Bindable(startingState); + State = new Bindable(startingState); + LastResult = new Bindable(); + animations = new Dictionary(); } - [BackgroundDependencyLoader] - private void load(TextureStore textures) + [BackgroundDependencyLoader(true)] + private void load(TextureStore textures, GameplayBeatmap gameplayBeatmap) { InternalChildren = new[] { @@ -44,6 +47,9 @@ namespace osu.Game.Rulesets.Taiko.UI animations[TaikoMascotAnimationState.Kiai] = new TaikoMascotAnimation(TaikoMascotAnimationState.Kiai), animations[TaikoMascotAnimationState.Fail] = new TaikoMascotAnimation(TaikoMascotAnimationState.Fail), }; + + if (gameplayBeatmap != null) + ((IBindable)LastResult).BindTo(gameplayBeatmap.LastJudgementResult); } protected override void LoadComplete() @@ -51,16 +57,22 @@ namespace osu.Game.Rulesets.Taiko.UI base.LoadComplete(); animations.Values.ForEach(animation => animation.Hide()); - state.BindValueChanged(mascotStateChanged, true); + + State.BindValueChanged(mascotStateChanged, true); + LastResult.BindValueChanged(onNewResult); } - public void OnNewResult(JudgementResult result) + private void onNewResult(ValueChangedEvent resultChangedEvent) { + var result = resultChangedEvent.NewValue; + if (result == null) + return; + // TODO: missing support for clear/fail state transition at end of beatmap gameplay if (triggerComboClear(result) || triggerSwellClear(result)) { - state.Value = TaikoMascotAnimationState.Clear; + State.Value = TaikoMascotAnimationState.Clear; // always consider a clear equivalent to a hit to avoid clear -> miss transitions lastObjectHit = true; } @@ -79,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void Update() { base.Update(); - state.Value = getNextState(); + State.Value = getNextState(); } private TaikoMascotAnimationState getNextState() @@ -87,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.UI // don't change state if current animation is playing // (used for clear state - others are manually animated on new beats) if (currentAnimation != null && !currentAnimation.Completed) - return state.Value; + return State.Value; if (!lastObjectHit) return TaikoMascotAnimationState.Fail; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 0fe0d6165b..21676510ad 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -40,8 +40,6 @@ namespace osu.Game.Rulesets.Taiko.UI private Container hitTargetOffsetContent; - private SkinnableDrawable mascotDrawable; - public TaikoPlayfield(ControlPointInfo controlPoints) { this.controlPoints = controlPoints; @@ -127,7 +125,7 @@ namespace osu.Game.Rulesets.Taiko.UI }, } }, - mascotDrawable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.TaikoDon), _ => Empty()) + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.TaikoDon), _ => Empty()) { Origin = Anchor.BottomLeft, Anchor = Anchor.TopLeft, @@ -212,11 +210,6 @@ namespace osu.Game.Rulesets.Taiko.UI addExplosion(judgedObject, type); break; } - - if (mascotDrawable.Drawable is DrawableTaikoMascot mascot) - { - mascot.OnNewResult(result); - } } private void addDrumRollHit(DrawableDrumRollTick drawableTick) =>