Added GodotSteamSync addon

This commit is contained in:
Chris Bell 2025-01-11 20:29:20 -06:00
parent 58be46ff19
commit 0ff0445442
41 changed files with 3018 additions and 2 deletions

View File

@ -0,0 +1,260 @@
extends Control
@onready var lobby_name_txt = $CreateLobbyCont/LobbyNameTxt
@onready var lobby_visibility = $CreateLobbyCont/LobbyVisibilityBtn
@onready var max_members = $CreateLobbyCont/MaxMembersBtn
@onready var lobby_member = preload("res://addons/godot_steam_sync/Lobby/lobby_member.tscn")
@onready var lobby_name_lbl = $LobbyPanel/MarginContainer/LobbyCont/Panel/LobbyNameLbl
@onready var start_btn = $LobbyPanel/MarginContainer/LobbyCont/StartBtn
var im_ready : bool = false
func _ready() -> void:
Steam.join_requested.connect(_on_lobby_join_requested)
#Steam.lobby_chat_update.connect(_on_lobby_chat_update)
Steam.lobby_created.connect(_on_lobby_created)
#Steam.lobby_data_update.connect(_on_lobby_data_update)
#Steam.lobby_invite.connect(_on_lobby_invite)
Steam.lobby_joined.connect(_on_lobby_joined)
#Steam.lobby_match_list.connect(_on_lobby_match_list)
#Steam.lobby_message.connect(_on_lobby_message)
Steam.persona_state_change.connect(_on_persona_change)
Steam.p2p_session_request.connect(_on_p2p_session_request)
Steam.p2p_session_connect_fail.connect(_on_p2p_session_connect_fail)
# Check for command line arguments
check_command_line()
func check_command_line() -> void:
var these_arguments: Array = OS.get_cmdline_args()
# There are arguments to process
if these_arguments.size() > 0:
# A Steam connection argument exists
if these_arguments[0] == "+connect_lobby":
# Lobby invite exists so try to connect to it
if int(these_arguments[1]) > 0:
# At this point, you'll probably want to change scenes
# Something like a loading into lobby screen
print("Command line lobby ID: %s" % these_arguments[1])
join_lobby(int(these_arguments[1]))
#region JoinLobbyRegion
func join_lobby(this_lobby_id: int) -> void:
print("Attempting to join lobby %s" % this_lobby_id)
# Clear any previous lobby members lists, if you were in a previous lobby
NetworkManager.LOBBY_MEMBERS.clear()
# Make the lobby join request to Steam
Steam.joinLobby(this_lobby_id)
func _on_lobby_joined(lobby_id: int, _permissions: int, _locked: bool, response: int):
# If joining succeed, this will be 1
if response == 1:
# Set this lobby ID as your lobby ID
NetworkManager.LOBBY_ID = lobby_id
# Print the lobby ID to a label
lobby_name_lbl.set_text(Steam.getLobbyData(NetworkManager.LOBBY_ID,"name"))
# Append to output
print("[STEAM] Joined lobby "+str(NetworkManager.LOBBY_ID)+".\n")
# Get the lobby members
get_lobby_members()
# Make the initial handshake
make_p2p_handshake()
change_lobby_ui(true)
# Else it failed for some reason
else:
# Get the failure reason
var FAIL_REASON: String
match response:
2: FAIL_REASON = "This lobby no longer exists."
3: FAIL_REASON = "You don't have permission to join this lobby."
4: FAIL_REASON = "The lobby is now full."
5: FAIL_REASON = "Uh... something unexpected happened!"
6: FAIL_REASON = "You are banned from this lobby."
7: FAIL_REASON = "You cannot join due to having a limited account."
8: FAIL_REASON = "This lobby is locked or disabled."
9: FAIL_REASON = "This lobby is community locked."
10: FAIL_REASON = "A user in the lobby has blocked you from joining."
11: FAIL_REASON = "A user you have blocked is in the lobby."
$Frame/Main/Displays/Outputs/Output.append_text("[STEAM] Failed joining lobby "+str(lobby_id)+": "+str(FAIL_REASON)+"\n")
# Reopen the server list
#_on_Open_Lobby_List_pressed()
func _on_lobby_join_requested(lobby_id: int, friend_id: int):
# Get the lobby owner's name
var OWNER_NAME = Steam.getFriendPersonaName(friend_id)
print("[STEAM] Joining "+str(OWNER_NAME)+"'s lobby...\n")
# Attempt to join the lobby
join_lobby(lobby_id)
#endregion
func change_lobby_ui(current : bool):
if current:
$CreateLobbyCont.hide()
$LobbyPanel.show()
$MembersPnl.show()
else:
$CreateLobbyCont.show()
$LobbyPanel.hide()
$MembersPnl.hide()
#region CreateLobbyRegion
func _on_create_lobby_btn_pressed():
if NetworkManager.LOBBY_ID == 0:
Steam.createLobby(lobby_visibility.get_selected_id(), int(max_members.get_item_text(max_members.get_selected_id())))
func _on_lobby_created(connect: int, this_lobby_id: int) -> void:
if connect == 1:
# Set the lobby ID
NetworkManager.LOBBY_ID = this_lobby_id
print("Created a lobby: %s" % NetworkManager.LOBBY_ID )
# Set this lobby as joinable, just in case, though this should be done by default
Steam.setLobbyJoinable(NetworkManager.LOBBY_ID, true)
# Set some lobby data
Steam.setLobbyData(NetworkManager.LOBBY_ID, "name", lobby_name_txt.text)
Steam.setLobbyData(NetworkManager.LOBBY_ID, "mode", "Mechatronauts")
# Allow P2P connections to fallback to being relayed through Steam if needed
var set_relay: bool = Steam.allowP2PPacketRelay(true)
get_lobby_members()
change_lobby_ui(true)
#endregion
func get_lobby_members() -> void:
# Clear your previous lobby list
NetworkManager.LOBBY_MEMBERS.clear()
for MEMBER in $MembersPnl/MarginContainer/ScrollContainer/VBoxContainer.get_children():
MEMBER.hide()
MEMBER.queue_free()
# Get the number of members from this lobby from Steam
var num_of_members: int = Steam.getNumLobbyMembers(NetworkManager.LOBBY_ID)
# Get the data of these players from Steam
for this_member in range(0, num_of_members):
# Get the member's Steam ID
var member_steam_id: int = Steam.getLobbyMemberByIndex(NetworkManager.LOBBY_ID, this_member)
# Get the member's Steam name
var member_steam_name: String = Steam.getFriendPersonaName(member_steam_id)
# Add them to the list
add_member_to_list(member_steam_id,member_steam_name)
if NetworkManager.STEAM_ID == Steam.getLobbyOwner(NetworkManager.LOBBY_ID):
NetworkManager.IS_READY[member_steam_id] = false
else:
start_btn.hide()
start_btn.disabled = true
# A user's information has changed
func _on_persona_change(this_steam_id: int, _flag: int) -> void:
# Make sure you're in a lobby and this user is valid or Steam might spam your console log
if NetworkManager.LOBBY_ID > 0:
print("A user (%s) had information change, update the lobby list" % this_steam_id)
# Update the player list
get_lobby_members()
func add_member_to_list(steam_id: int, steam_name: String):
print("Adding new player to the list: "+str(steam_id)+" / "+str(steam_name))
# Add them to the list
NetworkManager.LOBBY_MEMBERS.append({"steam_id":steam_id, "steam_name":steam_name })
# Instance the lobby member object
var THIS_MEMBER: Object = lobby_member.instantiate()
# Add their Steam name and ID
THIS_MEMBER.name = str(steam_id)
THIS_MEMBER._set_Member(steam_id, steam_name)
# Add the child node
$MembersPnl/MarginContainer/ScrollContainer/VBoxContainer.add_child(THIS_MEMBER)
#region P2PHandshake
func make_p2p_handshake() -> void:
print("Sending P2P handshake to the lobby")
P2P._send_P2P_Packet(0,0, {"message": "handshake", "from": NetworkManager.STEAM_ID},Steam.P2P_SEND_RELIABLE)
func _on_p2p_session_request(remote_id: int) -> void:
# Get the requester's name
var this_requester: String = Steam.getFriendPersonaName(remote_id)
print("%s is requesting a P2P session" % this_requester)
# Accept the P2P session; can apply logic to deny this request if needed
Steam.acceptP2PSessionWithUser(remote_id)
# Make the initial handshake
make_p2p_handshake()
#endregion
#region LobbyEvents
func _on_p2p_session_connect_fail(lobby_id: int, session_error: int) -> void:
# Note the session errors are: 0 - none, 1 - target user not running the same game, 2 - local user doesn't own app, 3 - target user isn't connected to Steam, 4 - connection timed out, 5 - unused
# If no error was given
if session_error == 0:
print("[WARNING] Session failure with "+str(lobby_id)+" [no error given].")
# Else if target user was not running the same game
elif session_error == 1:
print("[WARNING] Session failure with "+str(lobby_id)+" [target user not running the same game].")
# Else if local user doesn't own app / game
elif session_error == 2:
print("[WARNING] Session failure with "+str(lobby_id)+" [local user doesn't own app / game].")
# Else if target user isn't connected to Steam
elif session_error == 3:
print("[WARNING] Session failure with "+str(lobby_id)+" [target user isn't connected to Steam].")
# Else if connection timed out
elif session_error == 4:
print("[WARNING] Session failure with "+str(lobby_id)+" [connection timed out].")
# Else if unused
elif session_error == 5:
print("[WARNING] Session failure with "+str(lobby_id)+" [unused].")
# Else no known error
else:
print("[WARNING] Session failure with "+str(lobby_id)+" [unknown error "+str(session_error)+"].")
func _on_lobby_chat_update(this_lobby_id: int, change_id: int, making_change_id: int, chat_state: int) -> void:
# Get the user who has made the lobby change
var changer_name: String = Steam.getFriendPersonaName(change_id)
# If a player has joined the lobby
if chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_ENTERED:
print("%s has joined the lobby." % changer_name)
# Else if a player has left the lobby
elif chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_LEFT:
print("%s has left the lobby." % changer_name)
# Else if a player has been kicked
elif chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_KICKED:
print("%s has been kicked from the lobby." % changer_name)
# Else if a player has been banned
elif chat_state == Steam.CHAT_MEMBER_STATE_CHANGE_BANNED:
print("%s has been banned from the lobby." % changer_name)
# Else there was some unknown change
else:
print("%s did... something." % changer_name)
# Update the lobby now that a change has occurred
get_lobby_members()
#endregion
func _on_ready_btn_pressed():
if NetworkManager.STEAM_ID == Steam.getLobbyOwner(NetworkManager.LOBBY_ID):
im_ready = !im_ready
#for ready in NetworkManager.IS_READY.keys():
#if ready == NetworkManager.STEAM_ID:
NetworkManager.IS_READY[NetworkManager.STEAM_ID] = im_ready
else:
im_ready = !im_ready
P2P._send_P2P_Packet(0,Steam.getLobbyOwner(NetworkManager.LOBBY_ID),{"TYPE":NetworkManager.TYPES.READY,"steam_id":NetworkManager.STEAM_ID,"ready":im_ready},Steam.P2P_SEND_RELIABLE)
func _on_start_btn_pressed():
if NetworkManager.STEAM_ID == Steam.getLobbyOwner(NetworkManager.LOBBY_ID):
var result = NetworkManager.IS_READY.values().all(func(number): return number == true)
if result:
pass
#SceneManager.change_scene("res://godotsteam_sync_example/Test.tscn")

View File

