I want to try to make a projectile system that involves cloning and launching a part in the replicated storage that is meant to bounce off walls and deal damage. The client is supposed to trigger the event by holding down the mouse/screen while showing a visible path using raycasting then when you let go the part is supposed to travel the raycast line that was shown including the bouncing.
The issue with the script is that the client path is different from what the server shows when the part is released and is offset by a little but the difference can be critical at times. Also sometimes the part does not even bounce off some surfaces and completely ignores them.
Can anyone help with this problem the client and server scripts are provided below. Thanks in advance!
Server:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ThrowRazorwingEvent = ReplicatedStorage:WaitForChild("ThrowRazorwing")
local Workspace = game:GetService("Workspace")
local Debris = game:GetService("Debris")
local damagePerBounce = 25
local MAX_BOUNCES = 3
ThrowRazorwingEvent.OnServerEvent:Connect(function(player, startPosition, direction)
local RazorwingClone = ReplicatedStorage:WaitForChild("Razorwing"):Clone()
RazorwingClone.Parent = Workspace
local playerCharacter = player.Character
local playerRoot = playerCharacter and playerCharacter:FindFirstChild("HumanoidRootPart")
if playerRoot then
startPosition = playerRoot.Position + direction.Unit * 2
end
RazorwingClone.Position = startPosition
RazorwingClone.CanCollide = true
RazorwingClone.Anchored = false
local BodyVelocity = Instance.new("BodyVelocity")
BodyVelocity.Velocity = direction.Unit * 50
BodyVelocity.P = 1250
BodyVelocity.MaxForce = Vector3.new(100000, 100000, 100000)
BodyVelocity.Parent = RazorwingClone
local BodyGyro = Instance.new("BodyGyro")
BodyGyro.CFrame = CFrame.new(RazorwingClone.Position, RazorwingClone.Position + direction.Unit)
BodyGyro.P = 3000
BodyGyro.MaxTorque = Vector3.new(4000, 4000, 4000)
BodyGyro.Parent = RazorwingClone
local hitTable = {}
local bounces = 0
local runService = game:GetService("RunService")
local connection
connection = runService.Heartbeat:Connect(function(dt)
local moveStep = BodyVelocity.Velocity * dt
RazorwingClone.CFrame = RazorwingClone.CFrame + moveStep
BodyGyro.CFrame = CFrame.new(RazorwingClone.Position, RazorwingClone.Position + BodyVelocity.Velocity.Unit)
local rayLength = moveStep.Magnitude
local ray = Ray.new(RazorwingClone.Position, moveStep.Unit * rayLength)
local raycastParams = RaycastParams.new()
raycastParams.IgnoreWater = true
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {player.Character, RazorwingClone}
local result = Workspace:Raycast(ray.Origin, ray.Direction, raycastParams)
if result then
local hit = result.Instance
local position = result.Position
local normal = result.Normal
local incomingVelocity = BodyVelocity.Velocity
local dotProduct = incomingVelocity:Dot(normal)
local bounceVelocity = incomingVelocity - 2 * dotProduct * normal
BodyVelocity.Velocity = bounceVelocity
RazorwingClone.Position = position
bounces = bounces + 1
if bounces >= MAX_BOUNCES then
connection:Disconnect()
end
end
end)
RazorwingClone.Touched:Connect(function(hit)
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if humanoid and not hitTable[humanoid] and hit.Parent ~= player.Character then
humanoid:TakeDamage(damagePerBounce)
hitTable[humanoid] = true
local highlight = Instance.new("Highlight")
highlight.Parent = hit.Parent
highlight.Adornee = hit.Parent
highlight.FillColor = Color3.fromRGB(255, 0, 0)
highlight.FillTransparency = 0.5
game.ReplicatedStorage.HitMarkerEvent:FireClient(player, hit.Parent.Head)
Debris:AddItem(highlight, 0.5)
end
end)
Debris:AddItem(RazorwingClone, 10)
end)
Client:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local mouse = player:GetMouse()
local ThrowRazorwingEvent = ReplicatedStorage:WaitForChild("ThrowRazorwing")
local charging = false
local indicatorParts = {}
local reflectionEffects = {}
local MAX_BOUNCES = 3
local PATH_LENGTH = 100
local function createIndicatorPart()
local part = Instance.new("Part")
part.Anchored = true
part.CanCollide = false
part.Size = Vector3.new(0.5, 0.5, 5)
part.Color = Color3.fromRGB(0, 255, 0)
part.Material = Enum.Material.ForceField
part.Parent = workspace
table.insert(indicatorParts, part)
return part
end
local function createReflectionEffect(position)
local effect = Instance.new("Part")
effect.Size = Vector3.new(1, 1, 1)
effect.Shape = Enum.PartType.Ball
effect.Material = Enum.Material.Neon
effect.Color = Color3.fromRGB(255, 0, 0)
effect.Anchored = true
effect.CanCollide = false
effect.CFrame = CFrame.new(position)
effect.Parent = workspace
table.insert(reflectionEffects, effect)
game:GetService("Debris"):AddItem(effect, 0.5)
end
local function clearIndicators()
for _, part in ipairs(indicatorParts) do
part:Destroy()
end
indicatorParts = {}
for _, effect in ipairs(reflectionEffects) do
effect:Destroy()
end
reflectionEffects = {}
end
local function showPathWithReflections()
clearIndicators()
local character = player.Character
local rootPart = character and character:FindFirstChild("HumanoidRootPart")
if not rootPart then return end
local currentPosition = rootPart.Position + rootPart.CFrame.LookVector * 2
local currentDirection = (mouse.Hit.Position - rootPart.Position).Unit
local remainingDistance = PATH_LENGTH
local bounces = 0
while bounces <= MAX_BOUNCES and remainingDistance > 0 do
local raycastParams = RaycastParams.new()
raycastParams.IgnoreWater = true
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {character}
for _, part in ipairs(indicatorParts) do
table.insert(raycastParams.FilterDescendantsInstances, part)
end
for _, effect in ipairs(reflectionEffects) do
table.insert(raycastParams.FilterDescendantsInstances, effect)
end
local rayResult = workspace:Raycast(currentPosition, currentDirection * remainingDistance, raycastParams)
if rayResult then
local hitPosition = rayResult.Position
local distanceCovered = (hitPosition - currentPosition).Magnitude
local indicatorPart = createIndicatorPart()
indicatorPart.CFrame = CFrame.new((currentPosition + hitPosition) / 2, hitPosition)
indicatorPart.Size = Vector3.new(0.5, 0.5, distanceCovered)
createReflectionEffect(hitPosition)
local normal = rayResult.Normal
local incomingDirection = currentDirection
currentDirection = incomingDirection - 2 * incomingDirection:Dot(normal) * normal
currentPosition = hitPosition
remainingDistance = remainingDistance - distanceCovered
bounces = bounces + 1
else
local endPosition = currentPosition + currentDirection * remainingDistance
local indicatorPart = createIndicatorPart()
indicatorPart.CFrame = CFrame.new((currentPosition + endPosition) / 2, endPosition)
indicatorPart.Size = Vector3.new(0.5, 0.5, remainingDistance)
break
end
end
end
local function throwRazorwing()
local character = player.Character
local rootPart = character and character:FindFirstChild("HumanoidRootPart")
if rootPart then
local direction = (mouse.Hit.Position - rootPart.Position).Unit
local startPosition = rootPart.Position + rootPart.CFrame.LookVector * 2
ThrowRazorwingEvent:FireServer(startPosition, direction)
end
end
UserInputService.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
charging = true
showPathWithReflections()
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
charging = false
clearIndicators()
throwRazorwing()
end
end)
RunService.RenderStepped:Connect(function()
if charging then
showPathWithReflections()
end
end)