so i’m working on a bullet hell game where you have to dodge monster’s attacks, and it can be different types of attacks, some attacks go in a straight line from the monster to player, some attacks bounce of walls, some attacks follow the player until they hit them or disappear, etc…
what i did before is making the server side handle the visual part and the damage, which caused tons of lags when i was spawning a lot of projectiles, so i tried to find a way to make the game handle a lot of and found this post
it’s a bit different than what i’m trying to achieve but i followed a bit of this idea with some changes.
here’s my script, they are currently make the projectile only go forward
Server Side:
return function(atkObject: Instance, owner: Model, target: Model, dataMod: {})
local close = false
Event:FireAllClients(atkObject, owner, target, dataMod)
local speed: Vector3 = Vector3.new(1, 0, 0) * dataMod.Speed
local startPos = owner.PrimaryPart.Position
-- movement
coroutine.wrap(function()
while task.wait() and not close do
startPos = startPos + Vector3.new(1, 0, 0)
end
end)()
-- damage
coroutine.wrap(function()
local db = false
while task.wait() and not close do
if not db and DmgScript(startPos, dataMod, target) then
db = true
task.wait(dataMod.DamageDelay)
db = false
end
end
end)()
task.wait(dataMod.TimeForNextMove)
close = true
end
DamageScript:
i took the partsInRadius
function from the post above and changed it a bit, i know i can’t use the radius for every attack because not every attack will be a sphere, but i’ll leave that like this for now
function partsInRadius(hitboxPosition, hitboxRadius, filter: {Instance})
-- if a cframe was passed, just take the position out of it
if typeof(hitboxPosition) == "CFrame" then
hitboxPosition = hitboxPosition.Position
end
-- make a default overlap params if one is not passed in the argument
local overlapParams = OverlapParams.new()
overlapParams.FilterDescendantsInstances = filter
overlapParams.FilterType = Enum.RaycastFilterType.Include
local hit = workspace:GetPartBoundsInRadius(hitboxPosition, hitboxRadius, overlapParams)
-- now that we have all the parts the spatial query found, let's go through it and see if we can find anyone!
local hitCharacters = {}
for i, v in pairs(hit) do
if v.Parent then
v = v.Parent -- get the actual character model, since spatial query will return the parts in the model
end
-- if we've already accounted for their character or they don't have a humanoid, skip them!
if table.find(hitCharacters, v) or not v:FindFirstChild("Humanoid") then continue end
-- insert them into the array
table.insert(hitCharacters, v)
end
return hitCharacters
end
return function(position: CFrame, Attack, target: Model)
local hit = partsInRadius(position, 2, {target})
if #hit > 0 then
print(hit)
local player = game.Players:GetPlayerFromCharacter(hit[1])
if player and not hit[1]:FindFirstChildWhichIsA("ForceField") then
-- visualizehit(position)
_G.DamagePlayer(player, Attack.Damage)
return true
end
end
return false
end
Client Side:
vent.OnClientEvent:Connect(function(atkObject: Instance, owner: Model, target: Model, dataMod: {})
local speed: Vector3 = Vector3.new(1, 0, 0) * dataMod.Speed
local con
local atk: Part = atkObject:Clone()
atk.CanCollide = false
local startingPos = owner.PrimaryPart.Position
con = RunService.Heartbeat:Connect(function()
startingPos += Vector3.new(1, 0, 0)
atk.Position = startingPos
end)
atk.Parent = workspace.Temp
Derbis:AddItem(atk, dataMod.LifeTime)
task.wait(dataMod.LifeTime)
con:Disconnect()
end)
also here’s how the attack data look like
{
Damage = 2, -- amount of damage dealt to the player
DamageDelay = 1, -- time between each damage
LifeTime = 5, -- time till attack destroyed
Speed = 50, -- speed of the attack
TimeForNextMove = 1, -- time before next attack
TimeBeforeAttack = 2, -- time before attack starts
}
i’m currently not using the speed variable, because it seems like when i do use it the projectiles is a little too fast to even see it
there’s a little problem which it seams like the server side position is a bit ahead of the client side one, so i get damage a little before the attack could reach me. also i’m not sure if it’s a good approach in general with the wide variety of the attacks i will have.