mirror of
https://github.com/ppy/osu.git
synced 2026-05-26 06:49:54 +08:00
Merge 6c96f79913 into 1e3d5d7d81
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@@ -12,16 +17,143 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IApplicableToDrawableHitObject
|
||||
{
|
||||
private DrawableTaikoRuleset drawableTaikoRuleset = null!;
|
||||
|
||||
private TaikoPlayfieldAdjustmentContainer adjustmentContainer = null!;
|
||||
|
||||
private IReadOnlyList<Mod> mods = Array.Empty<Mod>();
|
||||
|
||||
private const float hd_base_fade_out_duration = 0.375f;
|
||||
|
||||
private const float hd_base_initial_alpha = 0.75f;
|
||||
|
||||
private const float hdhr_base_fade_out_duration = 0.2f;
|
||||
|
||||
private const float hdhr_base_initial_alpha = 0.2f;
|
||||
|
||||
private const float hidden_base_aspect = 4f / 3f;
|
||||
|
||||
/// <summary>
|
||||
/// Time range at which notes start to become visible in milliseconds.
|
||||
/// </summary>
|
||||
private const double nm_fade_in_time_range = 10000d / 1.4d;
|
||||
|
||||
/// <summary>
|
||||
/// Duration notes take to fade in in milliseconds.
|
||||
/// </summary>
|
||||
private const float nm_fade_in_duration = 400f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether note fading in is enabled.
|
||||
/// </summary>
|
||||
private bool fadeInEnabled = true;
|
||||
|
||||
private readonly BindableFloat hiddenFadeOutDuration = new BindableFloat(hd_base_fade_out_duration);
|
||||
|
||||
private readonly BindableFloat hiddenInitialAlpha = new BindableFloat(hd_base_initial_alpha);
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||
{
|
||||
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||
drawableTaikoRuleset.LockPlayfieldAspectRange.Value = false;
|
||||
drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||
adjustmentContainer = (TaikoPlayfieldAdjustmentContainer)drawableRuleset.PlayfieldAdjustmentContainer;
|
||||
|
||||
// drawableRuleset.Mods should always be non-null here, but just in case.
|
||||
mods = drawableRuleset.Mods ?? mods;
|
||||
|
||||
// Disable uppper bound for playfield aspect ratio
|
||||
// In stable, nomod time range is limited by fading notes in instead
|
||||
adjustmentContainer.MaximumAspect = float.PositiveInfinity;
|
||||
adjustmentContainer.MinimumAspect = 5f / 4f;
|
||||
|
||||
TaikoModHidden? hidden = mods.OfType<TaikoModHidden>().FirstOrDefault();
|
||||
|
||||
if (mods.OfType<TaikoModHardRock>().Any())
|
||||
{
|
||||
// Hardrock disables note fading in
|
||||
fadeInEnabled = false;
|
||||
|
||||
// For hardrock, the playfield time range is clamped to within classicMaxTimeRange and the equivalent
|
||||
// time range for a 16:10 aspect ratio.
|
||||
adjustmentContainer.TrimOnOverflow = false;
|
||||
|
||||
// Apply stable aspect ratio limits for hardrock (visually taken)
|
||||
adjustmentContainer.MaximumAspect = 1.963f;
|
||||
|
||||
// This is accurate to 4:3, but slightly off for 5:4
|
||||
adjustmentContainer.MinimumAspect = 1.666f;
|
||||
|
||||
adjustmentContainer.MinimumRelativeHeight = 0.26f;
|
||||
adjustmentContainer.MaximumRelativeHeight = 0.26f;
|
||||
|
||||
if (hidden != null)
|
||||
{
|
||||
hiddenInitialAlpha.BindTo(hidden.InitialAlpha);
|
||||
hiddenFadeOutDuration.BindTo(hidden.FadeOutDuration);
|
||||
drawableRuleset.OnUpdate += d => adjustHidden(
|
||||
hdhr_base_fade_out_duration, hdhr_base_initial_alpha, 16f / 9f, 0.8f);
|
||||
}
|
||||
}
|
||||
else if (hidden != null)
|
||||
{
|
||||
// Hidden disables note fading in
|
||||
fadeInEnabled = false;
|
||||
|
||||
// Stable limits the aspect ratio to 4:3
|
||||
adjustmentContainer.MaximumAspect = hidden_base_aspect;
|
||||
|
||||
// Enable playfield trimming for hidden
|
||||
adjustmentContainer.TrimOnOverflow = true;
|
||||
|
||||
// Enable aspect ratio adjustment for hidden (see adjustHidden)
|
||||
hiddenInitialAlpha.BindTo(hidden.InitialAlpha);
|
||||
hiddenFadeOutDuration.BindTo(hidden.FadeOutDuration);
|
||||
drawableRuleset.OnUpdate += d => adjustHidden(
|
||||
hd_base_fade_out_duration, hd_base_initial_alpha, hidden_base_aspect);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
if (drawable is DrawableTaikoHitObject hit)
|
||||
{
|
||||
hit.SnapJudgementLocation = true;
|
||||
|
||||
if (fadeInEnabled)
|
||||
{
|
||||
drawable.ApplyCustomUpdateState += fadeIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fade in notes with fixed duration.
|
||||
private void fadeIn(DrawableHitObject o, ArmedState state)
|
||||
{
|
||||
double preempt = nm_fade_in_time_range / drawableTaikoRuleset.ControlPointAt(o.HitObject.StartTime).Multiplier;
|
||||
double start = o.HitObject.StartTime - preempt;
|
||||
o.Alpha = 0;
|
||||
|
||||
using (o.BeginAbsoluteSequence(start))
|
||||
{
|
||||
o.FadeInFromZero(nm_fade_in_duration);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust hidden initial alpha and fade out duration for different aspect ratios
|
||||
private void adjustHidden(
|
||||
float baseFadeOutDuration,
|
||||
float baseInitialAlpha,
|
||||
float baseAspect,
|
||||
float adjustmentRatio = 1f)
|
||||
{
|
||||
float clampedAspect = adjustmentContainer.ClampedCurrentAspect;
|
||||
|
||||
float fadeOutDurationAdjustment = clampedAspect / baseAspect - 1;
|
||||
fadeOutDurationAdjustment *= adjustmentRatio;
|
||||
hiddenFadeOutDuration.Value = baseFadeOutDuration + fadeOutDurationAdjustment;
|
||||
|
||||
float initialAlphaAdjustment = clampedAspect / baseAspect - 1;
|
||||
initialAlphaAdjustment *= adjustmentRatio;
|
||||
hiddenInitialAlpha.Value = baseInitialAlpha + initialAlphaAdjustment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -29,7 +30,20 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
/// How long hitobjects take to fade out, in terms of the scrolling length.
|
||||
/// Range: [0, 1]
|
||||
/// </summary>
|
||||
private const float fade_out_duration = 0.375f;
|
||||
public readonly BindableFloat FadeOutDuration = new BindableFloat(0.375f)
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = 1f
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The initial alpha of hitobjects when they appear.
|
||||
/// </summary>
|
||||
public readonly BindableFloat InitialAlpha = new BindableFloat(1f)
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = 1f
|
||||
};
|
||||
|
||||
private DrawableTaikoRuleset drawableRuleset = null!;
|
||||
|
||||
@@ -51,7 +65,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
case DrawableHit:
|
||||
double preempt = drawableRuleset.TimeRange.Value / drawableRuleset.ControlPointAt(hitObject.HitObject.StartTime).Multiplier;
|
||||
double start = hitObject.HitObject.StartTime - preempt * fade_out_start_time;
|
||||
double duration = preempt * fade_out_duration;
|
||||
double duration = preempt * FadeOutDuration.Value;
|
||||
hitObject.Alpha = InitialAlpha.Value;
|
||||
|
||||
using (hitObject.BeginAbsoluteSequence(start))
|
||||
{
|
||||
|
||||
@@ -35,8 +35,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public new BindableDouble TimeRange => base.TimeRange;
|
||||
|
||||
public readonly BindableBool LockPlayfieldAspectRange = new BindableBool(true);
|
||||
|
||||
public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;
|
||||
|
||||
protected new TaikoPlayfieldAdjustmentContainer PlayfieldAdjustmentContainer => (TaikoPlayfieldAdjustmentContainer)base.PlayfieldAdjustmentContainer;
|
||||
@@ -110,10 +108,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
return ControlPoints[result];
|
||||
}
|
||||
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer
|
||||
{
|
||||
LockPlayfieldAspectRange = { BindTarget = LockPlayfieldAspectRange }
|
||||
};
|
||||
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
||||
|
||||
|
||||
@@ -120,6 +120,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
new Container
|
||||
{
|
||||
Name = "Bar line content",
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = hit_target_width / 2 + hit_target_offset },
|
||||
Children = new Drawable[]
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@@ -12,12 +11,38 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
{
|
||||
public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
|
||||
{
|
||||
public const float MAXIMUM_ASPECT = 16f / 9f;
|
||||
public const float MINIMUM_ASPECT = 5f / 4f;
|
||||
|
||||
private const float stable_gamefield_height = 480f;
|
||||
|
||||
public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);
|
||||
/// <summary>
|
||||
/// The maximum aspect ratio the playfield can be adjusted to.
|
||||
/// </summary>
|
||||
protected internal float MaximumAspect = 16f / 9f;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum aspect ratio the playfield can be adjusted to.
|
||||
/// </summary>
|
||||
protected internal float MinimumAspect = 5f / 4f;
|
||||
|
||||
/// <summary>
|
||||
/// Aspect ratio of the playfield's resolution clamped between <see cref="MinimumAspect"/> and
|
||||
/// <see cref="MaximumAspect"/>.
|
||||
/// </summary>
|
||||
protected internal float ClampedCurrentAspect { get; private set; } = 16f / 9f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum relative height of the playfield. This is a fraction of the available area.
|
||||
/// </summary>
|
||||
protected internal float MaximumRelativeHeight = 1f / 3f;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum relative height of the playfield. This is a fraction of the available area.
|
||||
/// </summary>
|
||||
protected internal float MinimumRelativeHeight = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the playfield should be trimmed when the aspect ratio exceeds the maximum.
|
||||
/// </summary>
|
||||
protected internal bool TrimOnOverflow = false;
|
||||
|
||||
public TaikoPlayfieldAdjustmentContainer()
|
||||
{
|
||||
@@ -37,41 +62,50 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
float relativeHeight = base_relative_height;
|
||||
|
||||
float widthScale = 1.0f;
|
||||
|
||||
// Players coming from stable expect to be able to change the aspect ratio regardless of the window size.
|
||||
// We originally wanted to limit this more, but there was considerable pushback from the community.
|
||||
//
|
||||
// As a middle-ground, the aspect ratio can still be adjusted in the downwards direction but has a maximum limit.
|
||||
// This is still a bit weird, because readability changes with window size, but it is what it is.
|
||||
if (LockPlayfieldAspectRange.Value)
|
||||
{
|
||||
float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y;
|
||||
//
|
||||
// This is separate from CurrentAspect as this needs to be the unbounded aspect ratio.
|
||||
float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y;
|
||||
|
||||
if (currentAspect > MAXIMUM_ASPECT)
|
||||
relativeHeight *= currentAspect / MAXIMUM_ASPECT;
|
||||
else if (currentAspect < MINIMUM_ASPECT)
|
||||
relativeHeight *= currentAspect / MINIMUM_ASPECT;
|
||||
if (currentAspect > MaximumAspect)
|
||||
{
|
||||
if (TrimOnOverflow)
|
||||
{
|
||||
widthScale = MaximumAspect / currentAspect;
|
||||
}
|
||||
else
|
||||
{
|
||||
relativeHeight *= currentAspect / MaximumAspect;
|
||||
}
|
||||
}
|
||||
else if (currentAspect < MinimumAspect)
|
||||
{
|
||||
relativeHeight *= currentAspect / MinimumAspect;
|
||||
}
|
||||
|
||||
// Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions.
|
||||
relativeHeight = Math.Min(relativeHeight, 1f / 3f);
|
||||
relativeHeight = Math.Clamp(relativeHeight, MinimumRelativeHeight, MaximumRelativeHeight);
|
||||
|
||||
Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f));
|
||||
Width = 1 / Scale.X;
|
||||
Width = 1 / Scale.X * widthScale;
|
||||
}
|
||||
|
||||
public double ComputeTimeRange()
|
||||
{
|
||||
float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y;
|
||||
|
||||
if (LockPlayfieldAspectRange.Value)
|
||||
currentAspect = Math.Clamp(currentAspect, MINIMUM_ASPECT, MAXIMUM_ASPECT);
|
||||
ClampedCurrentAspect = Math.Clamp(Parent!.ChildSize.X / Parent!.ChildSize.Y, MinimumAspect, MaximumAspect);
|
||||
|
||||
// in a game resolution of 1024x768, stable's scrolling system consists of objects being placed 600px (widthScaled - 40) away from their hit location.
|
||||
// however, the point at which the object renders at the end of the screen is exactly x=640, but stable makes the object start moving from beyond the screen instead of the boundary point.
|
||||
// therefore, in lazer we have to adjust the "in length" so that it's in a 640px->160px fashion before passing it down as a "time range".
|
||||
// see stable's "in length": https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManagerTaiko.cs#L168
|
||||
const float stable_hit_location = 160f;
|
||||
float widthScaled = currentAspect * stable_gamefield_height;
|
||||
float widthScaled = ClampedCurrentAspect * stable_gamefield_height;
|
||||
float inLength = widthScaled - stable_hit_location;
|
||||
|
||||
// also in a game resolution of 1024x768, stable makes hit objects scroll from 760px->160px at a duration of 6000ms, divided by slider velocity (i.e. at a rate of 0.1px/ms)
|
||||
|
||||
Reference in New Issue
Block a user