【Unreal Engine】マルチプレイ入門:RPCとRepNotifyで、2人が共有する扉を作る

作成: 2026-07-20最終更新: 2026-09-07

片方では開いた扉が、相手には閉じたまま。UE5のRPCとReplicationを、要求・判断・状態の共有に分けて整理します。Blueprintで上に開く扉を作り、2つの画面で確かめる手順と、所有権やRepNotifyのつまずきを図解します。

自分の画面では扉が開いたのに、友達の画面では閉じたまま。同じゲームに参加していても、1人用と同じようにメッシュを動かすだけでは、相手の画面まで変わるとは限りません。

マルチプレイでは、各プレイヤーの環境でゲームが動いています。その間で「開けてほしい」という要求と、「今は開いている」という状態をやり取りして、見える世界をそろえます。

この記事では、手前の範囲へ入ると上に開く扉を作ります。プレイヤーが頼む → サーバが判断する → 決まった状態を共有する。この流れを、Blueprintと2つのゲーム画面で確かめましょう。

片方が目印へ入ると、ホストとクライアントの両画面で同じ扉が開く

この記事でわかること

  • サーバとクライアントで、処理をどう分担するか
  • RPCによる要求と、Replicationによる状態の共有の違い
  • 自分のCharacterを経由して、共有の扉を開ける手順
  • 2画面で試し、どこまで処理が届いたか調べる方法

Sponsored

同じ世界を、それぞれの環境で動かしている

サーバ は、扉の開閉や敵のHPなど、共有するゲーム状態を決める側です。クライアント は、そこへ接続して遊ぶ側です。入力を受け取り、必要な要求を送り、届いた状態を自分の画面へ反映します。

サーバとクライアントには、それぞれ扉のActorがあります。片方のActorの変数を変更しても、別の環境にあるActorまで直接書き換わるわけではありません。ネットワークを通じて状態を伝える仕組みが必要です。

サーバとクライアントの各世界に扉のActorがあり、bDoorOpenの状態を共有する

クライアントも、カメラやUI、操作に応じた動きなどを処理しています。共有する扉については、誰か1人の見た目を正解にするのではなく、開けてよいかの判断をサーバに集める と考えると整理しやすくなります。

今回は Listen Server(リッスンサーバ) で試します。参加者の1人が、サーバとプレイヤーを兼ねる方式です。この人をホストと呼びます。残りの参加者はクライアントとして接続します。

まずは2画面で歩いてみる

BlueprintのThird Personテンプレートを使います。キャラクターは標準の BP_ThirdPersonCharacter のまま進めます。すでに自分のCharacterを使っている場合は、後のCast先もその名前に読み替えてください。

UEの PIE(Play In Editor) は、エディタからゲームを実行する機能です。Play横のメニューで、次を設定します。項目が見つからなければ「Advanced Settings」からMultiplayer Optionsを開きます。

項目設定意味
Playの表示方法New Editor Window (PIE)ゲームを別ウィンドウで開く
Number of Players22人分のゲームを起動する
Net ModePlay As Listen Server片方をホスト、もう片方をクライアントにする
Number of Playersを2、Net ModeをPlay As Listen Serverにして2画面を起動する

レベルにはPlayer Startを2個、重ならないように置いておきます。Playすると、ホストとクライアントの画面が開きます。ウィンドウのタイトルにあるServer/Clientなどの表示で見分けてください。

片方をクリックしてキャラクターを動かし、もう片方でもそのキャラクターが動くことを確認します。Shift + F1 でマウス操作をエディタへ戻すと、もう一方のウィンドウを選びやすくなります。

ここまでできれば、2つのゲームがつながっています。テンプレートのキャラクター移動はUEの仕組みで同期されますが、これから作る扉の開閉は、自分で共有する状態を決めます。

要求を送るRPC、状態を送るReplication

扉の例では、2つの役割を分けます。

伝えるもの使う仕組み扉での例
向こう側で処理してほしい要求RPCこの扉を開けてほしい
共有したい現在の状態Replication(レプリケーション)この扉は開いている

RPCは、ネットワークの向こう側へ処理を呼び出す仕組みです。今回は Server RPC で、クライアントからサーバへ要求します。サーバは条件を確かめ、問題なければ扉の bDoorOpen をtrueにします。

その値をクライアントへ伝えるのがReplicationです。クライアントは受け取った値を見て、自分の環境にある扉を開きます。

クライアントの要求をServer RPCで送り、サーバが距離を確認して開閉状態をReplicationで返す

Actorと変数の両方を設定する

