Making a combat game with ranged weapons? FastCast may be the module for you!

Is there any way of simulating the ray instantly?

I’ve tried figuring this out myself, but haven’t been able to…Does anyone know why when the FirePointObject on the Tool seems to either collide or be very close to colliding with another part, the bullet will fire at a completely different angle?


Sorry for the small Gif, but this showcases what I mean. You can see my first few bullets fire in random directions when touching or nearly touching another object, as well as the last few bullets. You also see one shot actually go in the correct direction when I’m further away from the object.

3 Likes

Is it possible to simulate air resistance and drag with FastCast?

1 Like

This is a roblox issue with cursor detection sadly.
I personally have this issue with EVERYTHING thats cursor-based.

As far as I am aware there is no direct solution (I may be wrong)
You could attempt the following though.
~ Raycast from the camera origin to get the hit cframe of the mouse. (unsure if this will work.)
~ Or just detect if the gun is very close to something and make it shoot infront of the barrel by default instead of going to the cursor.

2 Likes

Hey, thanks for the response!

In a way, I believe I’m already doing what you’re suggesting, but slightly different. Rather than using the Cursor for getting the Mouse.Hit.Position, I began using a Raycast from the center of the screen and then returning that position.

local function GetCenterOfScreen()
    local position = Camera.ViewportSize / 2
	local unitRay = Camera:ViewportPointToRay(position.X, position.Y)

    local rayParams = RaycastParams.new()
    rayParams.FilterDescendantsInstances = {Character}
    rayParams.FilterType = Enum.RaycastFilterType.Blacklist

	local raycastResult = Workspace:Raycast(unitRay.Origin, unitRay.Direction * 1_000, rayParams)
	local hit = CFrame.new(raycastResult.Position)

    return hit.Position
end

Although even with this, the issue still persists, unfortunately.

I was curious if you had an idea of how to get the direction forward of barrel? Currently I calculate the direction using the following equation fireDirection = (mousePoint - FirePointObject.WorldPosition).Unit.

Additionally, for anyone else who might be curious about the issue, as my previous Gif might not have shown it off greatly, here’s a better one:

1 Like

If someone is still looking for this, there’s a thread solving this issue:

Simply copy & paste the entire code block, set up the proper arguments (bullet speed, gravity, start position etc.) and use what the LaunchDirection function returns as your direction argument:

local CanReach,DIRECTION = LaunchDirection(..., ..., ..., ..., true)

Caster:Fire(FirePointObject.WorldPosition, DIRECTION, modifiedBulletSpeed, CastBehavior)
3 Likes

I would absolutely love a method to simulate once instantaneously. Similar to just WorldRoot:Raycast(), it would be FastCast:Once() that would return a hit. This way I could have FastCast hit reg on client and have a sanity check testing the entire path, including pierces and reflections if I have those implemented. I’ve tried numerous times to do hit reg on the server but the delay is very noticeable and makes gunplay awful. I’ve tried lag compensation methods such as player snapshots, but it still doesn’t beat client hit reg.

1 Like

May i ask how do you use the pierce function, i have been trying to do ricochet for so long. thank you, sorry if im interupting your time

I have done this for only default projectile motion projectiles.

So it should work unless you are changing the acceleration or velocity after the projectile is in flight.

Sidenote:

But keep in mind to check out other lag compensation server sided solutions and such like CSGO and stuff.

Ultimately it’s up to you to decide.

2 Likes

I created a staff weapon that fires orbs of magic, and when one of these orbs intercepts a part with a “Shield” attribute they are reflected back to the original player. This works fine, but there is one caveat: the changes I try to make to some ray parameters don’t seem to work properly, specifically the FilterDescendantInstances property.

When a player uses the staff to fire a projectile, the caster is told to ignore their character’s model; when the projectile is reflected back, the script tries to override the filter list with an array only containing the character of the player who reflected the projectile. This would allow the reflected projectile to hit the player who originally shot it, but in practice it refuses to work. The projectile goes through the staff user’s character every time, even if I try setting the ignore list to an empty array.

