Skip to content

Instantly share code, notes, and snippets.

@kxn4t
Last active July 5, 2025 11:10
Show Gist options
  • Save kxn4t/bd7acc37c17215e933f668370e89dbea to your computer and use it in GitHub Desktop.
Save kxn4t/bd7acc37c17215e933f668370e89dbea to your computer and use it in GitHub Desktop.
複数のアニメーションファイル間で足りないBlendShape操作を補完し、表情が破綻しないように修正できるEditor拡張
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
namespace kxn4t.gist
{
internal class MissingBlendShapeInserterConfirmationWindow : EditorWindow
{
private static List<AnimationClip> animationClips = new List<AnimationClip>();
private static string skinnedMeshRendererPath = "Body";
private static bool overwriteExistingFiles = true;
private static string newFilePath = "";
private static bool useReferenceGameObject = false;
private static GameObject referenceGameObject;
// スクロール位置を管理
private Vector2 scrollPos;
// 各アニメーションクリップに対する欠落しているBlendShapeのリスト
private Dictionary<AnimationClip, List<string>> missingBlendShapesPerClip = new Dictionary<AnimationClip, List<string>>();
// 各アニメーションクリップに対する適用フラグ
private Dictionary<AnimationClip, bool> clipApplyFlags = new Dictionary<AnimationClip, bool>();
// BlendShape名とその値の辞書
private Dictionary<string, float> blendShapeValues = new Dictionary<string, float>();
// 変更点が計算済みかどうかのフラグ
private bool changesCalculated = false;
public static void SetData(
List<AnimationClip> clips,
string rendererPath,
bool overwriteFiles,
string filePath,
bool useRefGameObject,
GameObject refGameObject)
{
animationClips = new List<AnimationClip>(clips);
skinnedMeshRendererPath = rendererPath;
overwriteExistingFiles = overwriteFiles;
newFilePath = filePath;
useReferenceGameObject = useRefGameObject;
referenceGameObject = refGameObject;
}
public static void ShowWindow()
{
var window = GetWindow<MissingBlendShapeInserterConfirmationWindow>("変更内容の確認");
window.minSize = new Vector2(600, 400);
window.CalculateMissingBlendShapes(); // 変更点の計算を開始
}
private void OnGUI()
{
if (!changesCalculated)
{
// 変更点がまだ計算中の場合
EditorGUILayout.LabelField("変更点を計算中...", EditorStyles.boldLabel);
return;
}
if (missingBlendShapesPerClip.Count == 0)
{
// 変更が必要なBlendShapeがない場合
EditorGUILayout.LabelField("すべてのアニメーションクリップに必要なBlendShapeが含まれています。", EditorStyles.boldLabel);
return;
}
DrawConfirmationHeader(); // ヘッダー部分の描画
DrawMissingBlendShapesList(); // 欠落しているBlendShapeのリストを描画
DrawApplyButton(); // 適用ボタンの描画
}
// 確認ヘッダーの描画
private void DrawConfirmationHeader()
{
EditorGUILayout.LabelField("変更内容の確認", EditorStyles.boldLabel);
// オプションに応じたメッセージを表示
string message = useReferenceGameObject
? "以下のアニメーションファイルにBlendShape操作を指定したSkinnedMeshの値で追加します。"
: "以下のアニメーションファイルにBlendShape操作を0で追加します。";
EditorGUILayout.LabelField(message);
}
// 欠落しているBlendShapeのリストを描画
private void DrawMissingBlendShapesList()
{
// スクロールビューの開始
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
foreach (var kvp in missingBlendShapesPerClip)
{
var clip = kvp.Key;
var missingBlendShapes = kvp.Value;
missingBlendShapes.Sort(); // BlendShape名を昇順にソート
EditorGUILayout.BeginVertical("box");
// チェックボックス付きのアニメーションクリップ名を表示
clipApplyFlags[clip] = EditorGUILayout.ToggleLeft($"アニメーション: {clip.name}", clipApplyFlags[clip], EditorStyles.boldLabel);
if (clipApplyFlags[clip])
DrawBlendShapeDetails(missingBlendShapes); // 詳細を描画
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
}
EditorGUILayout.EndScrollView(); // スクロールビューの終了
}
// BlendShapeの詳細を描画
private void DrawBlendShapeDetails(List<string> blendShapes)
{
foreach (var blendShape in blendShapes)
{
// BlendShapeの値を取得
float value = blendShapeValues.ContainsKey(blendShape) ? blendShapeValues[blendShape] : 0f;
// BlendShape名と値を表示
EditorGUILayout.LabelField($" - {blendShape} : {value}");
}
}
// 適用ボタンの描画
private void DrawApplyButton()
{
if (GUILayout.Button("変更を適用"))
ApplyChanges();
}
// 変更点の計算
private void CalculateMissingBlendShapes()
{
// 初期化
missingBlendShapesPerClip.Clear();
clipApplyFlags.Clear();
blendShapeValues.Clear();
changesCalculated = false;
// すべてのBlendShape名を取得
HashSet<string> allBlendShapeNames = GetAllBlendShapeNames();
// 各アニメーションクリップについて欠落しているBlendShapeを調べる
foreach (var clip in animationClips)
{
if (clip == null)
continue;
// アニメーションクリップであることを確認
if (!(clip is AnimationClip))
{
ShowInvalidClipError(clip);
continue;
}
// 欠落しているBlendShapeを取得
List<string> missingBlendShapes = GetMissingBlendShapes(clip, allBlendShapeNames);
if (missingBlendShapes.Count > 0)
{
// 欠落しているBlendShapeがある場合はリストに追加
missingBlendShapesPerClip[clip] = missingBlendShapes;
clipApplyFlags[clip] = true; // デフォルトで適用するように設定
}
}
// 参照GameObjectからBlendShapeの値を取得
if (useReferenceGameObject)
{
if (!RetrieveBlendShapeValues(allBlendShapeNames))
return; // 取得に失敗した場合は処理を中断
}
changesCalculated = true; // 変更点の計算が完了
}
// 無効なクリップのエラー表示
private void ShowInvalidClipError(AnimationClip clip)
{
EditorUtility.DisplayDialog("エラー", $"指定されたオブジェクトはAnimationClipではありません。オブジェクト名: {clip.name}", "OK");
}
// BlendShapeの値を取得
private bool RetrieveBlendShapeValues(HashSet<string> blendShapeNames)
{
// 参照GameObjectからSkinnedMeshRendererを取得
SkinnedMeshRenderer smr = GetSkinnedMeshRendererFromGameObject(referenceGameObject, skinnedMeshRendererPath);
if (smr == null)
{
ShowSMRNotFoundError();
Close(); // ウィンドウを閉じる
return false;
}
if (smr.sharedMesh == null)
{
ShowSharedMeshNullError();
Close(); // ウィンドウを閉じる
return false;
}
// 各BlendShapeの値を取得
foreach (var blendShapeName in blendShapeNames)
{
string name = blendShapeName.Substring("blendShape.".Length);
int index = smr.sharedMesh.GetBlendShapeIndex(name);
float value = index != -1 ? smr.GetBlendShapeWeight(index) : 0f;
blendShapeValues[blendShapeName] = value;
}
return true;
}
// SkinnedMeshRendererが見つからないエラー表示
private void ShowSMRNotFoundError()
{
EditorUtility.DisplayDialog("エラー", $"指定されたGameObjectにSkinnedMeshRendererが見つかりません。パス: {skinnedMeshRendererPath}", "OK");
}
// sharedMeshがnullのエラー表示
private void ShowSharedMeshNullError()
{
EditorUtility.DisplayDialog("エラー", $"指定されたSkinnedMeshRendererのsharedMeshがありません。パス: {skinnedMeshRendererPath}", "OK");
}
// GameObjectからSkinnedMeshRendererを取得
private SkinnedMeshRenderer GetSkinnedMeshRendererFromGameObject(GameObject go, string path)
{
// 指定したパスで子オブジェクトを検索
Transform child = go.transform.Find(path);
if (child != null)
return child.GetComponent<SkinnedMeshRenderer>();
// 自身の名前がパスと一致する場合
if (go.name == path)
return go.GetComponent<SkinnedMeshRenderer>();
return null; // 見つからなかった場合
}
// すべてのBlendShape名を取得
private HashSet<string> GetAllBlendShapeNames()
{
var blendShapeNames = new HashSet<string>();
// 各アニメーションクリップからBlendShape名を収集
foreach (var clip in animationClips)
{
if (clip == null)
continue;
var bindings = AnimationUtility.GetCurveBindings(clip);
foreach (var binding in bindings)
{
if (IsTargetBlendShape(binding))
blendShapeNames.Add(binding.propertyName);
}
}
return blendShapeNames;
}
// クリップから欠落しているBlendShapeを取得
private List<string> GetMissingBlendShapes(AnimationClip clip, HashSet<string> allBlendShapeNames)
{
var clipBlendShapes = new HashSet<string>();
var bindings = AnimationUtility.GetCurveBindings(clip);
// クリップが持っているBlendShape名を収集
foreach (var binding in bindings)
{
if (IsTargetBlendShape(binding))
clipBlendShapes.Add(binding.propertyName);
}
var missingBlendShapes = new List<string>();
// すべてのBlendShape名と比較して欠落しているものを抽出
foreach (var blendShape in allBlendShapeNames)
{
if (!clipBlendShapes.Contains(blendShape))
missingBlendShapes.Add(blendShape);
}
return missingBlendShapes;
}
// 対象のBlendShapeかどうかを判定
private bool IsTargetBlendShape(EditorCurveBinding binding)
{
return binding.type == typeof(SkinnedMeshRenderer) &&
binding.path.Equals(skinnedMeshRendererPath) &&
binding.propertyName.StartsWith("blendShape.") &&
!binding.isPPtrCurve;
}
// 変更の適用
private void ApplyChanges()
{
bool anyApplied = false; // 変更が適用されたかどうかのフラグ
foreach (var kvp in missingBlendShapesPerClip)
{
var clip = kvp.Key;
if (!clipApplyFlags[clip])
continue; // 適用しない場合はスキップ
// ターゲットクリップを準備
AnimationClip targetClip = PrepareTargetClip(clip);
if (targetClip == null)
continue; // 準備に失敗した場合はスキップ
// BlendShapeをクリップに追加
AddBlendShapesToClip(targetClip, kvp.Value);
anyApplied = true; // 変更が適用された
}
if (anyApplied)
{
// アセットデータベースを更新
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("完了", "変更が適用されました。", "OK");
}
else
{
EditorUtility.DisplayDialog("情報", "適用するアニメーションクリップが選択されていません。", "OK");
}
Close(); // ウィンドウを閉じる
}
// ターゲットクリップの準備
private AnimationClip PrepareTargetClip(AnimationClip clip)
{
if (overwriteExistingFiles)
{
// 上書き保存の場合
if (IsClipReadOnly(clip))
{
ShowReadOnlyError(clip);
return null;
}
return clip; // 既存のクリップを使用
}
else
{
// 新しいファイルに保存する場合
return CreateNewClip(clip);
}
}
// クリップが読み取り専用かどうかを確認
private bool IsClipReadOnly(AnimationClip clip)
{
string assetPath = AssetDatabase.GetAssetPath(clip);
string fullPath = Path.GetFullPath(assetPath);
return (File.GetAttributes(fullPath) & FileAttributes.ReadOnly) != 0;
}
// 読み取り専用エラーの表示
private void ShowReadOnlyError(AnimationClip clip)
{
string assetPath = AssetDatabase.GetAssetPath(clip);
EditorUtility.DisplayDialog("エラー", $"ファイルが読み取り専用のため、上書きできません。ファイルパス: {assetPath}", "OK");
}
// 新しいクリップの作成
private AnimationClip CreateNewClip(AnimationClip originalClip)
{
// クリップをコピー
AnimationClip newClip = Instantiate(originalClip);
string assetPath = GetNewAssetPath(originalClip);
if (string.IsNullOrEmpty(assetPath))
return null;
// 新しいアセットとして保存
AssetDatabase.CreateAsset(newClip, assetPath);
// 保存が成功したか確認
if (AssetDatabase.LoadAssetAtPath<AnimationClip>(assetPath) == null)
{
EditorUtility.DisplayDialog("エラー", $"アセットの作成に失敗しました。パス: {assetPath}", "OK");
return null;
}
return newClip;
}
// 新しいアセットパスを取得
private string GetNewAssetPath(AnimationClip originalClip)
{
string originalPath = AssetDatabase.GetAssetPath(originalClip);
string fileName = Path.GetFileNameWithoutExtension(originalPath);
string newFileName = fileName + "_modified.anim";
string directory = string.IsNullOrEmpty(newFilePath) ? Path.GetDirectoryName(originalPath) : newFilePath;
// 保存先のディレクトリが存在するか確認
if (!Directory.Exists(directory))
{
EditorUtility.DisplayDialog("エラー", $"保存先フォルダが存在しません。パス: {directory}", "OK");
return null;
}
// ユニークなアセットパスを生成
return AssetDatabase.GenerateUniqueAssetPath(Path.Combine(directory, newFileName));
}
// BlendShapeをクリップに追加
private void AddBlendShapesToClip(AnimationClip clip, List<string> blendShapes)
{
foreach (var blendShape in blendShapes)
{
// BlendShapeの値を取得
float value = blendShapeValues.ContainsKey(blendShape) ? blendShapeValues[blendShape] : 0f;
// カーブバインディングを設定
var binding = new EditorCurveBinding
{
type = typeof(SkinnedMeshRenderer),
path = skinnedMeshRendererPath,
propertyName = blendShape
};
// アニメーションカーブを作成
var curve = new AnimationCurve(new Keyframe(0f, value));
try
{
// カーブをクリップに設定
AnimationUtility.SetEditorCurve(clip, binding, curve);
}
catch (System.Exception ex)
{
EditorUtility.DisplayDialog("エラー", $"カーブの設定に失敗しました。BlendShape: {blendShape}, エラー: {ex.Message}", "OK");
}
}
}
}
}
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
namespace kxn4t.gist
{
internal class MissingBlendShapeInserterWindow : EditorWindow
{
// ユーザーが指定するアニメーションクリップのリスト
public List<AnimationClip> animationClips = new List<AnimationClip>();
// スクロール位置を管理
private Vector2 scrollPos;
private Vector2 mainScrollPos;
// 保存オプション関連
public bool overwriteExistingFiles = true;
public string newFilePath = "";
// SkinnedMeshRendererのパス(名前)
public string skinnedMeshRendererPath = "Body";
// BlendShapeの値を取得するためのオプションと参照GameObject
public bool useReferenceGameObject = false;
public GameObject referenceGameObject;
[MenuItem("Tools/kxn4t gists/Missing BlendShape Inserter")]
public static void ShowWindow()
{
var window = GetWindow<MissingBlendShapeInserterWindow>("Missing BlendShape Inserter");
window.minSize = new Vector2(600, 400);
}
private void OnGUI()
{
DrawHeader(); // ヘッダー部分の描画
// ウィンドウをスクロール可能にする
mainScrollPos = EditorGUILayout.BeginScrollView(mainScrollPos);
DrawDragAndDropArea(); // ドラッグ&ドロップエリアの描画
DrawAnimationClipList(); // アニメーションクリップリストの描画
DrawSkinnedMeshRendererPathField(); // SkinnedMeshRendererパスフィールドの描画
DrawReferenceGameObjectOption(); // 参照GameObjectオプションの描画
DrawSaveOptions(); // 保存オプションの描画
EditorGUILayout.EndScrollView(); // スクロールビューの終了
DrawExecuteButton(); // 実行ボタンの描画
}
// ヘッダー部分の描画
private void DrawHeader()
{
EditorGUILayout.LabelField("複数のアニメーションファイル間で足りないBlendShape操作を補完し、表情が破綻しないように修正できます。", EditorStyles.wordWrappedLabel);
}
// ドラッグ&ドロップエリアの描画
private void DrawDragAndDropArea()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("アニメーションファイルをドラッグ&ドロップで指定", EditorStyles.boldLabel);
// ドラッグ&ドロップエリアを作成
Rect dropArea = GUILayoutUtility.GetRect(0.0f, 50.0f, GUILayout.ExpandWidth(true));
GUI.Box(dropArea, "ここにアニメーションファイルをドラッグ&ドロップ");
// ドラッグ&ドロップの処理を行う
HandleDragAndDrop(dropArea);
}
// ドラッグ&ドロップの処理
private void HandleDragAndDrop(Rect dropArea)
{
Event evt = Event.current;
// マウスがドロップエリア内にあるか確認
if (!dropArea.Contains(evt.mousePosition))
return;
// イベントタイプによって処理を分岐
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
// ドラッグ&ドロップの見た目を変更
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
AddAnimationClips(DragAndDrop.objectReferences);
evt.Use(); // イベントを使用済みにする
}
break;
}
}
// アニメーションクリップの追加
private void AddAnimationClips(Object[] objects)
{
foreach (var obj in objects)
{
if (obj is AnimationClip clip)
{
if (!animationClips.Contains(clip))
animationClips.Add(clip);
// Noop
// else
// ShowDuplicateClipWarning(clip);
}
}
}
private void ShowDuplicateClipWarning(AnimationClip clip)
{
EditorUtility.DisplayDialog("注意", $"アニメーションクリップ '{clip.name}' は既にリストに追加されています。", "OK");
}
// アニメーションクリップリストの描画
private void DrawAnimationClipList()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("アニメーションリスト", EditorStyles.boldLabel);
// リストの高さを計算
int itemCount = animationClips.Count;
float itemHeight = EditorGUIUtility.singleLineHeight + 4f;
float maxListHeight = 200f;
float listHeight = Mathf.Min(itemCount * itemHeight, maxListHeight);
// スクロールビューの開始
scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Height(listHeight + 10f));
// 各アニメーションクリップを描画
for (int i = 0; i < animationClips.Count; i++)
{
DrawAnimationClipItem(i);
}
EditorGUILayout.EndScrollView(); // スクロールビューの終了
}
// 個々のアニメーションクリップアイテムの描画
private void DrawAnimationClipItem(int index)
{
EditorGUILayout.BeginHorizontal(); // 水平レイアウトの開始
// アニメーションクリップのフィールドを描画
AnimationClip oldClip = animationClips[index];
AnimationClip newClip = (AnimationClip)EditorGUILayout.ObjectField(animationClips[index], typeof(AnimationClip), false);
// クリップが変更された場合の処理
if (newClip != oldClip)
UpdateAnimationClipAt(index, newClip);
// 削除ボタン
if (GUILayout.Button("-", GUILayout.Width(30)))
{
animationClips.RemoveAt(index);
return;
}
EditorGUILayout.EndHorizontal(); // 水平レイアウトの終了
// 直前に描画した領域を取得し、ドラッグ&ドロップ処理を設定
Rect lastRect = GUILayoutUtility.GetLastRect();
HandleDragAndDropOnItem(lastRect, index);
}
// アニメーションクリップの更新と重複チェック
private void UpdateAnimationClipAt(int index, AnimationClip newClip)
{
if (newClip != null && animationClips.Contains(newClip))
{
// 重複している場合は注意を表示
ShowDuplicateClipWarning(newClip);
}
else
{
// 重複していない場合はリストを更新
animationClips[index] = newClip;
}
}
// 個々のアイテムへのドラッグ&ドロップ処理
private void HandleDragAndDropOnItem(Rect dropArea, int index)
{
Event evt = Event.current;
// マウスがアイテム領域内にあるか確認
if (!dropArea.Contains(evt.mousePosition))
return;
// イベントタイプによって処理を分岐
switch (evt.type)
{
case EventType.DragUpdated:
case EventType.DragPerform:
// ドラッグ&ドロップの見た目を変更
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (evt.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
ReplaceAnimationClipAt(index, DragAndDrop.objectReferences);
evt.Use(); // イベントを使用済みにする
}
break;
}
}
// アニメーションクリップの置き換え
private void ReplaceAnimationClipAt(int index, Object[] objects)
{
foreach (var obj in objects)
{
if (obj is AnimationClip clip)
{
if (!animationClips.Contains(clip))
animationClips[index] = clip; // 新しいクリップで置き換え
else if (animationClips[index] != clip)
ShowDuplicateClipWarning(clip); // 重複注意
}
}
}
// SkinnedMeshRendererのパスフィールドの描画
private void DrawSkinnedMeshRendererPathField()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("SkinnedMeshRendererのパス(名前)を指定", EditorStyles.boldLabel);
// パスを入力するテキストフィールドを描画
skinnedMeshRendererPath = EditorGUILayout.TextField("パス(名前):", skinnedMeshRendererPath);
}
// BlendShapeの値取得オプションの描画
private void DrawReferenceGameObjectOption()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("BlendShapeの値の設定", EditorStyles.boldLabel);
// オプションのトグルを描画
useReferenceGameObject = EditorGUILayout.ToggleLeft("指定したGameObjectのBlendShapeの値を使用する", useReferenceGameObject);
// オプションが有効な場合はGameObjectのフィールドを表示
if (useReferenceGameObject)
referenceGameObject = (GameObject)EditorGUILayout.ObjectField("GameObject:", referenceGameObject, typeof(GameObject), true);
}
// 保存オプションの描画
private void DrawSaveOptions()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("保存オプション", EditorStyles.boldLabel);
// 上書き保存のオプションを描画
overwriteExistingFiles = EditorGUILayout.ToggleLeft("元のファイルを上書きする", overwriteExistingFiles);
// 上書きしない場合は新しい保存先を指定するフィールドを表示
if (!overwriteExistingFiles)
DrawNewFilePathField();
}
// 新しいファイルパスフィールドの描画
private void DrawNewFilePathField()
{
EditorGUILayout.BeginHorizontal();
// ラベルとテキストフィールドを水平に配置
EditorGUILayout.LabelField("新しいファイルの保存先:", GUILayout.Width(150));
newFilePath = EditorGUILayout.TextField(newFilePath);
// フォルダ選択ボタン
if (GUILayout.Button("選択", GUILayout.Width(50)))
SelectNewFilePath();
EditorGUILayout.EndHorizontal();
}
// 新しいファイルパスの選択ダイアログを表示
private void SelectNewFilePath()
{
string selectedPath = EditorUtility.OpenFolderPanel("保存先を選択", "", "");
if (string.IsNullOrEmpty(selectedPath))
return;
// 選択されたパスがプロジェクト内か確認
if (selectedPath.StartsWith(Application.dataPath))
newFilePath = "Assets" + selectedPath.Substring(Application.dataPath.Length);
else
EditorUtility.DisplayDialog("エラー", "保存先はプロジェクトのAssetsフォルダ内を指定してください。", "OK");
}
// 実行ボタンの描画
private void DrawExecuteButton()
{
EditorGUILayout.Space();
// アニメーションクリップが指定されている場合のみボタンを有効化
GUI.enabled = animationClips.Count > 0 && animationClips.Exists(clip => clip != null);
// 実行ボタン
if (GUILayout.Button("実行"))
CalculateMissingBlendShapes();
GUI.enabled = true; // GUIの状態を元に戻す
}
// 変更点の計算と確認ウィンドウの表示
private void CalculateMissingBlendShapes()
{
// 入力の検証
if (!ValidateInputs())
return;
// データを確認ウィンドウに渡す
MissingBlendShapeInserterConfirmationWindow.SetData(
animationClips,
skinnedMeshRendererPath,
overwriteExistingFiles,
newFilePath,
useReferenceGameObject,
referenceGameObject);
// 確認ウィンドウを表示
MissingBlendShapeInserterConfirmationWindow.ShowWindow();
}
// 入力の検証
private bool ValidateInputs()
{
// SkinnedMeshRendererのパスが指定されているか確認
if (string.IsNullOrEmpty(skinnedMeshRendererPath))
{
EditorUtility.DisplayDialog("エラー", "SkinnedMeshRendererのパス(名前)を指定してください。", "OK");
return false;
}
// 参照GameObjectが必要な場合の確認
if (useReferenceGameObject && referenceGameObject == null)
{
EditorUtility.DisplayDialog("エラー", "BlendShapeの値を取得するGameObjectを指定してください。", "OK");
return false;
}
// 新しいファイルパスが必要な場合の確認
if (!overwriteExistingFiles && string.IsNullOrEmpty(newFilePath))
{
EditorUtility.DisplayDialog("エラー", "新しいファイルの保存先を指定してください。", "OK");
return false;
}
return true;
}
}
}
@kxn4t
Copy link
Author

kxn4t commented Sep 23, 2024

面倒なのでCC0で

@kxn4t
Copy link
Author

kxn4t commented Jul 5, 2025

VCCで入れられるようになってます~
https://kanameliser.booth.pm/items/6751267

さらにZatoolに改善版が統合されました
https://zatools.kb10uy.dev/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy