How to animate inanimate objects

So iv tried to animate a walker.
Friends have told me its impossible. But i want to do it. Iv seen multiple games do it aswell.
So…What do you guys have in mind?. How do i animate?.
Desktop_181102_2010

4 Likes

It’s pretty complex, but it’s harder when you need to do it procedurally. So first thing: Is this walker going to be procedurally animated – will it be controlled by an AI or player, or have some other form of animation that can’t be predicted – or will it be doing the same thing (e.g. just playing an idle animation forever, not actually moving)? Procedural animation is harder.

If you want to make procedural animations:
Unless you want to be doing some crazy CFrame math (which you might need to do anyway), your model will need to be unanchored and rigged. You would rig it with joints (using the new constraints, or the ones used in the default character models), and then make animations (manually, doing some advanced CFraming stuff and/or using the built-in behavior of the constraints, or easily, using the animation system and an AnimationController inside model). You’ll need to handle lots of edge cases, and it’ll be tough, but it’ll be nice and generally useful when it’s finished.

If you want to make hardcoded (predictable, not procedural) animations:
You can do the same things as above, but your job will be much easier as you can avoid a lot of math and other complexities. If you’re using the easy way as described above, you could literally just make an animation in the animation editor and play it on a loop. You would do a similar thing as this (but with more code and copying and pasting) if you were making your animations manually with CFrames and the built-in behavior of the joints.

Hopefully this isn’t too discouraging! I haven’t really delved that deep into this area of Roblox (which is one of the reasons why I haven’t given any code examples, as well as the fact that just describing what you could do is a big enough job in itself), it’s not really my forte, but it’s certainly achievable as long as you’re willing to learn and be persistent :slight_smile:

9 Likes

Cheers for helping!.

I want to make it so a player controls it. And I dont know alot of scripting. I can get around C-Framing tho. The animation does not have to be that high-level dinosaur type thing. Just typical go forward animation. Turn left/right. Backward animation. I dont know how to animate in general. As in, how to go about it. I barely even know the basics!. Im willing to learn!

2 Likes

Alright, here’s my recommendation. No CFraming knowledge will be required, however you may need some extra code to get left/right animations working. This will be based almost entirely off the default Roblox features for characters, and the hardest thing you’ll need to do is customise the core scripts (such as the animation/control scripts to handle left/right animations). This won’t be super advanced but will work with the least amount of complex stuff as possible.

Create a custom character with a humanoid and rig it using a plugin (I recommend the Character Creator plugin. Make sure to disable CanCollide on all parts except the root and the head, and also set the Humanoid’s HipHeight with some manual property changing during a test or your mech will sink into the ground). Next up, copy this rig and use this copy to make the animations in the default Animation Editor (you can use plugins made by other users, but they require some extra knowledge so are not recommended if you’re a complete beginner to animation – that being said, they do teach you some good stuff so if you’re interested in learning, let me or others know and we’ll help). Make sure to export the animations to Roblox.

Next, you’ll want to get a copy of the default animation script (you can do this by going into a test and copying the LocalScript inside the character), and then you want to replace the animation IDs with your custom ones. Whenever the mech character is made, you’ll want to put this LocalScript inside it – it’s up to you how you do that. If you’re mech is going to be the player’s only character in your game, you can name your character StarterCharacter and put it into StarterPlayer -> StarterCharacterScripts. You can put the modified LocalScript inside your character model, or just into StarterCharacterScripts, it’s up to you. Now if you test your mech, it should be playing your animations, except for the left/right ones.

Here’s the hard part: The scripting. You’ll probably need to modify the default control script (as it plainly deals with whether the player is moving left or right), as well as the animation script, and get them to communicate with each other so that when the player changes which direction they are moving in (left, right, forward, back, none), the control script tells the animation script what’s going on using a BindableEvent so that the animation script can play the correct animation. This is pretty complicated and it’ll be hard if you’re not an experienced scripted (although it’ll probably be a good learning experience if you can figure things out).

Here’s some sample (probably unusable in this state) code to give an idea of what I mean:

Animation LocalScript

local directionBindable = game.ReplicatedStorage.Bindables.CharacterDirectionChanged
local currentDirection = "None"

cirectionBindable.Event:Connect(function(newDirection)
    currentDirection = newDirection
end)

-- In the 'step animate' part of the Animation script which is running constantly
if currentDirection == "Left" then
    playAnimation("LeftWalk") 
