feat: implement foundational weapon systems, including hitscan and projectile base classes, reload mechanics, and player movement/camera integration.

This commit is contained in:
DottsGit
2026-06-09 22:33:40 -04:00
parent 5102b770c8
commit f63187e493
7 changed files with 116 additions and 15 deletions
+15 -7
View File
@@ -11,6 +11,7 @@ class_name BaseHitscanWeapon
@export var max_range: float = 100.0
@export var automatic: bool = true
@export var spread_angle: float = 0.01
@export var recoil_amplitude: float = 0.05
@export var penetration_count: int = 0
@export var penetration_damage_penalty: float = 0.5
@@ -46,15 +47,19 @@ func _process(delta: float) -> void:
fire_cooldown -= delta
if reloading:
reload_timer -= delta
var spin_speed = (PI * 2.0) / reload_time
var spin_speed = PI * 4.0
model_root.rotation.x += spin_speed * delta
if reload_timer <= 0.0:
current_ammo = max_ammo
reloading = false
model_root.rotation.x = 0.0
print(weapon_name, " Reloaded!")
if reload_timer > 0.0:
reload_timer -= delta
else:
# Continue the loop until facing normal direction (multiples of 2 PI)
var target_rot = ceil(model_root.rotation.x / (PI * 2.0)) * (PI * 2.0)
if target_rot - model_root.rotation.x < spin_speed * delta * 1.5:
current_ammo = max_ammo
reloading = false
model_root.rotation.x = 0.0
print(weapon_name, " Reloaded!")
elif automatic and is_firing and fire_cooldown <= 0.0 and current_ammo > 0:
_fire()
@@ -93,6 +98,9 @@ func _fire() -> void:
_shoot_hitscan()
_play_muzzle_flash()
if get_parent() and get_parent().has_method("add_recoil"):
get_parent().add_recoil(recoil_amplitude, randf_range(-recoil_amplitude * 0.5, recoil_amplitude * 0.5))
if fire_sound:
# Slight pitch variation
fire_sound.pitch_scale = 1.0 + randf_range(-0.05, 0.05)
+15 -7
View File
@@ -11,6 +11,7 @@ class_name BaseProjectileWeapon
@export var max_range: float = 200.0
@export var automatic: bool = true
@export var spread_angle: float = 0.01
@export var recoil_amplitude: float = 0.05
@export var projectile_speed: float = 50.0
@export var projectile_gravity: float = 0.0
@@ -45,15 +46,19 @@ func _process(delta: float) -> void:
fire_cooldown -= delta
if reloading:
reload_timer -= delta
var spin_speed = (PI * 2.0) / reload_time
var spin_speed = PI * 4.0
model_root.rotation.x += spin_speed * delta
if reload_timer <= 0.0:
current_ammo = max_ammo
reloading = false
model_root.rotation.x = 0.0
print(weapon_name, " Reloaded!")
if reload_timer > 0.0:
reload_timer -= delta
else:
# Continue the loop until facing normal direction (multiples of 2 PI)
var target_rot = ceil(model_root.rotation.x / (PI * 2.0)) * (PI * 2.0)
if target_rot - model_root.rotation.x < spin_speed * delta * 1.5:
current_ammo = max_ammo
reloading = false
model_root.rotation.x = 0.0
print(weapon_name, " Reloaded!")
elif automatic and is_firing and fire_cooldown <= 0.0 and current_ammo > 0:
_fire()
@@ -92,6 +97,9 @@ func _fire() -> void:
_shoot_projectile()
_play_muzzle_flash()
if get_parent() and get_parent().has_method("add_recoil"):
get_parent().add_recoil(recoil_amplitude, randf_range(-recoil_amplitude * 0.5, recoil_amplitude * 0.5))
if fire_sound:
fire_sound.pitch_scale = 1.0 + randf_range(-0.05, 0.05)
fire_sound.play()
+43
View File
@@ -55,6 +55,14 @@ func _setup_viewmodel_viewport() -> void:
dir_light.light_energy = 0.5
add_child(dir_light)
var _bob_timer: float = 0.0
var _recoil_pitch: float = 0.0
var _recoil_yaw: float = 0.0
func add_recoil(pitch: float, yaw: float) -> void:
_recoil_pitch += pitch * 20.0 # Scale up for visual model recoil
_recoil_yaw += yaw * 20.0
func _process(_delta: float) -> void:
if is_instance_valid(vm_camera) and is_instance_valid(camera):
vm_camera.global_transform = camera.global_transform
@@ -68,6 +76,41 @@ func _process(_delta: float) -> void:
else:
var over_speed_factor = clampf((hspeed - 11.0) / 19.0, 0.0, 1.0) # maxes out at 30 m/s
target_fov = lerpf(72.0, 80.0, over_speed_factor)
# Weapon Viewmodel Bobbing and Drift
if player.is_on_floor() and hspeed > 1.0:
# Scale bobbing frequency by walk speed (assuming base walk speed is ~10-11)
_bob_timer += _delta * 12.0 * (hspeed / 11.0)
var bob_y = sin(_bob_timer) * 0.015
var bob_x = cos(_bob_timer * 0.5) * 0.01
vm_camera.translate_object_local(Vector3(bob_x, bob_y, 0))
else:
_bob_timer = 0.0
if not player.is_on_floor():
# Airborne Drift
# Convert player velocity into camera's local space
var local_vel = camera.global_transform.basis.inverse() * player.velocity
# We want the weapon to lag behind movement.
# If player moves left (-x), weapon should drift right (+x relative to camera, which means vm_camera shifts left -x to make weapon appear right)
# Wait, if vm_camera shifts left, the weapon (which is rendered by it) appears to shift RIGHT on the screen!
# So vm_camera shifts in the SAME direction as local velocity.
var drift_offset = Vector3(local_vel.x * 0.01, local_vel.y * 0.01, 0)
# Clamp the drift
drift_offset.x = clampf(drift_offset.x, -0.1, 0.1)
drift_offset.y = clampf(drift_offset.y, -0.1, 0.1)
vm_camera.translate_object_local(drift_offset)
# Slight rotation tilt as well
vm_camera.rotate_object_local(Vector3.UP, deg_to_rad(-local_vel.x * 0.5))
vm_camera.rotate_object_local(Vector3.RIGHT, deg_to_rad(local_vel.y * 0.5))
# Visual Recoil
_recoil_pitch = lerpf(_recoil_pitch, 0.0, 15.0 * _delta)
_recoil_yaw = lerpf(_recoil_yaw, 0.0, 15.0 * _delta)
vm_camera.rotate_object_local(Vector3.RIGHT, deg_to_rad(_recoil_pitch))
vm_camera.rotate_object_local(Vector3.UP, deg_to_rad(_recoil_yaw))
# Add a slight backward kick along the local Z axis
vm_camera.translate_object_local(Vector3(0, 0, _recoil_pitch * 0.01))
vm_camera.fov = lerpf(vm_camera.fov, target_fov, 10.0 * _delta)