mirror of
https://github.com/osukey/osukey.git
synced 2025-08-07 16:43:52 +09:00
Merge pull request #2909 from tgi74/hit-shake
Add shake effect when clicking osu! hitobjects too early
This commit is contained in:
@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableHitCircle : DrawableHitCircle
|
protected class TestDrawableHitCircle : DrawableHitCircle
|
||||||
{
|
{
|
||||||
private readonly bool auto;
|
private readonly bool auto;
|
||||||
|
|
||||||
@ -94,6 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
this.auto = auto;
|
this.auto = auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TriggerJudgement() => UpdateResult(true);
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (auto && !userTriggered && timeOffset > 0)
|
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);
|
var result = HitObject.HitWindows.ResultFor(timeOffset);
|
||||||
if (result == HitResult.None)
|
if (result == HitResult.None)
|
||||||
|
{
|
||||||
|
Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ApplyResult(r => r.Type = result);
|
ApplyResult(r => r.Type = result);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Osu.Judgements;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
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;
|
public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Time.Current >= HitObject.StartTime - HitObject.TimePreempt;
|
||||||
|
|
||||||
|
private readonly ShakeContainer shakeContainer;
|
||||||
|
|
||||||
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
protected DrawableOsuHitObject(OsuHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
base.AddInternal(shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both });
|
||||||
Alpha = 0;
|
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)
|
protected sealed override void UpdateState(ArmedState state)
|
||||||
{
|
{
|
||||||
double transformTime = HitObject.StartTime - HitObject.TimePreempt;
|
double transformTime = HitObject.StartTime - HitObject.TimePreempt;
|
||||||
@ -68,6 +78,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private OsuInputManager osuActionInputManager;
|
private OsuInputManager osuActionInputManager;
|
||||||
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
|
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);
|
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 },
|
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||||
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
||||||
Ball = new SliderBall(s)
|
Ball = new SliderBall(s, this)
|
||||||
{
|
{
|
||||||
BypassAutoSizeAxes = Axes.Both,
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
Scale = new Vector2(s.Scale),
|
Scale = new Vector2(s.Scale),
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
},
|
},
|
||||||
HeadCircle = new DrawableSliderHead(s, s.HeadCircle),
|
HeadCircle = new DrawableSliderHead(s, s.HeadCircle)
|
||||||
|
{
|
||||||
|
OnShake = Shake
|
||||||
|
},
|
||||||
TailCircle = new DrawableSliderTail(s, s.TailCircle)
|
TailCircle = new DrawableSliderTail(s, s.TailCircle)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2018 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 System;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
|
|
||||||
@ -28,5 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (!IsHit)
|
if (!IsHit)
|
||||||
Position = slider.CurvePositionAt(completionProgress);
|
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;
|
private readonly Slider slider;
|
||||||
public readonly Drawable FollowCircle;
|
public readonly Drawable FollowCircle;
|
||||||
private Drawable drawableBall;
|
private Drawable drawableBall;
|
||||||
|
private readonly DrawableSlider drawableSlider;
|
||||||
|
|
||||||
public SliderBall(Slider slider)
|
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
|
||||||
{
|
{
|
||||||
|
this.drawableSlider = drawableSlider;
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -155,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Tracking = canCurrentlyTrack
|
Tracking = canCurrentlyTrack
|
||||||
&& lastState != null
|
&& lastState != null
|
||||||
&& ReceiveMouseInputAt(lastState.Mouse.NativeState.Position)
|
&& 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]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
AddInternal(trackManager);
|
Add(trackManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
TestTrackOwner owner = null;
|
TestTrackOwner owner = null;
|
||||||
PreviewTrack track = 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("start", () => track.Start());
|
||||||
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
|
AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
|
||||||
AddAssert("not stopped", () => track.IsRunning);
|
AddAssert("not stopped", () => track.IsRunning);
|
||||||
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
var track = getTrack();
|
var track = getTrack();
|
||||||
|
|
||||||
AddInternal(track);
|
Add(track);
|
||||||
|
|
||||||
return 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" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
<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="SharpCompress" Version="0.22.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.10.1" />
|
<PackageReference Include="NUnit" Version="3.10.1" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Reference in New Issue
Block a user