mirror of
https://github.com/ppy/osu.git
synced 2025-03-15 15:27:20 +08:00
Merge pull request #2909 from tgi74/hit-shake
Add shake effect when clicking osu! hitobjects too early
This commit is contained in:
commit
27c151db8f
@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDrawableHitCircle : DrawableHitCircle
|
||||
protected class TestDrawableHitCircle : DrawableHitCircle
|
||||
{
|
||||
private readonly bool auto;
|
||||
|
||||
@ -94,6 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
this.auto = auto;
|
||||
}
|
||||
|
||||
public void TriggerJudgement() => UpdateResult(true);
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (auto && !userTriggered && timeOffset > 0)
|
||||
|
23
osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs
Normal file
23
osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestCaseShaking : TestCaseHitCircle
|
||||
{
|
||||
public override void Add(Drawable drawable)
|
||||
{
|
||||
base.Add(drawable);
|
||||
|
||||
if (drawable is TestDrawableHitCircle hitObject)
|
||||
{
|
||||
Scheduler.AddDelayed(() => hitObject.TriggerJudgement(),
|
||||
hitObject.HitObject.StartTime - (hitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -88,7 +88,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||
if (result == HitResult.None)
|
||||
{
|
||||
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss));
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyResult(r => r.Type = result);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
@ -17,12 +18,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt;
|
||||
|
||||
private readonly ShakeContainer shakeContainer;
|
||||
|
||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
base.AddInternal(shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both });
|
||||
Alpha = 0;
|
||||
}
|
||||
|
||||
// Forward all internal management to shakeContainer.
|
||||
// This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690)
|
||||
protected override void AddInternal(Drawable drawable) => shakeContainer.Add(drawable);
|
||||
protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren);
|
||||
protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable);
|
||||
|
||||
protected sealed override void UpdateState(ArmedState state)
|
||||
{
|
||||
double transformTime = HitObject.StartTime - HitObject.TimePreempt;
|
||||
@ -68,6 +78,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
private OsuInputManager osuActionInputManager;
|
||||
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
|
||||
|
||||
protected virtual void Shake(double maximumLength) => shakeContainer.Shake(maximumLength);
|
||||
|
||||
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
|
||||
}
|
||||
}
|
||||
|
@ -44,14 +44,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
},
|
||||
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
||||
Ball = new SliderBall(s)
|
||||
Ball = new SliderBall(s, this)
|
||||
{
|
||||
BypassAutoSizeAxes = Axes.Both,
|
||||
Scale = new Vector2(s.Scale),
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0
|
||||
},
|
||||
HeadCircle = new DrawableSliderHead(s, s.HeadCircle),
|
||||
HeadCircle = new DrawableSliderHead(s, s.HeadCircle)
|
||||
{
|
||||
OnShake = Shake
|
||||
},
|
||||
TailCircle = new DrawableSliderTail(s, s.TailCircle)
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using OpenTK;
|
||||
|
||||
@ -28,5 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (!IsHit)
|
||||
Position = slider.CurvePositionAt(completionProgress);
|
||||
}
|
||||
|
||||
public Action<double> OnShake;
|
||||
|
||||
protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
|
||||
}
|
||||
}
|
||||
|
@ -36,9 +36,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
private readonly Slider slider;
|
||||
public readonly Drawable FollowCircle;
|
||||
private Drawable drawableBall;
|
||||
private readonly DrawableSlider drawableSlider;
|
||||
|
||||
public SliderBall(Slider slider)
|
||||
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
|
||||
{
|
||||
this.drawableSlider = drawableSlider;
|
||||
this.slider = slider;
|
||||
Masking = true;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
@ -155,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||
Tracking = canCurrentlyTrack
|
||||
&& lastState != null
|
||||
&& ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
|
||||
&& ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
||||
&& (drawableSlider?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(trackManager);
|
||||
Add(trackManager);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual
|
||||
TestTrackOwner owner = null;
|
||||
PreviewTrack track = null;
|
||||
|
||||
AddStep("get track", () => AddInternal(owner = new TestTrackOwner(track = getTrack())));
|
||||
AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack())));
|
||||
AddStep("start", () => track.Start());
|
||||
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
|
||||
AddAssert("not stopped", () => track.IsRunning);
|
||||
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
var track = getTrack();
|
||||
|
||||
AddInternal(track);
|
||||
Add(track);
|
||||
|
||||
return track;
|
||||
}
|
||||
|
39
osu.Game/Graphics/Containers/ShakeContainer.cs
Normal file
39
osu.Game/Graphics/Containers/ShakeContainer.cs
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
/// <summary>
|
||||
/// A container that adds the ability to shake its contents.
|
||||
/// </summary>
|
||||
public class ShakeContainer : Container
|
||||
{
|
||||
/// <summary>
|
||||
/// Shake the contents of this container.
|
||||
/// </summary>
|
||||
/// <param name="maximumLength">The maximum length the shake should last.</param>
|
||||
public void Shake(double maximumLength)
|
||||
{
|
||||
const float shake_amount = 8;
|
||||
const float shake_duration = 30;
|
||||
|
||||
// if we don't have enough time, don't bother shaking.
|
||||
if (maximumLength < shake_duration * 2)
|
||||
return;
|
||||
|
||||
var sequence = this.MoveToX(shake_amount, shake_duration / 2, Easing.OutSine).Then()
|
||||
.MoveToX(-shake_amount, shake_duration, Easing.InOutSine).Then();
|
||||
|
||||
// if we don't have enough time for the second shake, skip it.
|
||||
if (maximumLength > shake_duration * 4)
|
||||
sequence = sequence
|
||||
.MoveToX(shake_amount, shake_duration, Easing.InOutSine).Then()
|
||||
.MoveToX(-shake_amount, shake_duration, Easing.InOutSine).Then();
|
||||
|
||||
sequence.MoveToX(0, shake_duration / 2, Easing.InSine);
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2018.821.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2018.824.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.22.0" />
|
||||
<PackageReference Include="NUnit" Version="3.10.1" />
|
||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user