Hi Dev Community,
I was toying with the idea of using pivot points and a parent-child hierarchy to make custom rigs and wanted to post the converter (which I’m calling “Riggity”) here in case someone other than me finds the idea useful.
The basic idea:
- Create a folder or model to house the hierarchy
- Create a single BasePart to function as the root of the hierarchy
- Parent Parts/MeshParts into the hierarchy and set joint locations by moving Pivot Points (using Pivot Editor)
- Select the model/folder to convert and click the “Run Scripts” button on the MODEL tab (far right end)
- Navigate to the place where you stored the provided script and press “Open”
- Check Output window for any messages
- Animate your model in the usual way
Notes:
- It uses your edited pivot points to place attachments, which Humanoid:BuildRigFromAttachments then uses to do the real work.
- It will do some other stuff like set anchors and weld parts if named with leading “__” or “_” (see comments in script)
- It’s non destructive since it works from a clone of your source model (new model will be placed in same location as orig)
- Non BasePart objects should be copied to the new model more or less where you’d expect
- I tried to give it useful warning messages, so check the output if it isn’t doing what you want
Copy/paste the provided code to a Script and Right-click in Explorer to save to your drive. (Someone feel free verify the safety of using this code in that way for those who aren’t comfortable making that determination on their own).
Script
--[[
Riggity - Easy Rig Builder Using Pivots and Parent-Child Hierarchy (Version 0.2)
USE:
* Parent parts together in Studio and use native Pivot Editor to set joint locations.
- Place this hierarchy inside a folder or model
* Then run this script to convert the hierarchy of parts (w Pivot Edits) into an animatable model.
- Can run script using Studios "Run Script" button (far right side of MODEL tab)
Example Layout:
Folder (or Model)
[Can have other stuff parented here besides root BasePart as long as they aren't also BaseParts]
Root (Should be only BasePart child the Folder (or Model) - will become PrimaryPart of Model)
Body
LArm
RArm
Neck
Head
__Eye (names starting with "__" will be welded to parent and not re-parented)
__Eye
__Ear
__Ear
LLeg
RLeg
_Spike (names starting with '_' will be welded to parent (Body) and re-parented to model)
etc
NOTES:
* THERE SHOULD BE ONLY ONE BasePart object (Part, MeshPart, etc) parented to the selected folder (or model)
- The script drills down through a hierarchy of BaseParts and there can be only one at the root
- Other items in the selected folder (or model) that are not BaseParts will be copied to fin model
* PART NAMES SHOULD BE UNIQUE
- Parts to be welded and parented to a part need to have names starting with "__"
- Parts with names starting with "__" will be welded to their parent and not re-parented to the model
- These are also set to CanCollide false, etc
- Parts with names starting with "_" will be welded to their parent and reparented to model
* If you put folders or models within the hierarchy, be sure to sel the parent folder before running the script
- it will try to find folder or model ancestor as top of chain, so any you add could interrupt that process
* Script creates a clone so doesn't change your original src hierarchy
By Astr0Derp 2022
]]
--- Setup ---
-- set this to false if you want a Humanoid instead of an AnimationController in fin model
local USE_ANIMATION_CONTROLLER = true
-- change this if you don't want root part set to invisible
local ROOT_IS_INVISIBLE = true
-- change this if you don't want the model parented to workspace until end of processing
-- if there is a problem the resulting partially complete model can be used for troubleshooting
-- setting to false will wait to parent the model until the very end (showing nothing if it fails)
local PARENT_MODEL_EARLY = true
--- Main ---
local selected = game.Selection:Get()[1]
local selection
if selected:IsA("Folder") or selected:IsA("Model") then
selection = selected
else
selection = selected:FindFirstAncestorOfClass("Folder")
if not selection then
selection = selected:FindFirstAncestorOfClass("Model")
end
end
if selection == nil then
warn("Riggity: **FAILED!** \nPlease select a model or parent folder containing parts to rig.")
selected = {}
game.Selection:Set(selected)
return
elseif selection:FindFirstChildOfClass("Humanoid") ~= nil or
selection:FindFirstChildOfClass("AnimationController") ~= nil then
warn("Riggity: **FAILED!** \nThis model already has a Humanoid (or AnimationController).")
selected = {}
game.Selection:Set(selected)
return
end
local TMP_FOLDER = "riggity_tmp"
local model = Instance.new("Model")
model.Name = selection.Name
if PARENT_MODEL_EARLY then
model.Parent = workspace
end
-- let's clone the selection instead of messing with the original
local root = selection:FindFirstChildWhichIsA("BasePart"):Clone()
local nextItem = root
while nextItem do
-- DFS looking for BaseParts with BasePart children
local BasePartchild = nextItem:FindFirstChildWhichIsA("BasePart")
if BasePartchild == nil then
-- nextItem is a leafNode
local prevItem = nextItem.Parent
local leafNode = nextItem
if leafNode ~= root then
if leafNode.Name:match("^__") then
-- is sub-part lvl weld
leafNode.CanCollide = false
leafNode.CanQuery = false
leafNode.CanTouch = false
if leafNode:FindFirstChildOfClass("WeldConstraint") == nil then
local weld = Instance.new("WeldConstraint")
weld.Part0 = leafNode.Parent
weld.Part1 = leafNode
weld.Parent = leafNode
end
-- put in temp folder to not break DFS
local tmp = leafNode.Parent:FindFirstChild(TMP_FOLDER)
if tmp == nil then
tmp = Instance.new("Folder")
tmp.Name = TMP_FOLDER
tmp.Parent = leafNode.Parent
end
leafNode.Name = leafNode.Name:sub(3)
leafNode.Anchored = false
-- move to tmp folder
leafNode.Parent = tmp
elseif leafNode.Name:match("^_") then
-- is model lvl weld
if leafNode:FindFirstChildOfClass("WeldConstraint") == nil then
local weld = Instance.new("WeldConstraint")
weld.Part0 = leafNode.Parent
weld.Part1 = leafNode
weld.Parent = leafNode
end
leafNode.Name = nextItem.Name:sub(2)
leafNode.Anchored = false
-- move to model
leafNode.Parent = model
else
-- not a weld, so make part of rig
local att = Instance.new("Attachment")
att.Name = leafNode.Name .. "RigAttachment"
att.Parent = leafNode
att.WorldCFrame = leafNode.CFrame * leafNode.PivotOffset
att2 = att:Clone()
att2.Parent = leafNode.Parent
att2.CFrame = CFrame.new()
att2.WorldCFrame = att.WorldCFrame
-- unpack any tmp folder
local tmp_folder = leafNode:FindFirstChild(TMP_FOLDER)
if tmp_folder then
for _, tmpItem in tmp_folder:GetChildren() do
tmpItem.Parent = leafNode
end
tmp_folder:Destroy()
end
leafNode.Anchored = false
if model:FindFirstChild(leafNode.Name) then
warn("Riggity: **WARNING!** \nMore than one rigged part with same name: " .. leafNode.Name)
end
-- move to model
leafNode.Parent = model
end
-- continue the DFS
nextItem = prevItem
else
-- we're back at the root
leafNode.Anchored = true
leafNode.Parent = model
nextItem = nil
end
else
-- found child BasePart so keep drilling down
nextItem = BasePartchild
end
end
--[[
Final step uses Humanoid:BuildRigFromAttachments(), so the model needs a
HumanoidRootPart as the PrimaryPart.
Once the rig is built, the code below does this (not nec in this order)
1. swaps out the Humanoid for an AnimationController (if UseAnimationController is true)
and sets the root name to what it was originally.
2. sets model's PrimaryPart to root and makes it invis (if ROOT_IS_INVISIBLE == true)
3. moves any remaining items that were in the original folder (or model) to the new model
4. selects the new model in Studio
5. and we're done
]]
local rootName = root.Name
root.Name = "HumanoidRootPart"
model.PrimaryPart = root
if not PARENT_MODEL_EARLY then
model.Parent = workspace
end
if ROOT_IS_INVISIBLE then
model.PrimaryPart.Transparency = 1
end
local bpCount = 0
for _, otherItem in selection:GetChildren() do
if not otherItem:IsA("BasePart") then
otherItem.Parent = model
else
bpCount += 1
if bpCount > 1 then
warn("Riggity: **WARNING!** \nThere was more than one ROOT in your selected model/folder. \nCheck your parenting and try again.")
selected = {model}
game.Selection:Set(selected)
return
end
end
end
local hum = Instance.new("Humanoid")
hum.Parent = model
hum:BuildRigFromAttachments()
if USE_ANIMATION_CONTROLLER then
hum:Destroy()
local animController = Instance.new("AnimationController")
animController.Parent = model
model.PrimaryPart.Name = rootName
end
selected = {model}
game.Selection:Set(selected)
warn("Riggity: DONE! \nYour " .. model.Name .. " model is finished (See workspace).")
Here’s about 3 min of videos showing the rough process. The first two demonstrate construction of a very basic model, and the third converts it into something animatable. In the third video where I press the “Run Script” button, nothing shows in the video because it doesn’t show the pop-up window. At any rate, that’s where I navigate to the script and press “Open” to run it. Feel free to put all this through its paces and let me know if you run into issues with the script.
Enjoy!