Files
Convention-Unity-Demo/Assets/Scripts/Interaction/IInteraction.cs

445 lines
19 KiB
C#
Raw Normal View History

2025-10-08 00:35:50 +08:00
using System;
2025-09-25 19:04:05 +08:00
using System.Collections;
using Convention;
using Demo.Editor.UI;
using UnityEngine;
using UnityEngine.Events;
namespace Demo.Game
{
public interface IHookInteraction
{
}
public abstract class IInteraction : TimelineScriptObject
{
protected override void SetupTimelineItem(TimelineItem item)
{
item.SetupDuration(new(VisibleDuration.x, VisibleDuration.y), GetTimelineItemColor());
}
2025-10-08 00:35:50 +08:00
public const float IdentifiableJudgementTerminal = 0.16f;
[Content, SerializeField, Header("Cache")]
public float MyCacheUpdateTick = 0;
2025-09-25 19:04:05 +08:00
[Header(nameof(VisibleDuration))]
[Content, SerializeField] private Vector2 VisibleDuration;
2025-10-08 00:35:50 +08:00
public static float DefaultVisibleLength = IdentifiableJudgementTerminal * 15;
[Content, SerializeField] public UnityEvent VisibleDurationBeforeEvent = new(),
VisibleDurationBeginEvent = new(), VisibleDurationEndEvent = new();
2025-09-25 19:04:05 +08:00
[Header(nameof(InteractiveDuration))]
[Content, SerializeField] private Vector2 InteractiveDuration;
2025-10-08 00:35:50 +08:00
public static float DefaultInteractiveLength = IdentifiableJudgementTerminal * 9;
[Content, SerializeField] public UnityEvent InteractiveDurationBeforeEvent = new(),
InteractiveDurationBeginEvent = new(), InteractiveDurationEndEvent = new();
2025-09-25 19:04:05 +08:00
[Header(nameof(InteractableScoreInterval))]
[Content, SerializeField] private Vector2 InteractableScoreInterval;
2025-10-08 00:35:50 +08:00
public static float DefaultInteractableScoreIntervalLength = IdentifiableJudgementTerminal * 6;
[Content, SerializeField] public UnityEvent InteractableScoreIntervalBeforeEvent = new(),
InteractableScoreIntervalBeginEvent = new(), InteractableScoreIntervalEndEvent = new();
2025-09-25 19:04:05 +08:00
[Header(nameof(InteractableIntervalThatCanScoreBest))]
[Content, SerializeField] private Vector2 InteractableIntervalThatCanScoreBest;
2025-10-08 00:35:50 +08:00
public static float DefaultInteractableIntervalLengthThatCanScoreBest = IdentifiableJudgementTerminal * 3;
[Content, SerializeField] public UnityEvent InteractableIntervalThatCanScoreBestBeforeEvent = new(),
InteractableIntervalThatCanScoreBestBeginEvent = new(), InteractableIntervalThatCanScoreBestEndEvent = new();
[Flags,Serializable]
public enum UpdatePhaseComponent
{
OutsideVisibleDuration = 0b00000000,
//
InsideVisibleDuration = 0b00000001,
InsideInteractiveDuration = 0b00000010,
InsideInteractableScoreInterval = 0b00000100,
InsideInteractableIntervalThatCanScoreBest = 0b00001000,
//
EnterVisibleDuration = 0b00010000,
EnterInteractiveDuration = 0b00100000,
EnterInteractableScoreInterval = 0b01000000,
EnterInteractableIntervalThatCanScoreBest = 0b10000000,
}
[Flags, Serializable]
public enum UpdatePhase
{
// 进入可视区间之前
BeforeVisibleDuration
= UpdatePhaseComponent.OutsideVisibleDuration,
// 进入不可判定但可视的3级判定区间
EnterVisibleDuration
= UpdatePhaseComponent.InsideVisibleDuration | UpdatePhaseComponent.EnterVisibleDuration,
// 进入可判定为失误的2级判定区间
EnterInteractiveDuration
= UpdatePhaseComponent.InsideInteractiveDuration | UpdatePhaseComponent.EnterInteractiveDuration | UpdatePhase.EnterVisibleDuration,
// 进入可得分的1级判定区间
EnterInteractableScoreInterval
= UpdatePhaseComponent.InsideInteractableScoreInterval | UpdatePhaseComponent.EnterInteractableScoreInterval | UpdatePhase.EnterInteractiveDuration,
// 进入可得满分的0级判定区间
EnterInteractableIntervalThatCanScoreBest
= UpdatePhaseComponent.InsideInteractableIntervalThatCanScoreBest| UpdatePhaseComponent.EnterInteractableIntervalThatCanScoreBest
| UpdatePhase.EnterInteractableScoreInterval,
// 退出0级判定区间
AfterInteractableIntervalThatCanScoreBest
= UpdatePhase.EnterInteractableIntervalThatCanScoreBest&(~UpdatePhaseComponent.InsideInteractableIntervalThatCanScoreBest),
// 退出1级判定区间
AfterInteractableScoreInterval
= UpdatePhase.AfterInteractableIntervalThatCanScoreBest & (~UpdatePhaseComponent.InsideInteractableScoreInterval),
// 退出2级判断区间
AfterInteractiveDuration
= UpdatePhase.AfterInteractableScoreInterval & (~UpdatePhaseComponent.InsideInteractiveDuration),
// 退出3级判断区间, 已不可视
AfterVisibleDuration
= UpdatePhase.AfterInteractiveDuration & (~UpdatePhaseComponent.InsideVisibleDuration)
}
public enum JudgementLevel
{
None = -2,//No Judge
Default = -1,
BestLevel = 0,//Level0
ScoreLevel = 1,//Level1
Bad = 128,
}
2025-09-25 19:04:05 +08:00
[Header("Judgement")]
[Content, SerializeField] private JudgementLevel MyJudgementLevel = JudgementLevel.None;
[Content, SerializeField] private UpdatePhase MyUpdatePhase = default;
2025-09-25 19:04:05 +08:00
[Content, SerializeField] private float BestJudgementTimePoint = -1;
[Content, SerializeField] public UnityEvent<JudgementLevel> JudgementEvent = new();
2025-09-25 19:04:05 +08:00
public float GetBestJudgementTimePoint()
{
return BestJudgementTimePoint;
}
public bool IsInInteractiveDuration(float time)
{
return time > InteractiveDuration.x && time < InteractiveDuration.y;
}
public bool IsInInteractiveDuration()
{
return IsInInteractiveDuration(MyCacheUpdateTick);
}
2025-09-25 19:04:05 +08:00
public const int JudgementLevelCount = 3;
public override IEnumerator LoadScript(string script)
{
yield return base.LoadScript(script);
2025-10-08 00:35:50 +08:00
if (BestJudgementTimePoint <= 0)
2025-09-25 19:04:05 +08:00
{
2025-10-08 00:35:50 +08:00
DoSetupJudgement(0);
2025-09-25 19:04:05 +08:00
}
}
public override IEnumerator UnloadScript()
{
yield return base.UnloadScript();
VisibleDurationBeginEvent = new();
VisibleDurationEndEvent = new();
InteractiveDurationBeginEvent = new();
InteractiveDurationEndEvent = new();
InteractableScoreIntervalBeginEvent = new();
InteractableScoreIntervalEndEvent = new();
InteractableIntervalThatCanScoreBestBeginEvent = new();
InteractableIntervalThatCanScoreBestEndEvent = new();
BestJudgementTimePoint = -1;
}
public void InvokeJudgement(JudgementLevel level)
2025-09-25 19:04:05 +08:00
{
if (level == JudgementLevel.None)
2025-09-25 19:04:05 +08:00
return;
// 进入已判定状态
MyJudgementLevel = level;
JudgementEvent.Invoke(level);
// 触发还未触发的退出事件
2025-09-25 19:04:05 +08:00
{
if (((int)MyUpdatePhase & (int)UpdatePhaseComponent.EnterInteractableIntervalThatCanScoreBest) != 0)
{
InteractableIntervalThatCanScoreBestEndEvent.Invoke();
}
if (((int)MyUpdatePhase & (int)UpdatePhaseComponent.EnterInteractableScoreInterval) != 0)
{
InteractableScoreIntervalEndEvent.Invoke();
}
if (((int)MyUpdatePhase & (int)UpdatePhaseComponent.EnterInteractiveDuration) != 0)
{
InteractiveDurationEndEvent.Invoke();
}
if (((int)MyUpdatePhase & (int)UpdatePhaseComponent.EnterVisibleDuration) != 0)
{
VisibleDurationEndEvent.Invoke();
}
2025-09-25 19:04:05 +08:00
}
}
public abstract JudgementLevel JudgementBehaviour(float timePoint);
2025-09-25 19:04:05 +08:00
#region Update Judgement Stats
2025-10-08 00:35:50 +08:00
private void DoUpdateJudgementStats(float currentTime)
{
// 重置
if (MyCacheUpdateTick > currentTime)
{
MyUpdatePhase = UpdatePhase.BeforeVisibleDuration;
VisibleDurationBeforeEvent.Invoke();
InteractiveDurationBeforeEvent.Invoke();
InteractableScoreIntervalBeforeEvent.Invoke();
InteractableIntervalThatCanScoreBestBeforeEvent.Invoke();
MyJudgementLevel = JudgementLevel.None;
}
// 状态更新
{
// 在当前区间进行判断, 当前时间落入下一个区间时, 就进入下一个区间
switch (MyUpdatePhase)
{
case UpdatePhase.BeforeVisibleDuration:
if (currentTime > VisibleDuration.x)
{
MyUpdatePhase = UpdatePhase.EnterVisibleDuration;
VisibleDurationBeginEvent.Invoke();
}
break;
case UpdatePhase.EnterVisibleDuration:
if (currentTime > InteractiveDuration.x)
{
MyUpdatePhase = UpdatePhase.EnterInteractiveDuration;
InteractiveDurationBeginEvent.Invoke();
}
break;
case UpdatePhase.EnterInteractiveDuration:
if (currentTime > InteractableScoreInterval.x)
{
MyUpdatePhase = UpdatePhase.EnterInteractableScoreInterval;
InteractableScoreIntervalBeginEvent.Invoke();
}
break;
case UpdatePhase.EnterInteractableScoreInterval:
if (currentTime > InteractableIntervalThatCanScoreBest.x)
{
MyUpdatePhase = UpdatePhase.EnterInteractableIntervalThatCanScoreBest;
InteractableIntervalThatCanScoreBestBeginEvent.Invoke();
}
break;
// ---------------------------------------------------------
case UpdatePhase.EnterInteractableIntervalThatCanScoreBest:
if (currentTime > InteractableIntervalThatCanScoreBest.y)
{
MyUpdatePhase = UpdatePhase.AfterInteractableIntervalThatCanScoreBest;
InteractableIntervalThatCanScoreBestEndEvent.Invoke();
}
break;
case UpdatePhase.AfterInteractableIntervalThatCanScoreBest:
if (currentTime > InteractableScoreInterval.y)
{
MyUpdatePhase = UpdatePhase.AfterInteractableScoreInterval;
InteractableScoreIntervalEndEvent.Invoke();
}
break;
case UpdatePhase.AfterInteractableScoreInterval:
if (currentTime > InteractiveDuration.y)
{
MyUpdatePhase = UpdatePhase.AfterInteractiveDuration;
InteractiveDurationEndEvent.Invoke();
}
break;
case UpdatePhase.AfterInteractiveDuration:
if (currentTime > VisibleDuration.y)
{
MyUpdatePhase = UpdatePhase.AfterVisibleDuration;
VisibleDurationEndEvent.Invoke();
}
break;
default:
break;
}
}
// 记录
MyCacheUpdateTick = currentTime;
2025-10-08 00:35:50 +08:00
}
#endregion
2025-10-08 00:35:50 +08:00
2025-09-25 19:04:05 +08:00
protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
2025-10-08 00:35:50 +08:00
base.UpdateTicks(currentTime, deltaTime, tickType);
// 检定
if (MyJudgementLevel == JudgementLevel.None)
{
InvokeJudgement(JudgementBehaviour(currentTime));
}
2025-10-08 00:35:50 +08:00
// 更新状态
DoUpdateJudgementStats(currentTime);
}
public void DoSetupJudgementLevels(
float bestJudgementTimePoint,
float interactableIntervalThatCanScoreBest,
float interactableScoreInterval,
float interactiveDuration,
float visibleDuration)
{
this.BestJudgementTimePoint = bestJudgementTimePoint;
var bestJudgementTimePointValue = bestJudgementTimePoint;
// InteractableIntervalThatCanScoreBest
var interactableIntervalThatCanScoreBestValue = interactableIntervalThatCanScoreBest * 0.5f;
InteractableIntervalThatCanScoreBest = new(bestJudgementTimePointValue - interactableIntervalThatCanScoreBestValue,
bestJudgementTimePointValue + interactableIntervalThatCanScoreBestValue);
// InteractableScoreInterval
var interactableScoreIntervalValue = interactableScoreInterval * 0.5f;
InteractableScoreInterval = new(bestJudgementTimePointValue - interactableScoreIntervalValue,
bestJudgementTimePointValue + interactableScoreIntervalValue);
// InteractiveDuration
var interactiveDurationValue = interactiveDuration * 0.5f;
InteractiveDuration = new(bestJudgementTimePointValue - interactiveDurationValue,
bestJudgementTimePointValue + interactiveDurationValue);
// InteractableScoreInterval
var visibleDurationValue = visibleDuration * 0.5f;
VisibleDuration = new(bestJudgementTimePointValue - visibleDurationValue,
bestJudgementTimePointValue + visibleDurationValue);
}
public void DoSetupJudgement(float bestJudgementTimePoint)
{
DoSetupJudgementLevels(
bestJudgementTimePoint,
DefaultInteractableIntervalLengthThatCanScoreBest,
DefaultInteractableScoreIntervalLength,
DefaultInteractiveLength,
DefaultVisibleLength);
}
/// <summary>
/// 使用配置中的区间长度设置以最佳判定点为中心的各级区间
/// </summary>
/// <param name="bestJudgementTimePoint">最佳判定点</param>
[Convention.RScript.Variable.Attr.Method]
2025-10-08 00:35:50 +08:00
public void SetupJudgement(string bestJudgementTimePoint)
{
DoSetupJudgement(Parse(bestJudgementTimePoint));
2025-09-25 19:04:05 +08:00
}
/// <summary>
/// 通过传递对称区间进行初始化
/// </summary>
/// <param name="bestJudgementTimePoint">最佳判定点</param>
/// <param name="interactableIntervalThatCanScoreBest">区间时长,最终结果为
/// (bestJudgementTimePoint-interactableIntervalThatCanScoreBest/2,bestJudgementTimePoint+interactableIntervalThatCanScoreBest/2)</param>
/// <param name="interactableScoreInterval">区间时长,最终结果为
/// (bestJudgementTimePoint-interactableScoreInterval/2,bestJudgementTimePoint+interactableScoreInterval/2)</param>
/// <param name="interactiveDuration">区间时长,最终结果为
/// (bestJudgementTimePoint-interactiveDuration/2,bestJudgementTimePoint+interactiveDuration/2)</param>
/// <param name="visibleDuration">区间时长,最终结果为
/// (bestJudgementTimePoint-visibleDuration/2,bestJudgementTimePoint+visibleDuration/2)</param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetupJudgementLevels(
string bestJudgementTimePoint,
string interactableIntervalThatCanScoreBest,
string interactableScoreInterval,
string interactiveDuration,
string visibleDuration)
{
2025-10-08 00:35:50 +08:00
DoSetupJudgementLevels(
Parse(bestJudgementTimePoint),
Parse(interactableIntervalThatCanScoreBest),
Parse(interactableScoreInterval),
Parse(interactiveDuration),
Parse(visibleDuration));
2025-09-25 19:04:05 +08:00
}
/// <summary>
/// 设置可见区间显现但不可判定3级判定区间开始时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetVisibleDurationBegin(string value)
{
VisibleDuration.x = Parse(value);
}
/// <summary>
/// 设置可见区间显现但不可判定3级判定区间结束时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetVisibleDurationEnd(string value)
{
VisibleDuration.y = Parse(value);
}
/// <summary>
/// 设置2级判定区间可判定但错误的开始时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetInteractiveDurationBegin(string value)
{
InteractiveDuration.x = Parse(value);
}
/// <summary>
/// 设置2级判定区间可判定但错误的结束时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetInteractiveDurationEnd(string value)
{
InteractiveDuration.y = Parse(value);
}
/// <summary>
/// 设置1级判定区间可判定的开始时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetInteractableScoreIntervalBegin(string value)
{
InteractableScoreInterval.x = Parse(value);
}
/// <summary>
/// 设置1级判定区间可判定的结束时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetInteractableScoreIntervalEnd(string value)
{
InteractableScoreInterval.y = Parse(value);
}
/// <summary>
/// 设置0级判定区间最佳判定开始时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetInteractableIntervalThatCanScoreBestBegin(string value)
{
InteractableIntervalThatCanScoreBest.x = Parse(value);
2025-10-08 00:35:50 +08:00
if (BestJudgementTimePoint < 0)
{
BestJudgementTimePoint = Mathf.Lerp(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBest.y, 0.5f);
}
2025-09-25 19:04:05 +08:00
}
/// <summary>
/// 设置0级判定区间最佳判定结束时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
2025-09-25 19:04:05 +08:00
public void SetInteractableIntervalThatCanScoreBestEnd(string value)
{
InteractableIntervalThatCanScoreBest.y = Parse(value);
2025-10-08 00:35:50 +08:00
if (BestJudgementTimePoint < 0)
{
BestJudgementTimePoint = Mathf.Lerp(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBest.y, 0.5f);
}
2025-09-25 19:04:05 +08:00
}
}
}