// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Judgements { /// /// A drawable object which visualises the hit result of a . /// public class DrawableJudgement : PoolableDrawable { private const float judgement_size = 128; public JudgementResult Result { get; private set; } public DrawableHitObject JudgedObject { get; private set; } protected Container JudgementBody { get; private set; } private SkinnableDrawable skinnableJudgement; /// /// Duration of initial fade in. /// protected virtual double FadeInDuration => 100; /// /// Duration to wait until fade out begins. Defaults to . /// protected virtual double FadeOutDelay => FadeInDuration; /// /// Creates a drawable which visualises a . /// /// The judgement to visualise. /// The object which was judged. public DrawableJudgement(JudgementResult result, DrawableHitObject judgedObject) : this() { Apply(result, judgedObject); } public DrawableJudgement() { Size = new Vector2(judgement_size); Origin = Anchor.Centre; } [BackgroundDependencyLoader] private void load() { prepareDrawables(); } /// /// Apply top-level animations to the current judgement when successfully hit. /// Generally used for fading, defaulting to a simple fade out based on . /// This will be used to calculate the lifetime of the judgement. /// /// /// For animating the actual "default skin" judgement itself, it is recommended to use . /// This allows applying animations which don't affect custom skins. /// protected virtual void ApplyHitAnimations() { this.Delay(FadeOutDelay).FadeOut(400); } /// /// Apply top-level animations to the current judgement when missed. /// Generally used for fading, defaulting to a simple fade out based on . /// This will be used to calculate the lifetime of the judgement. /// /// /// For animating the actual "default skin" judgement itself, it is recommended to use . /// This allows applying animations which don't affect custom skins. /// protected virtual void ApplyMissAnimations() { this.Delay(600).FadeOut(200); } /// /// Associate a new result / object with this judgement. Should be called when retrieving a judgement from a pool. /// /// The applicable judgement. /// The drawable object. public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { Result = result; JudgedObject = judgedObject; } protected override void PrepareForUse() { base.PrepareForUse(); Debug.Assert(Result != null); prepareDrawables(); // not sure if this should remain going forward. skinnableJudgement.ResetAnimation(); this.FadeInFromZero(FadeInDuration, Easing.OutQuint); switch (Result.Type) { case HitResult.None: break; case HitResult.Miss: ApplyMissAnimations(); break; default: ApplyHitAnimations(); break; } if (skinnableJudgement.Drawable is IAnimatableJudgement animatable) { using (BeginAbsoluteSequence(Result.TimeAbsolute)) animatable.PlayAnimation(); } Expire(true); } private HitResult? currentDrawableType; private void prepareDrawables() { var type = Result?.Type ?? HitResult.Perfect; //TODO: better default type from ruleset // todo: this should be removed once judgements are always pooled. if (type == currentDrawableType) return; // sub-classes might have added their own children that would be removed here if .InternalChild was used. if (JudgementBody != null) RemoveInternal(JudgementBody); AddInternal(JudgementBody = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Child = skinnableJudgement = new SkinnableDrawable(new GameplaySkinComponent(type), _ => CreateDefaultJudgement(type), confineMode: ConfineMode.NoScaling) }); currentDrawableType = type; } protected virtual Drawable CreateDefaultJudgement(HitResult result) => new DefaultJudgementPiece(result); } }