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

Yeah the name might’ve misleaded me previously, although now I realize that the functionality of the function can be pretty powerful. Can’t wait to see how far I can push it.

If I set the CosmeticBulletObject after firing, will the FastCast still be creating a bullet from the template first and then replacing it with mine? If that’s the case, I feel like incorporating the PartCache module now may actually be slower than not having it at all. What I mean by this:

No PartCache module:

-- Caster creates new instance from the template
local cast = Caster:Fire(...)

With PartCache module:

-- Caster creates new instance from the template
-- Get a new CosmeticBulletObject from the PartCache module and put it into the RayInfo
-- Potentially some internal thing goes on inside FastCast that discards the old CosmeticBulletObject
local cast = Caster:Fire(...)
cast.RayInfo.CosmeticBulletObject = BulletCache:GetPart()

As you can see, even with the PartCache incorporated, the Caster may still be creating a new instance when its fired, which is slow and defeats the purpose of the PartCache (which is to avoid creating new instances). In my game, I plan to have a lot of players firing guns very rapidly, so keeping the server as fast as possible is crucial. Any insight on if my thinking here is right or wrong would be super appreciated! Also thanks for the insight about the terrain issue, I don’t think I’ve heard of that issue before.

Which is why you set the template to nil if you are using this method. If you don’t do that, you’ll actually leave a stray clone part just laying around in the parent for the cosmetic bullets because you overwrote its reference with your own.

Effectively what you’d be doing with this manual change is exactly what it seems like - manually controlling the cosmetic bullet that the module keeps track of rather than allowing it to create it automatically.

On a bit of a side note, I may write in support for PartCache natively anyway. I was working on another tiny utility I’m internally calling TypeMarshaller (it might have a different name on public release) which allows objects to return custom type names in typeof via providing a custom overwrite of the typeof function, and I can use this to determine whether or not the referenced module is an instance of PartCache.

FastCast v13.1.0 Released.

I’ve addressed the ever-popular request to incorporate PartCache into the module. It is now able to interface with PartCache (it doesn’t actually include it, it can just use it). See FastCastBehavior.CosmeticBulletProvider for more information.

Note: To use this, you need to upgrade PartCache. That, or add PartCache.__type = "PartCache" to the top of the module where the table is defined.

4 Likes

Ohhh so that’s why that was happening to me. I overrode the old CosmeticBulletObject anyway despite my fears and didn’t think of setting the template to nil. Also, that TypeMarshaller utility sounds OP. I’ve been experimenting with the new Luau type checking beta recently so I’ll be checking that function out once I get to developing today :eyes:

On a side note, this is awesome! I hope I didn’t pressure you too much into doing this haha. This update is super appreciated though and I’m gonna get right to updating my modules. Thank you for all the help!

1 Like

Hey, sorry to bother you here, but I’ve been having problems with actually firing a projectile. There’s a good chance I’m doing it wrong but it always ends up looking like this. Here’s the code:

local repStorage = game:GetService("ReplicatedStorage")
local attackRemote = repStorage.Remotes.AttackRemote

local deb = {}
local cooldowns = {
	PUNCH_COOLDOWN = 3;
	PSYCHIC_COOLDOWN = 1;
}

local fastCast = require(script.FastCastRedux)
local caster = fastCast.new()

local params = RaycastParams.new()
local castVisual = Instance.new("Part")
castVisual.Color = Color3.fromRGB(99, 45, 132)
castVisual.Size = Vector3.new(1,1,1)
local behavior = {
	Ranged = function(plr, weapon)
		local a = deb[plr] and print(os.clock() - deb[plr])
		if deb[plr] and os.clock() - deb[plr] < cooldowns[weapon:upper().."_COOLDOWN"] then return end
		deb[plr] = os.clock()

		local castBehavior = fastCast.newBehavior()
		params.FilterDescendantsInstances = {plr.Character}
		params.FilterType = Enum.RaycastFilterType.Blacklist
		castBehavior.RaycastParams = params
		castBehavior.MaxDistance = math.huge
		castBehavior.Acceleration = Vector3.new(0, -workspace.Gravity, 0)
		
		castBehavior.CosmeticBulletTemplate = castVisual
		castBehavior.CosmeticBulletContainer = workspace.visual
		
		local char = plr.Character or plr.CharacterAdded:Wait()
		local hum = char:WaitForChild("Humanoid")
		local headFacePosition = plr.Character.Head.CFrame * Vector3.new(0,0,-(plr.Character.Head.Size.Z/2 + 2))
		
		caster:Fire(headFacePosition, plr.Character.Head.CFrame.LookVector * 99999, 99, castBehavior)
		attackRemote:FireClient(plr, weapon)
		print("can punch againg")
	end;
	Melee = function(plr)

	end
}
attackRemote.OnServerEvent:Connect(function(plr, weapon)
	behavior["Ranged"](plr, weapon)
end)
1 Like

Do you connect to caster.LengthChanged anywhere? You need to use this function to CFrame your cosmetic bullet (refer to the example gun). Also, anchor your cosmetic bullet.

3 Likes

You can sync them to nearly 100% accuracy.

When you shoot on client, the server can offset the bullet based on your ping. I did this by manipulating some of the variables when fast cast starts. The issue with this is, close range shots will go through walls, but to fix that I added a raycast and its functioning fine now, although my syntax is pretty crappy.You can sync them to nearly 100% accuracy.

When you shoot on client, the server can offset the bullet based on your ping. I did this by manipulating some of the variables when fast cast starts. The issue with this is, close range shots will go through walls, but to fix that I added a raycast and its functioning fine now, although my syntax is pretty crappy.

