/// <summary> /// <see cref="VRC_AvatarDescriptor.CustomStandingAnims"/>、および<see cref="VRC_AvatarDescriptor.CustomSittingAnims"/>を作成します。 /// </summary> /// <param name="avatar"></param> /// <exception cref="FileNotFoundException">VRChat SDKに含まれるカスタムアニメーション設定用のテンプレートファイルが見つからなかった場合。</exception> /// <returns></returns> internal static void AddCustomAnims(GameObject avatar) { #if VRC_SDK_VRCSDK2 var avatarDescriptor = avatar.GetOrAddComponent <VRC_AvatarDescriptor>(); var templatePath = AssetDatabase.GUIDToAssetPath(VRChatUtility.CustomAnimsTemplateGUID); if (string.IsNullOrEmpty(templatePath)) { templatePath = VRChatUtility.CustomAnimsTemplatePath; } var template = AssetDatabase.LoadAssetAtPath <AnimatorOverrideController>(templatePath); if (!template) { new FileNotFoundException("VRChat SDKに含まれるカスタムアニメーション設定用のテンプレートファイルが見つかりません。", fileName: templatePath); } if (!avatarDescriptor.CustomStandingAnims) { avatarDescriptor.CustomStandingAnims = Duplicator.DuplicateAssetToFolder <AnimatorOverrideController>( source: template, prefabInstance: avatar, fileName: "CustomStandingAnims.overrideController" ); } if (!avatarDescriptor.CustomSittingAnims) { avatarDescriptor.CustomSittingAnims = Duplicator.DuplicateAssetToFolder <AnimatorOverrideController>( source: template, prefabInstance: avatar, fileName: "CustomSittingAnims.overrideController" ); } #endif }
/// <summary> /// 表情の設定を行うアニメーションクリップを作成します。 /// </summary> /// <param name="avatar"></param> /// <param name="vrmBlendShape"></param> /// <param name="clips"></param> /// <returns></returns> private static AnimationClip CreateFeeling( GameObject avatar, VRMBlendShapeClip clip, ref IEnumerable <VRMBlendShapeClip> clips ) { var fileName = clip.Preset + ".anim"; #if VRC_SDK_VRCSDK2 var anim = Duplicator.DuplicateAssetToFolder <AnimationClip>( source: UnityPath.FromUnityPath(Converter.RootFolderPath).Child("animations") .Child(BlendShapeReplacer.MappingBlendShapeToVRChatAnim[clip.Preset] + ".anim") .LoadAsset <AnimationClip>(), prefabInstance: avatar, fileName ); Transform transform = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath); if (transform.GetComponent <Animator>()) { var curve = new AnimationCurve(); foreach (var(seconds, value) in BlendShapeReplacer.NeutralAndBlinkStopperWeights) { curve.AddKey(new Keyframe(seconds, value)); } anim.SetCurve( VRChatUtility.AutoBlinkMeshPath, typeof(Behaviour), "m_Enabled", curve ); clips = BlendShapeReplacer.DuplicateShapeKeyToUnique(avatar, clip, clips); } #else var anim = new AnimationClip(); AssetDatabase.CreateAsset( anim, Duplicator.DetermineAssetPath(prefabInstance: avatar, typeof(AnimationClip), fileName) ); clips = BlendShapeReplacer.DuplicateShapeKeyToUnique(avatar, clip, clips); #endif SetBlendShapeCurves( avatar, animationClip: anim, clip: clip, keys: new Dictionary <float, float> { { 0, 1 }, { anim.length, 1 }, } ); return(anim); }
/// <summary> /// アニメーションクリップに、指定されたマテリアルを追加します。 /// </summary> /// <param name="avatar"></param> /// <param name="animationClip"></param> /// <param name="name"></param> /// <param name="binding"></param> /// <param name="secondsList"></param> private static void SetBlendShapeCurve( AnimationClip animationClip, GameObject avatar, string vrmBlendShapeName, IEnumerable <MaterialValueBinding> bindings, IEnumerable <float> secondsList ) { var materials = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath) .GetComponent <SkinnedMeshRenderer>().sharedMaterials; var materialIndex = materials.ToList().FindIndex(m => m.name == bindings.First().MaterialName); var material = Duplicator.DuplicateAssetToFolder <Material>( source: materials[materialIndex], prefabInstance: avatar, fileName: $"{materials[materialIndex].name}-{vrmBlendShapeName}.mat" ); VRMUtility.Bake(material, bindings); AnimationUtility.SetObjectReferenceCurve( animationClip, new EditorCurveBinding() { path = VRChatUtility.AutoBlinkMeshPath, type = typeof(SkinnedMeshRenderer), propertyName = $"m_Materials.Array.data[{materialIndex}]", }, secondsList .Select(seconds => new ObjectReferenceKeyframe() { time = seconds, value = material }).ToArray() ); }
/// <summary> /// 手の形に喜怒哀楽を割り当てます。 /// </summary> /// <param name="avatar"></param> /// <param name="clips"></param> /// <param name="vrmBlendShapeForFINGERPOINT"></param> private static void SetFeelings( GameObject avatar, IEnumerable <VRMBlendShapeClip> clips, VRMBlendShapeClip vrmBlendShapeForFINGERPOINT ) { #if VRC_SDK_VRCSDK2 VRChatUtility.AddCustomAnims(avatar: avatar); var avatarDescriptor = avatar.GetOrAddComponent <VRC_AvatarDescriptor>(); #elif VRC_SDK_VRCSDK3 var fxController = Duplicator.DuplicateAssetToFolder( source: AssetDatabase.LoadAssetAtPath <AnimatorController>( AssetDatabase.GUIDToAssetPath(BlendShapeReplacer.FXTemplateGUID) ), prefabInstance: avatar ); var avatarDescriptor = avatar.GetOrAddComponent <VRCAvatarDescriptor>(); avatarDescriptor.customizeAnimationLayers = true; avatarDescriptor.baseAnimationLayers = new[] { new VRCAvatarDescriptor.CustomAnimLayer() { type = VRCAvatarDescriptor.AnimLayerType.Base, isDefault = true, }, new VRCAvatarDescriptor.CustomAnimLayer() { type = VRCAvatarDescriptor.AnimLayerType.Additive, isDefault = true, }, new VRCAvatarDescriptor.CustomAnimLayer() { type = VRCAvatarDescriptor.AnimLayerType.Gesture, isDefault = true, }, new VRCAvatarDescriptor.CustomAnimLayer() { type = VRCAvatarDescriptor.AnimLayerType.Action, isDefault = true, }, new VRCAvatarDescriptor.CustomAnimLayer() { type = VRCAvatarDescriptor.AnimLayerType.FX, animatorController = fxController, }, }; avatarDescriptor.specialAnimationLayers = new[] { new VRCAvatarDescriptor.CustomAnimLayer() { type = VRCAvatarDescriptor.AnimLayerType.Sitting, isDefault = true, }, new VRCAvatarDescriptor.CustomAnimLayer() { type = VRCAvatarDescriptor.AnimLayerType.TPose, isDefault = true, }, new VRCAvatarDescriptor.CustomAnimLayer() { type = VRCAvatarDescriptor.AnimLayerType.IKPose, isDefault = true, }, }; var states = fxController.layers[1].stateMachine.states.Select(childState => childState.state).ToList(); #endif foreach (var preset in BlendShapeReplacer.MappingBlendShapeToVRChatAnim.Keys) { VRMBlendShapeClip blendShapeClip = preset == BlendShapePreset.Unknown ? vrmBlendShapeForFINGERPOINT : clips.FirstOrDefault(c => c.Preset == preset); if (!blendShapeClip) { continue; } AnimationClip animationClip = CreateFeeling(avatar, blendShapeClip, ref clips); string anim = BlendShapeReplacer.MappingBlendShapeToVRChatAnim[preset].ToString(); #if VRC_SDK_VRCSDK2 avatarDescriptor.CustomStandingAnims[anim] = animationClip; avatarDescriptor.CustomSittingAnims[anim] = animationClip; #elif VRC_SDK_VRCSDK3 states.First(s => s.name.ToLower() == anim.ToLower()).motion = animationClip; #endif } }