Player turns are managed & sequenced in World

This commit is contained in:
Rob Kelly 2024-11-17 16:53:07 -07:00
parent 5cfb1fce96
commit 3d1da45673
21 changed files with 260 additions and 78 deletions

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=55 format=3 uid="uid://bm2o3mex10v11"]
[gd_scene load_steps=54 format=3 uid="uid://bm2o3mex10v11"]
[ext_resource type="Texture2D" path="res://assets/textures/grass_fairway/grass_fairway_albedo.dds" id="2_e4m27"]
[ext_resource type="Texture2D" path="res://assets/textures/grass_rough/grass_rough_albedo.dds" id="4_p0awb"]
@ -9,7 +9,6 @@
[ext_resource type="Texture2D" path="res://assets/textures/sand_trap/sand_trap_albedo.dds" id="6_nlea7"]
[ext_resource type="Texture2D" path="res://assets/textures/sand_trap/sand_trap_normal.dds" id="7_f25dn"]
[ext_resource type="Texture2D" uid="uid://con5a36t6n6sq" path="res://assets/textures/clear_sea_water_2048x2048.png" id="7_t86h2"]
[ext_resource type="PackedScene" uid="uid://cy7t2tc4y3b4" path="res://src/player/shot_setup/shot_setup.tscn" id="8_h44v5"]
[ext_resource type="Texture2D" path="res://assets/textures/mountain_rock/mountain_rock_albedo.dds" id="8_ujbjy"]
[ext_resource type="Script" path="res://src/world/ball_zone/ball_zone.gd" id="9_jwlau"]
[ext_resource type="Texture2D" path="res://assets/textures/mountain_rock/mountain_rock_normal.dds" id="9_rrguh"]
@ -306,8 +305,17 @@ shape = SubResource("BoxShape3D_x3wvm")
[node name="Flag" parent="Course" instance=ExtResource("13_6jtao")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 540, 4, 452)
[node name="ShotSetup" parent="Course" instance=ExtResource("8_h44v5")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 212.717, 4, 294.073)
[node name="PlayerSpawn1" type="Marker3D" parent="Course" groups=["PlayerSpawn"]]
transform = Transform3D(-0.842697, 0, -0.538388, 0, 1, 0, 0.538388, 0, -0.842697, 212.717, 4, 294.073)
[node name="PlayerSpawn2" type="Marker3D" parent="Course" groups=["PlayerSpawn"]]
transform = Transform3D(-0.842697, 0, -0.538388, 0, 1, 0, 0.538388, 0, -0.842697, 217.046, 4, 292.261)
[node name="PlayerSpawn3" type="Marker3D" parent="Course" groups=["PlayerSpawn"]]
transform = Transform3D(-0.842697, 0, -0.538388, 0, 1, 0, 0.538388, 0, -0.842697, 218.01, 4, 289.245)
[node name="PlayerSpawn4" type="Marker3D" parent="Course" groups=["PlayerSpawn"]]
transform = Transform3D(-0.842697, 0, -0.538388, 0, 1, 0, 0.538388, 0, -0.842697, 214.256, 4, 290.91)
[node name="GuardRail" parent="Course" instance=ExtResource("16_xtxyj")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 211.416, 4, 288.708)

View File

@ -169,7 +169,7 @@ bones/23/scale = Vector3(1, 1, 1)
visible = false
[node name="BoneAttachment3D" type="BoneAttachment3D" parent="Armature/Skeleton3D" index="6"]
transform = Transform3D(-0.944824, 0.316851, -0.0831417, -0.326395, -0.932135, 0.156815, -0.027812, 0.1753, 0.984122, -0.1687, 3.13445, 0.86185)
transform = Transform3D(-0.947388, 0.307308, -0.089546, -0.319114, -0.928605, 0.189367, -0.0249588, 0.207979, 0.977815, -0.157835, 3.16355, 0.865627)
bone_name = "Hand.R"
bone_idx = 11

View File