ActorのClass Defaultsで Replicates をオンにすると、そのActorを複製・同期する対象にできます。さらに、共有する変数ごとにReplicationを設定します。

変数の設定何が起きるか
Replicatedサーバ側の値をクライアント側へ反映する
RepNotify値の反映に加え、変更を受け取ったときに通知関数を呼ぶ

RepNotify は、「値が届いたので見た目も更新しよう」という処理の入口です。bDoorOpen をRepNotifyにすると、OnRep_bDoorOpen という関数が作られます。ここから扉の見た目を更新します。

Blueprintでは、その変数を通常のSetノードで設定した側でもRepNotifyが呼ばれます。今回の実践はこの仕組みを使い、サーバ側と受信側で同じ見た目の更新を行います。

BlueprintのSetによる通知とクライアントの受信通知から、同じApplyDoorStateを呼ぶ

なお、Replicationは、全プレイヤーへ全ての値を毎フレーム送る仕組みではありません。距離などで送信対象が変わり、短時間に何度も変えた値の途中経過が、そのまま全て届くとも限りません。扉には「今、開いているか」という状態を持たせます。

Server RPCは、自分のCharacterを入口にする

クライアントからサーバへRPCを送るには、そのRPCを持つActorが、送信するクライアントに所有されていること が必要です。ここでの 所有 は、通信上「どのプレイヤーにつながっているActorか」という意味です。

自分が操作しているCharacterやPlayerControllerは、このつながりを持ちます。一方、レベルへ置いた共有の扉は、通常、特定のクライアントの所有物ではありません。触れたり、変数に参照を入れたりしても、所有したことにはなりません。

共有扉自身のRPCでは所有条件を満たさず、自分のCharacterのRPCから扉の参照を渡す

実践では、扉に触れたことをきっかけに、自分のCharacterに定義したServer RPC を呼びます。そこへ「開けたい扉」の参照を渡します。参照は、その扉を後から指定するためのものです。

「扉のグラフから呼ぶこと」が問題なのではありません。RPCがどのActorに定義され、その呼び出しのTargetが誰になっているか が重要です。共有扉自身のServer RPCをクライアントから呼ぶと、所有条件を満たさず、サーバで実行されません。

Authorityと、自分が操作するキャラは別

Authority は、そのActorの状態を決める側かどうかを表します。今回の共有扉では、サーバ側の扉がAuthorityを持ちます。Switch Has Authority は、その側だけに処理を絞る分岐です。

一方、Is Locally Controlled は「この環境で操作しているキャラクターか」を調べます。クライアントにもホストにも、自分が操作するキャラクターがいます。後の手順では、この違いを使って要求を送る側と、状態を書き換える側を分けます。

Sponsored

実践:上に開く共有の扉を用意する

手前の範囲へ入ると、扉が上へ移動して通れるようになります。一度開いたら、Playをやり直すまで開いたままにします。まずはこの1つの状態変化を、両画面でそろえましょう。

閉じた板を上へ移動すると、キャラクターが下を通れる

部品を配置する

Actorを親にした BP_SharedDoor を作ります。DefaultSceneRootの子に、次の3つを追加します。どれもDefaultSceneRootの直下に置き、ButtonZoneをDoorMeshの子にはしません。

名前種類設定
DoorMeshStatic MeshCube、Location (0, 0, 100)、Scale (0.2, 2, 2)、Movable、BlockAll
ButtonZoneBox CollisionLocation (-150, 0, 50)、Box Extent (80, 140, 50)、OverlapOnlyPawn、Generate Overlap Eventsオン
ButtonMarkerStatic MeshCube、Location (-150, 0, 5)、Scale (1.6, 2.8, 0.1)、NoCollision
DefaultSceneRootの下にDoorMesh、ButtonZone、ButtonMarkerを並べた部品階層と配置

Cubeはコンテンツブラウザで「Show Engine Content」をオンにすると、/Engine/BasicShapes/Cube から選べます。ButtonMarkerは、踏む場所が分かる目印です。接触の判定はButtonZoneが担当します。

Box Extent は箱の半分の大きさです。ButtonZoneは幅160cm・奥行き280cm・高さ100cmの範囲になります。Overlap は、ぶつかって止める代わりに、範囲へ入ったことを知らせる仕組みです。

Class Defaultsの ReplicatesをオンReplicate Movementをオフ にします。今回はActor全体の移動を送らず、開閉状態から各環境で子のDoorMeshを動かします。DoorMeshの「Component Replicates」と「Simulate Physics」もオフのままにします。

