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

How would I go about making the end of the projectile the starting position rather then the center as it is in the example, Lets say you have a LONG projectile like a beam when you spawn it at the OUTPUT of weapon it uses the projectiles center rather then the end or the beginning of it. I tried just offsetting it by a vector but that makes it look ugly when shooting sideways.

1 Like

Append this to the end of setting the cosmetic bullet CFrame in LengthChanged

... * CFrame.new(0, 0, -CosmeticBulletObject.Size.Z/2)

This will move the bullet forward by half it’s size relative to however it’s rotated, resulting in exactly what you want.

2 Likes

Thank you for a quick response, that solved it.

Hey, I’ve been experimenting quite a bit but haven’t managed to get what I’m aiming for. How can I make the bullet only stay on a certain Y axis as it travels, as in I don’t want to fire into the sky, only things on the same “level” as the character.

Wow I finally solved it, I was playing around with the server script but all I had to do was actually experiment in the client.

replaced the default firedirection with
local FireDirection = (Vector3.new(Mouse.Hit.p.X,game.Players.LocalPlayer.Character.PrimaryPart.Position.Y, Mouse.Hit.p.Z) - FirePointObject.WorldPosition)

1 Like

remember to change the ray’s fire direction based on a unit vector otherwise you’ll end up with wack stuff where the spread changes based on how far away someone is. I’ve seen lots of games on roblox do this and it’s incredibly annoying.

1 Like

I am having trouble solving Delay issue.

Displayed here https://gyazo.com/838f2e40c33548576573fda7b0b24e7c

Gun has some significant delay, which’s probably due to fact, that when player presses the button, program gets the current position at that time, but since player is moving sideways, position is not correct anymore.

The solution I had for this is to do client to client replication and have the server verify rather then client to server and have the server render. Obviously verify it on the server then send it back to the other clients to render, excluding the source client which can render right away.

In short everything looks much smoother if you render it on the client/clients rather then the server in most cases.

1 Like

But how would you actually implement that?

Well, a remote event, a server and client script, and the tool client script. All three of these need access to the fastcast module. So I would put it in ReplicatedStorage. The tool script will work as normal, except remove the damage function, and have it call a remote event to fire it serverside to do the hit verification(basically just fire the fastcast but have the CosmeticObject set to nil or make it invisible via transparency) - The server will be what we use for damage (I’ve seen many people make the mistake of trusting the client and this allows for easy exploitation). Have the server send all the information it received fired to all other clients as soon as its received.

When you do the server script receiving the RemoteEvent call it is vital you verify what your receiving and limit what your receiving from the client. I would only recommend you receive the direction and the tool.

Plug in the stuff you received from the client and manually insert the rest into the Fire() call for now you can make a system later if you have multiple tools that use fastcast. You can derive a start position from the tool you got sent. Use its handle or a specified output part. When you send this back to the clients (not the original client) only provide the tool and Direction, Use the same technique of plugging in the constants as you did for the server. let the client decide a start position this will solve your issue in the gif.

6 Likes

Yo I was wondering if you think there would be issues if each client only had like 2 casters, 1 for the bullets they fire, and 1 caster to render all the other bullets from players. Potentially, if there are many players, one of the casters could be called a lot. Do you think this could be an issue?

Generally speaking only one caster should handle both rendering and detection if you’re doing both on the client (Why render something with a separate object doing the literal exact same thing?)

Otherwise, no. This module is designed for intensive workload. It should not be problematic to have multiple casters each firing several bullets at once.

I do it this way, haven’t had any issues.

Would be nice if this had a bullet penetration option.

Though, I’ve already implemented it myself and it seems to be working fine for the most part. However, I don’t feel too confident about how I’ve scripted bullet penetration.

May I ask how you programmed bullet penetration?

You could fire the bullet again, but adding the part it just hit to the ignore list.

I am using your module for my game, which is about space combat. My first weapons prototype will be a defense satellite. I basically copied your gun example, removed the client side stuff (mouse clicking and mouse.hit.p), and made it shoot automatically in bursts (with separate delay timers for individual shots and bursts).

As far as I know I am inputting the correct values that the module needs, and I changed nothing in the FastCast module. I am also up to date as I tried replacing the FastCast module a few times. This is what happens:
Screenshot_84
The bullet stays in position, with no velocity being applied to it. The bullet is not hitting something because it prints (“hit”) if it hits something (as seen in the code later on).

It’s aiming is accurate though…?
Screenshot_85

