1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 15:33:05 +08:00

Merge pull request #205 from peppy/scoring

Wire up basic scoring.
This commit is contained in:
Thomas Müller 2016-11-29 20:56:53 +01:00 committed by GitHub
commit d0065d2d20
22 changed files with 494 additions and 213 deletions

View File

@ -0,0 +1,148 @@
using System.Collections.Generic;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
{
public class BezierApproximator
{
private int count;
private List<Vector2> controlPoints;
private Vector2[] subdivisionBuffer1;
private Vector2[] subdivisionBuffer2;
private const float TOLERANCE = 0.5f;
private const float TOLERANCE_SQ = TOLERANCE * TOLERANCE;
public BezierApproximator(List<Vector2> controlPoints)
{
this.controlPoints = controlPoints;
count = controlPoints.Count;
subdivisionBuffer1 = new Vector2[count];
subdivisionBuffer2 = new Vector2[count * 2 - 1];
}
/// <summary>
/// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds.
/// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function
/// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts
/// need to have a denser approximation to be more "flat".
/// </summary>
/// <param name="controlPoints">The control points to check for flatness.</param>
/// <returns>Whether the control points are flat enough.</returns>
private static bool IsFlatEnough(Vector2[] controlPoints)
{
for (int i = 1; i < controlPoints.Length - 1; i++)
if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > TOLERANCE_SQ)
return false;
return true;
}
/// <summary>
/// Subdivides n control points representing a bezier curve into 2 sets of n control points, each
/// describing a bezier curve equivalent to a half of the original curve. Effectively this splits
/// the original curve into 2 curves which result in the original curve when pieced back together.
/// </summary>
/// <param name="controlPoints">The control points to split.</param>
/// <param name="l">Output: The control points corresponding to the left half of the curve.</param>
/// <param name="r">Output: The control points corresponding to the right half of the curve.</param>
private void Subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r)
{
Vector2[] midpoints = subdivisionBuffer1;
for (int i = 0; i < count; ++i)
midpoints[i] = controlPoints[i];
for (int i = 0; i < count; i++)
{
l[i] = midpoints[0];
r[count - i - 1] = midpoints[count - i - 1];
for (int j = 0; j < count - i - 1; j++)
midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2;
}
}
/// <summary>
/// This uses <a href="https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm">De Casteljau's algorithm</a> to obtain an optimal
/// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points.
/// </summary>
/// <param name="controlPoints">The control points describing the bezier curve to be approximated.</param>
/// <param name="output">The points representing the resulting piecewise-linear approximation.</param>
private void Approximate(Vector2[] controlPoints, List<Vector2> output)
{
Vector2[] l = subdivisionBuffer2;
Vector2[] r = subdivisionBuffer1;
Subdivide(controlPoints, l, r);
for (int i = 0; i < count - 1; ++i)
l[count + i] = r[i + 1];
output.Add(controlPoints[0]);
for (int i = 1; i < count - 1; ++i)
{
int index = 2 * i;
Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]);
output.Add(p);
}
}
/// <summary>
/// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing
/// the control points until their approximation error vanishes below a given threshold.
/// </summary>
/// <param name="controlPoints">The control points describing the curve.</param>
/// <returns>A list of vectors representing the piecewise-linear approximation.</returns>
public List<Vector2> CreateBezier()
{
List<Vector2> output = new List<Vector2>();
if (count == 0)
return output;
Stack<Vector2[]> toFlatten = new Stack<Vector2[]>();
Stack<Vector2[]> freeBuffers = new Stack<Vector2[]>();
// "toFlatten" contains all the curves which are not yet approximated well enough.
// We use a stack to emulate recursion without the risk of running into a stack overflow.
// (More specifically, we iteratively and adaptively refine our curve with a
// <a href="https://en.wikipedia.org/wiki/Depth-first_search">Depth-first search</a>
// over the tree resulting from the subdivisions we make.)
toFlatten.Push(controlPoints.ToArray());
Vector2[] leftChild = subdivisionBuffer2;
while (toFlatten.Count > 0)
{
Vector2[] parent = toFlatten.Pop();
if (IsFlatEnough(parent))
{
// If the control points we currently operate on are sufficiently "flat", we use
// an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation
// of the bezier curve represented by our control points, consisting of the same amount
// of points as there are control points.
Approximate(parent, output);
freeBuffers.Push(parent);
continue;
}
// If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep
// subdividing the curve we are currently operating on.
Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count];
Subdivide(parent, leftChild, rightChild);
// We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration.
for (int i = 0; i < count; ++i)
parent[i] = leftChild[i];
toFlatten.Push(rightChild);
toFlatten.Push(parent);
}
output.Add(controlPoints[count - 1]);
return output;
}
}
}

