1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-17 22:17:25 +08:00

Merge branch 'master' into has-bindable-value

This commit is contained in:
Dan Balasescu 2017-04-10 19:40:37 +09:00 committed by GitHub
commit ba4d654a21
20 changed files with 333 additions and 114 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
@ -11,20 +12,21 @@ using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects;
using osu.Game.Modes.Taiko.Objects.Drawables;
using osu.Game.Modes.Taiko.UI;
using System;
namespace osu.Desktop.VisualTests.Tests
{
internal class TestCaseTaikoPlayfield : TestCase
{
public override string Description => "Taiko playfield";
private const double default_duration = 300;
private const float scroll_time = 1000;
private TaikoPlayfield playfield;
public override string Description => "Taiko playfield";
protected override double TimePerAction => default_duration * 2;
private const double default_duration = 300;
private const float scroll_time = 1000;
private readonly Random rng = new Random(1337);
private TaikoPlayfield playfield;
public override void Reset()
{
@ -41,7 +43,11 @@ namespace osu.Desktop.VisualTests.Tests
AddStep("Strong Rim", () => addRimHit(true));
AddStep("Add bar line", () => addBarLine(false));
AddStep("Add major bar line", () => addBarLine(true));
AddStep("Height test 1", () => changePlayfieldSize(1));
AddStep("Height test 2", () => changePlayfieldSize(2));
AddStep("Height test 3", () => changePlayfieldSize(3));
AddStep("Height test 4", () => changePlayfieldSize(4));
AddStep("Height test 5", () => changePlayfieldSize(5));
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
@ -57,21 +63,53 @@ namespace osu.Desktop.VisualTests.Tests
});
}
private void changePlayfieldSize(int step)
{
switch (step)
{
case 1:
addCentreHit(false);
break;
case 2:
addCentreHit(true);
break;
case 3:
addDrumRoll(false);
break;
case 4:
addDrumRoll(true);
break;
case 5:
addSwell(1000);
playfield.Delay(scroll_time - 100);
break;
}
playfield.ResizeTo(new Vector2(1, rng.Next(25, 400)), 500);
}
private void addHitJudgement()
{
TaikoHitResult hitResult = RNG.Next(2) == 0 ? TaikoHitResult.Good : TaikoHitResult.Great;
playfield.OnJudgement(new DrawableTestHit(new Hit())
var h = new DrawableTestHit(new Hit())
{
X = RNG.NextSingle(hitResult == TaikoHitResult.Good ? -0.1f : -0.05f, hitResult == TaikoHitResult.Good ? 0.1f : 0.05f),
Judgement = new TaikoJudgement
{
Result = HitResult.Hit,
TaikoResult = hitResult,
TimeOffset = 0,
SecondHit = RNG.Next(10) == 0
TimeOffset = 0
}
});
};
playfield.OnJudgement(h);
if (RNG.Next(10) == 0)
{
h.Judgement.SecondHit = true;
playfield.OnJudgement(h);
}
}
private void addMissJudgement()

View File

@ -14,8 +14,7 @@ namespace osu.Game.Modes.Catch.UI
{
public CatchPlayfield()
{
RelativeSizeAxes = Axes.Y;
Size = new Vector2(512, 0.9f);
Size = new Vector2(1, 0.9f);
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;

View File

@ -15,8 +15,7 @@ namespace osu.Game.Modes.Mania.UI
{
public ManiaPlayfield(int columns)
{
RelativeSizeAxes = Axes.Both;
Size = new Vector2(columns / 20f, 1f);
Size = new Vector2(0.8f, 1f);
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Beatmaps;
@ -46,5 +47,7 @@ namespace osu.Game.Modes.Osu.UI
return new DrawableSpinner(spinner);
return null;
}
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f);
}
}

View File

@ -38,8 +38,6 @@ namespace osu.Game.Modes.Osu.UI
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(0.75f);
Add(new Drawable[]
{

View File

@ -65,7 +65,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2),
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
BlendingMode = BlendingMode.Additive,
Masking = true,
Children = new []
@ -82,7 +82,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables
Name = "Target ring (thick border)",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2),
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
Masking = true,
BorderThickness = target_ring_thick_border,
BlendingMode = BlendingMode.Additive,

View File

@ -19,15 +19,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
/// </summary>
public class CirclePiece : TaikoPiece
{
public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f;
public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f;
public const float SYMBOL_BORDER = 8;
public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER;
/// <summary>
/// The amount to scale up the base circle to show it as a "strong" piece.
/// </summary>
private const float strong_scale = 1.5f;
/// <summary>
/// The colour of the inner circle and outer glows.
/// </summary>
@ -129,10 +124,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
if (isStrong)
{
Size *= strong_scale;
Size *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE;
//default for symbols etc.
Content.Scale *= strong_scale;
Content.Scale *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE;
}
}

