1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-18 08:30:00 +08:00
Files
osu-lazer/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
T
Dan Balasescu d4a4acd3f4 Fix mania hold notes dimming unexpectedly (#37008)
Fixes https://github.com/ppy/osu/issues/29223

This fixes several issues around hold note dimming.

Notice in the following video:
- The tail piece of the first note not getting dimmed.
- The body of the second note not responding to dimming at all.


https://github.com/user-attachments/assets/8e51053d-8d88-4e48-909b-79218d65917b

Then, notice in the following video:
- The body piece of the second note is dimmed from the very beginning.


https://github.com/user-attachments/assets/a514c630-5c72-4ba5-96d5-472bae1058b3

This requires a specific setup whereby the hold note and its components
must be reused from the pool. In particular:
1. The hold note must be long. So long that by the time the tail becomes
on screen, the body will already have dimmed.
2. The hold note must be re-used from the pool. We can induce this by
setting the pool sizes to 1 in
[`Column.cs`](https://github.com/ppy/osu/blob/780ce2666099c22d1e0673cafab544418b5d14b0/osu.Game.Rulesets.Mania/UI/Column.cs#L121-L123).
3. The second hold note should be placed far enough in the timeline that
the first hold note dies by the time it becomes visible.
  4. Scroll speed should be adjusted to fit the above constraints.

I haven't done a full deep dive into exactly _why_ this is happening, so
the fix here is hand-wavy. That said, just by looking at the old code in
`LegacyBodyPiece` you'll get a feeling that something's bound to go
wrong;
- It never resets the `missingFadeTime` state.
- It never resets the colours back to `Color4.White`.
- It applies transforms onto external components.
- It jumps through hoops to figure out how to set `missingFadeTime`.

My hope is that these changes first bring some sanity in the process,
and if it breaks again I'll consider doing a more proper root cause
(I've had this issue in the back of my mind for about 1 year).

With this PR, they now behave as expected:


https://github.com/user-attachments/assets/140b37c5-cf84-44ba-b797-86ac6d8130c8


https://github.com/user-attachments/assets/6f2342a4-6a9a-4941-a55a-24a357f27c25
2026-03-23 08:33:23 +01:00

147 lines
4.4 KiB
C#

// 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.
#nullable disable
using System.Collections.Generic;
using System.Threading;
using osu.Game.Audio;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
/// <summary>
/// Represents a hit object which requires pressing, holding, and releasing a key.
/// </summary>
public class HoldNote : ManiaHitObject, IHasDuration
{
public double EndTime
{
get => StartTime + Duration;
set => Duration = value - StartTime;
}
private double duration;
public double Duration
{
get => duration;
set
{
duration = value;
if (Tail != null)
Tail.StartTime = EndTime;
}
}
public override double StartTime
{
get => base.StartTime;
set
{
base.StartTime = value;
if (Head != null)
Head.StartTime = value;
if (Tail != null)
Tail.StartTime = EndTime;
}
}
public override int Column
{
get => base.Column;
set
{
base.Column = value;
if (Head != null)
Head.Column = value;
if (Tail != null)
Tail.Column = value;
}
}
public IList<IList<HitSampleInfo>> NodeSamples { get; set; }
/// <summary>
/// The head note of the hold.
/// </summary>
public HeadNote Head { get; protected set; }
/// <summary>
/// The tail note of the hold.
/// </summary>
public TailNote Tail { get; protected set; }
/// <summary>
/// The body of the hold.
/// This is an invisible and silent object that tracks the holding state of the <see cref="HoldNote"/>.
/// </summary>
public HoldNoteBody Body { get; protected set; }
/// <summary>
/// Whether sliding samples should be played when held.
/// </summary>
public bool PlaySlidingSamples { get; init; }
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
base.CreateNestedHitObjects(cancellationToken);
// Generally node samples will be populated by ManiaBeatmapConverter, but in a case like the editor they may not be.
// Ensure they are set to a sane default here.
NodeSamples ??= CreateDefaultNodeSamples(this);
AddNested(Head = new HeadNote
{
StartTime = StartTime,
Column = Column,
Samples = GetNodeSamples(0),
});
AddNested(Tail = new TailNote
{
StartTime = EndTime,
Column = Column,
Samples = GetNodeSamples(NodeSamples.Count - 1),
});
AddNested(Body = new HoldNoteBody
{
StartTime = StartTime,
Column = Column,
Duration = Duration
});
}
public override Judgement CreateJudgement() => new IgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public IList<HitSampleInfo> GetNodeSamples(int nodeIndex) => nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
/// <summary>
/// Create the default note samples for a hold note, based off their main sample.
/// </summary>
/// <remarks>
/// By default, osu!mania beatmaps in only play samples at the start of the hold note.
/// </remarks>
/// <param name="obj">The object to use as a basis for the head sample.</param>
/// <returns>Defaults for assigning to <see cref="HoldNote.NodeSamples"/>.</returns>
public static List<IList<HitSampleInfo>> CreateDefaultNodeSamples(HitObject obj) => new List<IList<HitSampleInfo>>
{
obj.Samples,
new List<HitSampleInfo>(),
};
}
}