diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..abf0140 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "GDScript: Launch Project", + "type": "godot", + "request": "launch", + "project": "${workspaceFolder}", + "debug_collisions": false, + "debug_paths": false, + "debug_navigation": false, + "additional_options": "" + } + ] +} \ No newline at end of file diff --git a/scenes/follow_camera.gd b/scenes/follow_camera.gd index 39e5dc9..36ff51f 100644 --- a/scenes/follow_camera.gd +++ b/scenes/follow_camera.gd @@ -2,9 +2,9 @@ extends Camera2D class_name FollowCamera -@export var target: Node2D +@export var target: Node2D = null func _physics_process(_delta): - if target: + if target != null: position = target.position diff --git a/scenes/follow_camera.tscn b/scenes/follow_camera.tscn index 41672d6..3564fbb 100644 --- a/scenes/follow_camera.tscn +++ b/scenes/follow_camera.tscn @@ -1,5 +1,6 @@ -[gd_scene format=3 uid="uid://c5ck3dhekpwb8"] +[gd_scene load_steps=2 format=3 uid="uid://c5ck3dhekpwb8"] + +[ext_resource type="Script" path="res://scenes/follow_camera.gd" id="1_kvu2h"] [node name="FollowCamera" type="Camera2D"] -editor_draw_limits = true -editor_draw_drag_margin = true +script = ExtResource("1_kvu2h") diff --git a/scenes/game.gd b/scenes/game.gd index eb4ec09..ffc60db 100644 --- a/scenes/game.gd +++ b/scenes/game.gd @@ -9,13 +9,16 @@ var local_player_color: Color ## The [PeerNodeDirectory] instance child of this node. -@onready var peer_dir: PeerNodeDirectory = $"PeerNodeDirectory" +@export var peer_dir: PeerNodeDirectory = null ## The [PlayerNodeDirectory] instance child of this node. -@onready var player_dir: PlayerNodeDirectory = $"PlayerNodeDirectory" +@export var player_dir: PlayerNodeDirectory = null ## The [PhaseTracker] instance child of this node. -@onready var phase_tracker: PhaseTracker = $"PhaseTracker" +@export var phase_tracker: PhaseTracker = null + +## The [LevelManager] instance child of this node. +@export var level_manager: LevelManager = null ## Initialize some signals needed by this node to function properly. @@ -92,3 +95,4 @@ func _on_playerdir_playernode_possessed(old: int, new: int, playernode: PlayerNo func _on_main_start_confirmed() -> void: if multiplayer.is_server(): phase_tracker.rpc_set_phase.rpc(PhaseTracker.Phase.PLAYING) + level_manager.rpc_next_level.rpc() \ No newline at end of file diff --git a/scenes/game.tscn b/scenes/game.tscn index fb89615..d74ecbc 100644 --- a/scenes/game.tscn +++ b/scenes/game.tscn @@ -1,12 +1,21 @@ -[gd_scene load_steps=5 format=3 uid="uid://dyxwp5rnxiff7"] +[gd_scene load_steps=10 format=3 uid="uid://dyxwp5rnxiff7"] [ext_resource type="Script" path="res://scenes/game.gd" id="1_cdtng"] [ext_resource type="PackedScene" uid="uid://7ux0cl08c2ho" path="res://scenes/peernode_directory.tscn" id="1_p0gju"] [ext_resource type="PackedScene" uid="uid://d3jo4swi2c7ae" path="res://scenes/playernode_directory.tscn" id="2_pvlqw"] [ext_resource type="PackedScene" uid="uid://dc8pe5dnk8kbv" path="res://scenes/phase_tracker.tscn" id="4_kab4c"] +[ext_resource type="PackedScene" uid="uid://dnryhtnk21ep1" path="res://scenes/level_manager.tscn" id="5_tdqk1"] +[ext_resource type="PackedScene" uid="uid://g03qgetpy6b5" path="res://scenes/level_playlist.tscn" id="6_bk2n3"] +[ext_resource type="Script" path="res://scenes/level_playlist.gd" id="7_akb5s"] +[ext_resource type="PackedScene" uid="uid://coh35s53wjxoa" path="res://scenes/golf_level_1.tscn" id="8_h8dsk"] +[ext_resource type="PackedScene" uid="uid://dts4o84p3vj5c" path="res://scenes/golf_level_2.tscn" id="9_2kliu"] -[node name="Game" type="Node"] +[node name="Game" type="Node" node_paths=PackedStringArray("peer_dir", "player_dir", "phase_tracker", "level_manager")] script = ExtResource("1_cdtng") +peer_dir = NodePath("PeerNodeDirectory") +player_dir = NodePath("PlayerNodeDirectory") +phase_tracker = NodePath("PhaseTracker") +level_manager = NodePath("LevelManager") [node name="PeerNodeDirectory" parent="." instance=ExtResource("1_p0gju")] @@ -14,6 +23,14 @@ script = ExtResource("1_cdtng") [node name="PhaseTracker" parent="." instance=ExtResource("4_kab4c")] +[node name="LevelManager" parent="." node_paths=PackedStringArray("playlist", "player_dir") instance=ExtResource("5_tdqk1")] +playlist = NodePath("LevelPlaylist") +player_dir = NodePath("../PlayerNodeDirectory") + +[node name="LevelPlaylist" parent="LevelManager" instance=ExtResource("6_bk2n3")] +script = ExtResource("7_akb5s") +levels = Array[PackedScene]([ExtResource("8_h8dsk"), ExtResource("9_2kliu")]) + [connection signal="child_entered_tree" from="PeerNodeDirectory" to="." method="_on_peerdir_peernode_created"] [connection signal="child_exiting_tree" from="PeerNodeDirectory" to="." method="_on_peerdir_peernode_destroyed"] [connection signal="peernode_identified" from="PeerNodeDirectory" to="." method="_on_peerdir_peernode_identified"] diff --git a/scenes/golf_ball.gd b/scenes/golf_ball.gd index 573315e..a3b4756 100644 --- a/scenes/golf_ball.gd +++ b/scenes/golf_ball.gd @@ -34,7 +34,8 @@ var player_name: String = "Player": return player_name set(value): player_name = value - player_label.text = value + if player_label: + player_label.text = value ## The color of the player represented by this scene. var player_color: Color = Color.WHITE: @@ -45,12 +46,12 @@ var player_color: Color = Color.WHITE: modulate = value -func _on_putt(putt_vector: Vector2): +func _on_putt(putt_vector: Vector2) -> void: strokes += 1 velocity += putt_vector -func do_movement(delta: float): +func do_movement(delta: float) -> void: # How much the body should move in this physics step. var movement = velocity * delta # How many times the body collided in the current physics step. @@ -87,7 +88,7 @@ func do_movement(delta: float): velocity *= physics_bounce_coefficient -func apply_friction(delta): +func apply_friction(delta) -> void: var new_velocity_length = max(0.0, velocity.length() - physics_friction * delta) velocity = velocity.normalized() * new_velocity_length @@ -100,7 +101,7 @@ func check_has_entered_hole() -> bool: return check_has_stopped() and hole_controller.over_hole -func enter_hole(): +func enter_hole() -> void: in_hole = true visible = false hole_sound.play() @@ -108,7 +109,11 @@ func enter_hole(): print("[GolfBall] Entered hole in: ", strokes) -func _physics_process(delta): +func _ready() -> void: + player_label.text = player_name + + +func _physics_process(delta) -> void: if not in_hole: do_movement(delta) apply_friction(delta) diff --git a/scenes/golf_level.gd b/scenes/golf_level.gd new file mode 100644 index 0000000..743bc55 --- /dev/null +++ b/scenes/golf_level.gd @@ -0,0 +1,174 @@ +extends Node2D +class_name GolfLevel + +@export_category("Level Data") + +## Whether the [field camera] follows the active player or not. +@export var camera_follows_player: bool = false + + + +@export_category("References") + +## The [Camera2D] of this level. +@export var camera: Camera2D = null + +## The [GolfTee] of this level. +@export var tee: GolfTee = null + +## The [GolfHole] of this level. +@export var hole: GolfHole = null + +## The [TileMap] of this level. +@export var map: TileMap = null + + +## If on server, this variable indicates the [GolfLevel] to replicate on clients. +## +## The [GolfLevel] in question should not be added to the scene tree, or it will cause problems on the client acting as server. +## +## On clients, this variable is ignored. +var target: GolfLevel = null + + +## The [PlayerNodeDirectory] to use to determine which players to spawn on clients. +## +## It should be set on instantiation of a new [GolfLevel]. +var player_dir: PlayerNodeDirectory = null + + +## The [PackedScene] to initialize on clients as [TileMap]. +const tilemap_scene: PackedScene = preload("res://scenes/golf_tilemap.tscn") + +## The [PackedScene] to initialize on clients as [GolfTee]. +const tee_scene: PackedScene = preload("res://scenes/golf_tee.tscn") + +## The [PackedScene] to initialize on clients as [GolfHole]. +const hole_scene: PackedScene = preload("res://scenes/golf_hole.tscn") + +## The [PackedScene] to initialize on clients as [Camera2D]. +const camera_scene: PackedScene = preload("res://scenes/follow_camera.tscn") + + +## Replicate the [field target] from the server to the clients via RPC. +func build() -> void: + build_tilemap() + build_tilemap_cells() + build_tee() + build_hole() + build_camera() + + +## Replicate the [field map] of the [field target] to the remote [field map]. +func build_tilemap() -> void: + Log.peer(self, "Replicating map...") + var tmap: TileMap = target.map + rpc_build_tilemap.rpc(tmap.global_position, tmap.global_rotation, tmap.global_scale) + +## Create the [field map] on clients. +@rpc("authority", "call_remote", "reliable") +func rpc_build_tilemap(tposition: Vector2, trotation: float, tscale: Vector2): + Log.peer(self, "Building map...") + map = tilemap_scene.instantiate() + map.global_position = tposition + map.global_rotation = trotation + map.global_scale = tscale + add_child(map) + +## Replicate the cells of [field target]'s [field map] to the remote [field map]. +## +## The [field map] must be already created on clients. +## +## Only takes layer 0 into consideration. +func build_tilemap_cells() -> void: + Log.peer(self, "Replicating map cells...") + var tmap: TileMap = target.map + const layer = 0 + for coords in tmap.get_used_cells(layer): + var source_id: int = tmap.get_cell_source_id(0, coords) + var atlas_coords: Vector2i = tmap.get_cell_atlas_coords(0, coords) + var alternative_tile: int = tmap.get_cell_alternative_tile(0, coords) + rpc_build_tilemap_cell.rpc(layer, coords, source_id, atlas_coords, alternative_tile) + +## Create a cell of [field map] on clients. +@rpc("authority", "call_remote", "reliable") +func rpc_build_tilemap_cell( + layer: int, + coords: Vector2i, + source_id: int, + atlas_coords: Vector2i = Vector2i(-1, -1), + alternative_tile: int = 0 +): + Log.peer(self, "Building map cell: %s" % coords) + map.set_cell(layer, coords, source_id, atlas_coords, alternative_tile) + + +## Replicate the [field tee] of the [field target] to the remote [field tee]. +func build_tee() -> void: + Log.peer(self, "Replicating tee...") + var ttee: GolfTee = target.tee + rpc_build_tee.rpc(ttee.global_position) + rpc_build_tee_balls.rpc() + +## Create the [GolfTee] object on clients. +@rpc("authority", "call_remote", "reliable") +func rpc_build_tee(tposition: Vector2): + Log.peer(self, "Building tee...") + tee = tee_scene.instantiate() + tee.global_position = tposition + add_child(tee) + +## Create and initialize the [GolfBall] object on clients, at the [GolfTee]'s position. +@rpc("authority", "call_remote", "reliable") +func rpc_build_tee_balls(): + Log.peer(self, "Building tee balls...") + for playernode in player_dir.get_children(): + var ball: GolfBall = tee.create(playernode) + add_child(ball) + +## Create and initialize a [GolfBall] for a single [PlayerNode] with the given name. +@rpc("authority", "call_remote", "reliable") +func rpc_build_tee_ball(player_name: String): + Log.peer(self, "Building tee ball for: %s" % player_name) + var playernode: PlayerNode = player_dir.get_playernode(player_name) + var ball: GolfBall = tee.create(playernode) + add_child(ball) + + +## Replicate the [field hole] of the [field target] to the remote [field hole]. +func build_hole() -> void: + Log.peer(self, "Replicating hole...") + var thole: GolfHole = target.hole + rpc_build_hole.rpc(thole.global_position, thole.global_scale) + +## Create the [GolfHole] object on clients. +@rpc("authority", "call_remote", "reliable") +func rpc_build_hole(tposition: Vector2, tscale: Vector2): + Log.peer(self, "Building hole...") + hole = hole_scene.instantiate() + hole.global_position = tposition + hole.global_scale = tscale + add_child(hole) + + +## Replicate the [field camera] of the [field target] to the remote [field camera]. +func build_camera() -> void: + Log.peer(self, "Replicating camera...") + var tcamera: FollowCamera = target.camera + rpc_build_camera.rpc(tcamera.global_position, target.camera_follows_player) + +## Create the [Camera2D] object on clients. +@rpc("authority", "call_remote", "reliable") +func rpc_build_camera(tposition: Vector2, tfocus: bool): + Log.peer(self, "Building camera...") + camera = camera_scene.instantiate() + camera.global_position = tposition + if tfocus: + camera.target = null # TODO: Find local player + add_child(camera) + + +func _on_playernode_created(node: Node): + Log.peer(self, "Spawning new player...") + var playernode: PlayerNode = node as PlayerNode + rpc_build_tee_ball.rpc(playernode.player_name) diff --git a/scenes/golf_level.tscn b/scenes/golf_level.tscn new file mode 100644 index 0000000..66dcc01 --- /dev/null +++ b/scenes/golf_level.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://bph5j1gd6gnap"] + +[ext_resource type="Script" path="res://scenes/golf_level.gd" id="1_evup0"] + +[node name="GolfLevel" type="Node2D"] +script = ExtResource("1_evup0") diff --git a/scenes/golf_level_1.tscn b/scenes/golf_level_1.tscn index e50a392..4e9022d 100644 --- a/scenes/golf_level_1.tscn +++ b/scenes/golf_level_1.tscn @@ -1,22 +1,25 @@ -[gd_scene load_steps=4 format=3 uid="uid://coh35s53wjxoa"] +[gd_scene load_steps=6 format=3 uid="uid://coh35s53wjxoa"] -[ext_resource type="TileSet" uid="uid://17esn8g8xqik" path="res://tiles/hole_tile_set.tres" id="1_r070t"] -[ext_resource type="PackedScene" uid="uid://ca06elq8io5wu" path="res://scenes/golf_ball.tscn" id="2_ek6nk"] +[ext_resource type="Script" path="res://scenes/golf_level.gd" id="1_qljh7"] +[ext_resource type="PackedScene" uid="uid://c5vbxqhevocy7" path="res://scenes/golf_tee.tscn" id="2_arnp6"] +[ext_resource type="PackedScene" uid="uid://c5ck3dhekpwb8" path="res://scenes/follow_camera.tscn" id="2_xa6so"] [ext_resource type="PackedScene" uid="uid://df7t70153a3bb" path="res://scenes/golf_hole.tscn" id="3_mxcvd"] +[ext_resource type="PackedScene" uid="uid://dck5lkoxskrel" path="res://scenes/golf_tilemap.tscn" id="4_1xa4t"] -[node name="GolfLevel1" type="Node2D"] +[node name="GolfLevel" type="Node2D" node_paths=PackedStringArray("camera", "tee", "hole", "map")] +script = ExtResource("1_qljh7") +camera = NodePath("FollowCamera") +tee = NodePath("GolfTee") +hole = NodePath("GolfHole") +map = NodePath("TileMap") -[node name="Camera" type="Camera2D" parent="."] +[node name="FollowCamera" parent="." instance=ExtResource("2_xa6so")] -[node name="TileMap" type="TileMap" parent="."] -scale = Vector2(0.125, 0.125) -tile_set = ExtResource("1_r070t") -format = 2 -layer_0/tile_data = PackedInt32Array(131067, 2, 0, 196603, 2, 0, 262139, 2, 0, 327675, 2, 0, 393211, 2, 0, 458747, 2, 0, -196612, 1, 0, -524291, 2, 0, -458755, 2, 0, -393219, 2, 0, -327683, 2, 0, -262147, 1, 0, -589822, 2, 0, -524286, 2, 0, 65540, 2, 0, 131076, 2, 0, 196612, 2, 0, 262148, 2, 0, 327684, 2, 0, 393220, 2, 0, -196613, 1, 805306368, -262148, 1, 805306368, -589827, 2, 0, -589826, 2, 0, 655358, 2, 0, -589825, 2, 0, 655359, 2, 0, -655360, 2, 0, 589824, 2, 0, -655359, 2, 0, 589825, 2, 0, -655358, 2, 0, 524284, 1, 536870912, 458755, 1, 805306368, 589821, 1, 536870912, 524283, 1, 268435456, 589820, 1, 268435456, 655357, 1, 268435456, 589826, 1, 0, 524291, 1, 0, 458756, 1, 0, 524290, 1, 805306368, -196604, 2, 0, -131068, 2, 0, -65532, 2, 0, 4, 2, 0, -262140, 1, 536870912, -327677, 1, 536870912, -327678, 1, 268435456, -393214, 2, 0, -262141, 1, 268435456, -458750, 2, 0, -5, 2, 0, 65531, 2, 0, -131077, 2, 0, -65541, 2, 0, 65535, 1, 805306368, 131071, 1, 268435456, 0, 1, 536870912, 65536, 1, 0) -metadata/_edit_lock_ = true - -[node name="GolfBall" parent="." instance=ExtResource("2_ek6nk")] +[node name="GolfTee" parent="." instance=ExtResource("2_arnp6")] position = Vector2(0, -112) [node name="GolfHole" parent="." instance=ExtResource("3_mxcvd")] position = Vector2(0, 100) + +[node name="TileMap" parent="." instance=ExtResource("4_1xa4t")] +layer_0/tile_data = PackedInt32Array(-458748, 2, 0, -524284, 2, 0, -589820, 2, 0, -655356, 2, 0, -655357, 2, 0, -655358, 2, 0, -655359, 2, 0, -655360, 2, 0, -589825, 2, 0, -589826, 2, 0, -589827, 2, 0, -589828, 2, 0, -589829, 2, 0, -524293, 2, 0, -458757, 2, 0, -393221, 2, 0, -327685, 2, 0, -262149, 2, 0, -393212, 2, 0, -327676, 2, 0, -262140, 2, 0, -196604, 2, 0, -131068, 2, 0, -65532, 2, 0, -196613, 2, 0, -131077, 2, 0, -65541, 2, 0, -5, 2, 0, 65531, 2, 0, 131067, 2, 0, 196603, 2, 0, 262139, 2, 0, 327675, 2, 0, 393211, 2, 0, 458747, 2, 0, 524283, 2, 0, 589819, 2, 0, 589820, 2, 0, 589821, 2, 0, 589822, 2, 0, 589823, 2, 0, 524288, 2, 0, 524289, 2, 0, 524290, 2, 0, 524291, 2, 0, 524292, 2, 0, 4, 2, 0, 65540, 2, 0, 131076, 2, 0, 196612, 2, 0, 262148, 2, 0, 327684, 2, 0, 393220, 2, 0, 458756, 2, 0) diff --git a/scenes/golf_level_2.tscn b/scenes/golf_level_2.tscn index b5ad6e7..0d3c8de 100644 --- a/scenes/golf_level_2.tscn +++ b/scenes/golf_level_2.tscn @@ -1,19 +1,30 @@ [gd_scene load_steps=6 format=3 uid="uid://dts4o84p3vj5c"] [ext_resource type="PackedScene" uid="uid://c5ck3dhekpwb8" path="res://scenes/follow_camera.tscn" id="1_3a7ly"] +[ext_resource type="Script" path="res://scenes/golf_level.gd" id="1_ac6s6"] [ext_resource type="TileSet" uid="uid://17esn8g8xqik" path="res://tiles/hole_tile_set.tres" id="1_h4d2b"] -[ext_resource type="PackedScene" uid="uid://ca06elq8io5wu" path="res://scenes/golf_ball.tscn" id="2_244ns"] -[ext_resource type="Script" path="res://scenes/follow_camera.gd" id="2_l7nsy"] +[ext_resource type="PackedScene" uid="uid://c5vbxqhevocy7" path="res://scenes/golf_tee.tscn" id="3_6q2yk"] [ext_resource type="PackedScene" uid="uid://df7t70153a3bb" path="res://scenes/golf_hole.tscn" id="3_kt60g"] -[node name="GolfLevel2" type="Node2D"] +[node name="GolfLevel" type="Node2D" node_paths=PackedStringArray("camera", "tee", "hole", "map")] +script = ExtResource("1_ac6s6") +camera_follows_player = true +camera = NodePath("FollowCamera") +tee = NodePath("GolfTee") +hole = NodePath("GolfHole") +map = NodePath("TileMap") [node name="FollowCamera" parent="." node_paths=PackedStringArray("target") instance=ExtResource("1_3a7ly")] process_callback = 0 position_smoothing_enabled = true drag_vertical_enabled = true -script = ExtResource("2_l7nsy") -target = NodePath("../GolfBall") +target = NodePath("") + +[node name="GolfTee" parent="." instance=ExtResource("3_6q2yk")] +position = Vector2(0, -112) + +[node name="GolfHole" parent="." instance=ExtResource("3_kt60g")] +position = Vector2(0, 488) [node name="TileMap" type="TileMap" parent="."] scale = Vector2(0.125, 0.125) @@ -21,9 +32,3 @@ tile_set = ExtResource("1_h4d2b") format = 2 layer_0/tile_data = PackedInt32Array(131067, 2, 0, 196603, 2, 0, 262139, 2, 0, 327675, 2, 0, 393211, 2, 0, 458747, 2, 0, -196612, 1, 0, -524291, 2, 0, -458755, 2, 0, -393219, 2, 0, -327683, 2, 0, -262147, 1, 0, -589822, 2, 0, -524286, 2, 0, 65540, 2, 0, 131076, 2, 0, 196612, 2, 0, 262148, 2, 0, 327684, 2, 0, 393220, 2, 0, -196613, 1, 805306368, -262148, 1, 805306368, -589827, 2, 0, -589826, 2, 0, -589825, 2, 0, -655360, 2, 0, -655359, 2, 0, -655358, 2, 0, 524284, 1, 536870912, 458755, 1, 805306368, 589821, 1, 536870912, 524283, 1, 268435456, 589820, 1, 268435456, 524291, 1, 0, 458756, 1, 0, 524290, 1, 805306368, -196604, 2, 0, -131068, 2, 0, -65532, 2, 0, 4, 2, 0, -262140, 1, 536870912, -327677, 1, 536870912, -327678, 1, 268435456, -393214, 2, 0, -262141, 1, 268435456, -458750, 2, 0, -5, 2, 0, 65531, 2, 0, -131077, 2, 0, -65541, 2, 0, 655357, 2, 0, 720893, 2, 0, 786429, 2, 0, 851965, 2, 0, 589826, 2, 0, 655362, 2, 0, 720898, 2, 0, 786434, 2, 0, 851970, 2, 0, 917506, 2, 0, 983042, 2, 0, 917501, 2, 0, 983037, 2, 0, 1048573, 2, 0, 1114109, 1, 0, 1179644, 1, 0, 1245179, 1, 0, 1048578, 1, 268435456, 1114115, 1, 268435456, 1179652, 1, 268435456, 1114116, 1, 536870912, 1179653, 1, 536870912, 1048579, 1, 536870912, 1114108, 1, 805306368, 1179643, 1, 805306368, 1245178, 1, 805306368, 1441783, 1, 805306368, 1376248, 1, 805306368, 1441784, 1, 0, 1310713, 1, 805306368, 1376249, 1, 0, 1310714, 1, 0, 1245189, 1, 268435456, 1245190, 1, 536870912, 1310726, 1, 268435456, 1310727, 1, 536870912, 1376263, 1, 268435456, 1376264, 1, 536870912, 1441800, 1, 268435456, 1441801, 1, 536870912, 1507337, 1, 268435456, 1507338, 1, 536870912, 1572874, 1, 268435456, 1572875, 1, 536870912, 1638411, 1, 268435456, 1638412, 1, 536870912, 1703948, 1, 268435456, 1703949, 1, 536870912, 1769485, 1, 268435456, 1769486, 1, 536870912, 1834993, 1, 805306368, 1769458, 1, 805306368, 1834994, 1, 0, 1703923, 1, 805306368, 1769459, 1, 0, 1638388, 1, 805306368, 1703924, 1, 0, 1572853, 1, 805306368, 1638389, 1, 0, 1507318, 1, 805306368, 1572854, 1, 0, 1507319, 1, 0, 1900529, 2, 0, 1966065, 2, 0, 2031601, 2, 0, 2097137, 2, 0, 2162673, 2, 0, 1835022, 2, 0, 1900558, 2, 0, 1966094, 2, 0, 2031630, 2, 0, 2097166, 2, 0, 2162702, 2, 0, 2162701, 2, 0, 2162700, 2, 0, 2162699, 2, 0, 2162698, 2, 0, 2162697, 2, 0, 2162696, 2, 0, 2162695, 2, 0, 2162694, 2, 0, 2162693, 2, 0, 2162692, 2, 0, 2162691, 2, 0, 2162690, 2, 0, 2162689, 2, 0, 2162688, 2, 0, 2228223, 2, 0, 2228222, 2, 0, 2228221, 2, 0, 2228220, 2, 0, 2228219, 2, 0, 2228218, 2, 0, 2228217, 2, 0, 2228216, 2, 0, 2228215, 2, 0, 2228214, 2, 0, 2228213, 2, 0, 2228212, 2, 0, 2228211, 2, 0, 2228210, 2, 0, 2228209, 2, 0, 1245184, 3, 805306368, 1441791, 3, 805306368, 1507328, 3, 805306368, 1703935, 3, 805306368, 1769472, 3, 805306368, 1835005, 3, 805306368, 1572861, 3, 805306368, 1376258, 3, 805306368, 1638402, 3, 805306368, 1703931, 3, 805306368, 1507332, 3, 805306368) metadata/_edit_lock_ = true - -[node name="GolfBall" parent="." instance=ExtResource("2_244ns")] -position = Vector2(0, -112) - -[node name="GolfHole" parent="." instance=ExtResource("3_kt60g")] -position = Vector2(0, 488) diff --git a/scenes/golf_tee.gd b/scenes/golf_tee.gd index bb8a32d..6b80b53 100644 --- a/scenes/golf_tee.gd +++ b/scenes/golf_tee.gd @@ -1,2 +1,46 @@ extends Node2D class_name GolfTee + + +## The [GolfBall] [PackedScene] to [method spawn]. +const ball_scene: PackedScene = preload("res://scenes/golf_ball.tscn") + + +## Create a new [GolfBall] from [field ball_scene], initialize it with details from the given [PlayerNode], and return it. +## +## Note that this does not add the [GolfBall] to the scene tree, as it is the [GolfLevel]'s responsibility to do so. +func create(playernode: PlayerNode) -> GolfBall: + # Create the [GolfBall] + var obj: GolfBall = ball_scene.instantiate() + # Setup the initial values + obj.global_position = global_position + obj.player_name = playernode.player_name + obj.player_color = playernode.player_color + obj.set_multiplayer_authority(playernode.get_multiplayer_authority()) + # Create callables to be able to cleanup signals on destruction + var on_name_changed: Callable = _on_name_changed.bind(obj) + var on_color_changed: Callable = _on_color_changed.bind(obj) + var on_possessed: Callable = _on_possessed.bind(obj) + var on_cleanup: Callable = _on_cleanup.bind(playernode, on_name_changed, on_color_changed, on_possessed) + # Setup signals to keep properties updated + playernode.name_changed.connect(on_name_changed) + playernode.color_changed.connect(on_color_changed) + playernode.possessed.connect(on_possessed) + obj.tree_exiting.connect(on_cleanup) + # Return the created [GolfBall] + return obj + + +func _on_name_changed(_old: String, new: String, obj: GolfBall) -> void: + obj.player_name = new + +func _on_color_changed(_old: Color, new: Color, obj: GolfBall) -> void: + obj.player_color = new + +func _on_possessed(_old: int, new: int, obj: GolfBall) -> void: + obj.set_multiplayer_authority(new) + +func _on_cleanup(playernode: PlayerNode, on_name_changed: Callable, on_color_changed: Callable, on_possessed: Callable) -> void: + playernode.name_changed.disconnect(on_name_changed) + playernode.color_changed.disconnect(on_color_changed) + playernode.possessed.disconnect(on_possessed) diff --git a/scenes/golf_tilemap.tscn b/scenes/golf_tilemap.tscn index 5c2b61d..344f76c 100644 --- a/scenes/golf_tilemap.tscn +++ b/scenes/golf_tilemap.tscn @@ -3,6 +3,7 @@ [ext_resource type="TileSet" uid="uid://17esn8g8xqik" path="res://tiles/hole_tile_set.tres" id="1_uf2u7"] [node name="TileMap" type="TileMap"] +scale = Vector2(0.125, 0.125) tile_set = ExtResource("1_uf2u7") format = 2 layer_0/name = "Walls" diff --git a/scenes/level_manager.gd b/scenes/level_manager.gd index 69e1dda..856c88d 100644 --- a/scenes/level_manager.gd +++ b/scenes/level_manager.gd @@ -1,74 +1,39 @@ extends Node class_name LevelManager -## The levels that should be loaded by the server and sent to the clients. -const levels: Array[PackedScene] = [ - preload("res://scenes/golf_level_1.tscn"), - preload("res://scenes/golf_level_2.tscn"), -] +## The blank [GolfLevel] to initialize on clients. +const base_scene: PackedScene = preload("res://scenes/golf_level.tscn") -## The index of the current level in [field levels]. -var level_idx: int = -1 +## The [LevelPlaylist] to use to load [GolfLevel]s from. +@export var playlist: LevelPlaylist = null -## Class containing the components of a level. -class Components: - ## All levels must include a [TileMap] named "TileMap". - var tilemap: TileMap = null - - ## The [PackedScene] to initialize on clients as [TileMap]. - const tilemap_scene: PackedScene = preload("res://scenes/golf_tilemap.tscn") - - ## All levels must include a [GolfTee] named "GolfTee". - ## - ## It will be used to spawn the player - var tee: GolfTee = null - - ## The [PackedScene] to initialize on clients as [GolfTee]. - const tee_scene: PackedScene = preload("res://scenes/golf_tee.tscn") - - ## All levels must include a [GolfHole] named "GolfHole". - var hole: GolfHole = null - - ## The [PackedScene] to initialize on clients as [GolfHole]. - const hole_scene = preload("res://scenes/golf_hole.tscn") - - ## Create a new [Components] instance from a [PackedScene]. - static func from_scene(scene: PackedScene) -> Components: - var obj = new() - obj.tilemap = scene.get_node("TileMap") - obj.tee = scene.get_node("GolfTee") - obj.hole = scene.get_node("GolfHole") - return obj - - ## Create the [TileMap] object on clients. - @rpc("authority", "call_remote", "reliable") - func rpc_build_tilemap(parent: Node): - var obj = tilemap_scene.instantiate() - parent.add_child(obj) - - ## Create the [GolfTee] object on clients. - @rpc("authority", "call_remote", "reliable") - func rpc_build_tee(parent: Node, position: Vector2): - var obj = tee_scene.instantiate() - obj.position = position - parent.add_child(obj) - - ## Create the [GolfHole] object on clients. - @rpc("authority", "call_remote", "reliable") - func rpc_build_hole(parent: Node, position: Vector2, scale: Vector2): - var obj = hole_scene.instantiate() - obj.position = position - obj.scale = scale - parent.add_child(obj) - - ## TODO: Place tiles +## The [PlayerNodeDirectory] to use to determine which players to spawn on clients. +@export var player_dir: PlayerNodeDirectory = null - -func get_current_level() -> PackedScene: - return levels[level_idx] +## The currently instantiated [GolfLevel]. +var level: GolfLevel = null -func load_level_components() -> Components: - return Components.from_scene(get_current_level()) - +## Create the empty [GolfLevel] object everywhere. +@rpc("authority", "call_local", "reliable") +func rpc_next_level(): + # Destroy the current level + if level != null: + level.target.queue_free() + player_dir.child_entered_tree.disconnect(level._on_playernode_created) + level.queue_free() + level = null + # Create the new level + level = base_scene.instantiate() + level.player_dir = player_dir + # Determine the next level + if multiplayer.is_server(): + var target = playlist.advance().instantiate() + level.target = target + player_dir.child_entered_tree.connect(level._on_playernode_created) + # Add the level to the tree + add_child(level) + # Start the replication + if multiplayer.is_server(): + level.build() diff --git a/scenes/level_manager.tscn b/scenes/level_manager.tscn index 22265d2..93a08b4 100644 --- a/scenes/level_manager.tscn +++ b/scenes/level_manager.tscn @@ -1,3 +1,6 @@ -[gd_scene format=3 uid="uid://dnryhtnk21ep1"] +[gd_scene load_steps=2 format=3 uid="uid://dnryhtnk21ep1"] + +[ext_resource type="Script" path="res://scenes/level_manager.gd" id="1_bv1lx"] [node name="LevelManager" type="Node"] +script = ExtResource("1_bv1lx") diff --git a/scenes/level_playlist.gd b/scenes/level_playlist.gd new file mode 100644 index 0000000..707cf52 --- /dev/null +++ b/scenes/level_playlist.gd @@ -0,0 +1,20 @@ +extends Node +class_name LevelPlaylist + + +## The [GolfLevel]s that should be sequentially loaded on the [LevelManager]. +@export var levels: Array[PackedScene] = [] + + +## The index of the current level in [field levels]. +var idx: int = -1 + + +## Advances to the next level in the playlist and returns it as a [PackedScene]. +## +## Returns null when the playlist is complete. +func advance() -> PackedScene: + idx += 1 + if idx >= len(levels): + return null + return levels[idx] diff --git a/scenes/level_playlist.tscn b/scenes/level_playlist.tscn new file mode 100644 index 0000000..880d1dc --- /dev/null +++ b/scenes/level_playlist.tscn @@ -0,0 +1,3 @@ +[gd_scene format=3 uid="uid://g03qgetpy6b5"] + +[node name="LevelPlaylist" type="Node"] diff --git a/scenes/phase_tracker.tscn b/scenes/phase_tracker.tscn index a0fcbe5..7695120 100644 --- a/scenes/phase_tracker.tscn +++ b/scenes/phase_tracker.tscn @@ -1,3 +1,6 @@ -[gd_scene format=3 uid="uid://dc8pe5dnk8kbv"] +[gd_scene load_steps=2 format=3 uid="uid://dc8pe5dnk8kbv"] + +[ext_resource type="Script" path="res://scenes/phase_tracker.gd" id="1_k51gn"] [node name="PhaseTracker" type="Node"] +script = ExtResource("1_k51gn")