Initial Commit
This commit is contained in:
8
PhysBone-Converter/Editor.meta
Normal file
8
PhysBone-Converter/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb81ed9ea3b9261488c105f6fc58a0ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
368
PhysBone-Converter/Editor/PhysBoneConverter.cs
Normal file
368
PhysBone-Converter/Editor/PhysBoneConverter.cs
Normal file
@@ -0,0 +1,368 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Dynamics.PhysBone.Components;
|
||||
|
||||
//This work is licensed under the Creative Commons Attribution-NonCommercial 2.0 License.
|
||||
//To view a copy of the license, visit https://creativecommons.org/licenses/by-nc/2.0/legalcode
|
||||
|
||||
//Made by Dreadrith#3238
|
||||
//Discord: https://discord.gg/ZsPfrGn
|
||||
//Github: https://github.com/Dreadrith/DreadScripts
|
||||
//Gumroad: https://gumroad.com/dreadrith
|
||||
//Ko-fi: https://ko-fi.com/dreadrith
|
||||
|
||||
namespace DreadScripts.PhysBoneConverter
|
||||
{
|
||||
public class PhysBoneConverter : EditorWindow
|
||||
{
|
||||
#region Automated
|
||||
private static readonly Dictionary<VRCPhysBoneCollider, DynamicBoneColliderBase> colliderDictionary = new Dictionary<VRCPhysBoneCollider, DynamicBoneColliderBase>();
|
||||
private static readonly AnimationCurve AngleToStiffnessCurve = SmoothCurveTangents(new AnimationCurve(
|
||||
new Keyframe(0, 1),
|
||||
new Keyframe(60, 0.5f),
|
||||
new Keyframe(105, 0.2f),
|
||||
new Keyframe(130, 0.1f),
|
||||
new Keyframe(180, 0)));
|
||||
|
||||
private static readonly GUIContent[] names =
|
||||
{
|
||||
new GUIContent("Damping","How much the bones get slowed down."),
|
||||
new GUIContent("Elasticity","How much force is applied to return each bone to its original orientation."),
|
||||
new GUIContent("Stiffness", "How much the bone's original orientation gets preserved."),
|
||||
new GUIContent("Inert", "How much the character's position change is ignored in physics simulation."),
|
||||
new GUIContent("Friction", "How much the bones get slowed down when colliding.")
|
||||
};
|
||||
|
||||
private static int optionsCount;
|
||||
private static FieldInfo frictionField;
|
||||
private static FieldInfo frictionDistribField;
|
||||
#endregion
|
||||
|
||||
#region Input
|
||||
public static GameObject targetRoot;
|
||||
public static bool makeBackup = true;
|
||||
public static bool[] bools = {true,true,true,true,true};
|
||||
public static float[] floats = {0.2f, 0.2f, 0, 0, 0};
|
||||
public static AnimationCurve[] curves = {new AnimationCurve(), new AnimationCurve(), new AnimationCurve(), new AnimationCurve(), new AnimationCurve()};
|
||||
#endregion
|
||||
|
||||
#region GUI
|
||||
[MenuItem("DreadTools/Utility/PhysBone Converter")]
|
||||
public static void ShowWindow() => GetWindow<PhysBoneConverter>("PhysBone Converter");
|
||||
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
EditorGUIUtility.labelWidth = 90;
|
||||
|
||||
using (new GUILayout.HorizontalScope(GUI.skin.box))
|
||||
targetRoot = (GameObject) EditorGUILayout.ObjectField(Helper.Content.root, targetRoot, typeof(GameObject), true);
|
||||
|
||||
for (int i = 0; i < optionsCount; i++)
|
||||
DrawFloatOption(i);
|
||||
|
||||
using (new EditorGUI.DisabledScope(!targetRoot))
|
||||
if (GUILayout.Button("Convert", Helper.Styles.toolbarButton))
|
||||
Helper.ReportCall(StartConversion);
|
||||
|
||||
|
||||
EditorGUIUtility.labelWidth = 0;
|
||||
|
||||
Helper.Credit();
|
||||
}
|
||||
|
||||
private static void DrawFloatOption(int index)
|
||||
{
|
||||
using (new GUILayout.HorizontalScope(GUI.skin.box))
|
||||
{
|
||||
EditorGUILayout.PrefixLabel(names[index]);
|
||||
if (!bools[index])
|
||||
{
|
||||
floats[index] = EditorGUILayout.Slider(floats[index], 0f, 1f);
|
||||
curves[index] = EditorGUILayout.CurveField(curves[index], GUILayout.Width(70));
|
||||
}
|
||||
else GUILayout.FlexibleSpace();
|
||||
|
||||
var ogColor = GUI.backgroundColor;
|
||||
GUI.backgroundColor = bools[index] ? Color.green : Color.red;
|
||||
bools[index] = GUILayout.Toggle(bools[index], "Auto", GUI.skin.button, GUILayout.ExpandWidth(false));
|
||||
GUI.backgroundColor = ogColor;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
targetRoot = targetRoot ?? FindObjectOfType<VRCAvatarDescriptor>()?.gameObject ?? FindObjectOfType<Animator>()?.gameObject;
|
||||
frictionField = typeof(DynamicBone).GetField("m_Friction");
|
||||
frictionDistribField = typeof(DynamicBone).GetField("m_FrictionDistrib");
|
||||
optionsCount = frictionField == null ? 4 : 5;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Main
|
||||
public static void StartConversion()
|
||||
{
|
||||
if (!targetRoot) return;
|
||||
colliderDictionary.Clear();
|
||||
var pbBones = targetRoot.GetComponentsInChildren<VRCPhysBone>(true);
|
||||
var pbColliders = targetRoot.GetComponentsInChildren<VRCPhysBoneCollider>(true);
|
||||
if (pbBones.Length == 0 && pbColliders.Length == 0)
|
||||
{
|
||||
Helper.GreenLog("No Physbones or colliders to convert.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (makeBackup)
|
||||
{
|
||||
var t = targetRoot.transform;
|
||||
var backup = Instantiate(targetRoot, t.position, t.rotation, t.parent);
|
||||
backup.name = backup.name.Replace(" (Backup)", string.Empty).Replace("(Clone)", " (Backup)");
|
||||
backup.SetActive(false);
|
||||
}
|
||||
|
||||
foreach (var c in pbColliders) ConvertCollider(c);
|
||||
|
||||
//1 to 1 Replacement is rarely incorrect.
|
||||
//In cases where there are multiple children of the PB and it's set to ignore, then each child needs its own DBone.
|
||||
foreach (var pb in pbBones) ConvertPhysBone(pb);
|
||||
|
||||
|
||||
foreach (var c in pbColliders) DestroyImmediate(c);
|
||||
foreach (var pb in pbBones) DestroyImmediate(pb);
|
||||
|
||||
Helper.GreenLog($"Finished conversion! Bones: {pbBones.Length} | Colliders: {pbColliders.Length}");
|
||||
}
|
||||
|
||||
private static void ConvertCollider(VRCPhysBoneCollider pbc)
|
||||
{
|
||||
Transform rootTransform = pbc.GetRootTransform();
|
||||
string collidersName = $"{rootTransform.name} Colliders";
|
||||
GameObject colliderParent = rootTransform.Find(collidersName)?.gameObject;
|
||||
if (!colliderParent)
|
||||
{
|
||||
colliderParent = new GameObject(collidersName)
|
||||
{
|
||||
transform =
|
||||
{
|
||||
parent = rootTransform,
|
||||
localPosition = Vector3.zero,
|
||||
localRotation = Quaternion.identity,
|
||||
localScale = Vector3.one
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
GameObject colliderTarget = new GameObject("Collider")
|
||||
{
|
||||
transform =
|
||||
{
|
||||
parent = colliderParent.transform,
|
||||
localPosition = pbc.position,
|
||||
localRotation = pbc.rotation,
|
||||
localScale = Vector3.one
|
||||
}
|
||||
};
|
||||
GameObjectUtility.EnsureUniqueNameForSibling(colliderTarget);
|
||||
|
||||
bool isPlane = pbc.shapeType == VRC.Dynamics.VRCPhysBoneColliderBase.ShapeType.Plane;
|
||||
|
||||
DynamicBoneColliderBase baseCollider;
|
||||
if (isPlane) baseCollider = colliderTarget.AddComponent<DynamicBonePlaneCollider>();
|
||||
else baseCollider = colliderTarget.AddComponent<DynamicBoneCollider>();
|
||||
|
||||
|
||||
baseCollider.m_Bound = (DynamicBoneColliderBase.Bound) (pbc.insideBounds ? 1 : 0);
|
||||
baseCollider.m_Center = Vector3.zero;
|
||||
baseCollider.m_Direction = DynamicBoneColliderBase.Direction.Y;
|
||||
|
||||
if (!isPlane)
|
||||
{
|
||||
var collider = (DynamicBoneCollider)baseCollider;
|
||||
collider.m_Radius = pbc.radius;
|
||||
collider.m_Height = pbc.height;
|
||||
}
|
||||
|
||||
colliderDictionary.Add(pbc, baseCollider);
|
||||
}
|
||||
|
||||
private static void ConvertPhysBone(VRCPhysBone pb)
|
||||
{
|
||||
var dbone = pb.gameObject.AddComponent<DynamicBone>();
|
||||
dbone.m_Root = pb.GetRootTransform();
|
||||
float scaleFactor = (Mathf.Abs(dbone.transform.lossyScale.x / dbone.m_Root.lossyScale.x));
|
||||
|
||||
|
||||
dbone.m_Damping = bools[0] ? 1 - pb.spring : floats[0];
|
||||
dbone.m_DampingDistrib = bools[0] ? InvertCurve(pb.springCurve) : curves[0];
|
||||
dbone.m_Elasticity = bools[1] ? pb.pull : floats[1];
|
||||
dbone.m_ElasticityDistrib = bools[1] ? pb.pullCurve : curves[1];
|
||||
dbone.m_Inert = bools[3] ? pb.immobile : floats[3];
|
||||
dbone.m_InertDistrib = bools[3] ? pb.immobileCurve : curves[3];
|
||||
if (!bools[4])
|
||||
{
|
||||
frictionField.SetValue(dbone, floats[4]);
|
||||
frictionDistribField.SetValue(dbone, curves[4]);
|
||||
}
|
||||
|
||||
dbone.m_Radius = pb.radius / scaleFactor;
|
||||
dbone.m_RadiusDistrib = pb.radiusCurve;
|
||||
dbone.m_Gravity = new Vector3(0, -pb.gravity / 10f, 0);
|
||||
|
||||
//Better Limit conversion, such as Hinge to Freeze Axis, can be done, but not all possibilities with PhysBone limits can be achieved.
|
||||
if (bools[2])
|
||||
{
|
||||
dbone.m_Stiffness = 0;
|
||||
if (pb.limitType == VRC.Dynamics.VRCPhysBoneBase.LimitType.Angle)
|
||||
{
|
||||
bool hasAnglecurve = pb.maxAngleXCurve != null && pb.maxAngleXCurve.keys.Length > 0;
|
||||
dbone.m_Stiffness = hasAnglecurve ? 1 : AngleToStiffnessCurve.Evaluate(pb.maxAngleX);
|
||||
|
||||
dbone.m_StiffnessDistrib = SmoothCurveTangents(DeriveCurve(SubdivideCurve(pb.maxAngleXCurve, 10, false), k =>
|
||||
{
|
||||
k.value = AngleToStiffnessCurve.Evaluate(k.value * 180);
|
||||
return k;
|
||||
}));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbone.m_Stiffness = floats[2];
|
||||
dbone.m_StiffnessDistrib = curves[2];
|
||||
}
|
||||
|
||||
dbone.m_Exclusions = pb.ignoreTransforms;
|
||||
dbone.m_Colliders = pb.colliders.Cast<VRCPhysBoneCollider>()
|
||||
.Where(pbc => pbc && colliderDictionary.ContainsKey(pbc))
|
||||
.Select(pbc => colliderDictionary[pbc]).ToList();
|
||||
|
||||
|
||||
if (pb.endpointPosition != Vector3.zero)
|
||||
{
|
||||
foreach (var t in dbone.m_Root.GetComponentsInChildren<Transform>().Where(t => t.childCount == 0 && !IsExcluded(t, dbone.m_Exclusions)))
|
||||
{
|
||||
GameObjectUtility.EnsureUniqueNameForSibling(new GameObject($"{t.name} EndBone")
|
||||
{
|
||||
transform =
|
||||
{
|
||||
parent = t,
|
||||
position = t.TransformPoint(pb.endpointPosition),
|
||||
localRotation = Quaternion.identity,
|
||||
localScale = Vector3.one,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Dunno if this optimizes anything in-game but aye better than nothing
|
||||
dbone.m_DistantDisable = true;
|
||||
dbone.m_ReferenceObject = targetRoot.transform;
|
||||
}
|
||||
|
||||
private static bool IsExcluded(Transform t, IEnumerable<Transform> exclusions) => exclusions.Any(e => e != null && t.IsChildOf(e));
|
||||
#endregion
|
||||
|
||||
#region Curve Functions
|
||||
private static AnimationCurve SmoothCurveTangents(AnimationCurve curve)
|
||||
{
|
||||
if (curve == null) return null;
|
||||
for (int i = 0; i < curve.keys.Length; i++)
|
||||
curve.SmoothTangents(i, 0);
|
||||
return curve;
|
||||
}
|
||||
private static AnimationCurve DeriveCurve(AnimationCurve curve, Func<Keyframe, Keyframe> KeyFunc)
|
||||
{
|
||||
if (curve == null) return null;
|
||||
int length = curve.keys.Length;
|
||||
|
||||
var newKeys = new Keyframe[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
newKeys[i] = KeyFunc(curve.keys[i]);
|
||||
|
||||
return new AnimationCurve(newKeys);
|
||||
}
|
||||
private static AnimationCurve InvertCurve(AnimationCurve curve) =>
|
||||
DeriveCurve(curve, k =>
|
||||
{
|
||||
k.value = -k.value;
|
||||
k.inTangent = -k.inTangent;
|
||||
k.outTangent = -k.outTangent;
|
||||
return k;
|
||||
});
|
||||
private static AnimationCurve SubdivideCurve(AnimationCurve curve, int keyLimit = 10, bool smoothCurve = true)
|
||||
{
|
||||
if (curve == null) return null;
|
||||
if (curve.length <= 1) return curve;
|
||||
List<Keyframe> keys = new List<Keyframe>(curve.keys);
|
||||
if (keys.Count > 1)
|
||||
{
|
||||
while (keys.Count < keyLimit)
|
||||
{
|
||||
var currentKeys = keys.OrderBy(k => k.time).ToList();
|
||||
for (int i = 0; i < currentKeys.Count - 1 && keys.Count < keyLimit; i++)
|
||||
{
|
||||
var time = (currentKeys[i].time + currentKeys[i + 1].time) / 2;
|
||||
keys.Add(new Keyframe(time, curve.Evaluate(time)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var newCurve = new AnimationCurve(keys.ToArray());
|
||||
if (smoothCurve) SmoothCurveTangents(newCurve);
|
||||
return newCurve;
|
||||
}
|
||||
private static void LogCurve(AnimationCurve curve)
|
||||
{
|
||||
Debug.Log(string.Join("\n", curve.keys.Select(k => k.time).Zip(curve.keys.Select(k => k.value), (t, v) => $"Time: {t} | Value: {v}")));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
public static class Helper
|
||||
{
|
||||
|
||||
internal static void ReportCall(Action action)
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMSG = $"{e.Message}\n\n{e.StackTrace}";
|
||||
if (EditorUtility.DisplayDialog("Error!", $"An unexpected error has occured and execution was halted. Please Press Copy and report this stack trace to Dreadrith#3238\n~~~~~~~~~~~~\n{errorMSG}", "Copy", "Nah"))
|
||||
EditorGUIUtility.systemCopyBuffer = errorMSG;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Credit()
|
||||
{
|
||||
using (new GUILayout.HorizontalScope())
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Made By Dreadrith#3238", "boldlabel"))
|
||||
Application.OpenURL("https://linktr.ee/Dreadrith");
|
||||
}
|
||||
}
|
||||
|
||||
private const string logName = "PBConverter";
|
||||
internal static void GreenLog(string msg) =>
|
||||
Debug.Log($"<color=green>[{logName}]</color> {msg}");
|
||||
|
||||
public static class Styles
|
||||
{
|
||||
public static readonly GUIStyle toolbarButton = GUI.skin.GetStyle("toolbarbutton");
|
||||
public static readonly GUIStyle box = GUI.skin.GetStyle("box");
|
||||
}
|
||||
|
||||
public static class Content
|
||||
{
|
||||
public static readonly GUIContent root = new GUIContent("Root", "The Root. Will convert all the PhysBones and Colliders under this root.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
11
PhysBone-Converter/Editor/PhysBoneConverter.cs.meta
Normal file
11
PhysBone-Converter/Editor/PhysBoneConverter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5362ab9190648fc4fa4a11324829ab0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
20
PhysBone-Converter/Instructions.txt
Normal file
20
PhysBone-Converter/Instructions.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
//Made by Dreadrith#3238
|
||||
//Discord: https://discord.gg/ZsPfrGn
|
||||
//Github: https://github.com/Dreadrith/DreadScripts
|
||||
//Gumroad: https://gumroad.com/dreadrith
|
||||
//Ko-fi: https://ko-fi.com/dreadrith
|
||||
|
||||
This tool will convert your PhysBones and PBColliders to DynamicBones and DBColliders.
|
||||
Of course, it's not perfect but it does almost the inverse conversion of PhysBones.
|
||||
|
||||
How to use
|
||||
----------
|
||||
Find the tool's window near the top left corner of Unity under
|
||||
DreadTools > Utility > PhysBone Converter
|
||||
|
||||
1- Make sure that your avatar or root is correctly set as "Root".
|
||||
2- Press Convert. Done!
|
||||
|
||||
You can optionally disable the "Auto" option for any setting and set your own value and curve.
|
||||
This will be the same value across all DynamicBones
|
||||
|
7
PhysBone-Converter/Instructions.txt.meta
Normal file
7
PhysBone-Converter/Instructions.txt.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54d64c9949521fa468c6dbab8670364f
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
2
PhysBone-Converter/LICENSE.md
Normal file
2
PhysBone-Converter/LICENSE.md
Normal file
@@ -0,0 +1,2 @@
|
||||
This work is licensed under the Creative Commons Attribution-NonCommercial 2.0 License.
|
||||
To view a copy of the license, visit https://creativecommons.org/licenses/by-nc/2.0/legalcode
|
7
PhysBone-Converter/LICENSE.md.meta
Normal file
7
PhysBone-Converter/LICENSE.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29f9273871992164daf47069bf15ea30
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
32
PhysBone-Converter/README.md
Normal file
32
PhysBone-Converter/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# PhysBone Converter
|
||||
This tool will convert your PhysBones and PBColliders to DynamicBones and DBColliders.<br>
|
||||
Of course, it's not perfect but it does almost the inverse conversion of PhysBones.<br><br>
|
||||
<a href=https://github.com/Dreadrith/PhysBone-Converter/releases/latest/download/PhysBoneConverter.unitypackage>Download here</a><br><br>
|
||||

|
||||
|
||||
# How to use
|
||||
Prerequisites: <a href=https://vrchat.com/home/download>VRCAvatars SDK</a> & <a href=https://assetstore.unity.com/packages/tools/animation/dynamic-bone-16743>Dynamic Bone</a> OR <a href=https://github.com/VRLabs/Dynamic-Bones-Stub/releases/latest/download/Dynamic_Bones_v1.3.2_Stub.unitypackage>Dynamic Bone Stub</a><br>
|
||||
|
||||
Find the tool's window near the top left corner of Unity under<br>
|
||||
DreadTools > Utility > PhysBone Converter
|
||||
|
||||
1- Make sure that your avatar or root is correctly set as "Root".<br>
|
||||
2- Press Convert. Done!
|
||||
|
||||
You can optionally disable the "Auto" option for any setting and set your own value and curve.<br>
|
||||
This will be the same value across all DynamicBones<br><br>
|
||||

|
||||
|
||||
# Potential Issues
|
||||
Issue: DreadTools button doesn't appear on the top toolbar<br>
|
||||
Solution: Your project has a script error which prevents new scripts from loading. Check your console for errors and read them for details about what's causing them. Below are some common errors
|
||||
|
||||
Issue: Error: the type or namespace name 'DynamicBoneColliderBase' could not be found.<br>
|
||||
Solution: You either don't have Dynamic Bones or your dynamic bones version doesn't match the one this script is made for. This was made for 1.2.x as it's the most common version. Try using the Dynamic Bone Stub listed above instead
|
||||
|
||||
Issue: Error: The type or namespace name 'VRCPhysBoneCollider' could not be found<br>
|
||||
Issue: Error: 'VRCPhysBone' does not contain a definition for 'maxAngleXCurve'...<br>
|
||||
Solution: Both of these can be solved with importing the proper Avatars VRCSDK. Currently, the latest Avatars VRCSDK should work.
|
||||
|
||||
Issue: The Dynamic Bones aren't acting the same as how PhysBones were.<br>
|
||||
Sadly, Dynamic Bones don't function the same as PhysBones and have less features. As such, they will behave differently and in some uncommon cases even look broken in comparison. This may be improved with manual tweaking.
|
7
PhysBone-Converter/README.md.meta
Normal file
7
PhysBone-Converter/README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fc128096b1a3c34cb474729e4bc841f
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
23
PhysBone-Converter/package.json
Normal file
23
PhysBone-Converter/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "com.dreadrith.physboneconverter",
|
||||
"displayName": "DreadTools - PhysBone Converter",
|
||||
"version": "1.0.3",
|
||||
"unity": "2019.4",
|
||||
"description": "Converts PhysBones to DynamicBones to the best of my capability",
|
||||
"keywords": [
|
||||
"Dreadrith",
|
||||
"DreadScripts",
|
||||
"DreadTools",
|
||||
"VRChat",
|
||||
"ChilloutVR"
|
||||
"Tool",
|
||||
"Converter",
|
||||
"Conversion",
|
||||
"DynamicBone"
|
||||
],
|
||||
"author": {
|
||||
"name": "Dreadrith",
|
||||
"email": "dreadscripts@gmail.com",
|
||||
"url": "https://linktr.ee/Dreadrith"
|
||||
}
|
||||
}
|
7
PhysBone-Converter/package.json.meta
Normal file
7
PhysBone-Converter/package.json.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1562724099a9d24a8757ab714f67242
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user