Hi! I’ve been having a lot of fun with Chickynoid lately, but I’ve run into a problem that I can’t seem to find on the devforum.
I want to implement a mechanic in which you can click on the ground, and then you fling away from the direction you’re pointing at. The solution I’ve implemented using Chickynoid’s weapon system works fine, and it feels pretty smooth.
This exact equation is fed to both the server and client (via ServerProcessCommand and ClientProcessCommand) and both call the same function. However, there seems to be something I’m doing wrong here.
The server keeps rolling back the client’s position to where the server believes the client should be. This creates jagged and unsatisfying movement, as you get teleported backwards whilst you are mid-air. I’ve debugged this on both the server and the client and their variables seem to be synced. Note the frequent yellow spikes in the corner.
I’ve tried turning it into something more akin to JumpPad (using :RegisterMoveState), but it had the same rollback problems as before. My strategy to fling the player is by adding directly to the Simulation’s velocity, using the camera’s direction as the launch direction. Adding to the Simulation’s velocity does seem to have an impact on the rollback frequency, as when directly setting the velocity, the rollback is lessened, but not completely eliminated.
Hi, so the reason why this happens is because unlike simulation’s process command function, as far as I know, weapons do not have the same complex techniques to sync it’s state between client and server.
Weapons only have a simple function that prevents the server from overriding it’s state for 0.5 seconds on client side.
The reason why I think these rollbacks happen is because chickynoid doesn’t do server reconciliation on weapons, so when the client tries to re-run the inputs to sync with the server, it doesn’t do it with the weapons, which produces a different output and causes this cycle of rollbacking which results in that jerky movement.
-- Some Example Server Script
local projectileData = {
From = from,
Direction = mouseDirection,
Velocity = velocity,
Character = player.Character,
CompensationDelay = shootDelay,
}
player:SetAttribute('NextShoot', currentTime + shootDelayMS)
targetWeapon:SetAttribute('Bullets',Bullets-1)
for _, targetPlayer in Players:GetPlayers() do
--if targetPlayer ~= player then
Remotes.Shoot:Fire(targetPlayer, player, serverTime, projectileData)
--end
end
local simulation = ProjectileSimulation.new(projectileData)
The simulation Module
--!strict
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local RunService = game:GetService('RunService')
local Signal = require(ReplicatedStorage.Packages.Signal)
local Trove = require(ReplicatedStorage.Packages.Trove)
local ProjectileSimulation = {}
ProjectileSimulation.__index = ProjectileSimulation
local MAX_RANGE: number = 1_000
export type ProjectileData = {
From: CFrame,
Direction: Vector3,
Velocity: number,
Character: Model,
MaxRange: number?,
CompensationDelay: number?
}
function ProjectileSimulation.new(data: ProjectileData)
local self = setmetatable({
_updateConnection = nil :: RBXScriptConnection?,
Trove = Trove.new(),
OnHit = Signal.new(),
OnUpdate = Signal.new(),
From = data.From,
Direction = data.Direction,
Velocity = data.Velocity,
Character = data.Character,
Position = data.From.Position + (data.Direction * data.Velocity * (data.CompensationDelay or 0)),
MaxRange = data.MaxRange or MAX_RANGE,
}, ProjectileSimulation)
return self
end
function ProjectileSimulation.Disconnect(self: ProjectileSimulation)
if not self._updateConnection then
return
end
self.Trove:Remove(self._updateConnection)
self._updateConnection = nil
end
function ProjectileSimulation.Connect(self: ProjectileSimulation)
if self._updateConnection then
return
end
self.OnUpdate:Fire(self.Position)
self._updateConnection = self.Trove:Add(RunService.Heartbeat:Connect(function(deltaTime: number)
self:Update(deltaTime)
end))
end
function ProjectileSimulation.Update(self: ProjectileSimulation, deltaTime: number)
self.Position += self.Direction * deltaTime * 15 * self.Velocity
self.OnUpdate:Fire(self.Position)
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {self.Character,workspace.Raycast_Ignores}
local raycast = workspace:Raycast(self.Position,self.Direction*(self.Velocity/4),params)
if raycast then
self.OnHit:Fire(raycast.Position,raycast.Instance)
self:Destroy()
end
if (self.Position - self.From.Position).Magnitude > self.MaxRange then
self:Destroy()
end
end
function ProjectileSimulation.Destroy(self: ProjectileSimulation)
self:Disconnect()
self.Trove:Destroy()
end
export type ProjectileSimulation = typeof(ProjectileSimulation.new(nil :: any))
return ProjectileSimulation
client replicating it
local simulation = ProjectileSimulation.new(data)
simulation.OnUpdate:Connect(function(newPosition: Vector3)
newProjectile:PivotTo(CFrame.lookAt(newPosition, target))
end)
I had tried not using the weapons system prior to this post, using a similar system to JumpPad in which it hooks to the physics simulation, but it appeared to not work, so I scrapped it. What deeply confused me at the time was that both jumping and JumpPad worked completely fine despite also messing with velocity, so why doesn’t mine work?! I now realize that I was using variables outside of the physics simulation, which is what caused the replication to become finicky.
-- This will cause no issues, as it is using the physics simulation's variables.
if cmd.f > 0 and simulation.state.jump <= 0 then
simulation.state.vel -= (cmd.fa - simulation.state.pos).Unit * 100
simulation.state.jump = 0.5
end
-- This will cause issues, as it is using a variable outside of the physics simulation.
if cooldown > 0 then
cooldown -= cmd.deltaTime
end
if cmd.f > 0 and cooldown <= 0 then
simulation.state.vel -= (cmd.fa - simulation.state.pos).Unit * 100
cooldown = 0.5
end
This is a really weird quirk in my opinion, it does sort of make sense to me, but I think I’m going to have to delve into the code more later to understand why it works this way. For now, I’ll just believe in the black magic.
I don’t know if you should still stick with chickynoid considering it is discontinued and roblox will release server authoritative physics beta by summer as far as I know (AuroraService).