Problem with projectile weapons

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 :smiley: (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:

Couldn’t you just attach a tiny area to the marker and use overlapping_bodies() to decide what to do?

Like if no body just run the normal behaviour, but if body branch out depending on the type of body.

1 Like

Sounds like something useful, I’ll definitely try it, thx

You could place a raycast on the gun spanning from one end to the other. If it collides with a wall, don’t shoot.

1 Like