What gives? Am I not meant to change this property in run time? The band-aid solution I have right now is to not place the staff user’s character in the ignore list in the first place, which isn’t great because you can occasionally hit yourself when firing a projectile.

Hey, thanks for replying!
I recently checked out your solution while back and I have tried implementing it, unfortunately I don’t believe I was setting it up properly. I will give it another shot and see if I can get it working properly!

Also, I looked towards games like CS:GO and tried implementing their lag compensation methods, it didn’t really turn out well and made server hit reg slightly better but still inconsistent.

Best regards,
Scythe

1 Like

I ended up fixing this issue by overriding the filter list in the CanRayPierce function, not OnRayPierced. Thanks for ADRENALXNE’s help, he had a similar post in this thread and found this solution.

This API member does not exist because it’s not so simple.

Imagine you do a hitreg with this hypothetical FastCast:Once() and you detect a hit on the person you’re aiming at. Cool, all good so far. But what happens when you fire the bullet that has travel time and say, some dude happens along and walks into the path of the bullet before it hits the guy you were aiming it? Now your pre-calculated result is flat out wrong.

This is why the module comes with the hit simulation it does - because you should be simulating the bullet, travel time and all.

1 Like

Ahh, I understand. Yeah I can see the complexity behind that and the issues that could arise. I appreciate you reading my reply and considering! I do really enjoy this module quite a lot, it’s been a major staple in many of my projects. Thanks a lot!

Is possible to make a projectile’s motion? I was looking on the dev forum and everything I didnt found I am trying to do something like this.

and I am doing something like:

caster.LengthChanged:Connect(function(cast, lastpoint, dir, length)
		local offset = CFrame.new(0, 0, -length)
		bullet.CFrame = CFrame.lookAt(lastpoint, lastpoint + dir):ToWorldSpace(offset)
	end)

How do you make the bullet render immediately when the cast starts instead of when the cast first updates, since if you have it render when it updates, it’ll start rendering at various distances in front of the firepoint rather than actually at the firepoint. It really breaks immersion and I can’t seem to fix it, I’ve tried moving the bullet position to the firepoint after the first render as well, but it just doesn’t do anything.

you need to increase the downward acceleration, its in the castbehavior i believe

1 Like

How can I make bullets pierce through the accessories?

How do I make both projectiles terminate when they hit each other? Right now only one random one gets terminated (usually the one that’s most recently fired).

EDIT: I’ve tried getting the bullet that it hit in OnRayHit, and returning it to PartCache in OnCastTerminating, but it didn’t work. The recently fired bullet gets returned, but it errors for the hit bullet saying that it’s not in-use (though it very obviously is as I tried doing table.find on the InUse table with the hit bullet, and it found it perfectly fine every time)

EDIT 2: Solved:) If anyone runs into this in the future, I keep all ActiveCasts in a table, storing them in the table on fire, having the projectile instance as the key, the ActiveCast as the value, and then when I determine that it hits a projectileI find the ActiveCast associated with the projectile instance from the table, and run :Terminate() on it.

5 Likes

Heya there!

There has been just one issue that I seemingly cannot solve.

Sometimes, the bullet ray shoots at the sky (past the zombie)? I’m guessing this is because of the muzzle being inside a zombie head. The video below showcases this: red part is muzzle position, green part is mouse position.

Here’s the module code that I use for the gun.

local setup = {}
setup.__index = setup

local PS = game:GetService("Players")
local RS = game:GetService("ReplicatedStorage")
local DBS = game:GetService("Debris")

local caster = require(script.Parent.Caster)
local partCache = require(script.Parent.Cache)
local damageDisplay = require(script.Parent.Parent.DamageDisplay)
local fx = RS.Events.FX
local effect = {
	blood = "Blood",
	hit = "Hit"
}

type params = {[string]: any}

