Custom FastCast system not working on Client side

What do I want to achieve?

So I’ve been making my own FastCast system that handles projectiles on client for optimisation.
I am currently working with the Heartbeat service on the client side to make a smooth arch for projectiles like grenades.

  1. What is my issue?

The projectiles are spawning, but they are not following the calculated trajectory and are instead just disappearing. They have not been destroyed and seems to stay in an odd world position.

I’ve got the script to print the projectile’s position and it returns as

0, -3.4028234663852886e+38, 0

I maybe missing an error in my code as so far I haven’t gotten any errors in the outliner.

The only think I believe could be causing the issue is that I am not using the mouse position to get the direction, instead I am using the parts LookVector through this line of code:

local FireCast = workspace.FireCastServer
local Players = game:GetService("Players")
local Weapon = script.Parent.Parent

Plr = nil




local Modual = Weapon.ProjectileTest
local Mode = "Projectile"
local blackList =  {}
local gunPos = Weapon.Position
local gunOr = Weapon.Orientation

local distance = Weapon.CFrame.LookVector

local rayPos = {
	Origin = gunPos,
	Direction = distance
}

while wait(1) do
	local Projectile = require(FireCast).new(Mode, Modual, blackList)
	task.synchronize()
	Projectile:Cast(gunPos, rayPos, Plr)
	task.desynchronize()
end

I will send the relevent sections of my script if anyone can see an issue.

Server side sends origin and direction to the client:

local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
local BulletEvent = game.ReplicatedStorage.RemoteEvents.BulletEvent
local ProjectileEvent = game.ReplicatedStorage.RemoteEvents.ProjectileEvent

local FireCast = {}

function FireCast.new(Mode, bulletModule, blackList)
	-- // Projectile Properties
	local newFireCast = {}
	newFireCast.Mode = Mode
	newFireCast.Module = bulletModule
	newFireCast.Params = RaycastParams.new()
	
	newFireCast.Params.FilterType = Enum.RaycastFilterType.Exclude
	newFireCast.Params.FilterDescendantsInstances = blackList
	
	-- // Projectile Creation
	local Bullet = game.ServerStorage.standIn:Clone()
	
	function newFireCast:Cast(start, ray, player)
		local Module = require(self.Module)
		--// Raycast Veriables Setup
		local function getMousePosition(unitRay, dist)
			local ori, dir = unitRay.Origin, unitRay.Direction * dist
			local Result = workspace:Raycast(ori, dir, self.Params)
			return Result and Result.Position or ori + dir
		end

		local distance = Module.DropOff
		local maxSpread = Module.Max_Spread
		local minSpread = Module.Min_Spread
		local Destination = getMousePosition(ray, distance)

		local Direction = (Destination - start).Unit
		local DirectionCF = CFrame.new(Vector3.new(), Direction)
		local spreadDirection = CFrame.fromOrientation(0, 0, math.random(0, math.pi * 2))
		local spreadAngles = CFrame.fromOrientation(math.rad(math.random(minSpread, maxSpread)), 0, 0)
		local finalDirection = (DirectionCF * spreadDirection * spreadAngles).LookVector
		
		if self.Mode == "Projectile" then
			BulletEvent:FireAllClients(self.Mode, self.Module, player, start, finalDirection)
		end
	end
	
	
	return newFireCast
end

return FireCast

And then projectile is instanced on the client side:

local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
local localPlayer = Players.LocalPlayer
local BulletEvent = game.ReplicatedStorage.RemoteEvents.BulletEvent
local fxFolder = localPlayer.Character:WaitForChild("FX")
local pbFolder = fxFolder:WaitForChild("PlayerBullets")
local bFolder = fxFolder:WaitForChild("Bullets")


local Projectile = {}

function Projectile.New(Mode, Module, Player)
	local newProjectile = {}
	newProjectile.Player = Player
	newProjectile.Mode = Mode
	newProjectile.Module = require(Module)
	
	function newProjectile:Fire(Origin, Goal, Number)
		local bulletFolder
		local Bullet
		local Properties = self.Module.Properties
		if self.Player == localPlayer.Name then
			bulletFolder = game.ReplicatedStorage.FXs.Bullets.Scripted
		else
			bulletFolder = game.ReplicatedStorage.FXs.Bullets.Uncripted
		end
		
		
		if self.Mode == "Projectile" then
			Bullet = bulletFolder.ProjectilePhysic:Clone()
			local Projectile = Bullet.Projectile
			Projectile.Position = Origin
			if Bullet:GetAttribute("Modual") then
				Bullet:SetAttribute("Modual", self.Module.Name)
			end
			print("Firing Projectile")
			if bulletFolder:GetAttribute("Player") == true then
				Bullet.Parent = pbFolder
			else
				Bullet.Parent = bFolder
			end
			
			Projectile.CFrame = CFrame.new(Origin, Origin + Goal)
			local position = Origin
			local speed  = self.Module.Speed
			local maxDistance = self.Module.DropOff
			local gravityMultiplier = self.Module.GravityMultiplier
			local gravity = Vector3.new(0, -workspace.Gravity, 0) * gravityMultiplier
			local Velocity = position * speed
			local Distance = 0
			local Connection
			
			Connection = RunService.Heartbeat:Connect(function(deltaTime)
				local stepVelocity = Velocity * gravity * deltaTime
				local stepDisplacment = (Velocity + stepVelocity) / 2 * deltaTime
				Velocity = stepVelocity
				print(Projectile.Position)
				local nextPosition = position + stepDisplacment
				Projectile.CFrame = CFrame.new(nextPosition, nextPosition + Velocity.Unit)
				position = nextPosition
				Distance += stepDisplacment.Magnitude
				
				if Distance >= maxDistance then
					Connection:Disconnect()
				end
			end)
		end
	end
	
	return newProjectile
end

return Projectile

If anyone needs more information on my code then please let me know, any help would be appriciated :smiley:

local stepVelocity = Velocity * gravity * deltaTime
This is wrong. Gravity needs to be added onto velocity, not be multiplied with. What you are doing is scaling the velocity vector by (0, -g, 0), or in other words multiplying the X and Z components by 0, which makes them 0, and scaling the Y component by the gravity, which explains why your end result is 0 on the XZ axes, and a large negative number on the Y axis.

The correct solution is:
Velocity_next = Velocity_previous + (Acceleration * dt)
Position_next = Position_previous + (Velocity * dt)
This is using Euler’s method, but you seem to be using some sort of mix between Euler for velocity and a 2nd order Runge-Kutta method for displacement, which I’m not sure of how well those two would mix, it should probably work but it’s better to be consistent.

I see what you mean now!

I got this come from a few yt videos that used the veriable stepDisplacment This veriable seemed to be the problem as well as the multiplication on the accelaration like you said.

Thanks a million for this!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.