feat: implement PlayerMovementController and grenade projectile system
This commit is contained in:
@@ -49,6 +49,10 @@ var hit_marker: Control
|
|||||||
var _hit_marker_tween: Tween
|
var _hit_marker_tween: Tween
|
||||||
var reload_ring: Control
|
var reload_ring: Control
|
||||||
|
|
||||||
|
var grenades: int = 2
|
||||||
|
var is_holding_grenade: bool = false
|
||||||
|
var trajectory_mesh: MultiMeshInstance3D
|
||||||
|
|
||||||
# Grapple
|
# Grapple
|
||||||
var grapple_rope: MeshInstance3D
|
var grapple_rope: MeshInstance3D
|
||||||
|
|
||||||
@@ -97,6 +101,7 @@ func _ready() -> void:
|
|||||||
|
|
||||||
_setup_audio()
|
_setup_audio()
|
||||||
_setup_grapple()
|
_setup_grapple()
|
||||||
|
_setup_grenade_trajectory()
|
||||||
|
|
||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
_damage_layer = CanvasLayer.new()
|
_damage_layer = CanvasLayer.new()
|
||||||
@@ -236,6 +241,24 @@ func _setup_grapple() -> void:
|
|||||||
grapple_rope.visible = false
|
grapple_rope.visible = false
|
||||||
get_tree().current_scene.call_deferred("add_child", grapple_rope)
|
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
|
var _last_hit_sound_time: int = 0
|
||||||
|
|
||||||
func spawn_damage_number(amount: float, hit_pos: Vector3) -> void:
|
func spawn_damage_number(amount: float, hit_pos: Vector3) -> void:
|
||||||
@@ -790,6 +813,12 @@ func _process(delta: float) -> void:
|
|||||||
else:
|
else:
|
||||||
reload_ring.progress = 0.0
|
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:
|
if is_dead:
|
||||||
# Continuously follow the ragdoll torso
|
# Continuously follow the ragdoll torso
|
||||||
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
if is_instance_valid(ragdoll_instance) and is_instance_valid(camera):
|
||||||
@@ -1024,6 +1053,87 @@ func _input(event: InputEvent) -> void:
|
|||||||
if event.is_pressed() and not event.is_echo():
|
if event.is_pressed() and not event.is_echo():
|
||||||
_on_respawn_pressed()
|
_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:
|
func _on_respawn_pressed() -> void:
|
||||||
if death_screen:
|
if death_screen:
|
||||||
death_screen.visible = false
|
death_screen.visible = false
|
||||||
@@ -1062,6 +1172,8 @@ func rpc_respawn(spawn_pos: Vector3) -> void:
|
|||||||
health = max_health
|
health = max_health
|
||||||
shield = max_shield
|
shield = max_shield
|
||||||
time_since_last_damage = 0.0
|
time_since_last_damage = 0.0
|
||||||
|
grenades = 2
|
||||||
|
is_holding_grenade = false
|
||||||
|
|
||||||
if is_instance_valid(ragdoll_instance):
|
if is_instance_valid(ragdoll_instance):
|
||||||
if ragdoll_instance.has_meta("spring_arm"):
|
if ragdoll_instance.has_meta("spring_arm"):
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ interact={
|
|||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":69,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":69,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
grenade={
|
||||||
|
"deadzone": 0.0,
|
||||||
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
]
|
||||||
|
}
|
||||||
reload={
|
reload={
|
||||||
"deadzone": 0.0,
|
"deadzone": 0.0,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":82,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":82,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
extends BouncingProjectile
|
||||||
|
class_name GrenadeProjectile
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
super._ready()
|
||||||
|
fuse_time = 4.0
|
||||||
|
bounciness = 0.5
|
||||||
|
drop_gravity = 15.0
|
||||||
|
explosion_radius = 5.0
|
||||||
|
damage = 120.0
|
||||||
|
|
||||||
|
func _setup_effects() -> void:
|
||||||
|
# Audio
|
||||||
|
flight_sound = AudioStreamPlayer3D.new()
|
||||||
|
flight_sound.bus = "SFX"
|
||||||
|
flight_sound.stream = load("res://assets/sounds/rocket_flight.wav")
|
||||||
|
flight_sound.autoplay = true
|
||||||
|
if flight_sound.stream is AudioStreamWAV:
|
||||||
|
flight_sound.stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||||
|
flight_sound.unit_size = 50.0
|
||||||
|
flight_sound.max_db = 6.0
|
||||||
|
flight_sound.volume_db = 0.0
|
||||||
|
add_child(flight_sound)
|
||||||
|
flight_sound.play()
|
||||||
|
|
||||||
|
# Grenade Body
|
||||||
|
var body_mesh = MeshInstance3D.new()
|
||||||
|
var sphere = SphereMesh.new()
|
||||||
|
sphere.radius = 0.1
|
||||||
|
sphere.height = 0.2
|
||||||
|
var b_mat = StandardMaterial3D.new()
|
||||||
|
b_mat.albedo_color = Color(0.2, 0.4, 0.2) # Dark green
|
||||||
|
b_mat.roughness = 0.6
|
||||||
|
b_mat.metallic = 0.5
|
||||||
|
sphere.material = b_mat
|
||||||
|
body_mesh.mesh = sphere
|
||||||
|
add_child(body_mesh)
|
||||||
|
|
||||||
|
# Smoke Trail (White translucent tail)
|
||||||
|
smoke_sys = GPUParticles3D.new()
|
||||||
|
var smoke_mat = ParticleProcessMaterial.new()
|
||||||
|
smoke_mat.direction = Vector3(0, 0, 1)
|
||||||
|
smoke_mat.spread = 5.0
|
||||||
|
smoke_mat.initial_velocity_min = 0.0
|
||||||
|
smoke_mat.initial_velocity_max = 0.5
|
||||||
|
smoke_mat.gravity = Vector3(0, 0.1, 0) # Less floaty
|
||||||
|
|
||||||
|
var grad = Gradient.new()
|
||||||
|
grad.add_point(0.0, Color(1.0, 1.0, 1.0, 0.5))
|
||||||
|
grad.add_point(1.0, Color(1.0, 1.0, 1.0, 0.0))
|
||||||
|
var grad_tex = GradientTexture1D.new()
|
||||||
|
grad_tex.gradient = grad
|
||||||
|
smoke_mat.color_ramp = grad_tex
|
||||||
|
|
||||||
|
var curve = Curve.new()
|
||||||
|
curve.add_point(Vector2(0.0, 0.5))
|
||||||
|
curve.add_point(Vector2(1.0, 1.5))
|
||||||
|
var curve_tex = CurveTexture.new()
|
||||||
|
curve_tex.curve = curve
|
||||||
|
smoke_mat.scale_curve = curve_tex
|
||||||
|
|
||||||
|
var smoke_mesh = QuadMesh.new()
|
||||||
|
smoke_mesh.size = Vector2(0.2, 0.2)
|
||||||
|
var s_mat = StandardMaterial3D.new()
|
||||||
|
s_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||||
|
s_mat.vertex_color_use_as_albedo = true
|
||||||
|
s_mat.albedo_texture = get_smoke_texture()
|
||||||
|
s_mat.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
|
||||||
|
s_mat.billboard_keep_scale = true
|
||||||
|
smoke_mesh.material = s_mat
|
||||||
|
smoke_sys.draw_pass_1 = smoke_mesh
|
||||||
|
smoke_sys.process_material = smoke_mat
|
||||||
|
smoke_sys.amount = 32
|
||||||
|
smoke_sys.lifetime = 1.0
|
||||||
|
smoke_sys.position = Vector3(0, 0, 0.0)
|
||||||
|
add_child(smoke_sys)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://3m8quupertk6
|
||||||
Reference in New Issue
Block a user