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 -
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)