変数と処理の入口を作る

BP_SharedDoorに、次を用意します。関数は「My Blueprint」のFunctionsの+から作ります。

名前種類設定/役割
bDoorOpenBoolean変数初期値false、Replication = RepNotify
ApplyDoorState関数引数なし。状態を見た目へ反映する
OpenDoor関数引数なし。サーバ側で開く状態にする

bDoorOpenをRepNotifyにすると、OnRep_bDoorOpen が自動で追加されます。同じ名前の関数を自分で作る必要はありません。Compileして保存します。

開いているかどうかを、見た目へ反映する

ApplyDoorStateでは、DoorMeshの 相対位置 を変えます。相対位置は、親であるDefaultSceneRootを基準にした位置です。扉Actorをレベルのどこへ置いても、同じ開き方にできます。

bDoorOpenDoorMeshの相対位置結果
false(0, 0, 100)床から高さ200cmの板で通路をふさぐ
true(0, 0, 350)板が250cm上へ移り、下を通れる

状態から位置を選ぶ

ApplyDoorStateを開き、Select Vector を置きます。Aを (0, 0, 350)、Bを (0, 0, 100) にし、bDoorOpenのGetをPick Aへつなぎます。Vector は、ここではX・Y・Zの3つの数値で表した位置です。

bDoorOpenをSelect VectorのPick Aへつなぎ、trueなら高さ350、falseなら100を選ぶ

Pick AがtrueならA、falseならBがReturn Valueから出ます。「開いていたら上の位置、閉じていたら下の位置」を選んでいるだけです。

関数の入口から Set Relative Location へ実行線をつなぎます。Components欄のDoorMeshをグラフへドラッグし、Targetへ。Select VectorのReturn ValueはNew Locationへつなぎ、SweepとTeleportはオフにします。

関数の実行線と、DoorMeshのTarget、選んだVectorのNew LocationをSet Relative Locationへつなぐ

Targetは「動かす部品」、New Locationは「移動先」です。今回はアニメーションせず、決まった位置へ切り替えます。

通知と初期表示から、同じ関数を呼ぶ

OnRep_bDoorOpenを開き、入口から ApplyDoorState を呼びます。さらにEvent Graphの Event BeginPlay からもApplyDoorStateを呼び、開始時の見た目をそろえます。どちらのTargetもSelf、つまりこの扉自身です。

OnRep_bDoorOpenとBeginPlayから、それぞれApplyDoorStateを呼ぶ

ApplyDoorStateは「今の状態に合う位置へ置く」だけなので、同じ値で呼び直しても扉が余分に持ち上がることはありません。HPバーやランプの色も、同じように現在の値から見た目を決めると扱いやすくなります。

自分のCharacterからサーバへ頼む

Character側にRPCを用意する

BP_ThirdPersonCharacterを開き、Event Graphで右クリックしてCustom Eventを追加し、Server_RequestOpenDoor と命名します。イベントを選び、Detailsで次を設定してください。

項目設定
ReplicatesRun on Server
Reliableオン
InputsDoor:BP_SharedDoorのObject Reference
CharacterにServer_RequestOpenDoorを作り、Run on ServerとReliable、Door参照の入力を設定する

Reliable は、受信を確認するまで再送する方式です。今回のような、たまに送る開放要求に使います。所有条件を無視して送れたり、切断後まで届いたりする保証ではありません。連続して大量に送ると待ちがたまるため、Tickから呼ぶような使い方は避けます。

CharacterのClass DefaultsでReplicatesがオンであることも確認し、Compileします。これで扉側からこのイベントを呼べます。

扉に入ったキャラクターを調べる

BP_SharedDoorへ戻ります。ButtonZoneを選び、Detailsのイベント欄から On Component Begin Overlap を追加します。実行出力を Cast to BP_ThirdPersonCharacter へ、Other ActorをそのObject入力へつなぎます。Cast Failedは何もしません。

ButtonZoneのOverlap実行線とOther ActorをCastへつなぎ、入ってきたCharacterを確認する

ここでの Cast は、「入ってきた相手を、このCharacterとして扱えるか」の確認です。成功したときのAs BP Third Person Characterから線を出し、Is Locally Controlled を検索して置きます。キャラクターをTargetに取り、赤いBooleanを返す、実行ピンのないノードを使います。

Castで得たCharacterをIs Locally ControlledのTargetへつなぐ

Cast成功の実行出力をBranchへつなぎ、Is Locally ControlledのReturn ValueをConditionへ。True側から Server_RequestOpenDoor を呼びます。RPCのTargetにはCastのAs BP Third Person Characterを、Door入力には Self ノードをつなぎます。

