Alle 86 Modelle der Godot-Referenz laufen durch die Blender→FBX-Pipeline
(nach bestandenem Vertical Function Test); jede Einheit zeigt ihr eigenes
Modell.
- Exportmanifest aus den Godot-Manifesten (tools/blender/make_manifest.py):
Units, Enemies, Props, Characters, Peoples, Buildings, Environment; die
drei Vergleichs-GLBs als Aliase. Neu in der Pipeline: Tangenten berechnet
Unity mit MikkTSpace, Generatoren laufen in einer temporären Kopie, die
Grüne Kluft wird wie das GLB zusammengefasst, Texturen kommen mit.
1.616 Blender- und 1.247 Unity-Prüfungen grün, zwei Exportläufe
byteidentisch; Kontaktbögen je Gruppe.
- Einheitenbibliothek nach Godots AssetLibrary: 70 Einträge der
Einheitengruppen, Figuren mit Skelett ersetzen das starre Modell ihrer ID.
Starre Modelle werden zur Laufzeit je Material zusammengefasst; Controller
werden an Ort und Stelle aktualisiert.
- Tests mit allen Modellen: EditMode 11/11, PlayMode 2/2. Vertical Function
Test Seed 2 DCE95C96556F3A1A und Seed 9 ab Runde 21 7B8E43AEFC0D60C5,
beide gleich dem Kern (Nachweise mit LUNE_EVIDENCE=u2a-batch; die Belege
von U2C und U2D bleiben unverändert).
- macOS-Player mit allen Modellen: 158-160 fps in der Übersicht
(116 Einheiten), 205-207 fps auf der Mid-Lane; Haupt- und Render-Thread
etwa doppelt so lang wie mit dem Platzhalter, Present-Ausreißer kürzer.
- Befund iCloud Drive: Konfliktkopien ("Name 2.ext") im Build-Cache ließen
den Build die Einheitenbibliothek mit altem Feldlayout schreiben; der
Player meldete sharedassets0.assets als beschädigt. LunePlayerBuild
entfernt solche Kopien vor dem Build und bricht ab, wenn während des
Builds neue entstehen. Bereits committete Kopien: Controller-Duplikate und
World/Generated 2.meta entfernt, drei U2D-Benchmarkläufe als -run1 bis
-run3 benannt.
- Dokumentation: U2-WORLD (Batch-Import), PORT-STATUS (Entscheidungen:
Modelle direkt in Git, Projektort außerhalb von iCloud Drive), PORT-QA.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
3.9 KiB
C#
79 lines
3.9 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace Luneraria.EditorTools
|
|
{
|
|
/// <summary>
|
|
/// Import rules for every FBX of the Blender pipeline under Assets/Luneraria/Art (U2A).
|
|
/// Metres stay metres, Blender's Z-up axes are converted by baking (no root rotation),
|
|
/// normals come from the file, tangents are computed with MikkTSpace (as in Blender), rigs are
|
|
/// generic with the Blender bone names, and clips get their Blender action names, loop flags
|
|
/// and contact events.
|
|
/// </summary>
|
|
public sealed class LuneModelPostprocessor : AssetPostprocessor
|
|
{
|
|
public const string ArtRoot = "Assets/Luneraria/Art/";
|
|
/// <summary>Raised on the figure at the hit frame of attack and ability clips.</summary>
|
|
public const string ContactEvent = "OnLuneContact";
|
|
|
|
public override uint GetVersion() { return 4; }
|
|
|
|
void OnPreprocessModel()
|
|
{
|
|
if (!assetPath.StartsWith(ArtRoot) || !assetPath.EndsWith(".fbx"))
|
|
return;
|
|
ExportReport report = ExportReport.ForModel(assetPath);
|
|
var importer = (ModelImporter)assetImporter;
|
|
importer.globalScale = 1f;
|
|
importer.useFileScale = true;
|
|
importer.bakeAxisConversion = true;
|
|
importer.importCameras = false;
|
|
importer.importLights = false;
|
|
importer.importVisibility = false;
|
|
importer.importBlendShapes = false;
|
|
importer.importNormals = ModelImporterNormals.Import;
|
|
importer.importTangents = ModelImporterTangents.CalculateMikk;
|
|
importer.meshCompression = ModelImporterMeshCompression.Off;
|
|
importer.isReadable = true;
|
|
importer.materialImportMode = ModelImporterMaterialImportMode.ImportViaMaterialDescription;
|
|
importer.materialLocation = ModelImporterMaterialLocation.InPrefab;
|
|
bool skinned = report != null && report.Skinned;
|
|
importer.animationType = skinned ? ModelImporterAnimationType.Generic : ModelImporterAnimationType.None;
|
|
importer.avatarSetup = skinned ? ModelImporterAvatarSetup.CreateFromThisModel : ModelImporterAvatarSetup.NoAvatar;
|
|
importer.importAnimation = skinned;
|
|
importer.optimizeGameObjects = false;
|
|
importer.animationCompression = ModelImporterAnimationCompression.Off;
|
|
importer.resampleCurves = false;
|
|
}
|
|
|
|
void OnPreprocessAnimation()
|
|
{
|
|
if (!assetPath.StartsWith(ArtRoot) || !assetPath.EndsWith(".fbx"))
|
|
return;
|
|
ExportReport report = ExportReport.ForModel(assetPath);
|
|
if (report == null || !report.Skinned)
|
|
return;
|
|
var importer = (ModelImporter)assetImporter;
|
|
var blender = report.Clips.ToDictionary(c => (string)c["name"], c => c);
|
|
var clips = new List<ModelImporterClipAnimation>();
|
|
foreach (ModelImporterClipAnimation clip in importer.defaultClipAnimations)
|
|
{
|
|
// Blender names takes "<armature>|<action>"; Unity keeps the action name.
|
|
string name = clip.takeName.Contains("|") ? clip.takeName.Substring(clip.takeName.LastIndexOf('|') + 1) : clip.takeName;
|
|
clip.name = name;
|
|
if (blender.TryGetValue(name, out var facts))
|
|
{
|
|
clip.loopTime = (bool)facts["loop"];
|
|
double contact = (double)facts["contact_seconds"];
|
|
double seconds = (double)facts["seconds"];
|
|
if (contact > 0.0 && seconds > 0.0)
|
|
clip.events = new[] { new AnimationEvent { functionName = ContactEvent, time = (float)(contact / seconds) } };
|
|
}
|
|
clips.Add(clip);
|
|
}
|
|
importer.clipAnimations = clips.ToArray();
|
|
}
|
|
}
|
|
}
|