First Commit

Base multiplayer example with obster
This commit is contained in:
Twirpytherobot
2025-11-12 18:41:45 +00:00
parent 2616f1f052
commit 1d63abc581
157 changed files with 73264 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
extends Node3D
class_name Body
const LERP_VELOCITY: float = 0.15
@export_category("Objects")
@export var _character: CharacterBody3D = null
@export var animation_player: AnimationPlayer = null
func apply_rotation(_velocity: Vector3) -> void:
var new_rotation_y = lerp_angle(rotation.y, atan2(-_velocity.x, -_velocity.z), LERP_VELOCITY)
rotation.y = new_rotation_y
# rpc("sync_player_rotation", new_rotation_y)
func animate(_velocity: Vector3) -> void:
if not _character.is_on_floor():
if _velocity.y < 0:
animation_player.play("Fall")
else:
animation_player.play("Jump")
return
if _velocity:
if _character.is_running() and _character.is_on_floor():
animation_player.play("Sprint")
return
animation_player.play("Run")
return
animation_player.play("Idle")
# @rpc("any_peer", "reliable")
# func sync_player_rotation(rotation_y: float) -> void:
# rotation.y = rotation_y
+1
View File
@@ -0,0 +1 @@
uid://v8j54rbc0ik
+123
View File
@@ -0,0 +1,123 @@
extends Node3D
@onready var skin_input: LineEdit = $Menu/MainContainer/MainMenu/Option2/SkinInput
@onready var nick_input: LineEdit = $Menu/MainContainer/MainMenu/Option1/NickInput
@onready var address_input: LineEdit = $Menu/MainContainer/MainMenu/Option3/AddressInput
@onready var players_container: Node3D = $PlayersContainer
@onready var menu: Control = $Menu
@export var player_scene: PackedScene
# multiplayer chat
@onready var message: LineEdit = $MultiplayerChat/VBoxContainer/HBoxContainer/Message
@onready var send: Button = $MultiplayerChat/VBoxContainer/HBoxContainer/Send
@onready var chat: TextEdit = $MultiplayerChat/VBoxContainer/Chat
@onready var multiplayer_chat: Control = $MultiplayerChat
var chat_visible = false
func _ready():
multiplayer_chat.hide()
menu.show()
multiplayer_chat.set_process_input(true)
if not multiplayer.is_server():
return
Network.connect("player_connected", Callable(self, "_on_player_connected"))
multiplayer.peer_disconnected.connect(_remove_player)
func _on_player_connected(peer_id, player_info):
# for id in Network.players.keys():
# var player_data = Network.players[id]
# if id != peer_id:
# rpc_id(peer_id, "sync_player_skin", id, player_data["skin"])
_add_player(peer_id, player_info)
func _on_host_pressed():
menu.hide()
Network.start_host(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower())
func _on_join_pressed():
menu.hide()
Network.join_game(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower(), address_input.text.strip_edges())
func _add_player(id: int, player_info : Dictionary):
if players_container.has_node(str(id)):
return
var player = player_scene.instantiate()
player.name = str(id)
player.position = get_spawn_point()
players_container.add_child(player, true)
var nick = Network.players[id]["nick"]
player.nickname.text = nick
# player.rpc("change_nick", nick)
var skin_enum = player_info["skin"]
player.set_player_skin(skin_enum)
# rpc("sync_player_skin", id, skin_enum)
# rpc("sync_player_position", id, player.position)
func get_spawn_point() -> Vector3:
var spawn_point = Vector2.from_angle(randf() * 2 * PI) * 10 # spawn radius
return Vector3(spawn_point.x, 0, spawn_point.y)
func _remove_player(id):
if not multiplayer.is_server() or not players_container.has_node(str(id)):
return
var player_node = players_container.get_node(str(id))
if player_node:
player_node.queue_free()
# @rpc("any_peer", "call_local")
# func sync_player_position(id: int, new_position: Vector3):
# var player = players_container.get_node(str(id))
# if player:
# player.position = new_position
# @rpc("any_peer", "call_local")
# func sync_player_skin(id: int, skin_color: Character.SkinColor):
# var player = players_container.get_node(str(id))
# if player:
# player.set_player_skin(skin_color)
func _on_quit_pressed() -> void:
get_tree().quit()
# ---------- MULTIPLAYER CHAT ----------
func toggle_chat():
if menu.visible:
return
chat_visible = !chat_visible
if chat_visible:
multiplayer_chat.show()
message.grab_focus()
else:
multiplayer_chat.hide()
get_viewport().set_input_as_handled()
func is_chat_visible() -> bool:
return chat_visible
func _input(event):
if event.is_action_pressed("toggle_chat"):
toggle_chat()
elif event is InputEventKey and event.keycode == KEY_ENTER:
_on_send_pressed()
func _on_send_pressed() -> void:
var trimmed_message = message.text.strip_edges()
if trimmed_message == "":
return # do not send empty messages
var nick = Network.players[multiplayer.get_unique_id()]["nick"]
rpc("msg_rpc", nick, trimmed_message)
message.text = ""
message.grab_focus()
@rpc("any_peer", "call_local")
func msg_rpc(nick, msg):
chat.text += str(nick, " : ", msg, "\n")
+1
View File
@@ -0,0 +1 @@
uid://d0dgljwwl463n
+90
View File
@@ -0,0 +1,90 @@
extends Node
const SERVER_ADDRESS: String = "127.0.0.1"
const SERVER_PORT: int = 8080
const MAX_PLAYERS : int = 10
var players = {}
var player_info = {
"nick" : "host",
"skin" : Character.SkinColor.BLUE
}
signal player_connected(peer_id, player_info)
signal server_disconnected
func _process(_delta):
if Input.is_action_just_pressed("quit"):
get_tree().quit(0)
func _ready() -> void:
multiplayer.server_disconnected.connect(_on_connection_failed)
multiplayer.connection_failed.connect(_on_server_disconnected)
multiplayer.peer_disconnected.connect(_on_player_disconnected)
multiplayer.peer_connected.connect(_on_player_connected)
multiplayer.connected_to_server.connect(_on_connected_ok)
func start_host(nickname: String, skin_color_str: String):
var peer = ENetMultiplayerPeer.new()
var error = peer.create_server(SERVER_PORT, MAX_PLAYERS)
if error:
return error
multiplayer.multiplayer_peer = peer
if !nickname or nickname.strip_edges() == "":
nickname = "Host_" + str(1)
player_info["nick"] = nickname
player_info["skin"] = skin_str_to_e(skin_color_str)
players[1] = player_info
player_connected.emit(1, player_info)
func join_game(nickname: String, skin_color_str: String, address: String = SERVER_ADDRESS):
var peer = ENetMultiplayerPeer.new()
var error = peer.create_client(address, SERVER_PORT)
if error:
return error
multiplayer.multiplayer_peer = peer
if !nickname or nickname.strip_edges() == "":
nickname = "Player_" + str(multiplayer.get_unique_id())
var skin_enum = skin_str_to_e(skin_color_str)
player_info["nick"] = nickname
player_info["skin"] = skin_enum
func _on_connected_ok():
var peer_id = multiplayer.get_unique_id()
players[peer_id] = player_info
player_connected.emit(peer_id, player_info)
func _on_player_connected(id):
_register_player.rpc_id(id, player_info)
@rpc("any_peer", "reliable")
func _register_player(new_player_info):
var new_player_id = multiplayer.get_remote_sender_id()
players[new_player_id] = new_player_info
player_connected.emit(new_player_id, new_player_info)
#print("debug function _register_player on Network.gd: ", players, "\n")
func _on_player_disconnected(id):
players.erase(id)
func _on_connection_failed():
multiplayer.multiplayer_peer = null
func _on_server_disconnected():
multiplayer.multiplayer_peer = null
players.clear()
server_disconnected.emit()
func skin_str_to_e(s):
match s.to_lower():
"blue": return Character.SkinColor.BLUE
"yellow": return Character.SkinColor.YELLOW
"green": return Character.SkinColor.GREEN
"red": return Character.SkinColor.RED
_: return Character.SkinColor.BLUE
+1
View File
@@ -0,0 +1 @@
uid://cedubc87s78fo
+136
View File
@@ -0,0 +1,136 @@
extends CharacterBody3D
class_name Character
const NORMAL_SPEED = 6.0
const SPRINT_SPEED = 10.0
const JUMP_VELOCITY = 10
enum SkinColor { BLUE, YELLOW, GREEN, RED }
@onready var nickname: Label3D = $PlayerNick/Nickname
@export_category("Objects")
@export var _body: Node3D = null
@export var _spring_arm_offset: Node3D = null
@export_category("Skin Colors")
@export var blue_texture : CompressedTexture2D
@export var yellow_texture : CompressedTexture2D
@export var green_texture : CompressedTexture2D
@export var red_texture : CompressedTexture2D
@onready var _bottom_mesh: MeshInstance3D = get_node("3DGodotRobot/RobotArmature/Skeleton3D/Bottom")
@onready var _chest_mesh: MeshInstance3D = get_node("3DGodotRobot/RobotArmature/Skeleton3D/Chest")
@onready var _face_mesh: MeshInstance3D = get_node("3DGodotRobot/RobotArmature/Skeleton3D/Face")
@onready var _limbs_head_mesh: MeshInstance3D = get_node("3DGodotRobot/RobotArmature/Skeleton3D/Llimbs and head")
var _current_speed: float
var _respawn_point = Vector3(0, 5, 0)
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
func _enter_tree():
set_multiplayer_authority(str(name).to_int())
$SpringArmOffset/SpringArm3D/Camera3D.current = is_multiplayer_authority()
func _ready():
return
func _physics_process(delta):
if not is_multiplayer_authority(): return
var current_scene = get_tree().get_current_scene()
if current_scene and current_scene.has_method("is_chat_visible") and current_scene.is_chat_visible() and is_on_floor():
freeze()
return
if not is_on_floor():
velocity.y -= gravity * delta
_body.animate(velocity)
if is_on_floor():
if Input.is_action_just_pressed("jump"):
velocity.y = JUMP_VELOCITY
else:
velocity.y -= gravity * delta
_move()
move_and_slide()
_body.animate(velocity)
func _process(_delta):
if not is_multiplayer_authority(): return
_check_fall_and_respawn()
func freeze():
velocity.x = 0
velocity.z = 0
_current_speed = 0
_body.animate(Vector3.ZERO)
func _move() -> void:
var _input_direction: Vector2 = Vector2.ZERO
if is_multiplayer_authority():
_input_direction = Input.get_vector(
"move_left", "move_right",
"move_forward", "move_backward"
)
var _direction: Vector3 = transform.basis * Vector3(_input_direction.x, 0, _input_direction.y).normalized()
is_running()
_direction = _direction.rotated(Vector3.UP, _spring_arm_offset.rotation.y)
if _direction:
velocity.x = _direction.x * _current_speed
velocity.z = _direction.z * _current_speed
_body.apply_rotation(velocity)
return
velocity.x = move_toward(velocity.x, 0, _current_speed)
velocity.z = move_toward(velocity.z, 0, _current_speed)
func is_running() -> bool:
if Input.is_action_pressed("shift"):
_current_speed = SPRINT_SPEED
return true
else:
_current_speed = NORMAL_SPEED
return false
func _check_fall_and_respawn():
if global_transform.origin.y < -15.0:
_respawn()
func _respawn():
global_transform.origin = _respawn_point
velocity = Vector3.ZERO
@rpc("any_peer", "reliable")
func change_nick(new_nick: String):
if nickname:
nickname.text = new_nick
func get_texture_from_name(skin_color: SkinColor) -> CompressedTexture2D:
match skin_color:
SkinColor.BLUE: return blue_texture
SkinColor.GREEN: return green_texture
SkinColor.RED: return red_texture
SkinColor.YELLOW: return yellow_texture
_: return blue_texture
@rpc("any_peer", "reliable")
func set_player_skin(skin_name: SkinColor) -> void:
var texture = get_texture_from_name(skin_name)
set_mesh_texture(_bottom_mesh, texture)
set_mesh_texture(_chest_mesh, texture)
set_mesh_texture(_face_mesh, texture)
set_mesh_texture(_limbs_head_mesh, texture)
func set_mesh_texture(mesh_instance: MeshInstance3D, texture: CompressedTexture2D) -> void:
if mesh_instance:
var material := mesh_instance.get_surface_override_material(0)
if material and material is StandardMaterial3D:
var new_material := material
new_material.albedo_texture = texture
mesh_instance.set_surface_override_material(0, new_material)
+1
View File
@@ -0,0 +1 @@
uid://c2si8gkbnde0c
+14
View File
@@ -0,0 +1,14 @@
extends Node3D
class_name SpringArmCharacter
const MOUSE_SENSIBILITY: float = 0.005
@export_category("Objects")
@export var _spring_arm: SpringArm3D = null
func _unhandled_input(_event) -> void:
if (_event is InputEventMouseMotion) and is_multiplayer_authority():
rotate_y(-_event.relative.x * MOUSE_SENSIBILITY)
_spring_arm.rotate_x(-_event.relative.y * MOUSE_SENSIBILITY)
_spring_arm.rotation.x = clamp(_spring_arm.rotation.x, -PI/4, PI/24)
+1
View File
@@ -0,0 +1 @@
uid://bj7yrijm7bppq