Hello again. In FPS there are two methods of firing: hitscan and projectiles.
hitscan is the simplest, you put a raycast on the player and whatever it hits when pressing the fire button will take damage, then you spawn a decal of a bullet hole and particles.
the second depends on physics, it can be used for rocket launchers, or to create realistic weapon firing, but it's only worth at long distances or for things like rocket launchers and blasters.
there is virtually no difference between the two, hitscan is better for games that need to run on low end devices, require fast accurate response, or for games where you don't care about bullet drop and delay.
projectiles I would only use whenever needed (rocket launcher) or if the game is trying to be some kind of simulator like Arma3.
projectile is much more difficult. you can use a physics object, but too fast and it will always pass through walls. An alternative I kind of invented is to shoot a raycast. the raycast has a range equal to the distance the bullet has to move the next frame (it runs in the physics_process which is locked to 60 frames), then the bullet teleports to the next position (I think it was basis * speed + global_position
). if the raycast hits, just do the same as with hitscan: particle effects, bullet hole decal, damage.
So, the first question is, which one is it? are you making an arcade or a simulation game?
@onready var ray = $RayCast3D
func _process(delta):
...
if ray.is_colliding():
mesh.visible = false
await get_tree().create_timer(1.0).timeout
queue_free()
first there's no need for await. second, you are running this in process
, which is tied to your game's framerate. it should run on physics_process
which is locked at 60 FPS.
I should probably post some of the code from my FPS, here, you will have to figure a few things out though. This is C_Projectile:
extends RayCast3D
@onready var assetmang : Asset_manager = get_node("/root/AssetManager")#ignore this
var tim : float = 0.0
@export var lifetime : float = 10.0
@export var weaponPower : float = 0.2#also ignore this
func _physics_process(delta):
#projectile self destructs after lifetime ends
if tim < lifetime:
tim += delta
else:
selfDestruct()
#projectile hits something
if self.is_colliding():
SpawnFX(self.get_collision_point()) #here you spawn particles, decals, etc
selfDestruct()
#projectile DOESN'T hit something
else:
self.translate_object_local(self.target_position * delta) #move the projectile
func selfDestruct():
queue_free()
target position of the raycast is then the distance the projectile must travel in a frame. at 60 FPS, a muzzle velocity of, say 600 m/s, would be a target_position of, say, Vector3(0.0, 0.0, -10.0)
.