How do I make rig's torso bend with playing animations?

I’m working on tower defense game and I’m reworking some towers while reworking it I got an idea to make tower’s torso and upper body bend to make it shoot enemy in correct way but after trying almost everything I just can’t make legs stay in place while rest bends, I have seen this in other games so I know it’s possible.

This is the closest I ever got to success -
obraz

I have tried making separate animations for legs and rest of the body, anchoring the legs while bending, calculating C0 and C1 of Hips but nothing worked so far. This is how the model looks in Explorer:


“Base” is PrimaryPart which is anchored if you need it.
Upgrades and Configuration folders only have some values for tower’s statistics and models for upgraded levels.

Main script of the tower (CoreGeneral)

local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage"):WaitForChild("ReplicatedStorage_CLOUD")

local Script = script
local Model = script.Parent.Parent

local Map = Workspace:WaitForChild("Map")
local Path = Map:WaitForChild("Path")
local Base = Path:FindFirstChild(tostring(#Path:GetChildren()))
local Torso = Model:WaitForChild("Torso")
local Head = Model:WaitForChild("Head")
local RightLeg = Model:WaitForChild("Right Leg")
local LeftLeg = Model:WaitForChild("Left Leg")
local Humanoid = Model:WaitForChild("Humanoid")
local HumanoidRootPart = Model:WaitForChild("HumanoidRootPart")

local Configuration = Model:WaitForChild("Configuration")
local _Damage = Configuration:WaitForChild("Damage")
local _Range = Configuration:WaitForChild("Range")
local _Can = Configuration:WaitForChild("Can")
local _ReloadTime = Configuration:WaitForChild("ReloadTime")
local _IsAttacker = Configuration:WaitForChild("IsAttacker")
local _Level = Configuration:WaitForChild("Level")
local _TargetingMode = Configuration:WaitForChild("TargetingMode")
local _Mode = Configuration:WaitForChild("Mode")
local _LastReadyTime = Configuration:WaitForChild("LastReadyTime")
local _IsReady = Configuration:WaitForChild("IsReady")

local Handles = {}

wait()
for _, v in pairs(Model:GetChildren()) do
	if v:FindFirstChild("Hold") then
		table.insert(Handles, v.Name)
	end
end

function getHandle()
	for _, v in pairs(Handles) do
		local obj = Model:WaitForChild(v)
		wait()
		if obj then
			return obj
		end
	end
end

local Enemies = Map:WaitForChild("Enemy")
local Animations = Model:WaitForChild("Animations")
local IdleAnim = Animations:WaitForChild("IdleAnimation")
local AttackAnim = Animations:WaitForChild("AttackAnimation")
local ReadyAnim = Animations:WaitForChild("ReadyAnimation")
local LegsAnim = Animations:WaitForChild("LegsAnimation")

local originalRightHipC0 = Torso["Right Hip"].C0
local originalLeftHipC0 = Torso["Left Hip"].C0

local AnimationTracks = {}

function playAnimation(animation, key, stopOthers)
	if stopOthers then
		for _, track in pairs(AnimationTracks) do
			track:Stop()
		end
	end

	local humanoidAnimator = Humanoid:FindFirstChild("Animator")
	if not humanoidAnimator then
		humanoidAnimator = Instance.new("Animator")
		humanoidAnimator.Parent = Humanoid
	end

	if AnimationTracks[key] then
		AnimationTracks[key]:Stop()
	end

	local newTrack = humanoidAnimator:LoadAnimation(animation)
	newTrack:Play()
	AnimationTracks[key] = newTrack
end

function bendTorsoToTarget(Target)
	local targetPosition = Target.Torso.Position or Target.HumanoidRootPart.Position
	local torsoPosition = Torso.Position

	local direction = (targetPosition - torsoPosition).unit

	-- Calculate the CFrame to face the target
	local targetCFrame = CFrame.lookAt(torsoPosition, torsoPosition + direction)
	local rotationOffset = CFrame.Angles(0, math.rad(-40), 0)
	local finalCFrame = targetCFrame * rotationOffset

	-- Set the torso's CFrame
	Torso.CFrame = finalCFrame

	-- Keep the legs stationary by restoring their C0 values
	Torso["Right Hip"].C0 = originalRightHipC0
	Torso["Left Hip"].C0 = originalLeftHipC0
end

function shootTarget(Target)
	spawn(function()
		if _Can.Value == true and _IsReady.Value then
			if not Target or not Target.Parent then
				return
			end

			_Can.Value = false
			for _, track in pairs(AnimationTracks) do
				track:Stop()
			end
			bendTorsoToTarget(Target)
			playAnimation(AttackAnim, "UpperBody", true)
			playAnimation(LegsAnim, "Legs", false)

			local weaponAttachment = getHandle():WaitForChild("Hold"):FindFirstChild("MuzzleAttachment")
			if not weaponAttachment then
				return
			end

			local enemyAttachment = Instance.new("Attachment")
			enemyAttachment.Parent = Target:FindFirstChild("Torso") or Target:FindFirstChild("HumanoidRootPart")

			weaponAttachment.MuzzleFlash.Enabled = true
			getHandle().FireSound:Play()

			local beam = weaponAttachment:FindFirstChild("BulletTrail")
			if beam then
				beam.Attachment0 = weaponAttachment
				beam.Attachment1 = enemyAttachment
				beam.Enabled = true
			end

			getHandle():WaitForChild("Rotate").MaxVelocity = 5

			wait(0.1)

			weaponAttachment.MuzzleFlash.Enabled = false

			if beam then
				beam.Enabled = false
			end
			enemyAttachment:Destroy()

			playAnimation(ReadyAnim, "UpperBody", true)
			playAnimation(LegsAnim, "Legs", false)
		end
	end)
	spawn(function()
		if Target.Configuration:FindFirstChild("Mechanical") then
			local Sparks = game.ReplicatedStorage.Sparks:Clone()
			Sparks.Parent = Target.Torso
			Map.Sounds.Ricochet:Play()
		else
			if Target.Configuration:FindFirstChild("Defense") then
				Target.Humanoid:TakeDamage(math.round(_Damage.Value * (math.abs(100 - Target.Configuration.Defense.Value) / 100)))
			else
				Target.Humanoid:TakeDamage(_Damage.Value)
			end
		end
		wait(_ReloadTime.Value)
		_Can.Value = true
	end)
end

function selectTarget(enemies)
	if _TargetingMode.Value == "ClosestToBase" then
		table.sort(enemies, function(a, b)
			local torsoA = a:FindFirstChild("Torso") or a:FindFirstChild("HumanoidRootPart")
			local torsoB = b:FindFirstChild("Torso") or b:FindFirstChild("HumanoidRootPart")
			if torsoA and torsoB then
				local pathA = a:FindFirstChild("Configuration") and a.Configuration:FindFirstChild("CurrentPath")
				local pathB = b:FindFirstChild("Configuration") and b.Configuration:FindFirstChild("CurrentPath")
				local distanceA = (torsoA.Position - Base.Position).Magnitude + (pathA and pathA.Value or 0)
				local distanceB = (torsoB.Position - Base.Position).Magnitude + (pathB and pathB.Value or 0)
				return distanceA < distanceB
			end
		end)
	elseif _TargetingMode.Value == "Strongest" then
		table.sort(enemies, function(a, b)
			return a.Humanoid.Health > b.Humanoid.Health
		end)
	elseif _TargetingMode.Value == "Random" then
		return enemies[math.random(1, #enemies)]
	elseif _TargetingMode.Value == "ClosestToTower" then
		table.sort(enemies, function(a, b)
			local torsoA = a:FindFirstChild("Torso") or a:FindFirstChild("HumanoidRootPart")
			local torsoB = b:FindFirstChild("Torso") or b:FindFirstChild("HumanoidRootPart")
			if torsoA and torsoB then
				return (torsoA.Position - Torso.Position).Magnitude < (torsoB.Position - Torso.Position).Magnitude
			end
		end)
	elseif _TargetingMode.Value == "FarthestFromBase" then
		table.sort(enemies, function(a, b)
			local torsoA = a:FindFirstChild("Torso") or a:FindFirstChild("HumanoidRootPart")
			local torsoB = b:FindFirstChild("Torso") or b:FindFirstChild("HumanoidRootPart")
			if torsoA and torsoB then
				local pathA = a:FindFirstChild("Configuration") and a.Configuration:FindFirstChild("CurrentPath")
				local pathB = b:FindFirstChild("Configuration") and b.Configuration:FindFirstChild("CurrentPath")
				local distanceA = (torsoA.Position - Base.Position).Magnitude + (pathA and pathA.Value or 0)
				local distanceB = (torsoB.Position - Base.Position).Magnitude + (pathB and pathB.Value or 0)
				return distanceA > distanceB
			end
		end)
	end

	return enemies[1]
end

function findPossibleTarget()
	local nearbyEnemies = {}
	for _, Enemy in pairs(Enemies:GetChildren()) do
		local torso = Enemy:FindFirstChild("Torso") or Enemy:FindFirstChild("HumanoidRootPart")
		if torso then
			if (Torso.Position - torso.Position).Magnitude < _Range.Value then
				table.insert(nearbyEnemies, Enemy)
			end
		end
	end

	if #nearbyEnemies > 0 then
		if tick() - _LastReadyTime.Value >= 2 and not _IsReady.Value then
			_Mode.Value = "Ready"
			playAnimation(ReadyAnim, "UpperBody", true)
			playAnimation(LegsAnim, "Legs", false)
			_LastReadyTime.Value = tick()
			getHandle():WaitForChild("StartUp"):Play()
			TweenService:Create(getHandle():WaitForChild("Rotate"), TweenInfo.new(2, Enum.EasingStyle.Sine, Enum.EasingDirection.In), {MaxVelocity = 5}):Play()
			wait(2)
			getHandle():WaitForChild("Spin"):Play()
			_IsReady.Value = true
		end

		if _IsReady.Value then
			local target = selectTarget(nearbyEnemies)
			if target then
				local targetTorso = target:FindFirstChild("Torso") or target:FindFirstChild("HumanoidRootPart")
				if targetTorso then
					bendTorsoToTarget(target)
					shootTarget(target)
					_LastReadyTime.Value = tick()
				end
			end
		end
	else
		if tick() - _LastReadyTime.Value >= 3 and _IsReady.Value then
			_Mode.Value = "Idle"
			playAnimation(IdleAnim, "UpperBody", true)
			playAnimation(LegsAnim, "Legs", false)
			_IsReady.Value = false
			TweenService:Create(getHandle():WaitForChild("Rotate"), TweenInfo.new(2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {MaxVelocity = 0}):Play()
			getHandle():WaitForChild("Spin"):Stop()
			getHandle():WaitForChild("Stopping"):Play()
		end
	end
end

playAnimation(IdleAnim, "UpperBody", true)
playAnimation(LegsAnim, "Legs", false)

spawn(function()
	while wait() do
		if _IsAttacker.Value == true then
			findPossibleTarget()
		end
	end
end)
1 Like

I’m a bit confused with what you’re asking, but if you’re asking about deforming the actual torso mesh (so it’s bendy), the only solid solution I could see is re-rigging the rig yourself in Blender and weight-painting different vertices of the torso to correspond with different bones of the rig. You could then animate the torso to be as bendy as you want. After that, you’d just export this custom rig and use it in game. My assumption is this is how “other games” you are referring to achieve this, but I could be wrong though.

2 Likes

It’s not what I mean, maybe it is confusing (I was tired when I wrote that). You know games like Welcome to Bloxburg where your character follows the cursor? I’m looking for something like that but the tower follows enemy

1 Like

Okay I’ve managed to make a script that calculates right C0 for hips

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.