View File

@ -39,7 +39,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
public TaikoPiece()
{
//just a default
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2);
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
}
}
}

View File

@ -15,12 +15,12 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces
/// Any tick that is not the first for a drumroll is not filled, but is instead displayed
/// as a hollow circle. This is what controls the border width of that circle.
/// </summary>
private const float tick_border_width = TaikoHitObject.CIRCLE_RADIUS / 2 / 4;
private const float tick_border_width = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 16;
/// <summary>
/// The size of a tick.
/// </summary>
private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2;
private const float tick_size = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 4;
private bool filled;
public bool Filled

View File

@ -4,15 +4,31 @@
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Taiko.UI;
namespace osu.Game.Modes.Taiko.Objects
{
public abstract class TaikoHitObject : HitObject
{
/// <summary>
/// HitCircle radius.
/// Diameter of a circle relative to the size of the <see cref="TaikoPlayfield"/>.
/// </summary>
public const float CIRCLE_RADIUS = 42f;
public const float PLAYFIELD_RELATIVE_DIAMETER = 0.5f;
/// <summary>
/// Scale multiplier for a strong circle.
/// </summary>
public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.5f;
/// <summary>
/// Default circle diameter.
/// </summary>
public const float DEFAULT_CIRCLE_DIAMETER = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT * PLAYFIELD_RELATIVE_DIAMETER;
/// <summary>
/// Default strong circle diameter.
/// </summary>
public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE;
/// <summary>
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="ControlPoint.BeatLength"/> of 1000ms.

View File

@ -18,11 +18,6 @@ namespace osu.Game.Modes.Taiko.UI
/// </summary>
internal class HitExplosion : CircularContainer
{
/// <summary>
/// The size multiplier of a hit explosion if a hit object has been hit with the second key.
/// </summary>
private const float secondhit_size_multiplier = 1.5f;
/// <summary>
/// The judgement this hit explosion visualises.
/// </summary>
@ -34,7 +29,7 @@ namespace osu.Game.Modes.Taiko.UI
{
Judgement = judgement;
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2);
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
@ -85,7 +80,7 @@ namespace osu.Game.Modes.Taiko.UI
/// </summary>
public void VisualiseSecondHit()
{
ResizeTo(Size * secondhit_size_multiplier, 50);
ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50);
}
}
}

View File

@ -15,16 +15,6 @@ namespace osu.Game.Modes.Taiko.UI
/// </summary>
internal class HitTarget : Container
{
/// <summary>
/// Diameter of normal hit object circles.
/// </summary>
private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2;
/// <summary>
/// Diameter of strong hit object circles.
/// </summary>
private const float strong_hit_diameter = normal_diameter * 1.5f;
/// <summary>
/// The 1px inner border of the taiko playfield.
/// </summary>
@ -37,7 +27,7 @@ namespace osu.Game.Modes.Taiko.UI
public HitTarget()
{
RelativeSizeAxes = Axes.Y;
Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT);
Children = new Drawable[]
{
@ -47,7 +37,7 @@ namespace osu.Game.Modes.Taiko.UI
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Y = border_offset,
Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset),
Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset),
Alpha = 0.1f
},
new CircularContainer
@ -55,7 +45,7 @@ namespace osu.Game.Modes.Taiko.UI
Name = "Strong Hit Ring",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(strong_hit_diameter),
Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER),
Masking = true,
BorderColour = Color4.White,
BorderThickness = border_thickness,
@ -75,7 +65,7 @@ namespace osu.Game.Modes.Taiko.UI
Name = "Normal Hit Ring",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(normal_diameter),
Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER),
Masking = true,
BorderColour = Color4.White,
BorderThickness = border_thickness,
@ -96,7 +86,7 @@ namespace osu.Game.Modes.Taiko.UI
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Y = -border_offset,
Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset),
Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset),
Alpha = 0.1f
},
};

View File

@ -21,7 +21,7 @@ namespace osu.Game.Modes.Taiko.UI
{
public InputDrum()
{
Size = new Vector2(TaikoPlayfield.PLAYFIELD_HEIGHT);
Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT);
const float middle_split = 10;

View File

@ -17,6 +17,7 @@ using osu.Game.Modes.Taiko.Objects.Drawables;
using osu.Game.Modes.Taiko.Scoring;
using osu.Game.Modes.UI;
using osu.Game.Modes.Taiko.Replays;
using OpenTK;
namespace osu.Game.Modes.Taiko.UI
{
@ -100,6 +101,17 @@ namespace osu.Game.Modes.Taiko.UI
}
}
protected override Vector2 GetPlayfieldAspectAdjust()
{
const float default_relative_height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT / 768;
const float default_aspect = 16f / 9f;
float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect;
return new Vector2(1, default_relative_height * aspectAdjust);
}
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
protected override IBeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter();