local function VisualizeRay(dir: Vector3, origin: Vector3)
	local mid = origin + dir / 2

	local part = Instance.new("Part") do
		part.Parent = workspace
		part.Anchored = true
		part.CFrame = CFrame.new(mid, origin)
		part.Size = Vector3.new(.1, .1, dir.Magnitude)
		part.Material = Enum.Material.Neon
		part.BrickColor = BrickColor.Red()
		part.CanCollide = false
		part.CanQuery = false
		part.CanTouch = false

		task.delay(.2, function()
			part:Destroy()
		end)
	end
end

local function VisualizePos(pos: Vector3, color: BrickColor)
	local part = Instance.new("Part") do
		part.Parent = workspace
		part.Anchored = true
		part.Position = pos
		part.Size = Vector3.new(.5,.5,.5)
		part.Material = Enum.Material.Neon
		part.BrickColor = color
		part.CanCollide = false
		part.CanQuery = false
		part.CanTouch = false

		--task.delay(.2, function()
		--	part:Destroy()
		--end)
	end
end

local function LoadBulletHole(parent: BasePart, pos: Vector3, normal: Vector3, hum: Humanoid)
	local hole = Instance.new("Part") do
		hole.CanCollide = false
		hole.CanQuery = false
		hole.CanTouch = false
		hole.Transparency = 1
		hole.Size = Vector3.new(.5, .5, .05)
		hole.Parent = parent
		hole.CFrame = CFrame.lookAt(pos, pos + normal)
	end

	local image = if hum then script.BloodHole:Clone() else script.PartHole:Clone() do
		image.Parent = hole
	end
	
	local weld = Instance.new("WeldConstraint") do
		weld.Parent = parent
		weld.Part0 = parent
		weld.Part1 = hole
	end

	DBS:AddItem(hole, 15)
end

function setup.new(params)
	local self = {}
	
	self.Tool = params.Tool
	self.Owner = if self.Tool.Parent:IsA("Backpack") then self.Tool.Parent.Parent else PS:GetPlayerFromCharacter(self.Tool.Parent)
	self.Character = self.Owner.Character or self.Owner.CharactedAdded:Wait()
	self.Damage = params.Damage
	self.AttackType = params.AttackType
	self.Bullet = Instance.new("Part")
	self.Multiplier = params.Multiplier
	self.DistanceBeforeMultiply = params.DistanceBeforeMultiply
	self.MinSpread = params.MinSpread
	self.MaxSpread = params.MaxSpread
	
	for i,v in pairs(params.BulletInfo) do
		if i == "Trail" and v:IsA("Trail") then
			local at0 = Instance.new("Attachment") do
				at0.Parent = self.Bullet
				at0.WorldPosition = (self.Bullet.CFrame * CFrame.new(0, 0, (self.Bullet.Size.Z / 2))).Position
			end
			local at1 = Instance.new("Attachment") do
				at1.Parent = self.Bullet
				at1.WorldPosition = (self.Bullet.CFrame * CFrame.new(0, 0, -(self.Bullet.Size.Z / 2))).Position
			end
			
			local clone = v:Clone() do
				clone.Parent = self.Bullet
				clone.Enabled = false
				clone.Attachment0 = at0
				clone.Attachment1 = at1
			end
		else
			if self.Bullet[i] then
				self.Bullet[i] = v
			end
		end
	end
		
	self.MaxDistance = params.MaxDistance
	self.Speed = params.Speed
	self.Gravity = params.Gravity
	self.RaycastParameters = params.RaycastParameters
	self.BulletParent = params.BulletParent
	self.PartCache = partCache.new(self.Bullet, 100, self.BulletParent)
	self.Hitbox = caster.new()
	self.Behavior = caster.newBehavior() do
		self.Behavior.RaycastParams = self.RaycastParameters
		self.Behavior.HighFidelityBehavior = caster.HighFidelityBehavior.Default
		self.Behavior.MaxDistance = self.MaxDistance
		self.Behavior.Acceleration = self.Gravity
		self.Behavior.CosmeticBulletContainer = self.BulletParent
		self.Behavior.CosmeticBulletProvider = self.PartCache
		self.Behavior.AutoIgnoreContainer = false
	end
	
	return setmetatable(self, setup)
end

