【Unreal Engine】マルチプレイヤー入門:ReplicationとRPCで2人で同じ世界を見る

作成: 2026-07-20

UE5のマルチプレイをBlueprintだけで始める入門。サーバが正・クライアントは表示というモデル、PIEで2窓起動して試す方法、Replicated変数とRepNotify、Server RPCの使い方まで。2人で押せるボタンと共有される扉を作りながら、自分だけ動く定番バグの直し方も解説します。

友達と2人で、同じゲーム世界を歩き回りたい。片方がボタンを押したら、もう片方の画面でも扉が開いてほしい。マルチプレイは難しそうですが、UEは 最初からマルチプレイ機能を内蔵 していて、Blueprintだけでも「2人で同じものが見える」ところまで作れます。

ただし、1人用ゲームの感覚のままだと必ずハマります。「自分の画面では動くのに、相手には伝わらない」という壁です。この壁の正体は、誰がゲームの状態を持っているか の理解不足です。この記事では、マルチプレイの基本モデルと、2人で共有する扉を作りながら、この定番バグの直し方までを解説します。オンラインの高度な最適化は扱わず、「2人で同じものが見える」までを丁寧にやります。

2つの画面に同じ扉が映り、片方で押したボタンがもう片方にも反映される様子とソフトブルーのクレイ人形

この記事でわかること

  • サーバが正、クライアントは表示 というモデル
  • PIEで 2窓起動 して2人プレイをテストする
  • Replicated変数RepNotify で値を全員に配る
  • 誰が実行しているか:Has AuthorityServer RPC
  • 🚨 Server RPCは「自分が所有するActor」から呼ぶ(黙って捨てられる条件)
  • 実践: 2人で押せるボタンと、共有される扉
  • 「自分だけ動いて相手に伝わらない」定番バグの直し方

Sponsored

サーバが正、クライアントは表示

マルチプレイで最初に頭を切り替えるのは、この一点です。ゲームの本当の状態はサーバが持っていて、クライアントはそれを映しているだけ です。

扉が開いているか、敵のHPがいくつか、といった「本当の値」はサーバにあります。各プレイヤーの画面(クライアント)は、サーバから送られてきた値を表示しているにすぎません。クライアントが勝手に値を書き換えても、それは自分の画面だけの嘘で、他の人には伝わりません。

サーバが正しい状態を持ち、各クライアントはそれを受け取って表示するだけ、という関係を中心のサーバと2つのクライアントで描いた図

テスト用に手軽なのが Listen Server です。プレイヤーの1人が、サーバも兼ねる形です。専用のサーバPCを立てなくても、片方がホストになって、もう片方が参加する、という構成で試せます。

このモデルさえ腹落ちすれば、マルチプレイのバグの大半は「サーバを通さずにクライアントで値を変えた」という同じ原因に見えてきます。

PIEで2窓起動して試す

2人プレイは、エディタの中で試せます。専用の設定は2つです。

  • Number of Players:同時に起動するプレイヤー数。2 にする。
  • Net Mode:どういう構成で起動するか。Play As Listen Server にする。
PlayボタンのメニューでNumber of Playersを2、Net ModeをListen Serverにすると2つのウィンドウが起動する様子を示した図

Playボタンの横(三点メニュー)でこれらを設定してPlayすると、ウィンドウが2つ 立ち上がります。片方がサーバ兼プレイヤー(ホスト)、もう片方がクライアントです。両方を並べて、片方で起きたことがもう片方に反映されるかを見ながら開発します。

マルチプレイは、この「2窓で見比べる」が基本の確認方法です。1窓だけで作っていると、同期できていないことに気づけません。

変数を同期する:ReplicatedとRepNotify

サーバが持つ値を、全クライアントに配る仕組みが Replication(レプリケーション) です。

まず、そのアクター自体を同期対象にします。Class Defaults の Replicates にチェックを入れます。動くアクター(キャラクターなど)なら Replicate Movement も入れると、位置が自動で同期されます。

次に、配りたい変数ごとに設定します。変数の詳細で Replication を選びます。

  • Replicated:値が変わると自動で全クライアントに配られる。
  • RepNotify:配られると同時に、クライアント側で決まった関数(OnRep_変数名)が呼ばれる。
アクターのReplicatesをオンにし、変数をRepNotifyにすると、サーバの変更が全クライアントに配られOnRep関数が呼ばれる流れの図

HPバーのように「値が変わったら見た目も更新したい」ものは RepNotify が便利です。値が届いた瞬間に OnRep_Health が各クライアントで呼ばれるので、そこでバーを描き直せば、全員の画面が揃います。

大事なのは、Replicated変数を書き換えていいのはサーバだけ ということです。クライアントで書き換えても配られません。

Sponsored

誰が実行しているか:AuthorityとRPC

マルチプレイのBlueprintでは、同じグラフが サーバでもクライアントでも 走ります。だから「今これはどっちで動いているのか」を意識する必要があります。

それを判定するのが Has Authority(または Switch Has Authority)です。Authority を持つ=サーバ、持たない=クライアント、と考えてください。「この処理はサーバだけでやりたい」ときは、Has Authority で分岐します。

