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

Maybe that was an earlier build. I did see it but it was last week.

Feel free to chime in, even if you aren’t TheFortex.

I mean, I told you the problem I just wanted confirmation. Something like “ROBLOX has an issue when creating beams in rapid succession” would do but if you must:

local RunService = game:GetService('RunService')

local FastCast = require(script.FastCastRedux)
local caster = FastCast.new()
local config = require(script.Parent:WaitForChild('Config'))

local firePoint = script.Parent.Point
local target = script.Parent.Parent.Parent:WaitForChild('Core'):WaitForChild('Target')
local weapongroup = script.Parent.Parent.Parent.Core:WaitForChild('WeaponGroups'):WaitForChild(config.WeaponGroup)

--Settings Load
local RNG = Random.new()							-- Set up a randomizer.
local DEBUG_VISUALIZE = false						-- If true, individual sub-rays will be shown with black cones.
local BULLET_MAXDIST = 10000							-- The furthest distance the bullet can travel 
local BULLET_GRAVITY = Vector3.new(0, 0, 0)		-- The amount of gravity applied to the bullet in world space (so yes, you can have sideways gravity)
local MIN_BULLET_SPREAD_ANGLE = config.minSpread					-- THIS VALUE IS VERY SENSITIVE. Try to keep changes to it small. The least accurate the bullet can be. This angle value is in degrees. A value of 0 means straight forward. Generally you want to keep this at 0 so there's at least some chance of a 100% accurate shot.
local MAX_BULLET_SPREAD_ANGLE = config.maxSpread					-- THIS VALUE IS VERY SENSITIVE. Try to keep changes to it small. The most accurate the bullet can be. This angle value is in degrees. A value of 0 means straight forward. This cannot be less than the value above. A value of 90 will allow the gun to shoot sideways at most, and a value of 180 will allow the gun to shoot backwards at most. Exceeding 180 will not add any more angular varience.								-- The amount of time that must pass after firing the gun before we can fire again.
local TAU = math.pi * 2							-- Set up mathematical constant Tau (pi * 2)

--local CosmeticBullet = Instance.new("Part")
--CosmeticBullet.Material = Enum.Material.Neon
--CosmeticBullet.Color = Color3.fromRGB(0, 196, 255)
--CosmeticBullet.CanCollide = false
--CosmeticBullet.Anchored = true
--CosmeticBullet.Size = Vector3.new(0.4, 0.4, 2.4)

local BeamTracer = Instance.new('Beam')
BeamTracer.Name = 'Tracer'
BeamTracer.Texture = config.Texture
BeamTracer.Width0 = 0
BeamTracer.Width1 = 0.35
BeamTracer.Color = config.Color
BeamTracer.FaceCamera = true
BeamTracer.LightInfluence = 0
BeamTracer.LightEmission = 1
BeamTracer.Transparency = NumberSequence.new(0.333, 0)
BeamTracer.TextureMode = Enum.TextureMode.Stretch
BeamTracer.TextureSpeed = 0
--BeamTracer.Color = config.Color
local tp1 = Instance.new('Part')
tp1.Anchored = true
tp1.Size = Vector3.new(0.5, 0.5, 0.5)
tp1.Transparency = 1
tp1.BrickColor = BrickColor.Red()
local ta1 = Instance.new('Attachment', tp1)
ta1.Name = 'ta1'
local tp2 = Instance.new('Part')
tp2.Anchored = true
tp2.Transparency = 1
tp2.Size = Vector3.new(0.5, 0.5, 0.5)
tp2.BrickColor = BrickColor.Blue()
local ta2 = Instance.new('Attachment', tp2)
ta2.Name = 'ta2'
BeamTracer.Attachment0 = ta1
BeamTracer.Attachment1 = ta2

local function Fire(direction)
	local directionalCF = CFrame.new(Vector3.new(), direction)
	-- Now, we can use CFrame orientation to our advantage.
	-- Overwrite the existing Direction value.
	local direction = (directionalCF * CFrame.fromOrientation(0, 0, RNG:NextNumber(0, TAU)) * CFrame.fromOrientation(math.rad(RNG:NextNumber(MIN_BULLET_SPREAD_ANGLE, MAX_BULLET_SPREAD_ANGLE)), 0, 0)).LookVector
	local cosmeticBullet = {tp1:Clone(), tp2:Clone(), BeamTracer:Clone()}
	cosmeticBullet[1].CFrame = CFrame.new(firePoint.Position, firePoint.Position + direction)
	cosmeticBullet[2].CFrame = CFrame.new(firePoint.Position, firePoint.Position + direction)
	cosmeticBullet[1].Parent = workspace.FX
	cosmeticBullet[2].Parent = workspace.FX
	cosmeticBullet[3].Parent = cosmeticBullet[1]
	cosmeticBullet[3].Attachment0 = cosmeticBullet[1].ta1
	cosmeticBullet[3].Attachment1 = cosmeticBullet[2].ta2
	
	caster:Fire(firePoint.Position, direction * BULLET_MAXDIST, config.Speed, cosmeticBullet, script.Parent, false, BULLET_GRAVITY, CanRayPierce)
end

