fix: tune camera and humanoid fbx imports
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public static class HumanoidAnimationFbxImportSettings
|
||||
{
|
||||
public const string ActorRootPath = "Assets/Game/Art/Actor/";
|
||||
public const string CityboyAvatarPath = "Assets/Game/Art/Actor/cityboy/cityboy_sk.fbx";
|
||||
public const string CityboyAvatarName = "Cityboy_sk Avatar";
|
||||
public const string CityboyHipBoneName = "mixamorig:Hips";
|
||||
|
||||
public static bool ShouldProcessAsset(string assetPath)
|
||||
{
|
||||
string normalizedPath = NormalizeAssetPath(assetPath);
|
||||
|
||||
return normalizedPath.EndsWith(".fbx", StringComparison.OrdinalIgnoreCase)
|
||||
&& normalizedPath.StartsWith(ActorRootPath, StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(normalizedPath, CityboyAvatarPath, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static ModelImporterClipAnimation ApplyClipSettings(ModelImporterClipAnimation clip)
|
||||
{
|
||||
clip.lockRootRotation = true;
|
||||
clip.keepOriginalOrientation = true;
|
||||
clip.lockRootHeightY = true;
|
||||
return clip;
|
||||
}
|
||||
|
||||
public static ModelImporterClipAnimation[] ApplyClipSettings(ModelImporterClipAnimation[] clips)
|
||||
{
|
||||
for (int i = 0; i < clips.Length; i++)
|
||||
{
|
||||
clips[i] = ApplyClipSettings(clips[i]);
|
||||
}
|
||||
|
||||
return clips;
|
||||
}
|
||||
|
||||
public static bool ShouldUseCityboyAvatar(string assetPath)
|
||||
{
|
||||
string filePath = ResolveAssetFilePath(assetPath);
|
||||
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ShouldUseCityboyAvatar(File.ReadAllBytes(filePath));
|
||||
}
|
||||
|
||||
public static bool ShouldUseCityboyAvatar(byte[] assetBytes)
|
||||
{
|
||||
if (assetBytes == null || assetBytes.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] marker = System.Text.Encoding.ASCII.GetBytes(CityboyHipBoneName);
|
||||
for (int i = 0; i <= assetBytes.Length - marker.Length; i++)
|
||||
{
|
||||
bool matched = true;
|
||||
for (int markerIndex = 0; markerIndex < marker.Length; markerIndex++)
|
||||
{
|
||||
if (assetBytes[i + markerIndex] != marker[markerIndex])
|
||||
{
|
||||
matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ModelImporterClipAnimation CreateClipAnimation(TakeInfo takeInfo)
|
||||
{
|
||||
string clipName = string.IsNullOrEmpty(takeInfo.defaultClipName) ? takeInfo.name : takeInfo.defaultClipName;
|
||||
|
||||
return ApplyClipSettings(new ModelImporterClipAnimation
|
||||
{
|
||||
name = clipName,
|
||||
takeName = takeInfo.name,
|
||||
firstFrame = takeInfo.startTime * takeInfo.sampleRate,
|
||||
lastFrame = takeInfo.stopTime * takeInfo.sampleRate
|
||||
});
|
||||
}
|
||||
|
||||
public static ModelImporterClipAnimation CreateClipAnimation(AnimationClip animationClip)
|
||||
{
|
||||
float frameRate = animationClip.frameRate > 0f ? animationClip.frameRate : 30f;
|
||||
|
||||
return ApplyClipSettings(new ModelImporterClipAnimation
|
||||
{
|
||||
name = animationClip.name,
|
||||
takeName = animationClip.name,
|
||||
firstFrame = 0f,
|
||||
lastFrame = animationClip.length * frameRate
|
||||
});
|
||||
}
|
||||
|
||||
private static string NormalizeAssetPath(string assetPath)
|
||||
{
|
||||
return string.IsNullOrEmpty(assetPath) ? string.Empty : assetPath.Replace('\\', '/');
|
||||
}
|
||||
|
||||
private static string ResolveAssetFilePath(string assetPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (Path.IsPathRooted(assetPath) || File.Exists(assetPath))
|
||||
{
|
||||
return assetPath;
|
||||
}
|
||||
|
||||
DirectoryInfo projectRoot = Directory.GetParent(Application.dataPath);
|
||||
return projectRoot == null ? assetPath : Path.Combine(projectRoot.FullName, NormalizeAssetPath(assetPath));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HumanoidAnimationFbxPostprocessor : AssetPostprocessor
|
||||
{
|
||||
private static readonly HashSet<string> ReimportingAssets = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private void OnPreprocessModel()
|
||||
{
|
||||
if (!(assetImporter is ModelImporter modelImporter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HumanoidAnimationFbxImportSettings.ShouldProcessAsset(assetPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modelImporter.importAnimation = true;
|
||||
modelImporter.animationType = ModelImporterAnimationType.Human;
|
||||
if (HumanoidAnimationFbxImportSettings.ShouldUseCityboyAvatar(assetPath))
|
||||
{
|
||||
Avatar cityboyAvatar = LoadCityboyAvatar();
|
||||
if (cityboyAvatar == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[HumanoidAnimationFbxPostprocessor] Cannot find Cityboy avatar at {HumanoidAnimationFbxImportSettings.CityboyAvatarPath}. Skip import settings for {assetPath}.");
|
||||
return;
|
||||
}
|
||||
|
||||
modelImporter.avatarSetup = ModelImporterAvatarSetup.CopyFromOther;
|
||||
modelImporter.sourceAvatar = cityboyAvatar;
|
||||
}
|
||||
else
|
||||
{
|
||||
modelImporter.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
|
||||
modelImporter.sourceAvatar = null;
|
||||
}
|
||||
|
||||
ModelImporterClipAnimation[] clips = GetAnimationClips(modelImporter);
|
||||
if (clips.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modelImporter.clipAnimations = HumanoidAnimationFbxImportSettings.ApplyClipSettings(clips);
|
||||
}
|
||||
|
||||
private static void OnPostprocessAllAssets(
|
||||
string[] importedAssets,
|
||||
string[] deletedAssets,
|
||||
string[] movedAssets,
|
||||
string[] movedFromAssetPaths)
|
||||
{
|
||||
foreach (string importedAsset in importedAssets)
|
||||
{
|
||||
ConfigureImportedAnimationClips(importedAsset);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureImportedAnimationClips(string assetPath)
|
||||
{
|
||||
if (!HumanoidAnimationFbxImportSettings.ShouldProcessAsset(assetPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReimportingAssets.Remove(assetPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(AssetImporter.GetAtPath(assetPath) is ModelImporter modelImporter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModelImporterClipAnimation[] existingClips = modelImporter.clipAnimations;
|
||||
if (existingClips != null && existingClips.Length > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModelImporterClipAnimation[] clips = CreateClipAnimationsFromImportedAssets(assetPath);
|
||||
if (clips.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modelImporter.clipAnimations = HumanoidAnimationFbxImportSettings.ApplyClipSettings(clips);
|
||||
ReimportingAssets.Add(assetPath);
|
||||
modelImporter.SaveAndReimport();
|
||||
}
|
||||
|
||||
private static ModelImporterClipAnimation[] GetAnimationClips(ModelImporter modelImporter)
|
||||
{
|
||||
ModelImporterClipAnimation[] clips = modelImporter.clipAnimations;
|
||||
if (clips != null && clips.Length > 0)
|
||||
{
|
||||
return clips;
|
||||
}
|
||||
|
||||
clips = modelImporter.defaultClipAnimations;
|
||||
if (clips != null && clips.Length > 0)
|
||||
{
|
||||
return clips;
|
||||
}
|
||||
|
||||
TakeInfo[] takes = modelImporter.importedTakeInfos;
|
||||
if (takes == null || takes.Length == 0)
|
||||
{
|
||||
return Array.Empty<ModelImporterClipAnimation>();
|
||||
}
|
||||
|
||||
clips = new ModelImporterClipAnimation[takes.Length];
|
||||
for (int i = 0; i < takes.Length; i++)
|
||||
{
|
||||
clips[i] = HumanoidAnimationFbxImportSettings.CreateClipAnimation(takes[i]);
|
||||
}
|
||||
|
||||
return clips;
|
||||
}
|
||||
|
||||
private static Avatar LoadCityboyAvatar()
|
||||
{
|
||||
Avatar fallbackAvatar = null;
|
||||
UnityEngine.Object[] assets = AssetDatabase.LoadAllAssetsAtPath(HumanoidAnimationFbxImportSettings.CityboyAvatarPath);
|
||||
|
||||
foreach (UnityEngine.Object asset in assets)
|
||||
{
|
||||
if (!(asset is Avatar avatar))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fallbackAvatar == null)
|
||||
{
|
||||
fallbackAvatar = avatar;
|
||||
}
|
||||
|
||||
if (string.Equals(avatar.name, HumanoidAnimationFbxImportSettings.CityboyAvatarName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return avatar;
|
||||
}
|
||||
}
|
||||
|
||||
return fallbackAvatar;
|
||||
}
|
||||
|
||||
private static ModelImporterClipAnimation[] CreateClipAnimationsFromImportedAssets(string assetPath)
|
||||
{
|
||||
UnityEngine.Object[] assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath);
|
||||
var clips = new List<ModelImporterClipAnimation>();
|
||||
|
||||
foreach (UnityEngine.Object asset in assets)
|
||||
{
|
||||
if (asset is AnimationClip animationClip)
|
||||
{
|
||||
clips.Add(HumanoidAnimationFbxImportSettings.CreateClipAnimation(animationClip));
|
||||
}
|
||||
}
|
||||
|
||||
return clips.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eca73c54c7164f2cb8873812efa969fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user