feat: 드로그 기본기2 ActorCore 모션 적용

- ActorCore 펀치/어퍼컷/촙 후보 FBX와 휴머노이드 클립 추출 에디터 창을 추가

- 드로그 강타, 기본기2, 기본기3 스킬 클립과 기본기2 패턴 3타 스텝을 새 모션 흐름에 맞게 갱신

- 미사용 도약/발구르기 파생 클립을 정리하고 아직 참조 중인 클립은 보존

- Unity 세션 부재로 에디터 컴파일과 콘솔 검증은 이번 커밋에서 미수행
This commit is contained in:
2026-05-11 17:57:54 +09:00
parent b4648672f6
commit 98b34af941
56 changed files with 213607 additions and 786970 deletions

View File

@@ -38,7 +38,7 @@ ModelImporter:
takeName: Armature|orc-chop takeName: Armature|orc-chop
internalID: -8751659441991159448 internalID: -8751659441991159448
firstFrame: 0 firstFrame: 0
lastFrame: 93.00001 lastFrame: 93
wrapMode: 0 wrapMode: 0
orientationOffsetY: 0 orientationOffsetY: 0
level: 0 level: 0

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 6a025fd35595e802b82bd468471d91e2 guid: 4ede17592b1c775238b5f84accfc854f
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

Binary file not shown.

View File

@@ -0,0 +1,110 @@
fileFormatVersion: 2
guid: d52352fed4bd85a37a04f51132ed34bb
ModelImporter:
serializedVersion: 24200
internalIDToNameTable: []
externalObjects: {}
materials:
materialImportMode: 2
materialName: 0
materialSearch: 1
materialLocation: 1
animations:
legacyGenerateAnimations: 4
bakeSimulation: 0
resampleCurves: 1
optimizeGameObjects: 0
removeConstantScaleCurves: 0
motionNodeName:
animationImportErrors:
animationImportWarnings:
animationRetargetingWarnings:
animationDoRetargetingWarnings: 0
importAnimatedCustomProperties: 0
importConstraints: 0
animationCompression: 1
animationRotationError: 0.5
animationPositionError: 0.5
animationScaleError: 0.5
animationWrapMode: 0
extraExposedTransformPaths: []
extraUserProperties: []
clipAnimations: []
isReadable: 0
meshes:
lODScreenPercentages: []
globalScale: 1
meshCompression: 0
addColliders: 0
useSRGBMaterialColor: 1
sortHierarchyByName: 1
importPhysicalCameras: 1
importVisibility: 1
importBlendShapes: 1
importCameras: 1
importLights: 1
nodeNameCollisionStrategy: 1
fileIdsGeneration: 2
swapUVChannels: 0
generateSecondaryUV: 0
useFileUnits: 1
keepQuads: 0
weldVertices: 1
bakeAxisConversion: 0
preserveHierarchy: 0
skinWeightsMode: 0
maxBonesPerVertex: 4
minBoneWeight: 0.001
optimizeBones: 1
generateMeshLods: 0
meshLodGenerationFlags: 0
maximumMeshLod: -1
meshOptimizationFlags: -1
indexFormat: 0
secondaryUVAngleDistortion: 8
secondaryUVAreaDistortion: 15.000001
secondaryUVHardAngle: 88
secondaryUVMarginMethod: 1
secondaryUVMinLightmapResolution: 40
secondaryUVMinObjectScale: 1
secondaryUVPackMargin: 4
useFileScale: 1
strictVertexDataChecks: 0
tangentSpace:
normalSmoothAngle: 60
normalImportMode: 0
tangentImportMode: 3
normalCalculationMode: 4
legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
blendShapeNormalImportMode: 1
normalSmoothingSource: 0
referencedClips: []
importAnimation: 1
humanDescription:
serializedVersion: 3
human: []
skeleton: []
armTwist: 0.5
foreArmTwist: 0.5
upperLegTwist: 0.5
legTwist: 0.5
armStretch: 0.05
legStretch: 0.05
feetSpacing: 0
globalScale: 1
rootMotionBoneName:
hasTranslationDoF: 0
hasExtraRoot: 0
skeletonHasParents: 1
lastHumanDescriptionAvatarSource: {instanceID: 0}
autoGenerateAvatarMappingIfUnspecified: 1
animationType: 2
humanoidOversampling: 1
avatarSetup: 0
addHumanoidExtraRootOnlyWhenUsingAvatar: 1
importBlendShapeDeformPercent: 1
remapMaterialsIfMaterialImportModeIsNone: 0
additionalBone: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 78e06db48a4e2714ca78cafca0b4800b guid: 936b212f6c1cb68aaab2cbd3180f4d7a
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: c901be6f00fe40f85ac3cefc15305b22 guid: b73b9afb945eae6ccb1b7f674a0bf7eb
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

