Merge pull request #1277 from peppy/ctb-improvements

osu!catch improvements
This commit is contained in:
Dan Balasescu 2017-09-19 21:50:18 +09:00 committed by GitHub
commit 2e799aaf67
12 changed files with 196 additions and 33 deletions

View File

@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
yield return new Fruit yield return new Fruit
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
Position = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X NewCombo = (obj as IHasCombo)?.NewCombo ?? false,
X = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X
}; };
} }
} }

View File

@ -0,0 +1,38 @@
// 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.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
internal class CatchBeatmapProcessor : BeatmapProcessor<CatchBaseHit>
{
public override void PostProcess(Beatmap<CatchBaseHit> beatmap)
{
if (beatmap.ComboColors.Count == 0)
return;
int comboIndex = 0;
int colourIndex = 0;
CatchBaseHit lastObj = null;
foreach (var obj in beatmap.HitObjects)
{
if (obj.NewCombo)
{
if (lastObj != null) lastObj.LastInCombo = true;
comboIndex = 0;
colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count;
}
obj.ComboIndex = comboIndex++;
obj.ComboColour = beatmap.ComboColors[colourIndex];
lastObj = obj;
}
}
}
}

View File

@ -2,11 +2,23 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public abstract class CatchBaseHit : HitObject public abstract class CatchBaseHit : HitObject, IHasXPosition, IHasCombo
{ {
public float Position { get; set; } public float X { get; set; }
public Color4 ComboColour { get; set; } = Color4.Gray;
public int ComboIndex { get; set; }
public virtual bool NewCombo { get; set; }
/// <summary>
/// The next fruit starts a new combo. Used for explodey.
/// </summary>
public virtual bool LastInCombo { get; set; }
} }
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableFruit : DrawableScrollingHitObject<CatchBaseHit> public class DrawableFruit : DrawableScrollingHitObject<CatchBaseHit>
{ {
private const float pulp_size = 30; private const float pulp_size = 20;
private class Pulp : Circle, IHasAccentColour private class Pulp : Circle, IHasAccentColour
{ {
@ -26,15 +26,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
Size = new Vector2(pulp_size); Size = new Vector2(pulp_size);
EdgeEffect = new EdgeEffectParameters Blending = BlendingMode.Additive;
{ Colour = Color4.White.Opacity(0.9f);
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = AccentColour.Opacity(0.5f),
};
} }
public Color4 AccentColour { get; set; } = Color4.White; private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Lighten(100),
};
}
}
} }
@ -42,12 +53,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
: base(h) : base(h)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Size = new Vector2(pulp_size * 2, pulp_size * 2.6f); Size = new Vector2(pulp_size * 2.2f, pulp_size * 2.8f);
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
X = h.Position; X = h.X;
Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1); AccentColour = HitObject.ComboColour;
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
} }
@ -71,6 +84,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Scale = new Vector2(0.6f), Scale = new Vector2(0.6f),
}, },
new Pulp new Pulp
@ -78,6 +92,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
AccentColour = AccentColour,
Y = -0.08f Y = -0.08f
}, },
new Pulp new Pulp
@ -85,6 +100,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
AccentColour = AccentColour,
Y = -0.08f Y = -0.08f
}, },
new Pulp new Pulp
@ -92,6 +108,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
}, },
} }
} }

View File

