Files
Cielonos/Assets/Scripts/SLSUtilities/Feedback/Runtime/FeedbackPlayer.cs
SoulliesOfficial 41140a2017 新Feedback系统
2026-04-12 02:11:15 -04:00

330 lines
11 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.Generic;
using UnityEngine;
namespace SLSUtilities.Feedback
{
/// <summary>
/// 反馈播放器状态枚举。
/// </summary>
public enum FeedbackPlayerState
{
Idle,
Playing,
Paused
}
/// <summary>
/// 运行时反馈播放器(纯 C# 类,非 MonoBehaviour管理一个 FeedbackData 的播放生命周期。
/// 由 FeedbackManager 的 Update 集中驱动,也可由外部(如 FeedbackSubcontroller手动驱动。
/// </summary>
public class FeedbackPlayer
{
private const float MIN_DURATION = 0.001f;
private FeedbackData _data;
private FeedbackPlayerState _state;
private float _currentTime;
private IFeedbackTimeProvider _timeProvider;
private Transform _ownerTransform;
private bool _isCompleted;
// 每个 Clip 的运行时状态
private enum ClipState { Pending, Active, Finished }
private ClipState[,] _clipStates; // [trackIndex, clipIndex]
private float[,] _clipElapsedTimes; // [trackIndex, clipIndex]
private bool _hasSoloTracks;
public FeedbackData Data => _data;
public FeedbackPlayerState State => _state;
public float CurrentTime => _currentTime;
public IFeedbackTimeProvider TimeProvider => _timeProvider;
public Transform OwnerTransform => _ownerTransform;
/// <summary>
/// 播放完毕事件。
/// </summary>
public event Action OnComplete;
/// <summary>
/// 被打断事件。
/// </summary>
public event Action OnInterrupt;
/// <summary>
/// 是否已播放完毕(自然结束)。
/// </summary>
public bool IsCompleted => _isCompleted;
/// <summary>
/// 是否处于活跃状态Playing 或 Paused
/// </summary>
public bool IsActive => _state == FeedbackPlayerState.Playing || _state == FeedbackPlayerState.Paused;
public FeedbackPlayer(FeedbackData data, IFeedbackTimeProvider timeProvider, Transform ownerTransform)
{
_data = data;
_timeProvider = timeProvider;
_ownerTransform = ownerTransform;
_state = FeedbackPlayerState.Idle;
_currentTime = 0f;
_isCompleted = false;
}
/// <summary>
/// 开始播放。
/// </summary>
public void Play()
{
if (_data == null)
{
Debug.LogWarning("[FeedbackPlayer] Cannot play: FeedbackData is null.");
return;
}
_currentTime = 0f;
_isCompleted = false;
_state = FeedbackPlayerState.Playing;
InitializeClipStates();
}
/// <summary>
/// 暂停播放。
/// </summary>
public void Pause()
{
if (_state == FeedbackPlayerState.Playing)
{
_state = FeedbackPlayerState.Paused;
}
}
/// <summary>
/// 恢复播放。
/// </summary>
public void Resume()
{
if (_state == FeedbackPlayerState.Paused)
{
_state = FeedbackPlayerState.Playing;
}
}
/// <summary>
/// 立即停止并复位所有已启动的 Action。
/// </summary>
public void Stop()
{
if (_state == FeedbackPlayerState.Idle) return;
InterruptAllActiveClips();
_state = FeedbackPlayerState.Idle;
OnInterrupt?.Invoke();
ClearEvents();
}
/// <summary>
/// 每帧由外部驱动调用FeedbackManager 或 Subcontroller
/// </summary>
public void Tick(float unscaledDeltaTime)
{
if (_state != FeedbackPlayerState.Playing) return;
if (_data == null || _data.tracks == null) return;
float totalDuration = _data.TotalDuration;
// 处理 TotalDuration 为 0 的边界情况
if (totalDuration <= 0f)
{
_state = FeedbackPlayerState.Idle;
_isCompleted = true;
OnComplete?.Invoke();
ClearEvents();
return;
}
List<FeedbackTrack> tracks = _data.tracks;
for (int trackIdx = 0; trackIdx < tracks.Count; trackIdx++)
{
FeedbackTrack track = tracks[trackIdx];
if (!ShouldPlayTrack(track)) continue;
for (int clipIdx = 0; clipIdx < track.clips.Count; clipIdx++)
{
FeedbackClip clip = track.clips[clipIdx];
if (clip?.action == null) continue;
float clipDeltaTime = ComputeClipDeltaTime(clip, unscaledDeltaTime);
ProcessClip(trackIdx, clipIdx, clip, clipDeltaTime);
}
}
_currentTime += unscaledDeltaTime;
if (_currentTime >= totalDuration)
{
_state = FeedbackPlayerState.Idle;
_isCompleted = true;
OnComplete?.Invoke();
ClearEvents();
}
}
/// <summary>
/// 清除所有事件订阅,防止完成/停止后残留引用。
/// </summary>
private void ClearEvents()
{
OnComplete = null;
OnInterrupt = null;
}
/// <summary>
/// 初始化所有 Clip 的运行时状态数组。
/// </summary>
private void InitializeClipStates()
{
List<FeedbackTrack> tracks = _data.tracks;
if (tracks == null || tracks.Count == 0) return;
int maxClips = 0;
_hasSoloTracks = false;
for (int i = 0; i < tracks.Count; i++)
{
if (tracks[i].clips != null && tracks[i].clips.Count > maxClips)
maxClips = tracks[i].clips.Count;
if (tracks[i].solo)
_hasSoloTracks = true;
}
_clipStates = new ClipState[tracks.Count, maxClips];
_clipElapsedTimes = new float[tracks.Count, maxClips];
}
/// <summary>
/// 判断轨道是否应该播放:处理 Mute 和 Solo 逻辑。
/// </summary>
private bool ShouldPlayTrack(FeedbackTrack track)
{
if (track.mute) return false;
if (_hasSoloTracks && !track.solo) return false;
return true;
}
/// <summary>
/// 根据 Clip 的时间设置计算实际 deltaTime。
/// </summary>
private float ComputeClipDeltaTime(FeedbackClip clip, float unscaledDeltaTime)
{
if (_timeProvider == null) return unscaledDeltaTime;
FeedbackTimeSettings settings = clip.overrideTimeSettings
? clip.timeSettings
: _data.defaultTimeSettings;
if (settings == null || !settings.useTimeScale) return unscaledDeltaTime;
return _timeProvider.GetDeltaTime(settings);
}
/// <summary>
/// 处理单个 Clip 的生命周期状态转换和回调调用。
/// </summary>
private void ProcessClip(int trackIdx, int clipIdx, FeedbackClip clip, float deltaTime)
{
ref ClipState clipState = ref _clipStates[trackIdx, clipIdx];
ref float elapsed = ref _clipElapsedTimes[trackIdx, clipIdx];
float safeDuration = Mathf.Max(clip.duration, MIN_DURATION);
switch (clipState)
{
case ClipState.Pending:
if (_currentTime >= clip.startTime)
{
clipState = ClipState.Active;
elapsed = _currentTime - clip.startTime;
FeedbackContext ctx = CreateContext(deltaTime, elapsed, safeDuration);
clip.action.OnStart(ctx);
float normalizedTime = Mathf.Clamp01(elapsed / safeDuration);
clip.action.OnUpdate(CreateContext(deltaTime, elapsed, safeDuration), normalizedTime);
}
break;
case ClipState.Active:
elapsed += deltaTime;
if (elapsed >= safeDuration)
{
elapsed = safeDuration;
clipState = ClipState.Finished;
FeedbackContext ctx = CreateContext(deltaTime, elapsed, safeDuration);
clip.action.OnUpdate(ctx, 1f);
clip.action.OnEnd(ctx);
}
else
{
float normalizedTime = Mathf.Clamp01(elapsed / safeDuration);
FeedbackContext ctx = CreateContext(deltaTime, elapsed, safeDuration);
clip.action.OnUpdate(ctx, normalizedTime);
}
break;
case ClipState.Finished:
break;
}
}
/// <summary>
/// 打断所有已激活的 Clip调用其 OnInterrupt 进行复位。
/// </summary>
private void InterruptAllActiveClips()
{
if (_data?.tracks == null || _clipStates == null) return;
List<FeedbackTrack> tracks = _data.tracks;
for (int trackIdx = 0; trackIdx < tracks.Count; trackIdx++)
{
if (tracks[trackIdx].clips == null) continue;
for (int clipIdx = 0; clipIdx < tracks[trackIdx].clips.Count; clipIdx++)
{
if (_clipStates[trackIdx, clipIdx] == ClipState.Active)
{
FeedbackClip clip = tracks[trackIdx].clips[clipIdx];
if (clip?.action == null) continue;
float elapsed = _clipElapsedTimes[trackIdx, clipIdx];
float safeDuration = Mathf.Max(clip.duration, MIN_DURATION);
FeedbackContext ctx = CreateContext(0f, elapsed, safeDuration);
clip.action.OnInterrupt(ctx);
}
_clipStates[trackIdx, clipIdx] = ClipState.Finished;
}
}
}
/// <summary>
/// 创建 FeedbackContext 实例。
/// </summary>
private FeedbackContext CreateContext(float deltaTime, float elapsedTime, float duration)
{
return new FeedbackContext
{
player = this,
owner = _ownerTransform,
deltaTime = deltaTime,
elapsedTime = elapsedTime,
duration = duration
};
}
}
}