では、クライアントで起きた入力(ボタンを押した)を、どうやってサーバに伝えるか。それが RPC(Remote Procedure Call) です。RPCは「向こう側で関数を実行してもらう」仕組みで、3種類あります。

種類どこで実行される用途
Serverサーバ上で実行クライアントの入力をサーバに伝える
Client特定のクライアントで実行サーバからそのプレイヤーだけに通知
Multicastサーバと全クライアントで実行エフェクトや音を全員に見せる
Server RPCはクライアントからサーバへ、Client RPCはサーバから1人へ、Multicastはサーバから全員へ、という3種類のRPCの向きを矢印で示した図

もう1つ、RPCには Reliable / Unreliable があります。Reliable は「必ず届く」、Unreliable は「届かないこともあるが軽い」。扉を開けるような、取りこぼすと困る大事な通信は Reliable にします。毎フレーム送るような軽い情報は Unreliable にします。

🚨 Server RPCは「自分が所有するActor」から呼ぶ

ここが最初に必ず引っかかる条件です。Server RPCは、どのActorから呼んでもいいわけではありません。

レベルに置いた共有の扉からServer RPCを呼ぶと所有者がいないため無視され、自分のキャラクターから呼ぶとサーバへ届くことを左右で対比した図

クライアントからのServer RPCがサーバへ届くのは、呼び出したActorがそのクライアントの所有物であるときだけです。所有者がいないActorから呼んでも、エラーも出ずに黙って捨てられます

呼び出し元所有者Server RPCは届くか
自分のCharacter / PlayerControllerそのクライアント届く
レベルに置いた扉・スイッチいない❌ 捨てられる
他プレイヤーのCharacter別のクライアント❌ 捨てられる

つまり、扉のBlueprintの中で Server_OpenDoor を作っても動きません。「自分が操作しているキャラクター」を経由させる必要があります。

「何も起きないのに、エラーも出ない」という症状のとき、まずここを疑ってください。この記事の実践は、最初からこの条件を満たす形で組みます。

基本の流れはこうです。クライアントが「自分のキャラから」Server RPCでサーバに頼む → サーバが Replicated 変数を書き換える → 変更が全員に配られる。この一方通行を守れば、同期は素直に動きます。

Sponsored

実践:2人で共有する扉

具体的に、2人で共有する扉を作ります。

協力パズルのスイッチ、サンドボックスの共有ドア、オンライン協力アクションのギミック。「片方が押したら、両方の画面で開く」構造はどれも同じです。ここでは、プレイヤーがボタンに触れると、両方の画面で扉が開く ものを作ります。

動かすとこうなります。2窓のどちらでボタンに乗っても、両方のウィンドウで扉が開きます。逆に、Server RPCを介さず直接変数を書くと、押した側の画面でしか開きません。この対比が、モデル理解の答え合わせになります。

2窓のどちらで踏んでも両画面で扉が開く様子と、直接変数を書くと踏んだ側だけ開く失敗を対比した実例図

準備

2つのBlueprintを使います。前節の所有権の条件があるので、RPCはプレイヤーのキャラクター側に置きます

BP_SharedDoor(扉のActor Blueprint):

