友達と2人で、同じゲーム世界を歩き回りたい。片方がボタンを押したら、もう片方の画面でも扉が開いてほしい。マルチプレイは難しそうですが、UEは 最初からマルチプレイ機能を内蔵 していて、Blueprintだけでも「2人で同じものが見える」ところまで作れます。
ただし、1人用ゲー ムの感覚のままだと必ずハマります。「自分の画面では動くのに、相手には伝わらない」という壁です。この壁の正体は、誰がゲームの状態を持っているか の理解不足です。この記事では、マルチプレイの基本モデルと、2人で共有する扉を作りながら、この定番バグの直し方までを解説します。オンラインの高度な最適化は扱わず、「2人で同じものが見える」までを丁寧にやります。
この記事でわかること
- サーバが正、クライアントは表示 というモデル
- PIEで 2窓起動 して2人プレイをテストする
- Replicated変数 と RepNotify で値を全員に配る
- 誰が実行しているか:Has Authority と Server RPC
- 🚨 Server RPCは「自分が所有するActor」から呼ぶ(黙って捨てられる条件)
- 実践: 2人で押せるボタンと、共有される扉
- 「自分だけ動いて相手に伝わらない」定番バグの直し方
サーバが正、クライアントは表示
マルチプレイで最初に頭を切り替えるのは、この一点です。ゲームの本当の状態はサーバが持っていて、クライアントはそれを映しているだけ です。
扉が開いているか、敵のHPがいくつか、といった「本当の値」はサーバにあります。各プレイヤーの画面(クライアント)は、サーバから送られてきた値を表示しているにすぎません。クライアントが勝手に値を書き換えても、それは自分の画面だけの嘘で、他の人には伝わりません。

テスト用に手軽なのが Listen Server です。プレイヤーの1人が、サーバも兼ねる形です。専用のサーバPCを立てなくても、片方がホストになって、もう片方が参加する、という構成で試せます。
このモデルさえ腹落ちすれば、マルチプレイのバグの大半は「サーバを通さずにクライアントで値を変えた」という同じ原因に見えてきます。
PIEで2窓起動して試す
2人プレイは、エディタの中で試せます。専用の設定は2つです。
- Number of Players:同時に起動するプレイヤー数。2 にする。
- Net Mode:どういう構成で起動するか。Play As Listen Server にする。

Playボタンの横(三点メニュー)でこれらを設定してPlayすると、ウィンドウが2つ 立ち上がります。片方がサーバ兼プレイヤー(ホスト)、もう片方がクライアントです。両方を並べて、片方で起きたことがもう片方に反映されるかを見ながら開発します。
マルチプレイは、この「2窓で見比べる」が基本の確認方法です。1窓だけで作っていると、同期できていないことに気づけません。
変数を同期する:ReplicatedとRepNotify
サーバが持つ値を、全クライアントに配る仕組みが Replication(レプリケーション) です。
まず、そのアクター自体を同期対象にします。Class Defaults の Replicates にチェックを入れます。動くアクター(キャラクターなど)なら Replicate Movement も入れると、位置が自動で同期されます。
次に、配りたい変数ごとに設定します。変数の詳細で Replication を選びます。
- Replicated:値が変わると自動で全クライアントに配られる。
- RepNotify:配られると同時に、クライアント側で決まった関数(
OnRep_変数名)が呼ばれる。

HPバーのように「値が変わったら見た目も更新したい」ものは RepNotify が便利です。値が届いた瞬間に OnRep_Health が各クライアントで呼ばれるので、そこでバーを描き直せば、全員の画面が揃います。
大事なのは、Replicated変数を書き換えていいのはサーバだけ ということです。クライアントで書き換えても配られません。
誰が実行しているか:AuthorityとRPC
マルチプレイのBlueprintでは、同じグラフが サーバでもクライアントでも 走ります。だから「今これはどっちで動いているのか」を意識する必要があります。
それを判定するのが Has Authority(または Switch Has Authority)です。Authority を持つ=サーバ、持たない=クライアント、と考えてください。「この処理はサーバだけでやりたい」ときは、Has Authority で分岐します。
では、クライアントで起きた入力(ボタンを押した)を、どうやってサーバに伝えるか。それが RPC(Remote Procedure Call) です。RPCは「向こう側で関数を実行してもらう」仕組みで、3種類あります。
| 種類 | どこで実行される | 用途 |
|---|---|---|
| Server | サーバ上で実行 | クライアントの入力をサーバに伝える |
| Client | 特定のクライアントで実行 | サーバからそのプレイヤーだけに通知 |
| Multicast | サーバと全クライアントで実行 | エフェクトや音を全員に見せる |