I don’t know what I am doing wrong! According to the prints it is supposed to be working. (The Vector3 is “ModifiedBulletVelocity” and “firesound” is when the sound function is called.)
Screenshot_86
Note:
Every ship has a “Core” where values and scripts are stored.

Help would be appreciated!

-- Latest update 11 June 2019 - Added bullet spread factor as suggested by https://devforum.roblox.com/t/making-a-combat-game-with-ranged-weapons-fastcast-may-be-the-module-for-you/133474/88?u=etithespirit

local FirePointObject = script.Parent
local FastCast = require(script.Parent.FastCast)

local Debris = game:GetService("Debris")

-- REMEMBER: THERE'S RESOURCES TO HELP YOU AT https://github.com/XanTheDragon/FastCastAPIDocs/wiki/API
local RNG = Random.new()							-- Set up a randomizer.
local BULLET_SPEED = 500							-- Studs/second - the speed of the bullet
local BULLET_MAXDIST = 1500							-- The furthest distance the bullet can travel 
local BULLET_DROP_GRAVITY = 0						-- The amount of gravity applied to the bullet. Default place gravity is 196.2 (see workspace.Gravity)
local BULLET_WIND_OFFSET = Vector3.new(0, 0, 0)	-- The amount of force applied to the bullet in world space. Useful for wind effects.
local MIN_BULLET_SPREAD_ANGLE = 0.3					-- 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 = 0.7					-- 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.
local FIRE_DELAY = 0.5								-- The amount of time that must pass after firing the gun before we can fire again.
local BURST_COUNT = 3
local BURST_DELAY = 2
local DAMAGE = {EMP=15,NORMAL=12}
local TAU = math.pi * 2							-- Set up mathematical constant Tau (pi * 2)

local CanFire = true -- Used for a cooldown.
local CurrentShot = 0

-- Now we set the caster values.
local Caster = FastCast.new() --Create a new caster object.
Caster.Gravity = BULLET_DROP_GRAVITY --Set the values accordingly.
Caster.ExtraForce = BULLET_WIND_OFFSET
Caster.IgnoreDescendantsInstance = workspace.Effects

-- Make a base cosmetic bullet object. This will be cloned every time we fire off a ray.
local CosmeticBullet = Instance.new("Part")
CosmeticBullet.Name = "Bolt"
CosmeticBullet.Material = Enum.Material.Neon
CosmeticBullet.Color = Color3.fromRGB(110, 153, 202)
CosmeticBullet.CanCollide = false
CosmeticBullet.Anchored = true
CosmeticBullet.Size = Vector3.new(0.25, 0.25, 1)

-- Bonus points: If you're going to be slinging a ton of bullets in a short period of time, you may see it fit to use PartCache.
-- https://devforum.roblox.com/t/partcache-for-all-your-quick-part-creation-needs/246641 

-- And a function to play fire sounds.
function PlayFireSound()
	print("firesound")
end

-- Create the spark effect for the bullet impact
function MakeParticleFX(Position, Normal)
	-- This is a trick I do with attachments all the time.
	-- Parent attachments to the Terrain - It counts as a part, and setting position/rotation/etc. of it will be in world space.
	-- UPD 11 JUNE 2019 - Attachments now have a "WorldPosition" value, but despite this, I still see it fit to parent attachments to terrain since its position never changes.
	local Attachment = Instance.new("Attachment")
	Attachment.CFrame = CFrame.new(Position, Position + Normal)
	Attachment.Parent = workspace.Terrain
	local Particle = ImpactParticle:Clone()
	Particle.Parent = Attachment
	Debris:AddItem(Attachment, Particle.Lifetime.Max) -- Automatically delete the particle effect after its maximum lifetime.
	
	-- A potentially better option in favor of this would be to use the Emit method (Particle:Emit(numParticles)) though I prefer this since it adds some natural spacing between the particles.
	Particle.Enabled = true
	wait(0.05)
	Particle.Enabled = false
end

