Files
AIC-Project/Client/Assets/Script/Editor/HumanoidAnimationFbxImportSettings.cs
T

292 lines
8.9 KiB
C#

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();
}
}