elseif currentDirection == "Right" then
    playerAnimation("RightWalk")
end

The modified PlayerModule -> ControlModule -> Keyboard ModuleScript

-- This function already exists, you just need to add the sending part
function Keyboard:UpdateMovement(inputState)
	if inputState == Enum.UserInputState.Cancel then
		self.moveVector = ZERO_VECTOR3
	else
		self.moveVector = Vector3.new(self.leftValue + self.rightValue, 0, self.forwardValue + self.backwardValue)
	end

    local toSend = -- Making use of inline statements like this can really lower the number of if statements you need to use
        (self.moveVector.X < 0 and "Left") or
        (self.moveVector.X > 0 and "Right") or
        (self.moveVector.X == 0 and "None")
    directionBindable:Fire(toSend) -- Send which direction the player is moving in to the animation script
end

That’s the gist of it, although there’s a bit more to it than that. It’s quite a big undertaking for a beginner so don’t worry too much if you encounter problems – just ask away on here.

Hopefully this has been helpful :slight_smile:

7 Likes

I’d advise against using the Character Creation Plugin - I’ve never gotten it to work. However, this plugin is more intuitive in my opinion, and is one I always use to Rig characters or weapon models.

How do I see that its properly rigged?. Can i see its rigging?. I did mess around with the ‘edit’ option but that still does not show me the mechs rigging. It does however show me a random humanoids.
I tried rigging the right leg and… I did rig it but i cant see its rigging in the edit menu.
The problem was the humanoid root part. When it was inside the model, i could not see the rigsDesktop_181104_1403

I’ve tried this too a time ago. Now that I was reading the reply’s , it actually works. thanks.

The ‘rigging’ objects get usually get parented inside HumanoidRootPart, or one of the parts they connect to (e.g. Right Arm). They are Motor6D objects and don’t look like anything special from the explorer.

The plugin should you the joints and their rotations in the viewport.

1 Like

Sorry for necrobumping this 2 year old thread, but I can’t resist mechs :heart:

Yep, it’s possible and yeah for tanks on legs.

I have combined the two resources below to do create the above animation.

  1. My Inverse kinematics FABRIK Module

  2. @iGottic procedural animation for R15

This post has really inspired me to release these two prototypes/example scripts for my current IK module

Firstly is the animation controller module which I completely stole from @iGottic to make the legs rotate in a sorta circle.

Procedural animation module script
--Module to handle the procedural animation the hip and legs
local CF			=CFrame.new -- We don't want to type CFrame.new over and over
local ANGLES		=CFrame.Angles -- Again, so we don't have to repeat it

--store the functions
local module = {}

--Variables
local left,right	= 0,math.pi -- Initial rotations
local x_and_y = Vector3.new(1, 0, 1)

local direction = Vector3.new(1, 0, 1) -- This will be changed
local stride = 12 -- Changes how far the legs move
local cycleSpeed = 4 -- How fast the leg-movement cycle is. Change this to suit your needs!

local strideOffset = 2