View File

@ -16,21 +16,21 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Primitives;
using System.Linq;
using osu.Game.Modes.Taiko.Objects.Drawables;
using System;
namespace osu.Game.Modes.Taiko.UI
{
public class TaikoPlayfield : Playfield<TaikoHitObject, TaikoJudgement>
{
/// <summary>
/// The play field height. This is relative to the size of hit objects
/// such that the playfield is just a bit larger than strong hits.
/// The default play field height.
/// </summary>
public const float PLAYFIELD_HEIGHT = TaikoHitObject.CIRCLE_RADIUS * 2 * 2;
public const float DEFAULT_PLAYFIELD_HEIGHT = 168f;
/// <summary>
/// The offset from <see cref="left_area_size"/> which the center of the hit target lies at.
/// </summary>
private const float hit_target_offset = TaikoHitObject.CIRCLE_RADIUS * 1.5f + 40;
private const float hit_target_offset = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
/// <summary>
/// The size of the left area of the playfield. This area contains the input drum.
@ -52,13 +52,11 @@ namespace osu.Game.Modes.Taiko.UI
public TaikoPlayfield()
{
RelativeSizeAxes = Axes.X;
Height = PLAYFIELD_HEIGHT;
AddInternal(new Drawable[]
{
rightBackgroundContainer = new Container
{
Name = "Transparent playfield background",
RelativeSizeAxes = Axes.Both,
BorderThickness = 2,
Masking = true,
@ -77,76 +75,88 @@ namespace osu.Game.Modes.Taiko.UI
},
}
},
new Container
new ScaleFixContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = left_area_size },
Children = new Drawable[]
RelativeSizeAxes = Axes.X,
Height = DEFAULT_PLAYFIELD_HEIGHT,
Children = new[]
{
new Container
{
X = hit_target_offset,
Name = "Transparent playfield elements",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = left_area_size },
Children = new Drawable[]
{
hitExplosionContainer = new Container<HitExplosion>
new Container
{
Anchor = Anchor.CentreLeft,
Name = "Hit target container",
X = hit_target_offset,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
hitExplosionContainer = new Container<HitExplosion>
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
BlendingMode = BlendingMode.Additive
},
barLineContainer = new Container<DrawableBarLine>
{
RelativeSizeAxes = Axes.Both,
},
new HitTarget
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
},
hitObjectContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
judgementContainer = new Container<DrawableTaikoJudgement>
{
RelativeSizeAxes = Axes.Y,
BlendingMode = BlendingMode.Additive
},
},
},
}
},
leftBackgroundContainer = new Container
{
Name = "Left overlay",
Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT),
BorderThickness = 1,
Children = new Drawable[]
{
leftBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new InputDrum
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2),
BlendingMode = BlendingMode.Additive
RelativePositionAxes = Axes.X,
Position = new Vector2(0.10f, 0),
Scale = new Vector2(0.9f)
},
barLineContainer = new Container<DrawableBarLine>
new Box
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 10,
ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)),
},
new HitTarget
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
},
hitObjectContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
judgementContainer = new Container<DrawableTaikoJudgement>
{
RelativeSizeAxes = Axes.Both,
BlendingMode = BlendingMode.Additive
},
},
},
}
},
leftBackgroundContainer = new Container
{
Size = new Vector2(left_area_size, PLAYFIELD_HEIGHT),
BorderThickness = 1,
Children = new Drawable[]
{
leftBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new InputDrum
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.X,
Position = new Vector2(0.10f, 0),
Scale = new Vector2(0.9f)
},
new Box
{
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 10,
ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)),
}
},
}
},
topLevelHitContainer = new Container
{
Name = "Top level hit objects",
RelativeSizeAxes = Axes.Both,
}
});
@ -208,5 +218,56 @@ namespace osu.Game.Modes.Taiko.UI
else
hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();
}
/// <summary>
/// This is a very special type of container. It serves a similar purpose to <see cref="FillMode.Fit"/>, however unlike <see cref="FillMode.Fit"/>,
/// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent.
///
/// <para>
/// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable
/// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this
/// container which takes care of reversing the width adjustment while appearing transparent to the user.
/// </para>
/// </summary>
private class ScaleFixContainer : Container
{
protected override Container<Drawable> Content => widthAdjustmentContainer;
private readonly WidthAdjustmentContainer widthAdjustmentContainer;
/// <summary>
/// We only want to apply DrawScale in the Y-axis to preserve aspect ratio and <see cref="TaikoPlayfield"/> doesn't care about having its width adjusted.
/// </summary>
protected override Vector2 DrawScale => Scale * RelativeToAbsoluteFactor.Y / DrawHeight;
public ScaleFixContainer()
{
AddInternal(widthAdjustmentContainer = new WidthAdjustmentContainer { ParentDrawScaleReference = () => DrawScale.X });
}
/// <summary>
/// The container type that reverses the <see cref="Drawable.DrawScale"/> width adjustment.
/// </summary>
private class WidthAdjustmentContainer : Container
{
/// <summary>
/// This container needs to know its parent's <see cref="Drawable.DrawScale"/> so it can reverse the width adjustment caused by <see cref="Drawable.DrawScale"/>.
/// </summary>
public Func<float> ParentDrawScaleReference;
public WidthAdjustmentContainer()
{
// This container doesn't care about height, it should always fill its parent
RelativeSizeAxes = Axes.Y;
}
protected override void Update()
{
base.Update();
// Reverse the DrawScale adjustment
Width = Parent.DrawSize.X / ParentDrawScaleReference();
}
}
}
}
}

