Browse Source

networking demos were notworking, not areworking,

Juan Linietsky 8 years ago
parent
commit
da56d77d42
40 changed files with 2086 additions and 0 deletions
  1. 72 0
      networking/multiplayer_pong/ball.gd
  2. BIN
      networking/multiplayer_pong/ball.png
  3. 23 0
      networking/multiplayer_pong/ball.png.import
  4. 31 0
      networking/multiplayer_pong/ball.tscn
  5. BIN
      networking/multiplayer_pong/icon.png
  6. 23 0
      networking/multiplayer_pong/icon.png.import
  7. 106 0
      networking/multiplayer_pong/lobby.gd
  8. 145 0
      networking/multiplayer_pong/lobby.tscn
  9. 66 0
      networking/multiplayer_pong/paddle.gd
  10. BIN
      networking/multiplayer_pong/paddle.png
  11. 23 0
      networking/multiplayer_pong/paddle.png.import
  12. 51 0
      networking/multiplayer_pong/paddle.tscn
  13. 52 0
      networking/multiplayer_pong/pong.gd
  14. 120 0
      networking/multiplayer_pong/pong.tscn
  15. 21 0
      networking/multiplayer_pong/project.godot
  16. BIN
      networking/multiplayer_pong/separator.png
  17. 23 0
      networking/multiplayer_pong/separator.png.import
  18. 23 0
      networking/simple_multiplayer/bomb.gd
  19. 211 0
      networking/simple_multiplayer/bomb.tscn
  20. BIN
      networking/simple_multiplayer/brickfloor.png
  21. 23 0
      networking/simple_multiplayer/brickfloor.png.import
  22. BIN
      networking/simple_multiplayer/charwalk.png
  23. 23 0
      networking/simple_multiplayer/charwalk.png.import
  24. BIN
      networking/simple_multiplayer/explosion.png
  25. 23 0
      networking/simple_multiplayer/explosion.png.import
  26. 175 0
      networking/simple_multiplayer/gamestate.gd
  27. 73 0
      networking/simple_multiplayer/lobby.gd
  28. 214 0
      networking/simple_multiplayer/lobby.tscn
  29. BIN
      networking/simple_multiplayer/montserrat.otf
  30. 91 0
      networking/simple_multiplayer/player.gd
  31. 199 0
      networking/simple_multiplayer/player.tscn
  32. 22 0
      networking/simple_multiplayer/project.godot
  33. 11 0
      networking/simple_multiplayer/rock.gd
  34. 73 0
      networking/simple_multiplayer/rock.tscn
  35. BIN
      networking/simple_multiplayer/rock_bit.png
  36. 23 0
      networking/simple_multiplayer/rock_bit.png.import
  37. 42 0
      networking/simple_multiplayer/score.gd
  38. 44 0
      networking/simple_multiplayer/tile_scene.tscn
  39. 27 0
      networking/simple_multiplayer/tileset.tres
  40. 33 0
      networking/simple_multiplayer/world.tscn

+ 72 - 0
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()
+
+

BIN
networking/multiplayer_pong/ball.png


+ 23 - 0
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

+ 31 - 0
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

BIN
networking/multiplayer_pong/icon.png


+ 23 - 0
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

+ 106 - 0
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")
+	

+ 145 - 0
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"]
+
+

+ 66 - 0
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
+
+	
+		
+		

BIN
networking/multiplayer_pong/paddle.png


+ 23 - 0
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

+ 51 - 0
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=[  ]]

+ 52 - 0
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())

+ 120 - 0
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"]

+ 21 - 0
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 )
+

BIN
networking/multiplayer_pong/separator.png


+ 23 - 0
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

+ 23 - 0
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)

+ 211 - 0
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"]
+
+

BIN
networking/simple_multiplayer/brickfloor.png


+ 23 - 0
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

BIN
networking/simple_multiplayer/charwalk.png


+ 23 - 0
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

BIN
networking/simple_multiplayer/explosion.png


+ 23 - 0
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

+ 175 - 0
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")

+ 73 - 0
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()

+ 214 - 0
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"]
+
+

BIN
networking/simple_multiplayer/montserrat.otf


+ 91 - 0
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

+ 199 - 0
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
+
+

+ 22 - 0
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) ]
+

+ 11 - 0
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()

+ 73 - 0
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 = ""
+
+

BIN
networking/simple_multiplayer/rock_bit.png


+ 23 - 0
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

+ 42 - 0
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()

+ 44 - 0
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 )

+ 27 - 0
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 = [  ]

File diff suppressed because it is too large
+ 33 - 0
networking/simple_multiplayer/world.tscn


Some files were not shown because too many files changed in this diff