2 Likes

Thanks for telling me that lol. I had a feeling I needed to look for something about CFrame or some kinda movement but ig I missed that hehe.

2 Likes

Quick follow-up question though, is it bad to create multiple part cache objects at once? Common sense should obviously say yes but are the effects severe?

1 Like

Quick question, does this work irregular projectiles like say, a fireball which is a circle? or is it only useful for guns?

1 Like

if you still didnt fix the problem DM me on discord and I’ll see if i can help you TheFortex#3192

1 Like

If you need different part pools then yeah go right ahead and make multiple. Just don’t make a new one every time the gun is equipped or whatever.

3 Likes

It would work. You just need to modify the script slightly

1 Like

Hey this is a question about the example gun I can’t check the source right now but are you creating a new RayCastParams every fire, or reusing one? The problem I see with reusing is bullet penetration, when something is hit you wanna add it to the ignore list. But if you add to the ignore-list it will affect all the bullets because it’s the same table reference. (or is raycastParams.FilterIgnoreDecednats doing something else)?

So are you creating a raycastparams each shot or reusing?

1 Like

Yah but it wouldn’t work that good with big projectiles since rays are only 1 stud thick. To combat this you can fire a ray on the edges of the part to. Now it won’t be very accurate, for more accuracy you need to fire more rays spread apart in the part. Idk if fastcast does this or not.

2 Likes

Every shot duplicates the input RaycastParams instance for the exact problem you mentioned above.

This code is visible in `ActiveCast`
local function CloneCastParams(params: RaycastParams): RaycastParams
	local clone = RaycastParams.new()
	clone.CollisionGroup = params.CollisionGroup
	clone.FilterType = params.FilterType
	clone.FilterDescendantsInstances = params.FilterDescendantsInstances
	clone.IgnoreWater = params.IgnoreWater
	return clone
end

Unfortunately, this methodology does violate Roblox’s proposed standards of using a singleton instance, but in the scope of module functionality vs. usage of cast parameters, this is the better choice.

It does not, FC has been limited to line segments for its entire lifetime. I’ve been investigating ways to reliably do this, but it doesn’t seem to be possible right away, at least not without critical performance losses. I’ve looked into a lot of methods from using an Explosion instance (or line of them) with a diameter of the segment’s length (effectively it makes a sphere encompassing a given segment), casting a number of rays out from each segment, and a couple more radius-based detection methods. All methods have proven to be slow/expensive, unreliable, or problematic in other contexts (unwanted interference with the developer’s game, specifically).

I may experiment with more methods, but I want to add resolution enforcement before anything else.

1 Like

A bit of a status update for you all, I’ve been working on v13.2.0 which adds the ability to enforce higher resolution (shorter segments) for physics casts to help with accurate simulation.

Right now the release plan is this. Offer 3 modes:

  1. Default - FastCast will behave as it normally does, and use a segment length based on delta time.
  2. OnlyWhenHit - Similar to Default, but if a segment registers a hit, it will recalculate that hit to see if it really should have hit. This has a drawback that may make it undesirable for extreme precision scenarios (click to see) .
  3. Always - FastCast will always enforce that the segment length is as close to a property named HighFidelitySegmentSize, which determines the goal segment size.

The API will be updated when this change is formally released, but ideally this should give you all expectations on what you’re going to see.

I anticipate this release will be buggy.

1 Like

Eti, I was just experimenting with the example gun for fastcast. For some reason, when I shoot the gun, it automatically goes in an arc. I looked back at the script and saw that gravity was already set to zero. I also tried turning pierce off, but it still shot in an arc. (Except without bouncing) The example gun was working fine and shot in a straight path before these 2 recent updates. Can you look into this?

2 Likes

Depends are your projectiles only static paths or things which only need the starting info to be synchronized after with the same logic? Remember this anything which starts with the same info with the safe logic will reach to the same destination. So richoets and things would work if the richoet amount isn’t randomized and based on a something which both the client and server will have the same . However for things like homing missles it wouldn’t be really possible to do this, over time the client version will become really inaccurate from the server missle.

Also keep in mind the server missile is delayed because of the amount of time it takes a remote to be recieved, so the world could have been updated by then. For example a zombie could have moved 20 studs, but the client projectile made before and could have hit it. The server projectile won’t hit do causing no damage.

if your projectiles are dynamic like homing missiles it becomes complicated. First of all we need to take out the part from the server to reduce the strain and create them on the clients, the server just has to have a data projectile which stores the properties and position of the projectile. OK then, when someone makes a projectile we need to send every client including the shooter to create a part with a special ID. We also need to do the same thing on the server (storing the projectile in an array). OK, then we add it to an array on the client like this.

local MyBullets = {}

OnEvent = function(projectileStarting, ID)    MyBullets[ID] = {Position = projectileStarting, Model = game.replicatedStorage.Bullet:Clone()} end

Ok then every heartbeat send the array of bullets to all the clients. Tell them to update there bullet by looping through the table and moving the coressponding projectile with the ID. For this use tweenservice, or some similar method.

Ok then when the projectile is destoryed send a destoryed event to the client telling them to remove their projectile from the array and destory the model.

However it’s likely that you don’t need something this extensive. Also some optimizing stuff you can add, set the tranpsarency to 1 of the model projectile if it’s not on the screen using Camera:WorldToScreenPoint on the client. Also, compressing the table when sent to the client, and decompressing it on the client to save bandwith.

1 Like

Could you explain how this is done? Like are you yielding less then one frame ect.

2 Likes