@ -1,7 +1,10 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -9,13 +12,22 @@ namespace osu.Game.Rulesets.Catch.Scoring
{ {
internal class CatchScoreProcessor : ScoreProcessor<CatchBaseHit> internal class CatchScoreProcessor : ScoreProcessor<CatchBaseHit>
{ {
public CatchScoreProcessor()
{
}
public CatchScoreProcessor(RulesetContainer<CatchBaseHit> rulesetContainer) public CatchScoreProcessor(RulesetContainer<CatchBaseHit> rulesetContainer)
: base(rulesetContainer) : base(rulesetContainer)
{ {
} }
protected override void SimulateAutoplay(Beatmap<CatchBaseHit> beatmap)
{
foreach (var obj in beatmap.HitObjects)
{
var fruit = obj as Fruit;
if (fruit != null)
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
}
base.SimulateAutoplay(beatmap);
}
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
{
protected override Beatmap CreateBeatmap()
{
var beatmap = new Beatmap();
for (int i = 0; i < 256; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap;
}
}
}

View File

@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.UI
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
protected override BeatmapProcessor<CatchBaseHit> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter() => new CatchBeatmapConverter(); protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter() => new CatchBeatmapConverter();
protected override Playfield CreatePlayfield() => new CatchPlayfield(); protected override Playfield CreatePlayfield() => new CatchPlayfield();

View File

@ -11,7 +11,6 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
@ -20,20 +19,25 @@ namespace osu.Game.Rulesets.Catch.UI
public class CatcherArea : Container public class CatcherArea : Container
{ {
private Catcher catcher; private Catcher catcher;
private Container explodingFruitContainer;
public void Add(DrawableHitObject fruit, Vector2 screenPosition) => catcher.AddToStack(fruit, screenPosition); public void Add(DrawableHitObject fruit, Vector2 screenPosition) => catcher.AddToStack(fruit, screenPosition);
public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.Position) < catcher.DrawSize.X / DrawSize.X / 2; public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
explodingFruitContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
catcher = new Catcher catcher = new Catcher
{ {
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.TopLeft, ExplodingFruitTarget = explodingFruitContainer,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
X = 0.5f, X = 0.5f,
} }
@ -51,18 +55,30 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
private Texture texture; private Texture texture;
private Container<DrawableHitObject> caughtFruit;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(TextureStore textures)
{ {
texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Child = createCatcherSprite(); Children = new Drawable[]
{
createCatcherSprite(),
caughtFruit = new Container<DrawableHitObject>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
}
};
} }
private int currentDirection; private int currentDirection;
private bool dashing; private bool dashing;
public Container ExplodingFruitTarget;
protected bool Dashing protected bool Dashing
{ {
get { return dashing; } get { return dashing; }
@ -139,16 +155,21 @@ namespace osu.Game.Rulesets.Catch.UI
return false; return false;
} }
/// <summary>
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
/// </summary>
private const double base_speed = 1.0 / 512;
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
if (currentDirection == 0) return; if (currentDirection == 0) return;
float speed = Dashing ? 1.5f : 1; double dashModifier = Dashing ? 1 : 0.5;
Scale = new Vector2(Math.Sign(currentDirection), 1); Scale = new Vector2(Math.Sign(currentDirection), 1);
X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime / 1800 * speed, 0, 1); X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
} }
public void AddToStack(DrawableHitObject fruit, Vector2 absolutePosition) public void AddToStack(DrawableHitObject fruit, Vector2 absolutePosition)
@ -163,13 +184,45 @@ namespace osu.Game.Rulesets.Catch.UI
float distance = fruit.DrawSize.X / 2 * fruit.Scale.X; float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
while (Children.OfType<DrawableFruit>().Any(f => Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance)) while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
{ {
fruit.X += RNG.Next(-5, 5); fruit.X += RNG.Next(-5, 5);
fruit.Y -= RNG.Next(0, 5); fruit.Y -= RNG.Next(0, 5);
} }
Add(fruit); caughtFruit.Add(fruit);
if (((CatchBaseHit)fruit.HitObject).LastInCombo)
explode();
}
private void explode()
{
var fruit = caughtFruit.ToArray();
foreach (var f in fruit)
{
var originalX = f.X * Scale.X;
if (ExplodingFruitTarget != null)
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f);
ExplodingFruitTarget.Add(f);
}
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
f.Expire();
}
} }
} }
} }

View File

@ -47,6 +47,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Beatmaps\CatchBeatmapConverter.cs" /> <Compile Include="Beatmaps\CatchBeatmapConverter.cs" />
<Compile Include="Beatmaps\CatchBeatmapProcessor.cs" />
<Compile Include="CatchDifficultyCalculator.cs" /> <Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="CatchInputManager.cs" /> <Compile Include="CatchInputManager.cs" />
<Compile Include="Scoring\CatchScoreProcessor.cs" /> <Compile Include="Scoring\CatchScoreProcessor.cs" />
@ -57,6 +58,7 @@
<Compile Include="Objects\Fruit.cs" /> <Compile Include="Objects\Fruit.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tests\TestCaseCatcher.cs" /> <Compile Include="Tests\TestCaseCatcher.cs" />
<Compile Include="Tests\TestCaseCatchPlayer.cs" />
<Compile Include="UI\CatcherArea.cs" /> <Compile Include="UI\CatcherArea.cs" />
<Compile Include="UI\CatchRulesetContainer.cs" /> <Compile Include="UI\CatchRulesetContainer.cs" />
<Compile Include="UI\CatchPlayfield.cs" /> <Compile Include="UI\CatchPlayfield.cs" />

View File

@ -16,10 +16,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
{ {
internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject> internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject>
{ {
public OsuScoreProcessor()
{
}
public OsuScoreProcessor(RulesetContainer<OsuHitObject> rulesetContainer) public OsuScoreProcessor(RulesetContainer<OsuHitObject> rulesetContainer)
: base(rulesetContainer) : base(rulesetContainer)
{ {

View File

@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps
{ {
BeatmapInfo = beatmapInfo; BeatmapInfo = beatmapInfo;
BeatmapSetInfo = beatmapInfo.BeatmapSet; BeatmapSetInfo = beatmapInfo.BeatmapSet;
Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo.Metadata; Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
Mods.ValueChanged += mods => applyRateAdjustments(); Mods.ValueChanged += mods => applyRateAdjustments();
} }

View File

@ -16,7 +16,7 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
internal class TestCasePlayer : OsuTestCase public class TestCasePlayer : OsuTestCase
{ {
protected Player Player; protected Player Player;
private RulesetStore rulesets; private RulesetStore rulesets;
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual
loadPlayerFor(rulesets.Query<RulesetInfo>().First()); loadPlayerFor(rulesets.Query<RulesetInfo>().First());
} }
private void loadPlayerFor(RulesetInfo r) protected virtual Beatmap CreateBeatmap()
{ {
Beatmap beatmap; Beatmap beatmap;
@ -53,6 +53,13 @@ namespace osu.Game.Tests.Visual
using (var reader = new StreamReader(stream)) using (var reader = new StreamReader(stream))
beatmap = BeatmapDecoder.GetDecoder(reader).Decode(reader); beatmap = BeatmapDecoder.GetDecoder(reader).Decode(reader);
return beatmap;
}
private void loadPlayerFor(RulesetInfo r)
{
var beatmap = CreateBeatmap();
beatmap.BeatmapInfo.Ruleset = r; beatmap.BeatmapInfo.Ruleset = r;
var instance = r.CreateInstance(); var instance = r.CreateInstance();