Hey!
I’m trying to replicate how Bee Swarm Simulator made the bees movement, so far it works fine when you’re idle, but I don’t like the way it looks when you move.
I’m using client-side rendering to reduce server-side lag and do smooth tweens on the client, the server just tells the cframe where the “bee” would move and the client detect the change and move the “bee” to the position.
What I want to achieve: (this is not bee swarm simulator, but a fan-made of it)
My version:
Scripts:
Server:
function Bee:Init()
task.spawn(function()
while self.Object.Parent do
local character = self.Player.Character or self.Player.CharacterAdded:Wait()
local humanoid = character.Humanoid
local oldCFrame = self.LastCFrame
local newCFrame = self:GetCFrame()
local distance = (newCFrame.Position - oldCFrame.Position).Magnitude
local t = (distance/self.Speed) + .5
if humanoid.MoveDirection.Magnitude > 0 then
self.Object:SetAttribute("CFrame", newCFrame)
task.wait(t)
else
self.Object:SetAttribute("CFrame", newCFrame)
task.wait(t + rng:NextNumber(3,6))
end
end
end)
end
function Bee:CreateObject()
local object = Instance.new("Configuration")
object.Name = self.ID
object:SetAttribute("Name", self.Name)
object:SetAttribute("CFrame", CFrame.new(0, 0, 0))
object:SetAttribute("Speed", self.Speed)
object.Parent = bees[self.Player.Name]
return object
end
function Bee:GetCFrame()
local character = self.Player.Character or self.Player.CharacterAdded:Wait()
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
local X = rng:NextNumber(-15, 15)
local Y = rng:NextNumber(-1, 1)
local Z = rng:NextNumber(-15, 15)
self.Offset = CFrame.new(X, Y, Z)
self.LastCFrame = humanoidRootPart.CFrame * self.Offset
return self.LastCFrame
end
Client:
function BeeController:CreateBee(playerFolder, beeObject)
if not clientBees[playerFolder] then clientBees[playerFolder] = {} end
if not clientBees[playerFolder][beeObject.Name] then
local model = ReplicatedStorage.Bees:FindFirstChild(beeObject:GetAttribute("Name")):Clone()
model:PivotTo(beeObject:GetAttribute("CFrame"))
model.Parent = beeObject
beeObject:GetAttributeChangedSignal("CFrame"):Connect(function()
local distance = (model.PrimaryPart.Position - beeObject:GetAttribute("CFrame").Position).Magnitude
local direction = (model.PrimaryPart.Position - beeObject:GetAttribute("CFrame").Position).Unit
local lookCF = CFrame.lookAt(model.PrimaryPart.Position, model.PrimaryPart.Position + direction * -1)
local tween = TweenService:Create(model.PrimaryPart, TweenInfo.new(.25), {CFrame = lookCF})
tween:Play()
tween.Completed:Wait()
local tween = TweenService:Create(model.PrimaryPart, TweenInfo.new(distance/beeObject:GetAttribute("Speed"), Enum.EasingStyle.Linear), {CFrame = CFrame.new(beeObject:GetAttribute("CFrame").Position, beeObject:GetAttribute("CFrame").Position+ direction * -1)})
tween:Play()
tween.Completed:Wait()
local tween = TweenService:Create(model.PrimaryPart, TweenInfo.new(.25), {CFrame = CFrame.new(model.PrimaryPart.CFrame.Position) * CFrame.Angles(beeObject:GetAttribute("CFrame"):ToEulerAnglesXYZ())})
tween:Play()
tween.Completed:Wait()
end)
clientBees[playerFolder][beeObject.Name] = beeObject
end
end
For the final position is looks like most of them seem to face the same orientation once they reach their goal. Is that because all the created parts have the same look vector?
The Bee Swarm bees also seem to have a Y-axis bob as they move. Replicating this might make them look more natural overall.
I think to create the y axis bob effect, there are 2 layers of CFrame manipulation with sines/cosines.
(Put this all in a loop, render stepped, while loop, whatever works better for you.)
• The first layer is just the position. Just do something like
local speed = 2
local bob = math.sin(tick() * speed)
local part = script.Parent — place inside an anchored part
part.CFrame = part.CFrame * CFrame.new(0,bob,0)
What we have now is a simple bob with no rotation, just position, but we will do that in the next layer.
• The second layer is now the rotation. Just use the code from the first layer, and add this.
Sorry, I completely forgot about this.
Anyways, I didn’t add the bob, but you could try what @Vixionxry is suggesting, I’m not sure if it would work if you’re using tweens to move, like I did in the post.
But you can use deltaTime to calculate the CFrame and calculate the T that can be used to lerp between 2 positions to move them here’s an example:
function self:getT(data)
if not data.target then return end
local deltaTime = workspace:GetServerTimeNow() - data.time
local distance = (data.target - data.start).Magnitude
local distanceTraveled = (data.speed * deltaTime) + data.distance
local t = distanceTraveled / distance
return math.clamp(t, 0, 1)
end
Now with this data you can calculate the CFrame like so:
function self:getCFrame(data)
local t = self:getT(data)
local position = lerp(t, data.start, data.target)
local lookAtCFrame = CFrame.new(data.start, data.target)
local cframePosition = CFrame.new(position)
local rotation = lookAtCFrame.Rotation
return cframePosition * rotation
end
Instead of choosing 1 postion inside the ring around the player and going there
This would make it so that if the player moves its delayed. Which is what you did and not what you want.
Solution:
redefine postion every movement frame with the set offset.
determine the offset and apply that to the current player location.
Dont refresh the offset unless it needs to wander around the player.
Only update the target position to be player.pos+offset and then move to the point.
Also to reduce lag maybe check if the player is moving, so you dont have to keep sending new postion data thats the same.