自分が操作するCharacterなら、そのCharacterのRPCへSelfである扉を渡す

このSelfは、今編集している BP_SharedDoor自身 です。TargetのCharacterに「この扉を開けて」と頼んでいます。BranchのFalse側は何もしません。同じキャラクターが他の環境でも見えているからといって、そこから重ねて要求しないためです。

図は同じOverlapグラフを分けて示しています。CastやIs Locally Controlledを、図の枚数分作る必要はありません。

Sponsored

サーバで確かめ、扉を開ける

指定された扉と距離を確認する

BP_ThirdPersonCharacterのServer_RequestOpenDoorへ戻ります。ここから先は、そのCharacterのサーバ側で実行されます。

入口から、実行ピン付きの Is Valid へつなぎます。イベントのDoor出力をInput Objectへ渡し、Is Not Valid側は何もしません。Is Valid は、相手が空ではなく、使える状態かを調べる確認です。

Server_RequestOpenDoorのDoorをIs Validへ渡し、有効な参照のときだけ進む

続いて Get Distance To を置きます。TargetはSelf、Other ActorはイベントのDoorです。このSelfは サーバ側のCharacter自身。Return Valueを <(Less、数値比較)のAへ入れ、Bを 400.0 にします。

サーバ側のCharacterからDoorまでの距離をGet Distance Toで調べ、400未満か比較する

Is Validの成功側からBranchへ実行線をつなぎ、比較の結果をConditionへ入れます。True側からDoorの OpenDoor を呼びます。Targetに、同じイベントのDoorをつなぎます。False側は何もしません。

参照の確認と距離の条件を通ったら、そのDoorをTargetにOpenDoorを呼ぶ

サーバが持つ位置で、Characterと扉の原点が400cm未満かを調べています。400cmは今回の配置用の値です。範囲の入口から多少進んでも判定できる余裕を取っています。実際のゲームでは、鍵の所持や使用権限など、その扉の条件もここで確かめます。

扉の共有状態を変更する

BP_SharedDoorのOpenDoorを開きます。入口から Switch Has Authority を置き、Authority側だけを Set bDoorOpen(true)へつなぎます。Remote側は何もしません。

OpenDoorのAuthority側だけでbDoorOpenをtrueにする

これで、共有状態を書き換えるのはサーバ側の扉だけになります。Switch Has Authorityは処理をサーバへ送るノードではありません。 今どちら側で実行しているかを見て、進む道を分けるだけです。送信は前のServer RPCが担当しました。

BlueprintのSet bDoorOpenからOnRep_bDoorOpenが呼ばれ、ホスト側の見た目が変わります。値がクライアントへ届くと、そちらでもOnRep_bDoorOpenからApplyDoorStateへ進みます。Setの直後にApplyDoorStateをもう1回追加する必要はありません。

接続全体を振り返ると、次の流れです。

BP_SharedDoor(操作している側)
ButtonZoneのBegin Overlap
  → Cast to BP_ThirdPersonCharacter(Object = Other Actor)
  → Branch(そのCharacterのIs Locally Controlled)
      True → そのCharacterのServer_RequestOpenDoor(Door = この扉)

BP_ThirdPersonCharacter(サーバ側)
Server_RequestOpenDoor(Run on Server / Reliable)
  → Is Valid(Door)
  → Branch(Self.GetDistanceTo(Door) < 400)
      True → Door.OpenDoor()

BP_SharedDoor(サーバ側)
OpenDoor → Switch Has Authority → Authority → Set bDoorOpen(true)
  → BlueprintのSetによるOnRep_bDoorOpen → ApplyDoorState

BP_SharedDoor(受信側)
bDoorOpenの変更を受信 → OnRep_bDoorOpen → ApplyDoorState

クライアント側から先に試す

両方のBlueprintをCompileして保存します。BP_SharedDoorを床へ1個置き、2個のPlayer StartはButtonZoneの外に配置します。ActorのScaleは (1, 1, 1) にし、扉まで500cm程度離しておくと試しやすくなります。

  1. 2人・Listen ServerでPlayします。両画面で扉が閉じていることを確認します。
  2. クライアントの画面 を操作し、床の目印へ入ります。両画面で扉が上がれば成功です。
  3. 扉の下を通り、両環境で通行できることを確認します。
  4. Playを止めて、もう一度開始します。今度はホストが目印へ入り、両画面で開くことを確認します。