@ -0,0 +1,34 @@
extends Panel
var STEAM_ID: int = 0
# Stored at the class level to enable comparisons when helper functions are called
var AVATAR: Image
# Is it ready? Do stuff!
func _ready():
# connect some signals
var SIGNAL_CONNECT: int = Steam.connect("avatar_loaded", Callable(self, "_loaded_Avatar"))
# Set this player up
func _set_Member(steam_id: int, steam_name: String) -> void:
# Set the ID and username
STEAM_ID = steam_id
$MarginContainer/HBoxContainer/ScrollContainer/UsernameLbl.set_text(steam_name)
# Get the avatar and show it
Steam.getPlayerAvatar(Steam.AVATAR_MEDIUM, STEAM_ID)
# Load an avatar
func _loaded_Avatar(id: int, this_size: int, buffer: PackedByteArray) -> void:
# Check we're only triggering a load for the right player, and check the data has actually changed
if id == STEAM_ID and (not AVATAR or not buffer == AVATAR.get_data()):
# Create the image and texture for loading
AVATAR = Image.create_from_data(this_size, this_size, false, Image.FORMAT_RGBA8, buffer)
# Apply it to the texture
var AVATAR_TEXTURE: ImageTexture = ImageTexture.create_from_image(AVATAR)
# Set it
$MarginContainer/HBoxContainer/Avatar.set_texture(AVATAR_TEXTURE)
func _on_view_btn_pressed():
Steam.activateGameOverlayToUser("steamid", STEAM_ID)

View File

@ -0,0 +1,55 @@
[gd_scene load_steps=3 format=3 uid="uid://d26bfvwtqaeqw"]
[ext_resource type="Script" path="res://addons/godot_steam_sync/Lobby/lobby_member.gd" id="1_vi8x1"]
[sub_resource type="LabelSettings" id="LabelSettings_x51ve"]
font_size = 10
[node name="LobbyMember" type="Panel"]
custom_minimum_size = Vector2(236, 36)
offset_right = 236.0
offset_bottom = 36.0
script = ExtResource("1_vi8x1")
metadata/_edit_use_anchors_ = true
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 3
theme_override_constants/margin_top = 3
theme_override_constants/margin_right = 3
theme_override_constants/margin_bottom = 3
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="Avatar" type="TextureRect" parent="MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
expand_mode = 1
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="UsernameLbl" type="Label" parent="MarginContainer/HBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
text = "Space War Name"
label_settings = SubResource("LabelSettings_x51ve")
horizontal_alignment = 1
vertical_alignment = 1
[node name="ViewBtn" type="Button" parent="MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
theme_override_colors/font_color = Color(0, 1, 0, 1)
theme_override_font_sizes/font_size = 14
text = "View"
[connection signal="pressed" from="MarginContainer/HBoxContainer/ViewBtn" to="." method="_on_view_btn_pressed"]

View File

@ -0,0 +1,174 @@
[gd_scene load_steps=2 format=3 uid="uid://cliutsom3dxmu"]
[ext_resource type="Script" path="res://addons/godot_steam_sync/Lobby/lobby.gd" id="1_2lhov"]
[node name="LobbyMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_2lhov")
[node name="Panel" type="Panel" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="MembersPnl" type="Panel" parent="."]
visible = false
layout_mode = 1
anchors_preset = -1
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -276.0
offset_top = -262.0
offset_right = -10.0
offset_bottom = -12.0
grow_horizontal = 0
grow_vertical = 0
metadata/_edit_use_anchors_ = true
[node name="MarginContainer" type="MarginContainer" parent="MembersPnl"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5
[node name="ScrollContainer" type="ScrollContainer" parent="MembersPnl/MarginContainer"]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MembersPnl/MarginContainer/ScrollContainer"]
layout_mode = 2
alignment = 1
[node name="CreateLobbyCont" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -143.0
offset_top = -162.0
offset_right = 143.0
offset_bottom = 162.0
grow_horizontal = 2
grow_vertical = 2
[node name="LobbyNameTxt" type="TextEdit" parent="CreateLobbyCont"]
custom_minimum_size = Vector2(0, 40)
layout_mode = 2
placeholder_text = "Lobby Name"
scroll_smooth = true
[node name="LobbyVisibilityBtn" type="OptionButton" parent="CreateLobbyCont"]
custom_minimum_size = Vector2(300, 35)
layout_mode = 2
item_count = 4
selected = 2
popup/item_0/text = "PRIVATE"
popup/item_0/id = 0
popup/item_1/text = "FRIENDS ONLY"
popup/item_1/id = 1
popup/item_2/text = "PUBLIC"
popup/item_2/id = 2
popup/item_3/text = "INVISIBLE"
popup/item_3/id = 3
[node name="MaxMembersBtn" type="OptionButton" parent="CreateLobbyCont"]
custom_minimum_size = Vector2(300, 35)
layout_mode = 2
item_count = 4
selected = 1
popup/item_0/text = "2"
popup/item_0/id = 0
popup/item_1/text = "4"
popup/item_1/id = 1
popup/item_2/text = "8"
popup/item_2/id = 2
popup/item_3/text = "16"
popup/item_3/id = 3
[node name="CreateLobbyBtn" type="Button" parent="CreateLobbyCont"]
custom_minimum_size = Vector2(0, 70)
layout_mode = 2
text = "Create Lobby"
[node name="LobbyPanel" type="Panel" parent="."]
visible = false
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -171.0
offset_top = -148.0
offset_right = 171.0
offset_bottom = 172.0
grow_horizontal = 2
grow_vertical = 2
[node name="MarginContainer" type="MarginContainer" parent="LobbyPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="LobbyCont" type="VBoxContainer" parent="LobbyPanel/MarginContainer"]
layout_mode = 2
[node name="Panel" type="Panel" parent="LobbyPanel/MarginContainer/LobbyCont"]
custom_minimum_size = Vector2(0, 60)
layout_mode = 2
[node name="LobbyNameLbl" type="Label" parent="LobbyPanel/MarginContainer/LobbyCont/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_font_sizes/font_size = 27
text = "Lobby Name"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Space" type="Control" parent="LobbyPanel/MarginContainer/LobbyCont"]
custom_minimum_size = Vector2(0, 55.06)
layout_mode = 2
[node name="ReadyBtn" type="Button" parent="LobbyPanel/MarginContainer/LobbyCont"]
custom_minimum_size = Vector2(0, 80)
layout_mode = 2
theme_override_font_sizes/font_size = 33
text = "READY"
[node name="StartBtn" type="Button" parent="LobbyPanel/MarginContainer/LobbyCont"]
custom_minimum_size = Vector2(0, 80)
layout_mode = 2
theme_override_font_sizes/font_size = 33
text = "Start"
[connection signal="pressed" from="CreateLobbyCont/CreateLobbyBtn" to="." method="_on_create_lobby_btn_pressed"]
[connection signal="pressed" from="LobbyPanel/MarginContainer/LobbyCont/ReadyBtn" to="." method="_on_ready_btn_pressed"]
[connection signal="pressed" from="LobbyPanel/MarginContainer/LobbyCont/StartBtn" to="." method="_on_start_btn_pressed"]

View File

@ -0,0 +1,24 @@
extends Control
@onready var online_text = $OnlineLbl
func _ready():
if NetworkManager.IS_ONLINE:
online_text.text = "online"
else:
online_text.text = "offline"
func _on_play_btn_pressed():
get_tree().change_scene_to_file("res://addons/godot_steam_sync/Lobby/lobby_menu.tscn")
func _on_exit_btn_pressed():
get_tree().quit()

View File

@ -0,0 +1,83 @@
[gd_scene load_steps=3 format=3 uid="uid://1d2bxfqc4m8x"]
[ext_resource type="Script" path="res://addons/godot_steam_sync/Lobby/main_menu.gd" id="1_6wvtt"]
[sub_resource type="SystemFont" id="SystemFont_j72ml"]
font_names = PackedStringArray("Sylfaen")
[node name="MainMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_6wvtt")
[node name="Panel" type="Panel" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="OnlineLbl" type="Label" parent="."]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -49.0
offset_top = -23.0
grow_horizontal = 0
grow_vertical = 0
theme_override_font_sizes/font_size = 0
text = "online"
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -175.0
offset_top = -35.0
offset_right = 175.0
offset_bottom = 35.0
grow_horizontal = 2
grow_vertical = 2
[node name="PlayBtn" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(350, 70)
layout_mode = 2
text = "Play Online
"
[node name="ExitBtn" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(350, 70)
layout_mode = 2
text = "Quit"
[node name="Titel" type="Label" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -277.0
offset_top = -201.0
offset_right = 277.0
offset_bottom = -143.0
grow_horizontal = 2
grow_vertical = 2
theme_override_fonts/font = SubResource("SystemFont_j72ml")
theme_override_font_sizes/font_size = 56
text = "GODOT STEAM SYNC"
uppercase = true
[connection signal="pressed" from="VBoxContainer/PlayBtn" to="." method="_on_play_btn_pressed"]
[connection signal="pressed" from="VBoxContainer/ExitBtn" to="." method="_on_exit_btn_pressed"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://14sorpwfe4hf"
path="res://.godot/imported/1.png-d99a973fff7536c0ce6f3facaed6e0f4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_steam_sync/Media/1.png"
dest_files=["res://.godot/imported/1.png-d99a973fff7536c0ce6f3facaed6e0f4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ctruhwnscx144"
path="res://.godot/imported/2.png-252d97d15980179767bc6bbd64608b77.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_steam_sync/Media/2.png"
dest_files=["res://.godot/imported/2.png-252d97d15980179767bc6bbd64608b77.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bbbywy0ofvkhx"
path="res://.godot/imported/image.png-c17ce491b4f523d51f56704d6387b67a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_steam_sync/Media/image.png"
dest_files=["res://.godot/imported/image.png-c17ce491b4f523d51f56704d6387b67a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -0,0 +1,55 @@
extends Node
var IS_ON_STEAM: bool = false
var IS_ON_STEAM_DECK: bool = false
var IS_ONLINE: bool = false
var IS_OWNED: bool = false
var STEAM_ID: int = 0
var STEAM_USERNAME: String = "No one"
var LOBBY_ID: int = 0
var LOBBY_MEMBERS: Array = []
var DATA : Dictionary
var LOBBY_MAX_MEMBERS: int = 4
var GAME_STARTED : bool = false
var IS_READY : Dictionary = {}
enum TYPES {START,READY,START_SCENE,TRANFORM_SYNC,PROPERTY,EVENT,RIGIDBODY_SYNC,SCENE_LOADED,COMMAND,VOICE,RAGDOLL}
@onready var player = preload("res://godotsteam_sync_example/fpc/character.tscn")
#region Initilazation
func _ready() -> void:
_initialize_Steam()
if IS_ON_STEAM_DECK:
get_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN
func _initialize_Steam() -> void:
if Engine.has_singleton("Steam"):
var INIT: Dictionary = Steam.steamInitEx(true,480)
# If the status isn't one, print out the possible error and quit the program
if INIT['status'] != 0:
print("[STEAM] Failed to initialize: "+str(INIT)+" Shutting down...")
get_tree().quit()
# Is the user actually using Steam; if false, the app assumes this is a non-Steam version
IS_ON_STEAM = true
# Checking if the app is on Steam Deck to modify certain behaviors
IS_ON_STEAM_DECK = Steam.isSteamRunningOnSteamDeck()
# Acquire information about the user
IS_ONLINE = Steam.loggedOn()
IS_OWNED = Steam.isSubscribed()
STEAM_ID = Steam.getSteamID()
STEAM_USERNAME = Steam.getPersonaName()
# Check if account owns the game
if IS_OWNED == false:
print("[STEAM] User does not own this game")
# Uncomment this line to close the game if the user does not own the game
get_tree().quit()
func _process(_delta: float) -> void:
if IS_ON_STEAM:
Steam.run_callbacks()
#endregion

View File

@ -0,0 +1,85 @@
class_name NetworkPlayerSpawner extends Node
## Where the players will be spawned.
@export var spawn_pos : Node
## The node the players will be in after they are spawned. It has to be at the center of the scene. It has to be named 'Players'.
@export var players_parent_node : Node
## Here you can choose which way each spawned player will move. If you choose an axis, the player born will automatically move one unit away. If the game is 2D, choosing the Z axis means the center.
@export_flags("X" ,"Y" ,"Z") var spawn_distance_axis = 1
@export var increase_spawn_distance : Vector3
func _ready():
players_parent_node.name = "Players"
if spawn_pos is Node3D:
for player in NetworkManager.LOBBY_MEMBERS.size():
var instance_player : Node = NetworkManager.player.instantiate()
instance_player.name = str(NetworkManager.LOBBY_MEMBERS[player]["steam_id"])
var direction = spawn_check()
match direction:
"CENTER":
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(0, 0, 0)
"X":
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(player + increase_spawn_distance.x, 0, 0)
"Y":
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(0, player + increase_spawn_distance.y, 0)
"Z":
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(0, 0, player + increase_spawn_distance.z)
"XY":
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(player + increase_spawn_distance.x, player + increase_spawn_distance.y, 0)
"XZ":
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(player + increase_spawn_distance.x, 0, player + increase_spawn_distance.z)
"YZ":
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(0, player + increase_spawn_distance.y, player + increase_spawn_distance.z)
"XYZ":
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(player + increase_spawn_distance.x, player + increase_spawn_distance.y, player + increase_spawn_distance.z)
_:
instance_player.transform.origin = spawn_pos.transform.origin + Vector3(0, 0, 0)
players_parent_node.add_child(instance_player)
if instance_player.name == str(NetworkManager.STEAM_ID):
instance_player.make_owner()
NetworkManager.GAME_STARTED = true
if spawn_pos is Node2D:
for player in NetworkManager.LOBBY_MEMBERS.size():
var instance_player : Node = NetworkManager.player.instantiate()
instance_player.name = str(NetworkManager.LOBBY_MEMBERS[player]["steam_id"])
var direction = spawn_check()
match direction:
"CENTER":
instance_player.position = spawn_pos.position + Vector2(0, 0)
"X":
instance_player.position = spawn_pos.position + Vector2(player + increase_spawn_distance.x, 0)
"Y":
instance_player.position = spawn_pos.position + Vector2(0, player + increase_spawn_distance.y)
"XY":
instance_player.position = spawn_pos.position + Vector2(player + increase_spawn_distance.x, player + increase_spawn_distance.y)
_:
instance_player.position = spawn_pos.position + Vector2(0, 0)
players_parent_node.add_child(instance_player)
if instance_player.name == str(NetworkManager.STEAM_ID):
instance_player.make_owner()
NetworkManager.GAME_STARTED = true
func spawn_check() -> String:
match spawn_distance_axis:
0:
return "CENTER"
1:
return "X"
2:
return "Y"
3:
return "XY"
4:
return "Z"
5:
return "XZ"
6:
return "YZ"
7:
return "XYZ"
_:
return "CENTER"
return "CENTER"

View File

@ -0,0 +1,123 @@
extends Node
const PACKET_READ_LIMIT: int = 32
func _process(_delta: float) -> void:
# Get packets only if lobby is joined
if NetworkManager.LOBBY_ID > 0:
_read_All_P2P_Packets()
func _read_P2P_Packet() -> void:
var packet_size0 : int = Steam.getAvailableP2PPacketSize(0)
#region Channel0
if packet_size0 > 0:
var this_packet0 : Dictionary = Steam.readP2PPacket(packet_size0, 0)
if this_packet0.is_empty() or this_packet0 == null:
print("WARNING: read an empty packet with non-zero size!")
# Get the remote user's ID
var packet_sender: int = this_packet0['remote_steam_id']
# Make the packet data readablev
var packet_code: PackedByteArray = this_packet0['data']
# Decompress the array before turning it into a useable dictionary
var READABLE: Dictionary = bytes_to_var(packet_code.decompress_dynamic(-1, FileAccess.COMPRESSION_GZIP))
# Append logic here to deal with packet data
if READABLE.has("TYPE"):
handle_packets(READABLE)
#endregion
func _read_All_P2P_Packets(read_count: int = 0) -> void:
if read_count >= PACKET_READ_LIMIT:
return
if Steam.getAvailableP2PPacketSize(0) > 0:
_read_P2P_Packet()
_read_All_P2P_Packets(read_count + 1)
var packet_size : float
func _send_P2P_Packet(channel: int,target: int, packet_data: Dictionary,send_type: int) -> bool:
# Create a data array to send the data through
var this_data: PackedByteArray
# Compress the PackedByteArray we create from our dictionary using the GZIP compression method
var compressed_data: PackedByteArray = var_to_bytes(packet_data).compress(FileAccess.COMPRESSION_GZIP)
this_data.append_array(compressed_data)
packet_size = this_data.size()
# If sending a packet to everyone
if target == 0:
# If there is more than one user, send packets
if NetworkManager.LOBBY_MEMBERS.size() > 1:
# Loop through all members that aren't you
for this_member in NetworkManager.LOBBY_MEMBERS:
if this_member['steam_id'] != NetworkManager.STEAM_ID:
return Steam.sendP2PPacket(this_member['steam_id'], this_data, send_type, channel)
else:
return Steam.sendP2PPacket(target, this_data, send_type, channel)
return false
func handle_start_packet(READABLE):
# This packet reading when someone ready
if READABLE["TYPE"] == NetworkManager.TYPES.READY:
NetworkManager.IS_READY[READABLE["steam_id"]] = READABLE["ready"]
# This packet reading when lobby leader change_scene
#if READABLE["TYPE"] == NetworkManager.TYPES.START_SCENE:
#SceneManager.change_scene(READABLE["scene"])
#
func handle_event_packets(READABLE):
#if READABLE["TYPE"] == NetworkManager.TYPES.COMMAND:
#print("COMMAND:" + READABLE["method"] + str(READABLE["args"]))
#if READABLE["args"] != null:
#Command.callv(READABLE["method"],READABLE["args"])
#else:
#Command.call(READABLE["method"])
if READABLE['TYPE'] == NetworkManager.TYPES.EVENT:
if READABLE["args"] != null:
get_tree().root.get_node(READABLE["node_path"]).callv(READABLE["method"],READABLE["args"])
else:
get_tree().root.get_node(READABLE["node_path"]).call(READABLE["method"])
func handle_property_packets(READABLE):
if READABLE['TYPE'] == NetworkManager.TYPES.TRANFORM_SYNC and NetworkManager.GAME_STARTED:
if READABLE["property"] == "global_position":
get_tree().root.get_node(READABLE["node_path"]).transform_buffer[0] = READABLE
if READABLE["property"] == "rotation":
get_tree().root.get_node(READABLE["node_path"]).transform_buffer[1] = READABLE
if READABLE["property"] == "scale":
get_tree().root.get_node(READABLE["node_path"]).transform_buffer[2] = READABLE
if READABLE["TYPE"] == NetworkManager.TYPES.RAGDOLL and NetworkManager.GAME_STARTED:
get_tree().root.get_node(READABLE["node_path"]).transform_buffer = READABLE
if READABLE["TYPE"] == NetworkManager.TYPES.RIGIDBODY_SYNC and NetworkManager.GAME_STARTED:
get_tree().root.get_node(READABLE["node_path"]).sync_data = READABLE
if READABLE["TYPE"] == NetworkManager.TYPES.PROPERTY and NetworkManager.GAME_STARTED:
if !READABLE["interpolated"]:
get_tree().root.get_node(READABLE["node_path"]).set(READABLE["property"],READABLE["value"])
else:
var DATA :Array = [READABLE["property"],READABLE["value"]]
get_tree().root.get_node(READABLE["node_path"]).DATA = DATA
##
func handle_voice(READABLE):
if READABLE["TYPE"] == NetworkManager.TYPES.VOICE and NetworkManager.GAME_STARTED:
get_tree().root.get_node(READABLE["node_path"]).process_voice_data(READABLE["voice_data"])
#await get_tree().create_timer(0.1).timeout
func handle_packets(READABLE):
handle_start_packet(READABLE)
handle_event_packets(READABLE)
handle_property_packets(READABLE)
handle_voice(READABLE)

View File

@ -0,0 +1,14 @@
extends Node
#@onready var loadingScreen = preload("res://addons/godot_steam_sync/SceneChanger/LoadingScreen.tscn")
#func send(method : String,args = null):
#var DATA : Dictionary = {"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.COMMAND,"args":args,"method":method}
#P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_RELIABLE)
#
#
#func start_scene(scene : String):
#NetworkManager.GAME_STARTED = false
#var instance = loadingScreen.instantiate()
#get_parent().add_child(instance)
#instance.change(scene)
#

View File

@ -0,0 +1,15 @@
@icon("res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/funcSyncIcon.png")
class_name FuncSync extends Synchronizer
signal FuncCalled(method : String,args)
func _ready() -> void:
connect("FuncCalled",_on_func_called)
func call_f(method : String,args = null):
emit_signal("FuncCalled",method,args)
func _on_func_called(method, args):
if NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.EVENT,"args":args,"node_path":get_parent().get_path(),"method":method}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_RELIABLE)

View File

@ -0,0 +1,71 @@
@icon("res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/propertySyncIcon.png")
class_name PropertySync extends Synchronizer
@export_group("SETTINGS","is")
## If true, onlu lobby owner will send the packet.
@export var is_only_lobby_owner : bool = false
@export_group("NODES","object")
## Select player if its not player or not inside player you can make 'is_only_lobby_owner' true.
@export var object_player : Node
@export_group("INTERPOLATION")
@export var is_interpolated :bool = false
@export var interpolation_value : float = 0.1
@export_group("")
@export var property_list : PackedStringArray## Hangi ozellikler yollayacaksak
var property_type : Array
var DATA : Array
var path : NodePath
var timer : Timer
func init_timer():
timer = Timer.new()
timer.process_callback = Timer.TIMER_PROCESS_PHYSICS
timer.wait_time = 0.1
add_child(timer)
timer.autostart = true
timer.start()
timer.connect("timeout",_on_timer_timeout)
func _ready():
init_timer()
if !is_interpolated:
path = get_parent().get_path()
set_process(false)
else:
path = get_path()
if is_only_lobby_owner == false and object_player.name != str(NetworkManager.STEAM_ID):
timer.stop()
elif is_only_lobby_owner:
if Steam.getLobbyOwner(NetworkManager.LOBBY_ID) != NetworkManager.STEAM_ID:
timer.stop()
for property in property_list:
var v = get_parent().get(property)
property_type.append(v)
func _process(delta):
# Data[0] property Data[1] value
if (!DATA.is_empty()):
get_parent().set(DATA[0],lerp(get_parent().get(DATA[0]),DATA[1],interpolation_value))
func _on_timer_timeout():
for property in property_list.size():
if property_type[property] != get_parent().get(property_list[property]) and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.PROPERTY,"value":get_parent().get(property_list[property]),"node_path":path,"property":property_list[property],"interpolated":is_interpolated}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_RELIABLE)
property_type[property] = get_parent().get(property_list[property])
func abort():
timer.stop()
set_process(false)

View File

@ -0,0 +1,76 @@
@icon("res://Network/RadomeSteamSync/SyncNode/transformSyncIcon.png")
class_name RagDollSync extends Synchronizer
@export_group("SETTINGS","is")
## If true, onlu lobby owner will send the packet.
@export var is_only_lobby_owner : bool = false
@export_group("NODES","object")
## Select player if its not player or not inside player you can make 'is_only_lobby_owner' true.
@export var object_player : Node
@export var interpolation : float = 0.35
signal simulating(status : bool)
var bones : Array[PhysicalBone3D]
var packet_index_pos : int = 0
var last_pos : Array[Vector3]
var transform_buffer : Dictionary = {}
var last_index_buffer : int
var timer : Timer
func init_timer():
timer = Timer.new()
timer.process_callback = Timer.TIMER_PROCESS_PHYSICS
timer.wait_time = 0.1
add_child(timer)
timer.connect("timeout",_on_pos_timer_timeout)
func _ready():
init_timer()
get_all_bones()
func get_pos_bone() -> Array[Vector3]:
var pos : Array[Vector3]
for bone in bones:
pos.append(bone.global_transform.origin)
return pos
func get_all_bones():
for i in get_parent().get_children():
if is_instance_of(i,PhysicalBone3D):
bones.append(i)
func _on_pos_timer_timeout():
if get_pos_bone() != last_pos and NetworkManager.GAME_STARTED:
var pos = get_pos_bone()
var DATA : Dictionary = {"Idx":packet_index_pos + 1,"TYPE":NetworkManager.TYPES.RAGDOLL,"value":pos,"node_path":get_path()}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index_pos = packet_index_pos + 1
last_pos = pos
func _physics_process(delta):
if transform_buffer.has("Idx") and NetworkManager.GAME_STARTED:
if transform_buffer["Idx"] >= last_index_buffer :
set_pos_bone(transform_buffer["value"])
func set_pos_bone(DATA):
for i in range(bones.size()):
var lerped_value = lerp(bones[i].global_transform.origin,DATA[i],interpolation)
bones[i].global_transform.origin = lerped_value
last_index_buffer = transform_buffer["Idx"]
func abort():
set_process(false)
timer.stop()
func _on_simulating(status : bool):
if status== true:
if is_only_lobby_owner and Steam.getLobbyOwner(NetworkManager.LOBBY_ID) == NetworkManager.STEAM_ID:
timer.start()
elif !is_only_lobby_owner and str(NetworkManager.STEAM_ID) == object_player.name:
timer.start()
else:
timer.stop()

View File

@ -0,0 +1,50 @@
class_name RigidBodySync2D extends Synchronizer
@export var interpolation_value = 0.3
var packet_index: int = 0
var state_data = null
var last_index : int = 0
var last_pos : Vector3 = Vector3.ZERO
var pTimer : Timer
func init_timer():
pTimer = Timer.new()
pTimer.process_callback = Timer.TIMER_PROCESS_PHYSICS
pTimer.wait_time = 1.0 / 30.0
add_child(pTimer)
pTimer.autostart = true
pTimer.start()
pTimer.connect("timeout",_on_timeout)
func _ready():
init_timer()
if Steam.getLobbyOwner(NetworkManager.LOBBY_ID) != NetworkManager.STEAM_ID:
pTimer.stop()
func _on_timeout():
if get_parent().position != last_pos and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"Idx":packet_index,"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.RIGIDBODY_SYNC,"value":get_parent().linear_velocity,"node_path":get_path(),}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index = packet_index + 1
last_pos = get_parent().linear_velocity
func update_physics_values():
var target_linear_velocity = state_data["value"][0]
var target_angular_velocity = state_data["value"][1]
var target_position = state_data["value"][2]
var target_rotation = state_data["value"][3]
# Linear velocity için Lerp
get_parent().linear_velocity = lerp(get_parent().linear_velocity, target_linear_velocity, interpolation_value)
# Angular velocity için Lerp
get_parent().angular_velocity = lerp(get_parent().angular_velocity, target_angular_velocity, interpolation_value)
# Position için Lerp
get_parent().position = lerp(get_parent().position, target_position, interpolation_value)
# Rotation için Lerp (Quaternion için)
get_parent().rotation = lerp(get_parent().rotation , target_rotation, interpolation_value)
func _physics_process(delta: float) -> void:
if state_data != null and NetworkManager.GAME_STARTED:
if state_data["Idx"] >= last_index :
update_physics_values()
last_index = state_data["Idx"]

View File

@ -0,0 +1,54 @@
class_name RigidBodySync3D extends Synchronizer
@export var interpolation_value = 0.3
var packet_index: int = 0
var state_data = null
var last_index : int = 0
var last_pos : Vector3 = Vector3.ZERO
var pTimer : Timer
func init_timer():
pTimer = Timer.new()
pTimer.process_callback = Timer.TIMER_PROCESS_PHYSICS
pTimer.wait_time = 1.0 /30.0
add_child(pTimer)
pTimer.autostart = true
pTimer.start()
pTimer.connect("timeout",_on_timer_timeout)
func _ready():
init_timer()
if Steam.getLobbyOwner(NetworkManager.LOBBY_ID) != NetworkManager.STEAM_ID:
pTimer.stop()
func _on_timer_timeout():
if get_parent().position != last_pos and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"Idx":packet_index,"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.RIGIDBODY_SYNC,"value":[get_parent().linear_velocity,get_parent().angular_velocity,get_parent().position,get_parent().rotation],"node_path":get_path()}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index = packet_index + 1
last_pos = get_parent().position
func update_physics_values():
var target_linear_velocity = state_data["value"][0]
var target_angular_velocity = state_data["value"][1]
var target_position = state_data["value"][2]
var target_rotation = state_data["value"][3]
# Linear velocity için Lerp
get_parent().linear_velocity = lerp(get_parent().linear_velocity, target_linear_velocity, interpolation_value)
# Angular velocity için Lerp
get_parent().angular_velocity = lerp(get_parent().angular_velocity, target_angular_velocity, interpolation_value)
# Position için Lerp
get_parent().position = lerp(get_parent().position, target_position, interpolation_value)
# Rotation için Lerp (Quaternion için)
get_parent().rotation = lerp(get_parent().rotation , target_rotation, interpolation_value)
func _physics_process(delta: float) -> void:
await get_tree().create_timer(0.1).timeout
if state_data != null and NetworkManager.GAME_STARTED:
if state_data["Idx"] >= last_index :
update_physics_values()
last_index = state_data["Idx"]

View File

@ -0,0 +1 @@
class_name Synchronizer extends Node

View File

@ -0,0 +1,127 @@
@icon("res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/transformSyncIcon.png")
class_name TransformSync2D extends Synchronizer
@export_group("SETTINGS","is")
## If true, onlu lobby owner will send the packet.
@export var is_only_lobby_owner : bool = false
@export_group("NODES","object")
## Select player if its not player or not inside player you can make 'is_only_lobby_owner' true.
@export var object_player : Node
@export_group("")
@export var Position : bool = true
@export var Rotation : bool = false
@export var Scale : bool = false
var packet_index_pos : int = 0
var packet_index_rot : int = 0
var packet_index_scale : int = 0
var last_pos : Vector2 = Vector2.ZERO
var last_rot : float = 0
var last_scale : Vector2 = Vector2.ZERO
var transform_buffer : Array = [null,null,null]
var last_index_buffer : PackedInt32Array = [0,0,0]
var posTimer : Timer
var rotTimer : Timer
var sclTimer : Timer
func init_pos_timer():
posTimer = Timer.new()
posTimer.process_callback = Timer.TIMER_PROCESS_PHYSICS
posTimer.wait_time = 0.1
add_child(posTimer)
posTimer.autostart = true
posTimer.start()
posTimer.connect("timeout",_on_pos_timer_timeout)
func init_rot_timer():
rotTimer = Timer.new()
rotTimer.process_callback = Timer.TIMER_PROCESS_PHYSICS
rotTimer.wait_time = 0.1
add_child(rotTimer)
rotTimer.autostart = true
rotTimer.start()
rotTimer.connect("timeout",_on_rot_timer_timeout)
func init_scl_timer():
sclTimer = Timer.new()
sclTimer.process_callback = Timer.TIMER_PROCESS_PHYSICS
sclTimer.wait_time = 0.1
add_child(sclTimer)
sclTimer.autostart = true
sclTimer.start()
sclTimer.connect("timeout",_on_scale_timer_timeout)
func _ready():
init_pos_timer()
init_rot_timer()
init_scl_timer()
if is_only_lobby_owner == false and object_player.name != str(NetworkManager.STEAM_ID):
posTimer.stop()
rotTimer.stop()
sclTimer.stop()
elif is_only_lobby_owner:
if Steam.getLobbyOwner(NetworkManager.LOBBY_ID) != NetworkManager.STEAM_ID:
posTimer.stop()
rotTimer.stop()
sclTimer.stop()
if !Position:
posTimer.stop()
if !Rotation:
rotTimer.stop()
if !Scale:
sclTimer.stop()
func _on_pos_timer_timeout():
if get_parent().global_position != last_pos and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"Idx":packet_index_pos + 1,"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.TRANFORM_SYNC,"value":get_parent().global_position,"node_path":get_path(),"property":"global_position"}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index_pos = packet_index_pos + 1
last_pos = get_parent().global_position
func _on_rot_timer_timeout():
if get_parent().rotation != last_rot and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"Idx":packet_index_rot + 1,"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.TRANFORM_SYNC,"value":get_parent().rotation,"node_path":get_path(),"property":"rotation"}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index_rot = packet_index_rot + 1
last_rot = get_parent().rotation
func _on_scale_timer_timeout():
if get_parent().scale != last_scale and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"Idx":packet_index_scale + 1,"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.TRANFORM_SYNC,"value":get_parent().scale,"node_path":get_path(),"property":"scale"}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index_scale = packet_index_scale + 1
last_scale = get_parent().scale
func _process(delta):
if transform_buffer[0] != null and NetworkManager.GAME_STARTED:
if transform_buffer[0]["Idx"] >= last_index_buffer[0] :
var lerped_value = lerp(get_parent().get(transform_buffer[0]["property"]),transform_buffer[0]["value"],0.1)
get_parent().set(transform_buffer[0]["property"],lerped_value)
last_index_buffer[0] = transform_buffer[0]["Idx"]
# Rotation
if transform_buffer[1] != null and NetworkManager.GAME_STARTED:
if transform_buffer[1]["Idx"] >= last_index_buffer[1]:
var lerped_value = lerp(get_parent().get(transform_buffer[1]["property"]),transform_buffer[1]["value"],0.1)
get_parent().set(transform_buffer[1]["property"],lerped_value)
last_index_buffer[1] = transform_buffer[1]["Idx"]
# Scale
if transform_buffer[2] != null and NetworkManager.GAME_STARTED:
if transform_buffer[2]["Idx"] >= last_index_buffer[2]:
var lerped_value = lerp(get_parent().get(transform_buffer[2]["property"]),transform_buffer[2]["value"],0.1)
get_parent().set(transform_buffer[2]["property"],lerped_value)
last_index_buffer[2] = transform_buffer[2]["Idx"]
func abort():
posTimer.stop()
rotTimer.stop()
sclTimer.stop()
set_process(false)

View File

@ -0,0 +1,126 @@
@icon("res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/transformSyncIcon.png")
class_name TransformSync3D extends Synchronizer
@export_group("SETTINGS","is")
## If true, onlu lobby owner will send the packet.
@export var is_only_lobby_owner : bool = false
@export_group("NODES","object")
@export var object_player : Node ## Select player if its not player or not inside player you can make 'is_only_lobby_owner' true.
@export_group("")
@export var Position : bool = true
@export var Rotation : bool = false
@export var Scale : bool = false
var packet_index_pos : int = 0
var packet_index_rot : int = 0
var packet_index_scale : int = 0
var last_pos : Vector3 = Vector3.ZERO
var last_rot : Vector3 = Vector3.ZERO
var last_scale : Vector3 = Vector3.ZERO
var transform_buffer : Array = [null,null,null]
var last_index_buffer : PackedInt32Array = [0,0,0]
var posTimer : Timer
var rotTimer : Timer
var sclTimer : Timer
func init_pos_timer():
posTimer = Timer.new()
posTimer.process_callback = Timer.TIMER_PROCESS_PHYSICS
posTimer.wait_time = 0.1
add_child(posTimer)
posTimer.autostart = true
posTimer.start()
posTimer.connect("timeout",_on_pos_timer_timeout)
func init_rot_timer():
rotTimer = Timer.new()
rotTimer.process_callback = Timer.TIMER_PROCESS_PHYSICS
rotTimer.wait_time = 0.1
add_child(rotTimer)
rotTimer.autostart = true
rotTimer.start()
rotTimer.connect("timeout",_on_rot_timer_timeout)
func init_scl_timer():
sclTimer = Timer.new()
sclTimer.process_callback = Timer.TIMER_PROCESS_PHYSICS
sclTimer.wait_time = 0.1
add_child(sclTimer)
sclTimer.autostart = true
sclTimer.start()
sclTimer.connect("timeout",_on_scale_timer_timeout)
func _ready():
init_pos_timer()
init_rot_timer()
init_scl_timer()
if is_only_lobby_owner == false and object_player.name != str(NetworkManager.STEAM_ID):
posTimer.stop()
rotTimer.stop()
sclTimer.stop()
elif is_only_lobby_owner:
if Steam.getLobbyOwner(NetworkManager.LOBBY_ID) != NetworkManager.STEAM_ID:
posTimer.stop()
rotTimer.stop()
sclTimer.stop()
if !Position:
posTimer.stop()
if !Rotation:
rotTimer.stop()
if !Scale:
sclTimer.stop()
func _on_pos_timer_timeout():
if get_parent().global_position != last_pos and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"Idx":packet_index_pos + 1,"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.TRANFORM_SYNC,"value":get_parent().global_position,"node_path":get_path(),"property":"global_position"}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index_pos = packet_index_pos + 1
last_pos = get_parent().global_position
func _on_rot_timer_timeout():
if get_parent().rotation != last_rot and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"Idx":packet_index_rot + 1,"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.TRANFORM_SYNC,"value":get_parent().rotation,"node_path":get_path(),"property":"rotation"}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index_rot = packet_index_rot + 1
last_rot = get_parent().rotation
func _on_scale_timer_timeout():
if get_parent().scale != last_scale and NetworkManager.GAME_STARTED:
var DATA : Dictionary = {"Idx":packet_index_scale + 1,"player_id":NetworkManager.STEAM_ID,"TYPE":NetworkManager.TYPES.TRANFORM_SYNC,"value":get_parent().scale,"node_path":get_path(),"property":"scale"}
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_UNRELIABLE)
packet_index_scale = packet_index_scale + 1
last_scale = get_parent().scale
func _process(delta):
if transform_buffer[0] != null and NetworkManager.GAME_STARTED:
if transform_buffer[0]["Idx"] >= last_index_buffer[0] :
var lerped_value = lerp(get_parent().get(transform_buffer[0]["property"]),transform_buffer[0]["value"],0.1)
get_parent().set(transform_buffer[0]["property"],lerped_value)
last_index_buffer[0] = transform_buffer[0]["Idx"]
# Rotation
if transform_buffer[1] != null and NetworkManager.GAME_STARTED:
if transform_buffer[1]["Idx"] >= last_index_buffer[1]:
var lerped_value = lerp(get_parent().get(transform_buffer[1]["property"]),transform_buffer[1]["value"],0.1)
get_parent().set(transform_buffer[1]["property"],lerped_value)
last_index_buffer[1] = transform_buffer[1]["Idx"]
# Scale
if transform_buffer[2] != null and NetworkManager.GAME_STARTED:
if transform_buffer[2]["Idx"] >= last_index_buffer[2]:
var lerped_value = lerp(get_parent().get(transform_buffer[2]["property"]),transform_buffer[2]["value"],0.1)
get_parent().set(transform_buffer[2]["property"],lerped_value)
last_index_buffer[2] = transform_buffer[2]["Idx"]
func abort():
posTimer.stop()
rotTimer.stop()
sclTimer.stop()
set_process(false)

View File

@ -0,0 +1,118 @@
extends Synchronizer
class_name VoiceSync
var current_sample_rate: int = 48000
var local_playback : AudioStreamGeneratorPlayback = null
var local_voice_buffer: PackedByteArray = PackedByteArray()
var use_optimal_sample_rate: bool = false
var DATA : Dictionary
const REF_SAMPLE_RATE : int = 48000
@export var loopback_enabled : bool = false
@export var audio_node : AudioStreamPlayer
@export var voice_key : String = "push_to_talk"
var streamplay : AudioStreamGenerator
var playback_stream : AudioStreamGeneratorPlayback
func _ready():
change_voice_settings(true)
DATA = {
"steam_id": NetworkManager.STEAM_ID,
"TYPE": NetworkManager.TYPES.VOICE,
"node_path": get_path(),
"voice_data": {}
}
if NetworkManager.LOBBY_MEMBERS.size() > 1:
loopback_enabled = false
if audio_node != null:
audio_node.stream.mix_rate = current_sample_rate
audio_node.play()
local_playback = audio_node.get_stream_playback()
func change_voice_settings(push_to_talk :bool):
if push_to_talk:
Steam.setInGameVoiceSpeaking(NetworkManager.STEAM_ID, false)
Steam.stopVoiceRecording()
set_process_input(true)
else:
Steam.setInGameVoiceSpeaking(NetworkManager.STEAM_ID, true)
Steam.startVoiceRecording()
set_process_input(false)
func record(voice : bool):
Steam.setInGameVoiceSpeaking(NetworkManager.STEAM_ID, voice)
if voice:
Steam.startVoiceRecording()
else:
Steam.stopVoiceRecording()
func check_for_voice() -> void:
var available_voice: Dictionary = Steam.getAvailableVoice()
if available_voice['result'] == Steam.VOICE_RESULT_OK and available_voice['buffer'] > 0:
var voice_data: Dictionary = Steam.getVoice()
if voice_data['result'] == Steam.VOICE_RESULT_OK:
DATA["voice_data"] = voice_data
P2P._send_P2P_Packet(0,0, DATA,Steam.P2P_SEND_RELIABLE)
if loopback_enabled:
process_voice_data(voice_data)
func _process(_delta: float) -> void:
if get_parent().name == str(NetworkManager.STEAM_ID):
check_for_voice()
if Input.is_action_pressed(voice_key):
record(true)
if Input.is_action_just_released(voice_key):
record(false)
func get_sample_rate() -> void:
var optimal_sample_rate: int = Steam.getVoiceOptimalSampleRate()
# SpaceWar uses 11000 for sample rate?!
# If are using Steam's "optimal" rate, set it; otherwise we default to 48000
if use_optimal_sample_rate:
current_sample_rate = optimal_sample_rate
else:
current_sample_rate = 48000
func process_voice_data(voice_data: Dictionary) -> void:
get_sample_rate()
var pitch : float = float(current_sample_rate)/REF_SAMPLE_RATE
audio_node.set_pitch_scale(pitch)
var decompressed_voice: Dictionary = Steam.decompressVoice(
voice_data['buffer'],
current_sample_rate)
if (
not decompressed_voice['result'] == Steam.VOICE_RESULT_OK
):
return
if local_playback != null:
if local_playback.get_frames_available() <= 0:
return
local_voice_buffer = decompressed_voice['uncompressed']
for i: int in range(0, mini(local_playback.get_frames_available() * 2, local_voice_buffer.size()), 2):
var raw_value = local_voice_buffer.decode_s16(i)
# Convert the 16-bit integer to a float on from -1 to 1
var amplitude: float = float(raw_value) / 32768.0
local_playback.push_frame(Vector2(amplitude, amplitude))
#local_voice_buffer.remove_at(0)
#local_voice_buffer.remove_at(0)

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d0hi31a5q15pi"
path="res://.godot/imported/funcSyncIcon.png-ee0ebc7f5fc0f5cd37293630b8d418fc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/funcSyncIcon.png"
dest_files=["res://.godot/imported/funcSyncIcon.png-ee0ebc7f5fc0f5cd37293630b8d418fc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dvqalcb5gle3l"
path="res://.godot/imported/propertySyncIcon.png-7230c83490a15b0c58480c84d77bc8ba.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/propertySyncIcon.png"
dest_files=["res://.godot/imported/propertySyncIcon.png-7230c83490a15b0c58480c84d77bc8ba.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://choidi1eihrtd"
path="res://.godot/imported/transformSyncIcon.png-70197aa309a4df07b6cd61ec5f7effd6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/transformSyncIcon.png"
dest_files=["res://.godot/imported/transformSyncIcon.png-70197aa309a4df07b6cd61ec5f7effd6.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -0,0 +1,20 @@
@tool
extends EditorPlugin
func _enter_tree() -> void:
add_autoload_singleton("NetworkManager","res://addons/godot_steam_sync/Network/RadomeSteamSync/NetworkManager.gd")
add_autoload_singleton("P2P","res://addons/godot_steam_sync/Network/RadomeSteamSync/P2P.gd")
# add_autoload_singleton("Command","res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/CommandSync.gd")
#add_autoload_singleton("SceneManager","res://addons/godot_steam_sync/SceneChanger/SceneManager.gd")
func _exit_tree() -> void:
remove_autoload_singleton("NetworkManager")
remove_autoload_singleton("P2P")
# remove_autoload_singleton("Command")
#remove_autoload_singleton("SceneManager")

View File

@ -0,0 +1,7 @@
[plugin]
name="GodotSteamSync"
description="Advanced GodotSteam Synchornizer and Lobby System."
author="Radome"
version="0.1"
script="godot_steam_sync.gd"

View File

@ -0,0 +1,176 @@
[gd_scene load_steps=13 format=3 uid="uid://dhi5myb4u2mij"]
[ext_resource type="Script" path="res://addons/godot_steam_sync/Network/RadomeSteamSync/NetworkPlayerSpawner.gd" id="1_a26tm"]
[ext_resource type="Script" path="res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/RigidBodySync3D.gd" id="2_io2do"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_ilft6"]
sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
[sub_resource type="Sky" id="Sky_ryjhm"]
sky_material = SubResource("ProceduralSkyMaterial_ilft6")
[sub_resource type="Environment" id="Environment_lmp12"]
background_mode = 2
sky = SubResource("Sky_ryjhm")
tonemap_mode = 2
glow_enabled = true
[sub_resource type="BoxMesh" id="BoxMesh_li3io"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_r06tq"]
albedo_color = Color(0.321728, 0.321728, 0.321728, 1)
[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_io4rt"]
data = PackedVector3Array(-0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5)
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_iltko"]
bounce = 1.0
[sub_resource type="SphereMesh" id="SphereMesh_jxxnq"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cdfuw"]
albedo_color = Color(0.415686, 1, 1, 1)
[sub_resource type="SphereShape3D" id="SphereShape3D_ktxia"]
[node name="Test" type="Node"]
[node name="NetworkPlayerSpawner" type="Node" parent="." node_paths=PackedStringArray("spawn_pos", "players_parent_node")]
script = ExtResource("1_a26tm")
spawn_pos = NodePath("../SpawnPos")
players_parent_node = NodePath("../Players")
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_lmp12")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
shadow_enabled = true
[node name="Players" type="Node3D" parent="."]
[node name="SpawnPos" type="Marker3D" parent="."]
[node name="Level" type="Node3D" parent="."]
[node name="MeshInstance3D" type="MeshInstance3D" parent="Level"]
transform = Transform3D(49.9588, 0, 0, 0, 1, 0, 0, 0, 61.4249, 0, -3.98574, 0)
mesh = SubResource("BoxMesh_li3io")
skeleton = NodePath("../..")
surface_material_override/0 = SubResource("StandardMaterial3D_r06tq")
[node name="StaticBody3D" type="StaticBody3D" parent="Level/MeshInstance3D"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/MeshInstance3D/StaticBody3D"]
shape = SubResource("ConcavePolygonShape3D_io4rt")
[node name="MeshInstance3D6" type="MeshInstance3D" parent="Level"]
transform = Transform3D(-0.506593, 0.999949, 0, -49.9562, -0.0101402, 0, 0, 0, 61.4249, -18.7014, -3.98574, 0)
mesh = SubResource("BoxMesh_li3io")
skeleton = NodePath("../..")
[node name="StaticBody3D" type="StaticBody3D" parent="Level/MeshInstance3D6"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/MeshInstance3D6/StaticBody3D"]
shape = SubResource("ConcavePolygonShape3D_io4rt")
[node name="MeshInstance3D8" type="MeshInstance3D" parent="Level"]
transform = Transform3D(-0.0344473, 0.0679944, 61.2827, -49.9562, -0.0101402, 0, 0.505421, -0.997635, 4.17676, -0.748235, -3.98574, -26.874)
mesh = SubResource("BoxMesh_li3io")
skeleton = NodePath("../..")
[node name="StaticBody3D" type="StaticBody3D" parent="Level/MeshInstance3D8"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/MeshInstance3D8/StaticBody3D"]
shape = SubResource("ConcavePolygonShape3D_io4rt")
[node name="MeshInstance3D9" type="MeshInstance3D" parent="Level"]
transform = Transform3D(-0.0344473, 0.0679944, 61.2827, -49.9562, -0.0101402, 0, 0.505421, -0.997635, 4.17676, -6.63549, -3.98574, 19.7729)
mesh = SubResource("BoxMesh_li3io")
skeleton = NodePath("../..")
[node name="StaticBody3D" type="StaticBody3D" parent="Level/MeshInstance3D9"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/MeshInstance3D9/StaticBody3D"]
shape = SubResource("ConcavePolygonShape3D_io4rt")
[node name="MeshInstance3D7" type="MeshInstance3D" parent="Level"]
transform = Transform3D(-0.506593, 0.999949, 0, -49.9562, -0.0101402, 0, 0, 0, 61.4249, 12.2278, -3.98574, 0)
mesh = SubResource("BoxMesh_li3io")
skeleton = NodePath("../..")
[node name="StaticBody3D" type="StaticBody3D" parent="Level/MeshInstance3D7"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/MeshInstance3D7/StaticBody3D"]
shape = SubResource("ConcavePolygonShape3D_io4rt")
[node name="Physics" type="Node3D" parent="."]
[node name="RigidBody3D" type="RigidBody3D" parent="Physics"]
physics_interpolation_mode = 1
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.20984, 0)
collision_layer = 4
collision_mask = 7
physics_material_override = SubResource("PhysicsMaterial_iltko")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Physics/RigidBody3D"]
mesh = SubResource("SphereMesh_jxxnq")
surface_material_override/0 = SubResource("StandardMaterial3D_cdfuw")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Physics/RigidBody3D"]
shape = SubResource("SphereShape3D_ktxia")
[node name="RigidBodySync3D" type="Node" parent="Physics/RigidBody3D"]
script = ExtResource("2_io2do")
[node name="RigidBody3D2" type="RigidBody3D" parent="Physics"]
physics_interpolation_mode = 1
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.5851, 1.20984, -2.08695)
collision_layer = 4
collision_mask = 7
physics_material_override = SubResource("PhysicsMaterial_iltko")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Physics/RigidBody3D2"]
mesh = SubResource("SphereMesh_jxxnq")
surface_material_override/0 = SubResource("StandardMaterial3D_cdfuw")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Physics/RigidBody3D2"]
shape = SubResource("SphereShape3D_ktxia")
[node name="RigidBodySync3D" type="Node" parent="Physics/RigidBody3D2"]
script = ExtResource("2_io2do")
[node name="RigidBody3D3" type="RigidBody3D" parent="Physics"]
physics_interpolation_mode = 1
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.84777, 1.20984, 4.17028)
collision_layer = 4
collision_mask = 7
physics_material_override = SubResource("PhysicsMaterial_iltko")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Physics/RigidBody3D3"]
mesh = SubResource("SphereMesh_jxxnq")
surface_material_override/0 = SubResource("StandardMaterial3D_cdfuw")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Physics/RigidBody3D3"]
shape = SubResource("SphereShape3D_ktxia")
[node name="RigidBodySync3D" type="Node" parent="Physics/RigidBody3D3"]
script = ExtResource("2_io2do")
[node name="RigidBody3D4" type="RigidBody3D" parent="Physics"]
physics_interpolation_mode = 1
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.84777, 1.8173, -2.41229)
collision_layer = 4
collision_mask = 7
physics_material_override = SubResource("PhysicsMaterial_iltko")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Physics/RigidBody3D4"]
mesh = SubResource("SphereMesh_jxxnq")
surface_material_override/0 = SubResource("StandardMaterial3D_cdfuw")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Physics/RigidBody3D4"]
shape = SubResource("SphereShape3D_ktxia")
[node name="RigidBodySync3D" type="Node" parent="Physics/RigidBody3D4"]
script = ExtResource("2_io2do")

View File

@ -0,0 +1,49 @@
@tool
extends Node
# This does not effect runtime yet but will in the future.
@export_category("Controller Editor Module")
@export_range(-360.0, 360.0, 0.01, "or_greater", "or_less") var head_y_rotation : float = 0.0:
set(new_rotation):
if HEAD:
head_y_rotation = new_rotation
HEAD.rotation.y = deg_to_rad(head_y_rotation)
update_configuration_warnings()
@export_range(-90.0, 90.0, 0.01, "or_greater", "or_less") var head_x_rotation : float = 0.0:
set(new_rotation):
if HEAD:
head_x_rotation = new_rotation
HEAD.rotation.x = deg_to_rad(head_x_rotation)
update_configuration_warnings()
@export_group("Nodes")
@export var CHARACTER : CharacterBody3D
@export var head_path : String = "Head" # Relative to the parent node
#@export var CAMERA : Camera3D
#@export var HEADBOB_ANIMATION : AnimationPlayer
#@export var JUMP_ANIMATION : AnimationPlayer
#@export var CROUCH_ANIMATION : AnimationPlayer
#@export var COLLISION_MESH : CollisionShape3D
@onready var HEAD = get_node("../" + head_path)
func _ready():
if !Engine.is_editor_hint():
print("not editor")
HEAD.rotation.y = deg_to_rad(head_y_rotation)
HEAD.rotation.x = deg_to_rad(head_x_rotation)
func _get_configuration_warnings():
var warnings = []
if head_y_rotation > 360:
warnings.append("The head rotation is greater than 360")
if head_y_rotation < -360:
warnings.append("The head rotation is less than -360")
# Returning an empty array gives no warnings
return warnings

View File

@ -0,0 +1,854 @@
[gd_scene load_steps=22 format=3 uid="uid://cc1m2a1obsyn4"]
[ext_resource type="Script" path="res://godotsteam_sync_example/fpc/EditorModule.gd" id="3_v3ckk"]
[ext_resource type="Script" path="res://godotsteam_sync_example/fpc/debug.gd" id="3_x1wcc"]
[ext_resource type="Script" path="res://addons/godot_steam_sync/Network/RadomeSteamSync/SyncNode/TransformSync3D.gd" id="4_ch187"]
[sub_resource type="GDScript" id="GDScript_r0lhh"]
script/source = "
# COPYRIGHT Colormatic Studios
# MIT licence
# Quality Godot First Person Controller v2
extends CharacterBody3D
var IS_OWNER : bool = false
func make_owner():
IS_OWNER = true
$Head/Camera.current = true
## The settings for the character's movement and feel.
@export_category(\"Character\")
## The speed that the character moves at without crouching or sprinting.
@export var base_speed : float = 3.0
## The speed that the character moves at when sprinting.
@export var sprint_speed : float = 6.0
## The speed that the character moves at when crouching.
@export var crouch_speed : float = 1.0
## How fast the character speeds up and slows down when Motion Smoothing is on.
@export var acceleration : float = 10.0
## How high the player jumps.
@export var jump_velocity : float = 4.5
## How far the player turns when the mouse is moved.
@export var mouse_sensitivity : float = 0.1
## Invert the Y input for mouse and joystick
@export var invert_mouse_y : bool = false # Possibly add an invert mouse X in the future
## Wether the player can use movement inputs. Does not stop outside forces or jumping. See Jumping Enabled.
@export var immobile : bool = false
## The reticle file to import at runtime. By default are in res://addons/fpc/reticles/. Set to an empty string to remove.
@export_file var default_reticle
@export_group(\"Nodes\")
## The node that holds the camera. This is rotated instead of the camera for mouse input.
@export var HEAD : Node3D
@export var CAMERA : Camera3D
@export var HEADBOB_ANIMATION : AnimationPlayer
@export var JUMP_ANIMATION : AnimationPlayer
@export var CROUCH_ANIMATION : AnimationPlayer
@export var COLLISION_MESH : CollisionShape3D
@export_group(\"Controls\")
# We are using UI controls because they are built into Godot Engine so they can be used right away
@export var JUMP : String = \"ui_accept\"
@export var LEFT : String = \"ui_left\"
@export var RIGHT : String = \"ui_right\"
@export var FORWARD : String = \"ui_up\"
@export var BACKWARD : String = \"ui_down\"
## By default this does not pause the game, but that can be changed in _process.
@export var PAUSE : String = \"ui_cancel\"
@export var CROUCH : String = \"crouch\"
@export var SPRINT : String = \"sprint\"
# Uncomment if you want controller support
#@export var controller_sensitivity : float = 0.035
#@export var LOOK_LEFT : String = \"look_left\"
#@export var LOOK_RIGHT : String = \"look_right\"
#@export var LOOK_UP : String = \"look_up\"
#@export var LOOK_DOWN : String = \"look_down\"
@export_group(\"Feature Settings\")
## Enable or disable jumping. Useful for restrictive storytelling environments.
@export var jumping_enabled : bool = true
## Wether the player can move in the air or not.
@export var in_air_momentum : bool = true
## Smooths the feel of walking.
@export var motion_smoothing : bool = true
@export var sprint_enabled : bool = true
@export var crouch_enabled : bool = true
@export_enum(\"Hold to Crouch\", \"Toggle Crouch\") var crouch_mode : int = 0
@export_enum(\"Hold to Sprint\", \"Toggle Sprint\") var sprint_mode : int = 0
## Wether sprinting should effect FOV.
@export var dynamic_fov : bool = true
## If the player holds down the jump button, should the player keep hopping.
@export var continuous_jumping : bool = true
## Enables the view bobbing animation.
@export var view_bobbing : bool = true
## Enables an immersive animation when the player jumps and hits the ground.
@export var jump_animation : bool = true
## This determines wether the player can use the pause button, not wether the game will actually pause.
@export var pausing_enabled : bool = true
## Use with caution.
@export var gravity_enabled : bool = true
# Member variables
var speed : float = base_speed
var current_speed : float = 0.0
# States: normal, crouching, sprinting
var state : String = \"normal\"
var low_ceiling : bool = false # This is for when the cieling is too low and the player needs to crouch.
var was_on_floor : bool = true # Was the player on the floor last frame (for landing animation)
# The reticle should always have a Control node as the root
var RETICLE : Control
# Get the gravity from the project settings to be synced with RigidBody nodes
var gravity : float = ProjectSettings.get_setting(\"physics/3d/default_gravity\") # Don't set this as a const, see the gravity section in _physics_process
# Stores mouse input for rotating the camera in the phyhsics process
var mouseInput : Vector2 = Vector2(0,0)
func _ready():
if IS_OWNER:
#It is safe to comment this line if your game doesn't start with the mouse captured
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
# If the controller is rotated in a certain direction for game design purposes, redirect this rotation into the head.
HEAD.rotation.y = rotation.y
rotation.y = 0
if default_reticle:
change_reticle(default_reticle)
# Reset the camera position
# If you want to change the default head height, change these animations.
HEADBOB_ANIMATION.play(\"RESET\")
JUMP_ANIMATION.play(\"RESET\")
CROUCH_ANIMATION.play(\"RESET\")
check_controls()
func check_controls(): # If you add a control, you might want to add a check for it here.
# The actions are being disabled so the engine doesn't halt the entire project in debug mode
if IS_OWNER:
if !InputMap.has_action(JUMP):
push_error(\"No control mapped for jumping. Please add an input map control. Disabling jump.\")
jumping_enabled = false
if !InputMap.has_action(LEFT):
push_error(\"No control mapped for move left. Please add an input map control. Disabling movement.\")
immobile = true
if !InputMap.has_action(RIGHT):
push_error(\"No control mapped for move right. Please add an input map control. Disabling movement.\")
immobile = true
if !InputMap.has_action(FORWARD):
push_error(\"No control mapped for move forward. Please add an input map control. Disabling movement.\")
immobile = true
if !InputMap.has_action(BACKWARD):
push_error(\"No control mapped for move backward. Please add an input map control. Disabling movement.\")
immobile = true
if !InputMap.has_action(PAUSE):
push_error(\"No control mapped for pause. Please add an input map control. Disabling pausing.\")
pausing_enabled = false
if !InputMap.has_action(CROUCH):
push_error(\"No control mapped for crouch. Please add an input map control. Disabling crouching.\")
crouch_enabled = false
if !InputMap.has_action(SPRINT):
push_error(\"No control mapped for sprint. Please add an input map control. Disabling sprinting.\")
sprint_enabled = false
func change_reticle(reticle): # Yup, this function is kinda strange
if IS_OWNER:
if RETICLE:
RETICLE.queue_free()
RETICLE = load(reticle).instantiate()
RETICLE.character = self
$UserInterface.add_child(RETICLE)
func _physics_process(delta):
if IS_OWNER:
# Big thanks to github.com/LorenzoAncora for the concept of the improved debug values
current_speed = Vector3.ZERO.distance_to(get_real_velocity())
$UserInterface/DebugPanel.add_property(\"Speed\", snappedf(current_speed, 0.001), 1)
$UserInterface/DebugPanel.add_property(\"Target speed\", speed, 2)
var cv : Vector3 = get_real_velocity()
var vd : Array[float] = [
snappedf(cv.x, 0.001),
snappedf(cv.y, 0.001),
snappedf(cv.z, 0.001)
]
var readable_velocity : String = \"X: \" + str(vd[0]) + \" Y: \" + str(vd[1]) + \" Z: \" + str(vd[2])
$UserInterface/DebugPanel.add_property(\"Velocity\", readable_velocity, 3)
# Gravity
#gravity = ProjectSettings.get_setting(\"physics/3d/default_gravity\") # If the gravity changes during your game, uncomment this code
if not is_on_floor() and gravity and gravity_enabled:
velocity.y -= gravity * delta
handle_jumping()
var input_dir = Vector2.ZERO
if !immobile: # Immobility works by interrupting user input, so other forces can still be applied to the player
input_dir = Input.get_vector(LEFT, RIGHT, FORWARD, BACKWARD)
handle_movement(delta, input_dir)
handle_head_rotation()
# The player is not able to stand up if the ceiling is too low
low_ceiling = $CrouchCeilingDetection.is_colliding()
handle_state(input_dir)
if dynamic_fov: # This may be changed to an AnimationPlayer
update_camera_fov()
if view_bobbing:
headbob_animation(input_dir)
if jump_animation:
if !was_on_floor and is_on_floor(): # The player just landed
match randi() % 2: #TODO: Change this to detecting velocity direction
0:
JUMP_ANIMATION.play(\"land_left\", 0.25)
1:
JUMP_ANIMATION.play(\"land_right\", 0.25)
was_on_floor = is_on_floor() # This must always be at the end of physics_process
func handle_jumping():
if IS_OWNER:
if jumping_enabled:
if continuous_jumping: # Hold down the jump button
if Input.is_action_pressed(JUMP) and is_on_floor() and !low_ceiling:
if jump_animation:
JUMP_ANIMATION.play(\"jump\", 0.25)
velocity.y += jump_velocity # Adding instead of setting so jumping on slopes works properly
else:
if Input.is_action_just_pressed(JUMP) and is_on_floor() and !low_ceiling:
if jump_animation:
JUMP_ANIMATION.play(\"jump\", 0.25)
velocity.y += jump_velocity
func handle_movement(delta, input_dir):
if IS_OWNER:
var direction = input_dir.rotated(-HEAD.rotation.y)
direction = Vector3(direction.x, 0, direction.y)
move_and_slide()
if in_air_momentum:
if is_on_floor():
if motion_smoothing:
velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
else:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
else:
if motion_smoothing:
velocity.x = lerp(velocity.x, direction.x * speed, acceleration * delta)
velocity.z = lerp(velocity.z, direction.z * speed, acceleration * delta)
else:
velocity.x = direction.x * speed
velocity.z = direction.z * speed
func handle_head_rotation():
if IS_OWNER:
HEAD.rotation_degrees.y -= mouseInput.x * mouse_sensitivity
if invert_mouse_y:
HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity * -1.0
else:
HEAD.rotation_degrees.x -= mouseInput.y * mouse_sensitivity
# Uncomment for controller support
#var controller_view_rotation = Input.get_vector(LOOK_DOWN, LOOK_UP, LOOK_RIGHT, LOOK_LEFT) * controller_sensitivity # These are inverted because of the nature of 3D rotation.
#HEAD.rotation.x += controller_view_rotation.x
#if invert_mouse_y:
#HEAD.rotation.y += controller_view_rotation.y * -1.0
#else:
#HEAD.rotation.y += controller_view_rotation.y
mouseInput = Vector2(0,0)
HEAD.rotation.x = clamp(HEAD.rotation.x, deg_to_rad(-90), deg_to_rad(90))
func handle_state(moving):
if IS_OWNER:
if sprint_enabled:
if sprint_mode == 0:
if Input.is_action_pressed(SPRINT) and state != \"crouching\":
if moving:
if state != \"sprinting\":
enter_sprint_state()
else:
if state == \"sprinting\":
enter_normal_state()
elif state == \"sprinting\":
enter_normal_state()
elif sprint_mode == 1:
if moving:
# If the player is holding sprint before moving, handle that cenerio
if Input.is_action_pressed(SPRINT) and state == \"normal\":
enter_sprint_state()
if Input.is_action_just_pressed(SPRINT):
match state:
\"normal\":
enter_sprint_state()
\"sprinting\":
enter_normal_state()
elif state == \"sprinting\":
enter_normal_state()
if crouch_enabled:
if crouch_mode == 0:
if Input.is_action_pressed(CROUCH) and state != \"sprinting\":
if state != \"crouching\":
enter_crouch_state()
elif state == \"crouching\" and !$CrouchCeilingDetection.is_colliding():
enter_normal_state()
elif crouch_mode == 1:
if Input.is_action_just_pressed(CROUCH):
match state:
\"normal\":
enter_crouch_state()
\"crouching\":
if !$CrouchCeilingDetection.is_colliding():
enter_normal_state()
# Any enter state function should only be called once when you want to enter that state, not every frame.
func enter_normal_state():
if IS_OWNER:
#print(\"entering normal state\")
var prev_state = state
if prev_state == \"crouching\":
CROUCH_ANIMATION.play_backwards(\"crouch\")
state = \"normal\"
speed = base_speed
func enter_crouch_state():
if IS_OWNER:
#print(\"entering crouch state\")
var prev_state = state
state = \"crouching\"
speed = crouch_speed
CROUCH_ANIMATION.play(\"crouch\")
func enter_sprint_state():
if IS_OWNER:
#print(\"entering sprint state\")
var prev_state = state
if prev_state == \"crouching\":
CROUCH_ANIMATION.play_backwards(\"crouch\")
state = \"sprinting\"
speed = sprint_speed
func update_camera_fov():
if IS_OWNER:
if state == \"sprinting\":
CAMERA.fov = lerp(CAMERA.fov, 85.0, 0.3)
else:
CAMERA.fov = lerp(CAMERA.fov, 75.0, 0.3)
func headbob_animation(moving):
if IS_OWNER:
if moving and is_on_floor():
var use_headbob_animation : String
match state:
\"normal\",\"crouching\":
use_headbob_animation = \"walk\"
\"sprinting\":
use_headbob_animation = \"sprint\"
var was_playing : bool = false
if HEADBOB_ANIMATION.current_animation == use_headbob_animation:
was_playing = true
HEADBOB_ANIMATION.play(use_headbob_animation, 0.25)
HEADBOB_ANIMATION.speed_scale = (current_speed / base_speed) * 1.75
if !was_playing:
HEADBOB_ANIMATION.seek(float(randi() % 2)) # Randomize the initial headbob direction
# Let me explain that piece of code because it looks like it does the opposite of what it actually does.
# The headbob animation has two starting positions. One is at 0 and the other is at 1.
# randi() % 2 returns either 0 or 1, and so the animation randomly starts at one of the starting positions.
# This code is extremely performant but it makes no sense.
else:
if HEADBOB_ANIMATION.current_animation == \"sprint\" or HEADBOB_ANIMATION.current_animation == \"walk\":
HEADBOB_ANIMATION.speed_scale = 1
HEADBOB_ANIMATION.play(\"RESET\", 1)
func _process(delta):
if IS_OWNER:
$UserInterface/DebugPanel.add_property(\"FPS\", Performance.get_monitor(Performance.TIME_FPS), 0)
var status : String = state
if !is_on_floor():
status += \" in the air\"
$UserInterface/DebugPanel.add_property(\"State\", status, 4)
if pausing_enabled:
if Input.is_action_just_pressed(PAUSE):
# You may want another node to handle pausing, because this player may get paused too.
match Input.mouse_mode:
Input.MOUSE_MODE_CAPTURED:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
#get_tree().paused = false
Input.MOUSE_MODE_VISIBLE:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
#get_tree().paused = false
func _unhandled_input(event : InputEvent):
if IS_OWNER:
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
mouseInput.x += event.relative.x
mouseInput.y += event.relative.y
# Toggle debug menu
elif event is InputEventKey:
if event.is_released():
# Where we're going, we don't need InputMap
if event.keycode == 4194338: # F7
$UserInterface/DebugPanel.visible = !$UserInterface/DebugPanel.visible
"
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kp17n"]
albedo_color = Color(0.909804, 0.596078, 0, 1)
clearcoat_enabled = true
clearcoat_roughness = 0.2
[sub_resource type="CapsuleMesh" id="CapsuleMesh_jw1de"]
material = SubResource("StandardMaterial3D_kp17n")
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uy03j"]
[sub_resource type="Animation" id="Animation_j8cx7"]
resource_name = "RESET"
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Mesh:scale")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(1, 1, 1)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Collision:scale")
tracks/1/interp = 2
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(1, 1, 1)]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Mesh:position")
tracks/2/interp = 2
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 1, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Collision:position")
tracks/3/interp = 2
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 1, 0)]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("Head:position")
tracks/4/interp = 2
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 1.5, 0)]
}
[sub_resource type="Animation" id="Animation_5ec5e"]
resource_name = "crouch"
length = 0.2
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Mesh:scale")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.2),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(1, 1, 1), Vector3(1, 0.75, 1)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Collision:scale")
tracks/1/interp = 2
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.2),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(1, 1, 1), Vector3(1, 0.75, 1)]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Mesh:position")
tracks/2/interp = 2
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.2),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
}
tracks/3/type = "value"
tracks/3/imported = false
tracks/3/enabled = true
tracks/3/path = NodePath("Collision:position")
tracks/3/interp = 2
tracks/3/loop_wrap = true
tracks/3/keys = {
"times": PackedFloat32Array(0, 0.2),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(0, 1, 0), Vector3(0, 0.75, 0)]
}
tracks/4/type = "value"
tracks/4/imported = false
tracks/4/enabled = true
tracks/4/path = NodePath("Head:position")
tracks/4/interp = 2
tracks/4/loop_wrap = true
tracks/4/keys = {
"times": PackedFloat32Array(0, 0.2),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(0, 1.5, 0), Vector3(0, 1.12508, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_5e5t5"]
_data = {
"RESET": SubResource("Animation_j8cx7"),
"crouch": SubResource("Animation_5ec5e")
}
[sub_resource type="Animation" id="Animation_gh776"]
resource_name = "RESET"
length = 0.001
loop_mode = 1
tracks/0/type = "bezier"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera:position:x")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"handle_modes": PackedInt32Array(0),
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
"times": PackedFloat32Array(0)
}
tracks/1/type = "bezier"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera:position:y")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"handle_modes": PackedInt32Array(0),
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
"times": PackedFloat32Array(0)
}
[sub_resource type="Animation" id="Animation_8ku67"]
resource_name = "sprint"
length = 2.0
loop_mode = 1
tracks/0/type = "bezier"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera:position:x")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
"points": PackedFloat32Array(0.06, -0.25, 0, 0.25, -0.01, 0, 0, 0, 0, 0, -0.06, -0.25, 0.01, 0.25, 0.01, 0, 0, 0, 0, 0, 0.06, -0.25, -0.01, 0.25, 0),
"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
}
tracks/1/type = "bezier"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera:position:y")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
"points": PackedFloat32Array(0.05, -0.25, 0, 0.2, -0.01, 0, -0.2, 0.000186046, 0.2, 0.000186046, 0.05, -0.2, -0.01, 0.2, -0.01, 0, -0.2, 0, 0.2, 0, 0.05, -0.2, -0.01, 0.25, 0),
"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
}
[sub_resource type="Animation" id="Animation_lrqmv"]
resource_name = "walk"
length = 2.0
loop_mode = 1
tracks/0/type = "bezier"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera:position:x")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"handle_modes": PackedInt32Array(0, 1, 0, 1, 0),
"points": PackedFloat32Array(0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, -0.04, -0.25, 0, 0.25, 0, 0, 0, 0, 0, 0, 0.04, -0.25, 0, 0.25, 0),
"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
}
tracks/1/type = "bezier"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera:position:y")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
"points": PackedFloat32Array(-0.05, -0.25, 0, 0.2, 0.005, 0, -0.2, 0.000186046, 0.2, 0.000186046, -0.05, -0.2, 0.005, 0.2, 0.005, 0, -0.2, 0, 0.2, 0, -0.05, -0.2, 0.005, 0.25, 0),
"times": PackedFloat32Array(0, 0.5, 1, 1.5, 2)
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_o0unb"]
_data = {
"RESET": SubResource("Animation_gh776"),
"sprint": SubResource("Animation_8ku67"),
"walk": SubResource("Animation_lrqmv")
}
[sub_resource type="Animation" id="Animation_fvvjq"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera:rotation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera:position")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_s07ye"]
resource_name = "jump"
length = 3.0
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera:rotation")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.6, 3),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0.0349066, 0, 0), Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_l1rph"]
resource_name = "land_left"
length = 1.5
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera:rotation")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.5, 1.5),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, 0.0174533), Vector3(0, 0, 0)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera:position")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.5, 1.5),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
}
[sub_resource type="Animation" id="Animation_vsknp"]
resource_name = "land_right"
length = 1.5
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Camera:rotation")
tracks/0/interp = 2
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.5, 1.5),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(-0.0349066, 0, -0.0174533), Vector3(0, 0, 0)]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Camera:position")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.5, 1.5),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 0,
"values": [Vector3(0, 0, 0), Vector3(0, -0.1, 0), Vector3(0, 0, 0)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_qeg5r"]
_data = {
"RESET": SubResource("Animation_fvvjq"),
"jump": SubResource("Animation_s07ye"),
"land_left": SubResource("Animation_l1rph"),
"land_right": SubResource("Animation_vsknp")
}
[sub_resource type="Theme" id="Theme_wdf0f"]
MarginContainer/constants/margin_bottom = 10
MarginContainer/constants/margin_left = 10
MarginContainer/constants/margin_right = 10
MarginContainer/constants/margin_top = 10
[sub_resource type="SphereShape3D" id="SphereShape3D_k4wwl"]
[node name="Character" type="CharacterBody3D" node_paths=PackedStringArray("HEAD", "CAMERA", "HEADBOB_ANIMATION", "JUMP_ANIMATION", "CROUCH_ANIMATION", "COLLISION_MESH")]
collision_layer = 2
collision_mask = 3
script = SubResource("GDScript_r0lhh")
default_reticle = "res://godotsteam_sync_example/fpc/reticles/reticle_1.tscn"
HEAD = NodePath("Head")
CAMERA = NodePath("Head/Camera")
HEADBOB_ANIMATION = NodePath("Head/HeadbobAnimation")
JUMP_ANIMATION = NodePath("Head/JumpAnimation")
CROUCH_ANIMATION = NodePath("CrouchAnimation")
COLLISION_MESH = NodePath("Collision")
JUMP = "jump"
LEFT = "left"
RIGHT = "right"
FORWARD = "forward"
BACKWARD = "backward"
PAUSE = "pause"
[node name="Mesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
mesh = SubResource("CapsuleMesh_jw1de")
[node name="Collision" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("CapsuleShape3D_uy03j")
[node name="CrouchAnimation" type="AnimationPlayer" parent="."]
libraries = {
"": SubResource("AnimationLibrary_5e5t5")
}
[node name="Head" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.5, 0)
[node name="Camera" type="Camera3D" parent="Head"]
[node name="HeadbobAnimation" type="AnimationPlayer" parent="Head"]
libraries = {
"": SubResource("AnimationLibrary_o0unb")
}
blend_times = [&"RESET", &"RESET", 0.5, &"RESET", &"walk", 0.5, &"walk", &"RESET", 0.5]
[node name="JumpAnimation" type="AnimationPlayer" parent="Head"]
libraries = {
"": SubResource("AnimationLibrary_qeg5r")
}
speed_scale = 4.0
[node name="UserInterface" type="Control" parent="."]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 1
[node name="DebugPanel" type="PanelContainer" parent="UserInterface"]
visible = false
layout_mode = 0
offset_left = 10.0
offset_top = 10.0
offset_right = 453.0
offset_bottom = 50.0
theme = SubResource("Theme_wdf0f")
script = ExtResource("3_x1wcc")
[node name="MarginContainer" type="MarginContainer" parent="UserInterface/DebugPanel"]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="UserInterface/DebugPanel/MarginContainer"]
layout_mode = 2
[node name="CrouchCeilingDetection" type="ShapeCast3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("SphereShape3D_k4wwl")
target_position = Vector3(0, 0.5, 0)
[node name="EditorModule" type="Node" parent="."]
script = ExtResource("3_v3ckk")
[node name="TransformSync3D" type="Node" parent="." node_paths=PackedStringArray("object_player")]
script = ExtResource("4_ch187")
object_player = NodePath("..")

View File

@ -0,0 +1,18 @@
extends PanelContainer
func _process(delta):
if visible:
pass
func add_property(title : String, value, order : int): # This can either be called once for a static property or called every frame for a dynamic property
var target
target = $MarginContainer/VBoxContainer.find_child(title, true, false) # I have no idea what true and false does here, the function should be more specific
if !target:
target = Label.new() # Debug lines are of type Label
$MarginContainer/VBoxContainer.add_child(target)
target.name = title
target.text = title + ": " + str(value)
elif visible:
target.text = title + ": " + str(value)
$MarginContainer/VBoxContainer.move_child(target, order)

View File

@ -0,0 +1,37 @@
[gd_scene load_steps=2 format=3 uid="uid://coqpusufa8a6k"]
[sub_resource type="GDScript" id="GDScript_10f85"]
script/source = "extends CenterContainer
@export_category(\"Reticle\")
@export_group(\"Nodes\")
@export var character : CharacterBody3D
@export_group(\"Settings\")
@export var dot_size : int = 1
@export var dot_color : Color = Color.WHITE
func _process(_delta):
if visible: # If the reticle is disabled (not visible), don't bother updating it
update_reticle_settings()
func update_reticle_settings():
$dot.scale.x = dot_size
$dot.scale.y = dot_size
$dot.color = dot_color
"
[node name="Reticle" type="CenterContainer"]
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_10f85")
[node name="dot" type="Polygon2D" parent="."]
polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)

View File

@ -0,0 +1,104 @@
[gd_scene load_steps=2 format=3 uid="uid://3mij3cjhkwsm"]
[sub_resource type="GDScript" id="GDScript_a8kpl"]
script/source = "extends CenterContainer
@export_category(\"Reticle\")
@export_group(\"Nodes\")
@export var reticle_lines : Array[Line2D]
@export var character : CharacterBody3D
@export_group(\"Animate\")
@export var animated_reticle : bool = true
@export var reticle_speed : float = 0.5
@export var reticle_spread : float = 4.0
@export_group(\"Dot Settings\")
@export var dot_size : int = 1
@export var dot_color : Color = Color.WHITE
@export_group(\"Line Settings\")
@export var line_color : Color = Color.WHITE
@export var line_width : int = 2
@export var line_length : int = 10
@export var line_distance : int = 5
@export_enum(\"None\", \"Round\") var cap_mode : int = 0
func _process(_delta):
if visible: # If the reticle is disabled (not visible), don't bother updating it
update_reticle_settings()
if animated_reticle:
animate_reticle_lines()
func animate_reticle_lines():
var vel = character.get_real_velocity()
var origin = Vector3(0,0,0)
var pos = Vector2(0,0)
var speed = origin.distance_to(vel)
reticle_lines[0].position = lerp(reticle_lines[0].position, pos + Vector2(0, -speed * reticle_spread), reticle_speed)
reticle_lines[1].position = lerp(reticle_lines[1].position, pos + Vector2(-speed * reticle_spread, 0), reticle_speed)
reticle_lines[2].position = lerp(reticle_lines[2].position, pos + Vector2(speed * reticle_spread, 0), reticle_speed)
reticle_lines[3].position = lerp(reticle_lines[3].position, pos + Vector2(0, speed * reticle_spread), reticle_speed)
func update_reticle_settings():
# Dot
$dot.scale.x = dot_size
$dot.scale.y = dot_size
$dot.color = dot_color
# Lines
for line in reticle_lines:
line.default_color = line_color
line.width = line_width
if cap_mode == 0:
line.begin_cap_mode = Line2D.LINE_CAP_NONE
line.end_cap_mode = Line2D.LINE_CAP_NONE
elif cap_mode == 1:
line.begin_cap_mode = Line2D.LINE_CAP_ROUND
line.end_cap_mode = Line2D.LINE_CAP_ROUND
# Please someone find a better way to do this
reticle_lines[0].points[0].y = -line_distance
reticle_lines[0].points[1].y = -line_length - line_distance
reticle_lines[1].points[0].x = -line_distance
reticle_lines[1].points[1].x = -line_length - line_distance
reticle_lines[2].points[0].x = line_distance
reticle_lines[2].points[1].x = line_length + line_distance
reticle_lines[3].points[0].y = line_distance
reticle_lines[3].points[1].y = line_length + line_distance
"
[node name="Reticle" type="CenterContainer" node_paths=PackedStringArray("reticle_lines")]
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_a8kpl")
reticle_lines = [NodePath("top"), NodePath("left"), NodePath("right"), NodePath("bottom")]
[node name="dot" type="Polygon2D" parent="."]
polygon = PackedVector2Array(-1, -1, 1, -1, 1, 1, -1, 1)
[node name="top" type="Line2D" parent="."]
points = PackedVector2Array(0, -5, 0, -15)
width = 2.0
[node name="left" type="Line2D" parent="."]
points = PackedVector2Array(-5, 0, -15, 0)
width = 2.0
[node name="right" type="Line2D" parent="."]
points = PackedVector2Array(5, 0, 15, 0)
width = 2.0
[node name="bottom" type="Line2D" parent="."]
points = PackedVector2Array(0, 5, 0, 15)
width = 2.0

View File

@ -0,0 +1 @@
extends Node

View File

@ -11,13 +11,14 @@ config_version=5
[application]
config/name="Steamforged Skies"
run/main_scene="res://assets/core/networking/scenes/lobby.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://assets/icon.png"
[autoload]
GameConsole="*res://addons/ingameconsole/GameConsole.tscn"
NetworkManager="*res://addons/godot_steam_sync/Network/RadomeSteamSync/NetworkManager.gd"
P2P="*res://addons/godot_steam_sync/Network/RadomeSteamSync/P2P.gd"
[debug]
@ -35,7 +36,7 @@ project/assembly_name="Steamforged Skies"
[editor_plugins]
enabled=PackedStringArray("res://addons/ingameconsole/plugin.cfg")
enabled=PackedStringArray("res://addons/godot_steam_sync/plugin.cfg", "res://addons/ingameconsole/plugin.cfg")
[input]