Files
ft/Client/Assets/Editor/PinballUncountableDomeBaker.cs
2026-06-29 21:18:33 +08:00

184 lines
6.8 KiB
C#
Raw Permalink 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.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;
}
}
}