fix: 3rd person and first person views

This commit is contained in:
DottsGit
2026-06-06 11:51:28 -04:00
parent 909b25548c
commit 3488b8e005
3 changed files with 124 additions and 11 deletions
+56
View File
@@ -167,6 +167,50 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
if is_crouching and current_state == "ground":
current_state = "crouch"
var is_holding_weapon: bool = false
func set_weapon(script_path: String) -> void:
# Clear existing weapons from root_pivot
for child in root_pivot.get_children():
if child.has_meta("is_third_person_weapon"):
child.queue_free()
if script_path == "":
is_holding_weapon = false
return
is_holding_weapon = true
var script = load(script_path)
if not script:
return
var w = script.new()
w.name = "ThirdPersonWeapon"
w.set_meta("is_third_person_weapon", true)
# Connect to ready so we can override the weapon's own _ready() calls
w.ready.connect(func():
w.set_process(false)
w.set_process_input(false)
if shadows_only:
_set_shadows_recursive(w)
# Force position after the weapon's _build_model() sets it for 1st person
# root_pivot is rotated 180 deg around Y, so local +Z is FORWARD and local -X is RIGHT
w.position = Vector3(-0.15, 1.0, 0.4)
w.rotation_degrees = Vector3(0, 180, 0)
)
# Attach to root_pivot so it stays steady and points perfectly forward
root_pivot.add_child(w)
func _set_shadows_recursive(node: Node) -> void:
if node is GeometryInstance3D:
node.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_SHADOWS_ONLY
for child in node.get_children():
_set_shadows_recursive(child)
func _process(delta: float) -> void:
var anim_speed = 1.0
if current_state == "ground" and movement_speed > 1.0:
@@ -320,6 +364,18 @@ func _process(delta: float) -> void:
t_thigh_r_rot.x = deg_to_rad(10)
t_calf_r_rot.x = 0.0
# Override right arm (and left) if holding a weapon
if is_holding_weapon and current_state != "death":
# Pose the right arm to look like it's holding the weapon handle
t_upper_arm_r_rot.x = -0.6
t_upper_arm_r_rot.z = 0.15
t_lower_arm_r_rot.x = -1.0
# Pose the left arm to look like it's holding the foregrip
t_upper_arm_l_rot.x = -0.5
t_upper_arm_l_rot.z = -0.15
t_lower_arm_l_rot.x = -1.1
var lerp_speed = 40.0 * delta
root_pivot.position = root_pivot.position.lerp(t_root_pos, lerp_speed)
root_pivot.rotation = _lerp_vec3(root_pivot.rotation, t_root_rot, lerp_speed)
+10 -6
View File
@@ -12,6 +12,7 @@ var camera: Camera3D
var muzzle_flash: OmniLight3D
var fire_sound: AudioStreamPlayer3D
var reload_sound: AudioStreamPlayer3D
var model_root: Node3D
func _ready() -> void:
set_process_input(true)
@@ -22,6 +23,9 @@ func _build_model() -> void:
# Simple FPS weapon placement (bottom right)
position = Vector3(0.3, -0.3, -0.6)
model_root = Node3D.new()
add_child(model_root)
# Stock
var stock = MeshInstance3D.new()
var stock_mesh = BoxMesh.new()
@@ -31,7 +35,7 @@ func _build_model() -> void:
stock_mesh.material = stock_mat
stock.mesh = stock_mesh
stock.position = Vector3(0, 0, 0.2)
add_child(stock)
model_root.add_child(stock)
# Left Barrel
var barrel_l = MeshInstance3D.new()
@@ -47,14 +51,14 @@ func _build_model() -> void:
barrel_l.mesh = barrel_mesh
barrel_l.rotation.x = deg_to_rad(90)
barrel_l.position = Vector3(-0.04, 0.03, -0.1)
add_child(barrel_l)
model_root.add_child(barrel_l)
# Right Barrel
var barrel_r = MeshInstance3D.new()
barrel_r.mesh = barrel_mesh
barrel_r.rotation.x = deg_to_rad(90)
barrel_r.position = Vector3(0.04, 0.03, -0.1)
add_child(barrel_r)
model_root.add_child(barrel_r)
# Muzzle Flash Light
muzzle_flash = OmniLight3D.new()
@@ -62,7 +66,7 @@ func _build_model() -> void:
muzzle_flash.light_energy = 0.0
muzzle_flash.omni_range = 4.0
muzzle_flash.position = Vector3(0, 0.03, -0.45)
add_child(muzzle_flash)
model_root.add_child(muzzle_flash)
# Audio Players
fire_sound = AudioStreamPlayer3D.new()
@@ -83,12 +87,12 @@ func _process(delta: float) -> void:
# Spin vertically (flip) over the duration of the reload
var spin_speed = (PI * 2.0) / reload_time
rotation.x += spin_speed * delta
model_root.rotation.x += spin_speed * delta
if reload_timer <= 0.0:
shells = 2
reloading = false
rotation.x = 0.0 # Snap back to perfectly level
model_root.rotation.x = 0.0 # Snap back to perfectly level
print("Shotgun Reloaded!")
func _input(event: InputEvent) -> void:
+57 -4
View File
@@ -55,7 +55,7 @@ func _setup_viewmodel_viewport() -> void:
dir_light.light_energy = 0.5
add_child(dir_light)
func _process(delta: float) -> void:
func _process(_delta: float) -> void:
if is_instance_valid(vm_camera) and is_instance_valid(camera):
vm_camera.global_transform = camera.global_transform
vm_camera.fov = camera.fov
@@ -97,12 +97,18 @@ func _spawn_weapon(slot: int, weapon_id: String) -> void:
weapons[slot] = w
w.visible = false
w.set_process_input(false)
w.set_meta("script_path", script_path)
_add_procedural_arms(w)
# Set weapons to viewmodel layer
_set_layer_recursive(w, 1 << 19)
func _set_layer_recursive(node: Node, layer_mask: int) -> void:
if node is VisualInstance3D:
if node is GeometryInstance3D:
node.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
if node is Light3D:
# Lights (e.g. muzzle flash) illuminate both world and viewmodel
node.layers = 1 | layer_mask
@@ -112,6 +118,50 @@ func _set_layer_recursive(node: Node, layer_mask: int) -> void:
for child in node.get_children():
_set_layer_recursive(child, layer_mask)
func _add_procedural_arms(weapon: Node3D) -> void:
# Attach to weapon instead of model_root to avoid arms spinning on reload
var mat = StandardMaterial3D.new()
mat.albedo_color = Color(0.2, 0.4, 0.8) # Player color
mat.roughness = 0.8
# Right Arm (Main Grip)
var r_arm = MeshInstance3D.new()
var r_mesh = BoxMesh.new()
r_mesh.size = Vector3(0.08, 0.7, 0.08)
r_mesh.material = mat
r_arm.mesh = r_mesh
var r_pivot = Node3D.new()
var r_shoulder = Vector3(0.25, -0.3, 0.5)
var r_hand = Vector3(0.04, -0.05, 0.05)
r_pivot.position = r_shoulder
r_pivot.look_at_from_position(r_shoulder, r_hand, Vector3.UP)
r_arm.rotation_degrees.x = 90 # Mesh points UP by default, we need it to point forward along -Z
r_arm.position.z = -0.35
r_pivot.add_child(r_arm)
weapon.add_child(r_pivot)
# Left Arm (Foregrip)
if "weapon_name" in weapon and weapon.weapon_name != "Knife":
var l_arm = MeshInstance3D.new()
var l_mesh = BoxMesh.new()
l_mesh.size = Vector3(0.08, 0.7, 0.08)
l_mesh.material = mat
l_arm.mesh = l_mesh
var l_pivot = Node3D.new()
var l_shoulder = Vector3(-0.25, -0.3, 0.4)
var l_hand = Vector3(-0.02, -0.02, -0.3)
l_pivot.position = l_shoulder
l_pivot.look_at_from_position(l_shoulder, l_hand, Vector3.UP)
l_arm.rotation_degrees.x = 90
l_arm.position.z = -0.35
l_pivot.add_child(l_arm)
weapon.add_child(l_pivot)
func _equip_slot(slot: int) -> void:
if weapons.has(active_slot):
var w = weapons[active_slot]
@@ -128,9 +178,12 @@ func _equip_slot(slot: int) -> void:
var w = weapons[active_slot]
w.visible = true
w.set_process_input(true)
print("Equipped slot ", slot)
else:
print("Slot ", slot, " is empty!")
# Sync 3rd person weapon
if player and player.has_node("HumanoidModel"):
var humanoid = player.get_node("HumanoidModel")
if humanoid.has_method("set_weapon") and w.has_meta("script_path"):
humanoid.set_weapon(w.get_meta("script_path"))
func _input(event: InputEvent) -> void:
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED: