Files
Cielonos/Assets/Scripts/SLSUtilities/Feedback/Runtime/FeedbackPlayer.cs
SoulliesOfficial 39b43680a9 爆更
2026-07-18 03:16:20 -04:00

317 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
{
public enum FeedbackPlayerState
{
Idle,
Playing,
Paused
}
/// <summary>
/// 运行时反馈播放器。播放器只读取 RuntimeFeedbackTemplateData 仅用于来源识别和停止匹配。
/// </summary>
public class FeedbackPlayer
{
private const float MIN_DURATION = 0.001f;
private readonly RuntimeFeedback _runtimeFeedback;
private readonly IFeedbackTimeProvider _timeProvider;
private readonly Transform _ownerTransform;
private FeedbackPlayerState _state;
private float _currentTime;
private bool _isCompleted;
private enum ClipState { Pending, Active, Finished }
private ClipState[,] _clipStates;
private float[,] _clipElapsedTimes;
private bool _hasSoloTracks;
public FeedbackData Data => TemplateData;
public FeedbackData TemplateData => _runtimeFeedback?.templateData;
public RuntimeFeedback RuntimeData => _runtimeFeedback;
public FeedbackPlayerState State => _state;
public float CurrentTime => _currentTime;
public IFeedbackTimeProvider TimeProvider => _timeProvider;
public Transform OwnerTransform => _ownerTransform;
public bool IsCompleted => _isCompleted;
public bool IsActive => _state == FeedbackPlayerState.Playing || _state == FeedbackPlayerState.Paused;
public event Action OnComplete;
public event Action OnInterrupt;
public FeedbackPlayer(FeedbackData data, IFeedbackTimeProvider timeProvider, Transform ownerTransform)
: this(data?.CreateRuntimeFeedback(), timeProvider, ownerTransform)
{
}
public FeedbackPlayer(RuntimeFeedback runtimeFeedback, IFeedbackTimeProvider timeProvider, Transform ownerTransform)
{
_runtimeFeedback = runtimeFeedback;
_timeProvider = timeProvider ?? DefaultFeedbackTimeProvider.Instance;
_ownerTransform = ownerTransform;
_state = FeedbackPlayerState.Idle;
_currentTime = 0f;
_isCompleted = false;
}
public void Play()
{
if (_runtimeFeedback == null)
{
Debug.LogWarning("[FeedbackPlayer] Cannot play: RuntimeFeedback is null.");
return;
}
_currentTime = 0f;
_isCompleted = false;
_state = FeedbackPlayerState.Playing;
InitializeClipStates();
}
public void Pause()
{
if (_state == FeedbackPlayerState.Playing)
{
_state = FeedbackPlayerState.Paused;
}
}
public void Resume()
{
if (_state == FeedbackPlayerState.Paused)
{
_state = FeedbackPlayerState.Playing;
}
}
public void Stop()
{
if (_state == FeedbackPlayerState.Idle) return;
InterruptAllActiveClips();
_state = FeedbackPlayerState.Idle;
OnInterrupt?.Invoke();
ClearEvents();
}
public void Tick(float deltaTime)
{
if (_state != FeedbackPlayerState.Playing) return;
if (_runtimeFeedback?.tracks == null) return;
float totalDuration = _runtimeFeedback.TotalDuration;
if (totalDuration <= 0f)
{
_state = FeedbackPlayerState.Idle;
_isCompleted = true;
OnComplete?.Invoke();
ClearEvents();
return;
}
List<FeedbackTrack> tracks = _runtimeFeedback.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 clipTimeScale = ComputeClipTimeScale(clip);
float clipDeltaTime = deltaTime * clipTimeScale;
ProcessClip(trackIdx, clipIdx, clip, clipDeltaTime, clipTimeScale);
}
}
_currentTime += deltaTime;
if (_currentTime >= totalDuration && AllClipsFinished())
{
_state = FeedbackPlayerState.Idle;
_isCompleted = true;
OnComplete?.Invoke();
ClearEvents();
}
}
private void ClearEvents()
{
OnComplete = null;
OnInterrupt = null;
}
private void InitializeClipStates()
{
List<FeedbackTrack> tracks = _runtimeFeedback.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];
}
private bool ShouldPlayTrack(FeedbackTrack track)
{
if (track == null) return false;
if (track.mute) return false;
if (_hasSoloTracks && !track.solo) return false;
return true;
}
private float ComputeClipTimeScale(FeedbackClip clip)
{
if (_timeProvider == null) return 1f;
if (clip?.action == null || clip.action.IgnoreTimeScale) return 1f;
FeedbackTimeSettings settings = clip.overrideTimeSettings ? clip.timeSettings : _runtimeFeedback.defaultTimeSettings;
if (settings == null || settings.timeScaleType == FeedbackTimeSettings.TimeScaleType.Unscaled) return 1f;
return _timeProvider.GetTimeScale(settings);
}
private void ProcessClip(int trackIdx, int clipIdx, FeedbackClip clip, float deltaTime, float timeScale)
{
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(trackIdx, clipIdx, deltaTime, elapsed, safeDuration, timeScale, GetTimeSettings(clip));
clip.action.OnStart(ctx);
float normalizedTime = Mathf.Clamp01(elapsed / safeDuration);
clip.action.OnUpdate(ctx, normalizedTime);
}
break;
case ClipState.Active:
FeedbackTimeSettings settings = GetTimeSettings(clip);
float currentTimeScale = timeScale;
if (settings != null && settings.applyDynamicTimeScale)
{
currentTimeScale = ComputeClipTimeScale(clip);
}
float adjustedDeltaTime = deltaTime * currentTimeScale;
elapsed += adjustedDeltaTime;
if (elapsed >= safeDuration)
{
elapsed = safeDuration;
clipState = ClipState.Finished;
FeedbackContext ctx = CreateContext(trackIdx, clipIdx, deltaTime, elapsed, safeDuration, currentTimeScale, settings);
clip.action.OnUpdate(ctx, 1f);
clip.action.OnEnd(ctx);
}
else
{
float normalizedTime = Mathf.Clamp01(elapsed / safeDuration);
FeedbackContext ctx = CreateContext(trackIdx, clipIdx, deltaTime, elapsed, safeDuration, currentTimeScale, settings);
clip.action.OnUpdate(ctx, normalizedTime);
}
break;
}
}
private FeedbackTimeSettings GetTimeSettings(FeedbackClip clip)
{
return clip.overrideTimeSettings ? clip.timeSettings : _runtimeFeedback.defaultTimeSettings;
}
private void InterruptAllActiveClips()
{
if (_runtimeFeedback?.tracks == null || _clipStates == null) return;
List<FeedbackTrack> tracks = _runtimeFeedback.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)
{
float elapsed = _clipElapsedTimes[trackIdx, clipIdx];
float safeDuration = Mathf.Max(clip.duration, MIN_DURATION);
FeedbackContext ctx = CreateContext(trackIdx, clipIdx, 0f, elapsed, safeDuration, 1f, GetTimeSettings(clip));
clip.action.OnInterrupt(ctx);
}
}
_clipStates[trackIdx, clipIdx] = ClipState.Finished;
}
}
}
private bool AllClipsFinished()
{
if (_clipStates == null) return true;
List<FeedbackTrack> tracks = _runtimeFeedback.tracks;
for (int trackIdx = 0; trackIdx < tracks.Count; trackIdx++)
{
FeedbackTrack track = tracks[trackIdx];
if (!ShouldPlayTrack(track)) continue;
if (track.clips == null) continue;
for (int clipIdx = 0; clipIdx < track.clips.Count; clipIdx++)
{
if (_clipStates[trackIdx, clipIdx] == ClipState.Active)
return false;
}
}
return true;
}
private FeedbackContext CreateContext(int trackIdx, int clipIdx, float deltaTime, float elapsedTime,
float duration, float timeScale, FeedbackTimeSettings timeSettings)
{
return new FeedbackContext
{
player = this,
runtimeFeedbackId = _runtimeFeedback.runtimeId,
trackIndex = trackIdx,
clipIndex = clipIdx,
owner = _ownerTransform,
deltaTime = deltaTime,
elapsedTime = elapsedTime,
duration = duration,
timeScale = timeScale,
timeSettings = timeSettings
};
}
}
}