先にクライアントを試すのは、サーバへ要求を送る経路を確認するためです。ホストだけで試すと、サーバ側でそのまま実行できてしまい、クライアントからの送信ミスを見落とすことがあります。

届く途中を、一段ずつ調べる

動かないときは、Print StringとOutput Logで確認します。RPCを呼ぶ直前、サーバ側のIs Validの成功後、OpenDoorのAuthority側へ一時的にログを入れると、止まった場所を絞れます。PIEではログのServer/Client表記にも注目してください。同じエディタ内で複数のゲームを動かすと、別の画面にもログが見える場合があります。表示されたウィンドウだけで、実行した側を判断しないようにします。

症状確認する場所
目印へ入っても反応しないButtonZoneとCharacterのCapsuleのGenerate Overlap Events、Cast先、開始位置
ホストからだけ開けられるRPCがCharacterにあるか、Targetが触れたCharacterか、Run on Serverか
RPCは届くが開かないDoorが有効か、距離が400未満か、OpenDoorのTargetがDoorか
ホストでは開くが、クライアントでは閉じている扉のReplicates、bDoorOpenのRepNotify、OnRepからApplyDoorStateへの接続
両方で状態はtrueなのに動かないDoorMeshのMovable、Set Relative LocationのTargetとNew Location
2人目を試しても変化がない一度開くとそのままの例。Playをやり直して試す

自分だけ変えるとどうなるか

共有状態を送る意味も、短い実験で確かめられます。いったんPlayを止め、扉側のBranch TrueからRPCへの線を外し、代わりに Set bDoorOpen(true)へつなぎます。

新しいPlayで クライアントだけ を目印へ入れると、その環境ではSetによるRepNotifyで扉が開きますが、サーバへ値は送られません。ホストの扉は閉じたままです。確認後は、追加したSetを外してRPCの接続へ戻してください。

クライアントだけで変数をSetすると、自分の扉は開くがホストの扉は閉じたままになる

RepNotifyが呼ばれたことと、相手へ変更が届いたことは別です。この違いが分かれば、「自分だけ開いた」という症状を調べる手掛かりになります。

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

残したい状態は、変数で持ちます。 扉が開いているという状態を持てば、後からその扉が同期対象になったクライアントにも現在の状態を渡せます。開く演出だけをMulticastで送っても、そのとき受け取れなかった相手へ過去の演出が再生されるわけではありません。

RPCには、ほかの送り先もあります。 Client RPCは、そのActorを所有するクライアントへ通知します。Multicast RPCは、サーバから呼ぶとサーバ自身と、そのActorが同期対象となる接続先で実行されます。クライアントからMulticastを呼んでも、全員への放送にはなりません。

踏んだままにしなくても、今回は開きます。 範囲から出たら閉じる仕組みへ広げるなら、2人が同時に乗った場合も考えます。単に誰か1人のEnd Overlapで閉じると、もう1人が乗っていても閉じてしまいます。

滑らかな開閉は、その後で足せます。 今回は位置を切り替えましたが、Timelineで現在の状態に向かって動かす方法もあります。まずは開閉が共有されることを確認してから、演出を加えましょう。

移動の同期は、別の仕組みも関わります。 Replicate Movementは主にActorのルートの移動を扱います。子メッシュの相対位置や任意の変数が、全てこのチェックだけで送られるわけではありません。Characterには専用の移動同期もあります。

同じPCで動いた次は、通信の条件も変えて試します。 遅延や通信の欠落を加えたテスト、ホストが遊ばないDedicated Serverでのテストをすると、2窓の最小構成では見えなかった問題を調べられます。専用サーバは、プレイヤーの代わりにゲーム進行だけを担当する構成です。

インターネット越しの参加は、次の段階です。 今回はPIEが接続先を用意してくれました。友達が部屋を探して参加する流れは、セッションとオンライン接続の記事で整理します。共有スコアなどの置き場所はGameMode・GameState・PlayerStateも参考になります。

まとめ

共有の扉では、自分のCharacterからServer RPCで要求し、サーバが扉の状態を決めました。クライアントは、その状態をReplicationで受け取り、RepNotifyから見た目を更新します。

自分の画面だけで成功を判断せず、まずクライアントから操作して、ホストと両方の状態を比べてみてください。「誰が頼み、どこで決め、誰が結果を受け取ったか」を追うことが、次の共有ギミックを作る土台になります。

参考:ネットワークの概要PIEのマルチプレイ設定変数のReplicationとBlueprintのRepNotifyRPCの実行条件

Unreal Engine このセクションのノート98