function setup:Fire(mousePos, endPos)
	local distBeforeMult = self.DistanceBeforeMultiply
	local player = self.Owner
	local hitbox = self.Hitbox
	local speed = self.Speed
	local damage = self.Damage
	local gravity = self.Gravity
	local multiplier = self.Multiplier
	local behavior = self.Behavior
	local cache = self.PartCache
	local params = self.RaycastParameters
	local attackType = self.AttackType
	local maxSpread = self.MaxSpread
	local minSpread = self.MinSpread
	local direction = (mousePos - endPos).Unit
	local debounce = {}
	local connection = {}
	
	VisualizePos(endPos, BrickColor.Red())
	VisualizePos(mousePos, BrickColor.Green())
	connection[1] = hitbox.LengthChanged:Connect(function(caster, origin, direction, length, velocity, bullet)
		if not bullet then
			return
		end
		
		if bullet:FindFirstChildWhichIsA("Trail") then
			bullet:FindFirstChildWhichIsA("Trail").Enabled = true
		end
		
		local bulletLength = bullet.Size.Z / 2
		local baseCFrame = CFrame.new(origin, origin + direction)
		
		bullet.CFrame = baseCFrame * CFrame.new(0, 0, -(length - bulletLength))
	end)
	connection[2] = hitbox.RayHit:Connect(function(caster, ray, velocity, bullet)
		local hitPart = ray.Instance
		local hitPoint = ray.Position
		local normal = ray.Normal

		local model = ray.Instance:FindFirstAncestorOfClass("Model")
		local hum = model and model:FindFirstChildOfClass("Humanoid")
		
		fx:FireAllClients(if hum then effect.blood else effect.hit, hitPart, hitPoint)
		LoadBulletHole(hitPart, hitPoint, ray.Normal, hum)
		
		if hum and not PS:GetPlayerFromCharacter(model) then
			if debounce[hum] then
				return
			end	
			
			--if attackType == "Destroying" then
			--	local blacklist = {"Torso", "HumanoidRootPart"}
			--	local rand = Random.new():NextInteger(1,65)
				
			--	if rand <= 25 and hitPart and not table.find(blacklist, hitPart.Name) then
			--		hitPart.Transparency = 1

			--		task.delay(.4, function()
			--			hitPart:Destroy()
			--		end)
			--	end
			--end
			
			local currDamage = if multiplier[hitPart.Name] then damage * multiplier[hitPart.Name] else damage
			
			if player:DistanceFromCharacter(hitPoint) < distBeforeMult then
				currDamage *= 1.3
			end

			debounce[hum] = true
			hum:TakeDamage(currDamage)
			damageDisplay.new(currDamage, hitPart)
		end
	end)
	connection[3] = hitbox.CastTerminating:Connect(function(caster)
		local cosmeticBullet = caster.RayInfo.CosmeticBulletObject
		
		if cosmeticBullet then
			if behavior.CosmeticBulletProvider then
				pcall(function()
					if cosmeticBullet:FindFirstChildWhichIsA("Trail") then
						cosmeticBullet:FindFirstChildWhichIsA("Trail").Enabled = false
					end
					
					behavior.CosmeticBulletProvider:ReturnPart(cosmeticBullet)
					
					for i,v in ipairs(connection) do
						v:Disconnect()
					end
				end)
			end
		end
	end)
	
	pcall(function()	
		local dirCF = CFrame.lookAt(Vector3.new(), direction)
		local spreadDir = CFrame.fromOrientation(0, 0, math.random(0, math.pi * 2))
		local spreadAngle = CFrame.fromOrientation(math.rad(math.random(minSpread, maxSpread)), 0, 0)
		local direction = (dirCF * spreadDir * spreadAngle).LookVector
		local modifSpeed = (direction * speed)
		
		hitbox:Fire(endPos, direction, modifSpeed, behavior)
	end)
end

return setup

I’m not really sure how I can solve this. Any help is appreciated!

EDIT: I think I solved it. I just moved the muzzle point backwards by a few studs (it’s now practically in the player’s shoulder).

1 Like