📶

【Godot4】 MultiplayerSpawner/Sychronizer でマルチプレイ実装

2022/08/12に公開約4,500字

Godot4で追加されたMultiplayerSpawnerMultiplayerSynchronizerを使うと、マルチプレイヤーゲームの実装が簡単に出来そう

https://www.youtube.com/watch?v=nQ4P3ogXp2Q

基本的に上記のyoutubeチュートリアルを参考にしてます。ただ、現在の最新バージョンGodot 4.0 alpha 14だと動かない部分があったので後述。

MultiplayerSpawner & MultiplayerSynchronizer のできること

  • MultiplayerSpawner : SceneTreeの同期処理(Nodeの追加、削除)
  • MultiplayerSynchronizer : Nodeプロパティの同期処理

他のエンジンで言うと何の機能に相当するんだろう...Photon Unity Networking の NetworkedObject とかそういう感じ?

マルチプレイゲームでよく書く処理といえば位置情報などの同期処理だが、gdscriptのrpcを使って書くと結構複雑だしいろいろなところで同じようなコードを何度も書くことになる。

MultiplayerSpawnerMultiplayerSynchronizerを使うとNodeのスポーン及びプロパティ同期をシンプルにできる。

前提知識

Godotでネットワーク機能を利用する前提として ENetMultiPlayerPeerを作成し、それをNode.multiplayer.multiplayer_peerにセットしておく必要がある。

# World.gd
func start_network(is_server: bool):
	var peer = ENetMultiplayerPeer.new()
	if is_server:
		peer.create_server(PORT)
	else:
		peer.create_client("localhost", PORT)
	self.multiplayer.multiplayer_peer = peer

MultiplayerSpawner

特定のNodeを監視し、その子要素の変更をリモートに同期する。
マルチプレイヤーを行うWorld, Levelに相当するシーンの子要素に追加しておく。

設定するプロパティ

  • Spawn Path
    • 同期するNodeのパス。指定したNodeにadd_child()メソッドでノードが追加されたりするとリモートのSceneTreeにも同期される
  • Auto Spawn List
    • ここに指定したシーンノードの生成が同期される

Spawn Path に指定した Node に対して add_child(), remove_child()など通常のNodeツリー操作を行うだけで、remoteで実行されているゲームでもSceneTreeが自動で同期される。

# World.gd
extends Node2D

const PlayerScene = preload("res://Player.tscn")

@onready var networked_nodes = $NetworkedNodes
@onready var spawner: MultiplayerSpawner = $MultiplayerSpawner

# Called when the node enters the scene tree for the first time.
func _ready():
	spawner.spawned.connect(func (x): print(x))
	print(spawner._spawnable_scenes)
	print(OS.get_cmdline_args())
	if "--server" in OS.get_cmdline_args():
		start_network(true)
	else:
		start_network(false)

func start_network(is_server: bool):
	var peer = ENetMultiplayerPeer.new()

	if is_server:
		self.multiplayer.peer_connected.connect(self.create_player)
		self.multiplayer.peer_disconnected.connect(self.destroy_player)
		peer.create_server(4242)
		print("server listening on localhost 4242")
	else:
		var target_ip = "localhost"
		peer.create_client(target_ip, 4242)
	
	self.get_tree().root.multiplayer.multiplayer_peer = peer

func create_player(id):
	var p = PlayerScene.instantiate()
	p.name = str(id)
	networked_nodes.add_child(p)

func destroy_player(id):
	networked_nodes.get_node(str(id)).queue_free()

サーバー側のシーンノードのmultiplayerAPIインスタンスに対しコールバックを登録して、クライアントが接続してきたらPlayerシーンをスポーンする 。

  • peer_connected => create_player(id)
  • peer_disconnected => destroy_player(id)

MultiplayerSynchronizer

特定のNodeのプロパティを監視し、その変更をリモートに同期する。
プロパティ同期を行うシーンノード以下に配置しておく。

設定するプロパティ

  • Root Path
    • 指定したNode Path以下にあるノードのプロパティを同期処理できる

冒頭のyoutubeチュートリアルそのままだと動かない。(alpha版なのでAPIが更新された影響と思われる)

SceneTreeでMultiplayerSynchronizerを選択しているときに下のパネルに表示されるReplication設定タブから、Add Properties to sync... > Player > sync_position と指定しておく必要あり

CharacterBody2Dのデフォルトスクリプトに少し書き加えると、すぐにネットワーク同期されるNodeにできる。

#Player.gd
extends CharacterBody2D

...

@export
var sync_position := Vector2.ZERO

...

func is_local_authority() -> bool:
	return name == str(multiplayer.get_unique_id())

func _physics_process(delta):
	# Playerノードの所有者がlocalではなくremote serverの場合は同期されるpositionを使う
	if not is_local_authority():
		position = sync_position
		return
		
  ...
	# 移動処理
  ...
	
	# サーバーに移動後の位置をpushする。1 = server peer id, コールするとサーバーで実行される。
	rpc_id(1, StringName('push_to_server'), position)

@rpc(any_peer, unreliable_ordered)
func push_to_server(_sync_pos: Vector2):
	if not multiplayer.is_server():
		return
	if name != str(multiplayer.get_remote_sender_id()):
		print("someone being naughty!", multiplayer.get_remote_sender_id(), ' tried to update ', name)
		return
	sync_position = _sync_pos
	

@rpc(any_peer, unreliable_ordered)アノテーションは、どのpeerからでも実行できるかつ信頼性が低いが速いというオプション。リアルタイム性の高い同期(position)向き。

rpc_id(1, StringName('push_to_server'), position)で呼び出すことでクライアントからサーバーへpositionをpushしていると見なせる。

サーバー側でpushされてきた情報でPlayerノードを更新すると、MultiplayerSynchronizerにより他のクライアントにもプロパティの変更が同期される。

参考

自分で書いたコード

https://github.com/harumaxy/godot4-multiplayer-sync-spawn-demo

参考にしたリポジトリ

https://github.com/MitchMakesThings/Godot-Things/tree/main/Networking

所感

3.x系よりシンプルいい感じにマルチプレイの同期処理を書けそう。

同期処理はよく使う機能なわりに自前で書こうとするとサーバー・クライアントサイドの処理の違いなどを意識したり複雑だが、コードで制御するんじゃなくエンジンに実装されてる標準機能に任せるってのは安心感と手軽さがあって良い。

GitHubで編集を提案

Discussion

ログインするとコメントできます