Building character models with body parts have been an obsession for me in the last weeks. I have googled, asked, googled, and asked again, played with Unity3d editor, tested code, and so on. You can see the result of my work in the previous post. First of all, I have to say that what I achieved is mostly derived from this thread and the sample posted there. My code is a copy&paste of that sample. Also, you can find a more extense solution in this Gamedev.net forum thread.
First, lets start with the model, which obviously, is divided in the required sections. Lets say we have head, torso and legs. Each part must be exported to a separate fbx file, but it must include the skeleton. Then, export the skeleton without geometry to another fbx.
Im going to assume that you want to instantiate all components, and even player, at runtime, from a C# script (no Unity editor involved, except for creating prefab an position marker), so, we will place the sections, the skeleton and everything inside Assets/Resources. Then we will create a prefab for the character hierarchy, which we will call char-prefab. Drag and drop the skeleton to the prefab, and create an animation controller for the Animator component.
And here starts the code!
#region TransformCatalog
class TransformCatalog : Dictionary<string, Transform>
{
#region Constructors
public TransformCatalog(Transform transform)
{
Catalog(transform);
}
#endregion
#region Catalog
private void Catalog(Transform transform)
{
Add(transform.name, transform);
foreach (Transform child in transform)
Catalog(child);
}
#endregion
}
#endregion
#region DictionaryExtensions
class DictionaryExtensions
{
public static TValue Find<TKey, TValue>(Dictionary<TKey, TValue> source, TKey key)
{
TValue value;
source.TryGetValue(key, out value);
return value;
}
}
#endregion
/**
* Manages all game info, instancing character, NPCs, etc
* */
public class GameInstance : MonoBehaviour {
public GameObject player;
string[] BodyParts;
// Use this for initialization
void Start () {
pause = false;
BodyParts = new string[2];
BodyParts[0] = "head";
BodyParts[1] = "torso";
BodyParts[2] = "legs";
InstancePlayer ();
}
// Update is called once per frame
void Update () {
}
void InstancePlayer() {
string[] test = new string[3];
test [0] = "mesh-head";
test [1] = "mesh-torso";
test [2] = "mesh-legs";
GameObject playerPrefab = Resources.Load("char-prefab") as GameObject;
player = Instantiate (playerPrefab, Vector3.zero, Quaternion.identity) as GameObject;
//find the empty gameobject marking the player position
var playerPos = GameObject.Find ("player");
player.transform.position = playerPos.transform.position;
BuildCharacter (player, test);
}
void BuildCharacter(GameObject parentGO, string[] pieces) {
GameObject temp; //temporary game object to hold pieces
GameObject[] body = new GameObject[5];
var boneCatalog = new TransformCatalog(parentGO.transform);
for (int i=0; i<=2; i++) {
var obj = Resources.Load(pieces[i]);
temp = Instantiate(obj) as GameObject;
body[i] = new GameObject(BodyParts[i]);
body[i].transform.SetParent(parentGO.transform);
body[i].transform.localPosition = Vector3.zero;
body[i].transform.localRotation = Quaternion.Euler(Vector3.zero);
body[i].transform.localScale = parentGO.transform.localScale;
//copy skinned mesh renderer components
CopySkinnedMeshRenderer(temp, body[i], boneCatalog);
//destroy it, or you will end with several pieces floating around
Destroy(temp);
}
}
void CopySkinnedMeshRenderer(GameObject source, GameObject target, TransformCatalog bones) {
var skinnedMeshRenderers = source.GetComponentsInChildren<SkinnedMeshRenderer> ();
foreach (var sourceRenderer in skinnedMeshRenderers)
{
var comp = target.AddComponent<SkinnedMeshRenderer>();
comp.sharedMesh = sourceRenderer.sharedMesh;
comp.materials = sourceRenderer.materials;
comp.bones = TranslateTransforms(sourceRenderer.bones, bones);
}
}
private Transform[] TranslateTransforms(Transform[] sources, TransformCatalog transformCatalog)
{
var targets = new Transform[sources.Length];
for (var index = 0; index < sources.Length; index++)
targets[index] = DictionaryExtensions.Find(transformCatalog, sources[index].name);
return targets;
}
}
What does this code does? It instantiates each piece, creates an empty GameObject in the character parent GameObject, and copies the SkinnedMeshRenderer component from the the first to the second. Then, it copies the bones from one to another. Yep, it is not clear for me from what one to what another. Sorry, I just copied the code and played with it until it worked, it doesnt means that I know why or how does it works. Also, I warn you that perhaps Im missing something. Judging from the sample posted in Gamedev, there could be more than simply copying SkinnedMeshRenderer and bones.
What I can confirm is that the previous code takes N body pieces and builds an animated mesh. Hope it works, and if it is useful to you, remember that our project needs donations!
First, lets start with the model, which obviously, is divided in the required sections. Lets say we have head, torso and legs. Each part must be exported to a separate fbx file, but it must include the skeleton. Then, export the skeleton without geometry to another fbx.
Im going to assume that you want to instantiate all components, and even player, at runtime, from a C# script (no Unity editor involved, except for creating prefab an position marker), so, we will place the sections, the skeleton and everything inside Assets/Resources. Then we will create a prefab for the character hierarchy, which we will call char-prefab. Drag and drop the skeleton to the prefab, and create an animation controller for the Animator component.
And here starts the code!
#region TransformCatalog
class TransformCatalog : Dictionary<string, Transform>
{
#region Constructors
public TransformCatalog(Transform transform)
{
Catalog(transform);
}
#endregion
#region Catalog
private void Catalog(Transform transform)
{
Add(transform.name, transform);
foreach (Transform child in transform)
Catalog(child);
}
#endregion
}
#endregion
#region DictionaryExtensions
class DictionaryExtensions
{
public static TValue Find<TKey, TValue>(Dictionary<TKey, TValue> source, TKey key)
{
TValue value;
source.TryGetValue(key, out value);
return value;
}
}
#endregion
/**
* Manages all game info, instancing character, NPCs, etc
* */
public class GameInstance : MonoBehaviour {
public GameObject player;
string[] BodyParts;
// Use this for initialization
void Start () {
pause = false;
BodyParts = new string[2];
BodyParts[0] = "head";
BodyParts[1] = "torso";
BodyParts[2] = "legs";
InstancePlayer ();
}
// Update is called once per frame
void Update () {
}
void InstancePlayer() {
string[] test = new string[3];
test [0] = "mesh-head";
test [1] = "mesh-torso";
test [2] = "mesh-legs";
GameObject playerPrefab = Resources.Load("char-prefab") as GameObject;
player = Instantiate (playerPrefab, Vector3.zero, Quaternion.identity) as GameObject;
//find the empty gameobject marking the player position
var playerPos = GameObject.Find ("player");
player.transform.position = playerPos.transform.position;
BuildCharacter (player, test);
}
void BuildCharacter(GameObject parentGO, string[] pieces) {
GameObject temp; //temporary game object to hold pieces
GameObject[] body = new GameObject[5];
var boneCatalog = new TransformCatalog(parentGO.transform);
for (int i=0; i<=2; i++) {
var obj = Resources.Load(pieces[i]);
temp = Instantiate(obj) as GameObject;
body[i] = new GameObject(BodyParts[i]);
body[i].transform.SetParent(parentGO.transform);
body[i].transform.localPosition = Vector3.zero;
body[i].transform.localRotation = Quaternion.Euler(Vector3.zero);
body[i].transform.localScale = parentGO.transform.localScale;
//copy skinned mesh renderer components
CopySkinnedMeshRenderer(temp, body[i], boneCatalog);
//destroy it, or you will end with several pieces floating around
Destroy(temp);
}
}
void CopySkinnedMeshRenderer(GameObject source, GameObject target, TransformCatalog bones) {
var skinnedMeshRenderers = source.GetComponentsInChildren<SkinnedMeshRenderer> ();
foreach (var sourceRenderer in skinnedMeshRenderers)
{
var comp = target.AddComponent<SkinnedMeshRenderer>();
comp.sharedMesh = sourceRenderer.sharedMesh;
comp.materials = sourceRenderer.materials;
comp.bones = TranslateTransforms(sourceRenderer.bones, bones);
}
}
private Transform[] TranslateTransforms(Transform[] sources, TransformCatalog transformCatalog)
{
var targets = new Transform[sources.Length];
for (var index = 0; index < sources.Length; index++)
targets[index] = DictionaryExtensions.Find(transformCatalog, sources[index].name);
return targets;
}
}
What does this code does? It instantiates each piece, creates an empty GameObject in the character parent GameObject, and copies the SkinnedMeshRenderer component from the the first to the second. Then, it copies the bones from one to another. Yep, it is not clear for me from what one to what another. Sorry, I just copied the code and played with it until it worked, it doesnt means that I know why or how does it works. Also, I warn you that perhaps Im missing something. Judging from the sample posted in Gamedev, there could be more than simply copying SkinnedMeshRenderer and bones.
What I can confirm is that the previous code takes N body pieces and builds an animated mesh. Hope it works, and if it is useful to you, remember that our project needs donations!
Comments
Post a Comment