mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
415 lines
14 KiB
C#
415 lines
14 KiB
C#
using System.Linq;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Audio.Sample;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Animations;
|
|
using osu.Framework.Graphics.Containers;
|
|
using osu.Framework.Graphics.Sprites;
|
|
using osu.Framework.Graphics.Textures;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Game.Configuration;
|
|
using osu.Game.Rulesets.Judgements;
|
|
using osu.Game.Rulesets.Scoring;
|
|
using osu.Game.Screens.Play;
|
|
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
|
using osu.Game.Skinning;
|
|
using osu.Game.Skinning.Components;
|
|
using osuTK;
|
|
using osuTK.Graphics;
|
|
|
|
namespace osu.Game.Rulesets.Mania.Skinning.Ez2.Ez2HUD
|
|
{
|
|
public partial class EzComHitResultScore : CompositeDrawable, ISerialisableDrawable //, IAnimatableJudgement
|
|
{
|
|
public bool UsesFixedAnchor { get; set; }
|
|
|
|
[SettingSource("Playback FPS", "The FPS value of this Animation")]
|
|
public BindableNumber<float> PlaybackFps { get; } = new BindableNumber<float>(60)
|
|
{
|
|
MinValue = 1,
|
|
MaxValue = 240,
|
|
Precision = 1f,
|
|
};
|
|
|
|
[SettingSource("HitResult Text Font", "HitResult Text Font", SettingControlType = typeof(EzEnumListSelector))]
|
|
public Bindable<OffsetNumberName> NameDropdown { get; } = new Bindable<OffsetNumberName>((OffsetNumberName)49);
|
|
|
|
// [SettingSource("Effect Type", "Effect Type")]
|
|
// public Bindable<EffectType> Effect { get; } = new Bindable<EffectType>(EffectType.Scale);
|
|
//
|
|
// [SettingSource("Effect Origin", "Effect Origin", SettingControlType = typeof(AnchorDropdown))]
|
|
// public Bindable<Anchor> EffectOrigin { get; } = new Bindable<Anchor>(Anchor.TopCentre)
|
|
// {
|
|
// Default = Anchor.TopCentre,
|
|
// Value = Anchor.TopCentre
|
|
// };
|
|
|
|
private Vector2 dragStartPosition;
|
|
private bool isDragging;
|
|
public Sprite? StaticSprite;
|
|
private Container? fullComboSprite;
|
|
private Sample? fullComboSound;
|
|
|
|
[Resolved]
|
|
private TextureStore textures { get; set; } = null!;
|
|
|
|
[Resolved]
|
|
private ScoreProcessor processor { get; set; } = null!;
|
|
|
|
[Resolved(canBeNull: true)]
|
|
private GameplayClockContainer gameplayClockContainer { get; set; } = null!;
|
|
|
|
[Resolved]
|
|
private JudgementCountController judgementCountController { get; set; } = null!;
|
|
|
|
[Resolved]
|
|
private ISampleStore sampleStore { get; set; } = null!;
|
|
|
|
public EzComHitResultScore()
|
|
{
|
|
Size = new Vector2(200, 50);
|
|
Anchor = Anchor.Centre;
|
|
Origin = Anchor.Centre;
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
AlwaysPresent = true;
|
|
|
|
fullComboSprite = new Container
|
|
{
|
|
Child = new Sprite
|
|
{
|
|
Anchor = Anchor.Centre,
|
|
Origin = Anchor.Centre,
|
|
Scale = new Vector2(1.5f),
|
|
Alpha = 0, // 初始隐藏
|
|
Texture = textures.Get("EzResources/AllCombo/ALL-COMBO2"),
|
|
}
|
|
};
|
|
|
|
AddInternal(fullComboSprite);
|
|
|
|
fullComboSound = sampleStore.Get("EzResources/AllCombo/full_combo_sound");
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
|
|
gameplayClockContainer.OnSeek += Clear;
|
|
|
|
processor.NewJudgement += processorNewJudgement;
|
|
|
|
NameDropdown.BindValueChanged(_ =>
|
|
{
|
|
ClearInternal(true);
|
|
|
|
StaticSprite?.Invalidate();
|
|
}, true);
|
|
}
|
|
|
|
private void processorNewJudgement(JudgementResult j)
|
|
{
|
|
Schedule(() =>
|
|
{
|
|
OnNewJudgement(j);
|
|
|
|
if (processor.JudgedHits == processor.MaximumCombo)
|
|
checkFullCombo();
|
|
});
|
|
}
|
|
|
|
protected void OnNewJudgement(JudgementResult judgement)
|
|
{
|
|
if (!judgement.IsHit || judgement.HitObject.HitWindows?.WindowFor(HitResult.Miss) == 0)
|
|
return;
|
|
|
|
if (!judgement.Type.IsScorable() || judgement.Type.IsBonus())
|
|
return;
|
|
|
|
// 清除内部元素前先结束所有变换
|
|
StaticSprite?.FinishTransforms();
|
|
|
|
ClearInternal();
|
|
StaticSprite = null;
|
|
|
|
var judgementText = CreateJudgementTexture(judgement.Type);
|
|
AddInternal(judgementText);
|
|
}
|
|
|
|
protected Drawable CreateJudgementTexture(HitResult result)
|
|
{
|
|
string basePath = getGifPath(result);
|
|
var singleTexture = textures.Get($"{basePath}.png");
|
|
|
|
if (singleTexture != null)
|
|
{
|
|
StaticSprite = new Sprite
|
|
{
|
|
Anchor = Anchor.Centre,
|
|
Origin = Anchor.Centre,
|
|
Scale = new Vector2(0.5f),
|
|
Texture = singleTexture,
|
|
Alpha = 0,
|
|
// 设置混合模式
|
|
Blending = new BlendingParameters
|
|
{
|
|
// 1. 标准透明混合(最常用)
|
|
// Source = BlendingType.SrcAlpha,
|
|
// Destination = BlendingType.OneMinusSrcAlpha,
|
|
|
|
// 2. 加法混合(发光效果)
|
|
Source = BlendingType.SrcAlpha,
|
|
Destination = BlendingType.One,
|
|
|
|
// 3. 减法混合(暗色透明)
|
|
// Source = BlendingType.Zero,
|
|
// Destination = BlendingType.OneMinusSrcColor,
|
|
|
|
// 4. 纯色叠加(忽略黑色)
|
|
// Source = BlendingType.One,
|
|
// Destination = BlendingType.One,
|
|
|
|
// 5. 柔和混合
|
|
// Source = BlendingType.DstColor,
|
|
// Destination = BlendingType.One,
|
|
}
|
|
};
|
|
|
|
Schedule(() =>
|
|
{
|
|
PlayAnimation(result, StaticSprite);
|
|
});
|
|
|
|
return StaticSprite;
|
|
}
|
|
|
|
// 不存在单张图片时,尝试加载动画帧
|
|
var animation = new TextureAnimation
|
|
{
|
|
Anchor = Anchor.Centre,
|
|
Origin = Anchor.Centre,
|
|
Scale = new Vector2(1.2f),
|
|
DefaultFrameLength = 1000 / PlaybackFps.Value,
|
|
Loop = false
|
|
};
|
|
|
|
for (int i = 0;; i++)
|
|
{
|
|
var texture = textures.Get($@"{basePath}/frame_{i}");
|
|
if (texture == null)
|
|
break;
|
|
|
|
animation.AddFrame(texture);
|
|
}
|
|
|
|
PlaybackFps.BindValueChanged(fps =>
|
|
{
|
|
animation.DefaultFrameLength = 1000 / fps.NewValue;
|
|
}, true);
|
|
|
|
PlayAnimationGif(result, animation);
|
|
|
|
animation.OnUpdate += _ =>
|
|
{
|
|
if (animation.CurrentFrameIndex == animation.FrameCount - 1)
|
|
animation.Expire();
|
|
};
|
|
|
|
return animation;
|
|
}
|
|
|
|
private string getGifPath(HitResult hitResult)
|
|
{
|
|
string textureNameReplace = NameDropdown.Value.ToString();
|
|
string basePath = $@"EzResources/GameTheme/{textureNameReplace}/judgement";
|
|
string resultName = hitResult switch
|
|
{
|
|
HitResult.Miss => "Miss",
|
|
HitResult.Meh => "Fail",
|
|
HitResult.Ok => "Fail",
|
|
HitResult.Good => "Good",
|
|
HitResult.Great => "Cool",
|
|
HitResult.Perfect => "Kool",
|
|
_ => string.Empty
|
|
};
|
|
|
|
return $"{basePath}/{resultName}";
|
|
}
|
|
|
|
private void checkFullCombo()
|
|
{
|
|
var missCounter = judgementCountController.Counters
|
|
.FirstOrDefault(counter => counter.Types.Contains(HitResult.Miss));
|
|
|
|
if (missCounter.ResultCount.Value == 0 && fullComboSprite != null)
|
|
{
|
|
// 显示 FULL COMBO 贴图
|
|
fullComboSprite.Alpha = 1;
|
|
fullComboSprite.FadeIn(50).Then().FadeOut(5000);
|
|
|
|
// 播放音效
|
|
fullComboSound?.Play();
|
|
}
|
|
}
|
|
|
|
public virtual void PlayAnimationGif(HitResult hitResult, Drawable drawable)
|
|
{
|
|
const float flash_speed = 60f;
|
|
applyFadeEffect(hitResult, drawable, flash_speed);
|
|
}
|
|
|
|
private void applyFadeEffect(HitResult hitResult, Drawable drawable, double flashSpeed)
|
|
{
|
|
if (!drawable.IsLoaded)
|
|
return;
|
|
|
|
// 为每种判定结果定义颜色数组
|
|
var colors = hitResult switch
|
|
{
|
|
HitResult.Miss => new[] { Color4.Red, Color4.IndianRed },
|
|
HitResult.Meh => new[] { Color4.Purple, Color4.MediumPurple },
|
|
HitResult.Ok => new[] { Color4.ForestGreen, Color4.SeaGreen },
|
|
HitResult.Good => new[] { Color4.Green, Color4.LightGreen },
|
|
HitResult.Great => new[] { Color4.AliceBlue, Color4.LightSkyBlue },
|
|
HitResult.Perfect => new[] { Color4.LightBlue, Color4.LightGreen },
|
|
_ => new[] { Color4.White }
|
|
};
|
|
|
|
if (drawable is TextureAnimation)
|
|
{
|
|
drawable.FadeColour(colors[0], 0);
|
|
var sequence = drawable.FadeColour(colors[0], flashSpeed, Easing.OutQuint);
|
|
|
|
for (int i = 1; i < colors.Length; i++)
|
|
{
|
|
sequence = sequence.Then().FadeColour(colors[i], flashSpeed, Easing.OutQuint);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// // 保持原有透明度,只将颜色调整为 20% 强度
|
|
// var fadedColors = colors.Select(c => new Color4(
|
|
// c.R * 0.5f + 0.5f, // 将颜色与白色混合,保持 20% 的原色
|
|
// c.G * 0.5f + 0.5f,
|
|
// c.B * 0.5f + 0.5f,
|
|
// 1f)).ToArray();
|
|
float[] weakerAlphas = new[] { 1f, 0.8f };
|
|
|
|
drawable.FadeTo(weakerAlphas[0], 0);
|
|
var sequence = drawable.FadeTo(weakerAlphas[0], flashSpeed, Easing.OutQuint);
|
|
|
|
for (int i = 1; i < weakerAlphas.Length; i++)
|
|
{
|
|
sequence = sequence.Then().FadeTo(weakerAlphas[i], flashSpeed, Easing.OutQuint);
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void PlayAnimation(HitResult hitResult, Drawable drawable)
|
|
{
|
|
double flashSpeed = PlaybackFps.Value * 2;
|
|
applyFadeEffect(hitResult, drawable, flashSpeed);
|
|
|
|
switch (hitResult)
|
|
{
|
|
case HitResult.Perfect:
|
|
// 中心直接绘制最大状态,向上移动并拉长压扁消失
|
|
applyEzStyleEffect(drawable, new Vector2(1.2f), 15);
|
|
break;
|
|
|
|
case HitResult.Great:
|
|
// 中心绘制,稍微放大后拉长压扁消失
|
|
applyEzStyleEffect(drawable, new Vector2(1.1f));
|
|
break;
|
|
|
|
case HitResult.Good:
|
|
// 中心小状态,向上放大并移动后拉长压扁消失
|
|
applyEzStyleEffect(drawable, new Vector2(1f));
|
|
break;
|
|
|
|
case HitResult.Ok:
|
|
case HitResult.Meh:
|
|
// 中心小状态,放大并向下移动后拉长压扁消失
|
|
applyEzStyleEffect(drawable, new Vector2(1f));
|
|
break;
|
|
|
|
case HitResult.Miss:
|
|
// 中心小状态,放大后快速消失
|
|
applyEzStyleEffect(drawable, new Vector2(1f));
|
|
break;
|
|
|
|
default:
|
|
applyEzStyleEffect(drawable, new Vector2(1.2f));
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void applyEzStyleEffect(Drawable drawable, Vector2 scaleUp, float moveDistance = 0)
|
|
{
|
|
// 先结束之前的所有变换
|
|
drawable.FinishTransforms();
|
|
// 固定拉长压扁比例
|
|
var finalScale = new Vector2(1.5f, 0.05f);
|
|
// 固定动画持续时间
|
|
const double scale_up_duration = 150; // 放大动画
|
|
const double scale_down_duration = 180; // 压扁动画
|
|
const double fade_out_duration = scale_down_duration + 10; // 淡出动画
|
|
|
|
// 重置状态
|
|
drawable.Alpha = 1;
|
|
// drawable.Position = Vector2.Zero;
|
|
|
|
drawable
|
|
// 第一步:放大动画,同时执行位移(如果有)
|
|
.ScaleTo(scaleUp, scale_up_duration, Easing.OutQuint)
|
|
.MoveTo(new Vector2(0, moveDistance), scale_up_duration, Easing.OutQuint)
|
|
.Delay(scale_up_duration + 20)
|
|
// 第二步:在放大基础上进行横向拉长和纵向压缩(使用固定比例)
|
|
.TransformTo(nameof(Scale), new Vector2(scaleUp.X * finalScale.X, scaleUp.Y * finalScale.Y), scale_down_duration, Easing.InQuint)
|
|
.MoveTo(new Vector2(0, -moveDistance / 10), fade_out_duration, Easing.InQuint)
|
|
.FadeOut(fade_out_duration, Easing.InQuint);
|
|
}
|
|
|
|
protected virtual void Clear()
|
|
{
|
|
FinishTransforms(true);
|
|
|
|
ClearInternal();
|
|
}
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
PlaybackFps.UnbindAll();
|
|
processor.NewJudgement -= processorNewJudgement;
|
|
gameplayClockContainer.OnSeek -= Clear;
|
|
base.Dispose(isDisposing);
|
|
}
|
|
|
|
protected override bool OnDragStart(DragStartEvent e)
|
|
{
|
|
dragStartPosition = e.ScreenSpaceMousePosition;
|
|
isDragging = true;
|
|
return true;
|
|
}
|
|
|
|
protected override void OnDrag(DragEvent e)
|
|
{
|
|
if (isDragging)
|
|
{
|
|
var delta = e.ScreenSpaceMousePosition - dragStartPosition;
|
|
Position += delta;
|
|
dragStartPosition = e.ScreenSpaceMousePosition;
|
|
}
|
|
}
|
|
|
|
protected override void OnDragEnd(DragEndEvent e)
|
|
{
|
|
isDragging = false;
|
|
}
|
|
}
|
|
}
|