2023-05-19

This commit is contained in:
Andrey Solomatin 2023-05-19 19:37:13 +03:00
parent ed9acaa143
commit f043ab1381
16 changed files with 562 additions and 13 deletions

View File

@ -9,6 +9,10 @@ var project_version: String = project_version_major + "." + project_version_mino
"." + project_version_patch
var project_locales: Array = TranslationServer.get_loaded_locales()
var current_map: String
var last_spawn: int = 0
# Called when the node enters the scene tree for the first time.
func _ready():
print(project_name, " v", project_version)
@ -17,7 +21,7 @@ func _ready():
randomize()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
func _process(_delta):
pass
@ -37,11 +41,62 @@ func get_dir_files(path):
return found_files
func host_setup(map: String):
create_world_category()
create_world_multiplayerspawner()
load_map(map)
Network.server_init()
func client_setup(remote_ip: String):
create_world_category()
create_world_multiplayerspawner()
Network.client_init(remote_ip)
func create_world_category():
var world = Node.new()
world.name = "World"
get_node("/root").add_child(world)
func create_world_multiplayerspawner():
var world_spawner = MultiplayerSpawner.new()
world_spawner.name = "WorldSpawner"
world_spawner.spawn_path = NodePath("../World")
world_spawner.add_spawnable_scene("res://scenes/maps/ttt_mc_rooftops_2016.tscn")
get_node("/root/").add_child(world_spawner)
func load_map(map: String):
pass
var level: Object
if map == "RANDOM_MAP":
current_map = "ttt_mc_rooftops_2016"
level = preload("res://scenes/maps/ttt_mc_rooftops_2016.tscn").instantiate()
else:
current_map = map.replace(".tscn", "")
level = load("res://scenes/maps/" + map).instantiate()
get_node("/root/World").add_child(level)
print(current_map, " map has been loaded")
func get_spawn(random := true):
var spawns_path = "/root/World/" + current_map + "/Spawns"
var spawns = get_node_or_null(spawns_path)
if spawns:
# Returns a random spawn point
if random:
var random_spawn = spawns.get_child(randi() % spawns.get_child_count()).global_transform
print("spawn_point from Game.get_spawn(random := true): ", random_spawn)
return random_spawn
# Returns ordinal spawn.
else:
var ordinal_spawn = spawns.get_child(last_spawn).global_transform
if last_spawn < spawns.get_child_count() - 1:
last_spawn += 1
else:
last_spawn = 0
return ordinal_spawn
else:
printerr("No spawns found!")

View File

@ -40,7 +40,7 @@ func _ready():
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
func _process(_delta):
pass
@ -78,3 +78,4 @@ func client_init(server_ip):
OS.alert("Failed to start multiplayer client")
return
multiplayer.multiplayer_peer = network
set_physics_process(true)

View File

