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

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

I dont know if your module allows it yet but you could have attachments act as the start position for the rays you do and have multiple of them i guess? much like how swordphin’s does it for his hitbox module i guess Raycast Hitbox 4.01: For all your melee needs! he has these dmg points he generate rays from (that can potentially solve hitboxes for wide projectiles)

1 Like

Sort of. When it runs for the frame, instead of just incrementing forward by the frame’s delta time, it figures out how much it would increment forward. Using that information, it then attempts to split that length into a number of parts with the specified length (via the highest number of whole segments possible, e.g. if I have a distance of 11 and my segment size is 2, the amount of segments will still be 5). After doing this, it upscales the segments so that they fit the full length the closest (2.2 studs/seg in the given example) and then does a for loop, simulating each sub-segment individually.

The result is higher fidelity without sacrificing any frame time or anything like that. This can unfortunately be expensive though (if there’s a lot of segments) so it’s pretty easy to cause a lot of lag by using this.

I addressed this several times in my other posts. The short answer is “No, that’s not how I want to do it.” because it’s inaccurate. I’ll have a solution that works well in time.

1 Like

FastCast is really cool. Big props to the creators.

1 Like

This can be done in a loop using coroutine.wrap.

Hey also I kinda figured out an issue I think with the client replication setup you showed in the video. If the server was laggy it’d do this.


https://gyazo.com/365b41ac36a988163e0420ad76684063

Basically the server might be laggy so maybe after 1 frame it’s position should be all the way there. Since your module raycasts to the next position and the projectile was supposted to be all the way at the endpoint based on the formula it would just raycast there. since the segments weren’t split into smaller segments it would just raycast to the endpoint skipping all the physcis. (When we include gravity)

Now the client must not have been laggy, so it would cover the full arc. Since the server projectile is doing hitdetection if the client hit in top of the arc it wouldn’t register because the server only did 1 raycast because of this lagginess.

Was this what the new update was supposted to solve, or was this already compensated in an older update?

2 Likes

This was already compensated for in the first release of the module itself.

This change helps with incorrect hit registry for physics casts (see the image – https://imgur.com/a/O80rc0D). It also makes certain operations like reflection a bit more accurate since the normal of the bullet is more accurate.

1 Like

I mean that’s good but that’s not really the problem Im talking about. That only happens when the projectile is hit. What I mean is the projectile on the client has a super good curve and hits but on the server it’s lagging so hit detection is not registered because the server raycasts to the next point and doesn’t hit anything making it not do the extra high resolution check.


https://gyazo.com/7d3f15bc8e2209b6f03efb350d50b73f

This problem. basically a client has a smooth projectile it hits the target, but the server has a non smooth projectile so the server projectile doesn’t damage the avatar, since the compensation is only for when something hits.

That’s why one of the ideas I had for replication was the server constantly telling the clients where to put their local projectile every two hearbeats so the clients actually never desync they’re local projectile. The client’s will lerp/tween to this position. Useful for homing missles where over time the server and the client will dsync.

What would be the best way to pass information in a bullet using PartCache, because I’m not entirely sure of how to grab an individual bullet before it is even fired out of the gun, making adding data to it impossible. If there is a good method to doing so, I would appreciate some help. I had done this prior without part cache, but I’m not sure how to do it with it now.

Basically I just want to have a folder with data like owner of bullet, the bullet the gun came from, ect

I’m trying to use FastCast but with a knife in place of the blue bullet in the gun example. I can’t get the knife to spin though. This is what I put in OnRayUpdated():

cosmeticBulletObject.CFrame = cosmeticBulletObject.CFrame * 
CFrame.fromEulerAnglesXYZ(1.3,1.3,1.3)

Can you help me figure out what’s wrong?

2 Likes

Haven’t fully used this, but by the looks of it and reading I’m pretty sure you’re supposed to be connecting to Caster.LengthChanged, which should fire every frame because the rays are updated every frame, iirc. Try that instead of OnRayUpdated

That or, maybe make sure you’re referencing the actual knife projectile, and not anything else.

1 Like

You shouldn’t do hit detection on both client and server, so this shouldn’t actually be a problem.

Generally speaking, the setup is that the server does the hit detection (in which case you would enable high fidelity casts), and the client does the rendering. Recalculated casts aren’t actually visible (not only does LengthChanged not fire for a recalculation, but it also is doing multiple segments within a single frame - even if it did, it wouldn’t even be visible). In this context, the client should listen to an event from the server to know when to terminate (server fires an event, client receives that event, calls cast:Terminate())

If you need to do hit detection on both client and server for some gameplay model you’re doing, then the best option is to always enforce a specific cast length.

Using the UserData table of an ActiveCast should work in your case despite this problem. If you immediately set it up after firing the cast, then there’s no chance for any desync or anything caused by data not existing when one of the Caster’s events fire.

You’ll need to keep track of a rotation value. I recommend using the UserData table to store this.

Here’s the basic idea. In the fire function, find the line that’s calling :Fire() on the caster, and add a new value to its UserData table.

local cast = Caster:Fire(...)
cast.UserData.Spin = 0

And then in the LengthChanged event handler (OnRayUpdated) do this:

function OnRayUpdated(cast, segmentOrigin, segmentDirection, length, segmentVelocity, cosmeticBulletObject)
	if cosmeticBulletObject == nil then return end
	local baseCFrame = CFrame.new(segmentOrigin, segmentOrigin + segmentDirection)
	cosmeticBulletObject.CFrame = baseCFrame * CFrame.fromOrientation(cast.UserData.Spin, 0, 0)
    cast.UserData.Spin += math.rad(30) -- Some number here to determine the speed.
end

That should work for you ideally. If you want a spin rate (so that lag doesn’t make it seem like it spins slower) then there’s a slightly more complicated method that could be done where you base the amount of rotation on how far the knife traveled in that single frame (via using the length parameter).

1 Like

Thanks! Got it to work.

I’m sure you’ve heard this a lot, but thank you so much for this module. You’ve made my life a lot easier. Also props to you for being so active and answering everyone’s questions. Keep up the great work!

2 Likes

Though unlikely somebody would have a gun such as your test weapon, if you shoot in the very wedge/corner of a part your bullets can no-clip through.
Ex: https://i.gyazo.com/1af2be062ef16d23316fe792f2d54ec4.mp4

There’s most likely a really small gap there. Unfortunately there’s no way to set the thickness of a raycast, only its length. So since the raycast is insanely thin, it’s probably not detecting a hit because it’s smaller than the gap, but the bullet, which is just the visualization of the raycast (that has size) appears to be hitting something. But, the bullet’s not doing any hit detection, the raycast is.

Or, it could just be a Roblox problem.

1 Like

no it’s a legitimate glitch
if you make the parts clip through each other and you aim at the seam you can slip projectiles through…

1 Like