Files
Convention-Unity-Demo/Assets/Scripts/Interaction/IInteraction.cs
2025-12-15 17:20:55 +08:00

473 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using System.IO;
using Convention;
using Demo.Editor.UI;
using UnityEngine;
using UnityEngine.Events;
namespace Demo.Game
{
namespace ConfigType
{
// IInteraction 配置抽象基类Config继承自TimelineScriptObject
public class IInteractionConfig : ScriptLoadableConfig
{
public Vector2 VisibleDuration;
public Vector2 InteractiveDuration;
public Vector2 InteractableScoreInterval;
public Vector2 InteractableIntervalThatCanScoreBest;
public IInteraction.JudgementLevel MyJudgementLevel;
public IInteraction.UpdatePhase MyUpdatePhase;
public override void Deserialize(BinaryReader reader)
{
VisibleDuration = BinarySerializeUtility.ReadVec2(reader);
InteractiveDuration = BinarySerializeUtility.ReadVec2(reader);
InteractableScoreInterval = BinarySerializeUtility.ReadVec2(reader);
InteractableIntervalThatCanScoreBest = BinarySerializeUtility.ReadVec2(reader);
MyJudgementLevel = (IInteraction.JudgementLevel)BinarySerializeUtility.ReadInt(reader);
MyUpdatePhase = (IInteraction.UpdatePhase)BinarySerializeUtility.ReadInt(reader);
base.Deserialize(reader);
}
public override void Serialize(BinaryWriter writer)
{
BinarySerializeUtility.WriteVec2(writer, VisibleDuration);
BinarySerializeUtility.WriteVec2(writer, InteractiveDuration);
BinarySerializeUtility.WriteVec2(writer, InteractableScoreInterval);
BinarySerializeUtility.WriteVec2(writer, InteractableIntervalThatCanScoreBest);
BinarySerializeUtility.WriteInt(writer, (int)MyJudgementLevel);
BinarySerializeUtility.WriteInt(writer, (int)MyUpdatePhase);
base.Serialize(writer);
}
}
}
public interface IHookInteraction
{
}
public abstract class IInteraction : TimelineScriptObject
{
protected override void SetupTimelineItem(TimelineItem item)
{
item.SetupDuration(new(VisibleDuration.x, VisibleDuration.y), GetTimelineItemColor());
}
public const float IdentifiableJudgementTerminal = 0.16f;
[Content, SerializeField, Header("Cache")]
public float MyCacheUpdateTick = 0;
[Header(nameof(VisibleDuration))]
[Content, SerializeField] private Vector2 VisibleDuration;
public static float DefaultVisibleLength = IdentifiableJudgementTerminal * 15;
[Content, SerializeField] public UnityEvent VisibleDurationBeforeEvent = new(),
VisibleDurationBeginEvent = new(), VisibleDurationEndEvent = new();
[Header(nameof(InteractiveDuration))]
[Content, SerializeField] private Vector2 InteractiveDuration;
public static float DefaultInteractiveLength = IdentifiableJudgementTerminal * 9;
[Content, SerializeField] public UnityEvent InteractiveDurationBeforeEvent = new(),
InteractiveDurationBeginEvent = new(), InteractiveDurationEndEvent = new();
[Header(nameof(InteractableScoreInterval))]
[Content, SerializeField] private Vector2 InteractableScoreInterval;
public static float DefaultInteractableScoreIntervalLength = IdentifiableJudgementTerminal * 6;
[Content, SerializeField] public UnityEvent InteractableScoreIntervalBeforeEvent = new(),
InteractableScoreIntervalBeginEvent = new(), InteractableScoreIntervalEndEvent = new();
[Header(nameof(InteractableIntervalThatCanScoreBest))]
[Content, SerializeField] private Vector2 InteractableIntervalThatCanScoreBest;
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,
}
[Header("Judgement")]
[Content, SerializeField] private JudgementLevel MyJudgementLevel = JudgementLevel.None;
[Content, SerializeField] private UpdatePhase MyUpdatePhase = default;
[Content, SerializeField] private float BestJudgementTimePoint = -1;
[Content, SerializeField] public UnityEvent<JudgementLevel> JudgementEvent = new();
public float GetBestJudgementTimePoint()
{
return BestJudgementTimePoint;
}
public bool IsInInteractiveDuration(float time)
{
return time > InteractiveDuration.x && time < InteractiveDuration.y;
}
public bool IsInInteractiveDuration()
{
return IsInInteractiveDuration(MyCacheUpdateTick);
}
public const int JudgementLevelCount = 3;
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)
{
if (level == JudgementLevel.None)
return;
// 进入已判定状态
MyJudgementLevel = level;
JudgementEvent.Invoke(level);
// 触发还未触发的退出事件
{
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();
}
}
}
public abstract JudgementLevel JudgementBehaviour(float timePoint);
#region Update Judgement Stats
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;
}
#endregion
protected override void UpdateTicks(float currentTime, float deltaTime, TickType tickType)
{
base.UpdateTicks(currentTime, deltaTime, tickType);
// 检定
if (MyJudgementLevel == JudgementLevel.None)
{
InvokeJudgement(JudgementBehaviour(currentTime));
}
// 更新状态
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]
public void SetupJudgement(float bestJudgementTimePoint)
{
DoSetupJudgement(bestJudgementTimePoint);
}
/// <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]
public void SetupJudgementLevels(
float bestJudgementTimePoint,
float interactableIntervalThatCanScoreBest,
float interactableScoreInterval,
float interactiveDuration,
float visibleDuration)
{
DoSetupJudgementLevels(
bestJudgementTimePoint,
interactableIntervalThatCanScoreBest,
interactableScoreInterval,
interactiveDuration,
visibleDuration);
}
/// <summary>
/// 设置可见区间显现但不可判定3级判定区间开始时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetVisibleDurationBegin(float value)
{
VisibleDuration.x = value;
}
/// <summary>
/// 设置可见区间显现但不可判定3级判定区间结束时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetVisibleDurationEnd(float value)
{
VisibleDuration.y = value;
}
/// <summary>
/// 设置2级判定区间可判定但错误的开始时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetInteractiveDurationBegin(float value)
{
InteractiveDuration.x = value;
}
/// <summary>
/// 设置2级判定区间可判定但错误的结束时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetInteractiveDurationEnd(float value)
{
InteractiveDuration.y = value;
}
/// <summary>
/// 设置1级判定区间可判定的开始时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetInteractableScoreIntervalBegin(float value)
{
InteractableScoreInterval.x = value;
}
/// <summary>
/// 设置1级判定区间可判定的结束时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetInteractableScoreIntervalEnd(float value)
{
InteractableScoreInterval.y = value;
}
/// <summary>
/// 设置0级判定区间最佳判定开始时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetInteractableIntervalThatCanScoreBestBegin(float value)
{
InteractableIntervalThatCanScoreBest.x = value;
if (BestJudgementTimePoint < 0)
{
BestJudgementTimePoint = Mathf.Lerp(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBest.y, 0.5f);
}
}
/// <summary>
/// 设置0级判定区间最佳判定结束时间
/// </summary>
/// <param name="value"></param>
[Convention.RScript.Variable.Attr.Method]
public void SetInteractableIntervalThatCanScoreBestEnd(float value)
{
InteractableIntervalThatCanScoreBest.y = value;
if (BestJudgementTimePoint < 0)
{
BestJudgementTimePoint = Mathf.Lerp(InteractableIntervalThatCanScoreBest.x, InteractableIntervalThatCanScoreBest.y, 0.5f);
}
}
}
}