using System.Collections.Generic; using UnityEditor; using UnityEngine; using PinballUncountable; namespace PinballUncountable.Editor { /// /// 在 Play 模式下录制 Dome 内小球的物理轨迹,烘焙为可回放数据。 /// 使用方式:进入 Play 模式 → 选中带 PinballUncountableDome 的物体 → 菜单 Pinball Uncountable / Bake Dome Trajectories /// public static class PinballUncountableDomeBaker { private const float SampleInterval = 0.02f; // 50fps,与回放一致 private const float BakeDuration = 5f; private static PinballUncountableDome _dome; private static List[] _samples; private static float _nextSampleTime; private static float _bakeEndTime; private static int _seed; [MenuItem("Pinball Uncountable/Bake Dome Trajectories", true)] private static bool ValidateBake() { return Application.isPlaying && GetDomeFromSelection() != null; } [MenuItem("Pinball Uncountable/Bake Dome Trajectories")] private static void Bake() { var dome = GetDomeFromSelection(); if (dome == null) { Debug.LogWarning("[DomeBaker] 请先在 Hierarchy 中选中带有 PinballUncountableDome 的物体,并处于 Play 模式。"); return; } StartBake(dome, (int)System.DateTime.Now.Ticks); } /// /// 使用指定种子烘焙一帧(可用于批量生成多组变体) /// public static void BakeWithSeed(PinballUncountableDome dome, int seed) { if (dome == null || !Application.isPlaying) { Debug.LogWarning("[DomeBaker] BakeWithSeed 需在 Play 模式下且 dome 非空。"); return; } StartBake(dome, seed); } private static PinballUncountableDome GetDomeFromSelection() { var go = Selection.activeGameObject; if (go == null) return null; return go.GetComponent() ?? go.GetComponentInChildren(); } private static void StartBake(PinballUncountableDome dome, int seed) { CleanupBakeState(); _dome = dome; _seed = seed; var so = new SerializedObject(dome); var ballsProp = so.FindProperty("balls"); var xRange = so.FindProperty("xRange").floatValue; var yRange = so.FindProperty("yRange").floatValue; int n = ballsProp.arraySize; if (n == 0) { Debug.LogError("[DomeBaker] Dome 的 balls 数组为空。"); CleanupBakeState(); return; } _dome.SetBakePhysicsCollisionEnabled(true); Random.InitState(seed); var rbs = new Rigidbody2D[n]; for (int i = 0; i < n; i++) { var refProp = ballsProp.GetArrayElementAtIndex(i); rbs[i] = refProp.objectReferenceValue as Rigidbody2D; if (rbs[i] != null) { rbs[i].simulated = true; rbs[i].velocity = Vector2.zero; rbs[i].angularVelocity = 0f; rbs[i].AddForce(new Vector2(Random.Range(-xRange, xRange), Random.Range(0, yRange)), ForceMode2D.Impulse); } } _samples = new List[n]; for (int i = 0; i < n; i++) _samples[i] = new List(); _nextSampleTime = Time.time; _bakeEndTime = Time.time + BakeDuration; EditorApplication.update += RecordUpdate; Debug.Log($"[DomeBaker] 开始录制 Dome 轨迹,种子={seed},时长={BakeDuration}s,球数={n}"); } private static void RecordUpdate() { if (_dome == null || _samples == null) { CleanupBakeState(); return; } var so = new SerializedObject(_dome); var ballsProp = so.FindProperty("balls"); int n = ballsProp.arraySize; while (Time.time >= _nextSampleTime && _nextSampleTime < _bakeEndTime) { for (int i = 0; i < n && i < _samples.Length; i++) { var rb = ballsProp.GetArrayElementAtIndex(i).objectReferenceValue as Rigidbody2D; if (rb != null) _samples[i].Add(rb.transform.position); } _nextSampleTime += SampleInterval; } if (Time.time >= _bakeEndTime) { EditorApplication.update -= RecordUpdate; FinishBake(); } } private static void FinishBake() { if (_dome == null || _samples == null) return; var variant = new DomeKickVariant { trajectories = new SerializableDomeTrajectory[_samples.Length] }; for (int i = 0; i < _samples.Length; i++) variant.trajectories[i] = new SerializableDomeTrajectory(_samples[i].ToArray()); string path = "Assets/ABPackage/pkgDownloads/EventPinballUncountable/EventPinballUncountableCowBoy/Data/DomeBaked.asset"; var existing = AssetDatabase.LoadAssetAtPath(path); if (existing != null) { var list = new List(existing.variants ?? System.Array.Empty()); list.Add(variant); existing.variants = list.ToArray(); EditorUtility.SetDirty(existing); AssetDatabase.SaveAssets(); Debug.Log($"[DomeBaker] 已追加 1 组轨迹到 {path},当前共 {existing.variants.Length} 组。"); } else { var asset = ScriptableObject.CreateInstance(); asset.variants = new[] { variant }; AssetDatabase.CreateAsset(asset, path); AssetDatabase.SaveAssets(); Debug.Log($"[DomeBaker] 已创建烘焙资源: {path},1 组轨迹。将此项赋给 Dome 的 Baked Data 即可使用回放。"); } CleanupBakeState(); } private static void CleanupBakeState() { EditorApplication.update -= RecordUpdate; if (_dome != null) { _dome.StopBalls(); _dome.SetBakePhysicsCollisionEnabled(false); } _dome = null; _samples = null; } } }