View File

@ -16,6 +16,7 @@ using System.Diagnostics;
using System.Linq;
using osu.Game.Modes.Replays;
using osu.Game.Modes.Scoring;
using OpenTK;
namespace osu.Game.Modes.UI
{
@ -167,6 +168,11 @@ namespace osu.Game.Modes.UI
{
public event Action<TJudgement> OnJudgement;
/// <summary>
/// Whether to apply adjustments to the child <see cref="Playfield{TObject,TJudgement}"/> based on our own size.
/// </summary>
public bool AspectAdjust = true;
public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor;
protected override Container<Drawable> Content => content;
@ -219,6 +225,19 @@ namespace osu.Game.Modes.UI
Playfield.PostProcess();
}
protected override void Update()
{
base.Update();
Playfield.Size = AspectAdjust ? GetPlayfieldAspectAdjust() : Vector2.One;
}
/// <summary>
/// In some cases we want to apply changes to the relative size of our contained <see cref="Playfield{TObject, TJudgement}"/> based on custom conditions.
/// </summary>
/// <returns></returns>
protected virtual Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); //a sane default
/// <summary>
/// Triggered when an object's Judgement is updated.
/// </summary>

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Modes.Objects;
@ -63,6 +64,12 @@ namespace osu.Game.Modes.UI
Add(HitObjects);
}
public override Axes RelativeSizeAxes
{
get { return Axes.Both; }
set { throw new InvalidOperationException($@"{nameof(Playfield<TObject, TJudgement>)}'s {nameof(RelativeSizeAxes)} should never be changed from {Axes.Both}"); }
}
/// <summary>
/// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield.
/// </summary>

View File

@ -0,0 +1,82 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Input;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio;
using System;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using OpenTK.Graphics;
namespace osu.Game.Screens.Play
{
public class HotkeyRetryOverlay : Container
{
public Action Action;
private SampleChannel retrySample;
private Box overlay;
private const int activate_delay = 400;
private const int fadeout_delay = 200;
private bool fired;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
retrySample = audio.Sample.Get(@"Menu/menuback");
RelativeSizeAxes = Axes.Both;
AlwaysPresent = true;
Children = new Drawable[]
{
overlay = new Box
{
Alpha = 0,
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
}
};
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Repeat) return false;
if (args.Key == Key.Tilde)
{
overlay.FadeIn(activate_delay, EasingTypes.Out);
return true;
}
return base.OnKeyDown(state, args);
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
if (args.Key == Key.Tilde && !fired)
{
overlay.FadeOut(fadeout_delay, EasingTypes.Out);
return true;
}
return base.OnKeyUp(state, args);
}
protected override void Update()
{
base.Update();
if (!fired && overlay.Alpha == 1)
{
fired = true;
retrySample.Play();
Action?.Invoke();
}
}
}
}

View File

@ -157,6 +157,10 @@ namespace osu.Game.Screens.Play
{
OnRetry = Restart,
OnQuit = Exit,
},
new HotkeyRetryOverlay
{
Action = Restart,
}
};
}

View File

@ -180,6 +180,7 @@
<Compile Include="Screens\Backgrounds\BackgroundScreenEmpty.cs" />
<Compile Include="Screens\Charts\ChartInfo.cs" />
<Compile Include="Screens\Edit\Editor.cs" />
<Compile Include="Screens\Play\HotkeyRetryOverlay.cs" />
<Compile Include="Screens\ScreenWhiteBox.cs" />
<Compile Include="Screens\Loader.cs" />
<Compile Include="Screens\Menu\Button.cs" />