diff --git a/Editor/Inspector/ScaleAdjusterInspector.cs b/Editor/Inspector/ScaleAdjusterInspector.cs new file mode 100644 index 00000000..67ce7a62 --- /dev/null +++ b/Editor/Inspector/ScaleAdjusterInspector.cs @@ -0,0 +1,148 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEngine; +using static nadena.dev.modular_avatar.core.editor.Localization; + +namespace nadena.dev.modular_avatar.core.editor +{ + [CustomEditor(typeof(ModularAvatarScaleAdjuster))] + [CanEditMultipleObjects] + internal class ScaleAdjusterInspector : MAEditorBase + { + private SerializedProperty _scale; + + private ModularAvatarScaleAdjuster[] _sortedTargets; + private Vector3[] _originalScales; + + private Vector3 gizmoScale = Vector3.one; + + private bool _adjustChildPositions; + + protected void OnEnable() + { + _scale = serializedObject.FindProperty("m_Scale"); + + _sortedTargets = targets.Cast().OrderBy(TransformDepth).ToArray(); + _originalScales = _sortedTargets.Select(t => t.Scale).ToArray(); + } + + private int TransformDepth(ModularAvatarScaleAdjuster obj) + { + var t = obj.transform; + var depth = 0; + + while (t != null) + { + depth++; + t = t.parent; + } + + return depth; + } + + protected void OnDisable() + { + } + + public void OnSceneGUI() + { + Selection.selectionChanged -= UnhideTools; + Selection.selectionChanged += UnhideTools; + Tools.hidden = (Tools.current == Tool.Scale); + if (!Tools.hidden) return; + + var handlePos = _sortedTargets[0].transform; + EditorGUI.BeginChangeCheck(); + var handleSize = HandleUtility.GetHandleSize(handlePos.position); + gizmoScale = Handles.ScaleHandle(gizmoScale, handlePos.position, handlePos.rotation, handleSize); + if (EditorGUI.EndChangeCheck()) + { + for (int i = 0; i < _sortedTargets.Length; i++) + { + UpdateScale(i, handlePos); + } + } + } + + private void UpdateScale(int i, Transform refTransform) + { + var xform = _sortedTargets[i].transform; + var target = _sortedTargets[i]; + + Matrix4x4 initialTransform = xform.parent.localToWorldMatrix * Matrix4x4.TRS( + xform.localPosition, + xform.localRotation, + xform.localScale + ); + + Matrix4x4 initialScale = Matrix4x4.TRS( + Vector3.zero, + Quaternion.identity, + _originalScales[i] + ); + + Matrix4x4 newTransform = refTransform.localToWorldMatrix * Matrix4x4.TRS( + Vector3.zero, + Quaternion.identity, + gizmoScale + ); + + float scaleX = TransformVec(Vector3.right); + float scaleY = TransformVec(Vector3.up); + float scaleZ = TransformVec(Vector3.forward); + + Undo.RecordObject(target, "Adjust scale"); + var targetL2W = target.transform.localToWorldMatrix; + var baseToScaleCoord = (targetL2W * Matrix4x4.Scale(target.Scale)).inverse * targetL2W; + + target.Scale = new Vector3(scaleX, scaleY, scaleZ); + + var scaleToBaseCoord = Matrix4x4.Scale(target.Scale); + + PrefabUtility.RecordPrefabInstancePropertyModifications(target); + + // Update child positions + if (_adjustChildPositions) + { + var updateTransform = scaleToBaseCoord * baseToScaleCoord; + foreach (Transform child in target.transform) + { + Undo.RecordObject(child, "Adjust scale"); + child.localPosition = updateTransform.MultiplyPoint(child.localPosition); + PrefabUtility.RecordPrefabInstancePropertyModifications(child); + } + } + + float TransformVec(Vector3 vec) + { + // first, place our measurement vector into world spoce + vec = (initialTransform * initialScale).MultiplyVector(vec); + // now put it into reference space + vec = refTransform.worldToLocalMatrix.MultiplyVector(vec); + // and return using the adjusted scale + vec = newTransform.MultiplyVector(vec); + // now come back into local space + vec = (initialTransform.inverse).MultiplyVector(vec); + + return vec.magnitude; + } + } + + private static void UnhideTools() + { + Tools.hidden = false; + } + + protected override void OnInnerInspectorGUI() + { + EditorGUILayout.PropertyField(_scale, G("scale_adjuster.scale")); + + _adjustChildPositions = EditorGUILayout.Toggle(G("scale_adjuster.adjust_children"), _adjustChildPositions); + + serializedObject.ApplyModifiedProperties(); + + Localization.ShowLanguageUI(); + } + } +} \ No newline at end of file diff --git a/Editor/Inspector/ScaleAdjusterInspector.cs.meta b/Editor/Inspector/ScaleAdjusterInspector.cs.meta new file mode 100644 index 00000000..c5972bfe --- /dev/null +++ b/Editor/Inspector/ScaleAdjusterInspector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2632280a7a3445d68589d63458159b8c +timeCreated: 1703662098 \ No newline at end of file diff --git a/Editor/Localization/en-us.json b/Editor/Localization/en-us.json index a4464709..a6c63387 100644 --- a/Editor/Localization/en-us.json +++ b/Editor/Localization/en-us.json @@ -232,5 +232,7 @@ "setup_outfit.err.no_animator": "Your avatar does not have an Animator component.", "setup_outfit.err.no_hips": "Your avatar does not have a Hips bone. Setup Outfit only works on humanoid avatars.", "setup_outfit.err.no_outfit_hips": "Unable to identify the Hips object for the outfit. Searched for objects containing the following names:", - "move_independently.group-header": "Objects to move together" + "move_independently.group-header": "Objects to move together", + "scale_adjuster.scale": "Scale adjustment", + "scale_adjuster.adjust_children": "Adjust position of child objects" } \ No newline at end of file diff --git a/Editor/Localization/ja-jp.json b/Editor/Localization/ja-jp.json index a8b10b5a..ac35563f 100644 --- a/Editor/Localization/ja-jp.json +++ b/Editor/Localization/ja-jp.json @@ -193,5 +193,7 @@ "setup_outfit.err.no_animator": "アバターにAnimatorコンポーネントがありません。", "setup_outfit.err.no_hips": "アバターにHipsボーンがありません。なお、Setup Outfitはヒューマノイドアバター以外には対応していません。", "setup_outfit.err.no_outfit_hips": "衣装のHipsボーンを発見できませんでした。以下の名前を含むボーンを探しました:", - "move_independently.group-header": "一緒に動かすオブジェクト" + "move_independently.group-header": "一緒に動かすオブジェクト", + "scale_adjuster.scale": "Scale調整値", + "scale_adjuster.adjust_children": "子オブジェクトの位置を調整" } diff --git a/Editor/MergeArmatureHook.cs b/Editor/MergeArmatureHook.cs index 3b38869e..58592f1f 100644 --- a/Editor/MergeArmatureHook.cs +++ b/Editor/MergeArmatureHook.cs @@ -78,6 +78,11 @@ internal void OnPreprocessAvatar(ndmf.BuildContext context, GameObject avatarGam TopoProcessMergeArmatures(mergeArmatures); #if MA_VRCSDK3_AVATARS + foreach (var c in avatarGameObject.transform.GetComponentsInChildren(true)) + { + BoneDatabase.AddMergedBone(c.transform); + } + foreach (var c in avatarGameObject.transform.GetComponentsInChildren(true)) { if (c.rootTransform == null) c.rootTransform = c.transform; diff --git a/Runtime/MAMoveIndependently.cs b/Runtime/MAMoveIndependently.cs index 179bb108..1bf2d269 100644 --- a/Runtime/MAMoveIndependently.cs +++ b/Runtime/MAMoveIndependently.cs @@ -56,7 +56,6 @@ void Awake() private void OnValidate() { - Debug.Log("=== OnValidate"); hideFlags = HideFlags.DontSave; _excluded = new HashSet(); if (m_groupedBones == null) @@ -110,6 +109,7 @@ private void CheckChildren(Transform parent) foreach (Transform child in parent) { if (_excluded.Contains(child)) continue; + if (child.GetComponent() != null) continue; _observed.Add(child); diff --git a/Runtime/ModularAvatarScaleAdjuster.cs b/Runtime/ModularAvatarScaleAdjuster.cs new file mode 100644 index 00000000..84eea760 --- /dev/null +++ b/Runtime/ModularAvatarScaleAdjuster.cs @@ -0,0 +1,268 @@ +using System; +using System.Collections.Generic; +using System.Linq; +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; + +namespace nadena.dev.modular_avatar.core +{ + [Serializable] + internal struct ScalePatch + { + public SkinnedMeshRenderer smr; + public int boneIndex; + + public ScalePatch(SkinnedMeshRenderer smr, int boneIndex) + { + this.smr = smr; + this.boneIndex = boneIndex; + } + + public bool Equals(ScalePatch other) + { + return smr.Equals(other.smr) && boneIndex == other.boneIndex; + } + + public override bool Equals(object obj) + { + return obj is ScalePatch other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return (smr.GetHashCode() * 397) ^ boneIndex; + } + } + } + + [ExecuteInEditMode] + [DisallowMultipleComponent] + [AddComponentMenu("Modular Avatar/MA Scale Adjuster")] + [HelpURL("https://modular-avatar.nadena.dev/docs/reference/scale-adjuster?lang=auto")] + public sealed class ModularAvatarScaleAdjuster : AvatarTagComponent + { + [SerializeField] private Vector3 m_Scale = Vector3.one; + + public Vector3 Scale + { + get => m_Scale; + set + { + m_Scale = value; + Update(); + } + } + + [SerializeField] internal Transform scaleProxy; + + [SerializeField] private List patches = new List(); + + private bool initialized = false; + +#if UNITY_EDITOR + private void Update() + { + if (this == null) return; + + PatchRenderers(); + + scaleProxy.localScale = m_Scale; + } + + void OnValidate() + { + initialized = false; + EditorApplication.delayCall += Update; + } + + private void PatchRenderers() + { + if (initialized || this == null) return; + + if (PrefabUtility.IsPartOfPrefabInstance(this)) + { + // Ensure we're using the same ScaleProxy as the corresponding prefab asset. + var prefab = PrefabUtility.GetCorrespondingObjectFromSource(this); + if (this.scaleProxy == null || prefab.scaleProxy == null || prefab.scaleProxy != + PrefabUtility.GetCorrespondingObjectFromSource(this.scaleProxy)) + { + if (prefab.scaleProxy == null && scaleProxy != null) + { + // Push our ScaleProxy down into the prefab (this happens after applying the ScaleAdjuster + // component to a prefab) + var assetPath = AssetDatabase.GetAssetPath(prefab); + PrefabUtility.ApplyAddedGameObject(scaleProxy.gameObject, assetPath, + InteractionMode.AutomatedAction); + prefab.scaleProxy = PrefabUtility.GetCorrespondingObjectFromSource(this.scaleProxy); + } + else + { + // Clear any duplicate scaleProxy we have + + if (scaleProxy != null) DestroyImmediate(scaleProxy.gameObject); + } + + var so = new SerializedObject(this); + var sp = so.FindProperty(nameof(scaleProxy)); + PrefabUtility.RevertPropertyOverride(sp, InteractionMode.AutomatedAction); + so.ApplyModifiedPropertiesWithoutUndo(); + + // Find the corresponding child + foreach (Transform t in transform) + { + if (PrefabUtility.GetCorrespondingObjectFromSource(t) == prefab.scaleProxy) + { + scaleProxy = t; + break; + } + } + } + } + + if (scaleProxy == null && !PrefabUtility.IsPartOfPrefabAsset(this)) + { + scaleProxy = new GameObject(gameObject.name + " (Scale Proxy)").transform; + scaleProxy.SetParent(transform, false); + scaleProxy.localPosition = Vector3.zero; + scaleProxy.localRotation = Quaternion.identity; + scaleProxy.localScale = m_Scale; + scaleProxy.gameObject.AddComponent(); + PrefabUtility.RecordPrefabInstancePropertyModifications(this); + } + + if (scaleProxy != null) + { + scaleProxy.hideFlags = HideFlags.HideInHierarchy; + + RewriteBoneReferences(transform, scaleProxy); + } + + initialized = true; + } + + private void RewriteBoneReferences(Transform oldBone, Transform newBone, Transform selfTransform = null) + { + if (selfTransform == null) selfTransform = transform; + + var prefabNewBone = PrefabUtility.GetCorrespondingObjectFromSource(newBone); + + var oldPatches = new HashSet(this.patches); + var newPatches = new HashSet(); + var avatarRoot = RuntimeUtil.FindAvatarInParents(selfTransform); + + if (avatarRoot != null) + { + foreach (var smr in avatarRoot.GetComponentsInChildren(true)) + { + var serializedObject = new SerializedObject(smr); + var bonesArray = serializedObject.FindProperty("m_Bones"); + int boneCount = bonesArray.arraySize; + + var parentSmr = PrefabUtility.GetCorrespondingObjectFromSource(smr); + var parentBones = parentSmr != null ? parentSmr.bones : null; + var propMods = PrefabUtility.GetPropertyModifications(smr); + + bool changed = false; + + for (int i = 0; i < boneCount; i++) + { + var boneProp = bonesArray.GetArrayElementAtIndex(i); + var bone = boneProp.objectReferenceValue as Transform; + if (bone == oldBone || bone == newBone || + (bone == null && oldPatches.Contains(new ScalePatch(smr, i)))) + { + if (parentBones != null && parentBones[i] == prefabNewBone) + { + // Remove any prefab overrides for this bone entry + changed = boneProp.objectReferenceValue != newBone; + boneProp.objectReferenceValue = newBone; + serializedObject.ApplyModifiedPropertiesWithoutUndo(); + PrefabUtility.RevertPropertyOverride(boneProp, InteractionMode.AutomatedAction); + } + else + { + boneProp.objectReferenceValue = newBone; + changed = true; + } + + newPatches.Add(new ScalePatch(smr, i)); + } + } + + if (changed) + { + serializedObject.ApplyModifiedPropertiesWithoutUndo(); + + ConfigurePrefab(); + } + } + + if (this != null && newPatches != oldPatches) + { + this.patches = newPatches.ToList(); + PrefabUtility.RecordPrefabInstancePropertyModifications(this); + } + } + } + + private void ConfigurePrefab() + { + if (this == null || !PrefabUtility.IsPartOfPrefabInstance(this)) return; + var source = PrefabUtility.GetCorrespondingObjectFromSource(this); + var path = AssetDatabase.GetAssetPath(source); + var root = PrefabUtility.LoadPrefabContents(path); + + foreach (var obj in root.GetComponentsInChildren()) + { + obj.PatchRenderers(); + } + + PrefabUtility.SaveAsPrefabAsset(root, path); + PrefabUtility.UnloadPrefabContents(root); + + initialized = false; + } + + protected override void OnDestroy() + { + base.OnDestroy(); + + UnpatchRenderers(); + } + + private void UnpatchRenderers() + { + var scaleProxy2 = this.scaleProxy; + var transform2 = this.transform; + + EditorApplication.delayCall += () => + { + if (scaleProxy2 == null) return; + + if (transform2 != null) + { + RewriteBoneReferences(scaleProxy2, transform2, transform2); + } + + try + { + DestroyImmediate(scaleProxy2.gameObject); + } + catch (InvalidOperationException e) + { + // not supported in Unity 2019... + } + }; + } +#else + private void Update() + { + // placeholder to make builds work + } +#endif + } +} \ No newline at end of file diff --git a/Runtime/ModularAvatarScaleAdjuster.cs.meta b/Runtime/ModularAvatarScaleAdjuster.cs.meta new file mode 100644 index 00000000..4a876858 --- /dev/null +++ b/Runtime/ModularAvatarScaleAdjuster.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 09a660aa9d4e47d992adcac5a05dd808 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ScaleProxy.cs b/Runtime/ScaleProxy.cs new file mode 100644 index 00000000..807b6fb8 --- /dev/null +++ b/Runtime/ScaleProxy.cs @@ -0,0 +1,82 @@ +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; + +namespace nadena.dev.modular_avatar.core +{ + [AddComponentMenu("")] + internal sealed class ScaleProxy : AvatarTagComponent + { +#if UNITY_EDITOR + void OnValidate() + { + EditorApplication.delayCall += DeferredValidate; + } + + private void DeferredValidate() + { + if (this == null) return; + + gameObject.hideFlags = HideFlags.HideInHierarchy; + + var parentObject = transform.parent; + var parentScaleAdjuster = + parentObject != null ? parentObject.GetComponent() : null; + + if (parentScaleAdjuster == null || parentScaleAdjuster.scaleProxy != transform) + { + if (PrefabUtility.IsPartOfPrefabAsset(this)) + { + var path = AssetDatabase.GetAssetPath(this); + var root = PrefabUtility.LoadPrefabContents(path); + + foreach (var obj in root.GetComponentsInChildren()) + { + obj.DeferredValidate(); + } + + PrefabUtility.SaveAsPrefabAsset(root, path); + PrefabUtility.UnloadPrefabContents(root); + } + else + { + SelfDestruct(); + } + } + } + + private void SelfDestruct() + { + var root = ndmf.runtime.RuntimeUtil.FindAvatarInParents(transform); + if (root == null) + { + root = transform; + while (root.parent != null) root = root.parent; + } + + foreach (var smr in root.GetComponentsInChildren(true)) + { + var bones = smr.bones; + bool changed = false; + + for (var i = 0; i < bones.Length; i++) + { + if (bones[i] == transform) + { + bones[i] = transform.parent; + changed = true; + } + } + + if (changed) + { + smr.bones = bones; + } + } + + DestroyImmediate(gameObject); + } +#endif + } +} \ No newline at end of file diff --git a/Runtime/ScaleProxy.cs.meta b/Runtime/ScaleProxy.cs.meta new file mode 100644 index 00000000..5fcb9766 --- /dev/null +++ b/Runtime/ScaleProxy.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3f0c19b32ba845a2a84f37f48e4ec4d5 +timeCreated: 1703659053 \ No newline at end of file diff --git a/UnitTests~/ComponentSettingsTest.cs b/UnitTests~/ComponentSettingsTest.cs index 603e5f0a..38c05392 100644 --- a/UnitTests~/ComponentSettingsTest.cs +++ b/UnitTests~/ComponentSettingsTest.cs @@ -39,6 +39,7 @@ public void CheckDisallowMultipleComponentIsSpecified(Type type) if (type == typeof(Activator)) return; if (type == typeof(AvatarActivator)) return; if (type == typeof(TestComponent)) return; + if (type == typeof(ScaleProxy)) return; // get icon var component = (MonoBehaviour) _gameObject.AddComponent(type); @@ -63,6 +64,7 @@ public void CheckHelpURL(Type type) if (type == typeof(Activator)) return; if (type == typeof(AvatarActivator)) return; if (type == typeof(TestComponent)) return; + if (type == typeof(ScaleProxy)) return; // get icon var helpUrl = type.GetCustomAttribute(); diff --git a/docs~/docs/reference/move-independently.md b/docs~/docs/reference/move-independently.md index f61bc68b..3db90839 100644 --- a/docs~/docs/reference/move-independently.md +++ b/docs~/docs/reference/move-independently.md @@ -20,4 +20,6 @@ For example, you might move the hips and upper leg objects together, but leave t ## Limitations While this component supports scaling an object independently of its children, non-uniform scales (where the X, Y, and Z -scales are not all the same) are not fully supported, and may result in unexpected behavior. +scales are not all the same) are not fully supported, and may result in unexpected behavior. If you need to adjust the +scale of each axis independently, you should use the [Scale Adjuster](scale-adjuster.md) component in addition to Move +Independently. diff --git a/docs~/docs/reference/scale-adjuster-warning-trs-tool.png b/docs~/docs/reference/scale-adjuster-warning-trs-tool.png new file mode 100644 index 00000000..488e1eb6 Binary files /dev/null and b/docs~/docs/reference/scale-adjuster-warning-trs-tool.png differ diff --git a/docs~/docs/reference/scale-adjuster.md b/docs~/docs/reference/scale-adjuster.md new file mode 100644 index 00000000..51154d86 --- /dev/null +++ b/docs~/docs/reference/scale-adjuster.md @@ -0,0 +1,37 @@ +# Scale Adjuster + +![Scale Adjuster](scale-adjuster.png) + +The Scale Adjuster component allows you to adjust the X/Y/Z scales of a specific bone without causing issues with +rotated child bones. + +## When should I use it? + +This component is primarily intended for use when fitting clothing not originally designed for your avatar. You can use +this to adjust the dimensions of specific bones, without breaking child bones. + +## When shouldn't I use it? + +When adjusting the overall scale of a bone (where X/Y/Z are being adjusted equally), it's usually better to use the +normal unity scale tools. + +## Setting up Scale Adjuster + +Simply add the Scale Adjuster component to the bone in question. Now, when you have the scaling tool selected, changes +will affect only this one bone. + +You can check or uncheck the "Adjust child positions" checkbox to adjust the relative position of child bones when the +scale of their parent changes. This is useful when you want to adjust the scale of a bone, but don't want to move the +child bones. Note that this adjusts only the _position_ of child bones, and not their scale. + +Scale Adjuster supports adjusting the scale of multiple bones by adding the Scale Component to all of the bones in +question, then selecting multiple bones before adjusting their scale. However, if these bones are rotated, the scale +adjustment won't be perfect, and may not give quite the results you expect. + +:::warning + +![Use this, not that](scale-adjuster-warning-trs-tool.png) + +Scale Adjuster only controls the unity scale tool. The combined Move/Rotate/Scale tool will still affect all children. + +::: \ No newline at end of file diff --git a/docs~/docs/reference/scale-adjuster.png b/docs~/docs/reference/scale-adjuster.png new file mode 100644 index 00000000..5ea30d0a Binary files /dev/null and b/docs~/docs/reference/scale-adjuster.png differ diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/move-independently.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/move-independently.md index 0bc99f0d..820a46bd 100644 --- a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/move-independently.md +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/move-independently.md @@ -20,4 +20,4 @@ MA Move Independentlyというコンポーネントを使うと、子オブジ ## 制限事項 子に影響を与えずにオブジェクトの大きさを調整することは対応していますが、XYZそれぞれのスケールが同じ出ない場合は -対応されず、変な挙動になることがあります。ご注意ください。 \ No newline at end of file +対応されず、変な挙動になることがあります。個別に調整する必要がある場合は、「[Scale Adjuster](scale-adjuster.md)」を併用しましょう。 \ No newline at end of file diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster-warning-trs-tool.png b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster-warning-trs-tool.png new file mode 100644 index 00000000..488e1eb6 Binary files /dev/null and b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster-warning-trs-tool.png differ diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster.md new file mode 100644 index 00000000..de88db0a --- /dev/null +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster.md @@ -0,0 +1,32 @@ +# Scale Adjuster + +![Scale Adjuster](scale-adjuster.png) + +Scale Adjusterを付けることで、回転した子ボーンに影響を与えることなく、ボーンのX/Y/Zスケールを個別に調整することができます。 + +## いつ使うべきか? + +このコンポーネントは、非対応衣装を導入するときに利用することを想定しています。これを利用すると、子ボーンを壊すことなく、 +特定のボーンの寸法を調整できます。 + +## 非推奨の場合 + +ボーンの全体のスケールを変更する場合(X/Y/Zが連動して変わる場合)は、Unity標準のスケールツールを使いましょう。 + +## 設定方法 + +該当のボーンにScale Adjusterコンポーネントを追加します。これで、スケールツールを選択したときに、このボーンのみに影響が出ます。 + +「子オブジェクトの位置を調整」という設定を入れると、親のスケールが変わる時は子ボーンの相対位置も調整されます。逆にオフにすることで、 +子ボーンを移動させずに親のスケールを調整できます。なお、子ボーンの位置のみが調整されます。子ボーンのスケールが変更されません。 + +複数のボーンのスケールを同時に調整することも対応しています。該当するボーンすべてにScale Adjusterを追加して、複数選択でスケール調整 +するとすべて同時に編集できます。ただし、ボーンが回転された場合は完璧に連動が取れない場合があります。ご注意ください。 + +:::warning + +![こっちのほうを使いましょう](scale-adjuster-warning-trs-tool.png) + +Scale Adjusterはスケール(拡大縮小)ツールのみに対応しています。移動・回転・スケールをまとめたツールでは、将来通り子ボーンにも影響が出ます。 + +::: \ No newline at end of file diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster.png b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster.png new file mode 100644 index 00000000..2b2de4b6 Binary files /dev/null and b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/scale-adjuster.png differ