Compare commits
7 Commits
428f00a812
...
multy
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c2227432c | |||
| 84433160fe | |||
| 588d11a9ff | |||
| 3df2a0af58 | |||
| 832e539a96 | |||
| 4e6c645938 | |||
| 1869dbf054 |
@@ -23,6 +23,11 @@ func _ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_body_entered(body: Node3D) -> void:
|
func _on_body_entered(body: Node3D) -> void:
|
||||||
|
# Container fill mutates world state (reparent/freeze); run only on the
|
||||||
|
# world owner. Item transforms replicate to clients via each item's
|
||||||
|
# MultiplayerSynchronizer (global_transform), so contents follow the plate.
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
if not body.is_in_group(target_group):
|
if not body.is_in_group(target_group):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,12 @@
|
|||||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_dhtvl"]
|
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_dhtvl"]
|
||||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vlcpl"]
|
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vlcpl"]
|
||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_el51w"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_el51w"]
|
||||||
|
[ext_resource type="Script" path="res://Prefabs/net_pickable.gd" id="7_net"]
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="Repl_item"]
|
||||||
|
properties/0/path = NodePath(".:global_transform")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_kek77"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_kek77"]
|
||||||
height = 0.0635376
|
height = 0.0635376
|
||||||
@@ -61,6 +67,12 @@ hand_pose = SubResource("Resource_2hcq0")
|
|||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.16517048, 0.042291984, -0.08604182)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.16517048, 0.042291984, -0.08604182)
|
||||||
hand_pose = SubResource("Resource_dlngj")
|
hand_pose = SubResource("Resource_dlngj")
|
||||||
|
|
||||||
|
[node name="NetPickable" type="Node" parent="."]
|
||||||
|
script = ExtResource("7_net")
|
||||||
|
|
||||||
|
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
replication_config = SubResource("Repl_item")
|
||||||
|
|
||||||
[node name="Container" parent="." unique_id=377793422 node_paths=PackedStringArray("meal_positions", "side_positions") instance=ExtResource("1_s5yhh")]
|
[node name="Container" parent="." unique_id=377793422 node_paths=PackedStringArray("meal_positions", "side_positions") instance=ExtResource("1_s5yhh")]
|
||||||
target_group = "platalbe_item"
|
target_group = "platalbe_item"
|
||||||
meal_positions = [NodePath("MealPositions/Slot1")]
|
meal_positions = [NodePath("MealPositions/Slot1")]
|
||||||
|
|||||||
@@ -6,6 +6,12 @@
|
|||||||
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_mgacb"]
|
[ext_resource type="Script" uid="uid://dvobm6vcfnqe8" path="res://addons/godot-xr-tools/hands/poses/hand_pose_settings.gd" id="4_mgacb"]
|
||||||
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vw7i6"]
|
[ext_resource type="PackedScene" uid="uid://ctw7nbntd5pcj" path="res://addons/godot-xr-tools/objects/grab_points/grab_point_hand_right.tscn" id="5_vw7i6"]
|
||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_cp3eg"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="6_cp3eg"]
|
||||||
|
[ext_resource type="Script" path="res://Prefabs/net_pickable.gd" id="7_net"]
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="Repl_item"]
|
||||||
|
properties/0/path = NodePath(".:global_transform")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_gkni8"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_gkni8"]
|
||||||
custom_solver_bias = 0.1
|
custom_solver_bias = 0.1
|
||||||
@@ -46,3 +52,9 @@ hand_pose = SubResource("Resource_lc22d")
|
|||||||
[node name="GrabPointHandRight" parent="." index="3" unique_id=514404634 instance=ExtResource("5_vw7i6")]
|
[node name="GrabPointHandRight" parent="." index="3" unique_id=514404634 instance=ExtResource("5_vw7i6")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.06531982, 0.042291984, -0.08604182)
|
||||||
hand_pose = SubResource("Resource_qyiot")
|
hand_pose = SubResource("Resource_qyiot")
|
||||||
|
|
||||||
|
[node name="NetPickable" type="Node" parent="." index="4"]
|
||||||
|
script = ExtResource("7_net")
|
||||||
|
|
||||||
|
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="5"]
|
||||||
|
replication_config = SubResource("Repl_item")
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://7earnjgdmwgx" path="res://Prefabs/cookable_item.tscn" id="10_65ccv"]
|
[ext_resource type="PackedScene" uid="uid://7earnjgdmwgx" path="res://Prefabs/cookable_item.tscn" id="10_65ccv"]
|
||||||
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://Prefabs/combine_recipe.gd" id="11_recipe"]
|
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://Prefabs/combine_recipe.gd" id="11_recipe"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="11_vjw41"]
|
[ext_resource type="PackedScene" uid="uid://bfj80jh13t6e5" path="res://Prefabs/food_item.tscn" id="11_vjw41"]
|
||||||
|
[ext_resource type="Script" path="res://Prefabs/net_pickable.gd" id="12_net"]
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="Repl_item"]
|
||||||
|
properties/0/path = NodePath(".:global_transform")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_m1xbq"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_m1xbq"]
|
||||||
size = Vector3(0.1, 0.1, 0.1)
|
size = Vector3(0.1, 0.1, 0.1)
|
||||||
@@ -63,3 +69,9 @@ recipes = Array[ExtResource("11_recipe")]([SubResource("Resource_i8ixk")])
|
|||||||
[node name="FoodItem" parent="." index="6" unique_id=63948206 instance=ExtResource("11_vjw41")]
|
[node name="FoodItem" parent="." index="6" unique_id=63948206 instance=ExtResource("11_vjw41")]
|
||||||
id = "cube"
|
id = "cube"
|
||||||
type = 1
|
type = 1
|
||||||
|
|
||||||
|
[node name="NetPickable" type="Node" parent="." index="7"]
|
||||||
|
script = ExtResource("12_net")
|
||||||
|
|
||||||
|
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="8"]
|
||||||
|
replication_config = SubResource("Repl_item")
|
||||||
|
|||||||
@@ -9,6 +9,12 @@
|
|||||||
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="7_wqxjj"]
|
[ext_resource type="Animation" uid="uid://d1xnpyc08njjx" path="res://addons/godot-xr-tools/hands/animations/right/Grip 4.res" id="7_wqxjj"]
|
||||||
[ext_resource type="PackedScene" uid="uid://7earnjgdmwgx" path="res://Prefabs/cookable_item.tscn" id="9_hwyyd"]
|
[ext_resource type="PackedScene" uid="uid://7earnjgdmwgx" path="res://Prefabs/cookable_item.tscn" id="9_hwyyd"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cfc4ho67u4r5e" path="res://Items/cooked_burger.tscn" id="9_r8jvx"]
|
[ext_resource type="PackedScene" uid="uid://cfc4ho67u4r5e" path="res://Items/cooked_burger.tscn" id="9_r8jvx"]
|
||||||
|
[ext_resource type="Script" path="res://Prefabs/net_pickable.gd" id="12_net"]
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="Repl_item"]
|
||||||
|
properties/0/path = NodePath(".:global_transform")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
|
||||||
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
[sub_resource type="CylinderShape3D" id="CylinderShape3D_fco8w"]
|
||||||
height = 0.0338974
|
height = 0.0338974
|
||||||
@@ -49,3 +55,9 @@ hand_pose = SubResource("Resource_qyiot")
|
|||||||
|
|
||||||
[node name="CookableItem" parent="." index="4" unique_id=280820828 instance=ExtResource("9_hwyyd")]
|
[node name="CookableItem" parent="." index="4" unique_id=280820828 instance=ExtResource("9_hwyyd")]
|
||||||
turns_into = ExtResource("9_r8jvx")
|
turns_into = ExtResource("9_r8jvx")
|
||||||
|
|
||||||
|
[node name="NetPickable" type="Node" parent="." index="5"]
|
||||||
|
script = ExtResource("12_net")
|
||||||
|
|
||||||
|
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="6"]
|
||||||
|
replication_config = SubResource("Repl_item")
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ albedo_color = Color(0.6784191, 0.54546416, 0.016760282, 1)
|
|||||||
albedo_color = Color(0.29, 0.101500005, 0, 1)
|
albedo_color = Color(0.29, 0.101500005, 0, 1)
|
||||||
|
|
||||||
[node name="Hamburger" unique_id=1675596942 groups=["platalbe_item"] instance=ExtResource("1_3gf3l")]
|
[node name="Hamburger" unique_id=1675596942 groups=["platalbe_item"] instance=ExtResource("1_3gf3l")]
|
||||||
gravity_scale = 0.04
|
gravity_scale = 0.4
|
||||||
|
|
||||||
[node name="CollisionShape3D" parent="." index="0"]
|
[node name="CollisionShape3D" parent="." index="0"]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.02, 0.02, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.02, 0.02, 0)
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
## The 2D UI rendered inside the world-space menu (XRToolsViewport2DIn3D).
|
||||||
|
## Lets the player type an IP on a numeric keypad and pick Host / Join / Solo.
|
||||||
|
## Pure UI: it emits intents; main.gd performs the networking.
|
||||||
|
|
||||||
|
signal host_pressed()
|
||||||
|
signal join_pressed(ip: String)
|
||||||
|
signal solo_pressed()
|
||||||
|
|
||||||
|
#var _ip := "127.0.0.1"
|
||||||
|
var _ip := "51.175.10.161"
|
||||||
|
|
||||||
|
@onready var _ip_label: Label = %IPLabel
|
||||||
|
@onready var _status: Label = %Status
|
||||||
|
@onready var _keypad: GridContainer = %Keypad
|
||||||
|
@onready var _host_btn: Button = %HostButton
|
||||||
|
@onready var _join_btn: Button = %JoinButton
|
||||||
|
@onready var _solo_btn: Button = %SoloButton
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
for child in _keypad.get_children():
|
||||||
|
if child is Button:
|
||||||
|
child.pressed.connect(_on_key.bind(child.text))
|
||||||
|
_host_btn.pressed.connect(func(): host_pressed.emit())
|
||||||
|
_join_btn.pressed.connect(func(): join_pressed.emit(_ip))
|
||||||
|
_solo_btn.pressed.connect(func(): solo_pressed.emit())
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_key(key: String) -> void:
|
||||||
|
match key:
|
||||||
|
"DEL":
|
||||||
|
_ip = _ip.substr(0, max(0, _ip.length() - 1))
|
||||||
|
_:
|
||||||
|
if _ip.length() < 21:
|
||||||
|
_ip += key
|
||||||
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh() -> void:
|
||||||
|
_ip_label.text = _ip if not _ip.is_empty() else "_"
|
||||||
|
|
||||||
|
|
||||||
|
## Called by main.gd (via network_menu) to show connection feedback.
|
||||||
|
func set_status(text: String) -> void:
|
||||||
|
if _status:
|
||||||
|
_status.text = text
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dvivyrspjcjgm
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Net/join_panel.gd" id="1_panel"]
|
||||||
|
|
||||||
|
[node name="JoinPanel" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource("1_panel")
|
||||||
|
|
||||||
|
[node name="Background" type="ColorRect" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
color = Color(0.09, 0.1, 0.13, 1)
|
||||||
|
|
||||||
|
[node name="Margin" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
theme_override_constants/margin_left = 24
|
||||||
|
theme_override_constants/margin_top = 16
|
||||||
|
theme_override_constants/margin_right = 24
|
||||||
|
theme_override_constants/margin_bottom = 16
|
||||||
|
|
||||||
|
[node name="VBox" type="VBoxContainer" parent="Margin"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="Margin/VBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "VRyHungry — Multiplayer"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="IPLabel" type="Label" parent="Margin/VBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_font_sizes/font_size = 34
|
||||||
|
text = "127.0.0.1"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Status" type="Label" parent="Margin/VBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.8, 0.8, 0.5, 1)
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
text = "Enter host IP, then Join"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="Keypad" type="GridContainer" parent="Margin/VBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_constants/h_separation = 8
|
||||||
|
theme_override_constants/v_separation = 8
|
||||||
|
columns = 3
|
||||||
|
|
||||||
|
[node name="B1" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "1"
|
||||||
|
|
||||||
|
[node name="B2" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "2"
|
||||||
|
|
||||||
|
[node name="B3" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "3"
|
||||||
|
|
||||||
|
[node name="B4" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "4"
|
||||||
|
|
||||||
|
[node name="B5" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "5"
|
||||||
|
|
||||||
|
[node name="B6" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "6"
|
||||||
|
|
||||||
|
[node name="B7" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "7"
|
||||||
|
|
||||||
|
[node name="B8" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "8"
|
||||||
|
|
||||||
|
[node name="B9" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "9"
|
||||||
|
|
||||||
|
[node name="BDot" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "."
|
||||||
|
|
||||||
|
[node name="B0" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 26
|
||||||
|
text = "0"
|
||||||
|
|
||||||
|
[node name="BDel" type="Button" parent="Margin/VBox/Keypad"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
theme_override_font_sizes/font_size = 22
|
||||||
|
text = "DEL"
|
||||||
|
|
||||||
|
[node name="Buttons" type="HBoxContainer" parent="Margin/VBox"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="HostButton" type="Button" parent="Margin/VBox/Buttons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
custom_minimum_size = Vector2(0, 56)
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "HOST"
|
||||||
|
|
||||||
|
[node name="JoinButton" type="Button" parent="Margin/VBox/Buttons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
custom_minimum_size = Vector2(0, 56)
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "JOIN"
|
||||||
|
|
||||||
|
[node name="SoloButton" type="Button" parent="Margin/VBox/Buttons"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
custom_minimum_size = Vector2(0, 56)
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
text = "SOLO"
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
## Client-server session manager for VRyHungry (listen-server model).
|
||||||
|
##
|
||||||
|
## Registered as the "NetworkManager" autoload. Owns transport (ENet), tracks
|
||||||
|
## the session, and is the single place that reassigns multiplayer authority
|
||||||
|
## (only the server does so). The world scene (main.gd) registers its spawners
|
||||||
|
## here via [method register_world]; higher layers (players, items, stations)
|
||||||
|
## build on top of this in later phases.
|
||||||
|
|
||||||
|
const DEFAULT_PORT := 24565
|
||||||
|
const MAX_CLIENTS := 7
|
||||||
|
|
||||||
|
## Emitted on every peer (including the server for its own local player) when a
|
||||||
|
## player peer joins. On the server this fires for each remote peer; the server
|
||||||
|
## uses it to spawn that peer's player.
|
||||||
|
signal player_joined(peer_id: int)
|
||||||
|
signal player_left(peer_id: int)
|
||||||
|
signal session_started(is_server: bool)
|
||||||
|
signal session_ended()
|
||||||
|
signal connection_failed()
|
||||||
|
|
||||||
|
# World hooks, registered by main.gd once the scene tree exists.
|
||||||
|
var _world: Node = null
|
||||||
|
var _players_spawner: MultiplayerSpawner = null
|
||||||
|
var _items_spawner: MultiplayerSpawner = null
|
||||||
|
|
||||||
|
var _log_file: FileAccess
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_open_log()
|
||||||
|
multiplayer.peer_connected.connect(_on_peer_connected)
|
||||||
|
multiplayer.peer_disconnected.connect(_on_peer_disconnected)
|
||||||
|
multiplayer.connected_to_server.connect(_on_connected_to_server)
|
||||||
|
multiplayer.connection_failed.connect(_on_connection_failed)
|
||||||
|
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Public API ------------------------------------------------------------
|
||||||
|
|
||||||
|
## Start hosting. The host is peer 1 and also plays (listen server).
|
||||||
|
func host(port: int = DEFAULT_PORT) -> Error:
|
||||||
|
var peer := ENetMultiplayerPeer.new()
|
||||||
|
var err := peer.create_server(port, MAX_CLIENTS)
|
||||||
|
if err != OK:
|
||||||
|
log_line("HOST failed to create_server on port %d: %s" % [port, error_string(err)])
|
||||||
|
return err
|
||||||
|
multiplayer.multiplayer_peer = peer
|
||||||
|
log_line("HOST started on port %d (peer id %d)" % [port, multiplayer.get_unique_id()])
|
||||||
|
session_started.emit(true)
|
||||||
|
# The host's own local player joins immediately.
|
||||||
|
_on_player_present(multiplayer.get_unique_id())
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
## Join an existing host.
|
||||||
|
func join(address: String = "127.0.0.1", port: int = DEFAULT_PORT) -> Error:
|
||||||
|
var peer := ENetMultiplayerPeer.new()
|
||||||
|
var err := peer.create_client(address, port)
|
||||||
|
if err != OK:
|
||||||
|
log_line("JOIN failed to create_client %s:%d: %s" % [address, port, error_string(err)])
|
||||||
|
return err
|
||||||
|
multiplayer.multiplayer_peer = peer
|
||||||
|
log_line("JOIN connecting to %s:%d ..." % [address, port])
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
## Leave the session and tear down transport.
|
||||||
|
func leave() -> void:
|
||||||
|
_go_offline()
|
||||||
|
log_line("Session ended")
|
||||||
|
session_ended.emit()
|
||||||
|
|
||||||
|
|
||||||
|
# Restore Godot's default OfflineMultiplayerPeer (rather than leaving the peer
|
||||||
|
# null), so is_multiplayer_authority()/get_unique_id() keep working while we are
|
||||||
|
# back in single-player / menu state.
|
||||||
|
func _go_offline() -> void:
|
||||||
|
if multiplayer.multiplayer_peer:
|
||||||
|
multiplayer.multiplayer_peer.close()
|
||||||
|
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Item spawning ---------------------------------------------------------
|
||||||
|
|
||||||
|
## Spawn a networked item. Server-only when online (replicates to all peers via
|
||||||
|
## the ItemsSpawner); works directly when offline. Returns the new node on the
|
||||||
|
## machine that owns spawning, else null.
|
||||||
|
func spawn_item(scene_path: String, xform: Transform3D) -> Node:
|
||||||
|
if is_online() and not is_server():
|
||||||
|
return null
|
||||||
|
var data := {"scene": scene_path, "xform": xform}
|
||||||
|
if is_online() and _items_spawner:
|
||||||
|
return _items_spawner.spawn(data)
|
||||||
|
# Offline: instantiate directly under the world.
|
||||||
|
var inst := _spawn_item_from_data(data)
|
||||||
|
if _world and inst:
|
||||||
|
_world.add_child(inst)
|
||||||
|
return inst
|
||||||
|
|
||||||
|
|
||||||
|
# MultiplayerSpawner custom spawn function: runs on every peer to build the node
|
||||||
|
# from the replicated payload.
|
||||||
|
func _spawn_item_from_data(data: Variant) -> Node:
|
||||||
|
var scene: PackedScene = load(data["scene"])
|
||||||
|
if not scene:
|
||||||
|
push_error("spawn_item: could not load scene %s" % str(data.get("scene")))
|
||||||
|
return null
|
||||||
|
var inst := scene.instantiate()
|
||||||
|
if inst is Node3D:
|
||||||
|
inst.transform = data["xform"]
|
||||||
|
return inst
|
||||||
|
|
||||||
|
|
||||||
|
# --- Item grab-authority transfer -----------------------------------------
|
||||||
|
|
||||||
|
## A client (or host) requests authority over an item it just grabbed. Runs on
|
||||||
|
## the server. If the item was snapped into a station, the station releases it
|
||||||
|
## so the grabber cleanly takes ownership.
|
||||||
|
@rpc("any_peer", "reliable")
|
||||||
|
func request_item_authority(item_path: NodePath) -> void:
|
||||||
|
if not is_server():
|
||||||
|
return
|
||||||
|
var sender := multiplayer.get_remote_sender_id()
|
||||||
|
# Assign authority + held state first (disables the item on the server so its
|
||||||
|
# snap zone won't re-grab it), then release it from any station.
|
||||||
|
_set_item_authority.rpc(item_path, sender)
|
||||||
|
var item := get_node_or_null(item_path)
|
||||||
|
if item:
|
||||||
|
_release_from_snap_zones(item)
|
||||||
|
|
||||||
|
|
||||||
|
## A player releases an item, forwarding its throw velocity so the server can
|
||||||
|
## resume simulating it. Runs on the server. If released next to a station, the
|
||||||
|
## server snaps it in (server-authoritative placement).
|
||||||
|
@rpc("any_peer", "reliable")
|
||||||
|
func release_item_authority(item_path: NodePath, lin: Vector3, ang: Vector3) -> void:
|
||||||
|
if not is_server():
|
||||||
|
return
|
||||||
|
_set_item_authority.rpc(item_path, 1)
|
||||||
|
var item := get_node_or_null(item_path)
|
||||||
|
if item is RigidBody3D:
|
||||||
|
item.freeze = false
|
||||||
|
item.linear_velocity = lin
|
||||||
|
item.angular_velocity = ang
|
||||||
|
_try_snap_into_station(item)
|
||||||
|
|
||||||
|
|
||||||
|
# All station snap zones in the world (nodes in the "station" group).
|
||||||
|
func _station_snap_zones() -> Array:
|
||||||
|
var zones := []
|
||||||
|
for station in get_tree().get_nodes_in_group("station"):
|
||||||
|
var zone = station.get_node_or_null("XRToolsSnapZone")
|
||||||
|
if zone:
|
||||||
|
zones.append(zone)
|
||||||
|
return zones
|
||||||
|
|
||||||
|
|
||||||
|
# If the item is snapped into any station, drop it from that station.
|
||||||
|
func _release_from_snap_zones(item: Node) -> void:
|
||||||
|
for zone in _station_snap_zones():
|
||||||
|
if zone.picked_up_object == item:
|
||||||
|
zone.drop_object()
|
||||||
|
|
||||||
|
|
||||||
|
# Snap the item into the nearest empty station snap zone within grab range.
|
||||||
|
func _try_snap_into_station(item: Node) -> void:
|
||||||
|
if not (item is Node3D):
|
||||||
|
return
|
||||||
|
for zone in _station_snap_zones():
|
||||||
|
if is_instance_valid(zone.picked_up_object):
|
||||||
|
continue
|
||||||
|
if zone.global_position.distance_to(item.global_position) <= zone.grab_distance:
|
||||||
|
zone.pick_up_object(item)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# Server broadcasts an authority assignment so every peer agrees on who owns the
|
||||||
|
# item (set_multiplayer_authority is a local call and must run everywhere).
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func _set_item_authority(item_path: NodePath, peer: int) -> void:
|
||||||
|
var item := get_node_or_null(item_path)
|
||||||
|
if not item:
|
||||||
|
return
|
||||||
|
item.set_multiplayer_authority(peer) # recursive: item + synchronizer + NetPickable
|
||||||
|
var np := item.get_node_or_null("NetPickable")
|
||||||
|
if np:
|
||||||
|
np.net_held_by = 0 if peer == 1 else peer
|
||||||
|
np.apply_held_state()
|
||||||
|
|
||||||
|
|
||||||
|
func is_server() -> bool:
|
||||||
|
return is_online() and multiplayer.is_server()
|
||||||
|
|
||||||
|
|
||||||
|
## True only when a real ENet session is active. Godot installs a default
|
||||||
|
## OfflineMultiplayerPeer, so a non-null peer alone does not mean "online".
|
||||||
|
func is_online() -> bool:
|
||||||
|
var p := multiplayer.multiplayer_peer
|
||||||
|
return p != null and not (p is OfflineMultiplayerPeer)
|
||||||
|
|
||||||
|
|
||||||
|
## True on the machine that owns authoritative world logic: the server when
|
||||||
|
## online, or the single player when offline. Station logic and spawning should
|
||||||
|
## only run where this is true, so state has one source of truth.
|
||||||
|
func owns_world() -> bool:
|
||||||
|
return not is_online() or is_server()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Station work-progress seam -------------------------------------------
|
||||||
|
|
||||||
|
## Reusable entry point for a client to contribute work to a station (e.g. a
|
||||||
|
## future chopping/gesture station). The client detects the gesture locally and
|
||||||
|
## calls this; the server validates and accumulates. Timer-driven stations like
|
||||||
|
## the Hob don't need it, but it is the drop-in seam for input-driven ones.
|
||||||
|
@rpc("any_peer", "reliable")
|
||||||
|
func submit_work(station_path: NodePath, amount: float) -> void:
|
||||||
|
if not is_server():
|
||||||
|
return
|
||||||
|
var station := get_node_or_null(station_path)
|
||||||
|
if station and station.has_method("add_work"):
|
||||||
|
station.add_work(multiplayer.get_remote_sender_id(), amount)
|
||||||
|
|
||||||
|
|
||||||
|
## Called by main.gd once the world scene is ready, passing its spawners.
|
||||||
|
func register_world(world: Node, players_spawner: MultiplayerSpawner, items_spawner: MultiplayerSpawner) -> void:
|
||||||
|
_world = world
|
||||||
|
_players_spawner = players_spawner
|
||||||
|
_items_spawner = items_spawner
|
||||||
|
if _items_spawner:
|
||||||
|
_items_spawner.spawn_function = _spawn_item_from_data
|
||||||
|
log_line("World registered (players_spawner=%s items_spawner=%s)" % [str(players_spawner != null), str(items_spawner != null)])
|
||||||
|
|
||||||
|
|
||||||
|
## Called by main.gd after it has registered the world and connected its
|
||||||
|
## player_joined/left listeners. Kicks off any command-line driven session so
|
||||||
|
## that session signals never fire before the world is listening.
|
||||||
|
func world_ready() -> void:
|
||||||
|
_handle_cmdline()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Session signal handlers ----------------------------------------------
|
||||||
|
|
||||||
|
func _on_peer_connected(peer_id: int) -> void:
|
||||||
|
log_line("peer_connected: %d" % peer_id)
|
||||||
|
# Only the server reacts by materialising that peer's player.
|
||||||
|
if is_server():
|
||||||
|
_on_player_present(peer_id)
|
||||||
|
|
||||||
|
func _on_peer_disconnected(peer_id: int) -> void:
|
||||||
|
log_line("peer_disconnected: %d" % peer_id)
|
||||||
|
if is_server():
|
||||||
|
_on_player_absent(peer_id)
|
||||||
|
|
||||||
|
func _on_connected_to_server() -> void:
|
||||||
|
log_line("connected_to_server (my id=%d)" % multiplayer.get_unique_id())
|
||||||
|
session_started.emit(false)
|
||||||
|
|
||||||
|
func _on_connection_failed() -> void:
|
||||||
|
log_line("connection_failed")
|
||||||
|
_go_offline()
|
||||||
|
connection_failed.emit()
|
||||||
|
|
||||||
|
func _on_server_disconnected() -> void:
|
||||||
|
log_line("server_disconnected")
|
||||||
|
_go_offline()
|
||||||
|
session_ended.emit()
|
||||||
|
|
||||||
|
|
||||||
|
# Player materialise/dematerialise. Phase 2 wires these to the PlayersSpawner;
|
||||||
|
# for now they announce presence so the transport layer is independently testable.
|
||||||
|
func _on_player_present(peer_id: int) -> void:
|
||||||
|
log_line("player_present: %d" % peer_id)
|
||||||
|
player_joined.emit(peer_id)
|
||||||
|
|
||||||
|
func _on_player_absent(peer_id: int) -> void:
|
||||||
|
log_line("player_absent: %d" % peer_id)
|
||||||
|
player_left.emit(peer_id)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Command-line driven test bootstrap -----------------------------------
|
||||||
|
|
||||||
|
func _handle_cmdline() -> void:
|
||||||
|
var args := OS.get_cmdline_user_args()
|
||||||
|
if args.has("--server"):
|
||||||
|
log_line("cmdline: --server")
|
||||||
|
host()
|
||||||
|
elif args.has("--join"):
|
||||||
|
var idx := args.find("--join")
|
||||||
|
var addr := "127.0.0.1"
|
||||||
|
if idx + 1 < args.size():
|
||||||
|
addr = args[idx + 1]
|
||||||
|
log_line("cmdline: --join %s" % addr)
|
||||||
|
join(addr)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Logging ---------------------------------------------------------------
|
||||||
|
|
||||||
|
func _open_log() -> void:
|
||||||
|
var dir := OS.get_environment("TEMP")
|
||||||
|
if dir.is_empty():
|
||||||
|
dir = OS.get_environment("TMPDIR")
|
||||||
|
if dir.is_empty():
|
||||||
|
dir = "user://"
|
||||||
|
var path := dir.path_join("vryhungry_net_%d.log" % OS.get_process_id())
|
||||||
|
_log_file = FileAccess.open(path, FileAccess.WRITE)
|
||||||
|
log_line("=== NetworkManager log (pid %d) ===" % OS.get_process_id())
|
||||||
|
|
||||||
|
func log_line(s: String) -> void:
|
||||||
|
var id := 0
|
||||||
|
var p := multiplayer.multiplayer_peer
|
||||||
|
if p != null and p.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
||||||
|
id = multiplayer.get_unique_id()
|
||||||
|
var line := "[NET %d] %s" % [id, s]
|
||||||
|
print(line)
|
||||||
|
if _log_file:
|
||||||
|
_log_file.store_line(line)
|
||||||
|
_log_file.flush()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://deefoory0vqmm
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
## World-space main menu: hosts the 2D join_panel on an XRToolsViewport2DIn3D so
|
||||||
|
## the player can point at it with a controller laser. Re-emits the panel's
|
||||||
|
## intents; main.gd drives the actual networking.
|
||||||
|
|
||||||
|
signal host_pressed()
|
||||||
|
signal join_pressed(ip: String)
|
||||||
|
signal solo_pressed()
|
||||||
|
|
||||||
|
@onready var _vp: XRToolsViewport2DIn3D = $Viewport2Din3D
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# The viewport instantiates its 2D scene during its own _ready; connect once
|
||||||
|
# that has happened.
|
||||||
|
_connect_scene.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _connect_scene() -> void:
|
||||||
|
_vp.connect_scene_signal("host_pressed", func(): host_pressed.emit())
|
||||||
|
_vp.connect_scene_signal("join_pressed", func(ip): join_pressed.emit(ip))
|
||||||
|
_vp.connect_scene_signal("solo_pressed", func(): solo_pressed.emit())
|
||||||
|
|
||||||
|
|
||||||
|
func show_menu() -> void:
|
||||||
|
visible = true
|
||||||
|
|
||||||
|
|
||||||
|
func hide_menu() -> void:
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func set_status(text: String) -> void:
|
||||||
|
var scene := _vp.get_scene_instance()
|
||||||
|
if scene and scene.has_method("set_status"):
|
||||||
|
scene.set_status(text)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://1o2nca75po1f
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[gd_scene load_steps=4 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Net/network_menu.gd" id="1_menu"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://clujaf3u776a3" path="res://addons/godot-xr-tools/objects/viewport_2d_in_3d.tscn" id="2_vp"]
|
||||||
|
[ext_resource type="PackedScene" path="res://Net/join_panel.tscn" id="3_panel"]
|
||||||
|
|
||||||
|
[node name="NetworkMenu" type="Node3D"]
|
||||||
|
script = ExtResource("1_menu")
|
||||||
|
|
||||||
|
[node name="Viewport2Din3D" parent="." instance=ExtResource("2_vp")]
|
||||||
|
screen_size = Vector2(0.6, 0.4)
|
||||||
|
scene = ExtResource("3_panel")
|
||||||
|
viewport_size = Vector2(600, 400)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
[gd_scene load_steps=7 format=3 uid="uid://cnetplayer0001"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Player/player.gd" id="1_player"]
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="Repl_player"]
|
||||||
|
properties/0/path = NodePath(".:head_xform")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
properties/1/path = NodePath(".:left_xform")
|
||||||
|
properties/1/spawn = true
|
||||||
|
properties/1/replication_mode = 1
|
||||||
|
properties/2/path = NodePath(".:right_xform")
|
||||||
|
properties/2/spawn = true
|
||||||
|
properties/2/replication_mode = 1
|
||||||
|
properties/3/path = NodePath(".:net_heartbeat")
|
||||||
|
properties/3/spawn = true
|
||||||
|
properties/3/replication_mode = 1
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Mat_head"]
|
||||||
|
albedo_color = Color(0.85, 0.7, 0.55, 1)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_head"]
|
||||||
|
material = SubResource("Mat_head")
|
||||||
|
size = Vector3(0.18, 0.24, 0.16)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Mat_hand"]
|
||||||
|
albedo_color = Color(0.2, 0.5, 0.9, 1)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="Mesh_hand"]
|
||||||
|
material = SubResource("Mat_hand")
|
||||||
|
size = Vector3(0.09, 0.05, 0.12)
|
||||||
|
|
||||||
|
[node name="Player" type="Node3D"]
|
||||||
|
script = ExtResource("1_player")
|
||||||
|
|
||||||
|
[node name="Avatar" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
[node name="Head" type="MeshInstance3D" parent="Avatar"]
|
||||||
|
mesh = SubResource("Mesh_head")
|
||||||
|
|
||||||
|
[node name="LeftHand" type="MeshInstance3D" parent="Avatar"]
|
||||||
|
mesh = SubResource("Mesh_hand")
|
||||||
|
|
||||||
|
[node name="RightHand" type="MeshInstance3D" parent="Avatar"]
|
||||||
|
mesh = SubResource("Mesh_hand")
|
||||||
|
|
||||||
|
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
replication_config = SubResource("Repl_player")
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
extends Node3D
|
||||||
|
|
||||||
|
## One instance per connected peer. The owning peer is the multiplayer
|
||||||
|
## authority. The local (authority) player builds the full XR rig from
|
||||||
|
## XROrigin.tscn and feeds its head/hand poses into synced properties; remote
|
||||||
|
## players show a lightweight avatar driven by those synced poses.
|
||||||
|
|
||||||
|
const RIG_SCENE := preload("res://XROrigin.tscn")
|
||||||
|
|
||||||
|
# Synced avatar pose. Authority writes these from the XR trackers each frame;
|
||||||
|
# remotes read them onto the Avatar. net_heartbeat is a monotonic counter used
|
||||||
|
# to confirm replication is live (also handy for debugging).
|
||||||
|
@export var head_xform: Transform3D = Transform3D.IDENTITY
|
||||||
|
@export var left_xform: Transform3D = Transform3D.IDENTITY
|
||||||
|
@export var right_xform: Transform3D = Transform3D.IDENTITY
|
||||||
|
@export var net_heartbeat: int = 0
|
||||||
|
|
||||||
|
var _rig: Node3D
|
||||||
|
var _camera: Node3D
|
||||||
|
var _left: Node3D
|
||||||
|
var _right: Node3D
|
||||||
|
var _is_local := false
|
||||||
|
|
||||||
|
@onready var _avatar: Node3D = $Avatar
|
||||||
|
@onready var _avatar_head: Node3D = $Avatar/Head
|
||||||
|
@onready var _avatar_left: Node3D = $Avatar/LeftHand
|
||||||
|
@onready var _avatar_right: Node3D = $Avatar/RightHand
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
# The spawner names each player by its owning peer id.
|
||||||
|
set_multiplayer_authority(str(name).to_int())
|
||||||
|
|
||||||
|
|
||||||
|
# (replication config is authored in Player.tscn's MultiplayerSynchronizer)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_is_local = is_multiplayer_authority()
|
||||||
|
if _is_local:
|
||||||
|
_setup_local()
|
||||||
|
else:
|
||||||
|
_setup_remote()
|
||||||
|
if Engine.has_singleton("NetworkManager") or get_node_or_null("/root/NetworkManager"):
|
||||||
|
NetworkManager.log_line("Player %s ready (authority=%d local=%s)" % [name, get_multiplayer_authority(), str(_is_local)])
|
||||||
|
|
||||||
|
|
||||||
|
func _setup_local() -> void:
|
||||||
|
# We see through the headset; hide our own avatar mesh.
|
||||||
|
_avatar.visible = false
|
||||||
|
_rig = RIG_SCENE.instantiate()
|
||||||
|
add_child(_rig)
|
||||||
|
_camera = _rig.get_node_or_null("XRCamera3D")
|
||||||
|
_left = _rig.get_node_or_null("XRControllerLeftHand")
|
||||||
|
_right = _rig.get_node_or_null("XRControllerRightHand")
|
||||||
|
_enable_xr()
|
||||||
|
|
||||||
|
|
||||||
|
func _enable_xr() -> void:
|
||||||
|
var xr := XRServer.find_interface("OpenXR")
|
||||||
|
if xr and xr.is_initialized():
|
||||||
|
get_viewport().use_xr = true
|
||||||
|
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||||
|
|
||||||
|
|
||||||
|
func _setup_remote() -> void:
|
||||||
|
_avatar.visible = true
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
if _is_local:
|
||||||
|
if _camera:
|
||||||
|
head_xform = _camera.global_transform
|
||||||
|
left_xform = _left.global_transform
|
||||||
|
right_xform = _right.global_transform
|
||||||
|
net_heartbeat += 1
|
||||||
|
else:
|
||||||
|
_avatar_head.global_transform = head_xform
|
||||||
|
_avatar_left.global_transform = left_xform
|
||||||
|
_avatar_right.global_transform = right_xform
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://ndrfa2xle033
|
||||||
+10
-5
@@ -54,6 +54,12 @@ func _on_body_entered(body: Node3D) -> void:
|
|||||||
if _combining or body == _item:
|
if _combining or body == _item:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Combining is a world-state change: only the server/offline host runs it.
|
||||||
|
# (Clients never have the base item snapped, so their zone won't monitor, but
|
||||||
|
# guard explicitly in case of overlap during authority handoff.)
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
|
|
||||||
var other := _find_combinable(body)
|
var other := _find_combinable(body)
|
||||||
if not other:
|
if not other:
|
||||||
return
|
return
|
||||||
@@ -75,11 +81,9 @@ func _combine(other_body: Node3D, result: PackedScene) -> void:
|
|||||||
_combining = false
|
_combining = false
|
||||||
return
|
return
|
||||||
|
|
||||||
# Spawn the result at the base item's location, in world space.
|
# Spawn the result (networked) at the base item's location, in world space.
|
||||||
var base_transform := _item.global_transform
|
var base_transform := _item.global_transform
|
||||||
var result_instance: Node3D = result.instantiate()
|
var result_instance := NetworkManager.spawn_item(result.resource_path, base_transform)
|
||||||
_item.get_tree().current_scene.add_child(result_instance)
|
|
||||||
result_instance.global_transform = base_transform
|
|
||||||
|
|
||||||
# Free the base item and consume the incoming item.
|
# Free the base item and consume the incoming item.
|
||||||
snap_zone.drop_object()
|
snap_zone.drop_object()
|
||||||
@@ -90,7 +94,8 @@ func _combine(other_body: Node3D, result: PackedScene) -> void:
|
|||||||
other_body.queue_free()
|
other_body.queue_free()
|
||||||
|
|
||||||
# Snap the result into the now-empty zone.
|
# Snap the result into the now-empty zone.
|
||||||
snap_zone.pick_up_object(result_instance)
|
if result_instance:
|
||||||
|
snap_zone.pick_up_object(result_instance)
|
||||||
|
|
||||||
|
|
||||||
# Find a CombinableItem among the direct children of [param node].
|
# Find a CombinableItem among the direct children of [param node].
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
class_name NetPickable
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
## Networks an XRToolsPickable (RigidBody3D). Default authority is the server,
|
||||||
|
## which simulates free items and syncs their transform. Grabbing an item with a
|
||||||
|
## hand transfers authority to the grabbing client (its local kinematic grab
|
||||||
|
## driver then drives the synced transform); releasing returns authority and the
|
||||||
|
## throw velocity to the server. Non-authority peers keep the body frozen and
|
||||||
|
## follow the synced transform, so only one machine ever simulates an item.
|
||||||
|
|
||||||
|
@onready var _item: XRToolsPickable = get_parent() as XRToolsPickable
|
||||||
|
|
||||||
|
var net_held_by := 0 # peer id currently holding (0 = free); kept consistent via NetworkManager
|
||||||
|
var _was_authority := true
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if not _item:
|
||||||
|
push_error("NetPickable must be a child of an XRToolsPickable")
|
||||||
|
return
|
||||||
|
_item.grabbed.connect(_on_grabbed)
|
||||||
|
_item.released.connect(_on_released)
|
||||||
|
_was_authority = _item.is_multiplayer_authority()
|
||||||
|
_apply_authority_state()
|
||||||
|
|
||||||
|
|
||||||
|
## Enable the item only where it is free or where we are its current holder, so
|
||||||
|
## a peer's snap zone / hand never grabs an item another peer is holding. Called
|
||||||
|
## by NetworkManager whenever net_held_by changes. Note: net_held_by is set per
|
||||||
|
## peer by RPC (not by a synchronizer), so the holder keeps its item enabled
|
||||||
|
## while everyone else disables their copy.
|
||||||
|
func apply_held_state() -> void:
|
||||||
|
if not _item:
|
||||||
|
return
|
||||||
|
_item.enabled = net_held_by == 0 or net_held_by == multiplayer.get_unique_id()
|
||||||
|
|
||||||
|
|
||||||
|
func _physics_process(_delta: float) -> void:
|
||||||
|
if not NetworkManager.is_online():
|
||||||
|
return
|
||||||
|
var mine := _item.is_multiplayer_authority()
|
||||||
|
if mine != _was_authority:
|
||||||
|
_was_authority = mine
|
||||||
|
_apply_authority_state()
|
||||||
|
|
||||||
|
|
||||||
|
# Configure physics for whether we own this item or merely mirror it.
|
||||||
|
func _apply_authority_state() -> void:
|
||||||
|
if not _item:
|
||||||
|
return
|
||||||
|
if _item.is_multiplayer_authority():
|
||||||
|
# We own physics: simulate the item whenever it is not held.
|
||||||
|
if not _item.is_picked_up():
|
||||||
|
_item.freeze = false
|
||||||
|
else:
|
||||||
|
# Someone else owns it: follow the synced transform kinematically.
|
||||||
|
_item.freeze = true
|
||||||
|
_item.freeze_mode = RigidBody3D.FREEZE_MODE_KINEMATIC
|
||||||
|
|
||||||
|
|
||||||
|
func _on_grabbed(_pickable: Node3D, by: Node3D) -> void:
|
||||||
|
if not NetworkManager.is_online():
|
||||||
|
return
|
||||||
|
# Only hand grabs transfer authority; snap-zone grabs are server-driven.
|
||||||
|
if not (by is XRToolsFunctionPickup):
|
||||||
|
return
|
||||||
|
NetworkManager.request_item_authority.rpc_id(1, _item.get_path())
|
||||||
|
|
||||||
|
|
||||||
|
func _on_released(_pickable: Node3D, by: Node3D) -> void:
|
||||||
|
if not NetworkManager.is_online():
|
||||||
|
return
|
||||||
|
if not (by is XRToolsFunctionPickup):
|
||||||
|
return
|
||||||
|
NetworkManager.release_item_authority.rpc_id(1, _item.get_path(), _item.linear_velocity, _item.angular_velocity)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://6n784li8n5ck
|
||||||
@@ -26,7 +26,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5270121, 0)
|
|||||||
collision_layer = 65536
|
collision_layer = 65536
|
||||||
collision_mask = 65536
|
collision_mask = 65536
|
||||||
script = ExtResource("1_dlkho")
|
script = ExtResource("1_dlkho")
|
||||||
snap_mode = 1
|
snap_mode = 0
|
||||||
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
||||||
|
|
||||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=969340130]
|
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=969340130]
|
||||||
|
|||||||
+9
-1
@@ -16,6 +16,11 @@ material = SubResource("StandardMaterial3D_m5xe2")
|
|||||||
[sub_resource type="SphereShape3D" id="SphereShape3D_vlqg6"]
|
[sub_resource type="SphereShape3D" id="SphereShape3D_vlqg6"]
|
||||||
radius = 0.3
|
radius = 0.3
|
||||||
|
|
||||||
|
[sub_resource type="SceneReplicationConfig" id="Repl_hob"]
|
||||||
|
properties/0/path = NodePath(".:progress")
|
||||||
|
properties/0/spawn = true
|
||||||
|
properties/0/replication_mode = 1
|
||||||
|
|
||||||
[node name="Hob" type="StaticBody3D" unique_id=1687971542 groups=["station"]]
|
[node name="Hob" type="StaticBody3D" unique_id=1687971542 groups=["station"]]
|
||||||
script = ExtResource("1_7jc4g")
|
script = ExtResource("1_7jc4g")
|
||||||
|
|
||||||
@@ -34,9 +39,12 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5270121, 0)
|
|||||||
collision_layer = 65536
|
collision_layer = 65536
|
||||||
collision_mask = 65536
|
collision_mask = 65536
|
||||||
script = ExtResource("2_er1dp")
|
script = ExtResource("2_er1dp")
|
||||||
snap_mode = 1
|
snap_mode = 0
|
||||||
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
metadata/_custom_type_script = "uid://cquqe4f1m1sw6"
|
||||||
|
|
||||||
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=370920392]
|
[node name="CollisionShape3D2" type="CollisionShape3D" parent="XRToolsSnapZone" unique_id=370920392]
|
||||||
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.000975132, 0)
|
transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, -0.000975132, 0)
|
||||||
shape = SubResource("SphereShape3D_vlqg6")
|
shape = SubResource("SphereShape3D_vlqg6")
|
||||||
|
|
||||||
|
[node name="ProgressSync" type="MultiplayerSynchronizer" parent="."]
|
||||||
|
replication_config = SubResource("Repl_hob")
|
||||||
|
|||||||
+32
-29
@@ -1,59 +1,62 @@
|
|||||||
extends StaticBody3D
|
extends StaticBody3D
|
||||||
|
|
||||||
|
## Cooking station. The cook timer and item conversion run only on the machine
|
||||||
|
## that owns world logic (server or offline host); `progress` is replicated to
|
||||||
|
## clients for UI via a MultiplayerSynchronizer. This is the reference
|
||||||
|
## implementation of the reusable "station work-progress" pattern: a server-owned
|
||||||
|
## progress value advanced by an input source (here, a timer), which converts the
|
||||||
|
## held item once it reaches the threshold and spawns the result over the network.
|
||||||
|
|
||||||
@export var cook_speed = 1
|
@export var cook_speed := 1.0
|
||||||
@onready var snap_zone: XRToolsSnapZone = $XRToolsSnapZone
|
@onready var snap_zone: XRToolsSnapZone = $XRToolsSnapZone
|
||||||
|
|
||||||
var item = null
|
var item = null
|
||||||
var progress = 0
|
var progress: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if snap_zone.initial_object:
|
if snap_zone.initial_object:
|
||||||
item = snap_zone.initial_object
|
item = snap_zone.initial_object
|
||||||
print("Hob has item: ", item)
|
|
||||||
else:
|
|
||||||
print("Hob is empty")
|
|
||||||
|
|
||||||
func convert_item(cookable: CookableItem) -> void:
|
func convert_item(cookable: CookableItem) -> void:
|
||||||
print("Hob converting ", cookable)
|
|
||||||
|
|
||||||
var old_pickable = snap_zone.picked_up_object
|
var old_pickable = snap_zone.picked_up_object
|
||||||
if not old_pickable:
|
if not old_pickable:
|
||||||
push_warning("Hob finished cooking item, but snap zone is missing its reference")
|
push_warning("Hob finished cooking item, but snap zone is missing its reference")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("picked_up_object ", snap_zone.picked_up_object)
|
|
||||||
|
|
||||||
var original_transform = old_pickable.global_transform
|
var original_transform: Transform3D = old_pickable.global_transform
|
||||||
var new_scene_instance = cookable.turns_into.instantiate()
|
|
||||||
|
|
||||||
# Add the new item to the hob's parent (so it lives in the main world space)
|
|
||||||
get_parent().add_child(new_scene_instance)
|
|
||||||
new_scene_instance.global_transform = original_transform
|
|
||||||
|
|
||||||
snap_zone.drop_object()
|
# Spawn the result over the network (server) or locally (offline).
|
||||||
print("old_pickable ", old_pickable)
|
var result := NetworkManager.spawn_item(cookable.turns_into.resource_path, original_transform)
|
||||||
|
|
||||||
|
# Remove the cooked item (despawn replicates for spawner-managed items).
|
||||||
|
snap_zone.drop_object()
|
||||||
old_pickable.queue_free()
|
old_pickable.queue_free()
|
||||||
snap_zone.pick_up_object(new_scene_instance)
|
|
||||||
|
|
||||||
|
|
||||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
# Snap the result into the now-empty zone.
|
||||||
func _process(delta: float) -> void:
|
if result:
|
||||||
# Find the CookableItem in held object
|
snap_zone.pick_up_object(result)
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
# Only the world owner runs cooking logic; clients receive `progress` + the
|
||||||
|
# converted item via replication.
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find the CookableItem in the held object.
|
||||||
var cookable = null
|
var cookable = null
|
||||||
if snap_zone.picked_up_object:
|
if snap_zone.picked_up_object:
|
||||||
var matches = snap_zone.picked_up_object.get_children().filter(func(c): return c is CookableItem)
|
var matches = snap_zone.picked_up_object.get_children().filter(func(c): return c is CookableItem)
|
||||||
if matches.size() > 0:
|
if matches.size() > 0:
|
||||||
cookable = matches[0]
|
cookable = matches[0]
|
||||||
|
|
||||||
# Cook it if exists, then convert item after cooking
|
# Advance progress and convert once cooked.
|
||||||
if cookable:
|
if cookable:
|
||||||
progress += cook_speed * delta
|
progress += cook_speed * delta
|
||||||
print("Cooking ", progress)
|
|
||||||
if progress >= cookable.cooking_time:
|
if progress >= cookable.cooking_time:
|
||||||
progress = 0
|
progress = 0.0
|
||||||
convert_item(cookable)
|
convert_item(cookable)
|
||||||
else:
|
else:
|
||||||
progress = 0
|
progress = 0.0
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ func _ready() -> void:
|
|||||||
|
|
||||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
# Only the world owner dispenses, so every client doesn't spawn its own copy.
|
||||||
|
if not NetworkManager.owns_world():
|
||||||
|
return
|
||||||
if not snap_zone.picked_up_object:
|
if not snap_zone.picked_up_object:
|
||||||
var new_item = item_scene.instantiate()
|
var new_item := NetworkManager.spawn_item(item_scene.resource_path, snap_zone.global_transform)
|
||||||
|
if new_item:
|
||||||
# Add the new item to the hob's parent (so it lives in the main world space)
|
snap_zone.pick_up_object(new_item)
|
||||||
get_parent().add_child(new_item)
|
|
||||||
new_item.global_transform = snap_zone.transform
|
|
||||||
snap_zone.pick_up_object(new_item)
|
|
||||||
|
|||||||
@@ -53,4 +53,4 @@
|
|||||||
Plate[`Hamburger`] accepts `Chips` => Plate[`Hamburger`, `Chips`]
|
Plate[`Hamburger`] accepts `Chips` => Plate[`Hamburger`, `Chips`]
|
||||||
Plate[`Hamburger`, `Chips`] accepts `Cheese` => Plate[`Hamburger(Cheese=true)`, `Chips`]
|
Plate[`Hamburger`, `Chips`] accepts `Cheese` => Plate[`Hamburger(Cheese=true)`, `Chips`]
|
||||||
The logic for combining has to be expanded for plates.
|
The logic for combining has to be expanded for plates.
|
||||||
- Plates have a property `is_dirty` they get after a customer clears the plate, and is disabled after being processed in `Sink`
|
- Plates have a property `is_dirty` they get after a customer clears the plate, and is disabled after being processed in `Sink`
|
||||||
|
|||||||
@@ -10,6 +10,4 @@ uv1_triplanar = true
|
|||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
next_pass = SubResource("StandardMaterial3D_0gbf8")
|
next_pass = SubResource("StandardMaterial3D_0gbf8")
|
||||||
metallic = 1.0
|
albedo_color = Color(0.35156325, 0.3515627, 0.3515629, 1)
|
||||||
metallic_specular = 0.0
|
|
||||||
roughness = 0.0
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
[ext_resource type="PackedScene" uid="uid://diyu06cw06syv" path="res://addons/godot-xr-tools/player/player_body.tscn" id="4_6xr3x"]
|
[ext_resource type="PackedScene" uid="uid://diyu06cw06syv" path="res://addons/godot-xr-tools/player/player_body.tscn" id="4_6xr3x"]
|
||||||
[ext_resource type="PackedScene" uid="uid://b6bk2pj8vbj28" path="res://addons/godot-xr-tools/functions/movement_turn.tscn" id="4_rd8py"]
|
[ext_resource type="PackedScene" uid="uid://b6bk2pj8vbj28" path="res://addons/godot-xr-tools/functions/movement_turn.tscn" id="4_rd8py"]
|
||||||
[ext_resource type="Script" uid="uid://ck4yn3hxuobj7" path="res://addons/godot-xr-tools/player/player_body.gd" id="5_rd8py"]
|
[ext_resource type="Script" uid="uid://ck4yn3hxuobj7" path="res://addons/godot-xr-tools/player/player_body.gd" id="5_rd8py"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://cqhw276realc" path="res://addons/godot-xr-tools/functions/function_pointer.tscn" id="6_ptr"]
|
||||||
|
|
||||||
[node name="XROrigin3D" type="XROrigin3D" unique_id=2055526621]
|
[node name="XROrigin3D" type="XROrigin3D" unique_id=2055526621]
|
||||||
|
|
||||||
@@ -25,6 +26,10 @@ strafe = true
|
|||||||
[node name="FunctionPickup" parent="XRControllerLeftHand" unique_id=2133415351 instance=ExtResource("3_rd8py")]
|
[node name="FunctionPickup" parent="XRControllerLeftHand" unique_id=2133415351 instance=ExtResource("3_rd8py")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.009340696, -0.0074635968, 0.024033919)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.009340696, -0.0074635968, 0.024033919)
|
||||||
|
|
||||||
|
[node name="FunctionPointer" parent="XRControllerLeftHand" instance=ExtResource("6_ptr")]
|
||||||
|
show_laser = 2
|
||||||
|
show_target = true
|
||||||
|
|
||||||
[node name="XRControllerRightHand" type="XRController3D" parent="." unique_id=202756852]
|
[node name="XRControllerRightHand" type="XRController3D" parent="." unique_id=202756852]
|
||||||
tracker = &"right_hand"
|
tracker = &"right_hand"
|
||||||
|
|
||||||
@@ -39,6 +44,10 @@ smooth_turn_speed = 3.5
|
|||||||
[node name="FunctionPickup" parent="XRControllerRightHand" unique_id=1876210450 instance=ExtResource("3_rd8py")]
|
[node name="FunctionPickup" parent="XRControllerRightHand" unique_id=1876210450 instance=ExtResource("3_rd8py")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.0074635968, 0.024033919)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.0074635968, 0.024033919)
|
||||||
|
|
||||||
|
[node name="FunctionPointer" parent="XRControllerRightHand" instance=ExtResource("6_ptr")]
|
||||||
|
show_laser = 2
|
||||||
|
show_target = true
|
||||||
|
|
||||||
[node name="PlayerBody" type="CharacterBody3D" parent="." unique_id=1444632058 groups=["player_body"] instance=ExtResource("4_6xr3x")]
|
[node name="PlayerBody" type="CharacterBody3D" parent="." unique_id=1444632058 groups=["player_body"] instance=ExtResource("4_6xr3x")]
|
||||||
process_priority = -100
|
process_priority = -100
|
||||||
process_physics_priority = -100
|
process_physics_priority = -100
|
||||||
|
|||||||
@@ -14,7 +14,10 @@
|
|||||||
"eee1990e-f957-4499-84b6-e68003fcb78e",
|
"eee1990e-f957-4499-84b6-e68003fcb78e",
|
||||||
"854c7e1e-7521-4bcd-83ff-bb3fecec3142",
|
"854c7e1e-7521-4bcd-83ff-bb3fecec3142",
|
||||||
"4ab36c89-a377-4f25-86a1-ac276aadf4a2",
|
"4ab36c89-a377-4f25-86a1-ac276aadf4a2",
|
||||||
"cde44fb1-3fdf-4ea0-8267-d28d894e4e7d"
|
"cde44fb1-3fdf-4ea0-8267-d28d894e4e7d",
|
||||||
|
"1ea70f45-8834-4115-be50-59eea96ca030",
|
||||||
|
"94a20766-e0b6-4bf7-8b1b-e99f85321d1b",
|
||||||
|
"8ae93002-d0f6-4967-8036-d394bad37c23"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -126,7 +129,7 @@
|
|||||||
{
|
{
|
||||||
"uuid": "d9cebd68-792e-4d01-a916-7df5f128b4a8",
|
"uuid": "d9cebd68-792e-4d01-a916-7df5f128b4a8",
|
||||||
"title": "Implement Multiplayer Synchronization (Network Architecture)",
|
"title": "Implement Multiplayer Synchronization (Network Architecture)",
|
||||||
"description": "Establish the client-server (or P2P) networking model for the game. Focus on syncing critical game state: physics objects (FoodItems), player actions (gestures/work progress), and station states (Chopping progress).",
|
"description": "Establish the client-server networking model for the game. Focus on syncing critical game state: physics objects (FoodItems), player actions (gestures/work progress), and station states (Chopping progress).",
|
||||||
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
{
|
||||||
@@ -148,8 +151,44 @@
|
|||||||
{
|
{
|
||||||
"details": "Test latency handling for high-frequency inputs like the \"wave arms\" gesture.",
|
"details": "Test latency handling for high-frequency inputs like the \"wave arms\" gesture.",
|
||||||
"done": false
|
"done": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"details": "Fix cubes spawn on click Host game",
|
||||||
|
"done": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"details": "All nodes get synced, don't just load same scene file",
|
||||||
|
"done": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "1ea70f45-8834-4115-be50-59eea96ca030",
|
||||||
|
"title": "Shelf",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "94a20766-e0b6-4bf7-8b1b-e99f85321d1b",
|
||||||
|
"title": "Item highlighing on hover",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "8ae93002-d0f6-4967-8036-d394bad37c23",
|
||||||
|
"title": "Ambient lighting",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "1b64306e-839a-49c1-a6d7-42aa91a62596",
|
||||||
|
"title": "New task",
|
||||||
|
"description": "",
|
||||||
|
"category": "bf4ec62e-526e-4027-876f-0b22bd17f79a",
|
||||||
|
"steps": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"layout": {
|
"layout": {
|
||||||
|
|||||||
@@ -1,12 +1,126 @@
|
|||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
var xr_interface :XRInterface
|
## World manager. Owns the multiplayer spawners and the world-space main menu,
|
||||||
|
## and turns NetworkManager's session events into spawned/despawned players and
|
||||||
|
## seeded items.
|
||||||
|
##
|
||||||
|
## Startup:
|
||||||
|
## - Command-line session (--server/--join): skip the menu, behave headlessly.
|
||||||
|
## - Otherwise: embody a local player (so they can point) and show the menu; the
|
||||||
|
## Host/Join/Solo buttons start the actual session.
|
||||||
|
|
||||||
func _ready():
|
const PLAYER_SCENE := preload("res://Player/Player.tscn")
|
||||||
xr_interface = XRServer.find_interface("OpenXR")
|
const CUBE_SCENE := "res://Items/PickupCube.tscn"
|
||||||
if xr_interface and xr_interface.is_initialized():
|
|
||||||
print("OpenXR started :)")
|
@onready var _players_spawner: MultiplayerSpawner = $PlayersSpawner
|
||||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
@onready var _items_spawner: MultiplayerSpawner = $ItemsSpawner
|
||||||
get_viewport().use_xr = true
|
@onready var _menu: Node3D = $NetworkMenu
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
NetworkManager.register_world(self, _players_spawner, _items_spawner)
|
||||||
|
NetworkManager.player_joined.connect(_on_player_joined)
|
||||||
|
NetworkManager.player_left.connect(_on_player_left)
|
||||||
|
NetworkManager.session_started.connect(_on_session_started)
|
||||||
|
NetworkManager.connection_failed.connect(_on_connection_failed)
|
||||||
|
NetworkManager.session_ended.connect(_on_session_ended)
|
||||||
|
_menu.host_pressed.connect(_on_menu_host)
|
||||||
|
_menu.join_pressed.connect(_on_menu_join)
|
||||||
|
_menu.solo_pressed.connect(_on_menu_solo)
|
||||||
|
|
||||||
|
# Start any command-line driven session now that we are listening.
|
||||||
|
NetworkManager.world_ready()
|
||||||
|
|
||||||
|
if NetworkManager.is_online():
|
||||||
|
# Command-line session: no menu.
|
||||||
|
_menu.hide_menu()
|
||||||
|
if _owns_spawning():
|
||||||
|
_spawn_starting_items()
|
||||||
else:
|
else:
|
||||||
print("OpenXR failed to start. Check your headset...")
|
# Show the menu; embody a local player so they can point at it.
|
||||||
|
_spawn_player(1)
|
||||||
|
_menu.show_menu()
|
||||||
|
|
||||||
|
|
||||||
|
# --- Menu button handlers --------------------------------------------------
|
||||||
|
|
||||||
|
func _on_menu_host() -> void:
|
||||||
|
_menu.hide_menu()
|
||||||
|
NetworkManager.host() # keeps the already-spawned local player (id 1)
|
||||||
|
_spawn_starting_items()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_menu_solo() -> void:
|
||||||
|
_menu.hide_menu()
|
||||||
|
_spawn_starting_items()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_menu_join(ip: String) -> void:
|
||||||
|
_menu.set_status("Connecting to %s ..." % ip)
|
||||||
|
# Our placeholder id "1" belongs to the host once we join; drop it and let
|
||||||
|
# the server spawn our real player.
|
||||||
|
_despawn_player(1)
|
||||||
|
NetworkManager.join(ip)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_session_started(is_server: bool) -> void:
|
||||||
|
if not is_server:
|
||||||
|
# Connected to a host as a client.
|
||||||
|
_menu.hide_menu()
|
||||||
|
# Stations are server-authoritative: clients must not run snap zones, or
|
||||||
|
# they create divergent local copies of snapped items.
|
||||||
|
_disable_client_snap_zones()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_connection_failed() -> void:
|
||||||
|
_menu.set_status("Connection failed — check the IP")
|
||||||
|
_spawn_player(1) # restore embodiment
|
||||||
|
_menu.show_menu()
|
||||||
|
|
||||||
|
|
||||||
|
# The client lost the server (or left): reload the scene to return to a clean
|
||||||
|
# main-menu state.
|
||||||
|
func _on_session_ended() -> void:
|
||||||
|
get_tree().reload_current_scene.call_deferred()
|
||||||
|
|
||||||
|
|
||||||
|
func _disable_client_snap_zones() -> void:
|
||||||
|
for station in get_tree().get_nodes_in_group("station"):
|
||||||
|
var zone = station.get_node_or_null("XRToolsSnapZone")
|
||||||
|
if zone:
|
||||||
|
zone.enabled = false
|
||||||
|
|
||||||
|
|
||||||
|
# --- Player / item spawning ------------------------------------------------
|
||||||
|
|
||||||
|
func _on_player_joined(peer_id: int) -> void:
|
||||||
|
if NetworkManager.is_server():
|
||||||
|
_spawn_player(peer_id)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_player_left(peer_id: int) -> void:
|
||||||
|
if NetworkManager.is_server():
|
||||||
|
_despawn_player(peer_id)
|
||||||
|
|
||||||
|
|
||||||
|
func _spawn_player(peer_id: int) -> void:
|
||||||
|
if has_node(str(peer_id)):
|
||||||
|
return
|
||||||
|
var p := PLAYER_SCENE.instantiate()
|
||||||
|
p.name = str(peer_id)
|
||||||
|
add_child(p)
|
||||||
|
|
||||||
|
|
||||||
|
func _despawn_player(peer_id: int) -> void:
|
||||||
|
var n := get_node_or_null(str(peer_id))
|
||||||
|
if n:
|
||||||
|
n.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _owns_spawning() -> bool:
|
||||||
|
return not NetworkManager.is_online() or NetworkManager.is_server()
|
||||||
|
|
||||||
|
|
||||||
|
func _spawn_starting_items() -> void:
|
||||||
|
NetworkManager.spawn_item(CUBE_SCENE, Transform3D(Basis(), Vector3(-0.705, 1.576, -1.032)))
|
||||||
|
NetworkManager.spawn_item(CUBE_SCENE, Transform3D(Basis(), Vector3(0.283, 1.561, -1.066)))
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
[gd_scene format=3 uid="uid://clw8ai6kngqxb"]
|
[gd_scene format=3 uid="uid://clw8ai6kngqxb"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_0xm2m"]
|
[ext_resource type="Script" uid="uid://b4ldkd3ngs0vp" path="res://main.gd" id="1_0xm2m"]
|
||||||
[ext_resource type="PackedScene" uid="uid://j5s5wus7tuhb" path="res://XROrigin.tscn" id="2_h2yge"]
|
|
||||||
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_272bh"]
|
[ext_resource type="Material" uid="uid://cmia50cfqxxo4" path="res://Textures/dev_material_3d.tres" id="3_272bh"]
|
||||||
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="3_h2yge"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="3_lquwl"]
|
[ext_resource type="PackedScene" uid="uid://j7caslh27nor" path="res://Stations/Hob.tscn" id="3_lquwl"]
|
||||||
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="4_7mycd"]
|
[ext_resource type="PackedScene" uid="uid://efaec6ymgabo" path="res://Stations/Counter.tscn" id="4_7mycd"]
|
||||||
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="6_7mycd"]
|
[ext_resource type="Sky" uid="uid://c1abtpwhdm2d5" path="res://Textures/sky/NightSkyHDRI009_16K.tres" id="6_7mycd"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="8_5vw27"]
|
[ext_resource type="PackedScene" uid="uid://dpot5qie20vf6" path="res://Items/burger.tscn" id="8_5vw27"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c0lknik4noobs" path="res://Items/BurgerBuns.tscn" id="8_vivmo"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://c6rift56ql3f8" path="res://Stations/BurgerBunsDispenser.tscn" id="9_2cqfq"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="9_kek77"]
|
[ext_resource type="PackedScene" uid="uid://bp3v1jl8pctro" path="res://Containers/plate.tscn" id="9_kek77"]
|
||||||
|
[ext_resource type="PackedScene" path="res://Net/network_menu.tscn" id="10_menu"]
|
||||||
[sub_resource type="BoxShape3D" id="BoxShape3D_vlqg6"]
|
[ext_resource type="PackedScene" uid="uid://b3m2ag8g5rj4r" path="res://Items/hamburger.tscn" id="12_yaehf"]
|
||||||
size = Vector3(5, 0.1, 5)
|
[ext_resource type="PackedScene" uid="uid://e4i6o5oriecx" path="res://Items/PickupCube.tscn" id="13_074og"]
|
||||||
|
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_24d3s"]
|
|
||||||
material = ExtResource("3_272bh")
|
|
||||||
size = Vector3(5, 0.1, 5)
|
|
||||||
|
|
||||||
[sub_resource type="Environment" id="Environment_bvwq1"]
|
[sub_resource type="Environment" id="Environment_bvwq1"]
|
||||||
background_mode = 2
|
background_mode = 2
|
||||||
sky = ExtResource("6_7mycd")
|
sky = ExtResource("6_7mycd")
|
||||||
|
ambient_light_source = 2
|
||||||
|
ambient_light_color = Color(1, 1, 1, 1)
|
||||||
|
ambient_light_sky_contribution = 0.35
|
||||||
|
ambient_light_energy = 0.1
|
||||||
reflected_light_source = 2
|
reflected_light_source = 2
|
||||||
tonemap_mode = 3
|
tonemap_mode = 3
|
||||||
ssr_enabled = true
|
ssr_enabled = true
|
||||||
@@ -27,49 +27,104 @@ ssao_enabled = true
|
|||||||
ssil_enabled = true
|
ssil_enabled = true
|
||||||
sdfgi_enabled = true
|
sdfgi_enabled = true
|
||||||
|
|
||||||
|
[sub_resource type="BoxShape3D" id="BoxShape3D_cegan"]
|
||||||
|
size = Vector3(15, 0.1, 15)
|
||||||
|
|
||||||
|
[sub_resource type="BoxMesh" id="BoxMesh_82xsv"]
|
||||||
|
material = ExtResource("3_272bh")
|
||||||
|
size = Vector3(15, 0.1, 15)
|
||||||
|
|
||||||
[node name="Main" type="Node3D" unique_id=1312265607]
|
[node name="Main" type="Node3D" unique_id=1312265607]
|
||||||
script = ExtResource("1_0xm2m")
|
script = ExtResource("1_0xm2m")
|
||||||
|
|
||||||
[node name="XROrigin3D" parent="." unique_id=2055526621 instance=ExtResource("2_h2yge")]
|
[node name="PlayersSpawner" type="MultiplayerSpawner" parent="." unique_id=1626417783]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8820953, 0)
|
_spawnable_scenes = PackedStringArray("uid://d3obr5ldhyiuq")
|
||||||
|
spawn_path = NodePath("..")
|
||||||
|
|
||||||
|
[node name="ItemsSpawner" type="MultiplayerSpawner" parent="." unique_id=462360509]
|
||||||
|
spawn_path = NodePath("..")
|
||||||
|
|
||||||
|
[node name="NetworkMenu" parent="." unique_id=1067581818 instance=ExtResource("10_menu")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, -0.8)
|
||||||
|
|
||||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1376644384]
|
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1376644384]
|
||||||
transform = Transform3D(-0.8660254, -0.43301278, 0.24999999, 0.3022632, -0.05510214, 0.9516306, -0.39829266, 0.8997021, 0.17860365, 0, 0, 0)
|
transform = Transform3D(-0.8660254, -0.43301278, 0.24999999, 0.3022632, -0.05510214, 0.9516306, -0.39829266, 0.8997021, 0.17860365, 0, 0, 0)
|
||||||
|
|
||||||
[node name="Floor" type="StaticBody3D" parent="." unique_id=122279514]
|
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
||||||
|
environment = SubResource("Environment_bvwq1")
|
||||||
|
|
||||||
|
[node name="DirectionalLight3D2" type="DirectionalLight3D" parent="." unique_id=1088862514]
|
||||||
|
transform = Transform3D(-0.8660254, -0.43301278, 0.24999999, 0.3022632, -0.05510214, 0.9516306, -0.39829266, 0.8997021, 0.17860365, 0, 0, 0)
|
||||||
|
|
||||||
|
[node name="Floor" type="StaticBody3D" parent="." unique_id=1667851334]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0)
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor" unique_id=958841648]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor" unique_id=1242150722]
|
||||||
shape = SubResource("BoxShape3D_vlqg6")
|
shape = SubResource("BoxShape3D_cegan")
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor/CollisionShape3D" unique_id=1939997056]
|
[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor/CollisionShape3D" unique_id=2074025214]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00018164515, 0.0006352663, -0.002532661)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00018164515, 0.0006352663, -0.002532661)
|
||||||
mesh = SubResource("BoxMesh_24d3s")
|
mesh = SubResource("BoxMesh_82xsv")
|
||||||
|
|
||||||
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("3_lquwl")]
|
[node name="Hob" parent="." unique_id=1687971542 instance=ExtResource("3_lquwl")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29336345, 0.8981018, -1.484)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.29336345, 0.8981018, -1.484)
|
||||||
|
|
||||||
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("4_7mycd")]
|
[node name="BurgerBuns" parent="." unique_id=1088240294 instance=ExtResource("8_vivmo")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.73186195, 0.8981018, -1.484)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.8162017, 1.6081157, -1.4714175)
|
||||||
|
|
||||||
[node name="PickableObject" parent="." unique_id=1675596942 instance=ExtResource("3_h2yge")]
|
[node name="BurgerBunsDispenser" parent="." unique_id=1720683779 instance=ExtResource("9_2cqfq")]
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -0.70505303, 1.5761534, -1.0317149)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.832131, 0.40028095, -1.4813508)
|
||||||
|
|
||||||
[node name="PickableObject2" parent="." unique_id=713087634 instance=ExtResource("3_h2yge")]
|
|
||||||
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, 0.28315204, 1.5607953, -1.0657519)
|
|
||||||
|
|
||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1177077250]
|
|
||||||
environment = SubResource("Environment_bvwq1")
|
|
||||||
|
|
||||||
[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=812168256]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.2856425, 1.4939771, -1.4959787)
|
|
||||||
visible = false
|
|
||||||
light_color = Color(1, 0.38431373, 0, 1)
|
|
||||||
light_energy = 1.784
|
|
||||||
shadow_enabled = true
|
|
||||||
|
|
||||||
[node name="burger" parent="." unique_id=1554047278 instance=ExtResource("8_5vw27")]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.482068, -1.2252241)
|
|
||||||
|
|
||||||
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("9_kek77")]
|
[node name="Plate" parent="." unique_id=190487773 instance=ExtResource("9_kek77")]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.22971058, 1.699144, -1.8499806)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.72025335, 1.4458435, -1.5107731)
|
||||||
|
|
||||||
|
[node name="burger" parent="." unique_id=1675596942 instance=ExtResource("8_5vw27")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1439226, 1.4195822, -1.2004558)
|
||||||
|
|
||||||
|
[node name="burger2" parent="." unique_id=2005944088 instance=ExtResource("8_5vw27")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1396115, 1.5300478, -1.2016745)
|
||||||
|
|
||||||
|
[node name="burger3" parent="." unique_id=1397498409 instance=ExtResource("8_5vw27")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1424646, 1.4969791, -1.210453)
|
||||||
|
|
||||||
|
[node name="burger4" parent="." unique_id=185317851 instance=ExtResource("8_5vw27")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.1414003, 1.4543622, -1.210453)
|
||||||
|
|
||||||
|
[node name="Hamburger" parent="." unique_id=1652598956 instance=ExtResource("12_yaehf")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6108264, 1.5438088, -1.4718918)
|
||||||
|
|
||||||
|
[node name="PickableObject" parent="." unique_id=1736331465 instance=ExtResource("13_074og")]
|
||||||
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.4133378, 1.4792972, -0.8079578)
|
||||||
|
|
||||||
|
[node name="PickableObject2" parent="." unique_id=1644484469 instance=ExtResource("13_074og")]
|
||||||
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.399936, 1.4639391, -0.92794985)
|
||||||
|
|
||||||
|
[node name="PickableObject3" parent="." unique_id=1829014935 instance=ExtResource("13_074og")]
|
||||||
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.5216382, 1.4792972, -0.8079578)
|
||||||
|
|
||||||
|
[node name="PickableObject4" parent="." unique_id=77451619 instance=ExtResource("13_074og")]
|
||||||
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.5082363, 1.4639391, -0.92794985)
|
||||||
|
|
||||||
|
[node name="Hamburger2" parent="." unique_id=1166243968 instance=ExtResource("12_yaehf")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.63934994, 1.5329368, -1.7388973)
|
||||||
|
|
||||||
|
[node name="PickableObject5" parent="." unique_id=880462791 instance=ExtResource("13_074og")]
|
||||||
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, 0, -2.6077032e-08, 1.4901161e-08, 1, -1.3030349, 1.4792972, -0.8079578)
|
||||||
|
|
||||||
|
[node name="PickableObject6" parent="." unique_id=1001033415 instance=ExtResource("13_074og")]
|
||||||
|
transform = Transform3D(1, 0, -2.2351742e-08, -2.9802322e-08, 1.0000001, -5.293956e-23, -2.6077032e-08, 1.4901161e-08, 1, -1.289633, 1.4639391, -0.92794985)
|
||||||
|
|
||||||
|
[node name="Counter" parent="." unique_id=1487893288 instance=ExtResource("4_7mycd")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.77353764, 0.8777853, -1.5049882)
|
||||||
|
|
||||||
|
[node name="Counter2" parent="." unique_id=782427350 instance=ExtResource("4_7mycd")]
|
||||||
|
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7615445, 0.8777853, -0.47819337)
|
||||||
|
|
||||||
|
[node name="Counter3" parent="." unique_id=601017957 instance=ExtResource("4_7mycd")]
|
||||||
|
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, -1.7615445, 0.8777853, 0.54801154)
|
||||||
|
|
||||||
|
[node name="Plate2" parent="." unique_id=640350345 instance=ExtResource("9_kek77")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.0154996, 1.4458435, -0.0048529953)
|
||||||
|
|
||||||
|
[node name="Plate3" parent="." unique_id=632801239 instance=ExtResource("9_kek77")]
|
||||||
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.5449673, 1.4458435, -0.0015302896)
|
||||||
|
|||||||
+1
-149
@@ -248,154 +248,6 @@ binding_path = "/user/hand/right/output/haptic"
|
|||||||
interaction_profile_path = "/interaction_profiles/khr/generic_controller"
|
interaction_profile_path = "/interaction_profiles/khr/generic_controller"
|
||||||
bindings = [SubResource("OpenXRIPBinding_r3qn1"), SubResource("OpenXRIPBinding_n01b8"), SubResource("OpenXRIPBinding_pjtev"), SubResource("OpenXRIPBinding_nqyri"), SubResource("OpenXRIPBinding_86uui"), SubResource("OpenXRIPBinding_nrtxc"), SubResource("OpenXRIPBinding_qovyo"), SubResource("OpenXRIPBinding_d6uso"), SubResource("OpenXRIPBinding_hvi7v"), SubResource("OpenXRIPBinding_7dxun"), SubResource("OpenXRIPBinding_rp8ih"), SubResource("OpenXRIPBinding_0uca0"), SubResource("OpenXRIPBinding_rjtq8"), SubResource("OpenXRIPBinding_lce2q"), SubResource("OpenXRIPBinding_ckeh6"), SubResource("OpenXRIPBinding_538mi"), SubResource("OpenXRIPBinding_548p5"), SubResource("OpenXRIPBinding_6o0wr"), SubResource("OpenXRIPBinding_fsghu"), SubResource("OpenXRIPBinding_88umk"), SubResource("OpenXRIPBinding_4uneg"), SubResource("OpenXRIPBinding_67o31"), SubResource("OpenXRIPBinding_lf1a1"), SubResource("OpenXRIPBinding_x1adc"), SubResource("OpenXRIPBinding_j1vtv"), SubResource("OpenXRIPBinding_tud50")]
|
bindings = [SubResource("OpenXRIPBinding_r3qn1"), SubResource("OpenXRIPBinding_n01b8"), SubResource("OpenXRIPBinding_pjtev"), SubResource("OpenXRIPBinding_nqyri"), SubResource("OpenXRIPBinding_86uui"), SubResource("OpenXRIPBinding_nrtxc"), SubResource("OpenXRIPBinding_qovyo"), SubResource("OpenXRIPBinding_d6uso"), SubResource("OpenXRIPBinding_hvi7v"), SubResource("OpenXRIPBinding_7dxun"), SubResource("OpenXRIPBinding_rp8ih"), SubResource("OpenXRIPBinding_0uca0"), SubResource("OpenXRIPBinding_rjtq8"), SubResource("OpenXRIPBinding_lce2q"), SubResource("OpenXRIPBinding_ckeh6"), SubResource("OpenXRIPBinding_538mi"), SubResource("OpenXRIPBinding_548p5"), SubResource("OpenXRIPBinding_6o0wr"), SubResource("OpenXRIPBinding_fsghu"), SubResource("OpenXRIPBinding_88umk"), SubResource("OpenXRIPBinding_4uneg"), SubResource("OpenXRIPBinding_67o31"), SubResource("OpenXRIPBinding_lf1a1"), SubResource("OpenXRIPBinding_x1adc"), SubResource("OpenXRIPBinding_j1vtv"), SubResource("OpenXRIPBinding_tud50")]
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_xri1r"]
|
|
||||||
action = SubResource("OpenXRAction_oi0ij")
|
|
||||||
binding_path = "/user/hand/left/input/aim/pose"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_etqcv"]
|
|
||||||
action = SubResource("OpenXRAction_oi0ij")
|
|
||||||
binding_path = "/user/hand/right/input/aim/pose"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_og5pg"]
|
|
||||||
action = SubResource("OpenXRAction_m08eo")
|
|
||||||
binding_path = "/user/hand/left/input/aim/pose"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_nwe40"]
|
|
||||||
action = SubResource("OpenXRAction_m08eo")
|
|
||||||
binding_path = "/user/hand/right/input/aim/pose"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_ts2ff"]
|
|
||||||
action = SubResource("OpenXRAction_c4j1d")
|
|
||||||
binding_path = "/user/hand/left/input/grip/pose"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_yhsv0"]
|
|
||||||
action = SubResource("OpenXRAction_c4j1d")
|
|
||||||
binding_path = "/user/hand/right/input/grip/pose"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_sf2dt"]
|
|
||||||
action = SubResource("OpenXRAction_sopde")
|
|
||||||
binding_path = "/user/hand/left/input/grip_surface/pose"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_67dwi"]
|
|
||||||
action = SubResource("OpenXRAction_sopde")
|
|
||||||
binding_path = "/user/hand/right/input/grip_surface/pose"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_hswdx"]
|
|
||||||
action = SubResource("OpenXRAction_iphn4")
|
|
||||||
binding_path = "/user/hand/left/input/menu/click"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_7gr0f"]
|
|
||||||
action = SubResource("OpenXRAction_iphn4")
|
|
||||||
binding_path = "/user/hand/right/input/system/click"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_uvspk"]
|
|
||||||
action = SubResource("OpenXRAction_wdehm")
|
|
||||||
binding_path = "/user/hand/left/input/x/click"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_ica2g"]
|
|
||||||
action = SubResource("OpenXRAction_wdehm")
|
|
||||||
binding_path = "/user/hand/right/input/a/click"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_5fecu"]
|
|
||||||
action = SubResource("OpenXRAction_clfly")
|
|
||||||
binding_path = "/user/hand/left/input/x/touch"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_0nfxl"]
|
|
||||||
action = SubResource("OpenXRAction_clfly")
|
|
||||||
binding_path = "/user/hand/right/input/a/touch"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_sbe0d"]
|
|
||||||
action = SubResource("OpenXRAction_e1frq")
|
|
||||||
binding_path = "/user/hand/left/input/y/click"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_rf1ko"]
|
|
||||||
action = SubResource("OpenXRAction_e1frq")
|
|
||||||
binding_path = "/user/hand/right/input/b/click"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_jx7ge"]
|
|
||||||
action = SubResource("OpenXRAction_l7aq8")
|
|
||||||
binding_path = "/user/hand/left/input/y/touch"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_d2w1t"]
|
|
||||||
action = SubResource("OpenXRAction_l7aq8")
|
|
||||||
binding_path = "/user/hand/right/input/b/touch"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_v2kct"]
|
|
||||||
action = SubResource("OpenXRAction_6ivru")
|
|
||||||
binding_path = "/user/hand/left/input/trigger/value"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_37uq4"]
|
|
||||||
action = SubResource("OpenXRAction_6ivru")
|
|
||||||
binding_path = "/user/hand/right/input/trigger/value"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_kooyb"]
|
|
||||||
action = SubResource("OpenXRAction_vfhwq")
|
|
||||||
binding_path = "/user/hand/left/input/trigger/value"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_51qre"]
|
|
||||||
action = SubResource("OpenXRAction_vfhwq")
|
|
||||||
binding_path = "/user/hand/right/input/trigger/value"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_fncxp"]
|
|
||||||
action = SubResource("OpenXRAction_5w03k")
|
|
||||||
binding_path = "/user/hand/left/input/trigger/touch"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_qi50k"]
|
|
||||||
action = SubResource("OpenXRAction_5w03k")
|
|
||||||
binding_path = "/user/hand/right/input/trigger/touch"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_h5icu"]
|
|
||||||
action = SubResource("OpenXRAction_typ1r")
|
|
||||||
binding_path = "/user/hand/left/input/squeeze/value"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_b1sv6"]
|
|
||||||
action = SubResource("OpenXRAction_typ1r")
|
|
||||||
binding_path = "/user/hand/right/input/squeeze/value"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_yu2t6"]
|
|
||||||
action = SubResource("OpenXRAction_clvbf")
|
|
||||||
binding_path = "/user/hand/left/input/squeeze/value"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_labib"]
|
|
||||||
action = SubResource("OpenXRAction_clvbf")
|
|
||||||
binding_path = "/user/hand/right/input/squeeze/value"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_altuc"]
|
|
||||||
action = SubResource("OpenXRAction_3k6la")
|
|
||||||
binding_path = "/user/hand/left/input/thumbstick"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_7p0fp"]
|
|
||||||
action = SubResource("OpenXRAction_3k6la")
|
|
||||||
binding_path = "/user/hand/right/input/thumbstick"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_yjnix"]
|
|
||||||
action = SubResource("OpenXRAction_i8esw")
|
|
||||||
binding_path = "/user/hand/left/input/thumbstick/click"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_pgh0x"]
|
|
||||||
action = SubResource("OpenXRAction_i8esw")
|
|
||||||
binding_path = "/user/hand/right/input/thumbstick/click"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_lplyu"]
|
|
||||||
action = SubResource("OpenXRAction_um1hv")
|
|
||||||
binding_path = "/user/hand/left/input/thumbstick/touch"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_ifnya"]
|
|
||||||
action = SubResource("OpenXRAction_um1hv")
|
|
||||||
binding_path = "/user/hand/right/input/thumbstick/touch"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_jl4vo"]
|
|
||||||
action = SubResource("OpenXRAction_sow2k")
|
|
||||||
binding_path = "/user/hand/left/output/haptic"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_1n6j6"]
|
|
||||||
action = SubResource("OpenXRAction_sow2k")
|
|
||||||
binding_path = "/user/hand/right/output/haptic"
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_o1nfs"]
|
|
||||||
interaction_profile_path = "/interaction_profiles/oculus/touch_controller"
|
|
||||||
bindings = [SubResource("OpenXRIPBinding_xri1r"), SubResource("OpenXRIPBinding_etqcv"), SubResource("OpenXRIPBinding_og5pg"), SubResource("OpenXRIPBinding_nwe40"), SubResource("OpenXRIPBinding_ts2ff"), SubResource("OpenXRIPBinding_yhsv0"), SubResource("OpenXRIPBinding_sf2dt"), SubResource("OpenXRIPBinding_67dwi"), SubResource("OpenXRIPBinding_hswdx"), SubResource("OpenXRIPBinding_7gr0f"), SubResource("OpenXRIPBinding_uvspk"), SubResource("OpenXRIPBinding_ica2g"), SubResource("OpenXRIPBinding_5fecu"), SubResource("OpenXRIPBinding_0nfxl"), SubResource("OpenXRIPBinding_sbe0d"), SubResource("OpenXRIPBinding_rf1ko"), SubResource("OpenXRIPBinding_jx7ge"), SubResource("OpenXRIPBinding_d2w1t"), SubResource("OpenXRIPBinding_v2kct"), SubResource("OpenXRIPBinding_37uq4"), SubResource("OpenXRIPBinding_kooyb"), SubResource("OpenXRIPBinding_51qre"), SubResource("OpenXRIPBinding_fncxp"), SubResource("OpenXRIPBinding_qi50k"), SubResource("OpenXRIPBinding_h5icu"), SubResource("OpenXRIPBinding_b1sv6"), SubResource("OpenXRIPBinding_yu2t6"), SubResource("OpenXRIPBinding_labib"), SubResource("OpenXRIPBinding_altuc"), SubResource("OpenXRIPBinding_7p0fp"), SubResource("OpenXRIPBinding_yjnix"), SubResource("OpenXRIPBinding_pgh0x"), SubResource("OpenXRIPBinding_lplyu"), SubResource("OpenXRIPBinding_ifnya"), SubResource("OpenXRIPBinding_jl4vo"), SubResource("OpenXRIPBinding_1n6j6")]
|
|
||||||
|
|
||||||
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_unnrh"]
|
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_unnrh"]
|
||||||
action = SubResource("OpenXRAction_oi0ij")
|
action = SubResource("OpenXRAction_oi0ij")
|
||||||
binding_path = "/user/hand/left/input/aim/pose"
|
binding_path = "/user/hand/left/input/aim/pose"
|
||||||
@@ -618,4 +470,4 @@ bindings = [SubResource("OpenXRIPBinding_jyu76"), SubResource("OpenXRIPBinding_a
|
|||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
action_sets = [SubResource("OpenXRActionSet_ngwcy")]
|
action_sets = [SubResource("OpenXRActionSet_ngwcy")]
|
||||||
interaction_profiles = [SubResource("OpenXRInteractionProfile_akdt0"), SubResource("OpenXRInteractionProfile_o1nfs"), SubResource("OpenXRInteractionProfile_d3nfp"), SubResource("OpenXRInteractionProfile_m1cgb")]
|
interaction_profiles = [SubResource("OpenXRInteractionProfile_akdt0"), SubResource("OpenXRInteractionProfile_d3nfp"), SubResource("OpenXRInteractionProfile_m1cgb")]
|
||||||
|
|||||||
@@ -19,9 +19,12 @@ config/icon="res://icon.svg"
|
|||||||
|
|
||||||
XRToolsUserSettings="*uid://bqgb8i74tm0t"
|
XRToolsUserSettings="*uid://bqgb8i74tm0t"
|
||||||
XRToolsRumbleManager="*uid://by853dk86g1qw"
|
XRToolsRumbleManager="*uid://by853dk86g1qw"
|
||||||
|
NetworkManager="*res://Net/network_manager.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
|
window/size/viewport_width=1920
|
||||||
|
window/size/viewport_height=1080
|
||||||
window/stretch/mode="canvas_items"
|
window/stretch/mode="canvas_items"
|
||||||
window/stretch/aspect="expand"
|
window/stretch/aspect="expand"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user