Wait if i use the module what would i put as my control points?
Not sure, I’ve never used those modules before. They should have some documentation though.
Also, here’s another module for tower defense games:
hm. It looks good, but it still seems… kind of too smooth? okay that just sounds really stupid, but I want to make the rotations realistic, not smoother than soap. can u give me some pointers on how to make a curve rotation from scratch? Thanks
also, some weird bug where the client dosent finish the tween beforethe server teleports it. Heres a video:
hers the code:
--!native
local module = {}
local ServerStorage = game:GetService("ServerStorage")
--local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
--local PhysicsService = game:GetService("PhysicsService")
local moveremote = ReplicatedStorage:WaitForChild("tweenenemy")
local Players = game:GetService("Players")
module.spawn = function(name: string, waypointfolder: Folder, exit, spawns: CFrame, boss: BoolValue)
local offset = math.random(-1, 1)
local model: Model = ServerStorage.Enemies:FindFirstChild(name):Clone()
model:PivotTo(spawns + Vector3.new(offset, 0, 0))
if boss then
ReplicatedStorage.AddHealthBar:FireAllClients(name, model.Health.Value, model.Health.Value)
end
--for _,parts:BasePart in pairs(model) do
-- if parts:IsA("BasePart") then
-- parts.CollisionGroup = "Enemies"
-- end
--end
model.Parent = Workspace:FindFirstChild(ReplicatedStorage.Map.Value).enemies
--local health = model.health
--health:GetPropertyChangedSignal("Value"):Connect(function()
-- if health.Value == 0 then
-- model:Destroy()
-- end
--end)
model.PrimaryPart:SetNetworkOwner(nil)
local enemySpeed = model.speed.value
--model.speed:GetPropertyChangedSignl("Value"):Connect(function()
-- enemySpeed = model.speed.Value
--end)
local _, size = model:GetBoundingBox()
for index = 1, #waypointfolder:GetChildren() - 2 do
local node = waypointfolder[index]
local finished = false
local distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
local movethread = task.spawn(function()
local movementgoal = {
CFrame = CFrame.lookAlong(
node.Position + Vector3.new(offset, size.Y / 2, 0),
model.PrimaryPart.CFrame.LookVector
),
}
ReplicatedStorage.tweenenemy:FireAllClients(model, distance / enemySpeed, movementgoal)
task.wait(distance / enemySpeed)
model.PrimaryPart.CFrame = CFrame.lookAlong(
node.Position + Vector3.new(offset, size.Y / 2, 0),
model.PrimaryPart.CFrame.LookVector
)
finished = true
end)
local speed: IntValue = model:WaitForChild("speed")
local changedthread = nil
local speedChanged = nil
speedChanged = speed:GetPropertyChangedSignal("Value"):Connect(function()
if movethread then
task.cancel(movethread)
movethread = nil
end
if changedthread then
task.cancel(changedthread)
changedthread = nil
end
enemySpeed = model.speed.value
distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
print(
"newspeed: "
.. tostring(enemySpeed)
.. " "
.. "newdistance: "
.. tostring(distance)
.. " "
.. "newtime: "
.. " "
.. tostring(distance / enemySpeed)
)
changedthread = task.spawn(function()
print("updating tweens")
local movementgoal = {
CFrame = CFrame.lookAlong(
node.Position + Vector3.new(offset, size.Y / 2, 0),
model.PrimaryPart.CFrame.LookVector
),
}
ReplicatedStorage.tweenenemy:FireAllClients(model, distance / enemySpeed, movementgoal)
task.wait(distance / enemySpeed)
model.PrimaryPart.CFrame = CFrame.lookAlong(
node.Position + Vector3.new(offset, size.Y / 2, 0),
model.PrimaryPart.CFrame.LookVector
)
finished = true
end)
end)
repeat
task.wait()
until finished == true
speedChanged:Disconnect()
local rotationgoal = {
CFrame = CFrame.lookAlong(model.PrimaryPart.Position, node.CFrame.LookVector),
}
local rotation
ReplicatedStorage.tweenenemy:FireAllClients(model, 0.05, rotationgoal)
task.wait(0.05)
if not model then
break
end
model.PrimaryPart.CFrame = CFrame.lookAlong(model.PrimaryPart.Position, node.CFrame.LookVector)
end
if model then
local node = exit
local finished = false
local distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
local movethread = task.spawn(function()
local movementgoal = {
CFrame = CFrame.lookAlong(
node.Position + Vector3.new(offset, size.Y / 2, 0),
model.PrimaryPart.CFrame.LookVector
),
}
ReplicatedStorage.tweenenemy:FireAllClients(model, distance / enemySpeed, movementgoal)
task.wait(distance / enemySpeed)
model.PrimaryPart.CFrame = CFrame.lookAlong(
node.Position + Vector3.new(offset, size.Y / 2, 0),
model.PrimaryPart.CFrame.LookVector
)
finished = true
end)
local speed: IntValue = model:WaitForChild("speed")
local changedthread = nil
local speedChanged = nil
speedChanged = speed:GetPropertyChangedSignal("Value"):Connect(function()
if movethread then
task.cancel(movethread)
movethread = nil
end
if changedthread then
task.cancel(changedthread)
changedthread = nil
end
enemySpeed = model.speed.value
distance = (node.Position + Vector3.new(offset, size.Y / 2, 0) - model.PrimaryPart.Position).Magnitude
print(
"newspeed: "
.. tostring(enemySpeed)
.. " "
.. "newdistance: "
.. tostring(distance)
.. " "
.. "newtime: "
.. " "
.. tostring(distance / enemySpeed)
)
changedthread = task.spawn(function()
print("updating tweens")
local movementgoal = {
CFrame = CFrame.lookAlong(
node.Position + Vector3.new(offset, size.Y / 2, 0),
model.PrimaryPart.CFrame.LookVector
),
}
ReplicatedStorage.tweenenemy:FireAllClients(model, distance / enemySpeed, movementgoal)
task.wait(distance / enemySpeed)
model.PrimaryPart.CFrame = CFrame.lookAlong(
node.Position + Vector3.new(offset, size.Y / 2, 0),
model.PrimaryPart.CFrame.LookVector
)
finished = true
end)
end)
repeat
task.wait()
until finished == true
speedChanged:Disconnect()
ReplicatedStorage.BaseHealth.Value = math.clamp(
ReplicatedStorage.BaseHealth.Value - model.Health.Value,
0,
ReplicatedStorage.MaxBaseHealth.Value
)
model:Destroy()
end
end
return module
Edit: the only things i changed with the spawning is how the enemys are spawned. I made the wave system work with dual lanes, so calling the module more.
Make the wait longer. It’s most likely looking like this because it takes time for the client to receive the event.
Is this what you want?
I am going to try implementing this today. Thank you!
Also i noticed that now, if the client is lagging badly, the enemy will start teleporting around. Is there any way I can fix this?
Okay, i am in the process of implementing it, i am just trying to figure out where to put the 3rd control point. I am using Bezier Path. Something in noticed about the system is that when the enemy count starts to rack up, the enemys start instantly teleporting/spawning right at the 1st or second node. Heres a vid:
Also, the lag is getting a bit out of hand is there any way i could optimize this more? And again, sorry for bothering you. Thanks!
Edit: this dosent have the curves implemented
You could try to get rid of all the humanoids, because those have a lot of lag.
You can try to put a point at every node for BezierPath.
I already did delete the humanoids.
So do I just put a point at the corner of every node, and use that for the control points?
@Katrist
No, just use every node’s position in your positions table for the BezierPath.new
.
Okay? But I thought we needed an extra node to pull the curve up?
If you want the curve to be more exaggerated, you can move the nodes.
Also, could you show some code?
@Katrist I tried implementing this, but it was super choppy, and it wouldn’t wait enough to delete its self. And on the 1st path, the enemies don’t damage the base at all.
Here’s the code:
--!native
--!strict
local module = {}
local ServerStorage = game:GetService("ServerStorage")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local PhysicsService = game:GetService("PhysicsService")
local moveremote = ReplicatedStorage:WaitForChild("tweenenemy")
local Players = game:GetService("Players")
module.spawn = function(name:string, waypointfolder:Folder, exit, spawns:CFrame, boss:BoolValue)
local offset = math.random(-1,1)
local tweenenemy = ReplicatedStorage.tweenenemy
local model: Model = ServerStorage.Enemies[name]:Clone()
model:PivotTo(spawns + Vector3.new(offset,0,0))
if boss then
ReplicatedStorage.AddHealthBar:FireAllClients(name,model.Health.Value,model.Health.Value)
end
--for _,parts:BasePart in pairs(model) do
-- if parts:IsA("BasePart") then
-- parts.CollisionGroup = "Enemies"
-- end
--end
model.Parent = Workspace:FindFirstChild(ReplicatedStorage.Map.Value).enemies
--local health = model.health
--health:GetPropertyChangedSignal("Value"):Connect(function()
-- if health.Value == 0 then
-- model:Destroy()
-- end
--end)
model.PrimaryPart:SetNetworkOwner(nil)
local speed:IntValue = model:WaitForChild("speed")
--model.speed:GetPropertyChangedSignl("Value"):Connect(function()
-- enemySpeed = model.speed.Value
--end)
local _, size = model:GetBoundingBox()
local nodepositions = {}
local enemyspeed = speed.Value
local nodeindex = 0
print("spawningenemy")
for _ = 1, #waypointfolder:GetChildren() - 2 do
nodeindex += 1
local node:Part = waypointfolder:FindFirstChild(tostring(nodeindex))
table.insert(nodepositions,node.CFrame.Position)
end
table.insert(nodepositions,waypointfolder.exit.CFrame.Position)
print("got position table")
print(unpack(nodepositions))
local totalwait = 0
nodeindex = 0
for _ = 1, #waypointfolder:GetChildren() - 2 do
local success, err = pcall(function()
nodeindex += 1
local node:Part = waypointfolder:FindFirstChild(tostring(nodeindex))
local distance = (node.CFrame.Position + Vector3.new(offset,size.Y / 2,0) - model.PrimaryPart.CFrame.Position).Magnitude
totalwait += distance
end)
if err then
warn(err)
end
end
print("generated wait")
tweenenemy:FireAllClients(model,nodepositions,Vector3.new(offset,size.Y / 2,0))
task.wait(totalwait / enemyspeed)
ReplicatedStorage.BaseHealth.Value -= model.Health.Value
model:Destroy()
end
return module
heres the client code:
ReplicatedStorage.tweenenemy.OnClientEvent:Connect(function(model:Model,Position:table,offset:Vector3)
print("tweening enemy")
local modelpath = BezierPath.new(Position,3)
for i = 0,1,1/100 do
model.PrimaryPart.CFrame = modelpath:CalculateUniformCFrame(i) + offset
task.wait(0.1)
end
end)
here’s a vid:
@Katrist
thank you and have a happy new year!
Edit: wrong code, replaced code
I would do this all on the server, since BezierPath is fairly performant.
Code:
--!native
--!strict
local module = {}
local ServerStorage = game:GetService("ServerStorage")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local PhysicsService = game:GetService("PhysicsService")
local moveremote = ReplicatedStorage:WaitForChild("tweenenemy")
local Players = game:GetService("Players")
module.spawn = function(name:string, waypointfolder:Folder, exit, spawns:CFrame, boss:BoolValue)
local offset = math.random(-1,1)
local tweenenemy = ReplicatedStorage.tweenenemy
local model: Model = ServerStorage.Enemies[name]:Clone()
model:PivotTo(spawns + Vector3.new(offset,0,0))
print("spawningenemy")
if boss then
ReplicatedStorage.AddHealthBar:FireAllClients(name,model.Health.Value,model.Health.Value)
end
--for _,parts:BasePart in pairs(model) do
-- if parts:IsA("BasePart") then
-- parts.CollisionGroup = "Enemies"
-- end
--end
model.Parent = Workspace:FindFirstChild(ReplicatedStorage.Map.Value).enemies
--local health = model.health
--health:GetPropertyChangedSignal("Value"):Connect(function()
-- if health.Value == 0 then
-- model:Destroy()
-- end
--end)
model.PrimaryPart:SetNetworkOwner(nil)
local speed:IntValue = model:WaitForChild("speed")
--model.speed:GetPropertyChangedSignl("Value"):Connect(function()
-- enemySpeed = model.speed.Value
--end)
local _, size = model:GetBoundingBox()
local nodePositions = {}
for i = 1, #waypointfolder:GetChildren() - 2 do
table.insert(nodePositions, waypointfolder[i].Position + Vector3.new(offset, size.Y / 2, 0))
end
table.insert(nodePositions, waypointfolder.exit.Position)
local newPath = BezierPath.new(nodePositions)
local t = 0
while t < 1 do
model:PivotTo(newPath:CalculateUniformCFrame(t))
t += speed.Value / 100
task.wait()
end
ReplicatedStorage.BaseHealth.Value -= model.Health.Value
model:Destroy()
end
return module
Make sure to add a variable for BezierPath, I didn’t add one.
If it’s too fast, make sure to change this value:
t += speed.Value / 100
Make the 100 a higher number for the speed to be lower, or make the 100 a lower number for the speed to be higher.
Have you anchored the PrimaryPart? You also won’t need :SetNetworkOwner(nil)
.
Happy new year to you too!
I tried anchoring the primary part before, but that didn’t really work. I don’t know if calling set network ownership hanged the script, but il try it today. Thank you!