Godot Version
4.4
Question
I implemented the projectile weapon mechanic, but I ran into a problem. Since in my case the projectile is spawned at the GunPoint (marker3d, the end of the barrel) and simply flies straight forward — if you stand close enough to a wall, the projectile will simply spawn inside it and continue flying, which I would like to avoid by deleting the player from existence, because he blew himself away
(I mean spawn a projectile that would immediately hit the wall). What is usually done in such situations?
Please don’t suggest spawning the projectile from the “eyes” or moving the GunPoint to the start of the gun… In my opinion, that’s not a solution and looks very janky.
ProjectileWeapon:
class_name ProjectileWeapon extends BaseWeapon
@export_group("Projectile Settings")
@export var projectile_velocity : int
@export var projectile_lifetime : float
@export_group("Projectile Model")
@export var projectile_model : PackedScene
func perform_attack(_controller: WeaponController, _bullets: int) -> void:
_controller._spawn_projectile(_bullets)
_spawn_projectile:
func _spawn_projectile(bullets : int) -> void:
if not current_weapon.projectile_model:
push_error("Projectile model not found!")
return
if not camera:
push_error("Camera not found!")
return
var gun_point = current_weapon_model.get_node("GunPoint")
var camera_quaternion = camera.global_transform.basis.get_rotation_quaternion()
for i in range(bullets):
var projectile = current_weapon.projectile_model.instantiate() as Projectile
get_tree().current_scene.add_child(projectile)
projectile.global_position = gun_point.global_position
var rand_yaw = deg_to_rad(randf_range(-current_weapon.accuracy, current_weapon.accuracy))
var rand_pitch = deg_to_rad(randf_range(-current_weapon.accuracy, current_weapon.accuracy))
var spread_local_quaternion = Quaternion.from_euler(Vector3(rand_pitch, rand_yaw, 0))
var total_quaternion = camera_quaternion * spread_local_quaternion
var forward = total_quaternion * (Vector3.FORWARD)
var velocity = forward * current_weapon.projectile_velocity
projectile.look_at(projectile.global_position + forward, Vector3.UP)
projectile.setup(velocity, current_weapon.damage, current_weapon.projectile_lifetime)
Projectile:
class_name Projectile extends Area3D
var velocity : Vector3
var damage : float
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _physics_process(delta: float) -> void:
velocity.y -= gravity * delta
var space_state = get_world_3d().direct_space_state
var start_pos = global_position
var end_pos = global_position + velocity * delta
var query = PhysicsRayQueryParameters3D.create(start_pos, end_pos)
query.collision_mask = 1
var result = space_state.intersect_ray(query)
if result:
global_position = result.position
_on_body_entered(result.collider)
return
global_position = end_pos
func setup(vel: Vector3, dmg: float, time: float):
velocity = vel
damage = dmg
get_tree().create_timer(time).timeout.connect(queue_free)
func _on_body_entered(body: Node3D) -> void:
print("Projectile hit: ", body.name, " at ", global_position)
DebugDraw3D.draw_sphere(global_position, 0.1, Color(1, 0, 0), 3)
var health_component = body.get_node_or_null("HealthComponent")
if health_component and health_component.has_method("take_damage"):
health_component.take_damage(damage, owner)
queue_free()
Gun model (example):
The gun model is attached to the camera (weapon_model_parent is Camera3D):
func spawn_weapon_model():
if current_weapon.weapon_model:
current_weapon_model = current_weapon.weapon_model.instantiate()
weapon_model_parent.add_child(current_weapon_model)
current_weapon_model.position = current_weapon.weapon_position
current_weapon_model.rotation_degrees = current_weapon.weapon_rotation
current_weapon_model.scale = current_weapon.weapon_scale
weapon_effects.apply_clip_and_fov_shader_to_model(current_weapon_model)
Technically the whole situation is looking like this:

