184 lines
6.8 KiB
C#
184 lines
6.8 KiB
C#
using System.Collections.Generic;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
using PinballUncountable;
|
||
|
||
namespace PinballUncountable.Editor
|
||
{
|
||
/// <summary>
|
||
/// 在 Play 模式下录制 Dome 内小球的物理轨迹,烘焙为可回放数据。
|
||
/// 使用方式:进入 Play 模式 → 选中带 PinballUncountableDome 的物体 → 菜单 Pinball Uncountable / Bake Dome Trajectories
|
||
/// </summary>
|
||
public static class PinballUncountableDomeBaker
|
||
{
|
||
private const float SampleInterval = 0.02f; // 50fps,与回放一致
|
||
private const float BakeDuration = 5f;
|
||
private static PinballUncountableDome _dome;
|
||
private static List<Vector2>[] _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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用指定种子烘焙一帧(可用于批量生成多组变体)
|
||
/// </summary>
|
||
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<PinballUncountableDome>() ?? go.GetComponentInChildren<PinballUncountableDome>();
|
||
}
|
||
|
||
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<Vector2>[n];
|
||
for (int i = 0; i < n; i++)
|
||
_samples[i] = new List<Vector2>();
|
||
|
||
_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<PinballUncountableDomeBakedData>(path);
|
||
if (existing != null)
|
||
{
|
||
var list = new List<DomeKickVariant>(existing.variants ?? System.Array.Empty<DomeKickVariant>());
|
||
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<PinballUncountableDomeBakedData>();
|
||
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;
|
||
}
|
||
}
|
||
}
|