3D Animation (Character)
Ball With Tail Rig for beginnings in character animation
Below is a extremely dated version of a Unicycle Rig. I would do a lot of the elements differently now.
Floursack built to be animated strictly FK. Excellent tool to teach Step Key and block animation techniques.
[FlourSack Character Rig 2024]
Beginning rig for learning walkcycles and other animation fundamentals.
[Humanoid stick character with just legs]
3D Stickman character with index finger and group fingers. Solid beginning for animating humanoid characters without the need for dialogue.
[Bipedal humanoid stick character]
Helpful MEL Scripts
Setting Keys Auto or Stepped
The key types can be changed via Preferences > Setting > Animation, but that can be cumbersome. The scripts below can be used to swap the default type of key set via a shelf executed script.
Set the key type to 'stepped' on new set keys.
keyTangent -e -global -itt clamped; keyTangent -e -global -ott step;
Set the key type to 'auto' on new set keys.
keyTangent -e -global -itt auto; keyTangent -e -global -ott auto;
Karl's Key Framer Helper
The MEL script below creates a floating window to help with character animation.
Extremely helpful for those indidviduals who are "clickers" instead of those who love keyboard shortcuts.
global string $select[];
$select = {"placeHolder", "otherPlaceHolder"};
window -resizeToFitChildren 1 KarlsCharacterKeyFramer;
columnLayout;
text -label "Make those keyframes";
button - label "Copy keyframes" -command "timeSliderCopyKey";
button - label "Paste keyframes" -command "timeSliderPasteKey false";
button - label "Delete keyframes" -command "timeSliderClearKey";
button - label "Gizmo Object" -command "manipRotateContext -e -mode 0 Rotate; manipMoveContext -e -mode 0 Move";
button - label "Gizmo World" -command "manipRotateContext -e -mode 1 Rotate; manipMoveContext -e -mode 2 Move";
button - label "Graph Editor" -command "GraphEditor";
button - label "Store Selection" -command "$select = `ls -sl`";
button - label "Get Selection" -command "select -r $select";
button - label "SET KEY" -command "setKeyframe";
button - label "SET KEY BLOCK" -command "setKeyframe -itt linear -ott step";
showWindow;
Mirror Transforms Locally
Mirror/swap control curves transforms. Good for swapping hands and feet to alternate mirror positions, like in a copied walkcycle pose.
Note: This script will only work when the mirrored curves are sharing a hierarchical parent which can be used as the x-axis local zero. Example, hand controllers parented to waist controllers, not floating or only parented to the 'root' curve.
Usage: Select a control curve (like a left hand), then select the alternate control curve (like a right hand), then execute the script.
// Get ordered selection
string $sel[] = `ls -sl -o`;
if (size($sel) != 2)
{
warning "Select exactly two curvs.";
}
else
{
string $curveA = $sel[0];
string $curveB = $sel[1];
// --------------------------------
// Store LOCAL transforms (vector attrs)
// --------------------------------
float $posA[] = `getAttr ($curveA + ".translate")`;
float $rotA[] = `getAttr ($curveA + ".rotate")`;
float $sclA[] = `getAttr ($curveA + ".scale")`;
float $posB[] = `getAttr ($curveB + ".translate")`;
float $rotB[] = `getAttr ($curveB + ".rotate")`;
float $sclB[] = `getAttr ($curveB + ".scale")`;
// --------------------------------
// Apply MIRRORED A → B (local space)
// --------------------------------
setAttr ($curveB + ".translate")
(-$posA[0]) $posA[1] $posA[2];
setAttr ($curveB + ".rotate")
$rotA[0] (-$rotA[1]) (-$rotA[2]);
setAttr ($curveB + ".scale")
$sclA[0] $sclA[1] $sclA[2];
// --------------------------------
// Apply MIRRORED B → A (local space)
// --------------------------------
setAttr ($curveA + ".translate")
(-$posB[0]) $posB[1] $posB[2];
setAttr ($curveA + ".rotate")
$rotB[0] (-$rotB[1]) (-$rotB[2]);
setAttr ($curveA + ".scale")
$sclB[0] $sclB[1] $sclB[2];
}
Mirror Around a Selected Pivot
Mirror/swap script which uses three curves. The user selects the two curves to mirror then the third curve used as a pivot.
Usage: Select the curves you intend to mirror and the third curve you want to mirror around, then execute the script.
// -------------------------------
// 3-Curve Mirror & Swap Reference-Safe Script
// -------------------------------
// First-time variable declaration
string $selection[] = `ls -sl -o`;
if (size($selection) != 3) {
warning "Select exactly 3 curves: curveA, curveB, mirrorCurve (last)";
} else {
// Assign selection (reuse variable, no redeclaration)
$selection = `ls -sl -o`;
string $curveA = $selection[0];
string $curveB = $selection[1];
string $mirror = $selection[2];
// -------------------------------
// Create utility nodes
// -------------------------------
string $mmA = `createNode multMatrix`;
string $decompA = `createNode decomposeMatrix`;
string $mmB = `createNode multMatrix`;
string $decompB = `createNode decomposeMatrix`;
// -------------------------------
// Connect world matrices to get local in mirror space
// -------------------------------
connectAttr -f ($curveA + ".worldMatrix[0]") ($mmA + ".matrixIn[0]");
connectAttr -f ($mirror + ".worldInverseMatrix[0]") ($mmA + ".matrixIn[1]");
connectAttr -f ($mmA + ".matrixSum") ($decompA + ".inputMatrix");
connectAttr -f ($curveB + ".worldMatrix[0]") ($mmB + ".matrixIn[0]");
connectAttr -f ($mirror + ".worldInverseMatrix[0]") ($mmB + ".matrixIn[1]");
connectAttr -f ($mmB + ".matrixSum") ($decompB + ".inputMatrix");
// -------------------------------
// Read local transforms
// -------------------------------
float $posA[] = `getAttr ($decompA + ".outputTranslate")`;
float $rotA[] = `getAttr ($decompA + ".outputRotate")`;
float $sclA[] = `getAttr ($decompA + ".outputScale")`;
float $posB[] = `getAttr ($decompB + ".outputTranslate")`;
float $rotB[] = `getAttr ($decompB + ".outputRotate")`;
float $sclB[] = `getAttr ($decompB + ".outputScale")`;
// -------------------------------
// Mirror across local X
// -------------------------------
float $mirAPos[] = { -$posA[0], $posA[1], $posA[2] };
float $mirARot[] = { $rotA[0], -$rotA[1], -$rotA[2] };
float $mirBPos[] = { -$posB[0], $posB[1], $posB[2] };
float $mirBRot[] = { $rotB[0], -$rotB[1], -$rotB[2] };
// -------------------------------
// Compose mirrored local matrices
// -------------------------------
string $cmA = `createNode composeMatrix`;
string $cmB = `createNode composeMatrix`;
setAttr -type "double3" ($cmA + ".inputTranslate") $mirBPos[0] $mirBPos[1] $mirBPos[2];
setAttr -type "double3" ($cmA + ".inputRotate") $mirBRot[0] $mirBRot[1] $mirBRot[2];
setAttr -type "double3" ($cmA + ".inputScale") $sclB[0] $sclB[1] $sclB[2];
setAttr -type "double3" ($cmB + ".inputTranslate") $mirAPos[0] $mirAPos[1] $mirAPos[2];
setAttr -type "double3" ($cmB + ".inputRotate") $mirARot[0] $mirARot[1] $mirARot[2];
setAttr -type "double3" ($cmB + ".inputScale") $sclA[0] $sclA[1] $sclA[2];
// -------------------------------
// Multiply with mirror world matrix
// -------------------------------
string $mmFinalA = `createNode multMatrix`;
string $mmFinalB = `createNode multMatrix`;
connectAttr -f ($cmA + ".outputMatrix") ($mmFinalA + ".matrixIn[0]");
connectAttr -f ($mirror + ".worldMatrix[0]") ($mmFinalA + ".matrixIn[1]");
connectAttr -f ($cmB + ".outputMatrix") ($mmFinalB + ".matrixIn[0]");
connectAttr -f ($mirror + ".worldMatrix[0]") ($mmFinalB + ".matrixIn[1]");
// -------------------------------
// Get final world matrices
// -------------------------------
float $finalA[] = `getAttr ($mmFinalA + ".matrixSum")`;
float $finalB[] = `getAttr ($mmFinalB + ".matrixSum")`;
// -------------------------------
// Apply final transforms (expanded for MEL xform)
// -------------------------------
xform -ws -m
$finalA[0] $finalA[1] $finalA[2] $finalA[3]
$finalA[4] $finalA[5] $finalA[6] $finalA[7]
$finalA[8] $finalA[9] $finalA[10] $finalA[11]
$finalA[12] $finalA[13] $finalA[14] $finalA[15]
$curveA;
xform -ws -m
$finalB[0] $finalB[1] $finalB[2] $finalB[3]
$finalB[4] $finalB[5] $finalB[6] $finalB[7]
$finalB[8] $finalB[9] $finalB[10] $finalB[11]
$finalB[12] $finalB[13] $finalB[14] $finalB[15]
$curveB;
// -------------------------------
// Cleanup
// -------------------------------
delete $mmA $decompA $mmB $decompB $cmA $cmB $mmFinalA $mmFinalB;
}