So… I’ve got this placement system the script works and all, there’s no problem with any of that BUT, I’ve been wondering how I should go about visually showing the models after placement.
Like right now whenever you “place” an item it sends an event from the client to the server and then the server clones the model and places it in the desired spot and it kind of does like a little tween and emits particles (which can be buggy at times).
SO, I was wondering. Should I just store the data on the server and have all the client’s handle the placement and the model???
hopefully this was kind of a coherent post.
any input helps!!!
also in the video the model-coming-up-from-the-ground-sequence looks good because this was recorded in studio. In the actual game it looks kinda buggy choppy (as it is on the server).
Probably should have sent my scripts too.
but I’m already using SyncTween
–Snippet of module
function PlacementController:place()
if not self.equipped then return end
if not self.visualizationModel then return end
if self.isModelColliding then return end
self.placed = true
PlaceEvent:FireServer(self.visualizationModel.Name, self.modelTargetCFrame)
self.visualizationModel:Destroy()
self:destroy()
end
–Server Script (handles event)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local ToolOrientedSystems = ReplicatedStorage.ToolOrientedSystems
local Replication = ReplicatedStorage.Replication
local TweenReplication = Replication.TweenReplication
local PlacementSystem = ToolOrientedSystems.PlacementSystem
local ObjectSetup = PlacementSystem.Object
local Remotes = PlacementSystem.Remotes
local SyncTween = require(TweenReplication.SyncTween)
local Place = Remotes.Place
Place.OnServerEvent:Connect(function (player: Player, model: string, cframe: CFrame)
model = ObjectSetup:FindFirstChild(model)
if not model then return end
local completed = false
model = model:Clone()
local clone = model:Clone()
clone.Parent = workspace
clone:PivotTo(cframe * CFrame.new(0,-clone.CollisionBox.Size.Y,0))
local tween = SyncTween.new(clone.PrimaryPart, TweenInfo.new(0.3, Enum.EasingStyle.Sine), {
CFrame = cframe
})
tween:Play()
tween.Completed:Connect(function ()
completed = true
end)
while not completed do
clone.CollisionBox.Emitter:Emit(5)
task.wait()
end
end)
I’m pretty sure SyncTween plays the tween effectively on the client and just updates on the server necessary info like once it ends though. Could you show how exactly it looks in-game? It looks pretty decent on Studio that’s pretty much how it should look in-game too, otherwise the issue might be something else.
When the server receives the RemoteEvent, instead of doing the placement on the server, you can just send it back to all clients (after validation).
Place.OnServerEvent:Connect(function (player: Player, model: string, cframe: CFrame)
model = ObjectSetup:FindFirstChild(model)
if not model then return end
local isValid = false
-- validate placement
if isValid then
Place:FireAllClients(model, cframe) -- let all players render it on their clients
end
end)
Lastly, you’ll have an .OnClientEvent on your client that will do the placement.
PlaceEvent.OnClientEvent:Connect(function(model, cframe)
model = ObjectSetup:FindFirstChild(model)
if not model then return end
local completed = false
-- model = model:Clone() -- removed this,, what does this even do?
local clone = model:Clone()
clone.Parent = workspace
clone:PivotTo(cframe * CFrame.new(0,-clone.CollisionBox.Size.Y,0))
local tween = SyncTween.new(clone.PrimaryPart, TweenInfo.new(0.3, Enum.EasingStyle.Sine), {
CFrame = cframe
})
tween:Play()
tween.Completed:Connect(function ()
completed = true
end)
while not completed do
clone.CollisionBox.Emitter:Emit(5)
task.wait() -- if particles still lag, add a longer delay and change ur particle to last longer
end
end)
As for benefits, besides not putting the stress on the server itself, you have more control on who renders what (like for example, if an object is out of sight for a specific player, the server can just tell that player’s client to not do any animations and particles on that object).
Not sure why you would need an UnreliableRemoteEvent for this? You’ll just send one event that tells the client to create/clone a ParticleEmitter (alongside the creation of the object).
I believe we might be thinking about different placement systems, I was thinking of a single RemoteEvent for creating the object + tweening + particle emitting.
The creation and the special effects are under the same event, so you’d want to ensure that there is no possibility that a client loses a “place object” packet and doesn’t render a placed object, aka we want a reliable RemoteEvent.
If you want two seperate events, one for creation of object and another for special effects. Use a reliable RemoteEvent for creation and an unreliable RemoteEvent for the special effects.
Just to chime in, I agree with you. OP needs an reliable remote event that handles (tweening, and particles), I would also suggest looking into making 2 separate modules for these on the client like TweenUtil and ParticleUtil, these two modules will just separate the data that was fired through the remote event, example RemoteEvent:FireAllClients("tent", effectData, tweenData . So it would go Model, EffectType which is a table, TweenData which is a table. The tables hold information that is compressed to lower network usage. Then your modules come in and decompress the data the client received. I don’t know what TweenSync is but maybe thats what that module does.
Sorry if this is a weird question but if you were personally creating a placement system do you think this would be the best way to go about handling the tweening/replicating portion?
Just want to make sure this is a reliable and clean way of doing it.
If you don’t mind, I was just wondering if you could explain a little more in depth or just link a post about how using multiple modules and tables lower network usage. Thought it was just more about organising, didn’t realise it could help with that side of things.
There are so many things to consider, but generally speaking:
Arrays > Dictionaries
Arrays use numerical keys (typically 1 byte) while dictionaries typically use non-numerical keys such as strings which cost unnecessary bytes.
Vector3int16.new() > Vector3.new() > CFrame.new()
CFrames have a positional and rotational component. Depending on your usecase, you can further compress these components into Vector3s, Vector2s, and sometimes even numbers.
Vector2int16.new() > Vector2.new()
Numbers > Strings
In application, numbers are normally 8 bytes (static; doesn’t change with amount of digits), but you can compress these numbers (e.g. only 1 byte which can represent an unsigned integer range of [0, 255]) and send them over instead of strings which increase by 1 byte per character.
Numbers can also be compressed further with buffer.new()
TweenInfo seems similar to a dictionary
For specific byte sizes of each datatype, read this post by @PysephDEV. Although there are a very few details that are outdated, so you can also read this (from the same person). I recommend these two if you want to comprehend the comparisons I made above, though you may need to do further research for the other stuff (such as buffers).
Now to visualize your network bandwidth, I recommend PacketProfiler (by the same person)! It’s basically a prediction of how much bytes are being sent over with each RemoteEvent call. Do keep in mind that certain datatypes (such as TweenInfo) are unfortunately not visualized.
If you want to see how you could apply some of the considerations above, you can read this section on a post I made specifically about compressing dictionaries, CFrames, and numbers.