备份,准备做双端
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Replay 录制/回放系统:记录输入流(trackIndex + dspTime)并可重放。
|
||||
/// 供后续快速接入(需配合 InputManager 和 autoplay 逻辑连接)。
|
||||
/// </summary>
|
||||
[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<InputEvent> inputEvents = new List<InputEvent>();
|
||||
|
||||
// 元数据:录制时的游戏版本、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<ReplayData>(json);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReplayRecorder
|
||||
{
|
||||
private static ReplayData currentReplay = null;
|
||||
private static bool isRecording = false;
|
||||
|
||||
/// <summary>
|
||||
/// 开始录制。通常在 GameManager 启动游玩时调用。
|
||||
/// </summary>
|
||||
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})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止录制并返回录制数据。通常在结算时调用。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录一次输入事件。在 InputManager.OnTrackPressedWithDspTime / OnTrackReleasedWithDspTime 中调用。
|
||||
/// </summary>
|
||||
public static void RecordInput(int trackIndex, double dspTime, bool isPress)
|
||||
{
|
||||
if (!isRecording || currentReplay == null) return;
|
||||
|
||||
currentReplay.inputEvents.Add(new ReplayData.InputEvent(trackIndex, dspTime, isPress));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消当前录制。
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 开始回放。在 GameManager 启动游玩前调用(需先设置 autoplay 模式)。
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止回放。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发回放输入。需接入 InputManager 的事件系统或直接调用判定逻辑。
|
||||
/// 当前为占位实现,供后续快速接入。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user