--[[
    Function to rotate both legs in a circular manner which 
]] 
function module.rotateLegs(dt,rig,raycastParams)


    -- Radius of the circle at CFrame front of the player
    local strideCF = CFrame.new(0, 0, -stride / 2) -- Turn that stride number into a CFrame we can use

    local raycastParams = raycastParams -- raycasting

    -- Begin the step-------
    local dt10 = math.min(dt*10, 1) -- Normalize dt for our needs

    local rootpart = rig.HumanoidRootPart
    local rootvel0 = rootpart.Velocity -- Our movement velocity

    local rootvel = rootvel0 * x_and_y --XY plane velocity only
    local rootvelm = rootvel.Magnitude --root velocity magnitude

    --if moving then lerp current direction
    if rootvelm > 0.1 then 
        --lerp current direction towards curren velocity
        direction = direction:Lerp(rootvel.unit, dt10) 
    end
    
    local tau = 2*math.pi
    local up		=rootvelm/16
    local cycle		=up*dt*cycleSpeed
    right			=(right+cycle)%tau
    left			=(left+cycle)%tau

    local waist1 = rig.RootMotorC1
    local waistjoint = rig.RootMotor

    local lowercf = rig.HumanoidRootPart.CFrame

    --Then  control the legs only if moving
    if rootvelm > 0.1 then

        local relv0		=lowercf:vectorToObjectSpace(rootvel)
        local relv1		=relv0*0.2
        
        do -- Upper Torso
            local goalCF = waistjoint.C1:Lerp(waist1*ANGLES(math.rad(relv1.Z),0.1*math.cos(right)-2*math.rad(relv1.X),math.rad(-relv1.X)):inverse(),dt10)	
            --local rotationOnly = goalCF-goalCF.Position
            waistjoint.C1	=	goalCF
        end

        do -- Right Leg
            local hip			=rig.rightLeg.RightHipAttachment.WorldPosition
            --Position of where the lower leg should be, spread out
            local ground		=rig.rightLeg.RightFootAttachment.WorldPosition
            -- right is the angle and the negative is the direction the position is handling
            --Stide CF
            --CFrame positioned at the ground looking at velocity direction
            local desiredPos	=(CF(ground, ground+direction)*ANGLES(-right, 0, 0)*strideCF).p
            local offset		=(desiredPos-hip)--vector from hip to the circle
            local raycastResult = workspace:Raycast(hip,offset.unit*(offset.magnitude+strideOffset),raycastParams)
            local footPos = raycastResult and raycastResult.Position or (hip + offset.unit*(offset.magnitude+strideOffset))
            
            --Do IK towards foot pos
            rig.rightLeg.RightLimbChain:IterateOnce(footPos,0.1)

        end

        do-- Left Leg
            local hip			= rig.leftLeg.LeftHipAttachment.WorldPosition
            local ground 		= rig.leftLeg.LeftFootAttachment.WorldPosition
            --rotate around where the ground should be, in a circular manner
            local desiredPos	=(CF(ground, ground+direction)*ANGLES(-left, 0, 0)*strideCF).p
            local offset		=(desiredPos-hip)
            local raycastResult = workspace:Raycast(hip,offset.unit*(offset.magnitude+strideOffset),raycastParams)
            local footPos = raycastResult and raycastResult.Position or (hip + offset.unit*(offset.magnitude+strideOffset))
            
            --Do IK towards foot pos
            rig.leftLeg.LeftLimbChain:IterateOnce(footPos,0.1)
        end

    else -- when not moving lerp to original position

        --when not moving go back to original position
        local goalCF = waistjoint.C1:Lerp(waist1, dt10)
        --local rotationOnly = goalCF-goalCF.Position
        waistjoint.C1	= goalCF

        local down = 10*Vector3.new(0,-1,0)
        do -- Right Leg
            --local hipcf			=rig.rightLeg.RightHipAttachment
            local hip			=rig.rightLeg.RightHipAttachment.WorldPosition
            local desiredPos	=rig.rightLeg.RightFootAttachment.WorldPosition+down
            local offset		=(desiredPos-hip)
            local raycastResult = workspace:Raycast(hip,offset.unit*(offset.magnitude),raycastParams)
            local footPos = raycastResult and raycastResult.Position or (hip + offset.unit*(offset.magnitude))
            
            rig.rightLeg.RightLimbChain:IterateOnce(footPos,0.1)
        end
        
        do -- Left Leg
            --local hipcf			=(rootcf*lhipcf)
            local hip			=rig.leftLeg.LeftHipAttachment.WorldPosition
            local desiredPos	=rig.leftLeg.LeftFootAttachment.WorldPosition+down
            local offset		=(desiredPos-hip)
            local raycastResult = workspace:Raycast(hip,offset.unit*(offset.magnitude),raycastParams)
            local footPos = raycastResult and raycastResult.Position or (hip + offset.unit*(offset.magnitude))
            
            --Do IK Towards foot pos
            rig.leftLeg.LeftLimbChain:IterateOnce(footPos,0.1)
        end

    end

end

return module

Next is the IK setup which is actually pretty hard and will definitely need to be modified for your mech.

Local script in starter player script
--Local script in starter player for controlling the mech animation

--Get service
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

--Modules required
--IK creator
local IKControllerPointer = ReplicatedStorage.Source.ObjectFolder.LimbChain
local LimbChain = require(IKControllerPointer)

--IK mover
local ProceduralAnimationPointer = ReplicatedStorage.Source.ModuleFolder.ProceduralAnimationController
local mechController = require(ProceduralAnimationPointer)

-------------------Import all the Constraints Types-----------------