@ -1,4 +1,4 @@
class_name Club extends Node
class_name Club extends Resource
## Base type for gfolf clubs
enum Type {
@ -10,6 +10,7 @@ enum Type {
SPECIAL,
}
@export var name: String
@export var type: Club.Type
@export var _model: PackedScene

View File

@ -1,7 +1,7 @@
class_name ClubCatalogType extends Node
## Provides access to Club equipment
@onready var debug_driver: Club = %"DEBUG Driver"
@onready var debug_iron: Club = %"DEBUG Iron"
@onready var debug_wedge: Club = %"DEBUG Wedge"
@onready var debug_putter: Club = %"DEBUG Putter"
@export var debug_driver: Club = preload("res://src/equipment/clubs/drivers/debug_driver.tres")
@export var debug_iron: Club = preload("res://src/equipment/clubs/irons/debug_iron.tres")
@export var debug_wedge: Club = preload("res://src/equipment/clubs/wedges/debug_wedge.tres")
@export var debug_putter: Club = preload("res://src/equipment/clubs/putters/debug_putter.tres")

View File

@ -1,35 +1,6 @@
[gd_scene load_steps=7 format=3 uid="uid://f5qw4losi1ag"]
[gd_scene load_steps=2 format=3 uid="uid://brn02ebnp248v"]
[ext_resource type="Script" path="res://src/equipment/clubs/club_catalog.gd" id="1_omf4k"]
[ext_resource type="Script" path="res://src/equipment/clubs/club.gd" id="2_hl4eh"]
[ext_resource type="PackedScene" uid="uid://dekwor3a7kqio" path="res://assets/models/clubs/driver.gltf" id="3_gocak"]
[ext_resource type="PackedScene" uid="uid://btpn1j6jwv4pr" path="res://assets/models/clubs/iron.gltf" id="4_ile8w"]
[ext_resource type="PackedScene" uid="uid://d4bnvf34xenya" path="res://assets/models/clubs/wedge.gltf" id="5_qa3by"]
[ext_resource type="PackedScene" uid="uid://bo41mkkc70fcl" path="res://assets/models/clubs/putter.gltf" id="6_8bavt"]
[node name="ClubCatalog" type="Node"]
script = ExtResource("1_omf4k")
[node name="DEBUG Driver" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("2_hl4eh")
type = 1
_model = ExtResource("3_gocak")
[node name="DEBUG Iron" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("2_hl4eh")
type = 2
_model = ExtResource("4_ile8w")
[node name="DEBUG Wedge" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("2_hl4eh")
type = 3
_model = ExtResource("5_qa3by")
[node name="DEBUG Putter" type="Node" parent="."]
unique_name_in_owner = true
script = ExtResource("2_hl4eh")
type = 4
_model = ExtResource("6_8bavt")

View File

@ -0,0 +1,10 @@
[gd_resource type="Resource" script_class="Club" load_steps=3 format=3 uid="uid://c1pnqsddvey3m"]
[ext_resource type="Script" path="res://src/equipment/clubs/club.gd" id="1_7mcyu"]
[ext_resource type="PackedScene" uid="uid://dekwor3a7kqio" path="res://assets/models/clubs/driver.gltf" id="1_ug675"]
[resource]
script = ExtResource("1_7mcyu")
name = "DEBUG Driver"
type = 1
_model = ExtResource("1_ug675")

View File

@ -0,0 +1,10 @@
[gd_resource type="Resource" script_class="Club" load_steps=3 format=3 uid="uid://ck17u5yn6k0bi"]
[ext_resource type="PackedScene" uid="uid://btpn1j6jwv4pr" path="res://assets/models/clubs/iron.gltf" id="1_5wgil"]
[ext_resource type="Script" path="res://src/equipment/clubs/club.gd" id="1_6ksva"]
[resource]
script = ExtResource("1_6ksva")
name = "DEBUG Iron"
type = 2
_model = ExtResource("1_5wgil")

View File

@ -0,0 +1,10 @@
[gd_resource type="Resource" script_class="Club" load_steps=3 format=3 uid="uid://dagld0q5krapu"]
[ext_resource type="PackedScene" uid="uid://bo41mkkc70fcl" path="res://assets/models/clubs/putter.gltf" id="1_ghpah"]
[ext_resource type="Script" path="res://src/equipment/clubs/club.gd" id="1_qlirk"]
[resource]
script = ExtResource("1_qlirk")
name = "DEBUG Putter"
type = 4
_model = ExtResource("1_ghpah")

View File

@ -0,0 +1,10 @@
[gd_resource type="Resource" script_class="Club" load_steps=3 format=3 uid="uid://dthtc1no2c4wy"]
[ext_resource type="Script" path="res://src/equipment/clubs/club.gd" id="1_gy3nk"]
[ext_resource type="PackedScene" uid="uid://d4bnvf34xenya" path="res://assets/models/clubs/wedge.gltf" id="1_voves"]
[resource]
script = ExtResource("1_gy3nk")
name = "DEBUG Wedge"
type = 3
_model = ExtResource("1_voves")

View File

@ -0,0 +1,15 @@
[gd_resource type="Resource" script_class="WorldPlayer" load_steps=6 format=3 uid="uid://crock3revdn73"]
[ext_resource type="Resource" uid="uid://c1pnqsddvey3m" path="res://src/equipment/clubs/drivers/debug_driver.tres" id="1_sn8fd"]
[ext_resource type="Resource" uid="uid://ck17u5yn6k0bi" path="res://src/equipment/clubs/irons/debug_iron.tres" id="2_piku2"]
[ext_resource type="Resource" uid="uid://dagld0q5krapu" path="res://src/equipment/clubs/putters/debug_putter.tres" id="3_tytwr"]
[ext_resource type="Script" path="res://src/player/world_player.gd" id="4_8ybyj"]
[ext_resource type="Resource" uid="uid://dthtc1no2c4wy" path="res://src/equipment/clubs/wedges/debug_wedge.tres" id="5_wm4ae"]
[resource]
script = ExtResource("4_8ybyj")
name = "DEBUG Gfolfer"
driver = ExtResource("1_sn8fd")
iron = ExtResource("2_piku2")
wedge = ExtResource("5_wm4ae")
putter = ExtResource("3_tytwr")

View File

@ -1,5 +1,7 @@
class_name ShotSetup extends Node3D
signal finished(source: ShotSetup)
enum Phase {
AIM,
POWER_ADJUST,
@ -44,6 +46,8 @@ const WASTED_BALL_RETURN_DELAY := 3.5
## Keep projection visible
@export var keep_projection := false
var player: WorldPlayer
var base_speed: float = ProjectSettings.get_setting("game/config/controls/camera/free_camera_speed")
var x_sensitivity: float = ProjectSettings.get_setting(
@ -63,16 +67,16 @@ var invert_pitch: bool = ProjectSettings.get_setting("game/config/controls/camer
var control_disabled := false
var hud: ShotHUD:
get:
return world.ui.shot_hud if world and world.ui else null
var phase: Phase:
var phase: Phase = Phase.FINISHED:
set(value):
if value != phase:
_on_phase_change(value)
phase = value
var hud: ShotHUD:
get:
return world.ui.shot_hud if world and world.ui else null
var club: Club.Type:
set(value):
if value != club:
@ -95,6 +99,7 @@ var shot_curve: float:
var _free_camera: FreeCamera
var _returning_free_camera := false
var _restart_queued := false
var _tracking_camera: OrbitalCamera
@ -134,15 +139,17 @@ var _tracking_camera: OrbitalCamera
@onready var camera_distance := zoom.position.z:
set = _set_camera_distance
@onready var phys_ball_scene := preload("res://src/player/physics_ball/physics_ball.tscn")
@onready var phys_ball_scene := preload("res://src/equipment/balls/physics_ball/physics_ball.tscn")
@onready var world: World = get_tree().get_first_node_in_group(World.group)
@onready var _target_rotation := Vector2(pitch.rotation.x, direction.rotation.y)
static var scene := preload("res://src/player/shot_setup/shot_setup.tscn")
func _init_deferred() -> void:
_on_phase_change(phase)
# Set up HUD
club = initial_club
@ -181,6 +188,15 @@ func camera_motion(motion: Vector2) -> void:
)
## Return this instance to the AIM phase the next time we process while FINISHED.
func queue_restart() -> void:
_restart_queued = true
func is_active() -> bool:
return phase != Phase.FINISHED
func finish_downswing() -> void:
phase = Phase.SHOT
@ -279,7 +295,13 @@ func end_shot_track() -> void:
## Called immediately before `club` is mutated.
func _on_club_change(new_club: Club.Type) -> void:
func _on_club_change(new_club_type: Club.Type) -> void:
var new_club := player.get_club(new_club_type)
if not new_club:
# `new_club` will be null if player has no club in the given slot
# TODO play bonk
return
# Hide all arrows
# TODO animate?
drive_arrow.hide()
@ -287,40 +309,36 @@ func _on_club_change(new_club: Club.Type) -> void:
iron_arrow.hide()
putt_arrow.hide()
physics_ball.iron_ball = false
hud.club_selector.value = new_club
match new_club:
hud.club_selector.value = new_club_type
# TODO club change animation
character.hold_right(new_club.get_model())
match new_club_type:
Club.Type.DRIVER:
shot_ref = drive_ref
drive_arrow.show()
# TODO get this from player loadout
# TODO club change animation
character.hold_right(Club.catalog.debug_driver.get_model())
Club.Type.PUTTER:
shot_ref = putt_ref
putt_arrow.show()
character.hold_right(Club.catalog.debug_putter.get_model())
Club.Type.WEDGE:
# TODO
shot_ref = wedge_ref
wedge_arrow.show()
character.hold_right(Club.catalog.debug_wedge.get_model())
Club.Type.IRON:
# TODO
shot_ref = iron_ref
iron_arrow.show()
physics_ball.iron_ball = true
character.hold_right(Club.catalog.debug_iron.get_model())
Club.Type.SPECIAL:
# TODO
print_debug("TODO: implement special clubs!")
# TODO figure this out
shot_ref = drive_ref
_:
print_debug("Not sure how to equip club type: ", new_club)
## Called immediately before `phase` is mutated.
func _on_phase_change(new_phase: Phase) -> void:
print_debug("Player ", player.name, ": change to ", Phase.keys()[new_phase])
match new_phase:
Phase.AIM:
hud.set_state_for_player(player)
hud.show_hud()
if not arrow.visible:
arrow_animation.play("show")
@ -358,6 +376,7 @@ func _on_phase_change(new_phase: Phase) -> void:
hud.power_bar.hide()
hud.curve_bar.hide()
travel_to_ball()
finished.emit(self)
func _process(delta: float) -> void:
@ -451,8 +470,9 @@ func _process(delta: float) -> void:
phase = Phase.AIM
return_ball()
Phase.FINISHED:
# TODO: hand over to next player where appropriate
phase = Phase.AIM
if _restart_queued:
_restart_queued = false
phase = Phase.AIM
func _on_physics_ball_sleeping_state_changed() -> void:
@ -483,3 +503,10 @@ func _on_physics_ball_body_entered(_body: Node) -> void:
func _on_ball_return_timer_timeout() -> void:
return_ball()
## Create a new instance for the given player.
static func create(_player: WorldPlayer) -> ShotSetup:
var instance: ShotSetup = ShotSetup.scene.instantiate()
instance.player = _player
return instance

View File

@ -0,0 +1,49 @@
class_name WorldPlayer extends Resource
## Container for the state of the player within the world.
# TODO character select
@export var name: String = "Gfolfer"
@export_category("Equipment")
@export var driver: Club
@export var iron: Club
@export var wedge: Club
@export var putter: Club
@export var special: Club
# TODO balls, pickups, etc
var shot_setup: ShotSetup:
get:
if not shot_setup:
shot_setup = ShotSetup.create(self)
return shot_setup
## Get the club equipped in the given slot.
##
## Returns `null` if the player has no club equipped in the given slot
func get_club(type: Club.Type) -> Club:
match type:
Club.Type.DRIVER:
return driver
Club.Type.IRON:
return iron
Club.Type.WEDGE:
return wedge
Club.Type.PUTTER:
return putter
Club.Type.SPECIAL:
return special
return null
## Create a debug player instance
static func create_debug() -> WorldPlayer:
var instance := WorldPlayer.new()
instance.driver = Club.catalog.debug_driver
instance.iron = Club.catalog.debug_iron
instance.wedge = Club.catalog.debug_wedge
instance.putter = Club.catalog.debug_wedge
return instance

View File

@ -1,2 +0,0 @@
class_name WorldPlayer extends Node
## Container for the state of the player within the world.

View File

@ -1,6 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://q8vdqfewwvsy"]
[ext_resource type="Script" path="res://src/player/world_player/world_player.gd" id="1_2wo11"]
[node name="WorldPlayer" type="Node"]
script = ExtResource("1_2wo11")

View File

@ -13,6 +13,10 @@ ClubSelectLabel/constants/shadow_offset_x = 6
ClubSelectLabel/constants/shadow_offset_y = 0
ClubSelectLabel/font_sizes/font_size = 84
ClubSelectLabel/fonts/font = ExtResource("1_3rv2b")
ClubSelectLabelDisabled/base_type = &"ClubSelectLabel"
ClubSelectLabelDisabled/colors/font_color = Color(0.4, 0.3848, 0.376, 1)
ClubSelectLabelDisabled/colors/font_outline_color = Color(0.2, 0.2, 0.2, 1)
ClubSelectLabelDisabled/colors/font_shadow_color = Color(0.2, 0.2, 0.2, 1)
ShotFeedback/base_type = &"RichTextLabel"
ShotFeedback/colors/font_shadow_color = Color(0, 0, 0, 1)
ShotFeedback/constants/shadow_offset_x = 6

View File

@ -20,11 +20,20 @@ const CLUB_ROTATION := {
@onready var club_hub: Control = %ClubHub
@onready var driver_label: Label = %DriverLabel
@onready var iron_label: Label = %IronLabel
@onready var wedge_label: Label = %WedgeLabel
@onready var special_label: Label = %SpecialLabel
@onready var putter_label: Label = %PutterLabel
@onready var driver_label: ClubSelectorLabel = %DriverLabel
@onready var iron_label: ClubSelectorLabel = %IronLabel
@onready var wedge_label: ClubSelectorLabel = %WedgeLabel
@onready var special_label: ClubSelectorLabel = %SpecialLabel
@onready var putter_label: ClubSelectorLabel = %PutterLabel
## Enable/disable labels based on the player's available equipment
func set_state_for_player(player: WorldPlayer) -> void:
driver_label.set_enabled(player.driver != null)
iron_label.set_enabled(player.iron != null)
wedge_label.set_enabled(player.wedge != null)
special_label.set_enabled(player.special != null)
putter_label.set_enabled(player.putter != null)
func _get_club_label(club: Club.Type) -> Label:

View File

@ -1,8 +1,9 @@
[gd_scene load_steps=7 format=3 uid="uid://445qd7m4qe2j"]
[gd_scene load_steps=8 format=3 uid="uid://445qd7m4qe2j"]
[ext_resource type="Script" path="res://src/ui/shot_hud/club_selector/club_selector.gd" id="1_cttgc"]
[ext_resource type="Script" path="res://src/ui/decorations/arc.gd" id="2_vijl7"]
[ext_resource type="Script" path="res://src/ui/decorations/double_rotator.gd" id="3_0ptmq"]
[ext_resource type="Script" path="res://src/ui/shot_hud/club_selector/club_selector_label.gd" id="4_j7p7f"]
[sub_resource type="Animation" id="Animation_crn6i"]
length = 0.001
@ -107,6 +108,7 @@ scale = Vector2(0.618, 0.618)
pivot_offset = Vector2(0, 20)
theme_type_variation = &"ClubSelectLabel"
text = "DRIVER"
script = ExtResource("4_j7p7f")
[node name="IronLabel" type="Label" parent="ClubHub"]
unique_name_in_owner = true
@ -126,6 +128,7 @@ scale = Vector2(0.618, 0.618)
pivot_offset = Vector2(0, 20)
theme_type_variation = &"ClubSelectLabel"
text = "IRON"
script = ExtResource("4_j7p7f")
[node name="WedgeLabel" type="Label" parent="ClubHub"]
unique_name_in_owner = true
@ -145,6 +148,7 @@ scale = Vector2(0.618, 0.618)
pivot_offset = Vector2(0, 20)
theme_type_variation = &"ClubSelectLabel"
text = "WEDGE"
script = ExtResource("4_j7p7f")
[node name="SpecialLabel" type="Label" parent="ClubHub"]
unique_name_in_owner = true
@ -164,6 +168,7 @@ scale = Vector2(0.618, 0.618)
pivot_offset = Vector2(0, 20)
theme_type_variation = &"ClubSelectLabel"
text = "SPECIAL"
script = ExtResource("4_j7p7f")
[node name="PutterLabel" type="Label" parent="ClubHub"]
unique_name_in_owner = true
@ -183,3 +188,4 @@ scale = Vector2(0.618, 0.618)
pivot_offset = Vector2(0, 20)
theme_type_variation = &"ClubSelectLabel"
text = "PUTTER"
script = ExtResource("4_j7p7f")

View File

@ -0,0 +1,8 @@
class_name ClubSelectorLabel extends Label
const ENABLED_THEME_TYPE := "ClubSelectLabel"
const DISABLED_THEME_TYPE := "ClubSelectLabelDisabled"
func set_enabled(is_enabled: bool) -> void:
theme_type_variation = ENABLED_THEME_TYPE if is_enabled else DISABLED_THEME_TYPE

View File

@ -17,6 +17,15 @@ class_name ShotHUD extends Control
@onready var _state: AnimationNodeStateMachinePlayback = hud_state_machine["parameters/playback"]
## Set any HUD state specific to the player.
func set_state_for_player(player: WorldPlayer) -> void:
print_debug("Setting HUD for player ", player.name)
club_selector.set_state_for_player(player)
# TODO life
# TODO special equipment
# TODO abilities
func show_hud() -> void:
_state.travel("visible")

View File

@ -6,6 +6,12 @@ class_name World extends Node
@export var initial_level: PackedScene = load("res://levels/debug_level/debug_level.tscn")
@export var players: Array[WorldPlayer] = []
@export var spawn_group := "PlayerSpawn"
var _spawns_available: Array[Node3D] = []
@onready var level: Node3D = %Level
@onready var ui: WorldUI = %UI
@ -15,6 +21,41 @@ static var group := "WorldGroup"
func _ready() -> void:
load_level(initial_level)
# Spawn players in the level
for player: WorldPlayer in players:
_spawn_player(player)
# Set first player as active
if players:
players[0].shot_setup.phase = ShotSetup.Phase.AIM
else:
push_warning("Warning: Starting game world with no players!")
func _random_spawn() -> Node3D:
# Get random spawn point
if not _spawns_available:
_spawns_available.assign(get_tree().get_nodes_in_group(spawn_group))
@warning_ignore("unsafe_cast")
var spawn_point := _spawns_available.pick_random() as Node3D
assert(spawn_point, "No nodes in spawn group `%s` found!" % spawn_group)
_spawns_available.erase(spawn_point)
return spawn_point
func _spawn_player(player: WorldPlayer) -> void:
var spawn_point := _random_spawn()
var shot_setup := player.shot_setup
shot_setup.finished.connect(_on_shot_finished)
shot_setup.global_transform = spawn_point.global_transform
spawn_point.add_sibling(shot_setup)
func _on_shot_finished(source: ShotSetup) -> void:
print_debug("Shot finished for ", source.player.name)
players.push_back(players.pop_front())
players[0].shot_setup.queue_restart()
## Instantiate and mantle the given level scene.
##
@ -27,4 +68,3 @@ func load_level(level_scene: PackedScene) -> void:
# Load the level
var instance: Node3D = level_scene.instantiate()
level.add_child(instance)
instance.reparent(level)

View File

@ -1,11 +1,14 @@
[gd_scene load_steps=4 format=3 uid="uid://cwnwcd8kushl3"]
[gd_scene load_steps=6 format=3 uid="uid://cwnwcd8kushl3"]
[ext_resource type="Script" path="res://src/world/world.gd" id="1_ybjyx"]
[ext_resource type="PackedScene" uid="uid://c4ifdiohng830" path="res://src/ui/shot_hud/shot_hud.tscn" id="2_5b7qb"]
[ext_resource type="Script" path="res://src/player/world_player.gd" id="2_e743i"]
[ext_resource type="Script" path="res://src/ui/world_ui.gd" id="2_imewa"]
[ext_resource type="Resource" uid="uid://crock3revdn73" path="res://src/player/debug_player.tres" id="3_pyw81"]
[node name="World" type="Node" groups=["WorldGroup"]]
script = ExtResource("1_ybjyx")
players = Array[ExtResource("2_e743i")]([ExtResource("3_pyw81")])
[node name="Level" type="Node3D" parent="."]
unique_name_in_owner = true