fix: 3rd person and first person views
This commit is contained in:
@@ -167,6 +167,50 @@ func update_state(state: String, speed: float, is_crouching: bool = false) -> vo
|
|||||||
if is_crouching and current_state == "ground":
|
if is_crouching and current_state == "ground":
|
||||||
current_state = "crouch"
|
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:
|
func _process(delta: float) -> void:
|
||||||
var anim_speed = 1.0
|
var anim_speed = 1.0
|
||||||
if current_state == "ground" and movement_speed > 1.0:
|
if current_state == "ground" and movement_speed > 1.0:
|
||||||
@@ -319,6 +363,18 @@ func _process(delta: float) -> void:
|
|||||||
t_calf_l_rot.x = 0.0
|
t_calf_l_rot.x = 0.0
|
||||||
t_thigh_r_rot.x = deg_to_rad(10)
|
t_thigh_r_rot.x = deg_to_rad(10)
|
||||||
t_calf_r_rot.x = 0.0
|
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
|
var lerp_speed = 40.0 * delta
|
||||||
root_pivot.position = root_pivot.position.lerp(t_root_pos, lerp_speed)
|
root_pivot.position = root_pivot.position.lerp(t_root_pos, lerp_speed)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ var camera: Camera3D
|
|||||||
var muzzle_flash: OmniLight3D
|
var muzzle_flash: OmniLight3D
|
||||||
var fire_sound: AudioStreamPlayer3D
|
var fire_sound: AudioStreamPlayer3D
|
||||||
var reload_sound: AudioStreamPlayer3D
|
var reload_sound: AudioStreamPlayer3D
|
||||||
|
var model_root: Node3D
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
set_process_input(true)
|
set_process_input(true)
|
||||||
@@ -22,6 +23,9 @@ func _build_model() -> void:
|
|||||||
# Simple FPS weapon placement (bottom right)
|
# Simple FPS weapon placement (bottom right)
|
||||||
position = Vector3(0.3, -0.3, -0.6)
|
position = Vector3(0.3, -0.3, -0.6)
|
||||||
|
|
||||||
|
model_root = Node3D.new()
|
||||||
|
add_child(model_root)
|
||||||
|
|
||||||
# Stock
|
# Stock
|
||||||
var stock = MeshInstance3D.new()
|
var stock = MeshInstance3D.new()
|
||||||
var stock_mesh = BoxMesh.new()
|
var stock_mesh = BoxMesh.new()
|
||||||
@@ -31,7 +35,7 @@ func _build_model() -> void:
|
|||||||
stock_mesh.material = stock_mat
|
stock_mesh.material = stock_mat
|
||||||
stock.mesh = stock_mesh
|
stock.mesh = stock_mesh
|
||||||
stock.position = Vector3(0, 0, 0.2)
|
stock.position = Vector3(0, 0, 0.2)
|
||||||
add_child(stock)
|
model_root.add_child(stock)
|
||||||
|
|
||||||
# Left Barrel
|
# Left Barrel
|
||||||
var barrel_l = MeshInstance3D.new()
|
var barrel_l = MeshInstance3D.new()
|
||||||
@@ -47,14 +51,14 @@ func _build_model() -> void:
|
|||||||
barrel_l.mesh = barrel_mesh
|
barrel_l.mesh = barrel_mesh
|
||||||
barrel_l.rotation.x = deg_to_rad(90)
|
barrel_l.rotation.x = deg_to_rad(90)
|
||||||
barrel_l.position = Vector3(-0.04, 0.03, -0.1)
|
barrel_l.position = Vector3(-0.04, 0.03, -0.1)
|
||||||
add_child(barrel_l)
|
model_root.add_child(barrel_l)
|
||||||
|
|
||||||
# Right Barrel
|
# Right Barrel
|
||||||
var barrel_r = MeshInstance3D.new()
|
var barrel_r = MeshInstance3D.new()
|
||||||
barrel_r.mesh = barrel_mesh
|
barrel_r.mesh = barrel_mesh
|
||||||
barrel_r.rotation.x = deg_to_rad(90)
|
barrel_r.rotation.x = deg_to_rad(90)
|
||||||
barrel_r.position = Vector3(0.04, 0.03, -0.1)
|
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 Light
|
||||||
muzzle_flash = OmniLight3D.new()
|
muzzle_flash = OmniLight3D.new()
|
||||||
@@ -62,7 +66,7 @@ func _build_model() -> void:
|
|||||||
muzzle_flash.light_energy = 0.0
|
muzzle_flash.light_energy = 0.0
|
||||||
muzzle_flash.omni_range = 4.0
|
muzzle_flash.omni_range = 4.0
|
||||||
muzzle_flash.position = Vector3(0, 0.03, -0.45)
|
muzzle_flash.position = Vector3(0, 0.03, -0.45)
|
||||||
add_child(muzzle_flash)
|
model_root.add_child(muzzle_flash)
|
||||||
|
|
||||||
# Audio Players
|
# Audio Players
|
||||||
fire_sound = AudioStreamPlayer3D.new()
|
fire_sound = AudioStreamPlayer3D.new()
|
||||||
@@ -83,12 +87,12 @@ func _process(delta: float) -> void:
|
|||||||
|
|
||||||
# Spin vertically (flip) over the duration of the reload
|
# Spin vertically (flip) over the duration of the reload
|
||||||
var spin_speed = (PI * 2.0) / reload_time
|
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:
|
if reload_timer <= 0.0:
|
||||||
shells = 2
|
shells = 2
|
||||||
reloading = false
|
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!")
|
print("Shotgun Reloaded!")
|
||||||
|
|
||||||
func _input(event: InputEvent) -> void:
|
func _input(event: InputEvent) -> void:
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func _setup_viewmodel_viewport() -> void:
|
|||||||
dir_light.light_energy = 0.5
|
dir_light.light_energy = 0.5
|
||||||
add_child(dir_light)
|
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):
|
if is_instance_valid(vm_camera) and is_instance_valid(camera):
|
||||||
vm_camera.global_transform = camera.global_transform
|
vm_camera.global_transform = camera.global_transform
|
||||||
vm_camera.fov = camera.fov
|
vm_camera.fov = camera.fov
|
||||||
@@ -97,12 +97,18 @@ func _spawn_weapon(slot: int, weapon_id: String) -> void:
|
|||||||
weapons[slot] = w
|
weapons[slot] = w
|
||||||
w.visible = false
|
w.visible = false
|
||||||
w.set_process_input(false)
|
w.set_process_input(false)
|
||||||
|
w.set_meta("script_path", script_path)
|
||||||
|
|
||||||
|
_add_procedural_arms(w)
|
||||||
|
|
||||||
# Set weapons to viewmodel layer
|
# Set weapons to viewmodel layer
|
||||||
_set_layer_recursive(w, 1 << 19)
|
_set_layer_recursive(w, 1 << 19)
|
||||||
|
|
||||||
func _set_layer_recursive(node: Node, layer_mask: int) -> void:
|
func _set_layer_recursive(node: Node, layer_mask: int) -> void:
|
||||||
if node is VisualInstance3D:
|
if node is VisualInstance3D:
|
||||||
|
if node is GeometryInstance3D:
|
||||||
|
node.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||||
|
|
||||||
if node is Light3D:
|
if node is Light3D:
|
||||||
# Lights (e.g. muzzle flash) illuminate both world and viewmodel
|
# Lights (e.g. muzzle flash) illuminate both world and viewmodel
|
||||||
node.layers = 1 | layer_mask
|
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():
|
for child in node.get_children():
|
||||||
_set_layer_recursive(child, layer_mask)
|
_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:
|
func _equip_slot(slot: int) -> void:
|
||||||
if weapons.has(active_slot):
|
if weapons.has(active_slot):
|
||||||
var w = weapons[active_slot]
|
var w = weapons[active_slot]
|
||||||
@@ -128,10 +178,13 @@ func _equip_slot(slot: int) -> void:
|
|||||||
var w = weapons[active_slot]
|
var w = weapons[active_slot]
|
||||||
w.visible = true
|
w.visible = true
|
||||||
w.set_process_input(true)
|
w.set_process_input(true)
|
||||||
print("Equipped slot ", slot)
|
|
||||||
else:
|
# Sync 3rd person weapon
|
||||||
print("Slot ", slot, " is empty!")
|
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:
|
func _input(event: InputEvent) -> void:
|
||||||
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user