캐릭터 움직임 및 애니메이션

This commit is contained in:
2026-03-09 15:31:30 +09:00
parent 1e8c23e128
commit 2aa52746e9
7613 changed files with 11324200 additions and 1724 deletions

View File

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

View File

@@ -0,0 +1,24 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ef83fc4eb87f44f279e62ce434242341, type: 3}
m_Name: Animation_PropBoneBindingConfig_Default
m_EditorClassIdentifier:
sourceRig: {fileID: 0}
targetRig: {fileID: 0}
propBoneDefinitions:
- parentBoneName: Hand_R
boneName: Prop_R
socketName: Prop_R_Socket
rotationOffset: {x: 0, y: 0, z: 0}
scale: 1
scaleCalculationBone1: Hand_R
scaleCalculationBone2: Elbow_R

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 950ef8de5862a40f286d0522ed646a00
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ef83fc4eb87f44f279e62ce434242341, type: 3}
m_Name: BigRig_01_PropBoneBindingConfig
m_EditorClassIdentifier:
sourceRig: {fileID: 919132149155446097, guid: b6b4b9f2696465340b5168dd9e2ca6b3, type: 3}
targetRig: {fileID: 919132149155446097, guid: 680888a45be9b438b8892609902cb5ea, type: 3}
propBoneDefinitions:
- parentBoneName: Hand_R
boneName: Prop_R
socketName: Prop_R_Socket
rotationOffset: {x: 3.9506698, y: 174.03122, z: 0.034283083}
scale: 1.2121843
scaleCalculationBone1: Hand_R
scaleCalculationBone2: Elbow_R

View File

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

View File

@@ -0,0 +1,24 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ef83fc4eb87f44f279e62ce434242341, type: 3}
m_Name: KidRig_01_PropBoneBindingConfig
m_EditorClassIdentifier:
sourceRig: {fileID: 919132149155446097, guid: b6b4b9f2696465340b5168dd9e2ca6b3, type: 3}
targetRig: {fileID: 919132149155446097, guid: f3859a60011274e39a1dd372974c01b8, type: 3}
propBoneDefinitions:
- parentBoneName: Hand_R
boneName: Prop_R
socketName: Prop_R_Socket
rotationOffset: {x: 0.0000038260173, y: 0.000004915283, z: 270.14746}
scale: 0.5464112
scaleCalculationBone1: Hand_R
scaleCalculationBone2: Elbow_R

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6a4523363b8e641ea9bb8276d6e8554e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ef83fc4eb87f44f279e62ce434242341, type: 3}
m_Name: POLYGONRig_01_PropBoneBindingConfig
m_EditorClassIdentifier:
sourceRig: {fileID: 919132149155446097, guid: b6b4b9f2696465340b5168dd9e2ca6b3, type: 3}
targetRig: {fileID: 100016, guid: 2dc7b382d25903545b405802eb2198ab, type: 3}
propBoneDefinitions:
- parentBoneName: Hand_R
boneName: Prop_R
socketName: Prop_R_Socket
rotationOffset: {x: 0.0000038260173, y: 0.000004915283, z: 270.14746}
scale: 1
scaleCalculationBone1: Hand_R
scaleCalculationBone2: Elbow_R

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 78d20d05e4a7448f69124477d9830caa
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,227 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// Controls UI interactions for the selected PropBoneBinder components.
/// </summary>
[CustomEditor(typeof(PropBoneBinder))]
[CanEditMultipleObjects]
public class PropBoneBinderEditor : Editor
{
private List<PropBoneBinder> _binders = new List<PropBoneBinder>();
private static bool _moreOptionsFoldout = false;
/// <summary>
/// Sets up the binders based on the current targets.
/// </summary>
private void OnEnable()
{
_binders.Clear();
for (int i = 0; i < targets.Length; i++)
{
if (targets[i] is PropBoneBinder)
{
_binders.Add(targets[i] as PropBoneBinder);
}
}
}
/// <summary>
/// Draws the Unity editor gui for the selected PropBoneBinder components.
/// </summary>
public override void OnInspectorGUI()
{
GUIStyle statusStyle = new GUIStyle(EditorStyles.helpBox);
string status = "";
bool editorDisabled = false;
// help text
if (_binders.Count == 1)
{
editorDisabled = PropBoneBinderEditorUtil.IsPrefabAsset(_binders[0]);
if (_binders[0].animator != null)
{
if (_binders[0].propBoneConfig != null)
{
if (_binders[0].IsPropBoneHierarchyConfigured())
{
if (_binders[0].AreBindingsConfigured())
{
statusStyle.normal.textColor = Color.green;
status = "This character has been set up!";
}
else
{
statusStyle.normal.textColor = Color.yellow;
status = "Bindings are not configured. You may need to bind the prop bones.";
}
}
else
{
statusStyle.normal.textColor = Color.yellow;
status = "Prop bones are not configured. You may need to create prop bones.";
}
}
else
{
statusStyle.normal.textColor = Color.red;
status = "Prop bone config is null. Setup a prop bone config.";
}
}
else
{
statusStyle.normal.textColor = Color.red;
status = "This character is not set up. Try 'One Click Setup'.";
}
}
else
{
int configuredCount = 0;
for (int i = 0; i < _binders.Count; ++i)
{
editorDisabled = editorDisabled || PropBoneBinderEditorUtil.IsPrefabAsset(_binders[i]);
if (_binders[i].IsConfigured)
{
configuredCount++;
}
}
if (configuredCount < _binders.Count)
{
statusStyle.normal.textColor = Color.red;
status = $"{configuredCount} of the {_binders.Count} selected characters are set up.";
}
else
{
statusStyle.normal.textColor = Color.green;
status = $"All {_binders.Count} selected characters are set up.";
}
}
EditorGUILayout.TextField("Setup Status", status, statusStyle);
GUILayout.Space(20);
if (editorDisabled)
{
EditorGUILayout.LabelField("Open prefab in edit mode or create an instance in the scene to set up prop bones.");
}
EditorGUI.BeginDisabledGroup(editorDisabled);
{
// quick setup options
if (GUILayout.Button("One Click Setup", GUILayout.Height(50)))
{
PropBoneBinderEditorUtil.AutomaticSetup(_binders);
}
}
EditorGUI.EndDisabledGroup();
GUILayout.Space(20);
DrawDefaultInspector();
// More Options
GUILayout.Space(20);
_moreOptionsFoldout = EditorGUILayout.Foldout(_moreOptionsFoldout, "More Options");
if (_moreOptionsFoldout)
{
EditorGUI.indentLevel++;
if (GUILayout.Button("Create New Bone Config Asset", GUILayout.Height(40)))
{
PropBoneBinderEditorUtil.CreateNewBoneConfigs(_binders);
}
GUILayout.Space(20);
if (editorDisabled)
{
EditorGUILayout.LabelField("Open prefab in edit mode or create an instance in the scene to edit prop bones.");
}
EditorGUI.BeginDisabledGroup(editorDisabled);
{
if (GUILayout.Button("Setup Animator Reference", GUILayout.Height(30)))
{
PropBoneBinderEditorUtil.SetupAnimatorReferences(_binders);
}
if (GUILayout.Button("Setup Prop Bone Config", GUILayout.Height(30)))
{
PropBoneBinderEditorUtil.SetupPropBoneConfigs(_binders);
}
if (GUILayout.Button("Create Prop Bones", GUILayout.Height(30)))
{
PropBoneBinderEditorUtil.CreatePropBones(_binders);
}
if (GUILayout.Button("Bind Prop Bones", GUILayout.Height(30)))
{
PropBoneBinderEditorUtil.BindPropBones(_binders);
}
GUILayout.Space(20);
if (GUILayout.Button("Reset", GUILayout.Height(30)))
{
PropBoneBinderEditorUtil.AutomaticReset(_binders);
}
}
EditorGUI.EndDisabledGroup();
EditorGUI.indentLevel--;
}
}
/// <summary>
/// Attempts to perform all the necessary steps required to configure the selected characters with prop bones.
/// </summary>
[MenuItem("Synty/Tools/Animation/Setup Prop Bones")]
private static void SetupSyntyPropBones()
{
List<PropBoneBinder> binders = new List<PropBoneBinder>();
for (int i = 0; i < Selection.objects.Length; ++i)
{
if (Selection.objects[i] is GameObject)
{
if (PrefabUtility.IsPartOfPrefabAsset(Selection.objects[i]))
{
Debug.LogWarning($"Cannot edit prefab asset {Selection.objects[i].name}. Open the asset in prefab edit mode or create a scene instance and try again.", Selection.objects[i]);
continue;
}
GameObject gameObject = Selection.objects[i] as GameObject;
Animator animator = gameObject.GetComponent<Animator>();
if (animator != null)
{
PropBoneBinder binder = gameObject.GetComponent<PropBoneBinder>();
if (binder == null)
{
binder = gameObject.AddComponent<PropBoneBinder>();
}
if (binder != null)
{
Debug.Log($"Setting up prop bones for game object.", gameObject);
binders.Add(binder);
}
}
}
}
if (binders.Count > 0)
{
PropBoneBinderEditorUtil.AutomaticSetup(binders);
}
else
{
Debug.LogError("Select some characters and try again.");
}
}
}
}

