diff --git a/networking/multiplayer_pong/ball.gd b/networking/multiplayer_pong/ball.gd new file mode 100644 index 00000000..af4c2a25 --- /dev/null +++ b/networking/multiplayer_pong/ball.gd @@ -0,0 +1,72 @@ + +extends Area2D + +const DEFAULT_SPEED=80 + +var direction = Vector2(1,0) +var ball_speed = DEFAULT_SPEED +var stopped=false + + + +onready var screen_size = get_viewport_rect().size + +sync func _reset_ball(for_left): + + position = screen_size /2 + if (for_left): + direction = Vector2(-1,0) + else: + direction = Vector2( 1,0) + + ball_speed = DEFAULT_SPEED + +sync func stop(): + stopped=true + +func _process(delta): + + # ball will move normally for both players + # even if it's sightly out of sync between them + # so each player sees the motion as smooth and not jerky + + if (not stopped): + translate( direction * ball_speed * delta ) + + # check screen bounds to make ball bounce + + var ball_pos = position + if ((ball_pos.y < 0 and direction.y < 0) or (ball_pos.y > screen_size.y and direction.y > 0)): + direction.y = -direction.y + + if (is_network_master()): + # only master will decide when the ball is out in the left side (it's own side) + # this makes the game playable even if latency is high and ball is going fast + # otherwise ball might be out in the other player's screen but not this one + + if (ball_pos.x < 0 ): + get_parent().rpc("update_score",false) + rpc("_reset_ball",false) + else: + # only the slave will decide when the ball is out in the right side (it's own side) + # this makes the game playable even if latency is high and ball is going fast + # otherwise ball might be out in the other player's screen but not this one + + if (ball_pos.x > screen_size.x): + get_parent().rpc("update_score",true) + rpc("_reset_ball",true) + + +sync func bounce(left,random): + + #using sync because both players can make it bounce + if (left): + direction.x = abs(direction.x) + else: + direction.x = -abs(direction.x) + + ball_speed *= 1.1 + direction.y = random*2.0 - 1 + direction = direction.normalized() + + diff --git a/networking/multiplayer_pong/ball.png b/networking/multiplayer_pong/ball.png new file mode 100644 index 00000000..c0f6da4f Binary files /dev/null and b/networking/multiplayer_pong/ball.png differ diff --git a/networking/multiplayer_pong/ball.png.import b/networking/multiplayer_pong/ball.png.import new file mode 100644 index 00000000..110a99ab --- /dev/null +++ b/networking/multiplayer_pong/ball.png.import @@ -0,0 +1,23 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex" + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true diff --git a/networking/multiplayer_pong/ball.tscn b/networking/multiplayer_pong/ball.tscn new file mode 100644 index 00000000..532c6fff --- /dev/null +++ b/networking/multiplayer_pong/ball.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://ball.gd" type="Script" id=1] +[ext_resource path="res://ball.png" type="Texture" id=2] + +[sub_resource type="CircleShape2D" id=1] + +custom_solver_bias = 0.0 +radius = 5.11969 + +[node name="ball" type="Area2D"] + +input_pickable = true +shapes/0/shape = SubResource( 1 ) +shapes/0/transform = Transform2D( 1, 0, 0, 1, 0, 0 ) +shapes/0/trigger = false +gravity_vec = Vector2( 0, 1 ) +gravity = 98.0 +linear_damp = 0.1 +angular_damp = 1.0 +script = ExtResource( 1 ) + +[node name="sprite" type="Sprite" parent="."] + +texture = ExtResource( 2 ) + +[node name="shape" type="CollisionShape2D" parent="."] + +shape = SubResource( 1 ) +trigger = false +_update_shape_index = 0 diff --git a/networking/multiplayer_pong/icon.png b/networking/multiplayer_pong/icon.png new file mode 100644 index 00000000..eab34de5 Binary files /dev/null and b/networking/multiplayer_pong/icon.png differ diff --git a/networking/multiplayer_pong/icon.png.import b/networking/multiplayer_pong/icon.png.import new file mode 100644 index 00000000..627820bd --- /dev/null +++ b/networking/multiplayer_pong/icon.png.import @@ -0,0 +1,23 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true diff --git a/networking/multiplayer_pong/lobby.gd b/networking/multiplayer_pong/lobby.gd new file mode 100644 index 00000000..d560c10a --- /dev/null +++ b/networking/multiplayer_pong/lobby.gd @@ -0,0 +1,106 @@ + +extends Control + +const DEFAULT_PORT = 8910 # some random number, pick your port properly + +#### Network callbacks from SceneTree #### + +# callback from SceneTree +func _player_connected(id): + #someone connected, start the game! + var pong = load("res://pong.tscn").instance() + pong.connect("game_finished",self,"_end_game",[],CONNECT_DEFERRED) # connect deferred so we can safely erase it from the callback + + get_tree().get_root().add_child(pong) + hide() + +func _player_disconnected(id): + + if (get_tree().is_network_server()): + _end_game("Client disconnected") + else: + _end_game("Server disconnected") + +# callback from SceneTree, only for clients (not server) +func _connected_ok(): + # will not use this one + pass + +# callback from SceneTree, only for clients (not server) +func _connected_fail(): + + _set_status("Couldn't connect",false) + + get_tree().set_network_peer(null) #remove peer + + get_node("panel/join").set_disabled(false) + get_node("panel/host").set_disabled(false) + +func _server_disconnected(): + _end_game("Server disconnected") + +##### Game creation functions ###### + +func _end_game(with_error=""): + if (has_node("/root/pong")): + #erase pong scene + get_node("/root/pong").free() # erase immediately, otherwise network might show errors (this is why we connected deferred above) + show() + + get_tree().set_network_peer(null) #remove peer + + get_node("panel/join").set_disabled(false) + get_node("panel/host").set_disabled(false) + + _set_status(with_error,false) + +func _set_status(text,isok): + #simple way to show status + if (isok): + get_node("panel/status_ok").set_text(text) + get_node("panel/status_fail").set_text("") + else: + get_node("panel/status_ok").set_text("") + get_node("panel/status_fail").set_text(text) + +func _on_host_pressed(): + + var host = NetworkedMultiplayerENet.new() + host.set_compression_mode(NetworkedMultiplayerENet.COMPRESS_RANGE_CODER) + var err = host.create_server(DEFAULT_PORT,1) # max: 1 peer, since it's a 2 players game + if (err!=OK): + #is another server running? + _set_status("Can't host, address in use.",false) + return + + get_tree().set_network_peer(host) + get_node("panel/join").set_disabled(true) + get_node("panel/host").set_disabled(true) + _set_status("Waiting for player..",true) + +func _on_join_pressed(): + + var ip = get_node("panel/address").get_text() + if (not ip.is_valid_ip_address()): + _set_status("IP address is invalid",false) + return + + var host = NetworkedMultiplayerENet.new() + host.set_compression_mode(NetworkedMultiplayerENet.COMPRESS_RANGE_CODER) + host.create_client(ip,DEFAULT_PORT) + get_tree().set_network_peer(host) + + _set_status("Connecting..",true) + + + +### INITIALIZER #### + +func _ready(): + # connect all the callbacks related to networking + get_tree().connect("network_peer_connected",self,"_player_connected") + get_tree().connect("network_peer_disconnected",self,"_player_disconnected") + get_tree().connect("connected_to_server",self,"_connected_ok") + get_tree().connect("connection_failed",self,"_connected_fail") + get_tree().connect("server_disconnected",self,"_server_disconnected") + diff --git a/networking/multiplayer_pong/lobby.tscn b/networking/multiplayer_pong/lobby.tscn new file mode 100644 index 00000000..d6fa8a49 --- /dev/null +++ b/networking/multiplayer_pong/lobby.tscn @@ -0,0 +1,145 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://lobby.gd" type="Script" id=1] + +[node name="lobby" type="Control"] + +margin_right = 40.0 +margin_bottom = 40.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +script = ExtResource( 1 ) + +[node name="title" type="Label" parent="."] + +margin_left = 210.0 +margin_top = 40.0 +margin_right = 430.0 +margin_bottom = 80.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "Multiplayer Pong" +align = 1 +valign = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="panel" type="Panel" parent="."] + +margin_left = 210.0 +margin_top = 160.0 +margin_right = 430.0 +margin_bottom = 270.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="address_label" type="Label" parent="panel"] + +margin_left = 10.0 +margin_top = 10.0 +margin_right = 62.0 +margin_bottom = 24.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "Address" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="address" type="LineEdit" parent="panel"] + +margin_left = 10.0 +margin_top = 30.0 +margin_right = 210.0 +margin_bottom = 54.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +text = "127.0.0.1" +expand_to_len = false +focus_mode = 2 +placeholder_alpha = 0.6 +caret_blink = false +caret_blink_speed = 0.65 + +[node name="host" type="Button" parent="panel"] + +margin_left = 10.0 +margin_top = 60.0 +margin_right = 90.0 +margin_bottom = 80.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Host" +flat = false + +[node name="join" type="Button" parent="panel"] + +margin_left = 130.0 +margin_top = 60.0 +margin_right = 210.0 +margin_bottom = 80.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Join" +flat = false + +[node name="status_ok" type="Label" parent="panel"] + +margin_left = 10.0 +margin_top = 90.0 +margin_right = 210.0 +margin_bottom = 104.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +custom_colors/font_color = Color( 0, 1, 0.015625, 1 ) +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="status_fail" type="Label" parent="panel"] + +margin_left = 10.0 +margin_top = 90.0 +margin_right = 210.0 +margin_bottom = 104.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +custom_colors/font_color = Color( 1, 0, 0, 1 ) +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[connection signal="pressed" from="panel/host" to="." method="_on_host_pressed"] + +[connection signal="pressed" from="panel/join" to="." method="_on_join_pressed"] + + diff --git a/networking/multiplayer_pong/paddle.gd b/networking/multiplayer_pong/paddle.gd new file mode 100644 index 00000000..fac59911 --- /dev/null +++ b/networking/multiplayer_pong/paddle.gd @@ -0,0 +1,66 @@ +extends Area2D + +export var left=false + +const MOTION_SPEED=150 + +var motion = 0 +var you_hidden=false + +onready var screen_size = get_viewport_rect().size + +#synchronize position and speed to the other peers +slave func set_pos_and_motion(p_pos,p_motion): + position=p_pos + motion=p_motion + +func _hide_you_label(): + you_hidden=true + get_node("you").hide() + +func _process(delta): + + #is the master of the paddle + if (is_network_master()): + + motion = 0 + if (Input.is_action_pressed("move_up")): + motion -= 1 + elif (Input.is_action_pressed("move_down")): + motion += 1 + + if (not you_hidden and motion!=0): + _hide_you_label() + + + motion*=MOTION_SPEED + + #using unreliable to make sure position is updated as fast as possible, even if one of the calls is dropped + rpc_unreliable("set_pos_and_motion",position,motion) + + else: + if (not you_hidden): + _hide_you_label() + + + translate( Vector2(0,motion*delta) ) + + # set screen limits + + var pos = position + + if (pos.y < 0 ): + position = Vector2( pos.x, 0) + elif (pos.y > screen_size.y): + position = Vector2( pos.x, screen_size.y) + + + +func _on_paddle_area_enter( area ): + + if (is_network_master()): + area.rpc("bounce",left,randf()) #random for new direction generated on each peer + + + + diff --git a/networking/multiplayer_pong/paddle.png b/networking/multiplayer_pong/paddle.png new file mode 100644 index 00000000..e23491ec Binary files /dev/null and b/networking/multiplayer_pong/paddle.png differ diff --git a/networking/multiplayer_pong/paddle.png.import b/networking/multiplayer_pong/paddle.png.import new file mode 100644 index 00000000..a942a1fd --- /dev/null +++ b/networking/multiplayer_pong/paddle.png.import @@ -0,0 +1,23 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex" + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true diff --git a/networking/multiplayer_pong/paddle.tscn b/networking/multiplayer_pong/paddle.tscn new file mode 100644 index 00000000..a62944e1 --- /dev/null +++ b/networking/multiplayer_pong/paddle.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://paddle.gd" type="Script" id=1] +[ext_resource path="res://paddle.png" type="Texture" id=2] + +[sub_resource type="CapsuleShape2D" id=1] + +custom_solver_bias = 0.0 +radius = 4.78568 +height = 23.6064 + +[node name="paddle" type="Area2D"] + +input_pickable = true +shapes/0/shape = SubResource( 1 ) +shapes/0/transform = Transform2D( 1, 0, 0, 1, 0, 0 ) +shapes/0/trigger = false +gravity_vec = Vector2( 0, 1 ) +gravity = 98.0 +linear_damp = 0.1 +angular_damp = 1.0 +script = ExtResource( 1 ) +left = false + +[node name="sprite" type="Sprite" parent="."] + +texture = ExtResource( 2 ) + +[node name="shape" type="CollisionShape2D" parent="."] + +shape = SubResource( 1 ) +trigger = false +_update_shape_index = 0 + +[node name="you" type="Label" parent="."] + +focus_ignore_mouse = true +focus_stop_mouse = true +size_flags_horizontal = 2 +size_flags_vertical = 0 +margin_left = -26.0 +margin_top = -33.0 +margin_right = 27.0 +margin_bottom = -19.0 +text = "You" +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[connection signal="area_entered" from="." to="." method="_on_paddle_area_enter" binds=[ ]] diff --git a/networking/multiplayer_pong/pong.gd b/networking/multiplayer_pong/pong.gd new file mode 100644 index 00000000..de9bb4e9 --- /dev/null +++ b/networking/multiplayer_pong/pong.gd @@ -0,0 +1,52 @@ + +extends Node2D + +const SCORE_TO_WIN=10 + +var score_left = 0 +var score_right = 0 + +signal game_finished() + +sync func update_score(add_to_left): + if (add_to_left): + + score_left+=1 + get_node("score_left").set_text( str(score_left) ) + else: + + score_right+=1 + get_node("score_right").set_text( str(score_right) ) + + var game_ended = false + + if (score_left==SCORE_TO_WIN): + get_node("winner_left").show() + game_ended=true + elif (score_right==SCORE_TO_WIN): + get_node("winner_right").show() + game_ended=true + + if (game_ended): + get_node("exit_game").show() + get_node("ball").rpc("stop") + +func _on_exit_game_pressed(): + emit_signal("game_finished") + +func _ready(): + + # by default, all nodes in server inherit from master + # while all nodes in clients inherit from slave + + if (get_tree().is_network_server()): + #if in the server, get control of player 2 to the other peeer, this function is tree recursive by default + get_node("player2").set_network_master(get_tree().get_network_connected_peers()[0]) + else: + #if in the client, give control of player 2 to itself, this function is tree recursive by default + get_node("player2").set_network_master(get_tree().get_network_unique_id()) + + #let each paddle know which one is left, too + get_node("player1").left=true + get_node("player2").left=false + print("unique id: ",get_tree().get_network_unique_id()) diff --git a/networking/multiplayer_pong/pong.tscn b/networking/multiplayer_pong/pong.tscn new file mode 100644 index 00000000..63cb4ebf --- /dev/null +++ b/networking/multiplayer_pong/pong.tscn @@ -0,0 +1,120 @@ +[gd_scene load_steps=12 format=2] + +[ext_resource path="res://pong.gd" type="Script" id=1] +[ext_resource path="res://separator.png" type="Texture" id=2] +[ext_resource path="res://paddle.tscn" type="PackedScene" id=3] +[ext_resource path="res://ball.tscn" type="PackedScene" id=4] + +[node name="pong" type="Node2D"] + +script = ExtResource( 1 ) + +[node name="separator" type="Sprite" parent="."] + +position = Vector2( 320, 200 ) +texture = ExtResource( 2 ) + +[node name="player1" parent="." instance=ExtResource( 3 )] + +position = Vector2( 32.49, 188.622 ) + +[node name="sprite" parent="player1"] + +self_modulate = Color( 1, 0, 0.960938, 1 ) + +[node name="player2" parent="." instance=ExtResource( 3 )] + +position = Vector2( 608.88, 188.622 ) + +[node name="sprite" parent="player2"] + +self_modulate = Color( 0, 0.929688, 1, 1 ) + +[node name="ball" parent="." instance=ExtResource( 4 )] + +position = Vector2( 320.387, 189.525 ) + +[node name="score_left" type="Label" parent="."] + +focus_ignore_mouse = true +focus_stop_mouse = true +size_flags_horizontal = 2 +size_flags_vertical = 0 +margin_left = 240.0 +margin_top = 10.0 +margin_right = 280.0 +margin_bottom = 30.0 +text = "0" +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="score_right" type="Label" parent="."] + +focus_ignore_mouse = true +focus_stop_mouse = true +size_flags_horizontal = 2 +size_flags_vertical = 0 +margin_left = 360.0 +margin_top = 10.0 +margin_right = 400.0 +margin_bottom = 30.0 +text = "0" +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="winner_left" type="Label" parent="."] + +visible = false +focus_ignore_mouse = true +focus_stop_mouse = true +size_flags_horizontal = 2 +size_flags_vertical = 0 +margin_left = 190.0 +margin_top = 170.0 +margin_right = 267.0 +margin_bottom = 184.0 +text = "The Winner!" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="winner_right" type="Label" parent="."] + +visible = false +focus_ignore_mouse = true +focus_stop_mouse = true +size_flags_horizontal = 2 +size_flags_vertical = 0 +margin_left = 380.0 +margin_top = 170.0 +margin_right = 457.0 +margin_bottom = 184.0 +text = "The Winner!" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="exit_game" type="Button" parent="."] + +visible = false +focus_ignore_mouse = false +focus_stop_mouse = true +size_flags_horizontal = 2 +size_flags_vertical = 2 +margin_left = 280.0 +margin_top = 340.0 +margin_right = 360.0 +margin_bottom = 360.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Exit Game" +flat = false + +[connection signal="pressed" from="exit_game" to="." method="_on_exit_game_pressed" binds=[ ]] +[editable path="player1"] +[editable path="player2"] diff --git a/networking/multiplayer_pong/project.godot b/networking/multiplayer_pong/project.godot new file mode 100644 index 00000000..d2244666 --- /dev/null +++ b/networking/multiplayer_pong/project.godot @@ -0,0 +1,21 @@ +[application] + +icon = "res://icon.png" +main_scene = "res://lobby.tscn" +name = "Pong Multiplayer" + +[display] + +stretch_2d = true +window/height = 400 +window/width = 640 + +[input] + +move_down = [ InputEvent(KEY,16777234) ] +move_up = [ InputEvent(KEY,16777232) ] + +[rendering] + +viewport/default_clear_color = Color( 0, 0, 0, 1 ) + diff --git a/networking/multiplayer_pong/separator.png b/networking/multiplayer_pong/separator.png new file mode 100644 index 00000000..56874a59 Binary files /dev/null and b/networking/multiplayer_pong/separator.png differ diff --git a/networking/multiplayer_pong/separator.png.import b/networking/multiplayer_pong/separator.png.import new file mode 100644 index 00000000..01df17d9 --- /dev/null +++ b/networking/multiplayer_pong/separator.png.import @@ -0,0 +1,23 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex" + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true diff --git a/networking/simple_multiplayer/bomb.gd b/networking/simple_multiplayer/bomb.gd new file mode 100644 index 00000000..c26663d4 --- /dev/null +++ b/networking/simple_multiplayer/bomb.gd @@ -0,0 +1,23 @@ +extends Area2D + +var in_area = [] +var owner + +# Called from the animation +func explode(): + if (not is_network_master()): + # But will call explosion only on master + return + for p in in_area: + if (p.has_method("exploded")): + p.rpc("exploded", owner) # Exploded has a master keyword, so it will only be received by the master + +func done(): + queue_free() + +func _on_bomb_body_enter(body): + if (not body in in_area): + in_area.append(body) + +func _on_bomb_body_exit(body): + in_area.erase(body) diff --git a/networking/simple_multiplayer/bomb.tscn b/networking/simple_multiplayer/bomb.tscn new file mode 100644 index 00000000..00f4f103 --- /dev/null +++ b/networking/simple_multiplayer/bomb.tscn @@ -0,0 +1,211 @@ +[gd_scene load_steps=10 format=2] + +[ext_resource path="res://bomb.gd" type="Script" id=1] +[ext_resource path="res://brickfloor.png" type="Texture" id=2] +[ext_resource path="res://explosion.png" type="Texture" id=3] + +[sub_resource type="RectangleShape2D" id=1] + +custom_solver_bias = 0.0 +extents = Vector2( 8, 96 ) + +[sub_resource type="RectangleShape2D" id=2] + +custom_solver_bias = 0.0 +extents = Vector2( 96, 8 ) + +[sub_resource type="Curve" id=3] + +min_value = 0.0 +max_value = 2.0 +bake_resolution = 100 +_data = [ Vector2( 0.00150494, 0.398437 ), 0.0, 0.0, 0, 0, Vector2( 0.0152287, 1.42969 ), 0.0, 0.0, 0, 0, Vector2( 0.478607, 1.30078 ), 0.0, 0.0, 0, 0, Vector2( 1, 0.291016 ), 0.0, 0.0, 0, 0 ] + +[sub_resource type="CurveTexture" id=4] + +width = 2048 +curve = SubResource( 3 ) + +[sub_resource type="ParticlesMaterial" id=5] + +trail_divisor = 1 +emission_shape = 2 +emission_box_extents = Vector3( 80, 1, 1 ) +flag_align_y = false +flag_rotate_y = false +flag_disable_z = true +spread = 45.0 +flatness = 0.0 +gravity = Vector3( 0, 0, 0 ) +initial_velocity = 1.0 +initial_velocity_random = 0.0 +angular_velocity = 187.85 +angular_velocity_random = 1.0 +orbit_velocity = 0.0 +orbit_velocity_random = 0.0 +linear_accel = 0.0 +linear_accel_random = 0.0 +radial_accel = 0.0 +radial_accel_random = 0.0 +tangential_accel = 0.0 +tangential_accel_random = 0.0 +damping = 0.0 +damping_random = 0.0 +angle = 0.0 +angle_random = 0.0 +scale = 1.0 +scale_random = 0.0 +scale_curve = SubResource( 4 ) +color = Color( 1, 1, 1, 1 ) +hue_variation = 0.0 +hue_variation_random = 0.0 +anim_speed = 0.0 +anim_speed_random = 0.0 +anim_offset = 0.0 +anim_offset_random = 0.0 +anim_loop = false +_sections_unfolded = [ "Angular Velocity", "Emission Shape", "Gravity", "Scale" ] + +[sub_resource type="Animation" id=6] + +length = 4.0 +loop = false +step = 0.1 +tracks/0/type = "value" +tracks/0/path = NodePath("sprite:self_modulate") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/keys = { +"times": PoolFloatArray( 0, 0.4, 0.6, 0.8, 1.1, 1.3, 1.5, 1.8, 1.9, 2, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 3 ), +"transitions": PoolFloatArray( 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ), +"update": 0, +"values": [ Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 1 ), Color( 8, 8, 8, 1 ), Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 1 ), Color( 8, 8, 8, 1 ), Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 1 ), Color( 8, 8, 8, 1 ), Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 1 ), Color( 8, 8, 8, 1 ), Color( 1, 1, 1, 1 ), Color( 8, 8, 8, 1 ), Color( 1, 1, 1, 1 ), Color( 8, 8, 8, 1 ), Color( 1, 1, 1, 1 ), Color( 1, 1, 1, 0 ) ] +} +tracks/1/type = "method" +tracks/1/path = NodePath(".") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/imported = false +tracks/1/keys = { +"times": PoolFloatArray( 2.8, 3.4 ), +"transitions": PoolFloatArray( 1, 1 ), +"values": [ { +"args": [ ], +"method": "explode" +}, { +"args": [ ], +"method": "done" +} ] +} +tracks/2/type = "value" +tracks/2/path = NodePath("explosion1:emitting") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/imported = false +tracks/2/keys = { +"times": PoolFloatArray( 0, 2.8 ), +"transitions": PoolFloatArray( 1, 1 ), +"update": 1, +"values": [ false, true ] +} +tracks/3/type = "value" +tracks/3/path = NodePath("explosion2:emitting") +tracks/3/interp = 1 +tracks/3/loop_wrap = true +tracks/3/imported = false +tracks/3/keys = { +"times": PoolFloatArray( 0, 2.8 ), +"transitions": PoolFloatArray( 1, 1 ), +"update": 1, +"values": [ false, true ] +} + +[node name="bomb" type="Area2D"] + +input_pickable = true +gravity_vec = Vector2( 0, 1 ) +gravity = 98.0 +linear_damp = 0.1 +angular_damp = 1.0 +audio_bus_override = false +audio_bus_name = "Master" +script = ExtResource( 1 ) + +[node name="sprite" type="Sprite" parent="."] + +position = Vector2( -2.92606, -2.92606 ) +texture = ExtResource( 2 ) +region_enabled = true +region_rect = Rect2( 144, 0, 48, 48 ) +_sections_unfolded = [ "Region" ] + +[node name="shape1" type="CollisionShape2D" parent="."] + +shape = SubResource( 1 ) + +[node name="shape2" type="CollisionShape2D" parent="."] + +shape = SubResource( 2 ) + +[node name="explosion1" type="Particles2D" parent="."] + +emitting = false +amount = 8 +lifetime = 0.5 +one_shot = true +preprocess = 0.0 +speed_scale = 1.0 +explosiveness = 0.95 +randomness = 0.0 +fixed_fps = 0 +fract_delta = true +visibility_rect = Rect2( -100, -100, 200, 200 ) +local_coords = true +draw_order = 0 +process_material = SubResource( 5 ) +texture = ExtResource( 3 ) +normal_map = null +h_frames = 1 +v_frames = 1 +_sections_unfolded = [ "Process Material", "Textures", "Time" ] + +[node name="explosion2" type="Particles2D" parent="."] + +rotation = 1.57162 +emitting = false +amount = 8 +lifetime = 1.0 +one_shot = true +preprocess = 0.0 +speed_scale = 1.0 +explosiveness = 0.95 +randomness = 0.0 +fixed_fps = 0 +fract_delta = true +visibility_rect = Rect2( -100, -100, 200, 200 ) +local_coords = true +draw_order = 0 +process_material = SubResource( 5 ) +texture = ExtResource( 3 ) +normal_map = null +h_frames = 1 +v_frames = 1 +_sections_unfolded = [ "Process Material", "Textures", "Time" ] + +[node name="anim" type="AnimationPlayer" parent="."] + +playback_process_mode = 1 +playback_default_blend_time = 0.0 +root_node = NodePath("..") +anims/anim = SubResource( 6 ) +playback/active = true +playback/speed = 1.0 +blend_times = [ ] +autoplay = "anim" + +[connection signal="body_entered" from="." to="." method="_on_bomb_body_enter"] + +[connection signal="body_exited" from="." to="." method="_on_bomb_body_exit"] + + diff --git a/networking/simple_multiplayer/brickfloor.png b/networking/simple_multiplayer/brickfloor.png new file mode 100644 index 00000000..f863169b Binary files /dev/null and b/networking/simple_multiplayer/brickfloor.png differ diff --git a/networking/simple_multiplayer/brickfloor.png.import b/networking/simple_multiplayer/brickfloor.png.import new file mode 100644 index 00000000..e107c8a7 --- /dev/null +++ b/networking/simple_multiplayer/brickfloor.png.import @@ -0,0 +1,23 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/brickfloor.png-bab1cbace80ab627972eea565951db9e.stex" + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true diff --git a/networking/simple_multiplayer/charwalk.png b/networking/simple_multiplayer/charwalk.png new file mode 100644 index 00000000..2812b589 Binary files /dev/null and b/networking/simple_multiplayer/charwalk.png differ diff --git a/networking/simple_multiplayer/charwalk.png.import b/networking/simple_multiplayer/charwalk.png.import new file mode 100644 index 00000000..8c2253f5 --- /dev/null +++ b/networking/simple_multiplayer/charwalk.png.import @@ -0,0 +1,23 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/charwalk.png-a9f067962a6454cc2f52a6e82832cbc5.stex" + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true diff --git a/networking/simple_multiplayer/explosion.png b/networking/simple_multiplayer/explosion.png new file mode 100644 index 00000000..d0d550fc Binary files /dev/null and b/networking/simple_multiplayer/explosion.png differ diff --git a/networking/simple_multiplayer/explosion.png.import b/networking/simple_multiplayer/explosion.png.import new file mode 100644 index 00000000..12e5656f --- /dev/null +++ b/networking/simple_multiplayer/explosion.png.import @@ -0,0 +1,23 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/explosion.png-730076d88b39dbfd5c22ad71f1135b01.stex" + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true diff --git a/networking/simple_multiplayer/gamestate.gd b/networking/simple_multiplayer/gamestate.gd new file mode 100644 index 00000000..b887c6ad --- /dev/null +++ b/networking/simple_multiplayer/gamestate.gd @@ -0,0 +1,175 @@ +extends Node + +# Default game port +const DEFAULT_PORT = 10567 + +# Max number of players +const MAX_PEERS = 12 + +# Name for my player +var player_name = "The Warrior" + +# Names for remote players in id:name format +var players = {} + +# Signals to let lobby GUI know what's going on +signal player_list_changed() +signal connection_failed() +signal connection_succeeded() +signal game_ended() +signal game_error(what) + +# Callback from SceneTree +func _player_connected(id): + # This is not used in this demo, because _connected_ok is called for clients + # on success and will do the job. + pass + +# Callback from SceneTree +func _player_disconnected(id): + if (get_tree().is_network_server()): + if (has_node("/root/world")): # Game is in progress + emit_signal("game_error", "Player " + players[id] + " disconnected") + end_game() + else: # Game is not in progress + # If we are the server, send to the new dude all the already registered players + unregister_player(id) + for p_id in players: + # Erase in the server + rpc_id(p_id, "unregister_player", id) + +# Callback from SceneTree, only for clients (not server) +func _connected_ok(): + # Registration of a client beings here, tell everyone that we are here + rpc("register_player", get_tree().get_network_unique_id(), player_name) + emit_signal("connection_succeeded") + +# Callback from SceneTree, only for clients (not server) +func _server_disconnected(): + emit_signal("game_error", "Server disconnected") + end_game() + +# Callback from SceneTree, only for clients (not server) +func _connected_fail(): + get_tree().set_network_peer(null) # Remove peer + emit_signal("connection_failed") + +# Lobby management functions + +remote func register_player(id, name): + if (get_tree().is_network_server()): + # If we are the server, let everyone know about the new player + rpc_id(id, "register_player", 1, player_name) # Send myself to new dude + for p_id in players: # Then, for each remote player + rpc_id(id, "register_player", p_id, players[p_id]) # Send player to new dude + rpc_id(p_id, "register_player", id, name) # Send new dude to player + + players[id] = name + emit_signal("player_list_changed") + +remote func unregister_player(id): + players.erase(id) + emit_signal("player_list_changed") + +remote func pre_start_game(spawn_points): + # Change scene + var world = load("res://world.tscn").instance() + get_tree().get_root().add_child(world) + + get_tree().get_root().get_node("lobby").hide() + + var player_scene = load("res://player.tscn") + + for p_id in spawn_points: + var spawn_pos = world.get_node("spawn_points/" + str(spawn_points[p_id])).position + var player = player_scene.instance() + + player.set_name(str(p_id)) # Use unique ID as node name + player.position=spawn_pos + player.set_network_master(p_id) #set unique id as master + + if (p_id == get_tree().get_network_unique_id()): + # If node for this peer id, set name + player.set_player_name(player_name) + else: + # Otherwise set name from peer + player.set_player_name(players[p_id]) + + world.get_node("players").add_child(player) + + # Set up score + world.get_node("score").add_player(get_tree().get_network_unique_id(), player_name) + for pn in players: + world.get_node("score").add_player(pn, players[pn]) + + if (not get_tree().is_network_server()): + # Tell server we are ready to start + rpc_id(1, "ready_to_start", get_tree().get_network_unique_id()) + elif players.size() == 0: + post_start_game() + +remote func post_start_game(): + get_tree().set_pause(false) # Unpause and unleash the game! + +var players_ready = [] + +remote func ready_to_start(id): + assert(get_tree().is_network_server()) + + if (not id in players_ready): + players_ready.append(id) + + if (players_ready.size() == players.size()): + for p in players: + rpc_id(p, "post_start_game") + post_start_game() + +func host_game(name): + player_name = name + var host = NetworkedMultiplayerENet.new() + host.create_server(DEFAULT_PORT, MAX_PEERS) + get_tree().set_network_peer(host) + +func join_game(ip, name): + player_name = name + var host = NetworkedMultiplayerENet.new() + host.create_client(ip, DEFAULT_PORT) + get_tree().set_network_peer(host) + +func get_player_list(): + return players.values() + +func get_player_name(): + return player_name + +func begin_game(): + assert(get_tree().is_network_server()) + + # Create a dictionary with peer id and respective spawn points, could be improved by randomizing + var spawn_points = {} + spawn_points[1] = 0 # Server in spawn point 0 + var spawn_point_idx = 1 + for p in players: + spawn_points[p] = spawn_point_idx + spawn_point_idx += 1 + # Call to pre-start game with the spawn points + for p in players: + rpc_id(p, "pre_start_game", spawn_points) + + pre_start_game(spawn_points) + +func end_game(): + if (has_node("/root/world")): # Game is in progress + # End it + get_node("/root/world").queue_free() + + emit_signal("game_ended") + players.clear() + get_tree().set_network_peer(null) # End networking + +func _ready(): + get_tree().connect("network_peer_connected", self, "_player_connected") + get_tree().connect("network_peer_disconnected", self,"_player_disconnected") + get_tree().connect("connected_to_server", self, "_connected_ok") + get_tree().connect("connection_failed", self, "_connected_fail") + get_tree().connect("server_disconnected", self, "_server_disconnected") diff --git a/networking/simple_multiplayer/lobby.gd b/networking/simple_multiplayer/lobby.gd new file mode 100644 index 00000000..596a60aa --- /dev/null +++ b/networking/simple_multiplayer/lobby.gd @@ -0,0 +1,73 @@ +extends Control + +func _ready(): + # Called every time the node is added to the scene. + gamestate.connect("connection_failed", self, "_on_connection_failed") + gamestate.connect("connection_succeeded", self, "_on_connection_success") + gamestate.connect("player_list_changed", self, "refresh_lobby") + gamestate.connect("game_ended", self, "_on_game_ended") + gamestate.connect("game_error", self, "_on_game_error") + +func _on_host_pressed(): + if (get_node("connect/name").text == ""): + get_node("connect/error_label").text="Invalid name!" + return + + get_node("connect").hide() + get_node("players").show() + get_node("connect/error_label").text="" + + var name = get_node("connect/name").text + gamestate.host_game(name) + refresh_lobby() + +func _on_join_pressed(): + if (get_node("connect/name").text == ""): + get_node("connect/error_label").text="Invalid name!" + return + + var ip = get_node("connect/ip").text + if (not ip.is_valid_ip_address()): + get_node("connect/error_label").text="Invalid IPv4 address!" + return + + get_node("connect/error_label").text="" + get_node("connect/host").disabled=true + get_node("connect/join").disabled=true + + var name = get_node("connect/name").text + gamestate.join_game(ip, name) + # refresh_lobby() gets called by the player_list_changed signal + +func _on_connection_success(): + get_node("connect").hide() + get_node("players").show() + +func _on_connection_failed(): + get_node("connect/host").disabled=false + get_node("connect/join").disabled=false + get_node("connect/error_label").set_text("Connection failed.") + +func _on_game_ended(): + show() + get_node("connect").show() + get_node("players").hide() + get_node("connect/host").disabled=false + get_node("connect/join").disabled + +func _on_game_error(errtxt): + get_node("error").text=errtxt + get_node("error").popup_centered_minsize() + +func refresh_lobby(): + var players = gamestate.get_player_list() + players.sort() + get_node("players/list").clear() + get_node("players/list").add_item(gamestate.get_player_name() + " (You)") + for p in players: + get_node("players/list").add_item(p) + + get_node("players/start").disabled=not get_tree().is_network_server() + +func _on_start_pressed(): + gamestate.begin_game() diff --git a/networking/simple_multiplayer/lobby.tscn b/networking/simple_multiplayer/lobby.tscn new file mode 100644 index 00000000..4e75d317 --- /dev/null +++ b/networking/simple_multiplayer/lobby.tscn @@ -0,0 +1,214 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://lobby.gd" type="Script" id=1] + +[node name="lobby" type="Control"] + +anchor_right = 1 +anchor_bottom = 1 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +script = ExtResource( 1 ) + +[node name="players" type="Panel" parent="."] + +visible = false +margin_left = 393.0 +margin_top = 113.0 +margin_right = 645.0 +margin_bottom = 468.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="label" type="Label" parent="players"] + +margin_left = 26.0 +margin_top = 18.0 +margin_right = 142.0 +margin_bottom = 32.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "Awaiting Players..." +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="start" type="Button" parent="players"] + +margin_left = 68.0 +margin_top = 307.0 +margin_right = 193.0 +margin_bottom = 336.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "START!" +flat = false + +[node name="list" type="ItemList" parent="players"] + +margin_left = 25.0 +margin_top = 37.0 +margin_right = 229.0 +margin_bottom = 296.0 +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="connect" type="Panel" parent="."] + +margin_left = 370.0 +margin_top = 157.0 +margin_right = 647.0 +margin_bottom = 324.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 + +[node name="name_label" type="Label" parent="connect"] + +margin_left = 14.0 +margin_top = 11.0 +margin_right = 56.0 +margin_bottom = 25.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "Name:" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="name" type="LineEdit" parent="connect"] + +margin_left = 17.0 +margin_top = 30.0 +margin_right = 173.0 +margin_bottom = 54.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +text = "The Warrior" +expand_to_len = false +focus_mode = 2 +placeholder_alpha = 0.6 +caret_blink = false +caret_blink_speed = 0.65 + +[node name="ip_label" type="Label" parent="connect"] + +margin_left = 15.0 +margin_top = 66.0 +margin_right = 57.0 +margin_bottom = 80.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +text = "IP:" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="ip" type="LineEdit" parent="connect"] + +margin_left = 17.0 +margin_top = 85.0 +margin_right = 173.0 +margin_bottom = 109.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +text = "127.0.0.1" +expand_to_len = false +focus_mode = 2 +placeholder_alpha = 0.6 +caret_blink = false +caret_blink_speed = 0.65 + +[node name="host" type="Button" parent="connect"] + +margin_left = 181.0 +margin_top = 31.0 +margin_right = 246.0 +margin_bottom = 51.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Host" +flat = false + +[node name="join" type="Button" parent="connect"] + +margin_left = 181.0 +margin_top = 87.0 +margin_right = 246.0 +margin_bottom = 107.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +group = null +text = "Join" +flat = false + +[node name="error_label" type="Label" parent="connect"] + +margin_left = 15.0 +margin_top = 125.0 +margin_right = 257.0 +margin_bottom = 139.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +custom_colors/font_color = Color( 0.820312, 0.291595, 0.291595, 1 ) +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="error" type="AcceptDialog" parent="."] + +visible = false +margin_right = 55.0 +margin_bottom = 58.0 +rect_clip_content = false +mouse_filter = 0 +size_flags_horizontal = 2 +size_flags_vertical = 2 +popup_exclusive = false +window_title = "Alert!" +resizable = false +dialog_hide_on_ok = true + +[connection signal="pressed" from="players/start" to="." method="_on_start_pressed"] + +[connection signal="pressed" from="connect/host" to="." method="_on_host_pressed"] + +[connection signal="pressed" from="connect/join" to="." method="_on_join_pressed"] + + diff --git a/networking/simple_multiplayer/montserrat.otf b/networking/simple_multiplayer/montserrat.otf new file mode 100644 index 00000000..d5727507 Binary files /dev/null and b/networking/simple_multiplayer/montserrat.otf differ diff --git a/networking/simple_multiplayer/player.gd b/networking/simple_multiplayer/player.gd new file mode 100644 index 00000000..eec8024a --- /dev/null +++ b/networking/simple_multiplayer/player.gd @@ -0,0 +1,91 @@ +extends KinematicBody2D + +const MOTION_SPEED = 90.0 + +slave var slave_pos = Vector2() +slave var slave_motion = Vector2() + +export var stunned = false + +# Use sync because it will be called everywhere +sync func setup_bomb(name, pos, by_who): + var bomb = preload("res://bomb.tscn").instance() + bomb.set_name(name) # Ensure unique name for the bomb + bomb.position=pos + bomb.owner = by_who + # No need to set network mode to bomb, will be owned by master by default + get_node("../..").add_child(bomb) + +var current_anim = "" +var prev_bombing = false +var bomb_index = 0 + +func _fixed_process(delta): + var motion = Vector2() + + if (is_network_master()): + if (Input.is_action_pressed("move_left")): + motion += Vector2(-1, 0) + if (Input.is_action_pressed("move_right")): + motion += Vector2(1, 0) + if (Input.is_action_pressed("move_up")): + motion += Vector2(0, -1) + if (Input.is_action_pressed("move_down")): + motion += Vector2(0, 1) + + var bombing = Input.is_action_pressed("set_bomb") + + if (stunned): + bombing = false + motion = Vector2() + + if (bombing and not prev_bombing): + var bomb_name = get_name() + str(bomb_index) + var bomb_pos = position + rpc("setup_bomb", bomb_name, bomb_pos, get_tree().get_network_unique_id()) + + prev_bombing = bombing + + rset("slave_motion", motion) + rset("slave_pos", position) + else: + position=slave_pos + motion = slave_motion + + var new_anim = "standing" + if (motion.y < 0): + new_anim = "walk_up" + elif (motion.y > 0): + new_anim = "walk_down" + elif (motion.x < 0): + new_anim = "walk_left" + elif (motion.x > 0): + new_anim = "walk_right" + + if (stunned): + new_anim = "stunned" + + if (new_anim != current_anim): + current_anim = new_anim + get_node("anim").play(current_anim) + + # FIXME: Use move_and_slide + move_and_slide(motion*MOTION_SPEED) + if (not is_network_master()): + slave_pos = position # To avoid jitter + +slave func stun(): + stunned = true + +master func exploded(by_who): + if (stunned): + return + rpc("stun") # Stun slaves + stun() # Stun master - could use sync to do both at once + +func set_player_name(name): + get_node("label").set_text(name) + +func _ready(): + stunned = false + slave_pos = position diff --git a/networking/simple_multiplayer/player.tscn b/networking/simple_multiplayer/player.tscn new file mode 100644 index 00000000..37f97925 --- /dev/null +++ b/networking/simple_multiplayer/player.tscn @@ -0,0 +1,199 @@ +[gd_scene load_steps=12 format=2] + +[ext_resource path="res://player.gd" type="Script" id=1] +[ext_resource path="res://charwalk.png" type="Texture" id=2] +[ext_resource path="res://montserrat.otf" type="DynamicFontData" id=3] + +[sub_resource type="CircleShape2D" id=1] + +custom_solver_bias = 0.0 +radius = 20.0 + +[sub_resource type="Animation" id=2] + +resource_name = "standing" +length = 0.8 +loop = true +step = 0.1 +tracks/0/type = "value" +tracks/0/path = NodePath("sprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/keys = { +"times": PoolFloatArray( 0, 0.2, 0.4, 0.6 ), +"transitions": PoolFloatArray( 1, 1, 1, 1 ), +"update": 1, +"values": [ 0, 4, 8, 12 ] +} + +[sub_resource type="Animation" id=3] + +resource_name = "stunned" +length = 1.2 +loop = false +step = 0.1 +tracks/0/type = "value" +tracks/0/path = NodePath("sprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/keys = { +"times": PoolFloatArray( 0 ), +"transitions": PoolFloatArray( 1 ), +"update": 1, +"values": [ 0 ] +} +tracks/1/type = "value" +tracks/1/path = NodePath("sprite:rotation_deg") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/imported = false +tracks/1/keys = { +"times": PoolFloatArray( 0, 1, 1.1 ), +"transitions": PoolFloatArray( 1, 0, 1 ), +"update": 0, +"values": [ 0.0, 720.0, 0.0 ] +} +tracks/2/type = "value" +tracks/2/path = NodePath(".:stunned") +tracks/2/interp = 1 +tracks/2/loop_wrap = true +tracks/2/imported = false +tracks/2/keys = { +"times": PoolFloatArray( 1 ), +"transitions": PoolFloatArray( 1 ), +"update": 1, +"values": [ false ] +} + +[sub_resource type="Animation" id=4] + +length = 0.8 +loop = true +step = 0.1 +tracks/0/type = "value" +tracks/0/path = NodePath("sprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/keys = { +"times": PoolFloatArray( 0, 0.2, 0.4, 0.6 ), +"transitions": PoolFloatArray( 1, 1, 1, 1 ), +"update": 1, +"values": [ 0, 4, 8, 12 ] +} + +[sub_resource type="Animation" id=5] + +length = 0.8 +loop = true +step = 0.2 +tracks/0/type = "value" +tracks/0/path = NodePath("sprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/keys = { +"times": PoolFloatArray( 0, 0.2, 0.4, 0.6 ), +"transitions": PoolFloatArray( 1, 1, 1, 1 ), +"update": 1, +"values": [ 1, 5, 9, 13 ] +} + +[sub_resource type="Animation" id=6] + +length = 0.8 +loop = true +step = 0.2 +tracks/0/type = "value" +tracks/0/path = NodePath("sprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/keys = { +"times": PoolFloatArray( 0, 0.2, 0.4, 0.6 ), +"transitions": PoolFloatArray( 1, 1, 1, 1 ), +"update": 1, +"values": [ 3, 7, 11, 15 ] +} + +[sub_resource type="Animation" id=7] + +length = 0.8 +loop = true +step = 0.2 +tracks/0/type = "value" +tracks/0/path = NodePath("sprite:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/keys = { +"times": PoolFloatArray( 0, 0.2, 0.4, 0.6 ), +"transitions": PoolFloatArray( 1, 1, 1, 1 ), +"update": 1, +"values": [ 2, 6, 10, 14 ] +} + +[sub_resource type="DynamicFont" id=8] + +size = 14 +use_mipmaps = false +use_filter = false +font_data = ExtResource( 3 ) + +[node name="player" type="KinematicBody2D"] + +input_pickable = false +collision_layer = 1 +collision_mask = 1 +collision/safe_margin = 0.08 +script = ExtResource( 1 ) +stunned = false + +[node name="sprite" type="Sprite" parent="."] + +position = Vector2( 0.0750351, 6.23615 ) +texture = ExtResource( 2 ) +offset = Vector2( -0.0750351, -6.23615 ) +vframes = 4 +hframes = 4 + +[node name="shape" type="CollisionShape2D" parent="."] + +shape = SubResource( 1 ) + +[node name="anim" type="AnimationPlayer" parent="."] + +playback_process_mode = 1 +playback_default_blend_time = 0.0 +root_node = NodePath("..") +anims/standing = SubResource( 2 ) +anims/stunned = SubResource( 3 ) +anims/walk_down = SubResource( 4 ) +anims/walk_left = SubResource( 5 ) +anims/walk_right = SubResource( 6 ) +anims/walk_up = SubResource( 7 ) +playback/active = true +playback/speed = 1.0 +blend_times = [ ] +autoplay = "" + +[node name="label" type="Label" parent="."] + +margin_left = -82.0 +margin_top = -35.0 +margin_right = 85.0 +margin_bottom = -14.0 +rect_clip_content = false +mouse_filter = 2 +size_flags_horizontal = 2 +size_flags_vertical = 0 +custom_fonts/font = SubResource( 8 ) +text = "Player 1" +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + + diff --git a/networking/simple_multiplayer/project.godot b/networking/simple_multiplayer/project.godot new file mode 100644 index 00000000..f26e2996 --- /dev/null +++ b/networking/simple_multiplayer/project.godot @@ -0,0 +1,22 @@ +[application] + +main_scene = "res://lobby.tscn" +name = "Multiplayer Bomber" + +[autoload] + +gamestate = "*res://gamestate.gd" + +[image_loader] + +filter = false +gen_mipmaps = false + +[input] + +move_down = [ InputEvent(KEY,16777234) ] +move_left = [ InputEvent(KEY,16777231) ] +move_right = [ InputEvent(KEY,16777233) ] +move_up = [ InputEvent(KEY,16777232) ] +set_bomb = [ InputEvent(KEY,32) ] + diff --git a/networking/simple_multiplayer/rock.gd b/networking/simple_multiplayer/rock.gd new file mode 100644 index 00000000..b7683267 --- /dev/null +++ b/networking/simple_multiplayer/rock.gd @@ -0,0 +1,11 @@ +extends KinematicBody2D + +# Sent to everyone else +slave func do_explosion(): + get_node("anim").play("explode") + +# Received by owner of the rock +master func exploded(by_who): + rpc("do_explosion") # Re-sent to slave rocks + get_node("../../score").rpc("increase_score", by_who) + do_explosion() diff --git a/networking/simple_multiplayer/rock.tscn b/networking/simple_multiplayer/rock.tscn new file mode 100644 index 00000000..26b37468 --- /dev/null +++ b/networking/simple_multiplayer/rock.tscn @@ -0,0 +1,73 @@ +[gd_scene load_steps=5 format=2] + +[ext_resource path="res://rock.gd" type="Script" id=1] +[ext_resource path="res://brickfloor.png" type="Texture" id=2] + +[sub_resource type="RectangleShape2D" id=1] + +custom_solver_bias = 0.0 +extents = Vector2( 24, 24 ) + +[sub_resource type="Animation" id=2] + +resource_name = "explode" +length = 1.0 +loop = false +step = 0.1 +tracks/0/type = "method" +tracks/0/path = NodePath(".") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/keys = { +"times": PoolFloatArray( 1 ), +"transitions": PoolFloatArray( 1 ), +"values": [ { +"args": [ ], +"method": "queue_free" +} ] +} +tracks/1/type = "value" +tracks/1/path = NodePath("sprite:visible") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/imported = false +tracks/1/keys = { +"times": PoolFloatArray( 0 ), +"transitions": PoolFloatArray( 1 ), +"update": 1, +"values": [ false ] +} + +[node name="rock" type="KinematicBody2D"] + +input_pickable = false +collision_layer = 1 +collision_mask = 1 +collision/safe_margin = 0.08 +script = ExtResource( 1 ) + +[node name="sprite" type="Sprite" parent="."] + +visible = false +texture = ExtResource( 2 ) +region_enabled = true +region_rect = Rect2( 96, 0, 48, 48 ) +_sections_unfolded = [ "Region" ] + +[node name="shape" type="CollisionShape2D" parent="."] + +shape = SubResource( 1 ) + +[node name="anim" type="AnimationPlayer" parent="."] + +playback_process_mode = 1 +playback_default_blend_time = 0.0 +root_node = NodePath("..") +anims/explode = SubResource( 2 ) +playback/active = true +playback/speed = 1.0 +blend_times = [ ] +autoplay = "" + + diff --git a/networking/simple_multiplayer/rock_bit.png b/networking/simple_multiplayer/rock_bit.png new file mode 100644 index 00000000..f4971815 Binary files /dev/null and b/networking/simple_multiplayer/rock_bit.png differ diff --git a/networking/simple_multiplayer/rock_bit.png.import b/networking/simple_multiplayer/rock_bit.png.import new file mode 100644 index 00000000..36c2f47c --- /dev/null +++ b/networking/simple_multiplayer/rock_bit.png.import @@ -0,0 +1,23 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/rock_bit.png-cd30ec3dce7edf848ee632b29d4d0c95.stex" + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +stream=false +size_limit=0 +detect_3d=true diff --git a/networking/simple_multiplayer/score.gd b/networking/simple_multiplayer/score.gd new file mode 100644 index 00000000..33142ebd --- /dev/null +++ b/networking/simple_multiplayer/score.gd @@ -0,0 +1,42 @@ +extends HBoxContainer + +var player_labels = {} + +func _process(delta): + var rocks_left = get_node("../rocks").get_child_count() + if (rocks_left == 0): + var winner_name = "" + var winner_score = 0 + for p in player_labels: + if (player_labels[p].score > winner_score): + winner_score = player_labels[p].score + winner_name = player_labels[p].name + + get_node("../winner").set_text("THE WINNER IS:\n" + winner_name) + get_node("../winner").show() + +sync func increase_score(for_who): + assert(for_who in player_labels) + var pl = player_labels[for_who] + pl.score += 1 + pl.label.set_text(pl.name + "\n" + str(pl.score)) + +func add_player(id, name): + var l = Label.new() + l.set_align(Label.ALIGN_CENTER) + l.set_text(name + "\n" + "0") + l.set_h_size_flags(SIZE_EXPAND_FILL) + var font = DynamicFont.new() + font.set_size(18) + font.set_font_data(preload("res://montserrat.otf")) + l.add_font_override("font", font) + add_child(l) + + player_labels[id] = { name = name, label = l, score = 0 } + +func _ready(): + get_node("../winner").hide() + set_process(true) + +func _on_exit_game_pressed(): + gamestate.end_game() diff --git a/networking/simple_multiplayer/tile_scene.tscn b/networking/simple_multiplayer/tile_scene.tscn new file mode 100644 index 00000000..583d7805 --- /dev/null +++ b/networking/simple_multiplayer/tile_scene.tscn @@ -0,0 +1,44 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://brickfloor.png" type="Texture" id=1] + +[sub_resource type="RectangleShape2D" id=1] + +custom_solver_bias = 0.0 +extents = Vector2( 24, 24 ) + +[node name="Node2D" type="Node2D"] + + +[node name="wall" type="Sprite" parent="."] + +position = Vector2( 24, 24 ) +texture = ExtResource( 1 ) +region = true +region_rect = Rect2( 0, 0, 48, 48 ) + +[node name="col" type="StaticBody2D" parent="wall"] + +input_pickable = false +shapes/0/shape = SubResource( 1 ) +shapes/0/transform = Transform2D( 1, 0, 0, 1, 0, 0 ) +shapes/0/trigger = false +collision_layer = 1 +collision_mask = 1 +constant_linear_velocity = Vector2( 0, 0 ) +constant_angular_velocity = 0.0 +friction = 1.0 +bounce = 0.0 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="wall/col"] + +shape = SubResource( 1 ) +trigger = false +_update_shape_index = 0 + +[node name="floor" type="Sprite" parent="."] + +position = Vector2( 72, 24 ) +texture = ExtResource( 1 ) +region = true +region_rect = Rect2( 48, 0, 48, 48 ) diff --git a/networking/simple_multiplayer/tileset.tres b/networking/simple_multiplayer/tileset.tres new file mode 100644 index 00000000..fb016c2e --- /dev/null +++ b/networking/simple_multiplayer/tileset.tres @@ -0,0 +1,27 @@ +[gd_resource type="TileSet" load_steps=2 format=2] + +[ext_resource path="res://brickfloor.png" type="Texture" id=1] + +[sub_resource type="RectangleShape2D" id=1] + +custom_solver_bias = 0.0 +extents = Vector2( 24, 24 ) + +[resource] + +0/name = "wall" +0/texture = ExtResource( 1 ) +0/tex_offset = Vector2( 0, 0 ) +0/region = Rect2( 0, 0, 48, 48 ) +0/occluder_offset = Vector2( 24, 24 ) +0/navigation_offset = Vector2( 24, 24 ) +0/shape_offset = Vector2( 24, 24 ) +0/shapes = [ SubResource( 1 ) ] +1/name = "floor" +1/texture = ExtResource( 1 ) +1/tex_offset = Vector2( 0, 0 ) +1/region = Rect2( 48, 0, 48, 48 ) +1/occluder_offset = Vector2( 24, 24 ) +1/navigation_offset = Vector2( 24, 24 ) +1/shape_offset = Vector2( 0, 0 ) +1/shapes = [ ] diff --git a/networking/simple_multiplayer/world.tscn b/networking/simple_multiplayer/world.tscn new file mode 100644 index 00000000..5edbfa66 --- /dev/null +++ b/networking/simple_multiplayer/world.tscn @@ -0,0 +1,431 @@ +[gd_scene load_steps=91 format=2] + +[ext_resource path="res://tileset.tres" type="TileSet" id=1] +[ext_resource path="res://rock.tscn" type="PackedScene" id=2] +[ext_resource path="res://score.gd" type="Script" id=3] +[ext_resource path="res://montserrat.otf" type="DynamicFontData" id=4] + +[sub_resource type="DynamicFont" id=1] + +size = 44 +use_mipmaps = false +use_filter = false +font_data = ExtResource( 4 ) + +[node name="world" type="Node2D"] + + +[node name="map" type="TileMap" parent="."] + +mode = 0 +tile_set = ExtResource( 1 ) +cell_size = Vector2( 48, 48 ) +cell_quadrant_size = 16 +cell/custom_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) +cell_half_offset = 2 +cell_tile_origin = 0 +cell_y_sort = false +collision_use_kinematic = false +collision_friction = 1.0 +collision_bounce = 0.0 +collision_layer = 1 +collision_mask = 1 +occluder_light_mask = 1 +tile_data = PoolIntArray( 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 65536, 0, 65537, 1, 65538, 1, 65539, 1, 65540, 1, 65541, 1, 65542, 1, 65543, 1, 65544, 1, 65545, 1, 65546, 1, 65547, 1, 65548, 1, 65549, 1, 65550, 1, 65551, 1, 65552, 1, 65553, 1, 65554, 1, 65555, 1, 65556, 0, 65557, 0, 131072, 0, 131073, 1, 131074, 0, 131075, 1, 131076, 0, 131077, 1, 131078, 0, 131079, 1, 131080, 0, 131081, 1, 131082, 0, 131083, 0, 131084, 0, 131085, 0, 131086, 0, 131087, 1, 131088, 0, 131089, 1, 131090, 0, 131091, 1, 131092, 0, 131093, 0, 196608, 0, 196609, 1, 196610, 1, 196611, 1, 196612, 0, 196613, 1, 196614, 1, 196615, 1, 196616, 1, 196617, 1, 196618, 0, 196619, 1, 196620, 1, 196621, 1, 196622, 1, 196623, 1, 196624, 1, 196625, 1, 196626, 1, 196627, 1, 196628, 0, 196629, 0, 262144, 0, 262145, 1, 262146, 0, 262147, 1, 262148, 0, 262149, 1, 262150, 0, 262151, 1, 262152, 0, 262153, 1, 262154, 0, 262155, 1, 262156, 0, 262157, 1, 262158, 0, 262159, 1, 262160, 0, 262161, 1, 262162, 0, 262163, 1, 262164, 0, 262165, 0, 327680, 0, 327681, 1, 327682, 1, 327683, 1, 327684, 1, 327685, 1, 327686, 0, 327687, 1, 327688, 1, 327689, 1, 327690, 1, 327691, 1, 327692, 1, 327693, 1, 327694, 1, 327695, 1, 327696, 1, 327697, 1, 327698, 0, 327699, 1, 327700, 0, 327701, 0, 393216, 0, 393217, 1, 393218, 0, 393219, 1, 393220, 0, 393221, 1, 393222, 0, 393223, 1, 393224, 0, 393225, 1, 393226, 0, 393227, 1, 393228, 0, 393229, 0, 393230, 0, 393231, 0, 393232, 0, 393233, 1, 393234, 0, 393235, 1, 393236, 0, 393237, 0, 458752, 0, 458753, 1, 458754, 1, 458755, 1, 458756, 1, 458757, 1, 458758, 1, 458759, 1, 458760, 1, 458761, 1, 458762, 1, 458763, 1, 458764, 1, 458765, 1, 458766, 1, 458767, 1, 458768, 1, 458769, 1, 458770, 1, 458771, 1, 458772, 0, 458773, 0, 524288, 0, 524289, 1, 524290, 0, 524291, 0, 524292, 0, 524293, 1, 524294, 0, 524295, 1, 524296, 0, 524297, 1, 524298, 0, 524299, 1, 524300, 0, 524301, 0, 524302, 0, 524303, 1, 524304, 0, 524305, 1, 524306, 0, 524307, 1, 524308, 0, 524309, 0, 589824, 0, 589825, 1, 589826, 1, 589827, 1, 589828, 1, 589829, 1, 589830, 1, 589831, 1, 589832, 0, 589833, 1, 589834, 1, 589835, 1, 589836, 0, 589837, 1, 589838, 1, 589839, 1, 589840, 1, 589841, 1, 589842, 1, 589843, 1, 589844, 0, 589845, 0, 655360, 0, 655361, 1, 655362, 0, 655363, 1, 655364, 0, 655365, 1, 655366, 0, 655367, 1, 655368, 0, 655369, 1, 655370, 0, 655371, 1, 655372, 0, 655373, 1, 655374, 0, 655375, 1, 655376, 0, 655377, 1, 655378, 0, 655379, 1, 655380, 0, 655381, 0, 720896, 0, 720897, 1, 720898, 1, 720899, 1, 720900, 1, 720901, 1, 720902, 1, 720903, 1, 720904, 1, 720905, 1, 720906, 1, 720907, 1, 720908, 1, 720909, 1, 720910, 1, 720911, 1, 720912, 1, 720913, 1, 720914, 1, 720915, 1, 720916, 0, 720917, 0, 786432, 0, 786433, 0, 786434, 0, 786435, 0, 786436, 0, 786437, 0, 786438, 0, 786439, 0, 786440, 0, 786441, 0, 786442, 0, 786443, 0, 786444, 0, 786445, 0, 786446, 0, 786447, 0, 786448, 0, 786449, 0, 786450, 0, 786451, 0, 786452, 0, 786453, 0 ) +__meta__ = { +"_edit_lock_": true +} + +[node name="spawn_points" type="Node2D" parent="."] + +editor/display_folded = true + +[node name="0" type="Position2D" parent="spawn_points"] + +position = Vector2( 72, 72 ) + +[node name="1" type="Position2D" parent="spawn_points"] + +position = Vector2( 264, 216 ) + +[node name="2" type="Position2D" parent="spawn_points"] + +position = Vector2( 72, 456 ) + +[node name="3" type="Position2D" parent="spawn_points"] + +position = Vector2( 360, 552 ) + +[node name="4" type="Position2D" parent="spawn_points"] + +position = Vector2( 840, 360 ) + +[node name="5" type="Position2D" parent="spawn_points"] + +position = Vector2( 456, 264 ) + +[node name="6" type="Position2D" parent="spawn_points"] + +position = Vector2( 696, 264 ) + +[node name="7" type="Position2D" parent="spawn_points"] + +position = Vector2( 744, 456 ) + +[node name="8" type="Position2D" parent="spawn_points"] + +position = Vector2( 312, 456 ) + +[node name="9" type="Position2D" parent="spawn_points"] + +position = Vector2( 696, 72 ) + +[node name="10" type="Position2D" parent="spawn_points"] + +position = Vector2( 504, 72 ) + +[node name="11" type="Position2D" parent="spawn_points"] + +position = Vector2( 936, 72 ) + +[node name="rocks" type="Node2D" parent="."] + + +[node name="rock" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 120, 72 ) + +[node name="rock1" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 264, 168 ) + +[node name="rock2" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 264, 120 ) + +[node name="rock3" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 216, 72 ) + +[node name="rock4" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 264, 72 ) + +[node name="rock5" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 312, 72 ) + +[node name="rock6" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 552, 168 ) + +[node name="rock7" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 600, 168 ) + +[node name="rock8" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 552, 216 ) + +[node name="rock9" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 264, 312 ) + +[node name="rock10" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 120, 360 ) + +[node name="rock11" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 168, 360 ) + +[node name="rock12" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 216, 360 ) + +[node name="rock13" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 120, 264 ) + +[node name="rock14" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 168, 216 ) + +[node name="rock15" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 72, 360 ) + +[node name="rock16" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 72, 312 ) + +[node name="rock17" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 72, 264 ) + +[node name="rock18" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 360, 360 ) + +[node name="rock19" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 408, 360 ) + +[node name="rock20" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 504, 360 ) + +[node name="rock21" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 600, 360 ) + +[node name="rock22" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 648, 360 ) + +[node name="rock23" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 504, 456 ) + +[node name="rock24" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 552, 456 ) + +[node name="rock25" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 552, 408 ) + +[node name="rock26" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 360, 456 ) + +[node name="rock27" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 360, 504 ) + +[node name="rock28" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 264, 504 ) + +[node name="rock29" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 264, 552 ) + +[node name="rock30" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 168, 456 ) + +[node name="rock31" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 168, 504 ) + +[node name="rock32" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 72, 552 ) + +[node name="rock33" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 120, 552 ) + +[node name="rock34" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 504, 552 ) + +[node name="rock35" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 600, 552 ) + +[node name="rock36" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 648, 552 ) + +[node name="rock37" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 648, 504 ) + +[node name="rock38" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 456, 216 ) + +[node name="rock39" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 360, 216 ) + +[node name="rock40" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 360, 168 ) + +[node name="rock41" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 456, 120 ) + +[node name="rock42" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 456, 408 ) + +[node name="rock43" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 456, 456 ) + +[node name="rock44" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 456, 504 ) + +[node name="rock45" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 600, 264 ) + +[node name="rock46" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 600, 72 ) + +[node name="rock47" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 408, 72 ) + +[node name="rock48" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 792, 168 ) + +[node name="rock49" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 744, 168 ) + +[node name="rock50" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 744, 264 ) + +[node name="rock51" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 792, 264 ) + +[node name="rock52" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 744, 360 ) + +[node name="rock53" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 744, 408 ) + +[node name="rock54" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 792, 552 ) + +[node name="rock55" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 840, 552 ) + +[node name="rock56" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 840, 504 ) + +[node name="rock57" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 840, 312 ) + +[node name="rock58" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 840, 264 ) + +[node name="rock59" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 840, 216 ) + +[node name="rock60" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 840, 120 ) + +[node name="rock61" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 792, 72 ) + +[node name="rock62" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 840, 72 ) + +[node name="rock63" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 936, 216 ) + +[node name="rock64" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 936, 264 ) + +[node name="rock65" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 936, 408 ) + +[node name="rock66" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 888, 456 ) + +[node name="rock67" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 936, 456 ) + +[node name="rock68" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 792, 456 ) + +[node name="rock69" parent="rocks" instance=ExtResource( 2 )] + +position = Vector2( 840, 456 ) + +[node name="players" type="Node2D" parent="."] + + +[node name="score" type="HBoxContainer" parent="."] + +focus_ignore_mouse = false +focus_stop_mouse = false +size_flags_horizontal = 2 +size_flags_vertical = 2 +margin_left = 0.0 +margin_top = 0.0 +margin_right = 1024.0 +margin_bottom = 40.0 +alignment = 0 +script = ExtResource( 3 ) + +[node name="winner" type="Label" parent="."] + +focus_ignore_mouse = true +focus_stop_mouse = true +size_flags_horizontal = 2 +size_flags_vertical = 0 +margin_left = 0.0 +margin_top = 0.0 +margin_right = 1031.0 +margin_bottom = 617.0 +custom_fonts/font = SubResource( 1 ) +custom_colors/font_color_shadow = Color( 0, 0, 0, 1 ) +custom_constants/shadow_offset_x = 2 +custom_constants/shadow_offset_y = 2 +custom_constants/shadow_as_outline = 1 +text = "THE WINNER IS: +YOU" +align = 1 +valign = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="exit_game" type="Button" parent="winner"] + +process/pause_mode = 2 +focus_ignore_mouse = false +focus_stop_mouse = true +size_flags_horizontal = 2 +size_flags_vertical = 2 +margin_left = 384.0 +margin_top = 408.0 +margin_right = 649.0 +margin_bottom = 469.0 +custom_fonts/font = SubResource( 1 ) +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "EXIT GAME" +flat = false + +[connection signal="pressed" from="winner/exit_game" to="score" method="_on_exit_game_pressed" binds=[ ]]