1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 06:03:08 +08:00

Add animation on failing

This commit is contained in:
Dean Herbert 2019-06-04 16:13:16 +09:00
parent 76474987ef
commit 9c214c3f0e
5 changed files with 191 additions and 7 deletions

View File

@ -0,0 +1,50 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneFailAnimation : AllPlayersTestScene
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty<Mod>();
return new FailPlayer();
}
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(AllPlayersTestScene),
typeof(TestPlayer),
typeof(Player),
};
protected override void AddCheckSteps()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State == Visibility.Visible);
}
private class FailPlayer : TestPlayer
{
public new FailOverlay FailOverlay => base.FailOverlay;
public FailPlayer()
: base(false, false)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
ScoreProcessor.FailConditions += _ => true;
}
}
}
}

View File

@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestPauseAfterFail()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible);
confirmClockRunning(false);

View File

@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.UI
/// <summary>
/// The playfield.
/// </summary>
public Playfield Playfield => playfield.Value;
public override Playfield Playfield => playfield.Value;
/// <summary>
/// Place to put drawables above hit objects but below UI.
@ -342,6 +342,11 @@ namespace osu.Game.Rulesets.UI
/// </summary>
public readonly BindableBool IsPaused = new BindableBool();
/// <summary>
/// The playfield.
/// </summary>
public abstract Playfield Playfield { get; }
/// <summary>
/// The frame-stable clock which is being used for playfield display.
/// </summary>

View File

@ -0,0 +1,113 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Game.Rulesets.UI;
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
/// <summary>
/// Manage the animation to be applied when a player fails.
/// Single file; automatically disposed after use.
/// </summary>
public class FailAnimation : Component
{
public Action OnComplete;
private readonly DrawableRuleset drawableRuleset;
private readonly BindableDouble trackFreq = new BindableDouble(1);
private Track track;
private const float duration = 2500;
private SampleChannel failSample;
public FailAnimation(DrawableRuleset drawableRuleset)
{
this.drawableRuleset = drawableRuleset;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio, IBindable<WorkingBeatmap> beatmap)
{
track = beatmap.Value.Track;
failSample = audio.Samples.Get(@"Gameplay/failsound");
}
private bool started;
/// <summary>
/// Start the fail animation playing.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if started more than once.</exception>
public void Start()
{
if (started) throw new InvalidOperationException("Animation cannot be started more than once.");
started = true;
failSample.Play();
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
{
OnComplete?.Invoke();
Expire();
});
track.AddAdjustment(AdjustableProperty.Frequency, trackFreq);
applyToPlayfield(drawableRuleset.Playfield);
drawableRuleset.Playfield.HitObjectContainer.FlashColour(Color4.Red, 500);
drawableRuleset.Playfield.HitObjectContainer.FadeOut(duration / 2);
}
protected override void Update()
{
base.Update();
if (!started)
return;
applyToPlayfield(drawableRuleset.Playfield);
}
private readonly List<DrawableHitObject> appliedObjects = new List<DrawableHitObject>();
private void applyToPlayfield(Playfield playfield)
{
foreach (var nested in playfield.NestedPlayfields)
applyToPlayfield(nested);
foreach (DrawableHitObject obj in playfield.HitObjectContainer.AliveObjects)
{
if (appliedObjects.Contains(obj))
continue;
obj.RotateTo(RNG.NextSingle(-90, 90), duration);
obj.ScaleTo(obj.Scale * 0.5f, duration);
obj.MoveToOffset(new Vector2(0, 400), duration);
appliedObjects.Add(obj);
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq);
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@ -173,7 +173,8 @@ namespace osu.Game.Screens.Play
fadeOut(true);
Restart();
},
}
},
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }
};
DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true);
@ -345,13 +346,13 @@ namespace osu.Game.Screens.Play
protected FailOverlay FailOverlay { get; private set; }
private FailAnimation failAnimation;
private bool onFail()
{
if (Mods.Value.OfType<IApplicableFailOverride>().Any(m => !m.AllowFail))
return false;
GameplayClockContainer.Stop();
HasFailed = true;
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
@ -360,9 +361,17 @@ namespace osu.Game.Screens.Play
if (PauseOverlay.State == Visibility.Visible)
PauseOverlay.Hide();
failAnimation.Start();
return true;
}
// Called back when the transform finishes
private void onFailComplete()
{
GameplayClockContainer.Stop();
FailOverlay.Retries = RestartCount;
FailOverlay.Show();
return true;
}
#endregion
@ -489,6 +498,13 @@ namespace osu.Game.Screens.Play
// still want to block if we are within the cooldown period and not already paused.
return true;
if (HasFailed && ValidForResume && !FailOverlay.IsPresent)
// ValidForResume is false when restarting
{
failAnimation.FinishTransforms(true);
return true;
}
GameplayClockContainer.ResetLocalAdjustments();
fadeOut();