using System;
using System.Collections.Generic;
using UnityEngine;
///
/// Replay 录制/回放系统:记录输入流(trackIndex + dspTime)并可重放。
/// 供后续快速接入(需配合 InputManager 和 autoplay 逻辑连接)。
///
[Serializable]
public class ReplayData
{
[Serializable]
public class InputEvent
{
public int trackIndex;
public double dspTime;
public bool isPress; // true=按下, false=松开
public InputEvent(int trackIndex, double dspTime, bool isPress)
{
this.trackIndex = trackIndex;
this.dspTime = dspTime;
this.isPress = isPress;
}
}
public string songId;
public string difficulty;
public int totalScore;
public float accuracy;
public int maxCombo;
public string recordedAt; // ISO8601 时间戳
public List inputEvents = new List();
// 元数据:录制时的游戏版本、offset 等(供回放时校验兼容性)
public float recordedSpawnOffset;
public float recordedVisualOffset;
public string gameVersion;
public ReplayData()
{
recordedAt = DateTime.UtcNow.ToString("o");
}
public string ToJson()
{
return JsonUtility.ToJson(this, prettyPrint: true);
}
public static ReplayData FromJson(string json)
{
return JsonUtility.FromJson(json);
}
}
public static class ReplayRecorder
{
private static ReplayData currentReplay = null;
private static bool isRecording = false;
///
/// 开始录制。通常在 GameManager 启动游玩时调用。
///
public static void StartRecording(string songId, string difficulty)
{
if (isRecording)
{
Debug.LogWarning("[ReplayRecorder] Already recording, stop first.");
return;
}
currentReplay = new ReplayData
{
songId = songId,
difficulty = difficulty,
recordedSpawnOffset = PlayerPrefs.GetFloat("UserGlobalDelaySeconds", 0f),
recordedVisualOffset = PlayerPrefs.GetFloat("UserVisualOffsetSeconds", 0f),
gameVersion = Application.version
};
isRecording = true;
Debug.Log($"[ReplayRecorder] Started recording: {songId} ({difficulty})");
}
///
/// 停止录制并返回录制数据。通常在结算时调用。
///
public static ReplayData StopRecording(int totalScore, float accuracy, int maxCombo)
{
if (!isRecording || currentReplay == null)
{
Debug.LogWarning("[ReplayRecorder] Not recording.");
return null;
}
currentReplay.totalScore = totalScore;
currentReplay.accuracy = accuracy;
currentReplay.maxCombo = maxCombo;
isRecording = false;
Debug.Log($"[ReplayRecorder] Stopped. Recorded {currentReplay.inputEvents.Count} input events.");
ReplayData result = currentReplay;
currentReplay = null;
return result;
}
///
/// 记录一次输入事件。在 InputManager.OnTrackPressedWithDspTime / OnTrackReleasedWithDspTime 中调用。
///
public static void RecordInput(int trackIndex, double dspTime, bool isPress)
{
if (!isRecording || currentReplay == null) return;
currentReplay.inputEvents.Add(new ReplayData.InputEvent(trackIndex, dspTime, isPress));
}
///
/// 取消当前录制。
///
public static void CancelRecording()
{
if (isRecording)
{
Debug.Log("[ReplayRecorder] Recording cancelled.");
}
isRecording = false;
currentReplay = null;
}
public static bool IsRecording => isRecording;
}
public class ReplayPlayer : MonoBehaviour
{
private ReplayData replayData;
private int nextEventIndex = 0;
private bool isPlaying = false;
private double replayStartDsp;
///
/// 开始回放。在 GameManager 启动游玩前调用(需先设置 autoplay 模式)。
///
public void StartPlayback(ReplayData replay)
{
if (replay == null || replay.inputEvents == null || replay.inputEvents.Count == 0)
{
Debug.LogError("[ReplayPlayer] Invalid replay data.");
return;
}
replayData = replay;
nextEventIndex = 0;
isPlaying = true;
replayStartDsp = AudioSettings.dspTime;
Debug.Log($"[ReplayPlayer] Started playback: {replay.inputEvents.Count} events");
}
///
/// 停止回放。
///
public void StopPlayback()
{
isPlaying = false;
replayData = null;
nextEventIndex = 0;
Debug.Log("[ReplayPlayer] Playback stopped.");
}
private void Update()
{
if (!isPlaying || replayData == null) return;
double currentDsp = AudioSettings.dspTime;
double elapsedDsp = currentDsp - replayStartDsp;
// 按时间顺序触发录制的输入事件
while (nextEventIndex < replayData.inputEvents.Count)
{
var evt = replayData.inputEvents[nextEventIndex];
if (evt.dspTime > elapsedDsp) break; // 还没到时间
// 触发输入事件(需接入 InputManager 或直接调用 Note 判定逻辑)
TriggerReplayInput(evt.trackIndex, evt.isPress, currentDsp);
nextEventIndex++;
}
// 所有事件已回放完成
if (nextEventIndex >= replayData.inputEvents.Count)
{
Debug.Log("[ReplayPlayer] Playback finished.");
isPlaying = false;
}
}
///
/// 触发回放输入。需接入 InputManager 的事件系统或直接调用判定逻辑。
/// 当前为占位实现,供后续快速接入。
///
private void TriggerReplayInput(int trackIndex, bool isPress, double dspTime)
{
// TODO: 接入 InputManager 或 JudgeManager
// 示例:InputManager.SimulateInput(trackIndex, dspTime, isPress);
// 或:直接触发 Note.HandlePress / HoldNote.HandleRelease 等
if (JudgeManager.IsDebugEnabled)
{
Debug.Log($"[ReplayPlayer] Trigger: track={trackIndex} press={isPress} dsp={dspTime:F3}");
}
}
public bool IsPlaying => isPlaying;
}