@ -0,0 +1,49 @@
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
# Set by the authority, synchronized on spawn.
@export var player := 1 :
set(id):
player = id
# Give authority over the player input to the appropriate peer.
$PlayerInput.set_multiplayer_authority(id)
# Player synchronized input.
@onready var input = $PlayerInput
func _ready():
# Set the camera as current if we are this player.
if player == multiplayer.get_unique_id():
$Camera3D.current = true
# Only process on server.
# EDIT: Left the client simulate player movement too to compesate network latency.
# set_physics_process(multiplayer.is_server())
func _physics_process(delta):
# Add the gravity.
if not is_on_floor():
velocity.y -= gravity * delta
# Handle Jump.
if input.jumping and is_on_floor():
velocity.y = JUMP_VELOCITY
# Reset jump state.
input.jumping = false
# Handle movement.
var direction = (transform.basis * Vector3(input.direction.x, 0, input.direction.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()

View File

@ -0,0 +1,43 @@
[gd_scene load_steps=7 format=3 uid="uid://dkaqqf5anv0bf"]
[ext_resource type="Script" path="res://scenes/controllers/Player.gd" id="1_mh1m5"]
[ext_resource type="Script" path="res://scenes/controllers/PlayerInput.gd" id="2_arrkr"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_ghomm"]
properties/0/path = NodePath(".:player")
properties/0/spawn = true
properties/0/sync = false
properties/1/path = NodePath(".:position")
properties/1/spawn = true
properties/1/sync = true
properties/2/path = NodePath(".:velocity")
properties/2/spawn = true
properties/2/sync = true
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_y3b1o"]
properties/0/path = NodePath("PlayerInput:direction")
properties/0/spawn = false
properties/0/sync = true
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_s521v"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_b4ty1"]
[node name="Player" type="CharacterBody3D"]
script = ExtResource("1_mh1m5")
[node name="ServerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_ghomm")
[node name="PlayerInput" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_y3b1o")
script = ExtResource("2_arrkr")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_s521v")
disabled = true
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("CapsuleMesh_b4ty1")
[node name="Camera3D" type="Camera3D" parent="."]

View File

@ -0,0 +1,23 @@
extends MultiplayerSynchronizer
# Set via RPC to simulate is_action_just_pressed.
@export var jumping := false
# Synchronized property.
@export var direction := Vector2()
func _ready():
# Only process for the local player
set_process(get_multiplayer_authority() == multiplayer.get_unique_id())
@rpc("call_local")
func jump():
jumping = true
func _process(delta):
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
if Input.is_action_just_pressed("ui_accept"):
jump.rpc()

View File

@ -0,0 +1,43 @@
extends Node3D
const SPAWN_RANDOM := 5.0
func _ready():
# We only need to spawn players on the server.
if not multiplayer.is_server():
return
multiplayer.peer_connected.connect(add_player)
multiplayer.peer_disconnected.connect(del_player)
# Spawn already connected players
for id in multiplayer.get_peers():
add_player(id)
# Spawn the local player unless this is a dedicated server export.
if not OS.has_feature("dedicated_server"):
add_player(1)
func _exit_tree():
if not multiplayer.is_server():
return
multiplayer.peer_connected.disconnect(add_player)
multiplayer.peer_disconnected.disconnect(del_player)
func add_player(id: int):
var character = preload("res://scenes/demos/scene_replication/player.tscn").instantiate()
# Set player id.
character.player = id
# Randomize character position.
var pos := Vector2.from_angle(randf() * 2 * PI)
character.position = Vector3(pos.x * SPAWN_RANDOM * randf(), 0, pos.y * SPAWN_RANDOM * randf())
character.name = str(id)
$Players.add_child(character, true)
func del_player(id: int):
if not $Players.has_node(str(id)):
return
$Players.get_node(str(id)).queue_free()

View File

@ -0,0 +1,46 @@
[gd_scene load_steps=6 format=3 uid="uid://efwjm4hfeqf2"]
[ext_resource type="PackedScene" uid="uid://c6br8h3si2ypg" path="res://scenes/demos/scene_replication/sphere.tscn" id="1_8awbr"]
[ext_resource type="Script" path="res://scenes/demos/scene_replication/level.gd" id="1_p4gvl"]
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_s66iy"]
[sub_resource type="PlaneMesh" id="PlaneMesh_7cbpt"]
size = Vector2(20, 20)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kgp6f"]
albedo_color = Color(0.164706, 0.478431, 1, 1)
[node name="World" type="Node3D"]
script = ExtResource("1_p4gvl")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.35697, 3.4425)
[node name="Floor" type="StaticBody3D" parent="."]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor"]
shape = SubResource("WorldBoundaryShape3D_s66iy")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor"]
mesh = SubResource("PlaneMesh_7cbpt")
surface_material_override/0 = SubResource("StandardMaterial3D_kgp6f")
[node name="OmniLight3D" type="OmniLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 10.4214, 0)
light_energy = 10.0
omni_range = 20.2379
[node name="Objects" type="Node3D" parent="."]
[node name="Sphere" parent="Objects" instance=ExtResource("1_8awbr")]
[node name="Sphere2" parent="Objects" instance=ExtResource("1_8awbr")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.09399, 4.03106, 0)
[node name="Players" type="Node3D" parent="."]
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."]
_spawnable_scenes = PackedStringArray("res://scenes/demos/scene_replication/player.tscn")
spawn_path = NodePath("../Players")
spawn_limit = 4

View File

@ -0,0 +1,67 @@
extends Node
const PORT = 4433
func _ready():
# Start paused
get_tree().paused = true
# You can save bandwith by disabling server relay and peer notifications.
multiplayer.server_relay = false
# Automatically start the server in headless mode.
if DisplayServer.get_name() == "headless":
print("Automatically starting dedicated server")
_on_host_pressed.call_deferred()
func _on_host_pressed():
# Start as server
var peer = ENetMultiplayerPeer.new()
peer.create_server(PORT)
if peer.get_connection_status() == MultiplayerPeer.CONNECTION_DISCONNECTED:
OS.alert("Failed to start multiplayer server")
return
multiplayer.multiplayer_peer = peer
start_game()
func _on_connect_pressed():
# Start as client
var txt : String = $UI/Net/Options/Remote.text
if txt == "":
OS.alert("Need a remote to connect to.")
return
var peer = ENetMultiplayerPeer.new()
peer.create_client(txt, PORT)
if peer.get_connection_status() == MultiplayerPeer.CONNECTION_DISCONNECTED:
OS.alert("Failed to start multiplayer client")
return
multiplayer.multiplayer_peer = peer
start_game()
func start_game():
# Hide the UI and unpause to start the game.
$UI.hide()
get_tree().paused = false
# Only change level on the server.
# Clients will instantiate the level via the spawner.
if multiplayer.is_server():
change_level.call_deferred(load("res://scenes/demos/scene_replication/level.tscn"))
# Call this function deferred and only on the main authority (server).
func change_level(scene: PackedScene):
# Remove old level if any.
var level = $Level
for c in level.get_children():
level.remove_child(c)
c.queue_free()
# Add new level.
level.add_child(scene.instantiate())
# The server can restart the level by pressing HOME.
func _input(event):
if not multiplayer.is_server():
return
if event.is_action("ui_home") and Input.is_action_just_pressed("ui_home"):
change_level.call_deferred(load("res://scenes/demos/scene_replication/level.tscn"))

View File

@ -0,0 +1,56 @@
[gd_scene load_steps=2 format=3 uid="uid://bvnwjpd0f3oa0"]
[ext_resource type="Script" path="res://scenes/demos/scene_replication/multiplayer.gd" id="1_jiv4u"]
[node name="Multiplayer" type="Node"]
script = ExtResource("1_jiv4u")
[node name="Level" type="Node" parent="."]
[node name="LevelSpawner" type="MultiplayerSpawner" parent="."]
_spawnable_scenes = PackedStringArray("res://scenes/demos/scene_replication/level.tscn")
spawn_path = NodePath("../Level")
spawn_limit = 1
[node name="UI" type="Control" parent="."]
process_mode = 3
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Net" type="VBoxContainer" parent="UI"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Options" type="HBoxContainer" parent="UI/Net"]
layout_mode = 2
[node name="Label" type="Label" parent="UI/Net/Options"]
layout_mode = 2
text = "Direct:"
[node name="Host" type="Button" parent="UI/Net/Options"]
layout_mode = 2
text = "Host"
[node name="Connect" type="Button" parent="UI/Net/Options"]
layout_mode = 2
text = "Connect"
[node name="Remote" type="LineEdit" parent="UI/Net/Options"]
layout_mode = 2
size_flags_horizontal = 3
text = "127.0.0.1"
placeholder_text = "Remote Host"
caret_blink = true
caret_blink_interval = 0.5
[connection signal="pressed" from="UI/Net/Options/Host" to="." method="_on_host_pressed"]
[connection signal="pressed" from="UI/Net/Options/Connect" to="." method="_on_connect_pressed"]

View File

@ -0,0 +1,49 @@
extends CharacterBody3D
const SPEED = 5.0
const JUMP_VELOCITY = 4.5
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
# Set by the authority, synchronized on spawn.
@export var player := 1 :
set(id):
player = id
# Give authority over the player input to the appropriate peer.
$PlayerInput.set_multiplayer_authority(id)
# Player synchronized input.
@onready var input = $PlayerInput
func _ready():
# Set the camera as current if we are this player.
if player == multiplayer.get_unique_id():
$Camera3D.current = true
# Only process on server.
# EDIT: Left the client simulate player movement too to compesate network latency.
# set_physics_process(multiplayer.is_server())
func _physics_process(delta):
# Add the gravity.
if not is_on_floor():
velocity.y -= gravity * delta
# Handle Jump.
if input.jumping and is_on_floor():
velocity.y = JUMP_VELOCITY
# Reset jump state.
input.jumping = false
# Handle movement.
var direction = (transform.basis * Vector3(input.direction.x, 0, input.direction.y)).normalized()
if direction:
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()

View File

@ -0,0 +1,44 @@
[gd_scene load_steps=7 format=3 uid="uid://cw8w6clmxedi6"]
[ext_resource type="Script" path="res://scenes/demos/scene_replication/player.gd" id="1_hwwnj"]
[ext_resource type="Script" path="res://scenes/demos/scene_replication/player_input.gd" id="2_ju3m6"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_fb1vy"]
properties/0/path = NodePath(".:player")
properties/0/spawn = true
properties/0/sync = false
properties/1/path = NodePath(".:position")
properties/1/spawn = true
properties/1/sync = true
properties/2/path = NodePath(".:velocity")
properties/2/spawn = true
properties/2/sync = true
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_hoavk"]
properties/0/path = NodePath(".:direction")
properties/0/spawn = false
properties/0/sync = true
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_37qaq"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_jab13"]
[node name="Player" type="CharacterBody3D"]
script = ExtResource("1_hwwnj")
[node name="ServerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_fb1vy")
[node name="PlayerInput" type="MultiplayerSynchronizer" parent="."]
root_path = NodePath(".")
replication_config = SubResource("SceneReplicationConfig_hoavk")
script = ExtResource("2_ju3m6")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("CapsuleShape3D_37qaq")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("CapsuleMesh_jab13")
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.40432, 2.04444)

View File

@ -0,0 +1,23 @@
extends MultiplayerSynchronizer
# Set via RPC to simulate is_action_just_pressed.
@export var jumping := false
# Synchronized property.
@export var direction := Vector2()
func _ready():
# Only process for the local player
set_process(get_multiplayer_authority() == multiplayer.get_unique_id())
@rpc("call_local")
func jump():
jumping = true
func _process(delta):
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
direction = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
if Input.is_action_just_pressed("ui_accept"):
jump.rpc()

View File

@ -0,0 +1,37 @@
[gd_scene load_steps=5 format=3 uid="uid://c6br8h3si2ypg"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_mp5c4"]
friction = 0.0
bounce = 1.0
[sub_resource type="SphereMesh" id="SphereMesh_deuj3"]
[sub_resource type="SphereShape3D" id="SphereShape3D_h6jr2"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_q1o6k"]
properties/0/path = NodePath(".:linear_velocity")
properties/0/spawn = true
properties/0/sync = true
properties/1/path = NodePath(".:angular_velocity")
properties/1/spawn = true
properties/1/sync = true
properties/2/path = NodePath(".:position")
properties/2/spawn = true
properties/2/sync = true
properties/3/path = NodePath(".:rotation")
properties/3/spawn = true
properties/3/sync = true
[node name="Sphere" type="RigidBody3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.42886, 0)
physics_material_override = SubResource("PhysicsMaterial_mp5c4")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("SphereMesh_deuj3")
skeleton = NodePath("../CollisionShape3D")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("SphereShape3D_h6jr2")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_q1o6k")

File diff suppressed because one or more lines are too long

View File

@ -85,10 +85,12 @@ func _process(_delta):
func _on_start_game_button_pressed():
Game.host_setup(MapMenuButton.text)
hide()
func _on_connect_button_pressed():
Network.client_init(RemoteIPEdit.text)
Game.client_setup(RemoteIPEdit.text)
hide()
func _on_mapmenubutton_index_pressed(index):

View File

@ -1,6 +1,6 @@
[gd_resource type="Theme" load_steps=2 format=3 uid="uid://be7exrb7e0yaf"]
[ext_resource type="FontFile" uid="uid://upwwuu3r5dau" path="res://fonts/liberation/LiberationSans-Regular.ttf" id="1_nonnu"]
[ext_resource type="FontFile" uid="uid://b1jnhc0ltacdf" path="res://fonts/liberation/LiberationSans-Regular.ttf" id="1_nonnu"]
[resource]
default_font = ExtResource("1_nonnu")