I just wanted to correct a very common misunderstanding that I see around here, even among people knowledgeable with Cinema 4D: that Cinema 4D doesn’t export joints to FBX, and that you have to convert your joints to bones before they can be imported into Unity. This is simply not true with R11. Just to make it really really clear:
Cinema 4D R11 exports joints to FBX!
Got it? Good.
The only thing you have to do is manually bake any IK or Xpresso before putting the .c4d file in your assets folder, as baking isn’t done automatically when exporting to FBX. I’ve put up a script on the wiki that does this in a reliable way. It also does some other nifty stuff, like automatically saving the baked file to your assets folder and deleting your controllers so they don’t clutter up the hierarchy in Unity.
Now stop perpetuating the myth and start using them joints!
Has anyone here had the time to test this more extensively?
I got one first test to work without IK but with IK setup … I can see the animation playing but the deformation fails.
The result I get in Unity looks like the picture below. Sometimes the limbs don’t look that weird but even baked (with your script) the IK still won’t show inside Unity - the movement apparently is without any JointRotation.
[edit] Another thing I noticed: if you pose a model with a Bones/ClaubeBonet skeleton without setting a keyframe the pose is immediately visible in unity. With a Joint/Skin setup this doesn’t work - a posed model without IK (for me) only shows up on animation.
Something that would interest me from UT’s side of the development: The manual says that there is “no reliable way of exporting animated characters that use the Joint’s” system. Did some features of Joint based animation work in R10.1 already but were just too buggy to support officially?
tolm: how complex meshes/Characters did you manage to implement so far? Did you encounter any odd behaviours since your initial post?
I’ve been using joints for the past couple of months with a rig that’s got 24 joints, some constraints, regular IK, spline IK and some XPresso. It’s definitely not as complex as a standard human (it’s a cute robot) but it does use a lot of the features you would use for a standard human rig. Since I got it set up correctly I haven’t had any problems at all.
I remember messing around with different ways to organise the animations when I set it up, and what I found worked the best was to have the character in T-pose without animation in a file named character.c4d, and each animation in a separate file with the character@animation.c4d naming scheme.
I also remember having some trouble with rotation not being exported/imported for joints that had no keyframes at all, but all I did then was animate them slightly, and that solved it. Perhaps that’s the problem you’re having?
I’ve tried the script on three different files (and no @ in the filesnames) so I figure I must be missing something.
The attached file is from Cinema R11 on a Mac. When running the script it saves out the new file into the right location but no baking has been applied.
Sorry. It should be the other way around; the filename has to have an @ in it, or the script won’t bake. I tried renaming your file IkTest@whatever.c4d and it bakes just fine.
I changed the file names to include the @ symbol and it still won’t bake for me.
When I run the script it runs through the animation and appears to open a new Cinema4D window momentarily, but when I check the newly created file it’s the same as the original.
I used the exact same file I sent you and renamed it just like you did and still no baking. Any ideas?
Ah! I found out what the problem is. My joint hierarchy isn’t parented to anything, so I wrote the script to skip anything at the top level that isn’t a joint. But your joint hierarchy is parented to a null, so the script skipped it. Stupid of me.
Here’s a new version that bakes keyframes for all joints in the scene, no matter where they are in the hierarchy:
/* Export to Unity script.
*
* Bakes animation for all joints in the scene, deletes
* any controllers or other objects that you specify,
* and saves the result as a new file at a specified path,
* leaving the original untouched.
*
* Note: The script will only bake animation if the
* filename has an @ in it.
*/
// Inputs. Change these to match your own setup.
var bakePosition = 1;
var bakeScale = 0;
var bakeRotation = 1;
var fps = 30;
var savePath = "/Users/User/Documents/Project/Assets/";
var deleteObjects;
assignArray()
{
deleteObjects = new(array,2);
deleteObjects[0] = "Controllers";
deleteObjects[1] = "High Poly";
}
// Constants
const var joint = 1019362;
const var goto_start = 12501;
const var goto_next_frame = 12414;
const var record_active_objects = 12410;
const var record_position = 12417;
const var record_scale = 12418;
const var record_rotation = 12419;
const var delete_object = 12109;
const var close = 12664;
const var use_expressions = 13522;
// Bakes the current frame for all joints
bakeJoints(doc, obj)
{
while (obj != NULL)
{
// Only key joints
if (obj->GetType() == joint)
{
doc->SetActiveObject(obj);
CallCommand(record_active_objects);
}
// Loop through all children and bake them as well
var child = obj->GetDown();
while (child != NULL)
{
bakeJoints(doc, child);
child = child->GetNext();
}
obj = obj->GetNext();
}
}
// Deletes the object hierarchy with the specified name
deleteObject(doc, name)
{
var obj = doc->GetFirstObject();
while (obj != NULL)
{
if (obj->GetName() == name)
{
doc->SetActiveObject(obj);
CallCommand(delete_object);
}
obj = obj->GetNext();
}
}
main(doc, op)
{
// Because you can't assign arrays outside functions - yeah stupid
assignArray();
// Set what to parameters to bake
if (IsCommandChecked(record_position) != bakePosition)
{
CallCommand(record_position);
}
if (IsCommandChecked(record_scale) != bakeScale)
{
CallCommand(record_scale);
}
if (IsCommandChecked(record_rotation) != bakeRotation)
{
CallCommand(record_rotation);
}
// Only bake keyframes if the filename contains "@"
var currFilename = doc->GetFilename();
var file = currFilename->GetLastString();
if(strstr(file, "@") != -1)
{
// Bake keyframes for all joints
CallCommand(goto_start);
var frame = doc->GetTime()->GetFrame(fps);
var maxFrame = doc->GetMaxTime()->GetFrame(fps);
for (; frame <= maxFrame; frame++)
{
var obj = doc->GetFirstObject();
bakeJoints(doc, obj);
CallCommand(goto_next_frame);
DrawViews(DA_NO_THREAD);
}
}
// Delete objects
var i;
for(i = 0; i < sizeof(deleteObjects); i++)
{
deleteObject(doc, deleteObjects[i]);
}
// Save as new file
var currFilename = doc->GetFilename();
var newFilename = new(Filename);
newFilename->SetFullString(savePath);
newFilename->AddLast(currFilename->GetLastString());
doc->Save(newFilename);
println(newFilename->GetFullString(), " exported");
// Close the new file
CallCommand(close);
// Reopen original file
LoadDocument(currFilename);
println(currFilename->GetFullString(), " reopened");
}
Unfortunately you can’t tag individual objects to be deleted. What the script does is look at the root hierarchies in the scene, and delete those whose names match the names in the deleteObjects array of the script.
So put all your controllers in a root null that you name “Controllers”, and all your high poly stuff in a root null named “High Poly”. Or name the nulls whatever you want and change the names in the script. If you have three nulls named “Splines”, “High Poly” and “Delete On Export”, the deleteObjects part would look like this:
In the description it mentions it gets around the gaps in the timeline when baking the object. Curious to see if it solves your problem, haven’t tested it out myself yet.
Yes I’ve already tried it, but it takes forever to process (45 min for 1010 frames) and at the end, some body members act weirdly. I have to admit my rigging is somehow complex, mixing expresso, constraints, clamps, IKs … So it should be difficult to just have it done without a problem
Good to hear you’ve managed to fix it by modifying that script. I will probably start exporting rigs to Unity in a couple of weeks, now I’m not so worried anymore that I will run into unfixable problems.