View File

@@ -1,53 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: "Anim_Drog_\uB3C4\uC57D_\uACF5\uC911_0"
serializedVersion: 7
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings: []
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -1,53 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: "Anim_Drog_\uB3C4\uC57D_\uC900\uBE44_0"
serializedVersion: 7
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings: []
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: c40824d39d8c11bedbeb448867e881c3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,53 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: "Anim_Drog_\uB3C4\uC57D_\uCC29\uC9C0_0"
serializedVersion: 7
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings: []
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 6b11f2b53d826bba3ab9f8086a42de14 guid: 0f08a330b8e1c0cbaba0f0332610a8a2
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 156c90879bba0d7649e8b29224daa390 guid: 53e4bf96ef5854e62ac4d374a8ac8c33
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 6439cde8bc726bd1caad7d6e18a31416 guid: f26d3360f86be8d4b9995f7701ed24f2
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 606aec780e456217687074cbbf23a2c8 guid: 6a1f5dcbbfd5ba4fc93114485bf62e8a
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 1f04142b14e6fd1e9a98b8ac5f2297cc guid: 3cb1d67023e25752aa6b5fb0318dfd03
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 79b23145d7dec4db98ff025556552e72 guid: 708630f3401af00baa9a8fa3e95ef1a0
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 10d643a69e2dd30dd878185745485fbb guid: 8c6ac0672bd9c5012bb68c79b24d4b42
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 7400000

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: b73a55c82a8bff692aa4a9e60395c247
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 3904a390dfdc98cd6bb386fc6c024eea
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -476,7 +476,7 @@ AnimatorState:
m_MirrorParameterActive: 0 m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0 m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0 m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 584d21bf1ba1a2dad845629563d8c26c, type: 2} m_Motion: {fileID: 7400000, guid: ebc8e8e59d375104babccd596aa3bdc0, type: 2}
m_Tag: m_Tag:
m_SpeedParameter: m_SpeedParameter:
m_MirrorParameter: m_MirrorParameter:

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,13 @@ MonoBehaviour:
requiredDamageRatio: 0.1 requiredDamageRatio: 0.1
telegraphAbnormality: {fileID: 0} telegraphAbnormality: {fileID: 0}
staggerDuration: 2 staggerDuration: 2
- Type: 0
Skill: {fileID: 11400000, guid: 067dec0123602b604a6e3ff2540d471f, type: 2}
Duration: 0
ChargeData:
requiredDamageRatio: 0.1
telegraphAbnormality: {fileID: 0}
staggerDuration: 2
cooldown: 3 cooldown: 3
minPhase: 1 minPhase: 1
skipJumpStepOnNoTarget: 0 skipJumpStepOnNoTarget: 0

View File

@@ -20,7 +20,7 @@ MonoBehaviour:
activationType: 1 activationType: 1
baseTypes: 1 baseTypes: 1
animationClips: animationClips:
- {fileID: 7400000, guid: c901be6f00fe40f85ac3cefc15305b22, type: 2} - {fileID: 7400000, guid: b73b9afb945eae6ccb1b7f674a0bf7eb, type: 2}
animationSpeed: 1 animationSpeed: 1
useRootMotion: 1 useRootMotion: 1
ignoreRootMotionY: 1 ignoreRootMotionY: 1

View File

@@ -19,9 +19,9 @@ MonoBehaviour:
activationType: 1 activationType: 1
baseTypes: 1 baseTypes: 1
animationClips: animationClips:
- {fileID: 7400000, guid: 6b11f2b53d826bba3ab9f8086a42de14, type: 2} - {fileID: 7400000, guid: 0f08a330b8e1c0cbaba0f0332610a8a2, type: 2}
- {fileID: 7400000, guid: 156c90879bba0d7649e8b29224daa390, type: 2} - {fileID: 7400000, guid: 53e4bf96ef5854e62ac4d374a8ac8c33, type: 2}
- {fileID: 7400000, guid: 6439cde8bc726bd1caad7d6e18a31416, type: 2} - {fileID: 7400000, guid: f26d3360f86be8d4b9995f7701ed24f2, type: 2}
animationSpeed: 1 animationSpeed: 1
useRootMotion: 1 useRootMotion: 1
ignoreRootMotionY: 1 ignoreRootMotionY: 1