--BallSocketConstraint
local BallSocketConstraintPointer = ReplicatedStorage.Source.ObjectFolder.ConstraintTypes.BallSocketConstraint
local BallSocketConstraint = require(BallSocketConstraintPointer)


--HingeConstraint
local HingeConstraintPointer = ReplicatedStorage.Source.ObjectFolder.ConstraintTypes.HingeConstraint
local HingeConstraint = require(HingeConstraintPointer)

--Rigid Constraint
local RigidConstraintPointer = ReplicatedStorage.Source.ObjectFolder.ConstraintTypes.RigidConstraint
local RigidConstraint = require(RigidConstraintPointer)

----------------------------------------------------------------


-- Pointers
local mech = workspace.Praetor
local lowerBody = workspace.Praetor

-- Obtain Motor6d's in left leg
local lHipToLegMotor = lowerBody.Hip.LUpperLeg
local lUpToKneeMotor = lowerBody.LeftLeg.LUpperLeg.LKnee
local lJKneeToLowMotor = lowerBody.LeftLeg.LKnee.LLowerLeg
local lLowToFeetMotor = lowerBody.LeftLeg.LLowerLeg.LFeet

-- Obtain Motor6d's in Right leg
local rHipToLegMotor = lowerBody.Hip.RUpperLeg
local rUpToKneeMotor = lowerBody.RightLeg.RUpperLeg.RKnee
local rJKneeToLowMotor = lowerBody.RightLeg.RKnee.RLowerLeg
local rLowToFeetMotor = lowerBody.RightLeg.RLowerLeg.RFeet

--Store the motor6d in table
local motorTable = {lHipToLegMotor,lUpToKneeMotor,lJKneeToLowMotor,lLowToFeetMotor}

--Store the motor6d in table
local motorRightTable = {rHipToLegMotor,rUpToKneeMotor,rJKneeToLowMotor,rLowToFeetMotor}

--Initialize the left leg chain
local leftLegChain = LimbChain.new(motorTable,true)

--Foot placement system
local footParams = RaycastParams.new()
footParams.FilterDescendantsInstances = {mech}

leftLegChain.FootPlacementRaycastParams = footParams
leftLegChain.LengthToFloor = 20

--Get all the attachments in the left foot
leftLegChain.FootBottomAttachment = lowerBody.LeftLeg.LFeet.FootBottom
leftLegChain.FootBottomRightAttachment = lowerBody.LeftLeg.LFeet.FootBottomRight

--Initialize the right leg chain
local rightLegChain = LimbChain.new(motorRightTable,true)

--Repeat for right leg
rightLegChain.FootPlacementRaycastParams = footParams
rightLegChain.LengthToFloor = 20

--Get all the attachments in the left foot
rightLegChain.FootBottomAttachment = lowerBody.LeftLeg.LFeet.FootBottom
rightLegChain.FootBottomRightAttachment = lowerBody.LeftLeg.LFeet.FootBottomRight


--Testing the constraint
local testBallSocketConstraint = mech.Constraints.UpperLegConstraint
local upperLegBallSocketConstraint = BallSocketConstraint.new(testBallSocketConstraint,30,30)

local kneePart = mech.Constraints.KneeConstraint
local lKneeBallSocket = BallSocketConstraint.new(kneePart,20,89)

local lLegPart = mech.Constraints.LowerLegConstraint
local lLegBallSocket = BallSocketConstraint.new(lLegPart,20,89)

--Make the FABRIK chain not move
local rigidFeet = RigidConstraint.new(leftLegChain,4)

--[[
    Create the alternative constraints which uses hinge
    More restrictive and glitchy close to original joint but better fitting and looks nicer visually
]]
local upperLegBallSocketConstraintAlternative = BallSocketConstraint.new(testBallSocketConstraint,40,40)
local lKneeHinge = HingeConstraint.new(kneePart,90,90)
local lLegHinge = HingeConstraint.new(lLegPart,90,90)


--Set up two constraint tables to allow
local leftLegConstraintsPrimary = {upperLegBallSocketConstraint,lKneeHinge,lLegHinge,rigidFeet}
local leftLegConstraintsSecondary = {upperLegBallSocketConstraintAlternative,lKneeBallSocket,lLegBallSocket,rigidFeet}

--Set the constraints of the object
leftLegChain:SetPrimaryConstraints(leftLegConstraintsPrimary)
leftLegChain:SetSecondaryConstraints(leftLegConstraintsSecondary)

