Skip to content

Commit bb833aa

Browse files
authored
Add support for networked multiplayer (godotengine#163)
1 parent 2beb6c4 commit bb833aa

File tree

15 files changed

+796
-355
lines changed

15 files changed

+796
-355
lines changed

‎enemies/red_robot/parts/part.gd‎

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,54 @@ extends RigidBody3D
22

33
var puff_effect = preload("res://enemies/red_robot/parts/part_disappear_effect/part_disappear.tscn")
44

5+
var _mat : Material = null
6+
57
@export var lifetime: float = 3.0
68
@export var lifetime_random: float = 3.0
79
@export var disappearing_time: float = 0.5
10+
@export var fade_value : float = 0.0 :
11+
set(value):
12+
fade_value = value
13+
if _mat:
14+
_mat.next_pass.set_shader_parameter("emission_cutout", fade_value)
815

9-
var _lifetime
1016
var _disappearing_counter = 0.0
11-
var _is_disappearing = false
12-
var _mat
13-
14-
@onready var _mesh = $Model.get_child(0)
15-
1617

1718
func _ready():
18-
_mat = _mesh.mesh.surface_get_material(0).duplicate()
19-
_mesh.mesh.surface_set_material(0, _mat)
20-
_mat.next_pass = _mat.next_pass.duplicate()
21-
randomize()
22-
_lifetime = lifetime + lifetime_random * randf()
23-
24-
25-
func start_disappear_countdown():
26-
await get_tree().create_timer(_lifetime).timeout
27-
_is_disappearing = true
19+
set_process(false)
20+
if not OS.has_feature("dedicated_server"):
21+
var mesh := $Model.get_child(0) as MeshInstance3D
22+
_mat = mesh.mesh.surface_get_material(0).duplicate()
23+
mesh.mesh.surface_set_material(0, _mat)
24+
_mat.next_pass = _mat.next_pass.duplicate()
25+
26+
27+
func explode():
28+
# Start synching.
29+
$MultiplayerSynchronizer.public_visibility = true
30+
freeze = false
31+
if not multiplayer.is_server():
32+
return
33+
get_node("Col1").disabled = false
34+
get_node("Col2").disabled = false
35+
linear_velocity = 3 * (Vector3.UP).normalized()
36+
angular_velocity = (Vector3(randf(), randf(), randf()).normalized() * 2 - Vector3.ONE) * 10
37+
await get_tree().create_timer(lifetime + lifetime_random * randf()).timeout
38+
set_process(true)
2839

2940

3041
func _process(delta):
31-
if not _is_disappearing:
32-
return
33-
var curve_val = pow(_disappearing_counter / disappearing_time, 2.0)
34-
_mat.next_pass.set_shader_parameter("emission_cutout", curve_val)
42+
fade_value = pow(_disappearing_counter / disappearing_time, 2.0)
3543
_disappearing_counter += delta
3644
if _disappearing_counter >= disappearing_time - 0.2:
37-
var puff = puff_effect.instantiate()
38-
get_parent().add_child(puff)
39-
puff.global_transform.origin = global_transform.origin
40-
_is_disappearing = false
41-
await get_tree().create_timer(0.2).timeout
42-
queue_free()
45+
destroy.rpc()
46+
set_process(false)
47+
48+
49+
@rpc("call_local")
50+
func destroy():
51+
var puff = puff_effect.instantiate()
52+
get_parent().add_child(puff)
53+
puff.global_transform.origin = global_transform.origin
54+
await get_tree().create_timer(0.2).timeout
55+
queue_free()

‎enemies/red_robot/red_robot.gd‎

Lines changed: 81 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,26 @@ enum State {
66
SHOOTING = 2,
77
}
88

9-
const PLAYER_AIM_TOLERANCE_DEGREES = 15
9+
const PLAYER_AIM_TOLERANCE_DEGREES = deg_to_rad(15)
1010

1111
const SHOOT_WAIT = 6.0
1212
const AIM_TIME = 1
1313

1414
const AIM_PREPARE_TIME = 0.5
1515
const BLEND_AIM_SPEED = 0.05
1616

17-
@export var health: int = 5
17+
signal exploded()
18+
1819
@export var test_shoot: bool = false
1920

20-
var state = State.APPROACH
21+
@export var target_position := Vector3()
22+
@export var health: int = 5
23+
@export var state : State = State.APPROACH
24+
@export var dead = false
25+
@export var aim_preparing = AIM_PREPARE_TIME
2126

2227
var shoot_countdown = SHOOT_WAIT
2328
var aim_countdown = AIM_TIME
24-
var aim_preparing = AIM_PREPARE_TIME
25-
var dead = false
2629

2730
var player = null
2831
var orientation = Transform3D()
@@ -56,15 +59,21 @@ func _ready():
5659
$AnimationTree.active = true
5760
if test_shoot:
5861
shoot_countdown = 0.0
59-
animation_tree["parameters/state/transition_request"] = "idle" # Go idle.
6062

63+
if dead:
64+
model.visible = false
65+
collision_shape.disabled = true
66+
animation_tree.active = false
67+
68+
animate()
6169

6270
func resume_approach():
6371
state = State.APPROACH
6472
aim_preparing = AIM_PREPARE_TIME
6573
shoot_countdown = SHOOT_WAIT
6674

6775

76+
@rpc("call_local")
6877
func hit():
6978
if dead:
7079
return
@@ -74,39 +83,24 @@ func hit():
7483
health -= 1
7584
if health == 0:
7685
dead = true
77-
var base_xf = global_transform.basis
7886
animation_tree.active = false
7987
model.visible = false
8088
death.visible = true
8189
collision_shape.disabled = true
8290

83-
death_shield1.freeze = false
84-
death_shield1.get_node("Col1").disabled = false
85-
death_shield1.get_node("Col2").disabled = false
86-
87-
death_shield2.freeze = false
88-
death_shield2.get_node("Col1").disabled = false
89-
death_shield2.get_node("Col2").disabled = false
90-
91-
death_head.freeze = false
92-
death_head.get_node("Col1").disabled = false
93-
death_head.get_node("Col2").disabled = false
94-
9591
death_detach_spark1.emitting = true
9692
death_detach_spark2.emitting = true
9793

98-
death_shield1.linear_velocity = 3 * (Vector3.UP - base_xf.x).normalized()
99-
death_shield2.linear_velocity = 3 * (Vector3.UP + base_xf.x).normalized()
100-
death_head.linear_velocity = 3 * (Vector3.UP).normalized()
101-
death_shield1.angular_velocity = (Vector3(randf(), randf(), randf()).normalized() * 2 - Vector3.ONE) * 10
102-
death_shield2.angular_velocity = (Vector3(randf(), randf(), randf()).normalized() * 2 - Vector3.ONE) * 10
103-
death_head.angular_velocity = (Vector3(randf(), randf(), randf()).normalized() * 2 - Vector3.ONE) * 10
104-
105-
death_shield1.start_disappear_countdown()
106-
death_shield2.start_disappear_countdown()
107-
death_head.start_disappear_countdown()
94+
death_shield1.explode()
95+
death_shield2.explode()
96+
death_head.explode()
10897

10998
explosion_sound.play()
99+
exploded.emit()
100+
101+
if multiplayer.is_server():
102+
await get_tree().create_timer(10.0).timeout
103+
queue_free()
110104

111105

112106
func shoot():
@@ -136,38 +130,73 @@ func shoot():
136130
player.add_camera_shake_trauma(13)
137131

138132

133+
func animate(delta:=0.0):
134+
if state == State.APPROACH:
135+
var to_player_local = target_position * global_transform
136+
# The front of the robot is +Z, and atan2 is zero at +X, so we need to use the Z for the X parameter (second one).
137+
var angle_to_player = atan2(to_player_local.x, to_player_local.z)
138+
if angle_to_player > PLAYER_AIM_TOLERANCE_DEGREES:
139+
animation_tree["parameters/state/transition_request"] = "turn_left"
140+
elif angle_to_player < -PLAYER_AIM_TOLERANCE_DEGREES:
141+
animation_tree["parameters/state/transition_request"] = "turn_right"
142+
elif target_position == Vector3.ZERO:
143+
animation_tree["parameters/state/transition_request"] = "idle"
144+
else:
145+
animation_tree["parameters/state/transition_request"] = "walk"
146+
else:
147+
animation_tree["parameters/state/transition_request"] = "idle"
148+
149+
# Aiming or shooting
150+
if target_position != Vector3.ZERO:
151+
animation_tree["parameters/aiming/blend_amount"] = clamp(aim_preparing / AIM_PREPARE_TIME, 0, 1)
152+
153+
var to_cannon_local = target_position + Vector3.UP * ray_mesh.global_transform
154+
var h_angle = rad_to_deg(atan2( to_cannon_local.x, -to_cannon_local.z ))
155+
var v_angle = rad_to_deg(atan2( to_cannon_local.y, -to_cannon_local.z ))
156+
var blend_pos = animation_tree.get("parameters/aim/blend_position")
157+
var h_motion = BLEND_AIM_SPEED * delta * -h_angle
158+
blend_pos.x += h_motion
159+
blend_pos.x = clamp(blend_pos.x, -1, 1)
160+
161+
var v_motion = BLEND_AIM_SPEED * delta * v_angle
162+
blend_pos.y += v_motion
163+
blend_pos.y = clamp(blend_pos.y, -1, 1)
164+
165+
animation_tree["parameters/aim/blend_position"] = blend_pos
166+
167+
139168
func _physics_process(delta):
169+
if dead:
170+
return
171+
172+
if not multiplayer.is_server():
173+
animate(delta)
174+
return
175+
140176
if test_shoot:
141177
shoot()
142178
test_shoot = false
143179

144-
if dead:
145-
return
146-
147180
if not player:
148-
animation_tree["parameters/state/transition_request"] = "idle" # Go idle.
181+
target_position = Vector3()
182+
animate(delta)
149183
set_velocity(gravity * delta)
150184
set_up_direction(Vector3.UP)
151185
move_and_slide()
152186
return
153187

188+
target_position = player.global_transform.origin
189+
154190
if state == State.APPROACH:
155191
if aim_preparing > 0:
156192
aim_preparing -= delta
157193
if aim_preparing < 0:
158194
aim_preparing = 0
159-
animation_tree["parameters/aiming/blend_amount"] = aim_preparing / AIM_PREPARE_TIME
160195

161-
var to_player_local = player.global_transform.origin * global_transform
196+
var to_player_local = target_position * global_transform
162197
# The front of the robot is +Z, and atan2 is zero at +X, so we need to use the Z for the X parameter (second one).
163198
var angle_to_player = atan2(to_player_local.x, to_player_local.z)
164-
var tolerance = deg_to_rad(PLAYER_AIM_TOLERANCE_DEGREES)
165-
if angle_to_player > tolerance:
166-
animation_tree["parameters/state/transition_request"] = "turn_left"
167-
elif angle_to_player < -tolerance:
168-
animation_tree["parameters/state/transition_request"] = "turn_right"
169-
else:
170-
animation_tree["parameters/state/transition_request"] = "walk"
199+
if angle_to_player > -PLAYER_AIM_TOLERANCE_DEGREES and angle_to_player < PLAYER_AIM_TOLERANCE_DEGREES:
171200
# Facing player, try to shoot.
172201
shoot_countdown -= delta
173202
if shoot_countdown < 0:
@@ -180,7 +209,6 @@ func _physics_process(delta):
180209
state = State.AIM
181210
aim_countdown = AIM_TIME
182211
aim_preparing = 0
183-
animation_tree["parameters/state/transition_request"] = "idle"
184212
else:
185213
# Player not in sight, do nothing.
186214
shoot_countdown = SHOOT_WAIT
@@ -195,34 +223,19 @@ func _physics_process(delta):
195223
if aim_preparing > AIM_PREPARE_TIME:
196224
aim_preparing = AIM_PREPARE_TIME
197225

198-
animation_tree["parameters/aiming/blend_amount"] = clamp(aim_preparing / AIM_PREPARE_TIME, 0, 1)
199226
aim_countdown -= delta
200227
if aim_countdown < 0 and state == State.AIM:
201228
var ray_origin = ray_from.global_transform.origin
202-
var ray_to = player.global_transform.origin + Vector3.UP # Above middle of player.
229+
var ray_to = target_position + Vector3.UP
203230
var col = get_world_3d().direct_space_state.intersect_ray(PhysicsRayQueryParameters3D.create(ray_origin, ray_to, 0xFFFFFFFF, [self]))
204231
if not col.is_empty() and col.collider == player:
205232
state = State.SHOOTING
206-
shoot_animation.play("shoot")
207233
shoot_countdown = SHOOT_WAIT
234+
play_shoot.rpc()
208235
else:
209236
resume_approach()
210237

211-
if animation_tree.active:
212-
var to_cannon_local = player.global_transform.origin + Vector3.UP * ray_mesh.global_transform
213-
var h_angle = rad_to_deg(atan2( to_cannon_local.x, -to_cannon_local.z ))
214-
var v_angle = rad_to_deg(atan2( to_cannon_local.y, -to_cannon_local.z ))
215-
var blend_pos = animation_tree.get("parameters/aim/blend_position")
216-
var h_motion = BLEND_AIM_SPEED * delta * -h_angle
217-
blend_pos.x += h_motion
218-
blend_pos.x = clamp(blend_pos.x, -1, 1)
219-
220-
var v_motion = BLEND_AIM_SPEED * delta * v_angle
221-
blend_pos.y += v_motion
222-
blend_pos.y = clamp(blend_pos.y, -1, 1)
223-
224-
animation_tree["parameters/aim/blend_position"] = blend_pos
225-
238+
animate(delta)
226239
# Apply root motion to orientation.
227240
orientation *= Transform3D(animation_tree.get_root_motion_rotation(), animation_tree.get_root_motion_position())
228241

@@ -240,13 +253,19 @@ func _physics_process(delta):
240253
global_transform.basis = orientation.basis
241254

242255

256+
@rpc("call_local")
257+
func play_shoot():
258+
shoot_animation.play("shoot")
259+
260+
243261
func shoot_check():
244262
test_shoot = true
245263

246264

247265
func _clip_ray(length):
248266
var mesh_offset = ray_mesh.position.z
249-
ray_mesh.get_surface_override_material(0).set_shader_parameter("clip", length + mesh_offset)
267+
if not OS.has_feature("dedicated_server"):
268+
ray_mesh.get_surface_override_material(0).set_shader_parameter("clip", length + mesh_offset)
250269

251270

252271
func _on_area_body_entered(body):

0 commit comments

Comments
 (0)