View File

@ -74,7 +74,5 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Hit100,
[Description(@"300")]
Hit300,
[Description(@"500")]
Hit500
}
}

View File

@ -75,12 +75,24 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
ball.Position = slider.Curve.PositionAt(t);
}
protected override void CheckJudgement(bool userTriggered)
{
var j = Judgement as OsuJudgementInfo;
var sc = startCircle.Judgement as OsuJudgementInfo;
if (!userTriggered && Time.Current >= HitObject.EndTime)
{
j.Score = sc.Score;
j.Result = sc.Result;
}
}
protected override void UpdateState(ArmedState state)
{
base.UpdateState(state);
Delay(HitObject.Duration);
FadeOut(100);
FadeOut(300);
}
private class Ball : Container

View File

@ -1,9 +1,7 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Database;
using OpenTK;
using osu.Game.Beatmaps;
using System;
@ -25,188 +23,6 @@ namespace osu.Game.Modes.Osu.Objects
public SliderCurve Curve;
}
public class SliderCurve
{
public double Length;
public List<Vector2> Path;
public CurveTypes CurveType;
private List<Vector2> calculatedPath;
public void Calculate()
{
switch (CurveType)
{
case CurveTypes.Linear:
calculatedPath = Path;
break;
default:
var bezier = new BezierApproximator(Path);
calculatedPath = bezier.CreateBezier();
break;
}
}
public Vector2 PositionAt(double progress)
{
progress = MathHelper.Clamp(progress, 0, 1);
double index = progress * (calculatedPath.Count - 1);
int flooredIndex = (int)index;
Vector2 pos = calculatedPath[flooredIndex];
if (index != flooredIndex)
pos += (calculatedPath[flooredIndex + 1] - pos) * (float)(index - flooredIndex);
return pos;
}
}
public class BezierApproximator
{
private int count;
private List<Vector2> controlPoints;
private Vector2[] subdivisionBuffer1;
private Vector2[] subdivisionBuffer2;
private const float TOLERANCE = 0.5f;
private const float TOLERANCE_SQ = TOLERANCE * TOLERANCE;
public BezierApproximator(List<Vector2> controlPoints)
{
this.controlPoints = controlPoints;
count = controlPoints.Count;
subdivisionBuffer1 = new Vector2[count];
subdivisionBuffer2 = new Vector2[count * 2 - 1];
}
/// <summary>
/// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds.
/// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function
/// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts
/// need to have a denser approximation to be more "flat".
/// </summary>
/// <param name="controlPoints">The control points to check for flatness.</param>
/// <returns>Whether the control points are flat enough.</returns>
private static bool IsFlatEnough(Vector2[] controlPoints)
{
for (int i = 1; i < controlPoints.Length - 1; i++)
if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > TOLERANCE_SQ)
return false;
return true;
}
/// <summary>
/// Subdivides n control points representing a bezier curve into 2 sets of n control points, each
/// describing a bezier curve equivalent to a half of the original curve. Effectively this splits
/// the original curve into 2 curves which result in the original curve when pieced back together.
/// </summary>
/// <param name="controlPoints">The control points to split.</param>
/// <param name="l">Output: The control points corresponding to the left half of the curve.</param>
/// <param name="r">Output: The control points corresponding to the right half of the curve.</param>
private void Subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r)
{
Vector2[] midpoints = subdivisionBuffer1;
for (int i = 0; i < count; ++i)
midpoints[i] = controlPoints[i];
for (int i = 0; i < count; i++)
{
l[i] = midpoints[0];
r[count - i - 1] = midpoints[count - i - 1];
for (int j = 0; j < count - i - 1; j++)
midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2;
}
}
/// <summary>
/// This uses <a href="https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm">De Casteljau's algorithm</a> to obtain an optimal
/// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points.
/// </summary>
/// <param name="controlPoints">The control points describing the bezier curve to be approximated.</param>
/// <param name="output">The points representing the resulting piecewise-linear approximation.</param>
private void Approximate(Vector2[] controlPoints, List<Vector2> output)
{
Vector2[] l = subdivisionBuffer2;
Vector2[] r = subdivisionBuffer1;
Subdivide(controlPoints, l, r);
for (int i = 0; i < count - 1; ++i)
l[count + i] = r[i + 1];
output.Add(controlPoints[0]);
for (int i = 1; i < count - 1; ++i)
{
int index = 2 * i;
Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]);
output.Add(p);
}
}
/// <summary>
/// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing
/// the control points until their approximation error vanishes below a given threshold.
/// </summary>
/// <param name="controlPoints">The control points describing the curve.</param>
/// <returns>A list of vectors representing the piecewise-linear approximation.</returns>
public List<Vector2> CreateBezier()
{
List<Vector2> output = new List<Vector2>();
if (count == 0)
return output;
Stack<Vector2[]> toFlatten = new Stack<Vector2[]>();
Stack<Vector2[]> freeBuffers = new Stack<Vector2[]>();
// "toFlatten" contains all the curves which are not yet approximated well enough.
// We use a stack to emulate recursion without the risk of running into a stack overflow.
// (More specifically, we iteratively and adaptively refine our curve with a
// <a href="https://en.wikipedia.org/wiki/Depth-first_search">Depth-first search</a>
// over the tree resulting from the subdivisions we make.)
toFlatten.Push(controlPoints.ToArray());
Vector2[] leftChild = subdivisionBuffer2;
while (toFlatten.Count > 0)
{
Vector2[] parent = toFlatten.Pop();
if (IsFlatEnough(parent))
{
// If the control points we currently operate on are sufficiently "flat", we use
// an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation
// of the bezier curve represented by our control points, consisting of the same amount
// of points as there are control points.
Approximate(parent, output);
freeBuffers.Push(parent);
continue;
}
// If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep
// subdividing the curve we are currently operating on.
Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count];
Subdivide(parent, leftChild, rightChild);
// We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration.
for (int i = 0; i < count; ++i)
parent[i] = leftChild[i];
toFlatten.Push(rightChild);
toFlatten.Push(parent);
}
output.Add(controlPoints[count - 1]);
return output;
}
}
public enum CurveTypes
{
Catmull,

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
{
public class SliderCurve
{
public double Length;
public List<Vector2> Path;
public CurveTypes CurveType;
private List<Vector2> calculatedPath;
public void Calculate()
{
switch (CurveType)
{
case CurveTypes.Linear:
calculatedPath = Path;
break;
default:
var bezier = new BezierApproximator(Path);
calculatedPath = bezier.CreateBezier();
break;
}
}
public Vector2 PositionAt(double progress)
{
progress = MathHelper.Clamp(progress, 0, 1);
double index = progress * (calculatedPath.Count - 1);
int flooredIndex = (int)index;
Vector2 pos = calculatedPath[flooredIndex];
if (index != flooredIndex)
pos += (calculatedPath[flooredIndex + 1] - pos) * (float)(index - flooredIndex);
return pos;
}
}
}

View File

@ -17,6 +17,8 @@ namespace osu.Game.Modes.Osu
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
protected override PlayMode PlayMode => PlayMode.Osu;
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Game.Modes.Osu
{
class OsuScore : Score
{
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects.Drawables;
namespace osu.Game.Modes.Osu
{
class OsuScoreProcessor : ScoreProcessor
{
protected override void UpdateCalculations(JudgementInfo judgement)
{
if (judgement != null)
{
switch (judgement.Result)
{
case HitResult.Hit:
Combo.Value++;
break;
case HitResult.Miss:
Combo.Value = 0;
break;
}
}
int score = 0;
int maxScore = 0;
foreach (OsuJudgementInfo j in Judgements)
{
switch (j.Score)
{
case OsuScoreResult.Miss:
maxScore += 300;
break;
case OsuScoreResult.Hit50:
score += 50;
maxScore += 300;
break;
case OsuScoreResult.Hit100:
score += 100;
maxScore += 300;
break;
case OsuScoreResult.Hit300:
score += 300;
maxScore += 300;
break;
}
}
TotalScore.Value = score;
Accuracy.Value = (double)score / maxScore;
}
}
}

View File

@ -41,6 +41,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Objects\BezierApproximator.cs" />
<Compile Include="Objects\Drawables\DrawableOsuHitObject.cs" />
<Compile Include="Objects\Drawables\Pieces\ApproachCircle.cs" />
<Compile Include="Objects\Drawables\Pieces\CirclePiece.cs" />
@ -53,6 +54,9 @@
<Compile Include="Objects\Drawables\Pieces\RingPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\Triangles.cs" />
<Compile Include="Objects\OsuHitObjectParser.cs" />
<Compile Include="Objects\SliderCurve.cs" />
<Compile Include="OsuScore.cs" />
<Compile Include="OsuScoreProcessor.cs" />
<Compile Include="UI\OsuComboCounter.cs" />
<Compile Include="UI\OsuHitRenderer.cs" />
<Compile Include="UI\OsuPlayfield.cs" />

View File

@ -18,6 +18,8 @@ namespace osu.Game.Modes.Catch
protected override PlayMode PlayMode => PlayMode.Catch;
public override ScoreProcessor CreateScoreProcessor() => null;
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
}
}

View File

@ -19,6 +19,8 @@ namespace osu.Game.Modes.Mania
protected override PlayMode PlayMode => PlayMode.Mania;
public override ScoreProcessor CreateScoreProcessor() => null;
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
}
}