--Set the region for the primary constraints
local leftLegRegionPart1 = mech.ConstraintZones.LeftLegPart1
local leftLegRegionPart2 = mech.ConstraintZones.LeftLegPart2
local leftLegRegion = {leftLegRegionPart1,leftLegRegionPart2}
leftLegChain:SetPrimaryConstraintRegion(leftLegRegion)

--Repeat for the right leg-----------

--Same constraints but for right leg
local rightBallSocketConstraintPart = mech.Constraints.rUpperLegConstraint
local rupperLegBallSocketConstraint = BallSocketConstraint.new(rightBallSocketConstraintPart,30,30)

local rKneePart = mech.Constraints.rKneeConstraint
local rKneeBallSocket = BallSocketConstraint.new(rKneePart,20,90)

local rLegPart = mech.Constraints.rLowerLegConstraint
local rLegBallSocket = BallSocketConstraint.new(rLegPart,20,80)

--Make the FABRIK chain not move
local rigidRightFeet = RigidConstraint.new(rightLegChain,4)

--[[
    Create the alternative constraints which uses hinge
    More restrictive and glitchy close to original joint but better fitting and looks nicer visually
]]
local rightUpperLegBallSocketConstraintAlternative = BallSocketConstraint.new(rightBallSocketConstraintPart,40,40)
local rKneeHinge = HingeConstraint.new(rKneePart,90,90)
local rLegHinge = HingeConstraint.new(rLegPart,90,90)

--Construct the constraints table
local rightLegConstraintsPrimary = {rightUpperLegBallSocketConstraintAlternative,rKneeHinge,rLegHinge,rigidRightFeet}
local rightLegConstraintsSecondary = {rupperLegBallSocketConstraint,rKneeBallSocket,rLegBallSocket,rigidRightFeet}

--Set the constraints of the object
rightLegChain:SetPrimaryConstraints(rightLegConstraintsPrimary)
rightLegChain:SetSecondaryConstraints(rightLegConstraintsSecondary)

--Set the region for the primary constraints
local rightLegRegionPart1 = mech.ConstraintZones.RightLegPart1
local rightLegRegionPart2 = mech.ConstraintZones.RightLegPart2
local rightLegRegion = {rightLegRegionPart1,rightLegRegionPart2}
rightLegChain:SetPrimaryConstraintRegion(rightLegRegion)



--turn on debug mode if u want
--leftLegChain:DebugModeOn()

--For the procedural animation

--Procedural animation controller setup

local movingPartsCollision = RaycastParams.new()
movingPartsCollision.FilterDescendantsInstances = {mech}

local mechRootMotor = lowerBody.HumanoidRootPart.LowerTorso

local mechRootPart = lowerBody.HumanoidRootPart
local leftHipAttachment = lowerBody.Hip.LeftHip
local rightHipAttachment = lowerBody.Hip.RightHip

local leftStepAttachment = lowerBody.Hip.LeftLegStep
local rightStepAttachment = lowerBody.Hip.RightLegStep

local mechRootMotorC1Store = mechRootMotor.C1

--Procedural animation controller setup

local praetorRig = {
    ["HumanoidRootPart"] = mechRootPart,
    ["RootMotor"] = mechRootMotor,
    ["RootMotorC1"] = mechRootMotorC1Store,
    ["rightLeg"] = {
        ["RightLimbChain"] = rightLegChain,
        ["RightHipAttachment"]= rightHipAttachment,
        ["RightFootAttachment"] = rightStepAttachment,
    },

    ["leftLeg"] = {
        ["LeftLimbChain"] =leftLegChain,
        ["LeftHipAttachment"]= leftHipAttachment,
        ["LeftFootAttachment"] = leftStepAttachment,
    }
}

--[[
    Then use the LimbChain object to control the motor every heartbeat
    ]]
RunService.Heartbeat:Connect(function(step)

    mechController.rotateLegs(step,praetorRig,movingPartsCollision)

    leftLegChain:UpdateMotors()
    rightLegChain:UpdateMotors()

end)

Yeah these scripts are just prototypes using my FABRIK IK method currently glitchy in various and complicated to setup I’ll be surprised if someone else makes it work in the current state.

Movement scripts are shipped separately though.

But yeah it’s possible :+1:, hope this helps.

4 Likes