extends Node # Автолоад "Net". Один и тот же скрипт работает и на клиенте, и на выделенном сервере # (RPC требуют одинаковый путь узла и скрипт). Сервер — это headless-запуск Godot. const PORT = 9080 const DEFAULT_URL = "ws://127.0.0.1:9080" const SEND_RATE = 20.0 const RETRY_DELAY = 3.0 const FLAG_SLIDING = 1 const FLAG_WALLRUN = 2 const FLAG_DASH = 4 const FLAG_MANTLE = 8 var is_server := false var ghosts := {} # peer_id -> Ghost var send_timer := 0.0 var connected := false func _ready() -> void: is_server = DisplayServer.get_name() == "headless" if is_server: _start_server() else: _connect_client() # ---------- Сервер ---------- func _start_server() -> void: var peer := WebSocketMultiplayerPeer.new() var err := peer.create_server(PORT) if err != OK: push_error("Не удалось открыть порт %d: %s" % [PORT, error_string(err)]) get_tree().quit(1) return multiplayer.multiplayer_peer = peer multiplayer.peer_connected.connect(func(id): print("+ игрок ", id)) multiplayer.peer_disconnected.connect(_server_peer_left) print("Сервер запущен на порту ", PORT) func _server_peer_left(id: int) -> void: print("- игрок ", id) for other in multiplayer.get_peers(): recv_left.rpc_id(other, id) # Клиент -> сервер: своё состояние. Сервер пересылает остальным. @rpc("any_peer", "unreliable_ordered") func submit_state(pos: Vector3, yaw: float, pitch: float, flags: int) -> void: if not is_server: return var sender := multiplayer.get_remote_sender_id() for other in multiplayer.get_peers(): if other != sender: recv_state.rpc_id(other, sender, pos, yaw, pitch, flags) # ---------- Клиент ---------- func _server_url() -> String: for arg in OS.get_cmdline_user_args(): if arg.begins_with("--server-url="): return arg.substr("--server-url=".length()) if OS.has_feature("web"): var proto = JavaScriptBridge.eval("window.location.protocol") var host = JavaScriptBridge.eval("window.location.host") return ("wss://" if proto == "https:" else "ws://") + str(host) + "/ws" return DEFAULT_URL func _connect_client() -> void: var peer := WebSocketMultiplayerPeer.new() var err := peer.create_client(_server_url()) if err != OK: push_warning("Не удалось подключиться: %s" % error_string(err)) _schedule_retry() return multiplayer.multiplayer_peer = peer if not multiplayer.connected_to_server.is_connected(_on_connected): multiplayer.connected_to_server.connect(_on_connected) multiplayer.connection_failed.connect(_on_lost) multiplayer.server_disconnected.connect(_on_lost) func _on_connected() -> void: connected = true func _on_lost() -> void: connected = false for g in ghosts.values(): g.queue_free() ghosts.clear() multiplayer.multiplayer_peer = null _schedule_retry() func _schedule_retry() -> void: get_tree().create_timer(RETRY_DELAY).timeout.connect(_connect_client) @rpc("authority", "unreliable_ordered") func recv_state(id: int, pos: Vector3, yaw: float, pitch: float, flags: int) -> void: var ghost: Ghost = ghosts.get(id) if ghost == null: ghost = Ghost.new() ghost.setup(id, pos) ghosts[id] = ghost get_tree().current_scene.add_child(ghost) ghost.set_target(pos, yaw, pitch, flags) @rpc("authority", "reliable") func recv_left(id: int) -> void: var ghost = ghosts.get(id) if ghost: ghost.queue_free() ghosts.erase(id) func _process(delta: float) -> void: if is_server or not connected: return send_timer += delta if send_timer < 1.0 / SEND_RATE: return send_timer = 0.0 var player := get_tree().get_first_node_in_group("local_player") if player == null: return submit_state.rpc_id(1, player.global_position, player.rotation.y, player.head.rotation.x, player.get_net_flags())