View File

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

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// Controls UI interactions for the selected PropBoneConfig asset.
/// </summary>
[CustomEditor(typeof(PropBoneConfig))]
[CanEditMultipleObjects]
public class PropBoneConfigEditor : Editor
{
private List<PropBoneConfig> _configs = new List<PropBoneConfig>();
/// <summary>
/// Sets up the _configs based on the current targets.
/// </summary>
private void OnEnable()
{
_configs.Clear();
for (int i = 0; i < targets.Length; i++)
{
if (targets[i] is PropBoneConfig)
{
_configs.Add(targets[i] as PropBoneConfig);
}
}
}
/// <summary>
/// Draws the Unity editor gui for the selected PropBoneConfig assets.
/// </summary>
public override void OnInspectorGUI()
{
DrawDefaultInspector();
EditorGUILayout.Space(20);
if (GUILayout.Button("Calculate Bone Offset Values", GUILayout.Height(30)))
{
for (int i = 0; i < _configs.Count; ++i)
{
_configs[i].CalculateOffsetValues();
}
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,466 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
#else
using UnityEditor.Experimental.SceneManagement;
#endif
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// Utility class to help configure many characters with prop bone binders at once.
/// </summary>
public static class PropBoneBinderEditorUtil
{
// Path where the prop bone binder tool stores the default config file.
private const string CONFIG_ASSET_NAME_DEFAULT = "Animation_PropBoneBindingConfig_Default.asset";
private const string CONFIG_ASSET_PATH_DEFAULT = FOLDER_PATH_DEFAULT + CONFIG_ASSET_NAME_DEFAULT;
// Folder path where the prop bone binder tool stores config files by default.
private const string FOLDER_PATH_DEFAULT = "Assets/Synty/Tools/SyntyPropBoneTool/Configs/";
/// <summary>
/// Generates a file name based on the targetRigName and the defined config folder path.
/// </summary>
/// <returns>A <c>string</c> that can be used as a file path for a config asset. This path is not gauranteed to be unique.</returns>
private static string GenerateNewConfigFileName(string targetRigName)
{
PropBoneConfigAsset defaultAsset = GetDefaultConfigAsset();
string targetFileName = targetRigName + "_PropBoneBindingConfig.asset";
string targetPath = defaultAsset.path.Replace(CONFIG_ASSET_NAME_DEFAULT, targetFileName);
return targetPath;
}
/// <summary>
/// Class used to help manage config assets in the AssetDatabase.
/// </summary>
private class PropBoneConfigAsset
{
public bool savedInAssetDatabase; // is true when loaded from or saved to the AssetDatabase
public PropBoneConfig config;
public string path;
}
/// <summary>
/// Returns true if the given PropBneBinder is part of a prefab asset.
/// </summary>
/// <param name="binder">The PropBoneBinder to test if it is part of a prefab asset.</param>
/// <returns>A <c>bool</c>. True when the binder is part of a prefab asset.</returns>
public static bool IsPrefabAsset(PropBoneBinder binder)
{
return PrefabUtility.IsPartOfPrefabAsset(binder);
}
/// <summary>
/// Attemps to automatically configure all given PropBoneBinders.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to setup.</param>
public static void AutomaticSetup(List<PropBoneBinder> binders)
{
SetupAnimatorReferences(binders);
SetupPropBoneConfigs(binders);
CreatePropBones(binders);
BindPropBones(binders);
}
/// <summary>
/// Attemps to reset all given PropBoneBinders.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to reset.</param>
public static void AutomaticReset(List<PropBoneBinder> binders)
{
for (int i = 0; i < binders.Count; ++i)
{
if (IsPrefabAsset(binders[i]))
{
Debug.LogWarning($"Cannot edit prefab asset {binders[i].gameObject.name}. Open the asset in prefab edit mode or create a scene instance and try again.", binders[i].gameObject);
continue;
}
binders[i].Reset();
}
}
/// <summary>
/// Attempts to find the first PropBoneConfigAsset with the given fileName.
/// </summary>
/// <param name="fileName">The file name of the config to find.</param>
/// <returns>The first found <c>PropBoneConfigAsset</c> with file name matching the given fileName or null if none is found.</returns>
private static PropBoneConfigAsset FindFirstConfig(string fileName)
{
string[] guids = AssetDatabase.FindAssets("t:PropBoneConfig");
for (int i = 0; i < guids.Length; ++i)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
if (path.Contains(fileName))
{
return LoadPropBoneConfig(path);
}
}
return null;
}
/// <summary>
/// Attempts to load the default PropBoneConfigAsset or if one is not found creates a new default PropBoneConfigAsset.
/// </summary>
/// <returns>A <c>PropBoneConfigAsset</c> that contains the default settings to use for PropBoneBingings.</returns>
private static PropBoneConfigAsset GetDefaultConfigAsset()
{
PropBoneConfigAsset defaultConfigAsset = FindFirstConfig(CONFIG_ASSET_NAME_DEFAULT);
if (defaultConfigAsset == null)
{
PropBoneConfig defaultConfig = CreatePropBoneConfig(PropBoneDefinitionPresets.PolygonBoneDefinition);
defaultConfigAsset = CreatePropBoneConfigAsset(defaultConfig, CONFIG_ASSET_PATH_DEFAULT);
SavePropBoneAssetToProject(defaultConfigAsset);
}
return defaultConfigAsset;
}
/// <summary>
/// Attempts to load the default PropBoneConfig or if one is not found creates a new default PropBoneConfig.
/// </summary>
/// <returns>A <c>PropBoneConfig</c> that contains the default settings to use for PropBoneBingings.</returns>
private static PropBoneConfig GetDefaultConfig()
{
return GetDefaultConfigAsset().config;
}
/// <summary>
/// Attempts to find and load a config that matched the given sourceRig and targetRig.
/// </summary>
/// <param name="sourceRig">The source rig to match when finding the PropBoneConfig.</param>
/// <param name="targetRig">The target rig to match when finding the PropBoneConfig.</param>
/// <returns>A <c>PropBoneConfig</c> that matches the sourceRig and targetRig or returns the default PropBoneConfig if a match is not found.</returns>
public static PropBoneConfig FindFirstMatchingConfig(GameObject sourceRig, GameObject targetRig)
{
string[] guids = AssetDatabase.FindAssets("t:PropBoneConfig");
for (int i = 0; i < guids.Length; ++i)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
PropBoneConfig config = LoadPropBoneConfig(path).config;
if (config != null)
{
if (config.targetRig == targetRig && config.sourceRig == sourceRig)
{
return config;
}
}
}
return GetDefaultConfig();
}
/// <summary>
/// Attempts to find matching PropBoneConfigs or creates a new ones and assigns them to all the given PropBoneBinders.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to setup.</param>
public static void SetupPropBoneConfigs(List<PropBoneBinder> binders)
{
PropBoneConfig defaultConfig = GetDefaultConfig();
for (int i = 0; i < binders.Count; ++i)
{
if (IsPrefabAsset(binders[i]))
{
Debug.LogWarning($"Cannot edit prefab asset {binders[i].gameObject.name}. Open the asset in prefab edit mode or create a scene instance and try again.", binders[i].gameObject);
continue;
}
if (binders[i].propBoneConfig == null)
{
GameObject targetRig = null;
if (binders[i].animator != null)
{
targetRig = binders[i].animator.gameObject;
}
if (PrefabUtility.IsPartOfPrefabInstance(targetRig))
{
string path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(targetRig);
if (path != null)
{
targetRig = AssetDatabase.LoadAssetAtPath<GameObject>(path);
}
}
else if (PrefabStageUtility.GetCurrentPrefabStage() != null)
{
string path = PrefabStageUtility.GetCurrentPrefabStage().assetPath;
if (path != null)
{
targetRig = AssetDatabase.LoadAssetAtPath<GameObject>(path);
}
}
if (targetRig == null)
{
Debug.LogError($"Cannot locate target model asset for binder: {binders[i].gameObject.name}. You will need to set up the prop bone config for this character manually and then run setup again.", binders[i].gameObject);
continue;
}
PropBoneConfig targetConfig = FindFirstMatchingConfig(defaultConfig.sourceRig, targetRig);
if (targetConfig.targetRig != targetRig)
{
PropBoneConfig newConfig = ClonePropBoneConfig(defaultConfig);
newConfig.targetRig = targetRig;
newConfig.CalculateOffsetValues();
Debug.Log($"Set up {targetRig.name} as target rig for {binders[i].gameObject.name}", targetRig);
PropBoneConfigAsset configAsset = CreatePropBoneConfigAsset(newConfig, GenerateNewConfigFileName(targetRig.name));
SavePropBoneAssetToProject(configAsset);
targetConfig = configAsset.config;
}
binders[i].propBoneConfig = targetConfig;
}
}
}
/// <summary>
/// Creates new PropBoneConfig files based on the ones assigned to the given binders or bases new configs on the default PropBoneConfig if none are assigned.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to create new bone configs for.</param>
public static void CreateNewBoneConfigs(List<PropBoneBinder> binders)
{
PropBoneConfig defaultConfig = GetDefaultConfig();
for (int i = 0; i < binders.Count; ++i)
{
if (IsPrefabAsset(binders[i]))
{
Debug.LogWarning($"Cannot edit prefab asset {binders[i].gameObject.name}. Open the asset in prefab edit mode or create a scene instance and try again.", binders[i].gameObject);
continue;
}
PropBoneConfig newConfigBase = binders[i].propBoneConfig != null ? binders[i].propBoneConfig : defaultConfig;
PropBoneConfig newConfig = ClonePropBoneConfig(newConfigBase);
PropBoneConfigAsset configAsset = CreatePropBoneConfigAsset(newConfig, GenerateNewConfigFileName(binders[i].name));
SavePropBoneAssetToProject(configAsset);
binders[i].propBoneConfig = configAsset.config;
}
}
/// <summary>
/// Attempts to assign the animator referenec on all the given binders.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to setup the animator references of.</param>
public static void SetupAnimatorReferences(List<PropBoneBinder> binders)
{
for (int i = 0; i < binders.Count; ++i)
{
PropBoneBinder binder = binders[i];
if (binder != null)
{
binder.SetupAnimatorReference();
}
}
}
/// <summary>
/// Attempts to create all the prop bones for all the given binders.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to create prop bones for.</param>
public static void CreatePropBones(List<PropBoneBinder> binders)
{
for (int i = 0; i < binders.Count; ++i)
{
if (IsPrefabAsset(binders[i]))
{
Debug.LogWarning($"Cannot edit prefab asset {binders[i].gameObject.name}. Open the asset in prefab edit mode or create a scene instance and try again.", binders[i].gameObject);
continue;
}
PropBoneBinder binder = binders[i];
if (binder != null)
{
binder.CreatePropBones();
}
}
}
/// <summary>
/// Attempts to clear all the prop bone bindings for all the given binders.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to create prop bones bindings for.</param>
public static void ClearPropBoneBindings(List<PropBoneBinder> binders)
{
for (int i = 0; i < binders.Count; ++i)
{
if (IsPrefabAsset(binders[i]))
{
Debug.LogWarning($"Cannot edit prefab asset {binders[i].gameObject.name}. Open the asset in prefab edit mode or create a scene instance and try again.", binders[i].gameObject);
continue;
}
PropBoneBinder binder = binders[i];
if (binder != null)
{
binder.ClearPropBoneBindings();
}
}
}
/// <summary>
/// Attempts to destroy all the prop bones on all the given bindings.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to destroy prop bones.</param>
public static void DestroyPropBones(List<PropBoneBinder> binders)
{
for (int i = 0; i < binders.Count; ++i)
{
if (IsPrefabAsset(binders[i]))
{
Debug.LogWarning($"Cannot edit prefab asset {binders[i].gameObject.name}. Open the asset in prefab edit mode or create a scene instance and try again.", binders[i].gameObject);
continue;
}
PropBoneBinder binder = binders[i];
if (binder != null)
{
binder.DestroyPropBones();
}
}
}
/// <summary>
/// Attempts to bind all the prop bones on all the given bindings.
/// </summary>
/// <param name="binders">All the PropBoneBinder components to bind prop bones.</param>
public static void BindPropBones(List<PropBoneBinder> binders)
{
for (int i = 0; i < binders.Count; ++i)
{
if (IsPrefabAsset(binders[i]))
{
Debug.LogWarning($"Cannot edit prefab asset {binders[i].gameObject.name}. Open the asset in prefab edit mode or create a scene instance and try again.", binders[i].gameObject);
continue;
}
PropBoneBinder binder = binders[i];
if (binder != null)
{
binder.BindPropBones();
binder.UpdateBones();
}
}
}
/// <summary>
/// Loads a PropBoneConfig file at the given path.
/// </summary>
/// <param name="path">The path to load the PropBoneConfig.</param>
/// <returns>A <c>PropBoneConfigAsset</c> found at the given path or null if no asset of type PropBoneConfigAsset exists at that path.</returns>
private static PropBoneConfigAsset LoadPropBoneConfig(string path)
{
PropBoneConfig config = AssetDatabase.LoadAssetAtPath<PropBoneConfig>(path);
if (config != null)
{
PropBoneConfigAsset configAsset = new PropBoneConfigAsset();
configAsset.config = config;
configAsset.savedInAssetDatabase = true;
configAsset.path = path;
return configAsset;
}
return null;
}
/// <summary>
/// Creates a new PropBoneConfig based on the given source config.
/// </summary>
/// <param name="source">The PropBoneConfig to clone.</param>
/// <returns>A new <c>PropBoneConfig</c> clones from the source PropBoneConfig.</returns>
private static PropBoneConfig ClonePropBoneConfig(PropBoneConfig source)
{
PropBoneConfig newPropBoneConfig = ScriptableObject.CreateInstance<PropBoneConfig>();
newPropBoneConfig.propBoneDefinitions = source.propBoneDefinitions;
newPropBoneConfig.sourceRig = source.sourceRig;
newPropBoneConfig.targetRig = source.targetRig;
return newPropBoneConfig;
}
/// <summary>
/// Creates a new PropBoneConfig containing the given PropBoneDefinitions.
/// </summary>
/// <param name="definitions">The prop bone definitions to be used by the new PropBoneConfig.</param>
/// <returns>A <c>PropBoneConfig</c> with the given PropBoneDefinitions.</returns>
private static PropBoneConfig CreatePropBoneConfig(PropBoneDefinition[] definitions)
{
PropBoneConfig newPropBoneConfig = ScriptableObject.CreateInstance<PropBoneConfig>();
newPropBoneConfig.propBoneDefinitions = definitions;
return newPropBoneConfig;
}
/// <summary>
/// Creates a new PropBoneConfigAsset of the given PropBoneConfig and with the given path.
/// </summary>
/// <param name="config">The config of the config asset.</param>
/// <param name="path">The desired path for the config asset. The path used ma.</param>
/// <param name="ensureUniquePath">When true the path is altered as necessary to ensure it is unique.</param>
/// <returns>A new <c>PropBoneConfigAsset</c> based on the given parameters.</returns>
private static PropBoneConfigAsset CreatePropBoneConfigAsset(PropBoneConfig config, string path, bool ensureUniquePath = true)
{
PropBoneConfigAsset result = new PropBoneConfigAsset();
result.savedInAssetDatabase = false;
result.path = path;
if (ensureUniquePath)
{
result.path = AssetDatabase.GenerateUniqueAssetPath(result.path);
if (string.IsNullOrEmpty(result.path))
{
// AssetDatabase.GenerateUniqueAssetPath(result.path); returns an empty path if the folder structure in the file path does not exist.
result.path = path;
}
}
result.config = config;
return result;
}
/// <summary>
/// Saves the given PropBoneConfigAsset to the AssetDatabase.
/// </summary>
/// <param name="asset">The PropBoneConfigAsset to save.</param>
private static void SavePropBoneAssetToProject(PropBoneConfigAsset asset)
{
EnsureFolderExists(asset.path);
AssetDatabase.CreateAsset(asset.config, asset.path);
AssetDatabase.SaveAssets();
asset.savedInAssetDatabase = true;
Debug.Log($"Successfully created prop bone config asset at path: {asset.path}.", asset.config);
}
/// <summary>
/// Checks if the folder at the given path exists, if not then the folder and all parent folders in the hierarchy are created.
/// </summary>
/// <param name="path">The folder path.</param>
private static void EnsureFolderExists(string path)
{
string[] split = path.Split('/');
string parentPath = split[0];
for (int i = 1; i < split.Length - 1; ++i)
{
string head = split[i];
if (!AssetDatabase.IsValidFolder(parentPath + "/" + head))
{
AssetDatabase.CreateFolder(parentPath, head);
}
parentPath += "/" + head;
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using System;
namespace Synty.Tools.SyntyPropBoneTool
{
[Serializable]
public enum UpdateType
{
Update,
FixedUpdate,
LateUpdate,
Manual
}
}

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using UnityEngine;
namespace Synty.Tools.SyntyPropBoneTool
{
// This is a patch that is only required if the rig does not match POLYGON Animation Packs' rigs
public class PropBone : MonoBehaviour
{
[HideInInspector]
[SerializeField]
private bool _wasSpawnedBySyntyTool = true;
public bool WasSpawnedBySyntyTool { get { return _wasSpawnedBySyntyTool; } set { _wasSpawnedBySyntyTool = value; } }
}
}

View File

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

View File

@@ -0,0 +1,486 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using System.Collections.Generic;
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// The PropBoneBinder is responsible for creating, managing and updating the prop bones at runtime.
/// Only one of these components is needed per character. It will manage all prop bones defined in the propBoneConfig.
/// </summary>
[ExecuteInEditMode]
public class PropBoneBinder : MonoBehaviour
{
[Tooltip("Reference to the animator for this character")]
public Animator animator;
[Tooltip("Configures how the bones are set up on the rig.")]
public PropBoneConfig propBoneConfig;
[Tooltip("Determins when this script will update the transforms. For best results run this script later than the animator.")]
public UpdateType updateType = UpdateType.LateUpdate;
public bool updateInEditMode = true;
[Tooltip("Rebinds all the bones on awake. Useful if your rigs change often, saves needing to rebind them at edit time.")]
public bool rebindOnAwake = false;
[Space]
[Tooltip("Bindings and offset values applied at runtime.")]
[SerializeField]
private List<PropBoneBinding> _propBoneBindings = new List<PropBoneBinding>();
/// <summary>
/// Returns true if the PropBoneBinder is configured correctly.
/// </summary>
public bool IsConfigured => AreReferencesConfigured() && AreBindingsConfigured();
/// <summary>
/// Returns true if the reference variables are configured correctly.
/// </summary>
/// <returns>A <c>bool</c> that is true when the reference variables are correctly configured.</returns>
public bool AreReferencesConfigured()
{
return animator != null && propBoneConfig != null;
}
/// <summary>
/// Returns true if the hierarchy is configured correctly according to the propBoneConfig.
/// </summary>
/// <returns>A <c>bool</c> that is true when the hierarchy is correctly configured.</returns>
public bool IsPropBoneHierarchyConfigured()
{
for (int i = 0; i < propBoneConfig.propBoneDefinitions.Length; ++i)
{
Transform propBone = TransformUtil.SearchHierarchy(transform, propBoneConfig.propBoneDefinitions[i].boneName);
Transform propBoneSocket = TransformUtil.SearchHierarchy(transform, propBoneConfig.propBoneDefinitions[i].socketName);
if (propBone == null || propBoneSocket == null || propBone.parent == null || propBone.parent.name != propBoneConfig.propBoneDefinitions[i].parentBoneName)
{
return false;
}
}
return true;
}
/// <summary>
/// Returns true if all the prop bone bindings are configured correctly according to the propBoneConfig.
/// </summary>
/// <returns>A <c>bool</c> that is true when all the prop bone bindings are correctly configured.</returns>
public bool AreBindingsConfigured()
{
if (_propBoneBindings.Count != propBoneConfig.propBoneDefinitions.Length)
{
return false;
}
for (int i = 0; i < _propBoneBindings.Count; ++i)
{
if (!_propBoneBindings[i].IsMatch(propBoneConfig.propBoneDefinitions[i]))
{
return false;
}
}
return true;
}
/// <summary>
/// Rebinds the PropBoneBindings on awake if rebindOnAwake is set to true.
/// </summary>
private void Awake()
{
if (rebindOnAwake)
{
BindPropBones();
}
}
/// <summary>
/// Attempts to create a PropBoneBinding in accordance to the giving PropBoneDefinition.
/// </summary>
/// <param name="boneDefinition">The definition of the prop bone to be created.</param>
/// <returns>A <c>PropBoneBinding</c> that is the new binding or null if the binding failed to be created.</returns>
private PropBoneBinding CreateBoneBinding(PropBoneDefinition boneDefinition)
{
Transform parent = TransformUtil.SearchHierarchy(transform, boneDefinition.parentBoneName);
if (parent == null)
{
Debug.LogError($"Cannot find parent bone {boneDefinition.parentBoneName}.", transform);
return null;
}
Transform bone = TransformUtil.SearchHierarchy(transform, boneDefinition.boneName);
if (bone == null)
{
Debug.LogError($"Cannot find bone {boneDefinition.boneName}.", transform);
}
else if (bone.parent.name != boneDefinition.parentBoneName)
{
Debug.LogError($"bone.parent {bone.parent.name} does not match {boneDefinition.parentBoneName} in hierarchy.", bone);
return null;
}
Transform socket = TransformUtil.SearchHierarchy(transform, boneDefinition.socketName);
if (socket == null)
{
Debug.LogError($"Cannot find socket {boneDefinition.socketName}.", transform);
return null;
}
else if (socket.parent != bone)
{
Debug.LogError($"socket {socket.name} is not parented to bone {bone.name}.", socket);
return null;
}
PropBoneBinding binding = new PropBoneBinding()
{
bone = bone,
socket = socket,
rotationOffset = boneDefinition.rotationOffset,
scale = boneDefinition.scale
};
return binding;
}
/// <summary>
/// Updates the prop bones if updateType is set to 'Update'.
/// </summary>
private void Update()
{
if (updateType == UpdateType.Update)
{
UpdateBones();
}
}
/// <summary>
/// Updates the prop bones if updateType is set to 'FixedUpdate'.
/// </summary>
private void FixedUpdate()
{
if (updateType == UpdateType.FixedUpdate)
{
UpdateBones();
}
}
/// <summary>
/// Updates the prop bones if updateType is set to 'LateUpdate'.
/// </summary>
private void LateUpdate()
{
if (updateType == UpdateType.LateUpdate)
{
UpdateBones();
}
}
/// <summary>
/// Updates the prop bones according to the prop bone bindings configuration.
/// If updateType is set to 'Manual' call this yourself when it is time to update the bones.
/// </summary>
public void UpdateBones()
{
if (!Application.isPlaying && !updateInEditMode)
{
return;
}
for (int index = 0; index < _propBoneBindings.Count; ++index)
{
UpdateBone(_propBoneBindings[index]);
}
}
/// <summary>
/// Updates the prop bone's position and rotation according to the prop bone binding configuration.
/// </summary>
/// <param name="boneInstance">The prop bone binding to update.</param>
public void UpdateBone(PropBoneBinding boneInstance)
{
if (boneInstance.IsValid)
{
Quaternion offsetRotation = Quaternion.Euler(boneInstance.rotationOffset);
Matrix4x4 localRotation = Matrix4x4.Rotate(offsetRotation);
Matrix4x4 localScale = Matrix4x4.Scale(Vector3.one * boneInstance.scale);
Matrix4x4 localTransform = localScale * localRotation;
boneInstance.socket.SetPositionAndRotation(
boneInstance.bone.parent.localToWorldMatrix.MultiplyPoint(localTransform.MultiplyPoint(boneInstance.bone.localPosition)),
boneInstance.bone.parent.rotation * offsetRotation * boneInstance.bone.localRotation);
}
}
/// <summary>
/// Attempts to find and set the animator reference.
/// </summary>
public void SetupAnimatorReference()
{
if (animator == null)
{
animator = GetComponent<Animator>();
}
if (animator == null)
{
Debug.LogError($"Animator reference is null. New bones may not bind correctly.", transform);
}
#if UNITY_EDITOR
EditorUtility.SetDirty(gameObject);
PrefabUtility.RecordPrefabInstancePropertyModifications(gameObject);
#endif
}
/// <summary>
/// Completely resets the prop bones and bindings including Destroying all bones instantiated by the PropBoneBinder.
/// Any objects parented to those bones will not be destroyed and will be reparented up the hierarchy.
/// </summary>
public void Reset()
{
animator = null;
propBoneConfig = null;
ClearPropBoneBindings();
DestroyPropBones();
#if UNITY_EDITOR
EditorUtility.SetDirty(gameObject);
PrefabUtility.RecordPrefabInstancePropertyModifications(gameObject);
#endif
}
/// <summary>
/// Attempts to create all the PropBoneBindings in accordance to the propBondConfig
/// </summary>
public void BindPropBones()
{
if (animator == null)
{
Debug.LogError($"animator reference is null.", transform);
}
_propBoneBindings.Clear();
if (propBoneConfig == null)
{
Debug.LogError($"Prop bone config is null.", transform);
return;
}
for (int i = 0; i < propBoneConfig.propBoneDefinitions.Length; ++i)
{
PropBoneBinding binding = CreateBoneBinding(propBoneConfig.propBoneDefinitions[i]);
if (binding == null)
{
Debug.LogError($"Could not create binding for prop bone definition {propBoneConfig.propBoneDefinitions[i].ToString()}.", transform);
continue;
}
_propBoneBindings.Add(binding);
if (binding.bone != null && binding.socket != null)
{
Debug.Log($"Successfully bound {binding.socket.name} to {binding.bone.name}", binding.socket);
}
}
#if UNITY_EDITOR
EditorUtility.SetDirty(gameObject);
PrefabUtility.RecordPrefabInstancePropertyModifications(gameObject);
#endif
}
/// <summary>
/// Clears all the current bindings
/// </summary>
public void ClearPropBoneBindings()
{
_propBoneBindings.Clear();
#if UNITY_EDITOR
EditorUtility.SetDirty(gameObject);
PrefabUtility.RecordPrefabInstancePropertyModifications(gameObject);
#endif
}
/// <summary>
/// Instantiates new Game Objects where nessessary to create the prop bones.
/// </summary>
public void CreatePropBones()
{
if (propBoneConfig == null)
{
Debug.LogError($"Prop bone config is null.", transform);
return;
}
for (int i = 0; i < propBoneConfig.propBoneDefinitions.Length; ++i)
{
CreatePropBones(gameObject, propBoneConfig.propBoneDefinitions[i]);
}
#if UNITY_EDITOR
EditorUtility.SetDirty(gameObject);
PrefabUtility.RecordPrefabInstancePropertyModifications(gameObject);
#endif
}
/// <summary>
/// Instantiates new Game Objects where nessessary to create the prop bones.
/// </summary>
/// <param name="editScope">The root game object to create the prop bone under.</param>
/// <param name="boneDefinition">The definition of the prop bone to be created.</param>
/// <param name="boneInstance">The prop bone binding to update.</param>
private void CreatePropBones(GameObject editScope, PropBoneDefinition boneDefinition)
{
Transform parent = TransformUtil.SearchHierarchy(editScope.transform, boneDefinition.parentBoneName);
if (parent == null)
{
Debug.LogError($"Cannot find parent prop bone {boneDefinition.parentBoneName} in hierarchy.", transform);
return;
}
Transform bone = TransformUtil.SearchHierarchy(editScope.transform, boneDefinition.boneName);
if (bone == null)
{
bone = CreatePropBone(boneDefinition.boneName, parent);
if (bone == null)
{
return;
}
}
else if (bone.parent.name != boneDefinition.parentBoneName)
{
Debug.LogError($"bone.parent {bone.parent.name} does not match {boneDefinition.parentBoneName} in hierarchy.", bone);
return;
}
Transform socket = TransformUtil.SearchHierarchy(editScope.transform, boneDefinition.socketName);
if (socket == null)
{
socket = CreatePropBone(boneDefinition.socketName, bone);
}
else if (socket.parent != bone)
{
Debug.LogError($"socket {socket.name} is not parented to bone {bone.name}.", socket);
return;
}
#if UNITY_EDITOR
EditorGUIUtility.PingObject(socket);
#endif
}
/// <summary>
/// Instantiates a new Game Object called 'name' and parents it to 'parent'.
/// </summary>
/// <param name="name">The name of the new bone to be created.</param>
/// <param name="parent">The transform to parent the new bone to.</param>
private Transform CreatePropBone(string name, Transform parent)
{
// does not check if there is already a bone called this. we should check before calling create
Transform boneInstance = new GameObject(name).transform;
boneInstance.SetParent(parent);
if (boneInstance.parent != parent)
{
Debug.LogError($"Something went wrong when creating prop bone {boneInstance.name}. You may need to enter prefab edit mode to set up this character.", gameObject);
DestroyImmediate(boneInstance.gameObject);
return null;
}
boneInstance.localPosition = Vector3.zero;
boneInstance.localRotation = Quaternion.identity;
boneInstance.localScale = Vector3.one;
// mark that these have been created by the tool
boneInstance.gameObject.AddComponent<PropBone>().WasSpawnedBySyntyTool = true;
Debug.Log($"Successfully created prop bone {boneInstance.name}.", boneInstance);
return boneInstance;
}
/// <summary>
/// Destroys all the prop bones that have been created by the PropBoneBinder and clears all bindings.
/// </summary>
public void DestroyPropBones()
{
DestroyPropBones(gameObject);
#if UNITY_EDITOR
EditorUtility.SetDirty(gameObject);
PrefabUtility.RecordPrefabInstancePropertyModifications(gameObject);
#endif
}
/// <summary>
/// Destroys all the prop bones that have been created by the PropBoneBinder and clears all bindings
/// </summary>
/// <param name="editScope">The root game objects to destroy prop bones from.</param>
private void DestroyPropBones(GameObject editScope)
{
_propBoneBindings.Clear();
PropBone[] bones = editScope.GetComponentsInChildren<PropBone>();
for (int i = 0; i < bones.Length; ++i)
{
if (bones[i].WasSpawnedBySyntyTool)
{
// Let's not destroy peoples swords and stuff.
for (int c = 0; c < bones[i].transform.childCount; ++c)
{
Transform child = bones[i].transform.GetChild(c);
child.SetParent(bones[i].transform.parent);
bool hasPropBone = child.GetComponent<PropBone>() != null;
if (!hasPropBone)
{
Debug.Log($"Successfully reparented object {child.name}.", child);
}
}
string boneName = bones[i].gameObject.name;
Transform parent = bones[i].transform.parent;
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(bones[i].gameObject);
}
else
{
Destroy(bones[i].gameObject);
}
if (parent != null)
{
Debug.Log($"Successfully destroyed bone {boneName} under {parent.name}.", parent);
}
else
{
Debug.Log($"Successfully destroyed bone {boneName}.");
}
}
else
{
GameObject gameObject = bones[i].gameObject;
// Remove the component but don't destroy the object.
if (Application.isEditor && !Application.isPlaying)
{
DestroyImmediate(bones[i]);
}
else
{
Destroy(bones[i]);
}
if (gameObject != null)
{
Debug.Log($"Successfully removed SyntyPropBone component from {gameObject.name}.", gameObject);
}
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,116 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using UnityEngine;
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// A configuration class used to define what bones are called, where they should be in the hierarchy and what offsets to use when updating them at runtime.
/// </summary>
[CreateAssetMenu(menuName = "Synty/Animation/Synty Prop Bone Config", order = 1)]
public class PropBoneConfig : ScriptableObject
{
[Tooltip("The rig used to create the animations. Rig must be in T Pose.")]
public GameObject sourceRig;
[Tooltip("The rig to play the animations on in Unity. Rig must be in T Pose.")]
public GameObject targetRig;
[Tooltip("Parameters that define where prop bones will generate and how to constrain them")]
public PropBoneDefinition[] propBoneDefinitions;
/// <summary>
/// Attempts to calculate the rotation and scale offset for all the bone definitions based on sourceRig and targetRig.
/// For the offsets to calculate correctly the sourceRig and targetRig must both be in T Pose.
/// </summary>
[ContextMenu("Calculate Offset Values")]
public void CalculateOffsetValues()
{
if (sourceRig == null)
{
Debug.LogError($"Source rig is null in config {name}.", this);
}
if (targetRig == null)
{
Debug.LogError($"Target rig is null in config {name}.", this);
}
if (targetRig == null || sourceRig == null)
{
return;
}
for (int i = 0; i < propBoneDefinitions.Length; ++i)
{
// Check the bones in the definition exist in the source and target rigs
Transform sourceParent = TransformUtil.SearchHierarchy(sourceRig.transform, propBoneDefinitions[i].parentBoneName);
Transform targetParent = TransformUtil.SearchHierarchy(targetRig.transform, propBoneDefinitions[i].parentBoneName);
if (sourceParent == null)
{
Debug.LogError($"Cannot find bone in source rig called {propBoneDefinitions[i].parentBoneName}.");
continue;
}
if (targetParent == null)
{
Debug.LogError($"Cannot find bone in target rig called {propBoneDefinitions[i].parentBoneName}.");
continue;
}
// Calculate the rotational offset between the source and target rigs
propBoneDefinitions[i].rotationOffset = (Quaternion.Inverse(targetParent.rotation) * sourceParent.rotation).eulerAngles;
// Check the scale calculation bones are defined and exist in the source and target rigs
if (propBoneDefinitions[i].scaleCalculationBone1 == "" || propBoneDefinitions[i].scaleCalculationBone2 == "")
{
continue;
}
Transform sourceScaleBone1 = TransformUtil.SearchHierarchy(sourceRig.transform, propBoneDefinitions[i].scaleCalculationBone1);
Transform sourceScaleBone2 = TransformUtil.SearchHierarchy(sourceRig.transform, propBoneDefinitions[i].scaleCalculationBone2);
Transform targetScaleBone1 = TransformUtil.SearchHierarchy(targetRig.transform, propBoneDefinitions[i].scaleCalculationBone1);
Transform targetScaleBone2 = TransformUtil.SearchHierarchy(targetRig.transform, propBoneDefinitions[i].scaleCalculationBone2);
if (sourceScaleBone1 == null)
{
Debug.LogError($"Cannot find bone in source rig called {propBoneDefinitions[i].scaleCalculationBone1}.");
continue;
}
if (sourceScaleBone2 == null)
{
Debug.LogError($"Cannot find bone in source rig called {propBoneDefinitions[i].scaleCalculationBone2}.");
continue;
}
if (targetScaleBone1 == null)
{
Debug.LogError($"Cannot find bone in target rig called {propBoneDefinitions[i].scaleCalculationBone1}.");
continue;
}
if (targetScaleBone2 == null)
{
Debug.LogError($"Cannot find bone in target rig called {propBoneDefinitions[i].scaleCalculationBone2}.");
continue;
}
// Calculate the scale offset between the source and target rigs
propBoneDefinitions[i].scale = 1;
float sourceLength = Vector3.Distance(sourceScaleBone1.position, sourceScaleBone2.position);
float targetLength = Vector3.Distance(targetScaleBone1.position, targetScaleBone2.position);
if (sourceLength < float.Epsilon)
{
Debug.LogError($"Distance betweem source rig's scale bones is zero: scale bone 1: {propBoneDefinitions[i].scaleCalculationBone1}, scale bone 2 {propBoneDefinitions[i].scaleCalculationBone2}.");
continue;
}
if (targetLength < float.Epsilon)
{
Debug.LogError($"Distance betweem target rig's scale bones is zero: scale bone 1: {propBoneDefinitions[i].scaleCalculationBone1}, scale bone 2 {propBoneDefinitions[i].scaleCalculationBone2}.");
continue;
}
propBoneDefinitions[i].scale = targetLength / sourceLength;
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,42 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using System;
using UnityEngine;
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// Defines the parameters used to update the prop bone at runtime.
/// </summary>
[Serializable]
public class PropBoneBinding
{
// Public so they can be see in the inspector for debug purposes
public Transform bone;
public Transform socket;
public Vector3 rotationOffset;
public float scale = 1;
// Returns true if the prop bone binding is able to be updated
public bool IsValid { get { return bone != null && socket != null; } }
/// <summary>
/// Compares the parameters of this prop bone binding with the given prop bone binding.
/// </summary>
/// <param name="other">The PropBoneDefinition to compare with.</param>
/// <returns>A <c>bool</c>. True when the bindings are equvilent.</returns>
public bool IsMatch(PropBoneDefinition other)
{
return bone.name == other.boneName
&& socket.name == other.socketName
&& (bone.parent != null ? bone.parent.name == other.parentBoneName : string.IsNullOrEmpty(other.parentBoneName))
&& rotationOffset == other.rotationOffset && scale == other.scale;
}
}
}

View File

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

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using System;
using UnityEngine;
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// Defines the parameters used by the PropBoneBinder to create the required bones and calculate the parameters used to update those bones at runtime.
/// </summary>
[Serializable]
public struct PropBoneDefinition
{
[Tooltip("The name of the bone in your character's rig to attach the prop bone.")]
public string parentBoneName;
[Tooltip("The name of the prop bone to instantiate.")]
public string boneName;
[Tooltip("The name of the additional transform created to attach props under.")]
public string socketName;
[Tooltip("Rotation offset used to compensate for differences in orientation of the parent bone between the source rig and the target rig.")]
public Vector3 rotationOffset;
[Tooltip("Scalar used to compensate for differences in size between the source rig and the target rig. 1 = target rig is the same scale as the reference rig, 2 = target rig is twice the size as the reference rig.")]
public float scale;
[Tooltip("Bone to use to calculate scalar value automatically.")]
public string scaleCalculationBone1;
[Tooltip("Bone to use to calculate scalar value automatically.")]
public string scaleCalculationBone2;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using UnityEngine;
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// Hard coded default values for PropBoneDefinitions.
/// </summary>
public static class PropBoneDefinitionPresets
{
// Preset configs for Synty rigs
public static PropBoneDefinition[] PolygonBoneDefinition
{
get
{
return new PropBoneDefinition[]
{
new PropBoneDefinition() {
parentBoneName = "Hand_R",
boneName = "Prop_R",
socketName = "Prop_R_Socket",
rotationOffset = new Vector3(0,0,0),
scale = 1f,
scaleCalculationBone1 = "Hand_R",
scaleCalculationBone2 = "Elbow_R"
}
};
}
}
}
}

View File

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

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2024 Synty Studios Limited. All rights reserved.
//
// Use of this software is subject to the terms and conditions of the Synty Studios End User Licence Agreement (EULA)
// available at: https://syntystore.com/pages/end-user-licence-agreement
//
// For additional details, see the LICENSE.MD file bundled with this software.
using UnityEngine;
namespace Synty.Tools.SyntyPropBoneTool
{
/// <summary>
/// Helper class containing helpful functions relating to Transform objects.
/// </summary>
public static class TransformUtil
{
/// <summary>
/// Performs a depth first recursive searche of the hierarchy to find a transform with the searchName.
/// </summary>
/// <param name="node">The current node in the recursive search.</param>
/// <param name="searchName">The name of the node to find.</param>
/// <returns>A <c>Transform</c> that is the first match to the searchName or null if no match is found.</returns>
public static Transform SearchHierarchy(Transform node, string searchName)
{
if (node == null)
{
return null;
}
if (node.name == searchName)
{
return node;
}
Transform result = null;
for (int childIndex = 0; childIndex < node.childCount; childIndex++)
{
result = SearchHierarchy(node.GetChild(childIndex), searchName);
if (result != null)
{
break;
}
}
return result;
}
}
}

View File

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