もう1つ、RPCには Reliable / Unreliable があります。Reliable は「必ず届く」、Unreliable は「届かないこともあるが軽い」。扉を開けるような、取りこぼすと困る大事な通信は Reliable にします。毎フレーム送るような軽い情報は Unreliable にします。
🚨 Server RPCは「自分が所有するActor」から呼ぶ
ここが最初に必ず引っかかる条件です。Server RPCは、どのActorから呼んでもいいわけではありません。

クライアントからのServer RPCがサーバへ届くのは、呼び出したActorがそのクライアントの所有物であるときだけです。所有者がいないActorから呼んでも、エラーも出ずに黙って捨てられます。
| 呼び出し元 | 所有者 | Server RPCは届くか |
|---|---|---|
| 自分のCharacter / PlayerController | そのクライアント | ⭕ 届く |
| レベルに置いた扉・スイッチ | いない | ❌ 捨てられる |
| 他プレイヤーのCharacter | 別のクライアント | ❌ 捨てられる |
つまり、扉のBlueprintの中で Server_OpenDoor を作っても動きません。「自分が操作しているキャラクター」を経由させる必要があります。
「何も起きないのに、エラーも出ない」という症状のとき、まずここを疑ってください。この記事の実践は、最初からこの 条件を満たす形で組みます。
基本の流れはこうです。クライアントが「自分のキャラから」Server RPCでサーバに頼む → サーバが Replicated 変数を書き換える → 変更が全員に配られる。この一方通行を守れば、同期は素直に動きます。
実践:2人で共有する扉
具体的に、2人で共有する扉を作ります。
協力パズルのスイッチ、サンドボックスの共有ドア、オンライン協力アクションのギミック。「片方が押したら、両方の画面で開く」構造はどれも同じです。ここでは、プレイヤーがボタンに触れると、両方の画面で扉が開く ものを作ります。
動かすとこうなります。2窓のどちらでボタンに乗っても、両方のウィンドウで扉が開きます。逆に、Server RPCを介さず直接変数を書くと、押した側の画面でしか開きません。この対比が、モデル理解の答え合わせになります。

準備
2つのBlueprintを使います。前節の所有権の条件があるので、RPCはプレイヤーのキャラクター側に置きます。
BP_SharedDoor(扉のActor Blueprint):
| 項目 | 設定 |
|---|---|
| Class Defaults | Replicates = true |
変数 bDoorOpen | 型 Boolean、Replication = RepNotify、初期値 false |
| ボタン | Box Collision(触れると反応) |
DoorMesh | Static Mesh(Cube を薄い扉型に)。開く動きはこのメッシュの 相対回転 で表す |
関数 ApplyDoorState | bDoorOpen を見て DoorMesh の相対回転を設定する(見た目の更新はここ1箇所だけ) |
関数 OpenDoor | サーバで bDoorOpen = true にして ApplyDoorState を呼ぶ |
BP_PlayerCharacter(プレイヤーのキャラクター):
| 項目 | 設定 |
|---|---|
カスタムイベント Server_RequestOpenDoor | Replicates = Run on Server 、Reliable をオン。入力に Door(型 BP_SharedDoor) |
bDoorOpen を RepNotify にすると、OnRep_bDoorOpen 関数が自動で作られます。
見た目の更新を
ApplyDoorStateにまとめる理由。OnRep_bDoorOpenは、サーバ自身では呼ばれないことがあります(サーバは自分で値を書いているので、通知が要らない)。そこで 「値を書いた直後」と「OnRep」の両方から同じ関数を呼ぶ 形にしておけば、どちらの環境でも見た目が揃います。「OnRepはクライ アントだけ」と暗記するより、この形にしてしまうほうが安全です。
グラフを組む

つなぎ方は次の流れです。
BP_SharedDoorの On Component Begin Overlap(ボタンに触れたら)で、Other ActorをCast To BP_PlayerCharacterする- そのキャラクターが
Is Locally ControlledかをBranchで確かめる(自分が操作しているキャラのときだけ送る。ここを抜くと、他プレイヤーの分まで送ろうとして無視されます) - True なら、そのキャラクターの
Server_RequestOpenDoor(Door = 扉自身)を呼ぶ。キャラクターは自分の所有物なので、これはサーバへ届きます Server_RequestOpenDoorの中(サーバで実行)で、DoorがIs Validか、そして 扉との距離が近いか を確かめる- 問題なければ
DoorのOpenDoorを呼び、bDoorOpen = trueにしてApplyDoorStateを実行する 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人で共有したい最初のものは何でしょうか。扉でもスコアでも、この一方通行に乗せれば、ちゃんと全員の画面で揃います。