Hey everyone,
I’ve been working on implementing a movement system in my game, and I’m encountering some challenges. My goal is to replicate the movement of my custom character from the client side to the server side seamlessly, similar to how Roblox’s default character system operates.
However, despite trying various approaches, I’m facing issues with getting my “FlyingBrick” to work properly. Even when I give network ownership to the player, it causes the brick to become stuck to the ground, preventing it from flying as intended.
Here’s a glimpse of my current setup:
- Check out my Space Ship Test With Brick video for a visual demonstration
- Additionally, I’ve created a RemoteEvent to make a fake brick follow the player on the server side: ServerSideView video
The main issue I’m encountering is latency and imperfection in the server-side replication. Currently, I’m using tweens, but they introduce delays and inaccuracies, especially when the player moves at high speeds or changes direction suddenly.
Here’s the behavior I’m aiming for, demonstrated in the “Chasing Stars Online Demo” video: Chasing Stars Online Demo video
Any insights or suggestions on how to achieve smoother and more accurate movement replication would be greatly appreciated.
Thanks in advance!
Also here is the Code if anyone Instrested.
FlyingBrick Model: https://create.roblox.com/store/asset/16164302114?viewFromStudio=true&keyword=&searchId=34D8497C-5EA0-4CC7-AFCC-6DDE5FACD603
--[[
!Made By OufGuy!
Controls:
{
F --> Change To Fly
B --> Stop
W -- SpeedUp
S -- SlowDown
Mouse --> Up,Down,Left,Right
}
Most of the Stuff here is Self Explanatory because of Function Names.
]]
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
--- Services
local player = Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local Humanoid = char:WaitForChild("Humanoid")
local mouse = player:GetMouse()
--- PlayerStuff
local Camera = workspace.CurrentCamera
local PlayersFolder = Workspace:WaitForChild("Players")
local PositionUpdate = ReplicatedStorage:WaitForChild("PlayerPositionUpdate")
local flyingBrick = char:WaitForChild("FlyingBrick")
local FakeFlyingBrick = flyingBrick:WaitForChild("FakeFlyingBrick")
FakeFlyingBrick.Transparency = 1
--- Objects N Stuff
local cameraMode = "Character"
local CameraDirection = Vector3.new(0, 3, -10)
local cameraOffset
local minCameraDistance = 10 -- Minimum distance between camera and brick
local cameraPosition = Camera.CFrame.Position
local CameraShakeIntensity = 0.2
local CameraShakeDuration = 0.5
local CameraShakeTimer = 0
--- CAMERASETTINGS
--- DAMAGE STUFF
local BaseEnergy = 150
local MaxEnergy = 150
local Energy = math.round(math.clamp(BaseEnergy, 0, MaxEnergy))
local BaseShield = 200
local MaxShield = 200
local Shield = math.round(math.clamp(BaseShield, 0, BaseShield))
local ShieldDamageReduce = 0.5
local EnergyDamageReduce = 1
local CulmulativeDamageOverShield = 0
local DamageToTake = 0
local DamageCanBeTaken = true
local DamageToTakeCooldown = 0
--- DAMAGE STUFF
--- TURN RELATED STUFF
local sensitivity = 0.1
local BaseTurnRate = 40
local maxTurningRate = math.rad(BaseTurnRate)
--- TURN RELATED STUFF
--- MOVE RELATED STUFF
local flyingSpeed = 0
local BaseIdleSpeed = 1
local IdleSpeed = BaseIdleSpeed
local BaseMinSpeed = 0.5
local MinSpeed = BaseMinSpeed
local BaseMaxSpeed = 3
local MaxSpeed = BaseMaxSpeed
local BaseAcceleration = 3
local Acceleration = BaseAcceleration -- More = Faster to Reach that speed
local BaseDeceleration = 0.5
local Deceleration = BaseDeceleration -- Less = Slower to SlowDown
local returnToSpeedTime = 10 * MaxSpeed
local SpeedDependingOnX = 0
local XEffectForSpeed = 0
local BaseBoost = 10
local MaxBoost = 10
local Boost = math.round(math.clamp(BaseBoost, 0, MaxBoost))
local CanBoost = true
local isBoosting = false
local isIdle = false
local isAccelerating = false
local isDecelerating = false
local returnToSpeedTimer = 0
local returnToSpeedTarget = MaxSpeed / 3
local CanTurn = false
local Stopped = true
local Leaving = false
--- MOVE RELATED STUFF
--- STATS
local SpeedCount = script.Parent.SpeedFrame.SpeedLabel
local hitTimer = 0
local TurnRateX = script.Parent:WaitForChild("TurnRateX")
local TurnRateZ = script.Parent:WaitForChild("TurnRateZ")
--- GUI
local function switchCameraMode()
-- Mode Changer
if cameraMode == "Character" then
cameraMode = "Brick"
Camera.CameraSubject = flyingBrick
mouse.TargetFilter = game.Workspace -- Ignore interactions with other objects
Camera.CameraType = Enum.CameraType.Scriptable
char.HumanoidRootPart.Anchored = true
else
cameraMode = "Character"
Camera.CameraSubject = char.Humanoid
mouse.TargetFilter = nil -- Allow interactions with other objects again
Camera.CameraType = Enum.CameraType.Custom
char.HumanoidRootPart.Anchored = true
end
end
local function shakeCamera()
-- Self Explanatory
if CameraShakeTimer > 0 then
local offsetX = math.random() * CameraShakeIntensity - CameraShakeIntensity / 2
local offsetY = math.random() * CameraShakeIntensity - CameraShakeIntensity / 2
local offsetZ = math.random() * CameraShakeIntensity - CameraShakeIntensity / 2
Camera.CFrame = Camera.CFrame * CFrame.new(offsetX, offsetY, offsetZ)
CameraShakeTimer = CameraShakeTimer - 1 / 60 -- Assuming 60 FPS
end
end
local function handleInput(deltaTime)
-- Mouse Controls for FlyingBrick
if cameraMode == "Brick" and CanTurn == true then
local lookDirection = (mouse.Hit.Position - flyingBrick.Position).Unit
local currentDirection = flyingBrick.CFrame.LookVector
local targetDirection = lookDirection:lerp(currentDirection, math.min(1, maxTurningRate * deltaTime))
local angle = math.acos(currentDirection:Dot(targetDirection))
local maxRotation = math.min(maxTurningRate * deltaTime, math.rad(80))
if angle > maxRotation then
targetDirection = currentDirection:lerp(targetDirection, maxRotation / angle)
end
flyingBrick.CFrame = CFrame.lookAt(flyingBrick.Position, flyingBrick.Position + targetDirection)
end
SpeedCount.Text = math.round(flyingSpeed * 100) -- Speed Value inside Control (is a GUI)
end
local function calculateTurnRateValues(mouseX, mouseY)
-- Turn Rates Depending on ScreenPosition
-- Base is 40
local screenWidth, screenHeight = workspace.CurrentCamera.ViewportSize.X, workspace.CurrentCamera.ViewportSize.Y
local normalizedX, normalizedY = mouseX / screenWidth, mouseY / screenHeight
local rangeX, rangeY = (BaseTurnRate + 5) - (-BaseTurnRate - 5), BaseTurnRate - (-BaseTurnRate - 10) -- 40 + 5 - (- 40 - 5), 40 - (-40 - 10)
local newXValue = (BaseTurnRate + 5) - normalizedX * rangeX -- 40 + 5
local newZValue = BaseTurnRate - normalizedY * rangeY -- 40
newXValue = math.clamp(newXValue, -BaseTurnRate, BaseTurnRate) -- -40, 40
newZValue = math.clamp(newZValue, -BaseTurnRate, BaseTurnRate) -- -40, 40
return newXValue, newZValue
end
local function adjustSpeed()
-- To Detect if Accelerating or Decelerating.
if cameraMode == "Brick" then
if Stopped == true and Leaving == false then
if UserInputService:IsKeyDown(Enum.KeyCode.W) then
Leaving = true
Stopped = false
CanTurn = false
task.wait(1)
if Leaving == true then
Leaving = false
CanTurn = true
end
elseif UserInputService:IsKeyDown(Enum.KeyCode.S) then
Leaving = true
Stopped = false
CanTurn = false
task.wait(1)
if Leaving == true then
Leaving = false
CanTurn = true
end
end
elseif Stopped == false and Leaving == false then
if UserInputService:IsKeyDown(Enum.KeyCode.W) and CanBoost then
isAccelerating = true
isDecelerating = false
returnToSpeedTimer = 0
isIdle = false
elseif UserInputService:IsKeyDown(Enum.KeyCode.S) then
isDecelerating = true
isAccelerating = false
returnToSpeedTimer = 0
isIdle = false
elseif UserInputService:IsKeyDown(Enum.KeyCode.B) then
isAccelerating = false
isDecelerating = false
isIdle = false
Stopped = true
elseif Stopped == false and Leaving == false then
isAccelerating = false
isDecelerating = false
isIdle = true
end
end
end
end
local function updateZ()
-- Z Orientation of Brick.
local TurnRateToUseLeftAndRight = script.Parent.TurnRateX.Value
local TurnRateToUseUpAndDown = script.Parent.TurnRateZ.Value
local CurrentX = flyingBrick.Orientation.X
local CurrentY = flyingBrick.Orientation.Y
local TTurnRate = math.abs(TurnRateToUseLeftAndRight) + math.abs(TurnRateToUseUpAndDown)
local function FlyingBlockZturnAsFunction()
-- Turning Z Orientation of Block
TTurnRate = math.abs(TurnRateToUseLeftAndRight) + math.abs(TurnRateToUseUpAndDown)
local newAngle = Vector3.new(CurrentX, CurrentY, TurnRateToUseLeftAndRight)
if Stopped == true then
newAngle = Vector3.new(CurrentX, CurrentY, 0)
else
newAngle = Vector3.new(CurrentX, CurrentY, math.clamp(TurnRateToUseLeftAndRight * 1.25,-80, 80))
end
flyingBrick.Orientation = newAngle
maxTurningRate = math.rad(TTurnRate)
end
FlyingBlockZturnAsFunction()
local function CameraTurnRateDependingOnZ()
-- CameraRoll Depending on Z Orientation or Set Value
local CameraTTurnRate = 0
if TurnRateToUseLeftAndRight >= 0 then
CameraTTurnRate = TurnRateToUseLeftAndRight
else
CameraTTurnRate = TurnRateToUseLeftAndRight
end
-- Set a threshold for TTurnRate
if TurnRateToUseLeftAndRight < 5 and TurnRateToUseLeftAndRight > -5 then
CameraTTurnRate = 0
end
-- Smoothly transition the camera roll
local targetRoll = math.rad(-CameraTTurnRate) -- Adjust the sign here
if Stopped == true then
targetRoll = 0
else
targetRoll = math.rad(-CameraTTurnRate)
end
local currentRoll = Camera:GetRoll()
local smoothness = 0.1 -- You can adjust this value for smoother or faster transitions
local newRoll = currentRoll + (targetRoll - currentRoll) * smoothness
Camera:SetRoll(newRoll)
end
CameraTurnRateDependingOnZ()
local function SpeedDependinonXasFunction()
-- Depending on X Orientation, Speed up or Slow Down
if CurrentX < 0 then
SpeedDependingOnX = math.abs(math.round(CurrentX))
elseif CurrentX > 0 then
SpeedDependingOnX = (math.abs(math.round(CurrentX))) * -1
end
XEffectForSpeed = SpeedDependingOnX / 400
IdleSpeed = BaseIdleSpeed + XEffectForSpeed
MinSpeed = BaseMinSpeed + XEffectForSpeed
MaxSpeed = BaseMaxSpeed + XEffectForSpeed
Acceleration = (BaseAcceleration + XEffectForSpeed) / 2
Deceleration = BaseDeceleration - XEffectForSpeed
end
SpeedDependinonXasFunction()
end
local function updateMovement(deltaTime)
-- True Movement Function
if cameraMode == "Brick" then
cameraOffset = flyingBrick.CameraOffSet.Value.Position
local forwardVector = flyingBrick.CFrame.LookVector
if isAccelerating and Stopped == false and Leaving == false and CanBoost == true then
flyingSpeed = math.min(MaxSpeed, flyingSpeed + Acceleration * deltaTime)
updateZ()
elseif isAccelerating and Stopped == false and Leaving == false and CanBoost == false then
returnToSpeedTimer = math.min(returnToSpeedTime, returnToSpeedTimer + deltaTime)
local targetSpeed = IdleSpeed
if flyingSpeed >= 0 then
flyingSpeed = (targetSpeed - flyingSpeed) * (returnToSpeedTimer / returnToSpeedTime) + flyingSpeed
elseif flyingSpeed < 0 then
flyingSpeed = (targetSpeed - flyingSpeed) * (returnToSpeedTimer / returnToSpeedTime * 0.5) + flyingSpeed
end
updateZ()
elseif isDecelerating and Stopped == false and Leaving == false then
if flyingSpeed <= 1 / MaxSpeed then
flyingSpeed = math.max(MinSpeed, flyingSpeed - Deceleration * deltaTime)
elseif flyingSpeed > 1 / MaxSpeed then
flyingSpeed = math.max(MinSpeed, flyingSpeed - (Deceleration * flyingSpeed * 2) * deltaTime)
end
updateZ()
elseif isIdle and Stopped == false and Leaving == false then
returnToSpeedTimer = math.min(returnToSpeedTime, returnToSpeedTimer + deltaTime)
local targetSpeed = IdleSpeed
if flyingSpeed >= 0 then
flyingSpeed = (targetSpeed - flyingSpeed) * (returnToSpeedTimer / returnToSpeedTime) + flyingSpeed
elseif flyingSpeed < 0 then
flyingSpeed = (targetSpeed - flyingSpeed) * (returnToSpeedTimer / returnToSpeedTime * 0.5) + flyingSpeed
end
updateZ()
elseif Stopped then
returnToSpeedTimer = math.min(returnToSpeedTime, returnToSpeedTimer + deltaTime)
local targetSpeed = 0
if flyingSpeed >= 0 then
flyingSpeed = (targetSpeed - flyingSpeed) * (returnToSpeedTimer / returnToSpeedTime) + flyingSpeed
elseif flyingSpeed < 0 then
flyingSpeed = (targetSpeed - flyingSpeed) * (returnToSpeedTimer / returnToSpeedTime * 0.5) + flyingSpeed
end
-- if flyingbrick.orientation.X > 0 then make Orientation.X to 0 in 0.5 seconds
if flyingBrick.Orientation.X > 0 then
flyingBrick.Orientation = Vector3.new(flyingBrick.Orientation.X - (flyingBrick.Orientation.X * deltaTime), flyingBrick.Orientation.Y, flyingBrick.Orientation.Z)
elseif flyingBrick.Orientation.X < 0 then
flyingBrick.Orientation = Vector3.new(flyingBrick.Orientation.X + (math.abs(flyingBrick.Orientation.X) * deltaTime), flyingBrick.Orientation.Y, flyingBrick.Orientation.Z)
end
-- if flyingbrick.orientation.Z > 0 then make Orientation.Z to 0 in 0.5 seconds
if flyingBrick.Orientation.Z > 0 then
flyingBrick.Orientation = Vector3.new(flyingBrick.Orientation.X, flyingBrick.Orientation.Y, flyingBrick.Orientation.Z - (flyingBrick.Orientation.Z * deltaTime))
elseif flyingBrick.Orientation.Z < 0 then
flyingBrick.Orientation = Vector3.new(flyingBrick.Orientation.X, flyingBrick.Orientation.Y, flyingBrick.Orientation.Z + (math.abs(flyingBrick.Orientation.Z) * deltaTime))
end
updateZ()
elseif Leaving then
returnToSpeedTimer = math.min(returnToSpeedTime, returnToSpeedTimer + deltaTime)
local targetSpeed = IdleSpeed
if flyingSpeed >= 0 then
flyingSpeed = (targetSpeed - flyingSpeed) * (returnToSpeedTimer / returnToSpeedTime) + flyingSpeed
elseif flyingSpeed < 0 then
flyingSpeed = (targetSpeed - flyingSpeed) * (returnToSpeedTimer / returnToSpeedTime * 0.5) + flyingSpeed
end
end
local movement = forwardVector * flyingSpeed
local function ShipHasHitObstacle()
-- To Bounce off the Brick.
local ray = Ray.new(flyingBrick.Position, movement.Unit * math.min(movement.magnitude, 50))
--- CollusionFilter!!!
local hit, hitPosition, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, {PlayersFolder, player, char, flyingBrick})
--- CollusionFilter!!!
if hit then
flyingBrick.Position = hitPosition - movement.Unit * 0.5 -- Adjust the offset to prevent sticking
-- Determine which side of the brick was hit based on the hit normal
local dotRight = flyingBrick.CFrame.RightVector:Dot(-hitNormal)
local dotLeft = flyingBrick.CFrame.RightVector:Dot(hitNormal)
local dotTop = flyingBrick.CFrame.UpVector:Dot(-hitNormal)
local dotBottom = flyingBrick.CFrame.UpVector:Dot(hitNormal)
local dotFront = flyingBrick.CFrame.LookVector:Dot(-hitNormal)
local XCalculate = flyingBrick.Orientation.X
local YCalculate = flyingBrick.Orientation.Y
local ZCalculate = flyingBrick.Orientation.Z
if dotFront > 0.8 then
print("Hit Front Side")
if XCalculate > 0 then
flyingBrick.Orientation = Vector3.new(XCalculate - 30, YCalculate + 180, ZCalculate)
elseif XCalculate < 0 then
flyingBrick.Orientation = Vector3.new(XCalculate + 30, YCalculate + 180, ZCalculate)
end
elseif dotTop > 0.5 then
print("Hit Top Side")
flyingBrick.Orientation = Vector3.new(XCalculate - 45, YCalculate, ZCalculate)
elseif dotBottom > 0.5 then
print("Hit Bottom Side")
flyingBrick.Orientation = Vector3.new(XCalculate + 45, YCalculate, ZCalculate)
elseif dotRight > 0.5 then
print("Hit Right Side")
flyingBrick.Orientation = Vector3.new(XCalculate, YCalculate + 45, ZCalculate)
elseif dotLeft > 0.5 then
print("Hit Left Side")
flyingBrick.Orientation = Vector3.new(XCalculate, YCalculate - 45, ZCalculate)
end
DamageToTake = math.round(flyingSpeed * 50)
if DamageCanBeTaken then
if Shield > 0 then
DamageToTake *= ShieldDamageReduce
Shield -= DamageToTake
print("Damage Taken: "..DamageToTake, "CurrentShield: "..Shield)
if Shield <= 0 then
CulmulativeDamageOverShield = DamageToTake + Shield
warn("Culmative Damage is: "..CulmulativeDamageOverShield)
Shield = 0
warn("Detected Shield is 0, CurrentShield: "..Shield)
end
elseif Shield <= 0 then
if CulmulativeDamageOverShield > 0 then
warn("CulmativeDamage Detected: "..CulmulativeDamageOverShield)
print("Current Damage to Take is: "..DamageToTake * EnergyDamageReduce)
DamageToTake = (DamageToTake + CulmulativeDamageOverShield) * EnergyDamageReduce
warn("CulmulativeDamage has been added to DamageToTake: "..DamageToTake)
Energy -= DamageToTake
print("Damage Taken: "..DamageToTake, "CurrentEnergy: "..Energy)
CulmulativeDamageOverShield = 0
else
DamageToTake *= EnergyDamageReduce
warn("No CulmulativeDamage has been added to DamageToTake: "..DamageToTake)
Energy -= DamageToTake
print("Damage Taken: "..DamageToTake, "CurrentEnergy: "..Energy)
end
end
end
DamageCanBeTaken = false
DamageToTakeCooldown = 0.5
hitTimer = 0.5
CameraShakeTimer = CameraShakeDuration
else
flyingBrick.Position = flyingBrick.Position + movement
end
end
ShipHasHitObstacle()
--- CAMERA STUFF
-- Update camera position to follow the front of the brick
local targetPosition = flyingBrick.Position + flyingBrick.CFrame.LookVector * CameraDirection.z
+ flyingBrick.CFrame.UpVector * CameraDirection.y
+ flyingBrick.CFrame.RightVector * CameraDirection.x
-- Ensure a minimum distance between camera and brick when moving in reverse
local cameraToBrick = cameraPosition - flyingBrick.Position
local cameraDistance = cameraToBrick.magnitude
if flyingSpeed < 0 and cameraDistance < minCameraDistance then
targetPosition = flyingBrick.Position + flyingBrick.CFrame.LookVector * -minCameraDistance
end
local lerpFactor = 0.25 -- Adjust this factor for smoothness
cameraPosition = cameraPosition:Lerp(targetPosition, lerpFactor)
local offsetMultiplier = Vector3.new(0 + (cameraOffset.X / 10), 0 + (cameraOffset.Y / 10), 0 + (cameraOffset.Z / 10))
local CameraRotation = Camera.CFrame.Rotation
local PositionOfCameraWithTarget = CFrame.new(Vector3.new(cameraPosition.X + offsetMultiplier.X, cameraPosition.Y + offsetMultiplier.Y, cameraPosition.Z + offsetMultiplier.Z), flyingBrick.Position)
Camera.CFrame = PositionOfCameraWithTarget * CFrame.fromEulerAnglesXYZ(math.rad(cameraOffset.Y * 0.525 ), 0, 0)
--- CAMERA STUFF
char.HumanoidRootPart.CFrame = flyingBrick.CFrame
--- Add AnyParts or Model.PrimaryPart.CFrame to follow flyingBrick.CFrame, its kinda Self Explanatory.
--- Make Sure the parts are Anchored.
end
end
local function onKeyDown(key)
if key == "f" then -- ChangeModes
switchCameraMode()
isIdle = false
isAccelerating = false
isDecelerating = false
CanTurn = false
Stopped = true
Leaving = false
end
if key == "b" then -- Stop
isIdle = false
isAccelerating = false
isDecelerating = false
CanTurn = false
Stopped = true
Leaving = false
end
end
-- Connect the functions to events
UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
if not gameProcessedEvent then
local object = input.UserInputType == Enum.UserInputType.MouseButton1 and mouse.Target
end
end)
RunService.RenderStepped:Connect(function(deltaTime)
--- HIT TIMER TO STOP MOVING FOR X SECONDS
if hitTimer >= 0.05 then
hitTimer = hitTimer - 1 * deltaTime
hitTimer = math.max(0, hitTimer) -- Ensure hitTimer doesn't go below 0
else
handleInput(deltaTime)
end
--- HIT TIMER TO STOP MOVING FOR X SECONDS
adjustSpeed()
updateMovement(deltaTime)
shakeCamera()
end)
mouse.KeyDown:Connect(onKeyDown)
mouse.Move:Connect(function()
local newXValue, newZValue = calculateTurnRateValues(mouse.X, mouse.Y)
TurnRateX.Value = newXValue
TurnRateZ.Value = newZValue
end)
while true do
-- Making Checks
task.wait(0.1)
if Boost > 0 then
if isAccelerating then
Boost -= 0.1
print("Boosting")
end
elseif Boost <= 0 and CanBoost == true then
CanBoost = false
print("OufOfBoost, Cannot Boost!")
end
if isAccelerating and CanBoost then
isBoosting = true
else
isBoosting = false
end
if Boost < MaxBoost and isBoosting == false then
Boost += 0.1
print("Filling Boost")
if Boost >= MaxBoost then
Boost = MaxBoost
CanBoost = true
print("Can Boost again!")
end
end
if DamageCanBeTaken == false then
DamageToTakeCooldown -= 0.1
if DamageToTakeCooldown <= 0 then
DamageToTakeCooldown = 0
DamageCanBeTaken = true
end
end
PositionUpdate:FireServer(flyingBrick, flyingBrick.CFrame) -- Replace Position of FakeFlyingBrick constantly from Server
end