2017-02-07 12:59:30 +08:00
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
2016-09-02 18:58:57 +08:00
2016-11-14 16:23:33 +08:00
using osu.Framework.Allocation ;
2016-09-02 18:58:57 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Sprites ;
2017-04-18 15:05:58 +08:00
using osu.Game.Rulesets.Taiko.Objects ;
using osu.Game.Rulesets.UI ;
2016-09-02 18:58:57 +08:00
using OpenTK ;
using OpenTK.Graphics ;
2017-04-18 15:05:58 +08:00
using osu.Game.Rulesets.Taiko.Judgements ;
using osu.Game.Rulesets.Objects.Drawables ;
2017-03-21 14:09:54 +08:00
using osu.Game.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Extensions.Color4Extensions ;
2017-04-03 09:20:20 +08:00
using System.Linq ;
2017-04-18 15:05:58 +08:00
using osu.Game.Rulesets.Taiko.Objects.Drawables ;
2017-04-10 13:28:01 +08:00
using System ;
2016-09-02 18:58:57 +08:00
2017-04-18 15:05:58 +08:00
namespace osu.Game.Rulesets.Taiko.UI
2016-09-02 18:58:57 +08:00
{
2017-03-23 18:00:18 +08:00
public class TaikoPlayfield : Playfield < TaikoHitObject , TaikoJudgement >
2016-09-02 18:58:57 +08:00
{
2017-03-21 14:09:54 +08:00
/// <summary>
2017-04-10 13:28:01 +08:00
/// The default play field height.
2017-03-21 14:09:54 +08:00
/// </summary>
2017-05-22 18:50:01 +08:00
public const float DEFAULT_PLAYFIELD_HEIGHT = 178f ;
2017-03-21 14:09:54 +08:00
/// <summary>
/// The offset from <see cref="left_area_size"/> which the center of the hit target lies at.
/// </summary>
2017-05-23 16:57:34 +08:00
public const float HIT_TARGET_OFFSET = TaikoHitObject . DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40 ;
2017-03-21 14:09:54 +08:00
/// <summary>
/// The size of the left area of the playfield. This area contains the input drum.
/// </summary>
private const float left_area_size = 240 ;
protected override Container < Drawable > Content = > hitObjectContainer ;
2017-03-23 13:37:00 +08:00
private readonly Container < HitExplosion > hitExplosionContainer ;
2017-05-24 13:53:28 +08:00
private readonly Container < KiaiHitExplosion > kiaiExplosionContainer ;
2017-04-01 22:59:44 +08:00
private readonly Container < DrawableBarLine > barLineContainer ;
2017-03-23 18:00:18 +08:00
private readonly Container < DrawableTaikoJudgement > judgementContainer ;
2017-03-21 14:09:54 +08:00
2017-03-23 13:37:00 +08:00
private readonly Container hitObjectContainer ;
2017-03-29 08:02:49 +08:00
private readonly Container topLevelHitContainer ;
2017-05-23 15:42:17 +08:00
private readonly Container overlayBackgroundContainer ;
private readonly Container backgroundContainer ;
private readonly Box overlayBackground ;
private readonly Box background ;
2017-03-21 14:09:54 +08:00
2016-09-02 18:58:57 +08:00
public TaikoPlayfield ( )
{
2017-03-21 14:09:54 +08:00
AddInternal ( new Drawable [ ]
{
2017-04-10 13:28:01 +08:00
new ScaleFixContainer
2017-03-21 14:09:54 +08:00
{
2017-04-10 13:28:01 +08:00
RelativeSizeAxes = Axes . X ,
Height = DEFAULT_PLAYFIELD_HEIGHT ,
Children = new [ ]
2017-03-21 14:09:54 +08:00
{
2017-05-23 15:42:17 +08:00
backgroundContainer = new Container
2017-05-11 16:18:22 +08:00
{
Name = "Transparent playfield background" ,
RelativeSizeAxes = Axes . Both ,
BorderThickness = 2 ,
Masking = true ,
2017-06-12 11:48:47 +08:00
EdgeEffect = new EdgeEffectParameters
2017-05-11 16:18:22 +08:00
{
Type = EdgeEffectType . Shadow ,
Colour = Color4 . Black . Opacity ( 0.2f ) ,
Radius = 5 ,
} ,
Children = new Drawable [ ]
{
2017-05-23 15:42:17 +08:00
background = new Box
2017-05-11 16:18:22 +08:00
{
RelativeSizeAxes = Axes . Both ,
Alpha = 0.6f
} ,
}
} ,
2017-03-21 14:09:54 +08:00
new Container
{
2017-05-23 15:42:17 +08:00
Name = "Right area" ,
2017-03-21 14:09:54 +08:00
RelativeSizeAxes = Axes . Both ,
2017-05-23 15:42:17 +08:00
Margin = new MarginPadding { Left = left_area_size } ,
2017-03-21 14:09:54 +08:00
Children = new Drawable [ ]
{
2017-04-10 13:28:01 +08:00
new Container
2017-03-21 14:09:54 +08:00
{
2017-05-23 15:42:17 +08:00
Name = "Masked elements" ,
2017-04-10 13:28:01 +08:00
RelativeSizeAxes = Axes . Both ,
2017-05-23 16:57:34 +08:00
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } ,
2017-05-23 15:42:17 +08:00
Masking = true ,
2017-04-10 13:28:01 +08:00
Children = new Drawable [ ]
{
2017-05-24 14:48:47 +08:00
hitExplosionContainer = new Container < HitExplosion >
{
RelativeSizeAxes = Axes . Y ,
BlendingMode = BlendingMode . Additive ,
} ,
2017-04-10 13:28:01 +08:00
barLineContainer = new Container < DrawableBarLine >
{
RelativeSizeAxes = Axes . Both ,
} ,
new HitTarget
{
Anchor = Anchor . CentreLeft ,
Origin = Anchor . Centre ,
} ,
hitObjectContainer = new Container
{
RelativeSizeAxes = Axes . Both ,
} ,
2017-05-23 15:42:17 +08:00
}
} ,
2017-05-24 13:53:28 +08:00
kiaiExplosionContainer = new Container < KiaiHitExplosion >
{
Name = "Kiai hit explosions" ,
RelativeSizeAxes = Axes . Y ,
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET } ,
BlendingMode = BlendingMode . Additive
} ,
2017-05-23 15:42:17 +08:00
judgementContainer = new Container < DrawableTaikoJudgement >
{
Name = "Judgements" ,
RelativeSizeAxes = Axes . Y ,
2017-05-23 16:57:34 +08:00
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET } ,
2017-05-23 15:42:17 +08:00
BlendingMode = BlendingMode . Additive
2017-03-21 14:09:54 +08:00
} ,
2017-04-10 13:28:01 +08:00
}
} ,
2017-05-23 15:42:17 +08:00
overlayBackgroundContainer = new Container
2017-04-10 13:28:01 +08:00
{
Name = "Left overlay" ,
Size = new Vector2 ( left_area_size , DEFAULT_PLAYFIELD_HEIGHT ) ,
BorderThickness = 1 ,
Children = new Drawable [ ]
{
2017-05-23 15:42:17 +08:00
overlayBackground = new Box
2017-03-21 19:39:18 +08:00
{
RelativeSizeAxes = Axes . Both ,
} ,
2017-04-10 13:28:01 +08:00
new InputDrum
2017-03-21 14:09:54 +08:00
{
2017-04-10 13:28:01 +08:00
Anchor = Anchor . Centre ,
2017-03-21 14:09:54 +08:00
Origin = Anchor . Centre ,
2017-04-10 13:28:01 +08:00
RelativePositionAxes = Axes . X ,
Position = new Vector2 ( 0.10f , 0 ) ,
Scale = new Vector2 ( 0.9f )
2017-03-21 14:09:54 +08:00
} ,
2017-04-10 13:28:01 +08:00
new Box
2017-03-21 15:33:25 +08:00
{
2017-04-10 13:28:01 +08:00
Anchor = Anchor . TopRight ,
RelativeSizeAxes = Axes . Y ,
Width = 10 ,
ColourInfo = Framework . Graphics . Colour . ColourInfo . GradientHorizontal ( Color4 . Black . Opacity ( 0.6f ) , Color4 . Black . Opacity ( 0 ) ) ,
2017-03-21 15:33:25 +08:00
} ,
2017-04-10 13:28:01 +08:00
}
2017-03-21 14:09:54 +08:00
} ,
}
} ,
topLevelHitContainer = new Container
{
2017-04-10 13:28:01 +08:00
Name = "Top level hit objects" ,
2017-03-21 14:09:54 +08:00
RelativeSizeAxes = Axes . Both ,
}
} ) ;
2016-09-02 18:58:57 +08:00
}
2016-11-12 18:44:16 +08:00
[BackgroundDependencyLoader]
2017-03-21 14:09:54 +08:00
private void load ( OsuColour colours )
2016-09-02 18:58:57 +08:00
{
2017-05-23 15:42:17 +08:00
overlayBackgroundContainer . BorderColour = colours . Gray0 ;
overlayBackground . Colour = colours . Gray1 ;
2016-09-02 18:58:57 +08:00
2017-05-23 15:42:17 +08:00
backgroundContainer . BorderColour = colours . Gray1 ;
background . Colour = colours . Gray0 ;
2017-03-21 14:09:54 +08:00
}
2017-03-23 18:00:18 +08:00
public override void Add ( DrawableHitObject < TaikoHitObject , TaikoJudgement > h )
2017-03-21 14:09:54 +08:00
{
h . Depth = ( float ) h . HitObject . StartTime ;
base . Add ( h ) ;
2017-03-29 08:01:40 +08:00
// Swells should be moved at the very top of the playfield when they reach the hit target
var swell = h as DrawableSwell ;
if ( swell ! = null )
swell . OnStart + = ( ) = > topLevelHitContainer . Add ( swell . CreateProxy ( ) ) ;
2017-03-21 14:09:54 +08:00
}
2017-03-21 20:26:01 +08:00
public void AddBarLine ( DrawableBarLine barLine )
{
barLineContainer . Add ( barLine ) ;
}
2017-03-23 18:00:18 +08:00
public override void OnJudgement ( DrawableHitObject < TaikoHitObject , TaikoJudgement > judgedObject )
2017-03-21 14:09:54 +08:00
{
2017-03-23 14:07:45 +08:00
bool wasHit = judgedObject . Judgement . Result = = HitResult . Hit ;
2017-04-03 09:20:20 +08:00
bool secondHit = judgedObject . Judgement . SecondHit ;
2017-03-21 15:33:25 +08:00
2017-03-23 18:00:18 +08:00
judgementContainer . Add ( new DrawableTaikoJudgement ( judgedObject . Judgement )
2017-03-21 15:33:25 +08:00
{
2017-03-23 14:07:45 +08:00
Anchor = wasHit ? Anchor . TopLeft : Anchor . CentreLeft ,
Origin = wasHit ? Anchor . BottomCentre : Anchor . Centre ,
2017-03-21 15:33:25 +08:00
RelativePositionAxes = Axes . X ,
2017-03-23 14:07:45 +08:00
X = wasHit ? judgedObject . Position . X : 0 ,
2017-03-21 15:33:25 +08:00
} ) ;
2017-04-03 09:20:20 +08:00
if ( ! wasHit )
return ;
2017-05-24 13:53:28 +08:00
bool isRim = judgedObject . HitObject is RimHit ;
2017-04-03 09:20:20 +08:00
if ( ! secondHit )
{
if ( judgedObject . X > = - 0.05f & & ! ( judgedObject is DrawableSwell ) )
{
// If we're far enough away from the left stage, we should bring outselves in front of it
topLevelHitContainer . Add ( judgedObject . CreateProxy ( ) ) ;
}
2017-05-24 13:53:28 +08:00
hitExplosionContainer . Add ( new HitExplosion ( judgedObject . Judgement , isRim ) ) ;
if ( judgedObject . HitObject . Kiai )
kiaiExplosionContainer . Add ( new KiaiHitExplosion ( judgedObject . Judgement , isRim ) ) ;
2017-04-03 09:20:20 +08:00
}
else
hitExplosionContainer . Children . FirstOrDefault ( e = > e . Judgement = = judgedObject . Judgement ) ? . VisualiseSecondHit ( ) ;
2016-09-02 18:58:57 +08:00
}
2017-04-10 09:33:52 +08:00
2017-04-10 10:54:01 +08:00
/// <summary>
2017-04-10 13:28:01 +08:00
/// This is a very special type of container. It serves a similar purpose to <see cref="FillMode.Fit"/>, however unlike <see cref="FillMode.Fit"/>,
/// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent.
2017-05-22 18:50:01 +08:00
///
2017-04-10 13:28:01 +08:00
/// <para>
/// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable
/// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this
/// container which takes care of reversing the width adjustment while appearing transparent to the user.
/// </para>
2017-04-10 10:54:01 +08:00
/// </summary>
2017-04-10 13:28:01 +08:00
private class ScaleFixContainer : Container
2017-04-10 09:33:52 +08:00
{
2017-04-10 13:28:01 +08:00
protected override Container < Drawable > Content = > widthAdjustmentContainer ;
private readonly WidthAdjustmentContainer widthAdjustmentContainer ;
/// <summary>
/// We only want to apply DrawScale in the Y-axis to preserve aspect ratio and <see cref="TaikoPlayfield"/> doesn't care about having its width adjusted.
/// </summary>
protected override Vector2 DrawScale = > Scale * RelativeToAbsoluteFactor . Y / DrawHeight ;
public ScaleFixContainer ( )
2017-04-10 09:33:52 +08:00
{
2017-04-10 13:28:01 +08:00
AddInternal ( widthAdjustmentContainer = new WidthAdjustmentContainer { ParentDrawScaleReference = ( ) = > DrawScale . X } ) ;
2017-04-10 09:33:52 +08:00
}
2017-04-10 13:28:01 +08:00
/// <summary>
/// The container type that reverses the <see cref="Drawable.DrawScale"/> width adjustment.
/// </summary>
private class WidthAdjustmentContainer : Container
2017-04-10 09:33:52 +08:00
{
2017-04-10 13:28:01 +08:00
/// <summary>
/// This container needs to know its parent's <see cref="Drawable.DrawScale"/> so it can reverse the width adjustment caused by <see cref="Drawable.DrawScale"/>.
/// </summary>
public Func < float > ParentDrawScaleReference ;
public WidthAdjustmentContainer ( )
{
// This container doesn't care about height, it should always fill its parent
RelativeSizeAxes = Axes . Y ;
}
2017-04-10 09:33:52 +08:00
2017-04-10 13:28:01 +08:00
protected override void Update ( )
{
base . Update ( ) ;
// Reverse the DrawScale adjustment
Width = Parent . DrawSize . X / ParentDrawScaleReference ( ) ;
}
2017-04-10 09:33:52 +08:00
}
}
2016-09-02 18:58:57 +08:00
}
}