ADD: 야모 개인스크립트 추가

This commit is contained in:
Yamo4490 2025-05-15 00:18:25 +09:00
parent 3f3d799879
commit 493a9c6273
101 changed files with 20200 additions and 0 deletions

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: c0567de9323ae234dbc4465a542d0be3
folderAsset: yes
timeCreated: 1536028717
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9adcb8c102e5f7948b0f7e44f6c4bf0e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,361 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Text.RegularExpressions;
// ver 1.0
// © 2019 gatosyocora
namespace VRCDeveloperTool
{
public class AnimationPropertyConverter : EditorWindow
{
private SkinnedMeshRenderer avatarMesh;
private List<AnimationClip> preAnimationClips;
private List<AnimationProperty> animPropertyList;
private string[] blendShapeNameList;
private string saveFolder = "Assets/";
private bool convertBlendShapeName = false;
private bool isOpeningPreAnimationClips = true;
private bool isOpeningAnimationPropertyList = true;
private Vector2 propertyScrollPos = Vector2.zero;
private bool isConvertAll = false;
public class AnimationProperty
{
public string propertyType;
public string preName;
public string posName;
public bool isConvert;
public int selectedBlendShapeIndex;
public List<int> animIndexHavingThisProperty;
public AnimationProperty(string type, string name, int animIndex)
{
propertyType = type;
preName = name;
posName = preName;
isConvert = false;
selectedBlendShapeIndex = 0;
animIndexHavingThisProperty = new List<int>() { animIndex };
}
public void AddAnimIndexHavingThisProperty(int animIndex)
{
animIndexHavingThisProperty.Add(animIndex);
}
public bool RemoveAnimIndexHavingThisProperty(int animIndex)
{
return animIndexHavingThisProperty.Remove(animIndex);
}
public bool existAnimHavingThisProperty()
{
return animIndexHavingThisProperty.Count > 0;
}
}
[MenuItem("VRCDeveloperTool/AnimationPropertyConverter")]
private static void Create()
{
GetWindow<AnimationPropertyConverter>("AnimationProperty Converter");
}
private void OnEnable()
{
avatarMesh = null;
preAnimationClips = new List<AnimationClip>();
preAnimationClips.Add(null);
animPropertyList = new List<AnimationProperty>();
blendShapeNameList = null;
}
private void OnGUI()
{
convertBlendShapeName = EditorGUILayout.ToggleLeft("Convert BlendShapeName", convertBlendShapeName);
if (convertBlendShapeName)
{
using (var check = new EditorGUI.ChangeCheckScope())
{
avatarMesh = EditorGUILayout.ObjectField(
"Avatar's SkinnedMeshRenderer",
avatarMesh,
typeof(SkinnedMeshRenderer),
true
) as SkinnedMeshRenderer;
if (check.changed && avatarMesh != null)
blendShapeNameList = GetBlendShapeNameList(avatarMesh);
}
}
isOpeningPreAnimationClips = EditorGUILayout.Foldout(isOpeningPreAnimationClips, "Pre AnimationClips");
if (isOpeningPreAnimationClips)
{
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.FlexibleSpace();
if (GUILayout.Button("+"))
{
preAnimationClips.Add(null);
}
if (GUILayout.Button("-") && preAnimationClips.Count > 1)
{
var animIndex = preAnimationClips.Count - 1;
CheckAndRemoveAnimProperties(ref animPropertyList, animIndex);
preAnimationClips.RemoveAt(animIndex);
}
}
using (new EditorGUI.IndentLevelScope())
{
for (int animIndex = 0; animIndex < preAnimationClips.Count; animIndex++)
{
using (var check = new EditorGUI.ChangeCheckScope())
{
preAnimationClips[animIndex] = EditorGUILayout.ObjectField(
"AnimationClip " + (animIndex + 1),
preAnimationClips[animIndex],
typeof(AnimationClip),
true
) as AnimationClip;
if (check.changed)
{
CheckAndRemoveAnimProperties(ref animPropertyList, animIndex);
if (preAnimationClips[animIndex] != null)
UpdateAnimationPropertyNameList(preAnimationClips[animIndex], ref animPropertyList, animIndex);
}
}
}
}
}
EditorGUILayout.Space();
isOpeningAnimationPropertyList = EditorGUILayout.Foldout(isOpeningAnimationPropertyList, "AnimationPropertyList");
if (isOpeningAnimationPropertyList)
{
using (new EditorGUI.IndentLevelScope())
{
using (new EditorGUILayout.HorizontalScope())
{
using (var check = new EditorGUI.ChangeCheckScope())
{
isConvertAll = EditorGUILayout.Toggle(isConvertAll, GUILayout.Width(30f));
if (check.changed)
ChangeAllIsConvertParams(isConvertAll, ref animPropertyList);
}
EditorGUILayout.LabelField("prePropertyName", EditorStyles.boldLabel);
EditorGUILayout.LabelField("posPropertyName", EditorStyles.boldLabel);
}
using (var scrollPos = new GUILayout.ScrollViewScope(propertyScrollPos))
{
propertyScrollPos = scrollPos.scrollPosition;
foreach (var animProperty in animPropertyList)
{
if (!convertBlendShapeName || animProperty.propertyType == "blendShape")
{
using (new EditorGUILayout.HorizontalScope())
{
animProperty.isConvert = EditorGUILayout.Toggle(animProperty.isConvert, GUILayout.Width(30f));
if (convertBlendShapeName && avatarMesh != null)
{
EditorGUILayout.LabelField(animProperty.preName);
}
else
{
EditorGUILayout.LabelField(animProperty.propertyType + "." + animProperty.preName);
}
using (var check = new EditorGUI.ChangeCheckScope())
{
if (convertBlendShapeName && avatarMesh != null)
{
animProperty.selectedBlendShapeIndex = EditorGUILayout.Popup(animProperty.selectedBlendShapeIndex, blendShapeNameList);
}
else
{
animProperty.posName = EditorGUILayout.TextField(animProperty.posName);
}
if (check.changed)
{
if (convertBlendShapeName && avatarMesh != null)
animProperty.posName = blendShapeNameList[animProperty.selectedBlendShapeIndex];
animProperty.isConvert = true;
}
}
}
}
}
}
}
}
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("AnimClipSaveFolder", saveFolder);
if (GUILayout.Button("Select Folder", GUILayout.Width(100)))
{
saveFolder = EditorUtility.OpenFolderPanel("Select saved folder", saveFolder, "");
var match = Regex.Match(saveFolder, @"Assets/.*");
saveFolder = match.Value + "/";
if (saveFolder == "/") saveFolder = "Assets/";
}
}
if (GUILayout.Button("Convert Property & Save as New File"))
{
ConvertAndCreateAnimationClips(preAnimationClips, animPropertyList, saveFolder, convertBlendShapeName);
}
}
/// <summary>
/// アニメーションファイルのプロパティの名前を変更して新しいファイルとして書き出す
/// </summary>
/// <param name="baseAnimClips"></param>
/// <param name="animPropertyList"></param>
/// <param name="saveFolder"></param>
/// <param name="convertBlendShapeName"></param>
private void ConvertAndCreateAnimationClips(List<AnimationClip> baseAnimClips, List<AnimationProperty> animPropertyList, string saveFolder, bool convertBlendShapeName)
{
// 変換するプロパティisConvert == trueのものだけのリストをつくる
var shoundConvertedPropertyList = animPropertyList.Where(x => x.isConvert).ToList<AnimationProperty>();
foreach (var baseAnimClip in baseAnimClips)
{
if (baseAnimClip == null) continue;
var convertedAnimClip = Object.Instantiate<AnimationClip>(baseAnimClip);
var bindings = AnimationUtility.GetCurveBindings(convertedAnimClip);
for (int i = 0; i < bindings.Length; i++)
{
// binding.propertyName == blendShape.vrc.v_silみたいになっている
var propertyType = bindings[i].propertyName.Split('.')[0];
var blendShapeName = bindings[i].propertyName.Replace(propertyType + ".", "");
// blendShapeだけ変更するモードだったらそれ以外の場合は処理しない
if (convertBlendShapeName && propertyType != "blendShape") continue;
// 変換するプロパティのリストに含まれるプロパティだけ変換する
var targetAnimProperty = shoundConvertedPropertyList.Find(x => x.propertyType == propertyType && x.preName == blendShapeName);
if (targetAnimProperty != null)
{
var curve = AnimationUtility.GetEditorCurve(convertedAnimClip, bindings[i]);
AnimationUtility.SetEditorCurve(convertedAnimClip, bindings[i], null);
bindings[i].propertyName = targetAnimProperty.propertyType + "." + targetAnimProperty.posName;
AnimationUtility.SetEditorCurve(convertedAnimClip, bindings[i], curve);
}
}
AssetDatabase.CreateAsset(convertedAnimClip, AssetDatabase.GenerateUniqueAssetPath(saveFolder + baseAnimClip.name + ".anim"));
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
/// <summary>
/// アニメーションプロパティリストを更新する
/// </summary>
/// <param name="animClips"></param>
/// <param name="animPropertyList"></param>
private void UpdateAnimationPropertyNameList(AnimationClip animClips, ref List<AnimationProperty> animPropertyList, int animIndex)
{
var bindings = AnimationUtility.GetCurveBindings(animClips);
foreach (var binding in bindings)
{
// binding.propertyName == blendShape.vrc.v_silみたいになっている
var propertyType = binding.propertyName.Split('.')[0];
var blendShapeName = binding.propertyName.Replace(propertyType + ".", "");
// animPropertyListに含まれていないプロパティだけ追加する
// すでに含まれている場合AnimIndexHavingThisPropertyに追加する
var animProperty = animPropertyList.Find(x => x.propertyType == propertyType && x.preName == blendShapeName);
if (animProperty == null)
animPropertyList.Add(new AnimationProperty(propertyType, blendShapeName, animIndex));
else
animProperty.AddAnimIndexHavingThisProperty(animIndex);
}
}
/// <summary>
/// ブレンドシェイプの名前一覧を取得する
/// </summary>
/// <param name="meshRenderer"></param>
/// <returns></returns>
private string[] GetBlendShapeNameList(SkinnedMeshRenderer meshRenderer)
{
var mesh = meshRenderer.sharedMesh;
if (mesh == null) return null;
var blendShapeNameList = new string[mesh.blendShapeCount];
for (int i = 0; i < mesh.blendShapeCount; i++)
blendShapeNameList[i] = mesh.GetBlendShapeName(i);
return blendShapeNameList;
}
/// <summary>
/// すべてのプロパティリストのisConvertを変更する
/// </summary>
/// <param name="isConvertAll"></param>
/// <param name="animPropertyList"></param>
private void ChangeAllIsConvertParams(bool isConvertAll, ref List<AnimationProperty> animPropertyList)
{
foreach (var animProperty in animPropertyList)
{
animProperty.isConvert = isConvertAll;
}
}
/// <summary>
/// プロパティリストに含まれるすべてのプロパティのAnimIndexHavingThisPropertyからanimIndexを削除して
/// リストに不要なプロパティか調べ、削除する
/// </summary>
/// <param name="animPropertyList"></param>
/// <param name="animIndex"></param>
private void CheckAndRemoveAnimProperties(ref List<AnimationProperty> animPropertyList, int animIndex)
{
foreach (var animProperty in animPropertyList.ToArray())
{
animProperty.RemoveAnimIndexHavingThisProperty(animIndex);
if (!animProperty.existAnimHavingThisProperty())
animPropertyList.Remove(animProperty);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9826d703d0a307c478e8e2a69f9ec980
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b7d1dbc790667b4d96b794d9102bb06
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,262 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Animations;
using System.Linq;
using System.IO;
using VRCDeveloperTool;
// ver 1.0.1
// Copyright (c) 2020 gatosyocora
namespace VRCDeveloperTool
{
public class AnimatorControllerDuplicator : EditorWindow
{
private RuntimeAnimatorController runtimeAnimatorController;
private static RuntimeAnimatorController tempController;
private List<ControllerAnimationClip> animationClips;
private string saveFolder;
private string endKeyword;
private bool isOverrideController = true;
private Vector2 scrollPos = Vector2.zero;
public class ControllerAnimationClip
{
public AnimationClip clip;
public List<int> controllerIndices;
public bool isDuplicate;
public ControllerAnimationClip(AnimationClip clip, int index, bool isDuplicate = true)
{
this.clip = clip;
controllerIndices = new List<int>();
AddIndex(index);
this.isDuplicate = isDuplicate;
}
public void AddIndex(int index)
{
controllerIndices.Add(index);
}
}
[MenuItem("CONTEXT/RuntimeAnimatorController/Duplicate Controller And Clips")]
private static void GetSelectController(MenuCommand menuCommand)
{
tempController = menuCommand.context as RuntimeAnimatorController;
Open();
}
[MenuItem("VRCDeveloperTool/AnimatorControllerDuplicator")]
public static void Open()
{
GetWindow<AnimatorControllerDuplicator>("AnimatorControllerDuplicator");
}
private void OnGUI()
{
if (tempController != null)
{
runtimeAnimatorController = tempController;
tempController = null;
LoadRuntimeControllerInfo(runtimeAnimatorController);
}
using (var check = new EditorGUI.ChangeCheckScope())
{
runtimeAnimatorController = EditorGUILayout.ObjectField(
"AnimatorController",
runtimeAnimatorController,
typeof(RuntimeAnimatorController),
true) as RuntimeAnimatorController;
EditorGUILayout.HelpBox("複製したいAnimatorOverrideControllerを設定してください", MessageType.Info);
if (!isOverrideController)
EditorGUILayout.HelpBox("まだAnimatorControllerは未対応です", MessageType.Error);
if (check.changed && runtimeAnimatorController != null)
{
LoadRuntimeControllerInfo(runtimeAnimatorController);
}
}
if (animationClips != null)
{
EditorGUILayout.LabelField("AnimaionClips", EditorStyles.boldLabel);
using (new EditorGUI.IndentLevelScope())
{
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("複製", EditorStyles.boldLabel, GUILayout.Width(50f));
EditorGUILayout.LabelField("AnimationClipの名前", EditorStyles.boldLabel);
}
using (var scroll = new EditorGUILayout.ScrollViewScope(scrollPos))
{
scrollPos = scroll.scrollPosition;
foreach (var animationClip in animationClips)
{
using (new EditorGUILayout.HorizontalScope())
{
animationClip.isDuplicate = EditorGUILayout.ToggleLeft(string.Empty, animationClip.isDuplicate, GUILayout.Width(50f));
EditorGUILayout.LabelField(animationClip.clip.name);
if (GUILayout.Button("Select"))
{
Selection.activeObject = animationClip.clip;
}
}
}
}
}
}
saveFolder = EditorGUILayout.TextField("保存先フォルダ", saveFolder);
endKeyword = EditorGUILayout.TextField("複製後Assetのキーワード", endKeyword);
EditorGUILayout.HelpBox("AnimatorControllerおよび選択したAnimationClipを複製します\n複製されると複製後のものがそれぞれ設定されます\n複製後Assetのキーワードに設定した文字がそれぞれの名前の末尾につきます", MessageType.Info);
using (new EditorGUI.DisabledGroupScope(runtimeAnimatorController == null || !isOverrideController))
{
if (GUILayout.Button("Duplicate AnimatorController & AnimationClips"))
{
DuplicateAnimatorControllerAndAnimationClips();
}
}
}
private void DuplicateAnimatorControllerAndAnimationClips()
{
var controllerPath = AssetDatabase.GetAssetPath(runtimeAnimatorController);
var newControllerPath = AssetDatabase.GenerateUniqueAssetPath(
saveFolder + "\\"
+ GatoEditorUtility.AddKeywordToEnd(Path.GetFileNameWithoutExtension(controllerPath), endKeyword)
+ Path.GetExtension(controllerPath));
var success = AssetDatabase.CopyAsset(controllerPath, newControllerPath);
if (!success)
{
Debug.LogError("AnimatorControllerの複製に失敗しました");
return;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
runtimeAnimatorController = AssetDatabase.LoadAssetAtPath(newControllerPath, typeof(RuntimeAnimatorController)) as RuntimeAnimatorController;
foreach (var animationClip in animationClips)
{
if (!animationClip.isDuplicate) continue;
var animClipPath = AssetDatabase.GetAssetPath(animationClip.clip);
var newAnimClipPath = AssetDatabase.GenerateUniqueAssetPath(
saveFolder + "\\"
+ GatoEditorUtility.AddKeywordToEnd(Path.GetFileNameWithoutExtension(animClipPath), endKeyword)
+ Path.GetExtension(animClipPath));
var successAnimClip = AssetDatabase.CopyAsset(animClipPath, newAnimClipPath);
if (!successAnimClip)
{
Debug.LogErrorFormat("AnimationClip:{0}の複製に失敗しました", animationClip.clip.name);
return;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
animationClip.clip = AssetDatabase.LoadAssetAtPath(newAnimClipPath, typeof(AnimationClip)) as AnimationClip;
foreach (var index in animationClip.controllerIndices)
{
runtimeAnimatorController.animationClips[index] = animationClip.clip;
}
}
if (isOverrideController)
{
var overrideController = runtimeAnimatorController as AnimatorOverrideController;
var baseAnimClips = overrideController.runtimeAnimatorController.animationClips;
foreach (var animationClip in animationClips)
{
foreach (var index in animationClip.controllerIndices)
{
var baseAnimClipName = baseAnimClips[index].name;
overrideController[baseAnimClipName] = animationClip.clip;
}
}
}
else
{
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private List<ControllerAnimationClip> DistinctControllerAnimationClips(List<ControllerAnimationClip> animClips)
{
var distinctedAnimClips = new List<ControllerAnimationClip>();
var animClipDictionary = new Dictionary<string, ControllerAnimationClip>();
for (int i = 0; i < animClips.Count; i++)
{
var animName = animClips[i].clip.name;
if (!animClipDictionary.ContainsKey(animName))
{
animClipDictionary.Add(animName, animClips[i]);
}
else
{
var animClipData = animClipDictionary[animName];
animClipData.AddIndex(animClips[i].controllerIndices.First());
}
}
distinctedAnimClips = animClipDictionary.Select(x => x.Value).ToList();
return distinctedAnimClips;
}
private void LoadRuntimeControllerInfo(RuntimeAnimatorController runtimeAnimatorController)
{
AnimatorController controller = runtimeAnimatorController as AnimatorController;
AnimatorOverrideController overrideController = runtimeAnimatorController as AnimatorOverrideController;
if (controller != null)
{
isOverrideController = false;
// AnimatorControllerからAnimationClipの取得
}
else if (overrideController != null)
{
isOverrideController = true;
// AnimatorOverrideControllerからAnimationClipの取得
// OverrideされたAnimationClipのみ取得
var baseAnimationController = overrideController.runtimeAnimatorController as AnimatorController;
animationClips = overrideController.animationClips
.Select((x, index) => new { Value = x, Index = index })
.Where(x => baseAnimationController.animationClips[x.Index].name != x.Value.name)
.Select(x => new ControllerAnimationClip(x.Value, x.Index))
.ToList();
animationClips = DistinctControllerAnimationClips(animationClips);
}
saveFolder = Path.GetDirectoryName(AssetDatabase.GetAssetPath(runtimeAnimatorController));
endKeyword = string.Empty;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b31bd1421b78b1e46b35ab56873af04e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 0ecf1e763fda23f48a947d07940662a5
folderAsset: yes
timeCreated: 1537791342
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,268 @@
using UnityEngine;
using UnityEditor;
#if VRC_SDK_VRCSDK2
using VRCSDK2;
#endif
using System.Collections.Generic;
// ver 1.2.2
// © 2018 gatosyocora
namespace VRCDeveloperTool
{
public class ComponentAdder : EditorWindow
{
GameObject targetObject = null;
private enum AddType
{
Current_Children_Only,
All_Childrens,
};
AddType addType;
private bool isRigidbody = false;
private bool useGravityFlag = true;
private bool isKinematicFlag = false;
private bool freezePosFlag = false;
private bool freezeRotFlag = false;
private bool isObjectSync = false;
private bool isPickup = false;
private bool isBoxCollider = false;
private bool isTriggerFlag = false;
[MenuItem("VRCDeveloperTool/Component Adder")]
private static void Create()
{
GetWindow<ComponentAdder>("Component Adder");
}
private void OnGUI()
{
targetObject = EditorGUILayout.ObjectField(
"ParentObject",
targetObject,
typeof(GameObject),
true
) as GameObject;
addType = (AddType)EditorGUILayout.EnumPopup("Add Type", addType);
guiRigidbody();
guiObjectSync();
guiPickup();
guiBoxCollider();
guiAction();
}
// 指定オブジェクトの直接的な子オブジェクトをすべて取得する
private List<GameObject> getCurrentChildrens(GameObject parentObj)
{
List<GameObject> objs = new List<GameObject>(parentObj.transform.childCount);
foreach (Transform child in parentObj.transform)
{
objs.Add(child.gameObject);
}
return objs;
}
// 指定オブジェクトの子オブジェクト以降をすべて取得する
private List<GameObject> getAllChildrens(GameObject parentObj)
{
List<GameObject> objs = new List<GameObject>();
var childTransform = parentObj.GetComponentsInChildren<Transform>();
foreach (Transform child in childTransform)
{
objs.Add(child.gameObject);
}
return objs;
}
// 特定のオブジェクトにコンポーネントを追加する
private void AddComponentObject(GameObject obj)
{
if (isRigidbody)
{
var rigid = obj.GetComponent<Rigidbody>();
if (rigid == null)
rigid = obj.AddComponent<Rigidbody>();
rigid.isKinematic = isKinematicFlag;
rigid.useGravity = useGravityFlag;
rigid.constraints = 0;
if (freezePosFlag) rigid.constraints |= RigidbodyConstraints.FreezePosition;
if (freezeRotFlag) rigid.constraints |= RigidbodyConstraints.FreezeRotation;
}
if (isObjectSync)
{
#if VRC_SDK_VRCSDK2
if (obj.GetComponent<VRC_ObjectSync>() == null)
{
var com = obj.AddComponent<VRC_ObjectSync>();
}
#endif
}
if (isPickup)
{
#if VRC_SDK_VRCSDK2
if (obj.GetComponent<VRC_Pickup>() == null)
{
var com = obj.AddComponent<VRC_Pickup>();
}
#endif
}
if (isBoxCollider)
{
if (obj.GetComponent<Collider>() == null || obj.GetComponent<BoxCollider>() != null)
{
var com = obj.GetComponent<BoxCollider>();
if (com == null)
com = obj.AddComponent<BoxCollider>();
com.isTrigger = isTriggerFlag;
}
}
}
// 特定のオブジェクトのコンポーネントを削除する
private void DeleteComponentObject(GameObject obj)
{
if (isPickup)
{
#if VRC_SDK_VRCSDK2
var com = obj.GetComponent<VRC_Pickup>();
if (com != null) DestroyImmediate(com);
#endif
}
if (isRigidbody)
{
var com = obj.GetComponent<Rigidbody>();
if (com != null) DestroyImmediate(com);
}
if (isObjectSync)
{
#if VRC_SDK_VRCSDK2
var com = obj.GetComponent<VRC_ObjectSync>();
if (com != null) DestroyImmediate(com);
#endif
}
if (isBoxCollider)
{
var com = obj.GetComponent<BoxCollider>();
if (com != null) DestroyImmediate(com);
}
}
private void guiRigidbody()
{
isRigidbody = EditorGUILayout.BeginToggleGroup("Rigidbody", isRigidbody);
if (isRigidbody)
{
useGravityFlag = EditorGUILayout.Toggle("useGravity", useGravityFlag);
isKinematicFlag = EditorGUILayout.Toggle("isKinematic", isKinematicFlag);
freezePosFlag = EditorGUILayout.Toggle("Freeze Positions", freezePosFlag);
freezeRotFlag = EditorGUILayout.Toggle("Freeze Rotations", freezeRotFlag);
}
EditorGUILayout.EndToggleGroup();
}
private void guiObjectSync()
{
isObjectSync = EditorGUILayout.BeginToggleGroup("VRC_ObjectSync", isObjectSync);
//syncPhysicsFlag = EditorGUILayout.Toggle("Synchronize Physics", syncPhysicsFlag);
//collisionTransferFlag = EditorGUILayout.Toggle("Collision Transfer", collisionTransferFlag);
#if VRC_SDK_VRCSDK2
#else
if (isObjectSync)
{
EditorGUILayout.HelpBox("VRCSDK2をインポートしてください", MessageType.Error);
}
#endif
EditorGUILayout.EndToggleGroup();
}
private void guiPickup()
{
isPickup = EditorGUILayout.BeginToggleGroup("VRC_Pickup", isPickup);
#if VRC_SDK_VRCSDK2
#else
if (isPickup)
{
EditorGUILayout.HelpBox("VRCSDK2をインポートしてください", MessageType.Error);
}
#endif
EditorGUILayout.EndToggleGroup();
}
private void guiBoxCollider()
{
isBoxCollider = EditorGUILayout.BeginToggleGroup("BoxCollider", isBoxCollider);
if (isBoxCollider)
{
isTriggerFlag = EditorGUILayout.Toggle("isTrigger", isTriggerFlag);
}
EditorGUILayout.EndToggleGroup();
}
private void guiAction()
{
EditorGUI.BeginDisabledGroup(targetObject == null);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Add/Change Components"))
{
List<GameObject> objs;
if (addType == AddType.Current_Children_Only)
{
objs = getCurrentChildrens(targetObject);
}
else
{
objs = getAllChildrens(targetObject);
}
foreach (GameObject obj in objs)
{
AddComponentObject(obj);
}
}
if (GUILayout.Button("Delete Components"))
{
List<GameObject> objs;
if (addType == AddType.Current_Children_Only)
{
objs = getCurrentChildrens(targetObject);
}
else
{
objs = getAllChildrens(targetObject);
}
foreach (GameObject obj in objs)
{
DeleteComponentObject(obj);
}
}
EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup();
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e5f0ce7345f2dcb4b9c8d9a0f1a9de10
timeCreated: 1537079080
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 38b7d06e6faf6454ba07c362e80890d1
folderAsset: yes
timeCreated: 1537791366
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: d4451bc762558274ea722d45840a70a0
folderAsset: yes
timeCreated: 1537791379
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 3d6aa19a0d34987449b69a30397c0171
timeCreated: 1537791431
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: bf1036ae4731baf41b35d98e03686f41
timeCreated: 1537791411
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: ea99b4a464dafa541b7d326a128bb5af
timeCreated: 1537791431
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 188c432963af8ea4fab44ea681a8df5d
timeCreated: 1537791431
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: d13ab5ba79c432547ab955ad1343f36d
timeCreated: 1537791431
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: fc63fb35bd0032944a05af79bb27fa14
timeCreated: 1537791431
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 445b91c568bfc6e438d551e42d995dca
timeCreated: 1537791431
licenseType: Free
NativeFormatImporter:
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,128 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEditor;
// ver 1.2
// © 2018-9-24 gatosyocora
namespace VRCDeveloperTool
{
public class HandPoseAdder : Editor
{
private static string ORIGIN_ANIM_PATH = "Assets/VRCDeveloperTool/Editor/HandPoseAdder/Animations/"; // コピー元となるAnimationファイルが置いてあるディレクトリのパス
private static readonly string[] HANDNAMES ={"LeftHand.Index", "LeftHand.Little", "LeftHand.Middle", "LeftHand.Ring", "LeftHand.Thumb",
"RightHand.Index", "RightHand.Little", "RightHand.Middle", "RightHand.Ring", "RightHand.Thumb"};
private static readonly string[] HANDPOSTYPES = { "1 Stretched", "2 Stretched", "3 Stretched", "Spread" };
// None
[MenuItem("CONTEXT/Motion/Clear Hand pose", false, 0)]
private static void ClearHandAnimationKeys(MenuCommand command)
{
ClearHandPoseAnimationKeys(command);
}
// FINGER POINT
[MenuItem("CONTEXT/Motion/Add Hand pose 'FINGER POINT'", false, 1)]
private static void AddFPAnimationKeys(MenuCommand command)
{
AddHandPoseAnimationKeys(command, ORIGIN_ANIM_PATH + "FingerPoint.anim");
}
// FIST
[MenuItem("CONTEXT/Motion/Add Hand pose 'FIST'", false, 2)]
private static void AddFISTAnimationKeys(MenuCommand command)
{
AddHandPoseAnimationKeys(command, ORIGIN_ANIM_PATH + "Fist.anim");
}
// HAND GUN
[MenuItem("CONTEXT/Motion/Add Hand pose 'HAND GUN'", false, 3)]
private static void AddHGAnimationKeys(MenuCommand command)
{
AddHandPoseAnimationKeys(command, ORIGIN_ANIM_PATH + "HandGun.anim");
}
// HAND OPEN
[MenuItem("CONTEXT/Motion/Add Hand pose 'HAND OPEN'", false, 4)]
private static void AddHOAnimationKeys(MenuCommand command)
{
AddHandPoseAnimationKeys(command, ORIGIN_ANIM_PATH + "HandOpen.anim");
}
// ROCKN ROLL
[MenuItem("CONTEXT/Motion/Add Hand pose 'ROCK N ROLL'", false, 5)]
private static void AddRRAnimationKeys(MenuCommand command)
{
AddHandPoseAnimationKeys(command, ORIGIN_ANIM_PATH + "RocknRoll.anim");
}
// THUMBS UP
[MenuItem("CONTEXT/Motion/Add Hand pose 'THUMBS UP'", false, 6)]
private static void AddTUAnimationKeys(MenuCommand command)
{
AddHandPoseAnimationKeys(command, ORIGIN_ANIM_PATH + "ThumbsUp.anim");
}
// VICTORY
[MenuItem("CONTEXT/Motion/Add Hand pose 'VICTORY'", false, 7)]
private static void AddVICTORYAnimationKeys(MenuCommand command)
{
AddHandPoseAnimationKeys(command, ORIGIN_ANIM_PATH + "Victory.anim");
}
// 特定のAnimationファイルのAnimationキー全てをコピーする
public static void AddHandPoseAnimationKeys(MenuCommand command, string originPath)
{
AnimationClip targetClip = command.context as AnimationClip;
AnimationClip originClip = (AnimationClip)AssetDatabase.LoadAssetAtPath(originPath, typeof(AnimationClip)); // originPathよりAnimationClipの読み込み
CopyAnimationKeys(originClip, targetClip);
}
// originClipに設定されたAnimationKeyをすべてtargetclipにコピーする
public static void CopyAnimationKeys(AnimationClip originClip, AnimationClip targetClip)
{
foreach (var binding in AnimationUtility.GetCurveBindings(originClip).ToArray())
{
// AnimationClipよりAnimationCurveを取得
AnimationCurve curve = AnimationUtility.GetEditorCurve(originClip, binding);
// AnimationClipにキーリダクションを行ったAnimationCurveを設定
AnimationUtility.SetEditorCurve(targetClip, binding, curve);
}
}
/// <summary>
/// 手の形に関するAnimationキーを全て削除する
/// </summary>
/// <param name="command"></param>
public static void ClearHandPoseAnimationKeys(MenuCommand command)
{
var targetClip = command.context as AnimationClip;
foreach (var handname in HANDNAMES)
{
foreach (var handpostype in HANDPOSTYPES)
{
var binding = new EditorCurveBinding();
binding.path = "";
binding.type = typeof(Animator);
binding.propertyName = handname + "." + handpostype;
// キーを削除する
AnimationUtility.SetEditorCurve(targetClip, binding, null);
}
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1f784a4681f082c418e5751c74cc50ec
timeCreated: 1537770890
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,121 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
// ver 1.1
// © 2018-9-25 gatosyocora
namespace VRCDeveloperTool
{
public class HandPoseSetting : EditorWindow
{
private static string EditorPath = "Assets/VRCDeveloperTool/Editor/HandPoseAdder/";
private static string handPoseName = "";
private static AnimationClip handPoseAnimClip = null;
[MenuItem("VRCDeveloperTool/HandPose Adder Setting")]
private static void Create()
{
loadSettingData();
GetWindow<HandPoseSetting>("HandPose Adder Setting");
}
private void OnGUI()
{
EditorGUILayout.PrefixLabel("Custom Hand Pose");
handPoseName = EditorGUILayout.TextField("Hand Pose Name", handPoseName);
handPoseAnimClip = EditorGUILayout.ObjectField(
"Hand Pose AnimationClip",
handPoseAnimClip,
typeof(AnimationClip),
true
) as AnimationClip;
EditorGUI.BeginDisabledGroup(handPoseName == "" || handPoseAnimClip == null);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Apply Setting"))
{
createSettingData(handPoseName, handPoseAnimClip);
}
EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(!File.Exists(EditorPath + "SettingData.asset") || !File.Exists(EditorPath + "CustomHandPoseAdder.cs"));
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Load Setting"))
{
loadSettingData();
}
if (GUILayout.Button("Clear Setting"))
{
handPoseName = "";
handPoseAnimClip = null;
ClearSettingData();
}
EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup();
}
// 設定データを保存
private static void createSettingData(string name, AnimationClip animClip)
{
HandPoseSettingData.CreateSettingData(name, animClip);
CreateCustomHandPoseAdderScript();
}
// 設定データを読み込み
private static void loadSettingData()
{
HandPoseSettingData settingData = HandPoseSettingData.LoadSettingData();
if (settingData == null) return;
handPoseName = settingData.handPoseName;
handPoseAnimClip = settingData.handPoseAnimClip;
}
// 追加した手の形のAnimationファイルをコピーするためのスクリプトを作成
public static void CreateCustomHandPoseAdderScript()
{
string scriptAssetPath = EditorPath + "CustomHandPoseAdder.cs";
StreamWriter sw = new StreamWriter(scriptAssetPath, false);
sw.WriteLine("using UnityEngine;");
sw.WriteLine("using UnityEditor;");
sw.WriteLine("using VRCDeveloperTool;");
sw.WriteLine("");
sw.WriteLine("// Generated by HandPoseSetting.cs ver 1.1");
sw.WriteLine("// © 2018-9-25 gatosyocora");
sw.WriteLine("");
sw.WriteLine("public class CustomHandPoseAdder : EditorWindow {");
sw.WriteLine(" [MenuItem(\"CONTEXT/Motion/Add Hand pose '" + handPoseName + "'\", false, 8)]");
sw.WriteLine(" private static void AddFPAnimationKeys(MenuCommand command) {");
sw.WriteLine(" string animClipPath = \"" + AssetDatabase.GetAssetPath(handPoseAnimClip) + "\";");
sw.WriteLine(" HandPoseAdder.AddHandPoseAnimationKeys(command, animClipPath);");
sw.WriteLine(" }");
sw.WriteLine("}");
sw.Flush();
sw.Close();
AssetDatabase.ImportAsset(scriptAssetPath);
}
// 設定データを管理するファイルとCustomスクリプトを削除
private static void ClearSettingData()
{
if (File.Exists(EditorPath + "SettingData.asset")) AssetDatabase.DeleteAsset(EditorPath + "SettingData.asset");
if (File.Exists(EditorPath + "CustomHandPoseAdder.cs")) AssetDatabase.DeleteAsset(EditorPath + "CustomHandPoseAdder.cs");
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 51c6f466a893c0f45b1aa5bc320b341a
timeCreated: 1537829968
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,50 @@
using UnityEngine;
using UnityEditor;
// ver 1.1
// © 2018-9-25 gatosyocora
namespace VRCDeveloperTool
{
public class HandPoseSettingData : ScriptableObject
{
private static string EditorPath = "Assets/VRCDeveloperTool/Editor/HandPoseAdder/";
/*
int handPoseNum = 0;
struct handPoseData
{
public string handPoseName;
public AnimationClip handPoseAnimClip;
}
List<handPoseData> handPoses;
*/
public string handPoseName; // 手の形の名前
public AnimationClip handPoseAnimClip; // 手の形のAnimationキーを持つAnimationClip
// 設定データを保存するファイルを作成
public static void CreateSettingData(string name, AnimationClip animClip)
{
var settingData = CreateInstance<HandPoseSettingData>();
settingData.handPoseName = name;
settingData.handPoseAnimClip = animClip;
AssetDatabase.CreateAsset(settingData, EditorPath + "SettingData.asset");
AssetDatabase.Refresh();
}
// 設定データを保存したファイルからデータを読み込み
public static HandPoseSettingData LoadSettingData()
{
var settingData = AssetDatabase.LoadAssetAtPath<HandPoseSettingData>(EditorPath + "SettingData.asset");
return settingData;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4cc22f6d24d159048837aed82bd0c02b
timeCreated: 1537828928
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cbaf9806198075042b23b2457593a9cb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,81 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
// ver 1.2
// (c) 2019 gatosyocora
namespace VRCDeveloperTool
{
public static class HumanoidPoseResetter
{
private static HumanBodyBones[] boneList
= {
HumanBodyBones.Hips,
HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm,
HumanBodyBones.RightUpperArm, HumanBodyBones.RightLowerArm,
HumanBodyBones.LeftUpperLeg, HumanBodyBones.LeftLowerLeg,
HumanBodyBones.RightUpperLeg,HumanBodyBones.RightLowerLeg,
/*HumanBodyBones.LeftThumbDistal, */HumanBodyBones.LeftThumbIntermediate, HumanBodyBones.LeftThumbProximal,
HumanBodyBones.LeftIndexDistal, HumanBodyBones.LeftIndexIntermediate, HumanBodyBones.LeftIndexProximal,
HumanBodyBones.LeftMiddleDistal, HumanBodyBones.LeftMiddleIntermediate, HumanBodyBones.LeftMiddleProximal,
HumanBodyBones.LeftRingDistal, HumanBodyBones.LeftRingIntermediate, HumanBodyBones.LeftRingProximal,
HumanBodyBones.LeftLittleDistal, HumanBodyBones.LeftLittleIntermediate, HumanBodyBones.LeftLittleProximal,
/*HumanBodyBones.RightThumbDistal, */HumanBodyBones.RightThumbIntermediate, HumanBodyBones.RightThumbProximal,
HumanBodyBones.RightIndexDistal, HumanBodyBones.RightIndexIntermediate, HumanBodyBones.RightIndexProximal,
HumanBodyBones.RightMiddleDistal, HumanBodyBones.RightMiddleIntermediate, HumanBodyBones.RightMiddleProximal,
HumanBodyBones.RightRingDistal, HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingProximal,
HumanBodyBones.RightLittleDistal, HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleProximal
};
public static void ResetPose(GameObject obj)
{
/* 対象オブジェクトのポーズを取得 */
Animator animator = obj.GetComponent<Animator>();
if (animator == null) return;
var sourcePath = AssetDatabase.GetAssetPath(animator.avatar);
var sourceObj = AssetDatabase.LoadAssetAtPath(sourcePath, typeof(GameObject)) as GameObject;
var boneTrans = new Transform[boneList.Length];
for (int i = 0; i < boneList.Length; i++)
{
boneTrans[i] = animator.GetBoneTransform(boneList[i]);
}
/* 対象オブジェクトの親Prefabのポーズを取得 */
Animator sourceAnim = sourceObj.GetComponent<Animator>();
if (sourceAnim == null) return;
var boneTrans_p = new Transform[boneList.Length];
for (int i = 0; i < boneList.Length; i++)
boneTrans_p[i] = sourceAnim.GetBoneTransform(boneList[i]);
for (int j = 0; j < boneList.Length; j++)
{
var trans = boneTrans[j];
var prefabTrans = boneTrans_p[j];
if (trans == null)
{
Debug.Log("[Transform not found]:" + j + ":" + boneList[j]);
continue;
}
Undo.RecordObject(trans, "Change Transform " + trans.name);
trans.localPosition = prefabTrans.localPosition;
trans.localRotation = prefabTrans.localRotation;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bdf532e6fe46b148a65d0eae915bc28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,76 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
// ver 1.2
// © 2019 gatosyocora
namespace VRCDeveloperTool
{
public class HumanoidPoseResetterEditor : EditorWindow
{
private GameObject targetObject = null;
private static HumanBodyBones[] boneList
= {
HumanBodyBones.Hips,
HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm,
HumanBodyBones.RightUpperArm, HumanBodyBones.RightLowerArm,
HumanBodyBones.LeftUpperLeg, HumanBodyBones.LeftLowerLeg,
HumanBodyBones.RightUpperLeg,HumanBodyBones.RightLowerLeg,
/*HumanBodyBones.LeftThumbDistal, */HumanBodyBones.LeftThumbIntermediate, HumanBodyBones.LeftThumbProximal,
HumanBodyBones.LeftIndexDistal, HumanBodyBones.LeftIndexIntermediate, HumanBodyBones.LeftIndexProximal,
HumanBodyBones.LeftMiddleDistal, HumanBodyBones.LeftMiddleIntermediate, HumanBodyBones.LeftMiddleProximal,
HumanBodyBones.LeftRingDistal, HumanBodyBones.LeftRingIntermediate, HumanBodyBones.LeftRingProximal,
HumanBodyBones.LeftLittleDistal, HumanBodyBones.LeftLittleIntermediate, HumanBodyBones.LeftLittleProximal,
/*HumanBodyBones.RightThumbDistal, */HumanBodyBones.RightThumbIntermediate, HumanBodyBones.RightThumbProximal,
HumanBodyBones.RightIndexDistal, HumanBodyBones.RightIndexIntermediate, HumanBodyBones.RightIndexProximal,
HumanBodyBones.RightMiddleDistal, HumanBodyBones.RightMiddleIntermediate, HumanBodyBones.RightMiddleProximal,
HumanBodyBones.RightRingDistal, HumanBodyBones.RightRingIntermediate, HumanBodyBones.RightRingProximal,
HumanBodyBones.RightLittleDistal, HumanBodyBones.RightLittleIntermediate, HumanBodyBones.RightLittleProximal
};
[MenuItem("VRCDeveloperTool/HumanoidPose Resetter")]
private static void Create()
{
GetWindow<HumanoidPoseResetterEditor>("HumanoidPose Resetter");
}
[MenuItem("GameObject/VRCDeveloperTool/Reset Pose", false, 20)]
public static void ResetPoseFromHierarchy(MenuCommand command)
{
var obj = command.context as GameObject;
HumanoidPoseResetter.ResetPose(obj);
}
private void OnGUI()
{
targetObject = EditorGUILayout.ObjectField(
"TargetObject",
targetObject,
typeof(GameObject),
true
) as GameObject;
GuiAction();
}
private void GuiAction()
{
EditorGUI.BeginDisabledGroup(targetObject == null);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Reset Pose"))
{
HumanoidPoseResetter.ResetPose(targetObject);
}
EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c08fbe5f95513a94bb86a4eccef27711
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 03b40ddbf4e82064b8013d1a4c0712a0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,128 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
// ver 1.02
// © 2019-2-1 gatosyocora
namespace VRCDeveloperTool
{
public class MeshBoundsSetter : EditorWindow
{
private GameObject targetObject = null;
private Vector3 boundsScale = new Vector3(1, 2, 1);
private List<GameObject> exclusions = new List<GameObject>();
[MenuItem("VRCDeveloperTool/MeshBounds Setter")]
private static void Create()
{
GetWindow<MeshBoundsSetter>("MeshBounds Setter");
}
private void OnGUI()
{
targetObject = EditorGUILayout.ObjectField(
"TargetObject",
targetObject,
typeof(GameObject),
true
) as GameObject;
boundsScale = EditorGUILayout.Vector3Field("Bounds Scale", boundsScale);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Exclusions");
if (GUILayout.Button("+"))
{
exclusions.Add(null);
}
if (GUILayout.Button("-"))
{
if (exclusions.Count > 0)
exclusions.RemoveAt(exclusions.Count - 1);
}
EditorGUILayout.EndHorizontal();
for (int i = 0; i < exclusions.Count; i++)
{
exclusions[i] = EditorGUILayout.ObjectField(
"Object " + (i + 1),
exclusions[i],
typeof(GameObject),
true
) as GameObject;
}
guiAction();
}
private void guiAction()
{
EditorGUI.BeginDisabledGroup(targetObject == null);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Set Bounds"))
{
BoundsSetter(targetObject);
}
EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup();
}
private void BoundsSetter(GameObject parentObj)
{
var objs = GetAllChildrens(parentObj);
foreach (var obj in objs)
{
// 除外リストに含まれていれば処理しない
if (exclusions.Contains(obj)) continue;
var mesh = obj.GetComponent<MeshRenderer>();
var skinnedMesh = obj.GetComponent<SkinnedMeshRenderer>();
if (mesh == null && skinnedMesh == null) continue;
// Mesh Rendererの場合
if (mesh != null)
{
Undo.RecordObject(mesh, "Change Transform " + mesh.name);
}
// SkinnedMeshRendererの場合
else
{
Undo.RecordObject(skinnedMesh, "Change Transform " + skinnedMesh.name);
var objScale = skinnedMesh.gameObject.transform.localScale;
var meshBoundsScale = new Vector3(boundsScale.x / objScale.x, boundsScale.y / objScale.y, boundsScale.z / objScale.z);
skinnedMesh.localBounds = new Bounds(Vector3.zero, meshBoundsScale);
}
}
}
// 指定オブジェクトの子オブジェクト以降をすべて取得する
private List<GameObject> GetAllChildrens(GameObject parentObj)
{
List<GameObject> objs = new List<GameObject>();
var childTransform = parentObj.GetComponentsInChildren<Transform>();
foreach (Transform child in childTransform)
{
objs.Add(child.gameObject);
}
return objs;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 71aa7b7ed4050f44c84d3ba57de2304a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5d4e191026c25604d8a8f1d8554eca58
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,244 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
// ver 1.0
// © 2019-2-16 gatosyocora
namespace VRCDeveloperTool
{
public class ProbeAnchorSetter : EditorWindow
{
private GameObject targetObject = null;
public enum TARGETPOS
{
HEAD,
CHEST,
//ARMATURE,
ROOTOBJECT,
}
private TARGETPOS targetPos = TARGETPOS.HEAD;
private const string TARGETOBJNAME = "Anchor Target";
private bool isGettingSkinnedMeshRenderer = true;
private bool isGettingMeshRenderer = true;
private bool isOpeningRendererList = false;
private List<SkinnedMeshRenderer> skinnedMeshList;
private List<MeshRenderer> meshList;
private bool[] isSettingToSkinnedMesh = null;
private bool[] isSettingToMesh = null;
private Vector2 leftScrollPos = Vector2.zero;
[MenuItem("VRCDeveloperTool/ProbeAnchor Setter")]
private static void Create()
{
GetWindow<ProbeAnchorSetter>("ProbeAnchor Setter");
}
private void OnGUI()
{
EditorGUI.BeginChangeCheck();
targetObject = EditorGUILayout.ObjectField(
"TargetObject",
targetObject,
typeof(GameObject),
true
) as GameObject;
if (EditorGUI.EndChangeCheck())
{
if (targetObject != null)
{
skinnedMeshList = GetSkinnedMeshList(targetObject);
meshList = GetMeshList(targetObject);
isSettingToSkinnedMesh = new bool[skinnedMeshList.Count];
for (int i = 0; i < skinnedMeshList.Count; i++) isSettingToSkinnedMesh[i] = true;
isSettingToMesh = new bool[meshList.Count];
for (int i = 0; i < meshList.Count; i++) isSettingToMesh[i] = true;
}
}
// 設定するRendererの選択
isGettingSkinnedMeshRenderer = EditorGUILayout.Toggle("Set To SkinnedMeshRenderer", isGettingSkinnedMeshRenderer);
isGettingMeshRenderer = EditorGUILayout.Toggle("Set To MeshRenderer", isGettingMeshRenderer);
// ライティングの計算の基準とする位置を選択
targetPos = (TARGETPOS)EditorGUILayout.EnumPopup("TargetPosition", targetPos);
// Rendererの一覧を表示
if (targetObject != null)
{
isOpeningRendererList = EditorGUILayout.Foldout(isOpeningRendererList, "Renderer List");
if (isOpeningRendererList)
{
leftScrollPos = EditorGUILayout.BeginScrollView(leftScrollPos, GUI.skin.box);
{
EditorGUI.indentLevel++;
int index = 0;
if (isGettingSkinnedMeshRenderer)
{
foreach (var skinnedMesh in skinnedMeshList)
{
EditorGUILayout.BeginHorizontal();
isSettingToSkinnedMesh[index] = EditorGUILayout.Toggle(skinnedMesh.gameObject.name, isSettingToSkinnedMesh[index]);
if (GUILayout.Button("Select"))
Selection.activeGameObject = skinnedMesh.gameObject;
EditorGUILayout.EndHorizontal();
index++;
}
}
index = 0;
if (isGettingMeshRenderer)
{
foreach (var mesh in meshList)
{
EditorGUILayout.BeginHorizontal();
isSettingToMesh[index] = EditorGUILayout.Toggle(mesh.gameObject.name, isSettingToMesh[index]);
if (GUILayout.Button("Select"))
Selection.activeGameObject = mesh.gameObject;
EditorGUILayout.EndHorizontal();
index++;
}
}
EditorGUI.indentLevel--;
}
EditorGUILayout.EndScrollView();
}
}
EditorGUI.BeginDisabledGroup(targetObject == null);
if (GUILayout.Button("Set ProbeAnchor"))
{
SetProbeAnchor(targetObject);
}
EditorGUI.EndDisabledGroup();
}
/// <summary>
/// 特定のオブジェクト以下のRendererのProbeAnchorに設定する
/// </summary>
/// <param name="obj"></param>
private void SetProbeAnchor(GameObject obj)
{
var animator = obj.GetComponent<Animator>();
if (animator == null) return;
// AnchorTargetを設定する基準の場所を取得
Transform targetPosTrans = null;
if (targetPos == TARGETPOS.HEAD)
{
targetPosTrans = animator.GetBoneTransform(HumanBodyBones.Head);
}
else if (targetPos == TARGETPOS.CHEST)
{
targetPosTrans = animator.GetBoneTransform(HumanBodyBones.Chest);
}
/*else if (targetPos == TARGETPOS.ARMATURE)
{
var hipsTrans = animator.GetBoneTransform(HumanBodyBones.Hips);
targetPosTrans = hipsTrans.parent;
}*/
else if (targetPos == TARGETPOS.ROOTOBJECT)
{
targetPosTrans = obj.transform;
}
if (targetPosTrans == null) return;
// AnchorTargetに設定用のオブジェクトを作成
GameObject anchorTargetObj = GameObject.Find(obj.name + "/" + TARGETOBJNAME);
if (anchorTargetObj == null)
{
anchorTargetObj = new GameObject(TARGETOBJNAME);
anchorTargetObj.transform.parent = obj.transform;
}
anchorTargetObj.transform.position = targetPosTrans.position;
// SkiinedMeshRendererに設定
if (isGettingSkinnedMeshRenderer)
{
int index = 0;
var skinnedMeshes = skinnedMeshList;
foreach (var skinnedMesh in skinnedMeshes)
{
if (isSettingToSkinnedMesh[index++])
skinnedMesh.probeAnchor = anchorTargetObj.transform;
else
skinnedMesh.probeAnchor = null;
}
}
// MeshRendererに設定
if (isGettingMeshRenderer)
{
int index = 0;
var meshes = meshList;
foreach (var mesh in meshes)
{
if (isSettingToMesh[index++])
mesh.probeAnchor = anchorTargetObj.transform;
else
mesh.probeAnchor = null;
}
}
}
/// <summary>
/// 指定オブジェクト以下のSkinnedMeshRendererのリストを取得する
/// </summary>
/// <param name="parentObj">親オブジェクト</param>
/// <returns>SkinnedMeshRendererのリスト</returns>
private List<SkinnedMeshRenderer> GetSkinnedMeshList(GameObject parentObj)
{
var skinnedMeshList = new List<SkinnedMeshRenderer>();
var skinnedMeshes = parentObj.GetComponentsInChildren<SkinnedMeshRenderer>(true);
foreach (var skinnedMesh in skinnedMeshes)
{
skinnedMeshList.Add(skinnedMesh);
}
return skinnedMeshList;
}
/// <summary>
/// 指定オブジェクト以下のMeshRendererのリストを取得する
/// </summary>
/// <param name="parentObj">親オブジェクト</param>
/// <returns>MeshRendererのリスト</returns>
private List<MeshRenderer> GetMeshList(GameObject parentObj)
{
var meshList = new List<MeshRenderer>();
var meshes = parentObj.GetComponentsInChildren<MeshRenderer>(true);
foreach (var mesh in meshes)
{
meshList.Add(mesh);
}
return meshList;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d8a4080ea13a5749994d099d4a6149a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 394a8c1a4729a6742a4182a7922497fe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,165 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
using System.IO;
namespace VRCDeveloperTool
{
public class ShapeKeyDeleter : EditorWindow
{
private SkinnedMeshRenderer renderer;
private List<string> shapeKeyNames;
private bool[] selectedShapeKeys;
private bool isOpenedBlendShape = true;
private Vector2 shapeKeyScrollPos = Vector2.zero;
private int lastSelectedIndex = -1; // 마지막 선택된 인덱스 저장
[MenuItem("VRCDeveloperTool/Mesh/ShapeKey Deleter")]
private static void Open()
{
GetWindow<ShapeKeyDeleter>("ShapeKey Deleter");
}
private void OnEnable()
{
renderer = null;
shapeKeyNames = null;
selectedShapeKeys = null;
isOpenedBlendShape = true;
shapeKeyScrollPos = Vector2.zero;
}
private void OnGUI()
{
using (var check = new EditorGUI.ChangeCheckScope())
{
renderer = EditorGUILayout.ObjectField(
"SkinnedMeshRenderer",
renderer,
typeof(SkinnedMeshRenderer),
true
) as SkinnedMeshRenderer;
if (check.changed)
{
if (renderer != null)
{
shapeKeyNames = GetBlendShapeListFromRenderer(renderer);
selectedShapeKeys = new bool[shapeKeyNames.Count()];
}
}
}
if (shapeKeyNames != null)
{
isOpenedBlendShape = EditorGUILayout.Foldout(isOpenedBlendShape, "Shape Keys");
if (isOpenedBlendShape)
{
using (new EditorGUI.IndentLevelScope())
using (var scroll = new EditorGUILayout.ScrollViewScope(shapeKeyScrollPos, GUI.skin.box))
{
shapeKeyScrollPos = scroll.scrollPosition;
Event e = Event.current;
for (int i = 0; i < shapeKeyNames.Count(); i++)
{
bool originalValue = selectedShapeKeys[i];
bool newValue = EditorGUILayout.ToggleLeft(shapeKeyNames[i], selectedShapeKeys[i]);
// Shift 클릭 처리
if (e.shift && lastSelectedIndex != -1 && newValue != originalValue)
{
int startIndex = Mathf.Min(lastSelectedIndex, i);
int endIndex = Mathf.Max(lastSelectedIndex, i);
for (int j = startIndex; j <= endIndex; j++)
{
selectedShapeKeys[j] = true;
}
}
// 마지막 선택된 인덱스 갱신
if (newValue != originalValue)
{
lastSelectedIndex = i;
}
selectedShapeKeys[i] = newValue;
}
}
}
}
using (new EditorGUI.DisabledScope(renderer == null || (selectedShapeKeys != null && selectedShapeKeys.All(x => !x))))
{
if (GUILayout.Button("Delete ShapeKeys"))
{
var selectedBlendShapeIndexs = selectedShapeKeys
.Select((isSelect, index) => new { Index = index, Value = isSelect })
.Where(x => x.Value)
.Select(x => x.Index)
.ToArray();
DeleteShapeKey(renderer, selectedBlendShapeIndexs);
shapeKeyNames = GetBlendShapeListFromRenderer(renderer);
selectedShapeKeys = new bool[shapeKeyNames.Count()];
}
}
}
private bool DeleteShapeKey(SkinnedMeshRenderer renderer, int[] selectedShapeKeyIndexs)
{
var mesh = renderer.sharedMesh;
if (mesh == null) return false;
var mesh_custom = Instantiate(mesh);
mesh_custom.ClearBlendShapes();
int frameIndex = 0;
string shapeKeyName;
float weight;
Vector3[] deltaVertices, deltaNormals, deltaTangents;
for (int blendShapeIndex = 0; blendShapeIndex < mesh.blendShapeCount; blendShapeIndex++)
{
deltaVertices = new Vector3[mesh.vertexCount];
deltaNormals = new Vector3[mesh.vertexCount];
deltaTangents = new Vector3[mesh.vertexCount];
mesh.GetBlendShapeFrameVertices(blendShapeIndex, frameIndex, deltaVertices, deltaNormals, deltaTangents);
weight = mesh.GetBlendShapeFrameWeight(blendShapeIndex, frameIndex);
if (!selectedShapeKeyIndexs.Contains(blendShapeIndex))
{
shapeKeyName = mesh.GetBlendShapeName(blendShapeIndex);
mesh_custom.AddBlendShapeFrame(shapeKeyName, weight, deltaVertices, deltaNormals, deltaTangents);
}
}
Undo.RecordObject(renderer, "Renderer " + renderer.name);
renderer.sharedMesh = mesh_custom;
var path = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)) + "/" + mesh.name + "_shapekeydeleted.asset";
AssetDatabase.CreateAsset(mesh_custom, AssetDatabase.GenerateUniqueAssetPath(path));
AssetDatabase.SaveAssets();
return true;
}
private List<string> GetBlendShapeListFromRenderer(SkinnedMeshRenderer renderer)
{
List<string> shapeKeyNames = new List<string>();
var mesh = renderer.sharedMesh;
if (mesh != null)
for (int i = 0; i < mesh.blendShapeCount; i++)
shapeKeyNames.Add(mesh.GetBlendShapeName(i));
return shapeKeyNames;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a2159ac33f59084e8747b8b2a84b43b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 64cbfc8dbdbd8b44e9828fd5431afb69
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,186 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
// ver 1.0
// Copyright (c) 2019 gatosyocora
namespace VRCDeveloperTool
{
public class ShapeKeyMixer : EditorWindow
{
private SkinnedMeshRenderer renderer;
private List<string> shapeKeyNames;
private bool[] selectedShapeKeys;
private bool isOpenedBlendShape = true;
private string combinedShapeKeyName = "";
private bool deleteOriginShapeKey = true;
private Vector2 shapeKeyScrollPos = Vector2.zero;
[MenuItem("VRCDeveloperTool/Mesh/ShapeKey Mixer")]
private static void Open()
{
GetWindow<ShapeKeyMixer>("ShapeKey Mixer");
}
private void OnEnable()
{
renderer = null;
shapeKeyNames = null;
selectedShapeKeys = null;
isOpenedBlendShape = true;
combinedShapeKeyName = "";
shapeKeyScrollPos = Vector2.zero;
}
private void OnGUI()
{
using (var check = new EditorGUI.ChangeCheckScope())
{
renderer = EditorGUILayout.ObjectField(
"SkinnedMeshRenderer",
renderer,
typeof(SkinnedMeshRenderer),
true
) as SkinnedMeshRenderer;
if (check.changed)
{
if (renderer != null)
{
shapeKeyNames = GetBlendShapeListFromRenderer(renderer);
selectedShapeKeys = new bool[shapeKeyNames.Count()];
}
}
}
if (shapeKeyNames != null)
{
isOpenedBlendShape = EditorGUILayout.Foldout(isOpenedBlendShape, "Shape Keys");
if (isOpenedBlendShape)
{
using (new EditorGUI.IndentLevelScope())
using (var scroll = new EditorGUILayout.ScrollViewScope(shapeKeyScrollPos, GUI.skin.box))
{
shapeKeyScrollPos = scroll.scrollPosition;
for (int i = 0; i < shapeKeyNames.Count(); i++)
{
selectedShapeKeys[i] = EditorGUILayout.ToggleLeft(shapeKeyNames[i], selectedShapeKeys[i]);
}
}
}
deleteOriginShapeKey = EditorGUILayout.Toggle("Delete Origin ShapeKey", deleteOriginShapeKey);
combinedShapeKeyName = EditorGUILayout.TextField("Mixed ShapeKey Name", combinedShapeKeyName);
}
using (new EditorGUI.DisabledScope(renderer == null || combinedShapeKeyName == "" || (selectedShapeKeys != null && selectedShapeKeys.Sum(x => x ? 1 : 0) <= 1)))
{
if (GUILayout.Button("Mix ShapeKeys"))
{
// 2つ以上が選択されている
if (selectedShapeKeys.Sum(x => x ? 1 : 0) > 1)
{
// 選択されている要素のインデックスの配列
var selectedBlendShapeIndexs = selectedShapeKeys
.Select((isSelect, index) => new { Index = index, Value = isSelect })
.Where(x => x.Value)
.Select(x => x.Index)
.ToArray();
MixShapeKey(renderer, selectedBlendShapeIndexs, combinedShapeKeyName, deleteOriginShapeKey);
}
shapeKeyNames = GetBlendShapeListFromRenderer(renderer);
selectedShapeKeys = new bool[shapeKeyNames.Count()];
}
}
}
private bool MixShapeKey(SkinnedMeshRenderer renderer, int[] selectedShapeKeyIndexs, string combinedBlendShapeName, bool deleteOriginShapeKey)
{
var mesh = renderer.sharedMesh;
if (mesh == null) return false;
var mesh_custom = Instantiate(mesh);
mesh_custom.ClearBlendShapes();
int frameIndex = 0;
string shapeKeyName;
float weight;
Vector3[] deltaVertices, deltaNormals, deltaTangents;
var combinedDeltaVertices = new Vector3[mesh.vertexCount];
var combinedDeltaNormals = new Vector3[mesh.vertexCount];
var combinedDeltaTangents = new Vector3[mesh.vertexCount];
float combinedWeight = 0;
for (int blendShapeIndex = 0; blendShapeIndex < mesh.blendShapeCount; blendShapeIndex++)
{
deltaVertices = new Vector3[mesh.vertexCount];
deltaNormals = new Vector3[mesh.vertexCount];
deltaTangents = new Vector3[mesh.vertexCount];
mesh.GetBlendShapeFrameVertices(blendShapeIndex, frameIndex, deltaVertices, deltaNormals, deltaTangents);
weight = mesh.GetBlendShapeFrameWeight(blendShapeIndex, frameIndex);
if (selectedShapeKeyIndexs.Contains(blendShapeIndex))
{
for (int i = 0; i < mesh.vertexCount; i++)
{
combinedDeltaVertices[i] += deltaVertices[i];
combinedDeltaNormals[i] += deltaNormals[i];
combinedDeltaTangents[i] += deltaTangents[i];
combinedWeight = Mathf.Max(combinedWeight, weight);
}
if (!deleteOriginShapeKey)
{
shapeKeyName = mesh.GetBlendShapeName(blendShapeIndex);
mesh_custom.AddBlendShapeFrame(shapeKeyName, weight, deltaVertices, deltaNormals, deltaTangents);
}
}
else
{
shapeKeyName = mesh.GetBlendShapeName(blendShapeIndex);
mesh_custom.AddBlendShapeFrame(shapeKeyName, weight, deltaVertices, deltaNormals, deltaTangents);
}
}
if (selectedShapeKeyIndexs.Length > 0)
mesh_custom.AddBlendShapeFrame(combinedShapeKeyName, combinedWeight, combinedDeltaVertices, combinedDeltaNormals, combinedDeltaTangents);
Undo.RecordObject(renderer, "Renderer " + renderer.name);
renderer.sharedMesh = mesh_custom;
var path = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)) + "/" + mesh.name + "_custom.asset";
AssetDatabase.CreateAsset(mesh_custom, AssetDatabase.GenerateUniqueAssetPath(path));
AssetDatabase.SaveAssets();
return true;
}
/// <summary>
/// SkinnedMeshRendererのもつメッシュのシェイプキーの名前のリストを取得する
/// </summary>
/// <param name="renderer"></param>
/// <returns></returns>
private List<string> GetBlendShapeListFromRenderer(SkinnedMeshRenderer renderer)
{
List<string> shapeKeyNames = new List<string>();
var mesh = renderer.sharedMesh;
if (mesh != null)
for (int i = 0; i < mesh.blendShapeCount; i++)
shapeKeyNames.Add(mesh.GetBlendShapeName(i));
return shapeKeyNames;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9134ffb5949591d44911390f91211dcc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 359dd1b9f7cde6c43978974cd33a2a52
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 608b764bb9c5e1e428df13219c16514b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1ac7f172661b0a74299f8e583d5c7695
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 931bf2795a569b2478bc0b01f922683b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,269 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Linq;
using System;
namespace VRCDeveloperTool
{
public class ShapeKeyNameChanger : EditorWindow
{
private List<string> shapeKeyNames;
private SkinnedMeshRenderer renderer;
private string[] posNames;
private bool useDuplication = false;
private Vector2 scrollPos = Vector2.zero;
enum SelectType
{
Input,
Select
}
private SelectType selectTab = SelectType.Input;
public static GUIContent[] tabToggles
{
get
{
return System.Enum.GetNames(typeof(SelectType)).Select(x => new GUIContent(x)).ToArray();
}
}
[Serializable]
public class JsonData
{
public string[] ShapeKeyNames;
}
private string[] selectableNames;
private int[] selectedIndices;
private void OnEnable()
{
shapeKeyNames = null;
renderer = null;
posNames = null;
selectableNames = LoadJsonDataFromResourceFolder("ShapeKeyNameChanger/shapekeynames");
}
[MenuItem("VRCDeveloperTool/Mesh/ShapeKeyName Changer")]
private static void Create()
{
GetWindow<ShapeKeyNameChanger>("ShapeKeyName Changer");
}
private void OnGUI()
{
using (var check = new EditorGUI.ChangeCheckScope())
{
renderer = EditorGUILayout.ObjectField(
"Renderer",
renderer,
typeof(SkinnedMeshRenderer),
true
) as SkinnedMeshRenderer;
if (check.changed)
{
if (renderer == null)
{
shapeKeyNames = null;
posNames = null;
selectedIndices = null;
}
else
{
shapeKeyNames = GetBlendShapeListFromRenderer(renderer);
posNames = shapeKeyNames.ToArray();
selectedIndices = new int[shapeKeyNames.Count];
for (int i = 0; i < selectedIndices.Length; i++)
{
selectedIndices[i] = -1;
}
}
}
}
selectTab = (SelectType)GUILayout.Toolbar((int)selectTab, tabToggles, "LargeButton", GUI.ToolbarButtonSize.Fixed);
if (shapeKeyNames != null)
{
using (var pos = new GUILayout.ScrollViewScope(scrollPos))
{
scrollPos = pos.scrollPosition;
if (selectTab == SelectType.Input)
{
for (int i = 0; i < shapeKeyNames.Count; i++)
{
using (new EditorGUILayout.HorizontalScope())
{
using (var toggle = new EditorGUI.ChangeCheckScope())
{
EditorGUILayout.Toggle(shapeKeyNames[i] != posNames[i], GUILayout.Width(30));
if (toggle.changed && shapeKeyNames[i] != posNames[i])
{
posNames[i] = shapeKeyNames[i];
selectedIndices[i] = -1;
}
}
posNames[i] = EditorGUILayout.TextField(shapeKeyNames[i], posNames[i]);
}
}
}
else
{
for (int i = 0; i < shapeKeyNames.Count; i++)
{
using (new EditorGUILayout.HorizontalScope())
{
using (var toggle = new EditorGUI.ChangeCheckScope())
{
EditorGUILayout.Toggle(shapeKeyNames[i] != posNames[i], GUILayout.Width(30));
if (toggle.changed && shapeKeyNames[i] != posNames[i])
{
posNames[i] = shapeKeyNames[i];
selectedIndices[i] = -1;
}
}
using (var check = new EditorGUI.ChangeCheckScope())
{
selectedIndices[i] = EditorGUILayout.Popup(shapeKeyNames[i], selectedIndices[i], selectableNames);
if (check.changed && selectedIndices[i] != -1)
posNames[i] = selectableNames[selectedIndices[i]];
}
}
}
}
}
}
using (new EditorGUI.DisabledScope(renderer == null))
{
useDuplication = EditorGUILayout.Toggle("Duplication ShapeKeys", useDuplication);
// 대문자 -> 소문자 변환 버튼
if (GUILayout.Button("Convert First Letter to Lowercase"))
{
ConvertFirstLetterToLowercase();
}
// 소문자 -> 대문자 변환 버튼
if (GUILayout.Button("Convert First Letter to Uppercase"))
{
ConvertFirstLetterToUppercase();
}
if (GUILayout.Button("Change ShapeKeyName"))
{
CreateNewShapeKeyNameMesh(renderer, posNames, useDuplication, shapeKeyNames);
shapeKeyNames = GetBlendShapeListFromRenderer(renderer);
posNames = shapeKeyNames.ToArray();
selectedIndices = new int[shapeKeyNames.Count];
for (int i = 0; i < selectedIndices.Length; i++)
{
selectedIndices[i] = -1;
}
}
}
}
// 첫 글자를 소문자로 변환하는 함수
private void ConvertFirstLetterToLowercase()
{
for (int i = 0; i < posNames.Length; i++)
{
if (!char.IsLower(posNames[i][0]))
{
posNames[i] = char.ToLower(posNames[i][0]) + posNames[i].Substring(1);
}
}
}
// 첫 글자를 대문자로 변환하는 함수
private void ConvertFirstLetterToUppercase()
{
for (int i = 0; i < posNames.Length; i++)
{
if (!char.IsUpper(posNames[i][0]))
{
posNames[i] = char.ToUpper(posNames[i][0]) + posNames[i].Substring(1);
}
}
}
private bool CreateNewShapeKeyNameMesh(SkinnedMeshRenderer renderer, string[] posShapeKeyNames, bool useDuplication, List<string> preShapeKeyNames)
{
var mesh = renderer.sharedMesh;
if (mesh == null) return false;
if (posShapeKeyNames.Length != mesh.blendShapeCount) return false;
var mesh_custom = UnityEngine.Object.Instantiate<Mesh>(mesh);
mesh_custom.ClearBlendShapes();
var frameIndex = 0;
var shapeKeyName = string.Empty;
Vector3[] deltaVertices, deltaNormals, deltaTangents;
for (int blendShapeIndex = 0; blendShapeIndex < mesh.blendShapeCount; blendShapeIndex++)
{
deltaVertices = new Vector3[mesh.vertexCount];
deltaNormals = new Vector3[mesh.vertexCount];
deltaTangents = new Vector3[mesh.vertexCount];
mesh.GetBlendShapeFrameVertices(blendShapeIndex, frameIndex, deltaVertices, deltaNormals, deltaTangents);
var weight = mesh.GetBlendShapeFrameWeight(blendShapeIndex, frameIndex);
shapeKeyName = posNames[blendShapeIndex];
if (useDuplication && !preShapeKeyNames[blendShapeIndex].Equals(shapeKeyName))
{
mesh_custom.AddBlendShapeFrame(preShapeKeyNames[blendShapeIndex], weight, deltaVertices, deltaNormals, deltaTangents);
}
mesh_custom.AddBlendShapeFrame(shapeKeyName, weight, deltaVertices, deltaNormals, deltaTangents);
}
Undo.RecordObject(renderer, "Renderer " + renderer.name);
renderer.sharedMesh = mesh_custom;
var path = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)) + "/" + mesh.name + "_custom.asset";
AssetDatabase.CreateAsset(mesh_custom, AssetDatabase.GenerateUniqueAssetPath(path));
AssetDatabase.SaveAssets();
return true;
}
private List<string> GetBlendShapeListFromRenderer(SkinnedMeshRenderer renderer)
{
List<string> shapeKeyNames = new List<string>();
var mesh = renderer.sharedMesh;
if (mesh != null)
for (int i = 0; i < mesh.blendShapeCount; i++)
shapeKeyNames.Add(mesh.GetBlendShapeName(i));
return shapeKeyNames;
}
private string[] LoadJsonDataFromResourceFolder(string path)
{
var textAsset = Resources.Load<TextAsset>(path);
var jsonData = JsonUtility.FromJson<JsonData>(textAsset.ToString());
return jsonData.ShapeKeyNames;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e5e446425695a2428c640bf776865be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 91e09be55fd184f469aadf4c0caf5f89
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,237 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using UnityEditorInternal;
using System.IO;
// ver 1.00
// (C) 2019 gatosyocora
namespace VRCDeveloperTool
{
public class ShapeKeyReorder : EditorWindow
{
private ReorderableList blendShapeReorderableList;
private SkinnedMeshRenderer renderer;
private Vector2 scrollPos = Vector2.zero;
public class BlendShape
{
public int index;
public string name;
public BlendShape(int index, string name)
{
this.index = index;
this.name = name;
}
}
[MenuItem("VRCDeveloperTool/Mesh/ShapeKey Reorder")]
private static void Open()
{
GetWindow<ShapeKeyReorder>("ShapeKey Reorder");
}
private void OnEnable()
{
renderer = null;
blendShapeReorderableList = null;
}
private void OnGUI()
{
using (var check = new EditorGUI.ChangeCheckScope())
{
renderer = EditorGUILayout.ObjectField(
"Renderer",
renderer,
typeof(SkinnedMeshRenderer),
true
) as SkinnedMeshRenderer;
if (check.changed)
{
if (renderer != null)
{
var blendShapePairList = new List<BlendShape>();
var mesh = renderer.sharedMesh;
var blendShapeCount = mesh.blendShapeCount;
for (int i = 0; i < blendShapeCount; i++)
{
blendShapePairList.Add(new BlendShape(i, mesh.GetBlendShapeName(i)));
}
blendShapeReorderableList = InitializeReorderableList<BlendShape>(blendShapePairList);
}
else
{
blendShapeReorderableList = null;
}
}
}
if (blendShapeReorderableList != null)
{
using (var scroll = new EditorGUILayout.ScrollViewScope(scrollPos))
{
scrollPos = scroll.scrollPosition;
blendShapeReorderableList.DoLayoutList();
}
}
using (new EditorGUI.DisabledGroupScope(renderer == null))
{
EditorGUILayout.LabelField("Auto Sort");
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("UnSort"))
{
blendShapeReorderableList.list
= blendShapeReorderableList.list
.Cast<BlendShape>()
.OrderBy(x => x.index)
.ToList();
}
if (GUILayout.Button("VRChat Default"))
{
blendShapeReorderableList.list
= SortByVRChatDefault(
blendShapeReorderableList.list as List<BlendShape>
);
}
if (GUILayout.Button("A-Z"))
{
blendShapeReorderableList.list
= blendShapeReorderableList.list
.Cast<BlendShape>()
.OrderBy(x => x.name)
.ToList();
}
if (GUILayout.Button("Z-A"))
{
blendShapeReorderableList.list
= blendShapeReorderableList.list
.Cast<BlendShape>()
.OrderByDescending(x => x.name)
.ToList();
}
}
EditorGUILayout.Space();
if (GUILayout.Button("Change ShapeKey order"))
{
CreateNewShapeKeyNameMesh(renderer, blendShapeReorderableList.list as List<BlendShape>);
}
}
}
/// <summary>
/// シェイプキーの順番を変えたメッシュを作成し, SkinnedMeshRendererに設定する
/// </summary>
/// <param name="renderer">シェイプキーの順番を変更したいメッシュを持つSkinnedMeshRenderer</param>
/// <param name="reorderdBlendShapeList">変更後のシェイプキーの名称のリスト</param>
/// <returns></returns>
private bool CreateNewShapeKeyNameMesh(SkinnedMeshRenderer renderer, List<BlendShape> reorderdBlendShapeList)
{
var mesh = renderer.sharedMesh;
if (mesh == null) return false;
if (reorderdBlendShapeList.Count != mesh.blendShapeCount) return false;
var mesh_custom = Object.Instantiate<Mesh>(mesh);
mesh_custom.ClearBlendShapes();
var blendShapeIndex = 0;
var frameIndex = 0;
var shapeKeyName = "";
Vector3[] deltaVertices, deltaNormals, deltaTangents;
for (int i = 0; i < reorderdBlendShapeList.Count; i++)
{
deltaVertices = new Vector3[mesh.vertexCount];
deltaNormals = new Vector3[mesh.vertexCount];
deltaTangents = new Vector3[mesh.vertexCount];
blendShapeIndex = reorderdBlendShapeList[i].index;
mesh.GetBlendShapeFrameVertices(blendShapeIndex, frameIndex, deltaVertices, deltaNormals, deltaTangents);
var weight = mesh.GetBlendShapeFrameWeight(blendShapeIndex, frameIndex);
shapeKeyName = reorderdBlendShapeList[i].name;
mesh_custom.AddBlendShapeFrame(shapeKeyName, weight, deltaVertices, deltaNormals, deltaTangents);
}
Undo.RecordObject(renderer, "Renderer " + renderer.name);
renderer.sharedMesh = mesh_custom;
var path = Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh)) + "/" + mesh.name + "_reorderd.asset";
AssetDatabase.CreateAsset(mesh_custom, AssetDatabase.GenerateUniqueAssetPath(path));
AssetDatabase.SaveAssets();
return true;
}
/// <summary>
/// シェイプキーの順番をVRChat標準のものに変更する
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
private List<BlendShape> SortByVRChatDefault(List<BlendShape> list)
{
var vrcBlendShapes
= new string[]{
"vrc.blink_left",
"vrc.blink_right",
"vrc.lowerlid_left",
"vrc.lowerlid_right",
};
var newList = new List<BlendShape>();
for (int i = 0; i < vrcBlendShapes.Length; i++)
{
var index = list.Select(x => x.name)
.ToList()
.IndexOf(vrcBlendShapes[i]);
if (index == -1) continue;
var blendShape = list[index];
list.RemoveAt(index);
newList.Add(blendShape);
}
newList.AddRange(list);
return newList;
}
/// <summary>
/// ReorderableListを作成する
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
private ReorderableList InitializeReorderableList<T>(List<T> list)
{
var reorderbleList = new ReorderableList(list, typeof(T));
reorderbleList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "BlendShape");
reorderbleList.drawElementCallback = (rect, index, isActive, isFoused) =>
{
var item = reorderbleList.list[index] as BlendShape;
rect.height = EditorGUIUtility.singleLineHeight;
EditorGUI.LabelField(rect, item.name);
};
reorderbleList.displayAdd = false;
reorderbleList.displayRemove = false;
return reorderbleList;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ed3c5d0ba2f70545b6b5ae60e0ab634
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f87b301891c2eab4782b10287033b78b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,299 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
// ver 1.0.1
// Copyright (c) 2019 gatosyocora
namespace VRCDeveloperTool
{
public class SubMeshDeleter : EditorWindow
{
private SkinnedMeshRenderer renderer;
private List<SubMeshInfo> subMeshList;
private int triangleCount = 0;
private string saveFolder = "Assets/";
private bool isOpenedSubMesh = true;
private Vector2 subMeshScrollPos = Vector2.zero;
[MenuItem("VRCDeveloperTool/Mesh/SubMesh Deleter")]
private static void Open()
{
GetWindow<SubMeshDeleter>("SubMeshDeleter");
}
private void OnEnable()
{
renderer = null;
subMeshList = null;
triangleCount = 0;
}
private void OnGUI()
{
using (var check = new EditorGUI.ChangeCheckScope())
{
renderer = EditorGUILayout.ObjectField(
"SkinnedMeshRenderer",
renderer,
typeof(SkinnedMeshRenderer),
true
) as SkinnedMeshRenderer;
if (check.changed)
{
if (renderer != null)
{
var mesh = renderer.sharedMesh;
if (mesh != null)
{
subMeshList = GetSubMeshList(mesh);
triangleCount = GetMeshTriangleCount(mesh);
saveFolder = GetMeshPath(mesh);
}
}
else
{
subMeshList = null;
}
}
}
if (subMeshList != null)
{
isOpenedSubMesh = EditorGUILayout.Foldout(isOpenedSubMesh, "SubMesh");
if (isOpenedSubMesh)
{
using (var scroll = new EditorGUILayout.ScrollViewScope(subMeshScrollPos))
{
subMeshScrollPos = scroll.scrollPosition;
for (int i = 0; i < subMeshList.Count(); i++)
{
subMeshList[i].selected = EditorGUILayout.ToggleLeft("subMesh " + (i + 1) + "(" + renderer.sharedMaterials[i].name + "):" + subMeshList[i].triangleCount, subMeshList[i].selected);
}
}
}
}
EditorGUILayout.LabelField("Triangle Count", triangleCount+"");
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Mesh SaveFolder", saveFolder);
if (GUILayout.Button("Select Folder", GUILayout.Width(100)))
{
saveFolder = EditorUtility.OpenFolderPanel("Select saved folder", saveFolder, "");
var match = Regex.Match(saveFolder, @"Assets/.*");
saveFolder = match.Value + "/";
if (saveFolder == "/") saveFolder = "Assets/";
}
}
using (new EditorGUI.DisabledGroupScope(subMeshList == null || subMeshList.Count() <= 1))
{
if (GUILayout.Button("Delete SubMesh"))
{
DeleteSelectedSubMesh(renderer, subMeshList);
var mesh = renderer.sharedMesh;
if (mesh != null)
{
subMeshList = GetSubMeshList(mesh);
triangleCount = GetMeshTriangleCount(mesh);
}
}
}
}
/// <summary>
/// 選択中のサブメッシュを削除する
/// </summary>
/// <param name="renderer"></param>
/// <param name="subMeshList"></param>
/// <returns></returns>
private bool DeleteSelectedSubMesh(SkinnedMeshRenderer renderer, List<SubMeshInfo> subMeshList)
{
// 削除する頂点インデックスのリスト(読み取り専用, 降順)
var deleteVerticesIndicesUniqueDescending
= subMeshList
.Where(x => x.selected)
.SelectMany(x => x.verticesIndices)
.Distinct()
.OrderByDescending(x => x)
.ToList()
.AsReadOnly();
// 削除するサブメッシュのインデックスのリスト
var deleteSubMeshIndexList
= subMeshList
.Select((value, index) => new { Value = value, Index = index })
.Where(x => x.Value.selected)
.Select(x => x.Index)
.ToList()
.AsReadOnly();
var mesh = renderer.sharedMesh;
var mesh_custom = Instantiate(mesh);
mesh_custom.Clear();
// 頂点を削除
var vertices = mesh.vertices.ToList();
var boneWeights = mesh.boneWeights.ToList();
var uvs = mesh.uv.ToList();
var normals = mesh.normals.ToList();
var tangents = mesh.tangents.ToList();
var uv2s = mesh.uv2.ToList();
var uv3s = mesh.uv3.ToList();
var uv4s = mesh.uv4.ToList();
foreach (var deleteVertexIndex in deleteVerticesIndicesUniqueDescending)
{
vertices.RemoveAt(deleteVertexIndex);
boneWeights.RemoveAt(deleteVertexIndex);
normals.RemoveAt(deleteVertexIndex);
tangents.RemoveAt(deleteVertexIndex);
if (deleteVertexIndex < uvs.Count())
uvs.RemoveAt(deleteVertexIndex);
if (deleteVertexIndex < uv2s.Count())
uv2s.RemoveAt(deleteVertexIndex);
if (deleteVertexIndex < uv3s.Count())
uv3s.RemoveAt(deleteVertexIndex);
if (deleteVertexIndex < uv4s.Count())
uv4s.RemoveAt(deleteVertexIndex);
}
mesh_custom.SetVertices(vertices);
mesh_custom.boneWeights = boneWeights.ToArray();
mesh_custom.normals = normals.ToArray();
mesh_custom.tangents = tangents.ToArray();
mesh_custom.SetUVs(0, uvs);
mesh_custom.SetUVs(1, uv2s);
mesh_custom.SetUVs(2, uv3s);
mesh_custom.SetUVs(3, uv4s);
// サブメッシュごとにポリゴンを処理
mesh_custom.subMeshCount = mesh.subMeshCount - deleteSubMeshIndexList.Count();
var subMeshNumber = 0;
for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
{
if (deleteSubMeshIndexList.Contains(subMeshIndex)) continue;
var subMeshTriangles = mesh.GetTriangles(subMeshIndex);
// インデックスがずれるので各頂点への対応付けが必要
// インデックスが大きいものから順に処理していく
// O(n*m)
foreach (var deleteVerticesIndex in deleteVerticesIndicesUniqueDescending) // n
for (int i = 0; i < subMeshTriangles.Count(); i++) // m
if (subMeshTriangles[i] > deleteVerticesIndex)
subMeshTriangles[i]--;
mesh_custom.SetTriangles(subMeshTriangles, subMeshNumber++);
}
// BlendShapeを設定する
string blendShapeName;
float frameWeight;
var deltaVertices = new Vector3[mesh.vertexCount];
var deltaNormals = new Vector3[mesh.vertexCount];
var deltaTangents = new Vector3[mesh.vertexCount];
List<Vector3> deltaVerticesList, deltaNormalsList, deltaTangentsList;
for (int blendshapeIndex = 0; blendshapeIndex < mesh.blendShapeCount; blendshapeIndex++)
{
blendShapeName = mesh.GetBlendShapeName(blendshapeIndex);
frameWeight = mesh.GetBlendShapeFrameWeight(blendshapeIndex, 0);
mesh.GetBlendShapeFrameVertices(blendshapeIndex, 0, deltaVertices, deltaNormals, deltaTangents);
deltaVerticesList = deltaVertices.ToList();
deltaNormalsList = deltaNormals.ToList();
deltaTangentsList = deltaTangents.ToList();
foreach (var deleteVertexIndex in deleteVerticesIndicesUniqueDescending)
{
deltaVerticesList.RemoveAt(deleteVertexIndex);
deltaNormalsList.RemoveAt(deleteVertexIndex);
deltaTangentsList.RemoveAt(deleteVertexIndex);
}
mesh_custom.AddBlendShapeFrame(blendShapeName, frameWeight,
deltaVerticesList.ToArray(),
deltaNormalsList.ToArray(),
deltaTangentsList.ToArray());
}
AssetDatabase.CreateAsset(mesh_custom, AssetDatabase.GenerateUniqueAssetPath(saveFolder + mesh.name + "_deleteSubmesh.asset"));
AssetDatabase.SaveAssets();
Undo.RecordObject(renderer, "Change mesh " + mesh_custom.name);
renderer.sharedMesh = mesh_custom;
// 削除したサブメッシュのマテリアルを参照から外す
var materials = renderer.sharedMaterials.ToList();
for (var index = materials.Count() - 1; index >= 0; index--)
if (deleteSubMeshIndexList.Contains(index))
materials.RemoveAt(index);
renderer.sharedMaterials = materials.ToArray();
return true;
}
/// <summary>
/// サブメッシュのリストを取得する
/// </summary>
/// <param name="mesh"></param>
/// <returns></returns>
private List<SubMeshInfo> GetSubMeshList(Mesh mesh)
{
List<SubMeshInfo> subMeshList = new List<SubMeshInfo>();
for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
{
var meshInfo = new SubMeshInfo(mesh, subMeshIndex);
subMeshList.Add(meshInfo);
}
return subMeshList;
}
/// <summary>
/// Meshのポリゴン数を取得する
/// </summary>
/// <param name="mesh"></param>
/// <returns></returns>
private int GetMeshTriangleCount(Mesh mesh)
{
return mesh.triangles.Length / 3;
}
/// <summary>
/// mesh保存先のパスを取得する
/// </summary>
/// <param name="Mesh"></param>
/// <returns></returns>
private string GetMeshPath(Mesh mesh)
{
return Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh))+"/";
}
public class SubMeshInfo
{
public int subMeshIndex;
public int[] verticesIndices;
public int vertexCount;
public int triangleCount;
public bool selected = false;
public SubMeshInfo(Mesh mesh, int subMeshIndex)
{
this.subMeshIndex = subMeshIndex;
this.verticesIndices = mesh.GetIndices(subMeshIndex);
vertexCount = verticesIndices.Length;
triangleCount = mesh.GetTriangles(subMeshIndex).Length / 3;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 39615a0c8ecb8ba48b303c82d3524389
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 27ecb87b1ac306e4e840689971bda2a7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,199 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Linq;
// MIT License
/*
* Copyright 2020 gatosyocora
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace VRCDeveloperTool
{
public class GatoEditorUtility
{
private const char BSLASH = '\\';
/// <summary>
/// インデントなしのHelpbox
/// </summary>
/// <param name="message"></param>
/// <param name="messageType"></param>
public static void NonIndentHelpBox(string message, MessageType messageType)
{
var currentIndentLevel = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
EditorGUILayout.HelpBox(message, messageType);
EditorGUI.indentLevel = currentIndentLevel;
}
/// <summary>
/// インデントなしのButton
/// </summary>
/// <param name="text"></param>
/// <param name="action"></param>
public static void NonIndentButton(string text, Action action)
{
var currentIndentLevel = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
if (GUILayout.Button(text))
{
action.Invoke();
}
EditorGUI.indentLevel = currentIndentLevel;
}
/// <summary>
/// パス内で存在しないフォルダを作成する
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool CreateNoExistFolders(string path)
{
string directoryPath;
if (string.IsNullOrEmpty(Path.GetExtension(path)))
{
directoryPath = path;
}
else
{
directoryPath = Path.GetDirectoryName(path);
}
if (!Directory.Exists(directoryPath))
{
var directories = directoryPath.Split(BSLASH);
directoryPath = "Assets";
for (int i = 1; i < directories.Length; i++)
{
if (!Directory.Exists(directoryPath + BSLASH + directories[i]))
{
AssetDatabase.CreateFolder(directoryPath, directories[i]);
}
directoryPath += BSLASH + directories[i];
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
return true;
}
return false;
}
/// <summary>
/// 任意のアセットを複製する
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="newAssetName"></param>
/// <param name="saveFolderPath"></param>
/// <returns></returns>
public static T DuplicateAsset<T>(T source, string newAssetPath) where T : UnityEngine.Object
{
var sourcePath = AssetDatabase.GetAssetPath(source);
return DuplicateAsset<T>(sourcePath, newAssetPath);
}
public static T DuplicateAsset<T>(string sourcePath, string newAssetPath) where T : UnityEngine.Object
{
var newFolderPath = Path.GetDirectoryName(newAssetPath);
CreateNoExistFolders(newFolderPath);
var newPath = AssetDatabase.GenerateUniqueAssetPath(newAssetPath);
AssetDatabase.CopyAsset(sourcePath, newPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
var newAsset = AssetDatabase.LoadAssetAtPath(newPath, typeof(T)) as T;
return newAsset;
}
/// <summary>
/// 最後にキーワードを追加する(重複なし)
/// </summary>
/// <param name="target"></param>
/// <param name="keyword"></param>
/// <returns></returns>
public static string AddKeywordToEnd(string target, string keyword)
{
if (string.IsNullOrEmpty(keyword)) return target;
var normalString = Regex.Replace(target, keyword + ".*", string.Empty);
return normalString + keyword;
}
/// <summary>
/// 特定のオブジェクトから特定のオブジェクトまでのパスを取得する
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static string GetHierarchyPathFromObj1ToObj2(GameObject obj1, GameObject obj2)
{
string path = obj2.name;
var parent = obj2.transform.parent;
while (parent != null)
{
if (parent.gameObject.name == obj1.name) return path;
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
/// <summary>
/// 特定のオブジェクトまでのパスを取得する
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static string GetHierarchyPath(GameObject obj)
{
string path = obj.name;
Transform parent = obj.transform.parent;
while (parent != null)
{
if (parent.parent == null) return path;
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
/// <summary>
/// フォルダ名からフォルダパスを取得する
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static string GetFolderPathFromName(string folderName)
{
var guid = AssetDatabase.FindAssets(folderName + " t:Folder").FirstOrDefault();
return AssetDatabase.GUIDToAssetPath(guid);
}
/// <summary>
/// 複製された2つのオブジェクト間で片方の特定のTransformに対応したTransformを取得する
/// </summary>
/// <param name="source"></param>
/// <param name="duplicated"></param>
/// <param name="target"></param>
/// <returns></returns>
public static Transform GetCorrespondTransformBetweenDuplicatedObjects(GameObject source, GameObject duplicated, Transform target)
{
if (source.transform == target) return duplicated.transform;
var path = GetHierarchyPathFromObj1ToObj2(source, target.gameObject);
return duplicated.transform.Find(path);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea831e1b44c1f2e40a8b07d7fcad1be8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1d61406e20153444092677a33f354983
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,37 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.IO;
// ver 1.0
// Copyright (c) 2020 gatosyocora
namespace VRCDeveloperTool
{
public class VRCAssetCreator : Editor
{
#if VRC_SDK_VRCSDK2
[MenuItem("Assets/Create/VRChat/CustomOverrideController", priority = 0)]
public static void CreateVRCCustomOverrideController()
{
var outputFolderPath = AssetDatabase.GetAssetPath(Selection.activeInstanceID);
var originalGuid = AssetDatabase.FindAssets("CustomOverrideEmpty").First();
var originalPath = AssetDatabase.GUIDToAssetPath(originalGuid);
var outputPath = outputFolderPath + "/" + Path.GetFileName(originalPath);
DuplicateAsset(originalPath, outputPath);
}
#endif
private static void DuplicateAsset(string originalPath, string outputPath)
{
outputPath = AssetDatabase.GenerateUniqueAssetPath(outputPath);
AssetDatabase.CopyAsset(originalPath, outputPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99d09038281ca144e87244fd8ff48e42
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7ebf40d759e151f4a86f46b6de8580cf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d8e14459547668344b92d23570c122e5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a93d8c7d5fa23224db2d4765580f70d9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 595f725aaeb14c74ebbc662992c556b6
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,376 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Threading.Tasks;
using System;
using UnityEditor.Animations;
using UnityEditor.SceneManagement;
#if VRC_SDK_VRCSDK2
using VRCSDK2;
#endif
// ver 1.0
// Copyright (c) 2020 gatosyocora
namespace VRCDeveloperTool
{
public class VRCAvatarTester : EditorWindow
{
#if VRC_SDK_VRCSDK2
private VRC_AvatarDescriptor avatar;
#endif
[SerializeField]
private Animator animator;
[SerializeField]
private AnimatorOverrideController controller;
[SerializeField]
private RuntimeAnimatorController defaultController;
public enum PlayingType
{
NONE, OVERRIDE, EMOTE
};
private PlayingType playingType = PlayingType.NONE;
public enum PlayingHand
{
NONE, RIGHT, LEFT, BOTH
};
private PlayingHand playingHand = PlayingHand.NONE;
private static readonly string[] OVERRIDES = new string[]
{
"FIST", "HANDOPEN", "FINGERPOINT", "VICTORY", "ROCKNROLL", "HANDGUN", "THUMBSUP"
};
private static readonly string[] EMOTES = new string[]
{
"EMOTE1", "EMOTE2", "EMOTE3", "EMOTE4", "EMOTE5", "EMOTE6", "EMOTE7", "EMOTE8"
};
[SerializeField]
private GameObject poseConstraintObj;
private PoseConstraint poseConstraint;
[MenuItem("VRCDeveloperTool/VRCAvatarTester")]
public static void Open()
{
GetWindow<VRCAvatarTester>(nameof(VRCAvatarTester));
}
private void Update()
{
#if VRC_SDK_VRCSDK2
// 再生中
if (EditorApplication.isPlayingOrWillChangePlaymode)
{
// 毎回取得しないとActiveへの変更がなぜか適用されない
poseConstraint = poseConstraintObj.GetComponent<PoseConstraint>();
animator.runtimeAnimatorController = controller;
if (playingType == PlayingType.OVERRIDE)
{
poseConstraint.Active = true;
animator.SetInteger($"Emote", 0);
switch (playingHand)
{
case PlayingHand.NONE:
animator.SetLayerWeight(animator.GetLayerIndex("HandLeft"), 0);
animator.SetLayerWeight(animator.GetLayerIndex("HandRight"), 0);
break;
case PlayingHand.RIGHT:
animator.SetLayerWeight(animator.GetLayerIndex("HandLeft"), 0);
animator.SetLayerWeight(animator.GetLayerIndex("HandRight"), 1);
break;
case PlayingHand.LEFT:
animator.SetLayerWeight(animator.GetLayerIndex("HandLeft"), 1);
animator.SetLayerWeight(animator.GetLayerIndex("HandRight"), 0);
break;
case PlayingHand.BOTH:
animator.SetLayerWeight(animator.GetLayerIndex("HandLeft"), 1);
animator.SetLayerWeight(animator.GetLayerIndex("HandRight"), 1);
break;
default:
break;
}
}
else if (playingType == PlayingType.EMOTE)
{
poseConstraint.Active = false;
animator.SetLayerWeight(animator.GetLayerIndex("HandLeft"), 0);
animator.SetLayerWeight(animator.GetLayerIndex("HandRight"), 0);
}
else
{
poseConstraint.Active = true;
animator.SetLayerWeight(animator.GetLayerIndex("HandLeft"), 0);
animator.SetLayerWeight(animator.GetLayerIndex("HandRight"), 0);
}
}
// 未再生
else
{
if (animator != null && controller != null)
{
animator.runtimeAnimatorController = defaultController;
animator.SetLayerWeight(animator.GetLayerIndex("HandLeft"), 0);
animator.SetLayerWeight(animator.GetLayerIndex("HandRight"), 0);
}
}
#endif
}
private void OnGUI()
{
#if VRC_SDK_VRCSDK2
using (var check = new EditorGUI.ChangeCheckScope())
{
avatar = EditorGUILayout.ObjectField("Avatar", avatar, typeof(VRC_AvatarDescriptor), true) as VRC_AvatarDescriptor;
if (check.changed)
{
if (avatar != null)
{
animator = avatar.gameObject.GetComponent<Animator>();
controller = avatar.CustomStandingAnims;
}
else
{
animator = null;
controller = null;
}
}
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Camera", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Scene View"))
{
EditorApplication.ExecuteMenuItem("Window/General/Scene");
}
if (GUILayout.Button("Game View"))
{
EditorApplication.ExecuteMenuItem("Window/General/Game");
}
}
/*
using (new EditorGUI.DisabledGroupScope(animator is null))
{
if (GUILayout.Button("Focus on Avatar"))
{
var sceneViewCamera = SceneView.lastActiveSceneView.camera;
sceneViewCamera.transform.position = animator.transform.position;
}
}
*/
GUILayout.Space(15);
EditorGUILayout.LabelField("Testing", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
using (new EditorGUI.DisabledGroupScope(EditorApplication.isPlayingOrWillChangePlaymode || avatar == null))
{
if (GUILayout.Button("Play"))
{
defaultController = animator.runtimeAnimatorController;
animator.runtimeAnimatorController = controller;
poseConstraintObj = CreatePoseConstrainterToRootIfNeeded();
poseConstraint = poseConstraintObj.GetComponent<PoseConstraint>();
poseConstraint.UpdateBoneInfo(animator);
EditorApplication.isPlaying = true;
}
}
using (new EditorGUI.DisabledGroupScope(!EditorApplication.isPlayingOrWillChangePlaymode))
{
if (GUILayout.Button("Stop"))
{
EditorApplication.isPlaying = false;
animator.runtimeAnimatorController = defaultController;
}
}
}
if (avatar == null && !EditorApplication.isPlaying)
{
EditorGUILayout.HelpBox("Avatarを設定してください", MessageType.Error);
}
if (avatar != null && animator != null && controller != null)
{
EditorGUILayout.HelpBox("Playを選択するとテストが実行できます", MessageType.Info);
}
EditorGUILayout.Space();
using (new EditorGUI.DisabledGroupScope(!EditorApplication.isPlayingOrWillChangePlaymode))
{
if (GUILayout.Button("Reset All"))
{
playingType = PlayingType.NONE;
playingHand = PlayingHand.NONE;
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("AnimationOverrides", EditorStyles.boldLabel);
using (new EditorGUI.IndentLevelScope())
{
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("NONE");
if (GUILayout.Button("Left"))
{
if (playingType == PlayingType.OVERRIDE &&
playingHand == PlayingHand.BOTH)
{
playingHand = PlayingHand.RIGHT;
}
else
{
playingType = PlayingType.NONE;
}
PlayOverride("Left", 0, animator);
}
if (GUILayout.Button("Right"))
{
if (playingType == PlayingType.OVERRIDE &&
playingHand == PlayingHand.BOTH)
{
playingHand = PlayingHand.LEFT;
}
else
{
playingType = PlayingType.NONE;
}
PlayOverride("Right", 0, animator);
}
}
for (int overrideNumber = 0; overrideNumber < OVERRIDES.Length; overrideNumber++)
{
AnimationClip overrideAnimation = null;
string overrideName = string.Empty;
if (controller != null)
{
overrideAnimation = controller[OVERRIDES[overrideNumber]];
if (overrideAnimation.name != OVERRIDES[overrideNumber])
{
overrideName = $"({overrideAnimation.name})";
}
}
// AnimationClipとOVERRIDES[overrideNumber]の名前が同じであれば未設定
using (new EditorGUI.DisabledGroupScope(controller == null || overrideAnimation.name == OVERRIDES[overrideNumber]))
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField(OVERRIDES[overrideNumber], overrideName);
if (GUILayout.Button("Left"))
{
playingType = PlayingType.OVERRIDE;
if (playingHand == PlayingHand.RIGHT ||
playingHand == PlayingHand.BOTH)
{
playingHand = PlayingHand.BOTH;
}
else
{
playingHand = PlayingHand.LEFT;
}
PlayOverride("Left", overrideNumber, animator);
}
if (GUILayout.Button("Right"))
{
playingType = PlayingType.OVERRIDE;
if (playingHand == PlayingHand.LEFT ||
playingHand == PlayingHand.BOTH)
{
playingHand = PlayingHand.BOTH;
}
else
{
playingHand = PlayingHand.RIGHT;
}
PlayOverride("Right", overrideNumber, animator);
}
}
}
}
}
EditorGUILayout.Space();
using (new EditorGUI.DisabledGroupScope(!EditorApplication.isPlayingOrWillChangePlaymode))
{
EditorGUILayout.LabelField("Emotes", EditorStyles.boldLabel);
using (new EditorGUI.IndentLevelScope())
{
for (int emoteNumber = 0; emoteNumber < EMOTES.Length; emoteNumber++)
{
AnimationClip emoteAnimation = null;
string buttonText = EMOTES[emoteNumber];
if (controller != null)
{
emoteAnimation = controller[EMOTES[emoteNumber]];
buttonText = emoteAnimation.name;
}
// 取得できるAnimationClipの名前が"EMOTE*"だったら設定されていない
using (new EditorGUI.DisabledGroupScope(emoteAnimation == null || emoteAnimation.name == EMOTES[emoteNumber]))
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField(EMOTES[emoteNumber]);
if (GUILayout.Button(buttonText) && emoteAnimation != null)
{
if (animator.GetInteger($"Emote") != 0) return;
playingType = PlayingType.EMOTE;
playingHand = PlayingHand.NONE;
PlayEmote(emoteNumber + 1, animator, emoteAnimation);
}
}
}
}
}
#else
EditorGUILayout.HelpBox("使用するにはVRCSDK2がプロジェクトにインポートされている必要があります", MessageType.Error);
#endif
}
private void PlayOverride(string hand, int overrideNumber, Animator animator)
{
animator.SetInteger($"HandGesture{hand}", overrideNumber);
}
private async void PlayEmote(int emoteNumber, Animator animator, AnimationClip animationClip)
{
int waitMilliSecond = (int)(animationClip.length * 1000);
if (animationClip == null) waitMilliSecond = 1000;
animator.SetInteger($"Emote", emoteNumber);
// EmoteAnimationの実行が終わるまで待つ必要がある
await Task.Delay(waitMilliSecond);
animator.SetInteger($"Emote", 0);
}
private GameObject CreatePoseConstrainterToRootIfNeeded()
{
var obj = GameObject.Find("PoseConstrainter");
if (obj is null)
{
var prefab = Resources.Load("Tester/PoseConstrainter");
obj = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
}
return obj;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad6a8a6e01f535447b7c5b353726db9a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/External/VRCDeveloperTool/LICENSE.txt (Stored with Git LFS) vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8ab5348371ff39442bb728561d7afefc
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/External/VRCDeveloperTool/README_VRCDeveloperTool.txt (Stored with Git LFS) vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f58d8d4097bd56349ba1953e6c80e2d9
timeCreated: 1537082424
licenseType: Free
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: efb64c83056940b49ab22f22512cc222
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// ver 1.00
// © 2019-1-31 gatosyocora
public class ScaleLimitVisualizer : MonoBehaviour {
public Vector3 scaleLimit = new Vector3(4, 5, 3);
public bool isWireFrame = true;
void OnDrawGizmos()
{
var pos = transform.position + new Vector3(0, (float)(scaleLimit.y / 2.0), 0);
if (isWireFrame)
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireCube(pos, scaleLimit);
}
else
{
Gizmos.color = new Color(1, 1, 0, 0.8f);
Gizmos.DrawCube(pos, scaleLimit);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5be28f395556f6b47937df98fb2db262
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 76f28943da1e75c4f89da9708fdca076
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,88 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace VRCDeveloperTool
{
public class PoseConstraint : MonoBehaviour
{
[Serializable]
public class BoneInfo
{
public Transform Transform { get; set; }
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; }
}
private List<BoneInfo> boneList;
public Animator animator;
public bool Active = true;
public void Start()
{
boneList = GetBoneInfo(animator);
Active = true;
}
private void LateUpdate()
{
if (Active && boneList != null) SetBoneInfo(boneList);
}
public void UpdateBoneInfo(Animator animator)
{
this.animator = animator;
#if UNITY_EDITOR
EditorUtility.SetDirty(this);
#endif
boneList = GetBoneInfo(animator);
}
private List<BoneInfo> GetBoneInfo(Animator animator)
{
return Enum.GetNames(typeof(HumanBodyBones))
.Select(boneName =>
{
if (Enum.TryParse(boneName, out HumanBodyBones bone))
{
Transform trans;
try
{
trans = animator.GetBoneTransform(bone);
}
catch (IndexOutOfRangeException)
{
return null;
}
if (trans is null) return null;
return new BoneInfo
{
Transform = trans,
Position = trans.position,
Rotation = trans.rotation
};
}
else
{
return null;
}
})
.Where(x => x != null)
.ToList();
}
private void SetBoneInfo(List<BoneInfo> boneList)
{
foreach (var bone in boneList)
{
bone.Transform.position = bone.Position;
bone.Transform.rotation = bone.Rotation;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ced8b4af24d7fee4e8b95e1c85e26eeb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
public class AssetBatchRenamer : EditorWindow
{
int removeFrontCount = 0;
int removeBackCount = 0;
string prefixToAdd = "";
string suffixToAdd = "";
[MenuItem("Tools/Asset Batch Renamer")]
public static void ShowWindow()
{
GetWindow<AssetBatchRenamer>("Asset Batch Renamer");
}
void OnGUI()
{
GUILayout.Label("선택한 에셋 이름 일괄 수정", EditorStyles.boldLabel);
removeFrontCount = EditorGUILayout.IntField("앞에서 제거할 문자 수", removeFrontCount);
removeBackCount = EditorGUILayout.IntField("뒤에서 제거할 문자 수", removeBackCount);
prefixToAdd = EditorGUILayout.TextField("앞에 추가할 문자열", prefixToAdd);
suffixToAdd = EditorGUILayout.TextField("뒤에 추가할 문자열", suffixToAdd);
if (GUILayout.Button("선택된 에셋 이름 변경"))
{
RenameSelectedAssets();
}
}
void RenameSelectedAssets()
{
Object[] selectedObjects = Selection.objects;
Dictionary<string, int> nameConflictMap = new Dictionary<string, int>();
Undo.RecordObjects(selectedObjects, "Batch Rename Assets");
foreach (Object obj in selectedObjects)
{
string assetPath = AssetDatabase.GetAssetPath(obj);
string assetName = Path.GetFileNameWithoutExtension(assetPath);
string assetExtension = Path.GetExtension(assetPath);
string assetDir = Path.GetDirectoryName(assetPath);
string newName = assetName;
// 앞 문자 제거
if (removeFrontCount > 0 && newName.Length > removeFrontCount)
newName = newName.Substring(removeFrontCount);
// 뒤 문자 제거
if (removeBackCount > 0 && newName.Length > removeBackCount)
newName = newName.Substring(0, newName.Length - removeBackCount);
// 앞뒤 추가
newName = prefixToAdd + newName + suffixToAdd;
// 이름 충돌 방지 처리
string finalName = newName;
int counter = 1;
while (AssetExists(assetDir, finalName, assetExtension) && finalName != assetName)
{
finalName = newName + "_" + counter;
counter++;
}
if (finalName != assetName)
{
string result = AssetDatabase.RenameAsset(assetPath, finalName);
if (!string.IsNullOrEmpty(result))
{
Debug.LogWarning($"[{obj.name}] 이름 변경 실패: {result}");
}
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
bool AssetExists(string directory, string name, string extension)
{
string fullPath = Path.Combine(directory, name + extension).Replace("\\", "/");
return AssetDatabase.LoadAssetAtPath<Object>(fullPath) != null;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ae8cfcdd869b3c644956981b4d6fd966
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,556 @@
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Collections.Generic;
using VRM;
using MagicaCloth2;
using MagicaClothType = MagicaCloth2.MagicaCloth;
using UnityEngine.Animations;
public class AvatarComponetCopier : EditorWindow
{
GameObject sourcePrefab;
GameObject destinationPrefab;
[MenuItem("Tools/Avatar Component Mover (SpringBone+MagicaCloth2)")]
static void ShowWindow()
{
GetWindow<AvatarComponetCopier>("Avatar Component Mover");
}
void OnGUI()
{
GUILayout.Label("Move VRMSpringBone/MagicaCloth2 & Collider Components", EditorStyles.boldLabel);
sourcePrefab = (GameObject)EditorGUILayout.ObjectField("Source Object", sourcePrefab, typeof(GameObject), true);
destinationPrefab = (GameObject)EditorGUILayout.ObjectField("Target Object", destinationPrefab, typeof(GameObject), true);
if (GUILayout.Button("Spring본콜라이더 복사"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
CopySpringBoneAndMagicaColliders();
}
if (GUILayout.Button("Spring본/Cloth 옮기기"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
MoveSpringBonesAndMagicaCloth();
}
if (GUILayout.Button("Constraint 값 복사"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
CopyConstraints();
}
if (GUILayout.Button("BlendShapeCopy"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
CopyBlendShapes();
}
if (GUILayout.Button("ActiveStateCopy"))
{
if (sourcePrefab == null || destinationPrefab == null)
{
EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
return;
}
CopyActiveStates();
}
}
void CopySpringBoneAndMagicaColliders()
{
// VRMSpringBoneColliderGroup
var srcColliders = sourcePrefab.GetComponentsInChildren<VRMSpringBoneColliderGroup>(true)
.OrderBy(c => GetTransformPath(c.transform, sourcePrefab.transform)).ToArray();
foreach (var srcCollider in srcColliders)
{
string path = GetTransformPath(srcCollider.transform, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
if (tgtTransform.GetComponent<VRMSpringBoneColliderGroup>() != null) continue;
var tgtCollider = tgtTransform.gameObject.AddComponent<VRMSpringBoneColliderGroup>();
CopyColliderGroupParameters(srcCollider, tgtCollider);
}
// MagicaCloth2 Colliders
CopyMagicaCollider<MagicaSphereCollider>();
CopyMagicaCollider<MagicaCapsuleCollider>();
CopyMagicaCollider<MagicaPlaneCollider>();
Debug.Log("VRMSpringBoneColliderGroup & MagicaCloth2 Collider 복사가 완료되었습니다.");
}
void CopyMagicaCollider<T>() where T : Component
{
var srcColliders = sourcePrefab.GetComponentsInChildren<T>(true)
.OrderBy(c => GetTransformPath(((Component)c).transform, sourcePrefab.transform)).ToArray();
foreach (var srcCollider in srcColliders)
{
var srcTr = ((Component)srcCollider).transform;
string path = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
if (tgtTransform.GetComponent<T>() != null) continue;
var tgtCollider = tgtTransform.gameObject.AddComponent<T>();
CopyMagicaColliderComponents(srcCollider, tgtCollider);
}
}
void CopyMagicaColliderComponents(Component src, Component tgt)
{
var fields = src.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var field in fields)
{
var value = field.GetValue(src);
if (typeof(UnityEngine.Object).IsAssignableFrom(field.FieldType))
{
Object refObj = value as Object;
if (refObj == null) { field.SetValue(tgt, null); continue; }
if (refObj is Transform tr)
{
string refPath = GetTransformPath(tr, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(refPath);
if (tgtTr != null)
field.SetValue(tgt, tgtTr);
}
else if (refObj is GameObject go)
{
string refPath = GetTransformPath(go.transform, sourcePrefab.transform);
var tgtGo = destinationPrefab.transform.Find(refPath);
if (tgtGo != null)
field.SetValue(tgt, tgtGo.gameObject);
}
else if (refObj is Component comp)
{
string refPath = GetTransformPath(comp.gameObject.transform, sourcePrefab.transform);
var tgtGo = destinationPrefab.transform.Find(refPath);
if (tgtGo != null)
{
var tgtComps = tgtGo.GetComponents(comp.GetType());
int compIdx = comp.gameObject.GetComponents(comp.GetType()).ToList().IndexOf(comp);
if (compIdx >= 0 && compIdx < tgtComps.Length)
field.SetValue(tgt, tgtComps[compIdx]);
}
}
else
{
// 기타 UnityEngine.Object 타입은 그대로 복사
field.SetValue(tgt, refObj);
}
}
else
{
// 값 타입, enum, string 등은 그대로 복사
field.SetValue(tgt, value);
}
}
}
void MoveSpringBonesAndMagicaCloth()
{
// VRMSpringBone
var srcSpringBones = sourcePrefab.GetComponentsInChildren<VRMSpringBone>(true)
.OrderBy(s => GetTransformPath(s.transform, sourcePrefab.transform)).ToArray();
foreach (var srcSpringBone in srcSpringBones)
{
string path = GetTransformPath(srcSpringBone.transform, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
var tgtSpringBone = tgtTransform.gameObject.AddComponent<VRMSpringBone>();
CopyVRMSpringBoneComponents(srcSpringBone, tgtSpringBone);
DestroyImmediate(srcSpringBone);
}
// MagicaCloth
var srcMagicaCloths = sourcePrefab.GetComponentsInChildren<MagicaClothType>(true)
.OrderBy(s => GetTransformPath(s.transform, sourcePrefab.transform)).ToArray();
foreach (var srcCloth in srcMagicaCloths)
{
string path = GetTransformPath(srcCloth.transform, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
var tgtCloth = tgtTransform.gameObject.AddComponent<MagicaClothType>();
CopyMagicaClothComponents(srcCloth, tgtCloth);
DestroyImmediate(srcCloth);
}
Debug.Log("VRMSpringBone & MagicaCloth 옮기기가 완료되었습니다.");
}
void CopyColliderGroupParameters(VRMSpringBoneColliderGroup source, VRMSpringBoneColliderGroup target)
{
target.Colliders = source.Colliders.Select(c => new VRMSpringBoneColliderGroup.SphereCollider
{
Offset = c.Offset,
Radius = c.Radius
}).ToArray();
}
void CopyVRMSpringBoneComponents(VRMSpringBone original, VRMSpringBone copy)
{
copy.m_comment = original.m_comment;
copy.m_stiffnessForce = original.m_stiffnessForce;
copy.m_gravityPower = original.m_gravityPower;
copy.m_gravityDir = original.m_gravityDir;
copy.m_dragForce = original.m_dragForce;
copy.m_center = FindCorrespondingTransform(original.m_center, destinationPrefab.transform);
copy.m_hitRadius = original.m_hitRadius;
copy.m_updateType = original.m_updateType;
copy.RootBones = original.RootBones
.Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
.Where(t => t != null)
.ToList();
copy.ColliderGroups = original.ColliderGroups
.Select(colliderGroup => FindCorrespondingColliderGroup(colliderGroup, destinationPrefab.transform))
.Where(cg => cg != null)
.ToArray();
}
Transform FindCorrespondingTransform(Transform original, Transform searchRoot)
{
if (original == null) return null;
var path = GetTransformPath(original, sourcePrefab.transform);
return searchRoot.Find(path);
}
string GetTransformPath(Transform current, Transform root)
{
if (current == root) return "";
var path = current.name;
while (current.parent != null && current.parent != root)
{
current = current.parent;
path = current.name + "/" + path;
}
return path;
}
VRMSpringBoneColliderGroup FindCorrespondingColliderGroup(VRMSpringBoneColliderGroup original, Transform searchRoot)
{
if (original == null) return null;
var path = GetTransformPath(original.transform, sourcePrefab.transform);
var correspondingTransform = searchRoot.Find(path);
return correspondingTransform != null ? correspondingTransform.GetComponent<VRMSpringBoneColliderGroup>() : null;
}
void CopyMagicaClothComponents(MagicaClothType src, MagicaClothType tgt)
{
var fields = typeof(MagicaClothType).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var field in fields)
{
var value = field.GetValue(src);
// ClothType 우선 복사 및 분기 처리
if (field.Name == "serializeData" && value is MagicaCloth2.ClothSerializeData sdata)
{
var tdata = new MagicaCloth2.ClothSerializeData();
// ClothType을 먼저 복사
tdata.clothType = sdata.clothType;
// ClothType에 따라 분기
if (sdata.clothType == MagicaCloth2.ClothProcess.ClothType.MeshCloth)
{
// sourceRenderers 복사
tdata.sourceRenderers = sdata.sourceRenderers
.Select(r => {
if (r == null) return null;
string path = GetTransformPath(r.transform, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(path);
return tgtTr ? tgtTr.GetComponent<Renderer>() : null;
})
.Where(r => r != null)
.ToList();
}
else if (sdata.clothType == MagicaCloth2.ClothProcess.ClothType.BoneCloth || sdata.clothType == MagicaCloth2.ClothProcess.ClothType.BoneSpring)
{
// rootBones 복사
tdata.rootBones = sdata.rootBones
.Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
.Where(t => t != null)
.ToList();
}
// colliderCollisionConstraint 복사
if (sdata.colliderCollisionConstraint != null)
{
var srcCol = sdata.colliderCollisionConstraint;
var tgtCol = new MagicaCloth2.ColliderCollisionConstraint.SerializeData();
tgtCol.mode = srcCol.mode;
tgtCol.friction = srcCol.friction;
tgtCol.limitDistance = srcCol.limitDistance;
// colliderList 변환
tgtCol.colliderList = srcCol.colliderList
.Select(c => {
if (c == null) return null;
string path = GetTransformPath(c.transform, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(path);
return tgtTr ? tgtTr.GetComponent<MagicaCloth2.ColliderComponent>() : null;
})
.Where(c => c != null)
.ToList();
// collisionBones 변환
tgtCol.collisionBones = srcCol.collisionBones
.Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
.Where(t => t != null)
.ToList();
tdata.colliderCollisionConstraint = tgtCol;
}
// 공통 필드 복사
tdata.paintMaps = new List<Texture2D>(sdata.paintMaps);
tdata.paintMode = sdata.paintMode;
tdata.connectionMode = sdata.connectionMode;
tdata.rotationalInterpolation = sdata.rotationalInterpolation;
tdata.rootRotation = sdata.rootRotation;
tdata.updateMode = sdata.updateMode;
tdata.animationPoseRatio = sdata.animationPoseRatio;
tdata.reductionSetting = sdata.reductionSetting;
tdata.customSkinningSetting = sdata.customSkinningSetting;
tdata.normalAlignmentSetting = sdata.normalAlignmentSetting;
tdata.cullingSettings = sdata.cullingSettings;
tdata.normalAxis = sdata.normalAxis;
tdata.gravity = sdata.gravity;
tdata.gravityDirection = sdata.gravityDirection;
tdata.gravityFalloff = sdata.gravityFalloff;
tdata.stablizationTimeAfterReset = sdata.stablizationTimeAfterReset;
tdata.blendWeight = sdata.blendWeight;
tdata.damping = sdata.damping;
tdata.radius = sdata.radius;
// 기타 값들도 필요시 추가 복사
field.SetValue(tgt, tdata);
continue;
}
if (field.Name == "serializeData2" && value is MagicaCloth2.ClothSerializeData2 sdata2)
{
var tdata2 = new MagicaCloth2.ClothSerializeData2();
// boneAttributeDict 변환
tdata2.boneAttributeDict = sdata2.boneAttributeDict.ToDictionary(
kvp => FindCorrespondingTransform(kvp.Key, destinationPrefab.transform),
kvp => kvp.Value
);
// selectionData 등 나머지 값은 그대로 복사
tdata2.selectionData = sdata2.selectionData;
tdata2.preBuildData = sdata2.preBuildData;
field.SetValue(tgt, tdata2);
continue;
}
// customSkinningSetting.skinningBones 변환
if (field.Name == "customSkinningSetting" && value != null)
{
var srcSkin = value;
var skinField = value.GetType().GetField("skinningBones");
if (skinField != null)
{
var srcList = skinField.GetValue(srcSkin) as List<Transform>;
if (srcList != null)
{
var tgtList = srcList
.Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
.Where(t => t != null)
.ToList();
skinField.SetValue(srcSkin, tgtList);
}
}
field.SetValue(tgt, srcSkin);
continue;
}
// 이하 기존 로직
if (typeof(UnityEngine.Object).IsAssignableFrom(field.FieldType))
{
Object refObj = value as Object;
if (refObj == null) { field.SetValue(tgt, null); continue; }
if (refObj is Transform tr)
{
string refPath = GetTransformPath(tr, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(refPath);
if (tgtTr != null)
field.SetValue(tgt, tgtTr);
}
else if (refObj is GameObject go)
{
string refPath = GetTransformPath(go.transform, sourcePrefab.transform);
var tgtGo = destinationPrefab.transform.Find(refPath);
if (tgtGo != null)
field.SetValue(tgt, tgtGo.gameObject);
}
else if (refObj is Component comp)
{
string refPath = GetTransformPath(comp.gameObject.transform, sourcePrefab.transform);
var tgtGo = destinationPrefab.transform.Find(refPath);
if (tgtGo != null)
{
var tgtComps = tgtGo.GetComponents(comp.GetType());
int compIdx = comp.gameObject.GetComponents(comp.GetType()).ToList().IndexOf(comp);
if (compIdx >= 0 && compIdx < tgtComps.Length)
field.SetValue(tgt, tgtComps[compIdx]);
}
}
else
{
// 기타 UnityEngine.Object 타입은 그대로 복사
field.SetValue(tgt, refObj);
}
}
else
{
// 값 타입, enum, string 등은 그대로 복사
field.SetValue(tgt, value);
}
}
}
void CopyConstraints()
{
var srcTransforms = sourcePrefab.GetComponentsInChildren<Transform>(true);
foreach (var srcTr in srcTransforms)
{
string path = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(path);
if (tgtTr == null) continue;
// PositionConstraint
var srcPos = srcTr.GetComponent<PositionConstraint>();
if (srcPos != null)
CopyConstraintComponent<PositionConstraint>(srcPos, tgtTr);
// RotationConstraint
var srcRot = srcTr.GetComponent<RotationConstraint>();
if (srcRot != null)
CopyConstraintComponent<RotationConstraint>(srcRot, tgtTr);
// ParentConstraint
var srcParent = srcTr.GetComponent<ParentConstraint>();
if (srcParent != null)
CopyConstraintComponent<ParentConstraint>(srcParent, tgtTr);
}
Debug.Log("Constraint 값 복사가 완료되었습니다.");
}
void CopyConstraintComponent<T>(T srcConstraint, Transform tgtTr) where T : Behaviour
{
// 이미 있으면 삭제 후 새로 추가
var tgtConstraint = tgtTr.GetComponent<T>();
if (tgtConstraint != null)
DestroyImmediate(tgtConstraint);
tgtConstraint = tgtTr.gameObject.AddComponent<T>();
if (srcConstraint is PositionConstraint srcPos && tgtConstraint is PositionConstraint tgtPos)
{
tgtPos.weight = srcPos.weight;
tgtPos.constraintActive = srcPos.constraintActive;
tgtPos.locked = srcPos.locked;
for (int i = 0; i < srcPos.sourceCount; i++)
{
var src = srcPos.GetSource(i);
var srcTr = src.sourceTransform;
if (srcTr == null) continue;
string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
if (tgtSourceTr == null) continue;
var newSource = src;
newSource.sourceTransform = tgtSourceTr;
tgtPos.AddSource(newSource);
}
tgtPos.translationAtRest = srcPos.translationAtRest;
tgtPos.translationOffset = srcPos.translationOffset;
tgtPos.translationAxis = srcPos.translationAxis;
}
else if (srcConstraint is RotationConstraint srcRot && tgtConstraint is RotationConstraint tgtRot)
{
tgtRot.weight = srcRot.weight;
tgtRot.constraintActive = srcRot.constraintActive;
tgtRot.locked = srcRot.locked;
for (int i = 0; i < srcRot.sourceCount; i++)
{
var src = srcRot.GetSource(i);
var srcTr = src.sourceTransform;
if (srcTr == null) continue;
string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
if (tgtSourceTr == null) continue;
var newSource = src;
newSource.sourceTransform = tgtSourceTr;
tgtRot.AddSource(newSource);
}
tgtRot.rotationAtRest = srcRot.rotationAtRest;
tgtRot.rotationOffset = srcRot.rotationOffset;
tgtRot.rotationAxis = srcRot.rotationAxis;
}
else if (srcConstraint is ParentConstraint srcParent && tgtConstraint is ParentConstraint tgtParent)
{
tgtParent.weight = srcParent.weight;
tgtParent.constraintActive = srcParent.constraintActive;
tgtParent.locked = srcParent.locked;
for (int i = 0; i < srcParent.sourceCount; i++)
{
var src = srcParent.GetSource(i);
var srcTr = src.sourceTransform;
if (srcTr == null) continue;
string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
if (tgtSourceTr == null) continue;
var newSource = src;
newSource.sourceTransform = tgtSourceTr;
tgtParent.AddSource(newSource);
// Offset도 복사
tgtParent.SetTranslationOffset(i, srcParent.GetTranslationOffset(i));
tgtParent.SetRotationOffset(i, srcParent.GetRotationOffset(i));
}
tgtParent.translationAtRest = srcParent.translationAtRest;
tgtParent.rotationAtRest = srcParent.rotationAtRest;
tgtParent.translationAxis = srcParent.translationAxis;
tgtParent.rotationAxis = srcParent.rotationAxis;
}
}
void CopyBlendShapes()
{
var srcRenderers = sourcePrefab.GetComponentsInChildren<SkinnedMeshRenderer>(true);
foreach (var srcRenderer in srcRenderers)
{
string path = GetTransformPath(srcRenderer.transform, sourcePrefab.transform);
var tgtTransform = destinationPrefab.transform.Find(path);
if (tgtTransform == null) continue;
var tgtRenderer = tgtTransform.GetComponent<SkinnedMeshRenderer>();
if (tgtRenderer == null) continue;
int blendShapeCount = srcRenderer.sharedMesh != null ? srcRenderer.sharedMesh.blendShapeCount : 0;
for (int i = 0; i < blendShapeCount; i++)
{
string shapeName = srcRenderer.sharedMesh.GetBlendShapeName(i);
int tgtIndex = tgtRenderer.sharedMesh != null ? tgtRenderer.sharedMesh.GetBlendShapeIndex(shapeName) : -1;
if (tgtIndex >= 0)
{
float value = srcRenderer.GetBlendShapeWeight(i);
tgtRenderer.SetBlendShapeWeight(tgtIndex, value);
}
}
}
Debug.Log("BlendShape 값 복사가 완료되었습니다.");
}
void CopyActiveStates()
{
var srcTransforms = sourcePrefab.GetComponentsInChildren<Transform>(true);
foreach (var srcTr in srcTransforms)
{
string path = GetTransformPath(srcTr, sourcePrefab.transform);
var tgtTr = destinationPrefab.transform.Find(path);
if (tgtTr == null) continue;
tgtTr.gameObject.SetActive(srcTr.gameObject.activeSelf);
}
Debug.Log("ActiveState 복사가 완료되었습니다.");
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5c2754eea0159c42bfcf47f3ff1baea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,109 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class FindUnusedBones : EditorWindow
{
private List<string> excludeStrings = new List<string>(); // 제외할 문자열 리스트
[MenuItem("Tools/Find Unused Bones")]
public static void ShowWindow()
{
GetWindow<FindUnusedBones>("Find Unused Bones");
}
private void OnGUI()
{
GUILayout.Label("Exclude Strings", EditorStyles.boldLabel);
// 동적으로 문자열 입력 필드와 + - 버튼을 추가
for (int i = 0; i < excludeStrings.Count; i++)
{
GUILayout.BeginHorizontal();
excludeStrings[i] = EditorGUILayout.TextField($"Exclude String {i + 1}", excludeStrings[i]);
if (GUILayout.Button("-", GUILayout.Width(20)))
{
excludeStrings.RemoveAt(i);
}
GUILayout.EndHorizontal();
}
if (GUILayout.Button("+", GUILayout.Width(20)))
{
excludeStrings.Add(string.Empty); // 빈 문자열 입력 필드 추가
}
GUILayout.Space(20);
if (GUILayout.Button("Find and Select Unused Bones"))
{
FindAndSelectUnusedBones();
}
}
private void FindAndSelectUnusedBones()
{
if (Selection.activeGameObject == null)
{
Debug.LogWarning("No GameObject selected!");
return;
}
Transform root = Selection.activeGameObject.transform;
SkinnedMeshRenderer[] skinnedMeshRenderers = root.GetComponentsInChildren<SkinnedMeshRenderer>();
HashSet<Transform> usedBones = new HashSet<Transform>();
HashSet<Transform> excludedObjects = new HashSet<Transform>();
// Add all bones used by SkinnedMeshRenderers to usedBones set
foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
{
foreach (var bone in skinnedMeshRenderer.bones)
{
usedBones.Add(bone);
}
// Add the SkinnedMeshRenderer's gameObject and its parents to excludedObjects set
Transform current = skinnedMeshRenderer.transform;
while (current != null)
{
excludedObjects.Add(current);
current = current.parent;
}
}
List<Transform> unusedBones = new List<Transform>();
Transform[] allBones = root.GetComponentsInChildren<Transform>();
foreach (var bone in allBones)
{
// 문자열 필터링: 사용자가 입력한 문자열을 포함한 오브젝트는 제외
bool excludeBone = false;
foreach (var excludeString in excludeStrings)
{
if (!string.IsNullOrEmpty(excludeString) && bone.name.ToLower().Contains(excludeString.ToLower()))
{
excludeBone = true;
break;
}
}
if (!usedBones.Contains(bone) &&
!excludedObjects.Contains(bone) &&
bone != root &&
!excludeBone) // 필터링된 오브젝트 제외
{
unusedBones.Add(bone);
}
}
if (unusedBones.Count > 0)
{
Selection.objects = unusedBones.ConvertAll(b => b.gameObject).ToArray();
Debug.Log($"Found {unusedBones.Count} unused bones. Selected in the hierarchy.");
}
else
{
Debug.Log("No unused bones found.");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 06f428b08583ac34a9c1227c00a7e89e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,248 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
public class MaterialAndTextureTool : EditorWindow
{
private GameObject targetPrefab;
private string materialOutputPath = "Assets/DuplicatedMaterials";
private string textureOutputPath = "Assets/DuplicatedTextures";
private Dictionary<string, List<Material>> duplicateMaterialMap = new Dictionary<string, List<Material>>();
private Dictionary<string, List<Texture>> duplicateTextureMap = new Dictionary<string, List<Texture>>();
private Dictionary<Material, Material> materialCopies = new Dictionary<Material, Material>();
private Dictionary<Texture, Texture> textureCopies = new Dictionary<Texture, Texture>();
private HashSet<Material> collectedMaterials = new HashSet<Material>();
private HashSet<Texture> collectedTextures = new HashSet<Texture>();
private Vector2 scroll;
[MenuItem("Tools/Material & Texture Tool")]
public static void ShowWindow()
{
var window = GetWindow<MaterialAndTextureTool>("MatTex Tool");
window.minSize = new Vector2(600, 500);
}
private void OnGUI()
{
GUILayout.Label("\uD83C\uDF1F 머테리얼 & 텍스처 유틸리티", EditorStyles.boldLabel);
targetPrefab = (GameObject)EditorGUILayout.ObjectField("\uD83D\uDCE6 타겟 프리팹", targetPrefab, typeof(GameObject), true);
materialOutputPath = EditorGUILayout.TextField("\uD83D\uDCC2 머테리얼 저장 경로", materialOutputPath);
textureOutputPath = EditorGUILayout.TextField("\uD83D\uDCC2 텍스처 저장 경로", textureOutputPath);
if (GUILayout.Button("\uD83D\uDCCB 중복 이름 검사")) CollectDuplicates();
if (GUILayout.Button("\uD83D\uDD04 중복 이름 자동 변경")) RenameDuplicateAssets();
if (GUILayout.Button("\uD83D\uDD04 머테리얼 및 텍스처 복사")) DuplicateMaterialsAndTextures();
scroll = EditorGUILayout.BeginScrollView(scroll);
GUILayout.Space(10);
GUILayout.Label("\u26A0\uFE0F 중복된 이름의 머테리얼", EditorStyles.boldLabel);
DrawDuplicateList(duplicateMaterialMap);
GUILayout.Space(10);
GUILayout.Label("\u26A0\uFE0F 중복된 이름의 텍스처", EditorStyles.boldLabel);
DrawDuplicateList(duplicateTextureMap);
GUILayout.Space(10);
GUILayout.Label("\uD83D\uDD0D 참조된 모든 머테리얼", EditorStyles.boldLabel);
foreach (var mat in collectedMaterials)
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(mat.name, GUILayout.Width(200)))
{
EditorGUIUtility.PingObject(mat);
}
EditorGUILayout.ObjectField(mat, typeof(Material), false);
EditorGUILayout.EndHorizontal();
}
GUILayout.Space(10);
GUILayout.Label("\uD83D\uDD0D 참조된 모든 텍스처", EditorStyles.boldLabel);
foreach (var tex in collectedTextures)
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button(tex.name, GUILayout.Width(200)))
{
EditorGUIUtility.PingObject(tex);
}
EditorGUILayout.ObjectField(tex, typeof(Texture), false);
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
}
void CollectDuplicates()
{
duplicateMaterialMap.Clear();
duplicateTextureMap.Clear();
collectedMaterials.Clear();
collectedTextures.Clear();
if (targetPrefab == null) return;
HashSet<Material> seenMaterials = new HashSet<Material>();
HashSet<Texture> seenTextures = new HashSet<Texture>();
var renderers = targetPrefab.GetComponentsInChildren<Renderer>(true);
foreach (var renderer in renderers)
{
foreach (var mat in renderer.sharedMaterials)
{
if (mat == null || seenMaterials.Contains(mat)) continue;
seenMaterials.Add(mat);
collectedMaterials.Add(mat);
if (!duplicateMaterialMap.ContainsKey(mat.name))
duplicateMaterialMap[mat.name] = new List<Material>();
duplicateMaterialMap[mat.name].Add(mat);
Shader shader = mat.shader;
int count = ShaderUtil.GetPropertyCount(shader);
for (int i = 0; i < count; i++)
{
string propName = ShaderUtil.GetPropertyName(shader, i);
Texture tex = mat.GetTexture(propName);
if (tex == null || seenTextures.Contains(tex)) continue;
seenTextures.Add(tex);
collectedTextures.Add(tex);
if (!duplicateTextureMap.ContainsKey(tex.name))
duplicateTextureMap[tex.name] = new List<Texture>();
duplicateTextureMap[tex.name].Add(tex);
}
}
}
}
void DrawDuplicateList<T>(Dictionary<string, List<T>> map) where T : Object
{
foreach (var pair in map)
{
if (pair.Value.Count < 2) continue;
GUILayout.Label("\u26A0\uFE0F " + pair.Key + " (" + pair.Value.Count + "개)", GetRedStyle());
foreach (var obj in pair.Value)
{
EditorGUILayout.ObjectField(obj, typeof(T), false);
}
}
}
void RenameDuplicateAssets()
{
Dictionary<string, int> renameCount = new Dictionary<string, int>();
RenameAssetGroup(duplicateMaterialMap, ".mat", renameCount);
RenameAssetGroup(duplicateTextureMap, null, renameCount);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
void RenameAssetGroup<T>(Dictionary<string, List<T>> map, string extOverride, Dictionary<string, int> counter) where T : Object
{
foreach (var pair in map)
{
if (pair.Value.Count < 2) continue;
foreach (var obj in pair.Value)
{
string path = AssetDatabase.GetAssetPath(obj);
if (string.IsNullOrEmpty(path)) continue;
if (!counter.ContainsKey(pair.Key)) counter[pair.Key] = 1;
else counter[pair.Key] += 1;
string newName = pair.Key + "_" + counter[pair.Key];
string result = AssetDatabase.RenameAsset(path, newName);
if (result != "")
{
Debug.LogWarning("리네이밍 실패: " + result);
}
}
}
}
void DuplicateMaterialsAndTextures()
{
if (targetPrefab == null) return;
materialCopies.Clear();
textureCopies.Clear();
if (!AssetDatabase.IsValidFolder(materialOutputPath)) AssetDatabase.CreateFolder("Assets", "DuplicatedMaterials");
if (!AssetDatabase.IsValidFolder(textureOutputPath)) AssetDatabase.CreateFolder("Assets", "DuplicatedTextures");
Renderer[] renderers = targetPrefab.GetComponentsInChildren<Renderer>(true);
foreach (var renderer in renderers)
{
Material[] newMats = new Material[renderer.sharedMaterials.Length];
for (int i = 0; i < newMats.Length; i++)
{
Material orig = renderer.sharedMaterials[i];
if (orig == null) continue;
if (!materialCopies.ContainsKey(orig))
{
Material newMat = new Material(orig);
string matPath = AssetDatabase.GenerateUniqueAssetPath(materialOutputPath + "/" + orig.name + "_Copy.mat");
AssetDatabase.CreateAsset(newMat, matPath);
materialCopies[orig] = newMat;
CopyTextures(orig, newMat);
EditorUtility.SetDirty(newMat);
}
newMats[i] = materialCopies[orig];
}
Undo.RecordObject(renderer, "Apply Copied Materials");
renderer.sharedMaterials = newMats;
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
void CopyTextures(Material original, Material copy)
{
Shader shader = original.shader;
int count = ShaderUtil.GetPropertyCount(shader);
string[] maskProps = { "_MaskMap", "_OcclusionMap", "_DetailMask", "_RoughnessMap", "_MetallicGlossMap" };
for (int i = 0; i < count; i++)
{
string prop = ShaderUtil.GetPropertyName(shader, i);
Texture tex = original.GetTexture(prop);
if (tex == null) continue;
bool isTexEnv = ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.TexEnv;
bool isMask = System.Array.IndexOf(maskProps, prop) >= 0;
if (isTexEnv || isMask)
{
if (!textureCopies.ContainsKey(tex))
{
string path = AssetDatabase.GetAssetPath(tex);
string newPath = AssetDatabase.GenerateUniqueAssetPath(textureOutputPath + "/" + tex.name + "_Copy" + Path.GetExtension(path));
AssetDatabase.CopyAsset(path, newPath);
Texture newTex = AssetDatabase.LoadAssetAtPath<Texture>(newPath);
textureCopies[tex] = newTex;
}
copy.SetTexture(prop, textureCopies[tex]);
EditorUtility.SetDirty(copy);
}
}
}
GUIStyle GetRedStyle()
{
var style = new GUIStyle(EditorStyles.label);
style.normal.textColor = Color.red;
return style;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d0746affa2959d544a0f12528165f87b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,198 @@
// ... existing code ...
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class ObjectNameModifier : EditorWindow
{
private string prefixText = string.Empty;
private string suffixText = string.Empty;
private GameObject rootObject;
private Dictionary<string, List<Transform>> duplicateGroups = new();
private Vector2 scroll;
[MenuItem("Tools/Object Name Modifier")]
public static void ShowWindow()
{
GetWindow<ObjectNameModifier>("Object Name Modifier");
}
private void OnGUI()
{
GUILayout.Label("Modify Object Names", EditorStyles.boldLabel);
prefixText = EditorGUILayout.TextField("Prefix", prefixText);
suffixText = EditorGUILayout.TextField("Suffix", suffixText);
if (GUILayout.Button("Apply Prefix and Suffix"))
ApplyPrefixAndSuffix();
GUILayout.Space(5);
if (GUILayout.Button("Remove First Character"))
RemoveFirstCharacter();
if (GUILayout.Button("Remove Last Character"))
RemoveLastCharacter();
GUILayout.Space(5);
if (GUILayout.Button("Replace Spaces with Underscores"))
ReplaceSpacesWithUnderscores();
GUILayout.Space(5);
// ▼▼▼ 추가된 부분: 하위 오브젝트 이름순 정렬 버튼 ▼▼▼
if (GUILayout.Button("Sort Children by Name (A-Z)") && Selection.activeGameObject != null)
{
SortChildrenByName(Selection.activeGameObject);
}
// ▲▲▲ 추가된 부분 끝 ▲▲▲
GUILayout.Space(20);
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
GUILayout.Label("Find and Fix Duplicate Names", EditorStyles.boldLabel);
rootObject = (GameObject)EditorGUILayout.ObjectField("Target Root Object", rootObject, typeof(GameObject), true);
if (GUILayout.Button("Find Duplicate Names") && rootObject != null)
{
FindDuplicateNames(rootObject);
}
if (duplicateGroups.Count > 0)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Duplicate Name List", EditorStyles.boldLabel);
scroll = EditorGUILayout.BeginScrollView(scroll, GUILayout.Height(200));
foreach (var kvp in duplicateGroups)
{
EditorGUILayout.LabelField($"{kvp.Key} ({kvp.Value.Count} items)");
foreach (var t in kvp.Value)
{
EditorGUILayout.ObjectField(" ↳", t, typeof(Transform), true);
}
}
EditorGUILayout.EndScrollView();
if (GUILayout.Button("Auto-Rename Duplicates"))
{
RenameDuplicates();
}
}
else if (rootObject != null)
{
EditorGUILayout.HelpBox("No duplicate names found.", MessageType.Info);
}
}
void ApplyPrefixAndSuffix()
{
foreach (GameObject obj in Selection.gameObjects)
{
Undo.RecordObject(obj, "Change Object Name");
obj.name = prefixText + obj.name + suffixText;
}
}
void RemoveFirstCharacter()
{
foreach (GameObject obj in Selection.gameObjects)
{
if (obj.name.Length > 0)
{
Undo.RecordObject(obj, "Remove First Character");
obj.name = obj.name.Substring(1);
}
}
}
void RemoveLastCharacter()
{
foreach (GameObject obj in Selection.gameObjects)
{
if (obj.name.Length > 0)
{
Undo.RecordObject(obj, "Remove Last Character");
obj.name = obj.name.Substring(0, obj.name.Length - 1);
}
}
}
void ReplaceSpacesWithUnderscores()
{
foreach (GameObject obj in Selection.gameObjects)
{
Undo.RecordObject(obj, "Replace Spaces");
obj.name = obj.name.Replace(" ", "_");
}
}
void FindDuplicateNames(GameObject root)
{
duplicateGroups.Clear();
Dictionary<string, List<Transform>> nameMap = new();
foreach (Transform t in root.GetComponentsInChildren<Transform>(true))
{
if (!nameMap.ContainsKey(t.name))
nameMap[t.name] = new List<Transform>();
nameMap[t.name].Add(t);
}
foreach (var kvp in nameMap)
{
if (kvp.Value.Count > 1)
{
duplicateGroups[kvp.Key] = kvp.Value;
}
}
}
void RenameDuplicates()
{
foreach (var kvp in duplicateGroups)
{
for (int i = 0; i < kvp.Value.Count; i++)
{
string newName = (i == 0) ? kvp.Key : $"{kvp.Key}_{i}";
Undo.RecordObject(kvp.Value[i], "Rename Duplicate");
kvp.Value[i].name = newName;
}
}
Debug.Log("Duplicate names have been renamed.");
FindDuplicateNames(rootObject); // 재검사
}
// ▼▼▼ 수정된 부분: 하위 오브젝트 이름순 정렬 함수 ▼▼▼
void SortChildrenByName(GameObject parent)
{
if (parent == null) return;
// 하위 오브젝트 리스트 가져오기
List<Transform> children = new List<Transform>();
for (int i = 0; i < parent.transform.childCount; i++)
{
children.Add(parent.transform.GetChild(i));
}
// 유니티 기본 정렬 순서로 정렬 (대소문자 구분 없음, 자연스러운 숫자 정렬)
children.Sort((a, b) => EditorUtility.NaturalCompare(a.name, b.name));
// 정렬된 순서대로 SiblingIndex 재설정
for (int i = 0; i < children.Count; i++)
{
Undo.SetTransformParent(children[i], parent.transform, "Sort Children By Name");
children[i].SetSiblingIndex(i);
}
Debug.Log($"{parent.name}의 하위 오브젝트들이 이름순으로 정렬되었습니다.");
}
// ▲▲▲ 수정된 부분 끝 ▲▲▲
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 508b4125473559440ad9e4a9efa9d308
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
using UnityEditor;
public class RecompileScript
{
[MenuItem("Tools/Recompile")]
public static void Recompile()
{
// 현재의 에디터 애플리케이션의 상태를 강제로 리컴파일합니다.
AssetDatabase.Refresh();
EditorUtility.RequestScriptReload();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5e68efe303abedf4c8f3b7374ad2fb73
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,127 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class RenameHumanoidBones : EditorWindow
{
private Animator selectedAnimator;
private Dictionary<HumanBodyBones, Transform> humanoidBones = new Dictionary<HumanBodyBones, Transform>();
private Dictionary<HumanBodyBones, string> newBoneNames = new Dictionary<HumanBodyBones, string>();
[MenuItem("Tools/Humanoid Bone Renamer")]
public static void ShowWindow()
{
GetWindow<RenameHumanoidBones>("Humanoid Bone Renamer");
}
private Vector2 scrollPosition;
private void OnGUI()
{
GUILayout.Label("Humanoid Bone Renamer", EditorStyles.boldLabel);
if (GUILayout.Button("Find Humanoid Bones"))
{
FindHumanoidBones();
}
if (selectedAnimator == null)
{
GUILayout.Label("No valid Humanoid Animator selected.", EditorStyles.helpBox);
return;
}
GUILayout.Label("Selected GameObject: " + selectedAnimator.gameObject.name, EditorStyles.boldLabel);
if (humanoidBones.Count > 0)
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(400));
EditorGUILayout.BeginVertical("box");
GUILayout.Label("Humanoid Bones", EditorStyles.boldLabel);
foreach (var bone in humanoidBones)
{
EditorGUILayout.BeginHorizontal();
// First column: Unity's Humanoid bone name
GUILayout.Label(bone.Key.ToString(), GUILayout.Width(150));
// Second column: Current bone name
GUILayout.Label(bone.Value != null ? bone.Value.name : "None", GUILayout.Width(150));
// Third column: Input field for new name (defaulting to the first column's name)
if (!newBoneNames.ContainsKey(bone.Key))
{
newBoneNames[bone.Key] = bone.Key.ToString();
}
newBoneNames[bone.Key] = EditorGUILayout.TextField(newBoneNames[bone.Key], GUILayout.Width(150));
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
EditorGUILayout.EndScrollView();
}
if (GUILayout.Button("Change Humanoid Bones Name"))
{
ChangeHumanoidBoneNames();
}
}
private void FindHumanoidBones()
{
selectedAnimator = null;
humanoidBones.Clear();
newBoneNames.Clear();
if (Selection.activeGameObject == null)
{
Debug.LogError("No GameObject selected. Please select a GameObject with an Animator component.");
return;
}
selectedAnimator = Selection.activeGameObject.GetComponent<Animator>();
if (selectedAnimator == null || selectedAnimator.avatar == null || !selectedAnimator.avatar.isValid || !selectedAnimator.avatar.isHuman)
{
Debug.LogError("Selected GameObject does not have a valid Humanoid Avatar.");
return;
}
for (int i = 0; i < HumanTrait.BoneCount; i++)
{
HumanBodyBones bone = (HumanBodyBones)i;
Transform boneTransform = selectedAnimator.GetBoneTransform(bone);
if (boneTransform != null)
{
humanoidBones[bone] = boneTransform;
}
}
Debug.Log("Humanoid bones found and ready for renaming.");
}
private void ChangeHumanoidBoneNames()
{
if (selectedAnimator == null)
{
Debug.LogError("No valid Humanoid Animator selected.");
return;
}
foreach (var bone in humanoidBones)
{
if (bone.Value != null && !string.IsNullOrWhiteSpace(newBoneNames[bone.Key]))
{
string newName = newBoneNames[bone.Key];
Debug.Log($"Renaming {bone.Value.name} to {newName}");
bone.Value.name = newName;
}
}
Debug.Log("Bone renaming completed.");
}
}

Some files were not shown because too many files have changed in this diff Show More