View File

@ -1,6 +1,7 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
@ -18,6 +19,8 @@ namespace osu.Game.Modes.Taiko
protected override PlayMode PlayMode => PlayMode.Taiko;
public override ScoreProcessor CreateScoreProcessor() => null;
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
}
}

View File

@ -13,13 +13,11 @@ namespace osu.Game.Modes.Objects.Drawables
{
public abstract class DrawableHitObject : Container, IStateful<ArmedState>
{
//todo: move to a more central implementation. this logic should not be at a drawable level.
public Action<DrawableHitObject, JudgementInfo> OnHit;
public Action<DrawableHitObject, JudgementInfo> OnMiss;
public event Action<DrawableHitObject, JudgementInfo> OnJudgement;
public Container<DrawableHitObject> ChildObjects;
protected JudgementInfo Judgement;
public JudgementInfo Judgement;
public abstract JudgementInfo CreateJudgementInfo();
@ -73,20 +71,20 @@ namespace osu.Game.Modes.Objects.Drawables
{
default:
State = ArmedState.Hit;
OnHit?.Invoke(this, Judgement);
break;
case HitResult.Miss:
State = ArmedState.Miss;
OnMiss?.Invoke(this, Judgement);
break;
}
OnJudgement?.Invoke(this, Judgement);
return true;
}
protected virtual void CheckJudgement(bool userTriggered)
{
//todo: consider making abstract.
}
protected override void Update()
@ -113,6 +111,7 @@ namespace osu.Game.Modes.Objects.Drawables
public class JudgementInfo
{
public ulong? ComboAtHit;
public HitResult? Result;
public double TimeOffset;
}

View File

@ -18,6 +18,8 @@ namespace osu.Game.Modes
public abstract ScoreOverlay CreateScoreOverlay();
public abstract ScoreProcessor CreateScoreProcessor();
public abstract HitRenderer CreateHitRendererWith(List<HitObject> objects);
public abstract HitObjectParser CreateHitObjectParser();

19
osu.Game/Modes/Score.cs Normal file
View File

@ -0,0 +1,19 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Game.Modes
{
public class Score
{
public double TotalScore { get; set; }
public double Accuracy { get; set; }
public double Combo { get; set; }
public double MaxCombo { get; set; }
}
}

View File

@ -0,0 +1,54 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Framework.Configuration;
using osu.Game.Modes.Objects.Drawables;
namespace osu.Game.Modes
{
public abstract class ScoreProcessor
{
public virtual Score GetScore() => new Score()
{
TotalScore = TotalScore,
Combo = Combo,
MaxCombo = HighestCombo,
Accuracy = Accuracy
};
public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 };
public readonly BindableDouble Accuracy = new BindableDouble { MinValue = 0, MaxValue = 1 };
public readonly BindableInt Combo = new BindableInt();
public readonly BindableInt HighestCombo = new BindableInt();
public readonly List<JudgementInfo> Judgements = new List<JudgementInfo>();
public ScoreProcessor()
{
Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); };
}
public void AddJudgement(JudgementInfo judgement)
{
Judgements.Add(judgement);
UpdateCalculations(judgement);
judgement.ComboAtHit = (ulong)Combo.Value;
}
/// <summary>
/// Update any values that potentially need post-processing on a judgement change.
/// </summary>
/// <param name="newJudgement">A new JudgementInfo that triggered this calculation. May be null.</param>
protected abstract void UpdateCalculations(JudgementInfo newJudgement);
}
}

View File

@ -262,5 +262,13 @@ namespace osu.Game.Modes.UI
(d as ComboCounter).DisplayedCount = CurrentValue;
}
}
public void Set(ulong value)
{
if (value == 0)
Roll();
else
Count = value;
}
}
}

View File

@ -14,11 +14,21 @@ namespace osu.Game.Modes.UI
{
public abstract class HitRenderer : Container
{
public Action<HitObject> OnHit;
public Action<HitObject> OnMiss;
public event Action<JudgementInfo> OnJudgement;
public event Action OnAllJudged;
protected void TriggerOnJudgement(JudgementInfo j)
{
OnJudgement?.Invoke(j);
if (AllObjectsJudged)
OnAllJudged?.Invoke();
}
protected Playfield Playfield;
public bool AllObjectsJudged => Playfield.HitObjects.Children.First()?.Judgement.Result != null; //reverse depth sort means First() instead of Last().
public IEnumerable<DrawableHitObject> DrawableObjects => Playfield.HitObjects.Children;
}
@ -68,22 +78,13 @@ namespace osu.Game.Modes.UI
if (drawableObject == null) continue;
drawableObject.OnHit = onHit;
drawableObject.OnMiss = onMiss;
drawableObject.OnJudgement += onJudgement;
Playfield.Add(drawableObject);
}
}
private void onMiss(DrawableHitObject obj, JudgementInfo judgement)
{
OnMiss?.Invoke(obj.HitObject);
}
private void onHit(DrawableHitObject obj, JudgementInfo judgement)
{
OnHit?.Invoke(obj.HitObject);
}
private void onJudgement(DrawableHitObject o, JudgementInfo j) => TriggerOnJudgement(j);
protected abstract DrawableHitObject GetVisualRepresentation(T h);
}