function Fire(Direction)
	local DirectionalCF = CFrame.new(Vector3.new(), Direction)
	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 ModifiedBulletSpeed = Direction * BULLET_SPEED
	
	-- Prepare a new cosmetic bullet
	local Bullet = CosmeticBullet:Clone()
	Bullet.CFrame = CFrame.new(FirePointObject.Position, FirePointObject.Position + Direction)
	Bullet.Parent = workspace.Effects
	
	-- NOTE: It may be a good idea to make a Folder in your workspace named "CosmeticBullets" (or something of that nature) and use FireWithBlacklist on the descendants of this folder!
	-- Quickly firing bullets in rapid succession can cause the caster to hit other casts' bullets from the same gun (The caster only ignores the bullet of that specific shot, not other bullets).
	-- Do note that if you do this, you will need to remove the Equipped connection that sets IgnoreDescendantsInstance, as this property is not used with FireWithBlacklist
	
	-- Fire the caster
	Caster:FireWithBlacklist(FirePointObject.Position, Direction * BULLET_MAXDIST, ModifiedBulletSpeed, script.Parent.Parent:GetChildren(), Bullet)
	print(ModifiedBulletSpeed)
	-- Play the sound
	PlayFireSound()
end

function OnRayHit(HitPart, HitPoint, Normal, Material, CosmeticBulletObject)
	-- This function will be connected to the Caster's "RayHit" event.
	print("hit")
	CosmeticBulletObject:Destroy()
	if HitPart and HitPart.Parent then
		local Core = HitPart.Parent:FindFirstChildOfClass("Core")
		if Core then
			if Core.Shield.Value ~= 0 then
				Core.Shield.Value = Core.Shield.Value - DAMAGE.EMP
				if Core.Shield.Value < 0 then
					Core.Shield.Value = 0
				end
			else
				Core.Hull.Value = Core.Hull.Value - DAMAGE.NORMAL
				if Core.Hull.Value < 0 then
					Core.Hull.Value = 0
				end
			end
		else
			print("hit does not have core")
		end
		--MakeParticleFX(HitPoint, Normal) -- Particle FX
	end
end

function OnRayUpdated(CastOrigin, SegmentOrigin, SegmentDirection, Length, CosmeticBulletObject)
	-- Whenever the caster steps forward by one unit, this function is called.
	-- The bullet argument is the same object passed into the fire function.
	local BulletLength = CosmeticBulletObject.Size.Z / 2 -- This is used to move the bullet to the right spot based on a CFrame offset
	CosmeticBulletObject.CFrame = CFrame.new(SegmentOrigin, SegmentOrigin + SegmentDirection) * CFrame.new(0, 0, -(Length - BulletLength))
end
	
-- Before I make the connections, I will check for proper values. In production scripts that you are writing that you know you will write properly, you should not do this.
-- This is included exclusively as a result of this being an example script, and users may tweak the values incorrectly.
assert(MAX_BULLET_SPREAD_ANGLE >= MIN_BULLET_SPREAD_ANGLE, "Error: MAX_BULLET_SPREAD_ANGLE cannot be less than MIN_BULLET_SPREAD_ANGLE!")
if (MAX_BULLET_SPREAD_ANGLE > 180) then
	warn("Warning: MAX_BULLET_SPREAD_ANGLE is over 180! This will not pose any extra angular randomization. The value has been changed to 180 as a result of this.")
	MAX_BULLET_SPREAD_ANGLE = 180
end

while wait() do
	if CanFire and CurrentShot < BURST_COUNT then
		for i, player in pairs(workspace.Players:GetChildren()) do
			if (player.Core.Position - script.Parent.Position).magnitude < BULLET_MAXDIST-BULLET_SPEED then
				print("shooting at: " .. player.Name)
				local Direction = (player.Core.CFrame.p - script.Parent.Position).Unit
				Fire(Direction)
				CurrentShot = CurrentShot+1
				wait(FIRE_DELAY)
			end
		end
	else
		CanFire = false
		CurrentShot = 0
		wait(BURST_DELAY)
		CanFire = true
	end
end

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

The following part of the code is faulty (the very end):

while wait() do
	...
end

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

Move the connections so that they are before the loop then it’ll work fine. It can’t connect because the code will never go anywhere after the loop.

1 Like

ok I ended up using this for some guns i made and it was super useful, so thanks 4 that. the only suggestion I can offer is remove the LengthChanged and RayHit events and replace them as parameters for when the :Fire method is called on a caster. I was using a single caster for all weapons but some weapons needed different things to happen when it hit something, so the best thing I could think of without modifying your code was inserting a value inside of the bullet object itself and checking what it was to do things such as explosions/different particle effects. Im not a fan of this approach but it worked. However, I think replacing the custom events could improve your module and make some things easier.

1 Like

I’m using FastCast but the ray is intersecting with CanCollide=false objects.

Is there a way to ignore objects with CanCollide=false? Or do I have to recast the ray manually?