項目設定
Class DefaultsReplicates = true
変数 bDoorOpen型 Boolean、Replication = RepNotify、初期値 false
ボタンBox Collision(触れると反応)
DoorMeshStatic Mesh(Cube を薄い扉型に)。開く動きはこのメッシュの 相対回転 で表す
関数 ApplyDoorStatebDoorOpen を見て DoorMesh の相対回転を設定する(見た目の更新はここ1箇所だけ
関数 OpenDoorサーバで bDoorOpen = true にして ApplyDoorState を呼ぶ

BP_PlayerCharacter(プレイヤーのキャラクター):

項目設定
カスタムイベント Server_RequestOpenDoorReplicates = Run on ServerReliable をオン。入力に Door(型 BP_SharedDoor

bDoorOpen を RepNotify にすると、OnRep_bDoorOpen 関数が自動で作られます。

見た目の更新を ApplyDoorState にまとめる理由。 OnRep_bDoorOpen は、サーバ自身では呼ばれないことがあります(サーバは自分で値を書いているので、通知が要らない)。そこで 「値を書いた直後」と「OnRep」の両方から同じ関数を呼ぶ 形にしておけば、どちらの環境でも見た目が揃います。「OnRepはクライアントだけ」と暗記するより、この形にしてしまうほうが安全です。

グラフを組む

上段「自分のキャラからサーバへ頼む」でOn Component Begin Overlap→Cast To BP_PlayerCharacter→Is Locally Controlled→Server_RequestOpenDoor、下段「サーバが確かめて書く」でIs Valid→距離チェック→OpenDoor→ApplyDoorStateへ折り返す2段組みの完成ノードグラフ図

つなぎ方は次の流れです。

  1. BP_SharedDoorOn Component Begin Overlap(ボタンに触れたら)で、Other ActorCast To BP_PlayerCharacter する
  2. そのキャラクターが Is Locally Controlled かを Branch で確かめる(自分が操作しているキャラのときだけ送る。ここを抜くと、他プレイヤーの分まで送ろうとして無視されます)
  3. True なら、そのキャラクターの Server_RequestOpenDoor(Door = 扉自身)を呼ぶ。キャラクターは自分の所有物なので、これはサーバへ届きます
  4. Server_RequestOpenDoor の中(サーバで実行)で、DoorIs Valid か、そして 扉との距離が近いか を確かめる
  5. 問題なければ DoorOpenDoor を呼び、bDoorOpen = true にして ApplyDoorState を実行する
  6. bDoorOpen は RepNotify なので値が全クライアントへ配られ、各クライアントの OnRep_bDoorOpen から ApplyDoorState が呼ばれる

読み上げ用の疑似コードにすると、こうなります。

BP_SharedDoor
On Component Begin Overlap (Button)
  → chara = Cast To BP_PlayerCharacter(Other Actor)
  → Branch (chara.IsLocallyControlled)      // 自分のキャラだけ
      True → chara.Server_RequestOpenDoor(Door = self)

OpenDoor():                                  // サーバでのみ呼ばれる
  bDoorOpen = true                           // Replicated変数はサーバだけが書く
  ApplyDoorState()                           // サーバ自身の見た目も更新

OnRep_bDoorOpen():                           // 値が届いたクライアントで呼ばれる
  ApplyDoorState()

ApplyDoorState():                            // 見た目の更新はここ1箇所
  Set Relative Rotation(
      Target = DoorMesh,
      New Rotation = bDoorOpen ? (0, 0, 90) : (0, 0, 0))

BP_PlayerCharacter
Server_RequestOpenDoor(Door)                 // Run on Server / Reliable
  → Is Valid(Door) が false なら何もしない
  → 距離チェック: (Door.GetActorLocation() - GetActorLocation()).Length() < 300.0
  → Door.OpenDoor()

サーバ側で距離を確かめているのが要点です。 「クライアントが頼んできたから開ける」ではなく、サーバが自分で条件を確認してから開ける。この一手間が、チートへの最初の防壁になります。

確かめる

Number of Players = 2、Net Mode = Listen Server で Play します。片方のウィンドウでプレイヤーをボタンに乗せると、両方の画面で扉が開けば成功 です。もう片方で乗っても、同じく両方で開きます。

うまくいかないときの切り分けです。

  • 押した側でしか開かない → Server RPCを介さず、Overlapから直接 bDoorOpen を書いています。クライアントで書いた値は配られません
  • まったく開かない・エラーも出ない所有権の条件を満たしていません。Server_RequestOpenDoor を扉側(BP_SharedDoor)に作っていませんか。RPCはキャラクター側に置きます
  • サーバ側(1つ目のウィンドウ)だけ開かないOpenDoor の中で ApplyDoorState を呼び忘れています。OnRep はサーバでは呼ばれないことがあります
  • クライアント側だけ開かない → アクターの Replicates が false か、変数の Replication が未設定です

ポイントは2つです。

  • Server RPCは自分の所有物から呼ぶ:レベルに置いた扉やスイッチには所有者がいないので、そこに置いたServer RPCは黙って捨てられます。入力は必ず自分のキャラクターやPlayerControllerを経由させてください
  • 見た目の更新を1箇所にまとめるApplyDoorState を作り、サーバが値を書いた直後とクライアントの OnRep の両方から呼びます。「どちらの環境でも同じ関数が走る」形にしておけば、片側だけ開かない事故が起きません

ダメージのような「勝敗に関わる計算」も同じ発想で、ダメージはサーバで計算する のが基本です。クライアントは結果を受け取って表示するだけにします。

おまけ:先に知っておくと良いこと

サーバ/クライアントの役割は Game Framework が土台。 どのクラスがサーバに1つで、どれが各プレイヤーにあるのか、という土台は GameMode / GameState / PlayerState で決まっています。GameModeはサーバにしか存在しない、といった性質は、マルチプレイでそのまま効いてきます。

まずはListen Serverで十分。 専用サーバ(Dedicated Server)は、規模が大きくなってから考えれば十分です。個人開発の入口では、片方がホストになる Listen Server で「同期の考え方」を身につけるのが先です。

Steam連携やマッチメイキングは別の話。 「離れた友達とネットで繋ぐ」には、Online Subsystem(Steamなど)やセッション管理が要ります。これは Replication とは別のレイヤーの話なので、まず同じPCの2窓で同期を完成させてから、次のステップとして取り組むのが現実的です。

まとめ

マルチプレイの土台は、たった1つの原則です。サーバが状態を持ち、クライアントは表示する。だから、クライアントの入力は Server RPC でサーバに送り、サーバが Replicated 変数を書き、RepNotify で全員に配る。この一方通行さえ守れば、「2人で同じものが見える」は素直に作れます。

そして、その入口でつまずくのは決まって 所有権 です。 Server RPCは自分が所有するActor(=自分のキャラクターやPlayerController)から呼ぶ 。レベルに置いた扉から呼んでも、エラーすら出ずに捨てられます。

あなたのゲームで、2人で共有したい最初のものは何でしょうか。扉でもスコアでも、この一方通行に乗せれば、ちゃんと全員の画面で揃います。