function OnRayUpdated(castOrigin, segmentOrigin, segmentDirection, length, cosmeticBulletObject)
	local baseCFrame = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
	cosmeticBulletObject[1].CFrame = baseCFrame * CFrame.new(Vector3.new(0, 0, length), segmentOrigin - segmentDirection)
	cosmeticBulletObject[2].Position = segmentOrigin
end

function OnRayHit(hitPart, hitPoint, normal, material, cosmeticBulletObject)
	cosmeticBulletObject[3]:Destroy()
	cosmeticBulletObject[2]:Destroy()
	cosmeticBulletObject[1]:Destroy()
end

function CanRayPierce(hitPart, hitPoint, normal, material)
	if hitPart.Parent == workspace.FX then
		return true
	end
end

caster.LengthChanged:Connect(OnRayUpdated)
caster.RayHit:Connect(OnRayHit)

RunService.Heartbeat:Connect(function()
	if target.Value ~= nil then
		for i=1, config.BurstSize do
			if not weapongroup.Value then return end
			Fire((target.Value.Position - firePoint.Position).Unit)
			wait(config.ShotCooldown)
		end
		wait(config.BurstCooldown)
	end
end)
2 Likes

For everyone recently – Apologies for the recent regressions! I seem to be having a number of issues in person on my own time and they seem to have adversely affected my motive to work on this module. The result is a huge quality drop in the versions at 11.0.0 and later.

I’m working on a number of patches to try to resolve some of the problems caused. I also want to do more tweaks to the API (renaming events mainly), but I need to ensure things are backwards compatible. I’ll post more on this later down the line.

I’ve also updated the Changelog on the wiki. I completely neglected to do this before, which was bound to have caused issues for people.

Edit: With this post I’ve also released 11.1.2

* Fixed a bug causing pause and resume to do nothing.
3 Likes

Stop being so entitled, OP maintains this module for free, the least you could do is be polite when asking for help

I looked at your issue and it’s not an issue with FC itself. I know I help people here from time to time, but the general best idea would be to go to the scripting help subforum.

Since my API is clearly documented, it should be good to go for posting there. Generally speaking, posting here is if you think there’s a bug with the module that I have to fix.

Since you are using a modified version of the module, that is completely ruled out.

That’s what I was thinking, but I just wanted to make sure because I modified FastCast to work with two parts per cast and not one. I do delete the beam object first so I don’t think its because I delete one of the points it bugs out.

And to the people who think i’m being rude that are jumping in and reading this (not you EtiTheSpirit, your module is amazing):

I’m not being rude. I asked for confirmation if this was a Roblox issue with rendering beams months ago, so forgive me if i’m more direct now. I still have not found a fix after all this time and I had to abandon the project. Don’t jump in to a conversation that is months old and tell me that i’m entitled because I have lost my patience.

And maybe I am a little angry that I had to abandon one or two months of work. Show a little sympathy, please.

I’ve just released a brand new not garbage API! FastCast API

Let me know if you have any issues with it. The one hosted on the Gist (the old link) is obsolete now and will be deleted soon.

6 Likes

The new API for RayHit is a bit problematic for me.

I currently rely on the returned parameter Position that older versions of FastCast have, but with the new one it returns a RaycastResult.

This means that if the ray returns no hit, this is nil. This is a problem because I cannot obtain the Position parameter from anywhere in that event.

Would it be possible to add a property to the ActiveCast that contains the end position of the ray regardless if an object is hit or not?

Edit: I’ve found a temporary workaround using UserData, where I store the end goal as UserData.End and then check the position if no object was hit in the RayHit’s ActiveCaster parameter. Would still be nice to have this in the module itself though.

RayHit will never fire if the returned RaycastResult is nil (think about it – a nil RaycastResult is returned from WorldRoot:Raycast() if there wasn’t a hit, and with no hit, RayHit has no reason to fire). This change is part of why the CastTerminated event was created, as this one fires whenever the cast ends for any reason. If it ends due to a hit, RayHit will fire first (with a non-nil RaycastResult) and then CastTerminated will fire after.

If you still need to access the position CastTerminated, you can call cast:GetPosition() (where cast is the parameter in the event)

I can update the API to reflect on this.

1 Like

There’s a problem where if you’re running around a target while shooting at them, the bullets seemingly curve around the target. In order to hit the target, you have to aim behind the target which is a little awkward since usually you aim ahead of it to compensate for bullet velocity. The problem is especially noticeable when you aim off and on the target while running, you can see the bullets bending left and right but never actually on the target. It’s as if the target has a magnetic field or something lol. The module works perfect when you’re standing still but when I tested it in-game, people were complaining about the inaccuracies of the guns and that’s when I realized about the problem. I was wondering if this was an intentional feature or if there is any way to solve this? I know for a fact the problem isn’t the bullet inheriting the player’s velocity.

Are you using the example gun? Check the fire method. There’s code that makes bullets inherit the velocity of the player, which includes walking.

FastCast v12.0.0 Released

This one comes with a pretty big API change. No new features worth noting, but a lot of organization in the code was done. See Changelist - FastCast API for more information.

