feat: implement PlayerMovementController and grenade projectile system

This commit is contained in:
DottsGit
2026-06-09 23:04:32 -04:00
parent e8e5391d9f
commit 887d9c1523
4 changed files with 194 additions and 0 deletions
+112
View File
@@ -49,6 +49,10 @@ var hit_marker: Control
var _hit_marker_tween: Tween
var reload_ring: Control
var grenades: int = 2
var is_holding_grenade: bool = false
var trajectory_mesh: MultiMeshInstance3D
# Grapple
var grapple_rope: MeshInstance3D
@@ -97,6 +101,7 @@ func _ready() -> void:
_setup_audio()
_setup_grapple()
_setup_grenade_trajectory()
if is_multiplayer_authority():
_damage_layer = CanvasLayer.new()
@@ -236,6 +241,24 @@ func _setup_grapple() -> void:
grapple_rope.visible = false
get_tree().current_scene.call_deferred("add_child", grapple_rope)
func _setup_grenade_trajectory() -> void:
trajectory_mesh = MultiMeshInstance3D.new()
var mm = MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_3D
mm.instance_count = 60
trajectory_mesh.multimesh = mm
var dot_mesh = SphereMesh.new()
dot_mesh.radius = 0.05
dot_mesh.height = 0.1
var dot_mat = StandardMaterial3D.new()
dot_mat.albedo_color = Color(1.0, 1.0, 1.0, 0.5)
dot_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
dot_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
dot_mesh.material = dot_mat
trajectory_mesh.multimesh.mesh = dot_mesh
trajectory_mesh.visible = false
get_tree().current_scene.call_deferred("add_child", trajectory_mesh)
var _last_hit_sound_time: int = 0
func spawn_damage_number(amount: float, hit_pos: Vector3) -> void:
@@ -789,6 +812,12 @@ func _process(delta: float) -> void:
reload_ring.progress = 0.0
else:
reload_ring.progress = 0.0
if is_multiplayer_authority() and not is_dead:
if is_holding_grenade and grenades > 0:
_draw_trajectory()
elif is_instance_valid(trajectory_mesh):
trajectory_mesh.visible = false
if is_dead:
# Continuously follow the ragdoll torso
@@ -1023,6 +1052,87 @@ func _input(event: InputEvent) -> void:
if event is InputEventKey or event is InputEventMouseButton or event is InputEventJoypadButton:
if event.is_pressed() and not event.is_echo():
_on_respawn_pressed()
if not is_dead and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
if event.is_action_pressed("grenade") and grenades > 0:
is_holding_grenade = true
elif event.is_action_released("grenade") and is_holding_grenade:
is_holding_grenade = false
_throw_grenade()
func _draw_trajectory() -> void:
if not camera or not trajectory_mesh: return
trajectory_mesh.visible = true
var throw_speed = 20.0
var throw_up_speed = 5.0
var throw_vel = camera.global_transform.basis.z * -throw_speed + Vector3.UP * throw_up_speed + velocity
var gravity = 15.0 # Typical default
var current_pos = camera.global_position + camera.global_transform.basis.z * -0.5
var time_step = 0.05
var bounciness = 0.5
var space_state = get_world_3d().direct_space_state
for i in range(trajectory_mesh.multimesh.instance_count):
var t = Transform3D(Basis(), current_pos)
trajectory_mesh.multimesh.set_instance_transform(i, t)
var next_vel = throw_vel
next_vel.y -= gravity * time_step
var next_pos = current_pos + throw_vel * time_step
var query = PhysicsRayQueryParameters3D.create(current_pos, next_pos)
query.exclude = [get_rid()]
var hit = space_state.intersect_ray(query)
if hit:
current_pos = hit.position + hit.normal * 0.05
throw_vel = next_vel.bounce(hit.normal) * bounciness
else:
current_pos = next_pos
throw_vel = next_vel
func _throw_grenade() -> void:
if grenades <= 0: return
grenades -= 1
var throw_speed = 20.0
var throw_up_speed = 5.0
var throw_vel = camera.global_transform.basis.z * -throw_speed + Vector3.UP * throw_up_speed + velocity
var spawn_pos = camera.global_position + camera.global_transform.basis.z * -0.5
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
server_spawn_grenade.rpc_id(1, spawn_pos, throw_vel)
else:
_spawn_grenade_local(spawn_pos, throw_vel, 1)
@rpc("any_peer", "call_local", "reliable")
func server_spawn_grenade(pos: Vector3, vel: Vector3) -> void:
if not multiplayer.is_server(): return
var id = multiplayer.get_remote_sender_id()
client_spawn_grenade.rpc(pos, vel, id)
@rpc("authority", "call_local", "reliable")
func client_spawn_grenade(pos: Vector3, vel: Vector3, owner_id: int) -> void:
_spawn_grenade_local(pos, vel, owner_id)
func _spawn_grenade_local(pos: Vector3, vel: Vector3, owner_id: int) -> void:
var g_scene = load("res://weapons/grenade_projectile.gd")
if not g_scene: return
var g = g_scene.new()
g.global_position = pos
g.velocity = vel
var p = null
if owner_id == multiplayer.get_unique_id():
p = self
else:
var root = get_tree().current_scene
if root.has_node("Players/" + str(owner_id)):
p = root.get_node("Players/" + str(owner_id))
g.owner_player = p
get_tree().current_scene.add_child(g)
func _on_respawn_pressed() -> void:
if death_screen:
@@ -1062,6 +1172,8 @@ func rpc_respawn(spawn_pos: Vector3) -> void:
health = max_health
shield = max_shield
time_since_last_damage = 0.0
grenades = 2
is_holding_grenade = false
if is_instance_valid(ragdoll_instance):
if ragdoll_instance.has_meta("spring_arm"):