View File

@@ -19,8 +19,8 @@ MonoBehaviour:
activationType: 1 activationType: 1
baseTypes: 1 baseTypes: 1
animationClips: animationClips:
- {fileID: 7400000, guid: 606aec780e456217687074cbbf23a2c8, type: 2} - {fileID: 7400000, guid: 6a1f5dcbbfd5ba4fc93114485bf62e8a, type: 2}
- {fileID: 7400000, guid: 1f04142b14e6fd1e9a98b8ac5f2297cc, type: 2} - {fileID: 7400000, guid: 3cb1d67023e25752aa6b5fb0318dfd03, type: 2}
animationSpeed: 1 animationSpeed: 1
useRootMotion: 1 useRootMotion: 1
ignoreRootMotionY: 1 ignoreRootMotionY: 1

View File

@@ -0,0 +1,65 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 94f0a76cebcac2f4fb5daf1b675fd79f, type: 3}
m_Name: "Data_Skill_Drog_\uCF64\uBCF4-\uAE30\uBCF8\uAE302_3"
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Skills.SkillData
skillName: "\uCF64\uBCF4-\uAE30\uBCF8\uAE302 2\uD0C0"
description: "\uAE30\uBCF8\uAE30 \uCF64\uBCF42\uC758 \uB9C8\uBB34\uB9AC \uD0C0\uACA9\uC785\uB2C8\uB2E4."
icon: {fileID: 0}
skillRole: 1
activationType: 1
baseTypes: 1
animationClips:
- {fileID: 7400000, guid: 6a1f5dcbbfd5ba4fc93114485bf62e8a, type: 2}
animationSpeed: 1
useRootMotion: 1
ignoreRootMotionY: 1
jumpToTarget: 0
blockMovementWhileCasting: 1
blockJumpWhileCasting: 1
blockOtherSkillsWhileCasting: 1
castTargetTrackingMode: 1
castTargetRotationSpeed: 12
castTargetStopDistance: 2.5
allowedWeaponTraits: 0
cooldown: 0
manaCost: 0
maxGemSlotCount: 0
castStartEffects: []
triggeredEffects:
- triggerIndex: 0
effects:
- {fileID: 11400000, guid: fdbbc8f5dc30568cf934a62595987d5c, type: 2}
isChanneling: 0
loopPhase:
enabled: 0
loopMode: 1
maxDuration: 3
tickInterval: 0.5
tickEffects: []
exitEffects: []
loopVfxPrefab: {fileID: 0}
loopVfxMountPath:
loopVfxLengthScale: 1
loopVfxWidthScale: 1
releasePhase:
enabled: 0
animationClips: []
startEffects: []
channelDuration: 3
channelTickInterval: 0.5
channelTickEffects: []
channelEndEffects: []
channelVfxPrefab: {fileID: 0}
channelVfxMountPath:
channelVfxLengthScale: 1
channelVfxWidthScale: 1

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 9d61fd0c58927165096b86a443f50346 guid: 067dec0123602b604a6e3ff2540d471f
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 7400000 mainObjectFileID: 11400000
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

@@ -20,7 +20,7 @@ MonoBehaviour:
baseTypes: 1 baseTypes: 1
animationClips: animationClips:
- {fileID: 7400000, guid: 3f077b889a0c441a8be8f51575b34f37, type: 2} - {fileID: 7400000, guid: 3f077b889a0c441a8be8f51575b34f37, type: 2}
- {fileID: 7400000, guid: 10d643a69e2dd30dd878185745485fbb, type: 2} - {fileID: 7400000, guid: 8c6ac0672bd9c5012bb68c79b24d4b42, type: 2}
animationSpeed: 1 animationSpeed: 1
useRootMotion: 1 useRootMotion: 1
ignoreRootMotionY: 1 ignoreRootMotionY: 1

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,382 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEditor;
/// <summary>
/// ActorCore 기준 FBX를 참고해 소스 FBX에서 휴머노이드 클립을 추출하는 에디터 윈도우입니다.
/// 소스 FBX는 우선 Create From This Model로 재임포트한 뒤, 유효한 휴머노이드 클립만 .anim으로 저장합니다.
/// </summary>
public sealed class ActorCoreHumanoidClipExtractorWindow : EditorWindow
{
private const string MenuPath = "Tools/Animation/Extract ActorCore Humanoid Clips";
private const string DefaultReferencePath = "Assets/External/Animations/ActorCore/uppercut-l.fbx";
private const string DefaultOutputFolder = "Assets/External/Animations/ActorCore/Extracted";
private UnityEngine.Object referenceFbx;
private UnityEngine.Object sourceFbx;
private DefaultAsset outputFolder;
private bool overwriteExisting = true;
private Vector2 scrollPosition;
private struct ImporterState
{
public bool importAnimation;
public ModelImporterAnimationType animationType;
public ModelImporterAvatarSetup avatarSetup;
public Avatar sourceAvatar;
public ModelImporterClipAnimation[] clipAnimations;
}
[MenuItem(MenuPath, false, 25)]
public static void ShowWindow()
{
var window = GetWindow<ActorCoreHumanoidClipExtractorWindow>("ActorCore Clip Extractor");
window.minSize = new Vector2(520f, 360f);
window.InitializeDefaults();
window.Show();
}
private void OnEnable()
{
InitializeDefaults();
}
private void InitializeDefaults()
{
if (referenceFbx == null)
referenceFbx = AssetDatabase.LoadMainAssetAtPath(DefaultReferencePath);
if (outputFolder == null)
outputFolder = AssetDatabase.LoadAssetAtPath<DefaultAsset>(DefaultOutputFolder);
}
private void OnGUI()
{
EditorGUILayout.Space(8f);
EditorGUILayout.LabelField("ActorCore 휴머노이드 클립 추출", EditorStyles.boldLabel);
EditorGUILayout.Space(4f);
EditorGUILayout.HelpBox(
"기준 FBX의 휴머노이드 구조를 검증용으로 참고하고, 소스 FBX는 Create From This Model로 재임포트한 뒤 "
+ "유효한 휴머노이드 클립만 개별 .anim으로 추출합니다.",
MessageType.Info);
using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPosition))
{
scrollPosition = scrollView.scrollPosition;
referenceFbx = EditorGUILayout.ObjectField(
"기준 FBX",
referenceFbx,
typeof(UnityEngine.Object),
false);
sourceFbx = EditorGUILayout.ObjectField(
"소스 FBX",
sourceFbx,
typeof(UnityEngine.Object),
false);
outputFolder = (DefaultAsset)EditorGUILayout.ObjectField(
"출력 폴더",
outputFolder,
typeof(DefaultAsset),
false);
overwriteExisting = EditorGUILayout.ToggleLeft("기존 .anim 덮어쓰기", overwriteExisting);
EditorGUILayout.Space(12f);
DrawSelectionSummary();
}
EditorGUILayout.Space(8f);
using (new EditorGUI.DisabledScope(!CanExtract()))
{
if (GUILayout.Button("추출", GUILayout.Height(34f)))
Extract();
}
}
private void DrawSelectionSummary()
{
EditorGUILayout.LabelField("선택 요약", EditorStyles.boldLabel);
DrawAssetPathLabel("기준 FBX 경로", referenceFbx);
DrawAssetPathLabel("소스 FBX 경로", sourceFbx);
DrawAssetPathLabel("출력 폴더 경로", outputFolder);
if (sourceFbx == null)
return;
string sourcePath = AssetDatabase.GetAssetPath(sourceFbx);
if (!IsFbxPath(sourcePath))
return;
AnimationClip[] clips = AssetDatabase.LoadAllAssetsAtPath(sourcePath)
.OfType<AnimationClip>()
.ToArray();
string[] clipNames = clips.Select(clip => clip.name).ToArray();
string clipSummary = clipNames.Length > 0
? string.Join(", ", clipNames)
: "(없음)";
EditorGUILayout.LabelField("소스 내장 클립", clipSummary, EditorStyles.wordWrappedLabel);
}
private static void DrawAssetPathLabel(string label, UnityEngine.Object asset)
{
string assetPath = asset != null ? AssetDatabase.GetAssetPath(asset) : "(미선택)";
EditorGUILayout.LabelField(label, assetPath, EditorStyles.wordWrappedLabel);
}
private bool CanExtract()
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
return false;
return IsValidFbx(referenceFbx) && IsValidFbx(sourceFbx) && IsValidFolder(outputFolder);
}
private static bool IsValidFbx(UnityEngine.Object asset)
{
return asset != null && IsFbxPath(AssetDatabase.GetAssetPath(asset));
}
private static bool IsFbxPath(string assetPath)
{
return !string.IsNullOrEmpty(assetPath)
&& assetPath.EndsWith(".fbx", StringComparison.OrdinalIgnoreCase);
}
private static bool IsValidFolder(DefaultAsset asset)
{
if (asset == null)
return false;
string path = AssetDatabase.GetAssetPath(asset);
return !string.IsNullOrEmpty(path) && AssetDatabase.IsValidFolder(path);
}
private void Extract()
{
string referencePath = AssetDatabase.GetAssetPath(referenceFbx);
string sourcePath = AssetDatabase.GetAssetPath(sourceFbx);
string outputPath = AssetDatabase.GetAssetPath(outputFolder);
var referenceImporter = AssetImporter.GetAtPath(referencePath) as ModelImporter;
var sourceImporter = AssetImporter.GetAtPath(sourcePath) as ModelImporter;
if (referenceImporter == null || sourceImporter == null)
{
EditorUtility.DisplayDialog(
"ActorCore Clip Extractor",
"FBX ModelImporter를 찾지 못했습니다.",
"확인");
return;
}
int referenceSkeletonCount = referenceImporter.humanDescription.skeleton?.Length ?? 0;
TakeInfo[] originalTakeInfos = sourceImporter.importedTakeInfos ?? Array.Empty<TakeInfo>();
ImporterState originalState = CaptureImporterState(sourceImporter);
if (!PrepareSourceImporter(sourceImporter))
{
EditorUtility.DisplayDialog(
"ActorCore Clip Extractor",
"소스 FBX를 휴머노이드 기준으로 준비하지 못했습니다. 콘솔을 확인해 주세요.",
"확인");
return;
}
try
{
AnimationClip[] clips = AssetDatabase.LoadAllAssetsAtPath(sourcePath)
.OfType<AnimationClip>()
.Where(IsUsableHumanoidClip)
.ToArray();
if (clips.Length == 0)
{
TakeInfo[] takeInfos = sourceImporter.importedTakeInfos ?? Array.Empty<TakeInfo>();
string takeSummary = takeInfos.Length > 0
? string.Join(", ", takeInfos.Select(take => take.name))
: "(없음)";
int preparedSkeletonCount = sourceImporter.humanDescription.skeleton?.Length ?? 0;
bool humanoidImportFailed = originalTakeInfos.Length > 0
&& takeInfos.Length == 0
&& preparedSkeletonCount == 0;
string failureReason = humanoidImportFailed
? "원본 FBX에는 테이크가 있었지만, 휴머노이드로 임시 재임포트하는 단계에서 Unity가 유효한 Avatar 구조를 만들지 못했습니다.\n"
+ "이 경우는 보통 본 매핑 또는 루트 계층이 휴머노이드 조건을 만족하지 않아 `Invalid Avatar Rig Configuration`이 발생한 상황입니다."
: "이 경우는 보통 소스 FBX가 Unity에서 애니메이션 테이크를 읽지 못하는 상태입니다.\n"
+ "원본 툴에서 애니메이션 포함 상태로 다시 export하거나, Blender에서 NLA/All Actions 기반으로 다시 export해야 합니다.";
EditorUtility.DisplayDialog(
"ActorCore Clip Extractor",
"추출 가능한 휴머노이드 클립이 없습니다.\n\n"
+ $"원본 테이크 수: {originalTakeInfos.Length}\n"
+ $"Unity가 감지한 테이크 수: {takeInfos.Length}\n"
+ $"휴머노이드 스켈레톤 수: {preparedSkeletonCount}\n"
+ $"테이크 목록: {takeSummary}\n\n"
+ failureReason,
"확인");
return;
}
List<string> createdPaths = new List<string>();
AssetDatabase.StartAssetEditing();
try
{
foreach (AnimationClip clip in clips)
{
string fileName = SanitizeClipName(clip.name);
string targetPath = $"{outputPath}/{fileName}.anim";
if (!overwriteExisting)
targetPath = AssetDatabase.GenerateUniqueAssetPath(targetPath);
AnimationClip existingClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(targetPath);
AnimationClip copiedClip = UnityEngine.Object.Instantiate(clip);
copiedClip.name = fileName;
if (existingClip != null && overwriteExisting)
{
EditorUtility.CopySerialized(copiedClip, existingClip);
EditorUtility.SetDirty(existingClip);
}
else
{
AssetDatabase.CreateAsset(copiedClip, targetPath);
}
createdPaths.Add(targetPath);
}
}
finally
{
AssetDatabase.StopAssetEditing();
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
int sourceSkeletonCount = sourceImporter.humanDescription.skeleton?.Length ?? 0;
string message = $"기준 스켈레톤 수: {referenceSkeletonCount}\n"
+ $"소스 스켈레톤 수: {sourceSkeletonCount}\n"
+ $"추출 클립 수: {createdPaths.Count}\n\n"
+ string.Join("\n", createdPaths);
Debug.Log($"[ActorCoreHumanoidClipExtractor] {message}");
EditorUtility.DisplayDialog("ActorCore Clip Extractor", message, "확인");
}
finally
{
RestoreImporterState(sourceImporter, originalState);
}
}
private static bool PrepareSourceImporter(ModelImporter importer)
{
if (importer == null)
return false;
importer.importAnimation = true;
importer.animationType = ModelImporterAnimationType.Human;
importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel;
importer.sourceAvatar = null;
importer.SaveAndReimport();
return true;
}
private static ImporterState CaptureImporterState(ModelImporter importer)
{
return new ImporterState
{
importAnimation = importer.importAnimation,
animationType = importer.animationType,
avatarSetup = importer.avatarSetup,
sourceAvatar = importer.sourceAvatar,
clipAnimations = CloneClipAnimations(importer.clipAnimations),
};
}
private static void RestoreImporterState(ModelImporter importer, ImporterState state)
{
if (importer == null)
return;
importer.importAnimation = state.importAnimation;
importer.animationType = state.animationType;
importer.avatarSetup = state.avatarSetup;
importer.sourceAvatar = state.sourceAvatar;
importer.clipAnimations = state.clipAnimations ?? Array.Empty<ModelImporterClipAnimation>();
importer.SaveAndReimport();
}
private static ModelImporterClipAnimation[] CloneClipAnimations(ModelImporterClipAnimation[] clips)
{
if (clips == null || clips.Length == 0)
return Array.Empty<ModelImporterClipAnimation>();
return clips.Select(clip => new ModelImporterClipAnimation
{
name = clip.name,
takeName = clip.takeName,
firstFrame = clip.firstFrame,
lastFrame = clip.lastFrame,
loopTime = clip.loopTime,
loopPose = clip.loopPose,
cycleOffset = clip.cycleOffset,
lockRootRotation = clip.lockRootRotation,
lockRootHeightY = clip.lockRootHeightY,
lockRootPositionXZ = clip.lockRootPositionXZ,
keepOriginalOrientation = clip.keepOriginalOrientation,
keepOriginalPositionY = clip.keepOriginalPositionY,
keepOriginalPositionXZ = clip.keepOriginalPositionXZ,
heightFromFeet = clip.heightFromFeet,
mirror = clip.mirror,
maskType = clip.maskType,
additiveReferencePoseFrame = clip.additiveReferencePoseFrame,
hasAdditiveReferencePose = clip.hasAdditiveReferencePose,
wrapMode = clip.wrapMode,
}).ToArray();
}
private static bool IsUsableHumanoidClip(AnimationClip clip)
{
if (clip == null)
return false;
if (clip.name.StartsWith("__preview__", StringComparison.Ordinal))
return false;
if (!clip.humanMotion)
return false;
if (clip.empty)
return false;
return true;
}
private static string SanitizeClipName(string clipName)
{
string sanitized = clipName;
int separatorIndex = sanitized.LastIndexOf('|');
if (separatorIndex >= 0 && separatorIndex + 1 < sanitized.Length)
sanitized = sanitized.Substring(separatorIndex + 1);
foreach (char invalid in Path.GetInvalidFileNameChars())
sanitized = sanitized.Replace(invalid, '_');
return sanitized.Trim();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2d1baf05ee0d5d7cf838a1599adbb71d