View File

@ -15,6 +15,7 @@ namespace osu.Game.Modes.UI
public ComboCounter ComboCounter;
public ScoreCounter ScoreCounter;
public PercentageCounter AccuracyCounter;
public Score Score { get; set; }
protected abstract KeyCounterCollection CreateKeyCounter();
protected abstract ComboCounter CreateComboCounter();
@ -45,5 +46,13 @@ namespace osu.Game.Modes.UI
AccuracyCounter = CreateAccuracyCounter(),
};
}
public void BindProcessor(ScoreProcessor processor)
{
//bind processor bindables to combocounter, score display etc.
processor.TotalScore.ValueChanged += delegate { ScoreCounter?.Set((ulong)processor.TotalScore.Value); };
processor.Accuracy.ValueChanged += delegate { AccuracyCounter?.Set((float)processor.Accuracy.Value); };
processor.Combo.ValueChanged += delegate { ComboCounter?.Set((ulong)processor.Combo.Value); };
}
}
}

View File

@ -19,6 +19,8 @@ using OpenTK.Input;
using MouseState = osu.Framework.Input.MouseState;
using OpenTK;
using osu.Framework.GameModes;
using osu.Game.Modes.UI;
using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Play
{
@ -38,6 +40,9 @@ namespace osu.Game.Screens.Play
private Ruleset ruleset;
private ScoreProcessor scoreProcessor;
private HitRenderer hitRenderer;
[BackgroundDependencyLoader]
private void load(AudioManager audio, BeatmapDatabase beatmaps, OsuGameBase game)
{
@ -84,10 +89,12 @@ namespace osu.Game.Screens.Play
ruleset = Ruleset.GetRuleset(usablePlayMode);
var scoreOverlay = ruleset.CreateScoreOverlay();
var hitRenderer = ruleset.CreateHitRendererWith(beatmap.HitObjects);
scoreOverlay.BindProcessor(scoreProcessor = ruleset.CreateScoreProcessor());
hitRenderer.OnHit += delegate (HitObject h) { scoreOverlay.OnHit(h); };
hitRenderer.OnMiss += delegate (HitObject h) { scoreOverlay.OnMiss(h); };
hitRenderer = ruleset.CreateHitRendererWith(beatmap.HitObjects);
hitRenderer.OnJudgement += scoreProcessor.AddJudgement;
hitRenderer.OnAllJudged += hitRenderer_OnAllJudged;
if (Autoplay)
hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.State = ArmedState.Hit));
@ -106,6 +113,18 @@ namespace osu.Game.Screens.Play
};
}
private void hitRenderer_OnAllJudged()
{
Delay(1000);
Schedule(delegate
{
Push(new Results
{
Score = scoreProcessor.GetScore()
});
});
}
protected override void OnEntering(GameMode last)
{
base.OnEntering(last);

View File

@ -2,19 +2,29 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using osu.Game.Modes;
using osu.Game.Screens.Backgrounds;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Screens.Ranking
{
class Results : GameModeWhiteBox
class Results : OsuGameMode
{
protected override BackgroundMode CreateBackground() => new BackgroundModeCustom(@"Backgrounds/bg4");
protected override BackgroundMode CreateBackground() => new BackgroundModeBeatmap(Beatmap);
private static readonly Vector2 BACKGROUND_BLUR = new Vector2(20);
ScoreDisplay scoreDisplay;
protected override void OnEntering(GameMode last)
{
base.OnEntering(last);
Background.Schedule(() => Background.FadeColour(Color4.DarkGray, 500));
Background.Schedule(() => (Background as BackgroundModeBeatmap)?.BlurTo(BACKGROUND_BLUR, 1000));
}
protected override bool OnExiting(GameMode next)
@ -22,5 +32,63 @@ namespace osu.Game.Screens.Ranking
Background.Schedule(() => Background.FadeColour(Color4.White, 500));
return base.OnExiting(next);
}
public Score Score
{
set
{
scoreDisplay?.FadeOut(500);
scoreDisplay?.Expire();
scoreDisplay = new ScoreDisplay(value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
Add(scoreDisplay);
scoreDisplay.FadeIn(500);
scoreDisplay.ScaleTo(0.1f);
scoreDisplay.ScaleTo(1, 1000, EasingTypes.OutElastic);
scoreDisplay.RotateTo(360 * 5, 1000, EasingTypes.OutElastic);
}
}
}
class ScoreDisplay : Container
{
public ScoreDisplay(Score s)
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
new FlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FlowDirection.VerticalOnly,
Children = new Drawable[]
{
new SpriteText
{
TextSize = 40,
Text = $@"Accuracy: {s.Accuracy:#0.00%}",
},
new SpriteText
{
TextSize = 40,
Text = $@"Score: {s.TotalScore}",
},
new SpriteText
{
TextSize = 40,
Text = $@"MaxCombo: {s.MaxCombo}",
}
}
}
};
}
}
}

View File

@ -67,6 +67,8 @@
<Compile Include="Graphics\Cursor\CursorTrail.cs" />
<Compile Include="Graphics\UserInterface\BackButton.cs" />
<Compile Include="Modes\Objects\HitObjectParser.cs" />
<Compile Include="Modes\Score.cs" />
<Compile Include="Modes\ScoreProcesssor.cs" />
<Compile Include="Overlays\DragBar.cs" />
<Compile Include="Overlays\MusicController.cs" />
<Compile Include="Beatmaps\Beatmap.cs" />