Files
Emberwild/Assets/GAME/Script/Editor/BuiltPrefabMigrator.cs
T
2026-07-22 12:56:13 +02:00

110 lines
4.4 KiB
C#

using UnityEditor;
using UnityEngine;
using Ashwild.Building;
using FishNet.Object;
namespace Ashwild.EditorTools
{
/// <summary>
/// One-shot migration for the move to server-spawned builds: every committed structure is now a real
/// NetworkObject, so each buildable's BuiltPrefab needs a NetworkObject and a BuiltStructure on its
/// root. Prefabs authored before that change have neither (or only BuiltStructure, which is now a
/// NetworkBehaviour and cannot function alone), and a missing NetworkObject makes the prefab silently
/// unspawnable — the exact failure that used to leave a placed chest deactivated.
///
/// Safe to re-run: it only touches prefabs that are actually missing a component, and reports what it
/// changed. Kept as an explicit menu action rather than an automatic postprocessor because it rewrites
/// authored assets, which should never happen behind the designer's back.
/// </summary>
public static class BuiltPrefabMigrator
{
#region Menu
/// <summary>
/// Scans every BuildableData, ensures its BuiltPrefab carries NetworkObject + BuiltStructure, and
/// asks FishNet to rescan its spawnable prefab collection afterwards.
/// </summary>
[MenuItem("Tools/Ashwild/Migrate Built Prefabs To Network Objects")]
public static void Migrate()
{
string[] guids = AssetDatabase.FindAssets("t:BuildableData");
int migrated = 0;
int alreadyFine = 0;
int missingPrefab = 0;
foreach (string guid in guids)
{
BuildableData buildable = AssetDatabase.LoadAssetAtPath<BuildableData>(AssetDatabase.GUIDToAssetPath(guid));
if (buildable == null) continue;
if (buildable.BuiltPrefab == null)
{
Debug.LogWarning($"[BuiltPrefabMigrator] '{buildable.name}' has no BuiltPrefab — skipped.", buildable);
missingPrefab++;
continue;
}
if (EnsureNetworked(buildable.BuiltPrefab)) migrated++;
else alreadyFine++;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
RefreshNetworkPrefabRegistry();
Debug.Log($"[BuiltPrefabMigrator] Done — {migrated} prefab(s) migrated, {alreadyFine} already correct, {missingPrefab} buildable(s) without a BuiltPrefab.");
}
#endregion
#region Internal Helpers
/// <summary>
/// Adds whatever the prefab root is missing and saves it. The NetworkObject goes on first so that
/// BuiltStructure's RequireComponent is already satisfied when it is added. Returns true when the
/// prefab was actually changed.
/// </summary>
private static bool EnsureNetworked(GameObject prefab)
{
string path = AssetDatabase.GetAssetPath(prefab);
if (string.IsNullOrEmpty(path)) return false;
GameObject root = PrefabUtility.LoadPrefabContents(path);
bool changed = false;
if (root.GetComponent<NetworkObject>() == null)
{
root.AddComponent<NetworkObject>();
changed = true;
}
if (root.GetComponent<BuiltStructure>() == null)
{
root.AddComponent<BuiltStructure>();
changed = true;
}
if (changed)
{
PrefabUtility.SaveAsPrefabAsset(root, path);
Debug.Log($"[BuiltPrefabMigrator] Migrated '{path}'.", AssetDatabase.LoadAssetAtPath<GameObject>(path));
}
PrefabUtility.UnloadPrefabContents(root);
return changed;
}
/// <summary>
/// Forces FishNet to rescan DefaultPrefabObjects so the migrated prefabs are spawnable. Invoked
/// through the menu item because the generator API is internal to the FishNet assembly.
/// </summary>
private static void RefreshNetworkPrefabRegistry()
{
const string menu = "Tools/Fish-Networking/Utility/Refresh Default Prefabs";
if (!EditorApplication.ExecuteMenuItem(menu))
Debug.LogWarning($"[BuiltPrefabMigrator] Could not run '{menu}' — run it by hand so the built prefabs are registered as spawnable.");
}
#endregion
}
}