-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Implement OsuModDepth
#25653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement OsuModDepth
#25653
Changes from 8 commits
dc588e6
cf6e50f
937689e
ebcde63
b90000f
ec5c7d7
68907fe
594ea4d
160edcd
b0878e3
e470a42
19d0236
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
using System.Linq; | ||
using osu.Framework.Bindables; | ||
using osu.Framework.Graphics.Sprites; | ||
using osu.Framework.Localisation; | ||
using osu.Game.Configuration; | ||
using osu.Game.Rulesets.Mods; | ||
using osu.Game.Rulesets.Objects.Drawables; | ||
using osu.Game.Rulesets.Osu.Objects; | ||
using osu.Game.Rulesets.Osu.Objects.Drawables; | ||
using osu.Game.Rulesets.Osu.UI; | ||
using osu.Game.Rulesets.UI; | ||
using osuTK; | ||
|
||
namespace osu.Game.Rulesets.Osu.Mods | ||
{ | ||
public class OsuModDepth : ModWithVisibilityAdjustment, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject> | ||
{ | ||
public override string Name => "Depth"; | ||
public override string Acronym => "DH"; | ||
public override IconUsage? Icon => FontAwesome.Solid.Cube; | ||
public override ModType Type => ModType.Fun; | ||
public override LocalisableString Description => "3D. Almost."; | ||
public override double ScoreMultiplier => 1; | ||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray(); | ||
|
||
private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); | ||
private readonly float minDepth = depthForScale(1.5f); | ||
|
||
[SettingSource("Maximum depth", "How far away objects appear.", 0)] | ||
public BindableFloat MaxDepth { get; } = new BindableFloat(100) | ||
{ | ||
Precision = 10, | ||
MinValue = 50, | ||
MaxValue = 200 | ||
}; | ||
|
||
[SettingSource("Show Approach Circles", "Whether approach circles should be visible.", 1)] | ||
public BindableBool ShowApproachCircles { get; } = new BindableBool(true); | ||
|
||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state); | ||
|
||
protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state); | ||
|
||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset) | ||
{ | ||
// Hide judgment displays and follow points as they won't make any sense. | ||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. | ||
drawableRuleset.Playfield.DisplayJudgements.Value = false; | ||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); | ||
} | ||
|
||
private void applyTransform(DrawableHitObject drawable, ArmedState state) | ||
{ | ||
switch (drawable) | ||
{ | ||
case DrawableHitCircle circle: | ||
if (!ShowApproachCircles.Value) | ||
{ | ||
var hitObject = (OsuHitObject)drawable.HitObject; | ||
double appearTime = hitObject.StartTime - hitObject.TimePreempt; | ||
|
||
using (circle.BeginAbsoluteSequence(appearTime)) | ||
circle.ApproachCircle.Hide(); | ||
} | ||
|
||
break; | ||
} | ||
} | ||
|
||
public void Update(Playfield playfield) | ||
{ | ||
double time = playfield.Time.Current; | ||
|
||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects) | ||
{ | ||
switch (drawable) | ||
{ | ||
case DrawableHitCircle circle: | ||
processObject(time, circle, 0); | ||
break; | ||
|
||
case DrawableSlider slider: | ||
processObject(time, slider, slider.HitObject.Duration); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
private void processObject(double time, DrawableOsuHitObject drawable, double duration) | ||
{ | ||
var hitObject = drawable.HitObject; | ||
|
||
double baseSpeed = MaxDepth.Value / hitObject.TimePreempt; | ||
double offsetAfterStartTime = duration + hitObject.MaximumJudgementOffset + 500; | ||
double slowSpeed = Math.Min(-minDepth / offsetAfterStartTime, baseSpeed); | ||
|
||
double decelerationTime = hitObject.TimePreempt * 0.2; | ||
float decelerationDistance = (float)(decelerationTime * (baseSpeed + slowSpeed) * 0.5); | ||
|
||
float z; | ||
|
||
if (time < hitObject.StartTime) | ||
{ | ||
double timeOffset = time - (hitObject.StartTime - decelerationTime); | ||
double deceleration = (slowSpeed - baseSpeed) / decelerationTime; | ||
z = decelerationDistance - (float)(baseSpeed * timeOffset + deceleration * timeOffset * timeOffset * 0.5); | ||
} | ||
else | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My proposal was to only slow objects down when actually required (even if applied to all sliders to start with). I think not applying this whenever we can avoid doing so will make the visuals of this mod much better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah okay, we are on the same page then There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even better would be only applying when a slider gets "too long" (maybe too long for autoplay to hit it correctly with no autoplay path adjustment?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I've made some changes. Now every object moves at a constant speed, except for long sliders. There's a check for how big it would become at the end time if allowed to move at a constant speed. If its scale will be bigger than 1.5 deceleration will be applied. |
||
{ | ||
double endTime = hitObject.StartTime + offsetAfterStartTime; | ||
z = -(float)((Math.Min(time, endTime) - hitObject.StartTime) * slowSpeed); | ||
} | ||
|
||
float scale = scaleForDepth(z); | ||
drawable.Position = toPlayfieldPosition(scale, hitObject.Position); | ||
drawable.Scale = new Vector2(scale); | ||
} | ||
|
||
private static float scaleForDepth(float depth) => 100 / (depth - camera_position.Z); | ||
|
||
private static float depthForScale(float scale) => 100 / scale + camera_position.Z; | ||
|
||
private static Vector2 toPlayfieldPosition(float scale, Vector2 positionAtZeroDepth) | ||
{ | ||
return (positionAtZeroDepth - camera_position.Xy) * scale + camera_position.Xy; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe DP is better?