1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-21 23:27:21 +08:00

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
{
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
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
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>
{
private const float pulp_size = 30;
private const float pulp_size = 20;
private class Pulp : Circle, IHasAccentColour
{
@ -26,15 +26,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
Size = new Vector2(pulp_size);
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = AccentColour.Opacity(0.5f),
};
Blending = BlendingMode.Additive;
Colour = Color4.White.Opacity(0.9f);
}
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)
{
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;
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;
}
@ -71,6 +84,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Scale = new Vector2(0.6f),
},
new Pulp
@ -78,6 +92,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AccentColour = AccentColour,
Y = -0.08f
},
new Pulp
@ -85,6 +100,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AccentColour = AccentColour,
Y = -0.08f
},
new Pulp
@ -92,6 +108,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativePositionAxes = Axes.Both,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
},
}
}

View File

@ -1,7 +1,10 @@
// 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.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
@ -9,13 +12,22 @@ namespace osu.Game.Rulesets.Catch.Scoring
{
internal class CatchScoreProcessor : ScoreProcessor<CatchBaseHit>
{
public CatchScoreProcessor()
{
}
public CatchScoreProcessor(RulesetContainer<CatchBaseHit> 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);
protected override BeatmapProcessor<CatchBaseHit> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter() => new CatchBeatmapConverter();
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.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
@ -20,20 +19,25 @@ namespace osu.Game.Rulesets.Catch.UI
public class CatcherArea : Container
{
private Catcher catcher;
private Container explodingFruitContainer;
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]
private void load()
{
Children = new Drawable[]
{
explodingFruitContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
catcher = new Catcher
{
RelativePositionAxes = Axes.Both,
Anchor = Anchor.TopLeft,
ExplodingFruitTarget = explodingFruitContainer,
Origin = Anchor.TopCentre,
X = 0.5f,
}
@ -51,18 +55,30 @@ namespace osu.Game.Rulesets.Catch.UI
{
private Texture texture;
private Container<DrawableHitObject> caughtFruit;
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
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 bool dashing;
public Container ExplodingFruitTarget;
protected bool Dashing
{
get { return dashing; }
@ -139,16 +155,21 @@ namespace osu.Game.Rulesets.Catch.UI
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()
{
base.Update();
if (currentDirection == 0) return;
float speed = Dashing ? 1.5f : 1;
double dashModifier = Dashing ? 1 : 0.5;
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)
@ -163,13 +184,45 @@ namespace osu.Game.Rulesets.Catch.UI
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.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>
<Compile Include="Beatmaps\CatchBeatmapConverter.cs" />
<Compile Include="Beatmaps\CatchBeatmapProcessor.cs" />
<Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="CatchInputManager.cs" />
<Compile Include="Scoring\CatchScoreProcessor.cs" />
@ -57,6 +58,7 @@
<Compile Include="Objects\Fruit.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tests\TestCaseCatcher.cs" />
<Compile Include="Tests\TestCaseCatchPlayer.cs" />
<Compile Include="UI\CatcherArea.cs" />
<Compile Include="UI\CatchRulesetContainer.cs" />
<Compile Include="UI\CatchPlayfield.cs" />

View File

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

View File

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

View File

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