This is a major version update (11 => 12) for a good reason. The API of the Fire method has been completely changed to use a new data table containing most cast information. The API documentation goes over exactly how this works, so please have a look at it! https://etithespirit.github.io/FastCastAPIDocs/fastcast-objects/fcrayinfo/ (The example gun should help too.)

Edit: This also comes with some writing improvements to the API to make some stuff more clear and provide examples for more complicated things (right now that’s just the pierce function, may be more in the future depending on feedback).

4 Likes

I am using the example gun, I haven’t changed the scripts at all except remove the bottom part that makes sure not to set the angle above 180 and changed the firerate to 0.1, bullets per shot to 1, and bullet spread angle to 0. https://gyazo.com/8e4e86fde7a1f80eee7f69a2fcacdbbb You can clearly see the dummy doing some kind of witchcraft that makes the bullets curve around it. I also removed the part that makes the bullet inherit the player velocity to see if that made any difference but the bullet still curves around the target.

That is such a strange issue… The bullets are flying straight (the curvature is just a really neat illusion).

It’s related to some form of client/server lag apparently. To test this, I modified the client code to fire the event with only the mouse’s position in 3D space, then I calculate the direction on the serverside. This resolved the problem. I’ll release the example gun with this change.

Edit: New version of the example gun is live. Thanks for the report.

2 Likes

I have made a tutorial on how to use the example gun! Check it out here: https://youtu.be/KuDfxCEaeyQ

I plan to do another Major version increment here in about a day. Version 13.0.0 will be another rewrite of the module, but rather than to do things over, it will be to port it to the Luau strong type system.

Version 13 is LIVE.

  • The long comment at the top of the module has been updated with new links since I deleted the old wiki.

Please note: While the system in Roblox is done, the type checker in script editors is NOT done. There are a number of type mismatch errors + missing type errors that are false and can be safely ignored.

The module using nonstrict formatting will no longer be supported nor developed past the release of v13.

8 Likes

Hi! I ran into a problem today where it errors when I try to do FiringCaster.CastTerminated:Wait()
See code here:

local function FireHook(side,fireType)
    local DataTable = {}

    if side == CustomEnums.GrappleSideType.Left then
    	DataTable = Left
    end

    if side == CustomEnums.GrappleSideType.Right then
    	DataTable = Right
    end

    local FireSide

    if side == CustomEnums.GrappleSideType.Left then
    	FireSide = "Left"
    end

    if side == CustomEnums.GrappleSideType.Right then
	    FireSide = "Right"
    end

    if fireType == CustomEnums.GrappleFireType.Fire then
	    local mousePos = Mouse.Hit.p
	
	    local FirePoint = FirePoints[FireSide .. "FirePoint"]
	
	    if FirePoint then
		    local TargetCF = CFrame.new(FirePoint.WorldPosition,mousePos)
		
		    local FiringCaster = DataTable.Caster
		
		    if FiringCaster then
			    DataTable.Cast = FiringCaster:Fire(TargetCF.Position,TargetCF.LookVector,375,CastParams)
			    DataTable.Activated = true
			
			    local EndPoint = DataTable.Cast.RayInfo.CosmeticBulletObject:WaitForChild("End")
			
			    local Beam = Instance.new("Beam")
			    Beam.Texture = "rbxassetid://5670527105"
			    Beam.FaceCamera = true
			    Beam.Attachment0 = FirePoint
			    Beam.Attachment1 = EndPoint
			    Beam.LightInfluence = 0
			    Beam.TextureMode = Enum.TextureMode.Static
			    Beam.TextureSpeed = 7.5
			    Beam.Parent = FirePoint
			
			    DataTable.Objects[#DataTable.Objects + 1] = Beam
			    DataTable.Objects[#DataTable.Objects + 1] = DataTable.Cast.RayInfo.CosmeticBulletObject
			
			    local ActiveCast = FiringCaster.CastTerminating:Wait()
			    DataTable.Activated = false
			
			if not DataTable.ManualTerminated then
				ActiveCast.RayInfo.CosmeticBulletObject.Anchored = true
			end
		    end
	    end
    end

    if fireType == CustomEnums.GrappleFireType.Cancel then	
    	if DataTable.Activated then
	        DataTable.Cast:Terminate()
		    DataTable.ManualTerminated = true
	    end
	
	    for i,v in ipairs(DataTable.Objects) do
		    if v:IsA("Instance") then v:Destroy() end
	    end
    end
end

The error is:
14:49:02.483 - Workspace.ProjectInfiniti.ODMGear:164: attempt to call a nil value

line 164 is FiringCaster.CastTerminating:Wait()

EDIT: I just checked the Signal module, and doesnt really look like it has a Wait() function (if so, can you add the wait function)

I’ve just released a patch including Signal:Wait() among some other fixes.

https://etithespirit.github.io/FastCastAPIDocs/changelog/#fastcast-1302

4 Likes

ok thanks! great help for my project

1 Like

I’ve just released 13.0.4 because I realize that I accidentally caused a memory leak in signal objects, so I’ve fixed that. You might wanna upgrade.

https://etithespirit.github.io/FastCastAPIDocs/changelog/#fastcast-1304

1 Like