3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MPPMでPlayerの起動順を制御する

Posted at

MPPM(Multiplayer Play Mode)とは

MPPMは、1つのUnityエディタ上で複数のプレイヤーを同時に起動・実行できる機能です。
個別にビルドを行うことなく、ネットワーク同期や接続挙動を即座に検証できるため、マルチプレイヤー開発の検証効率を大きく向上させます。

開発初期からデバッグまで活用できる、実践的で信頼性の高いローカルテスト手法です。

やりたいこと

MPPMを使ってPlayerを4つ起動させる
起動させるシーンはMyGameTitleScene

  • 条件
    • 同時に4つ起動せず、mainEdtior(Player1)から起動しPlayer2,Player3,Player4の順に起動する
    • MyGameTitleSceneで実行される、アセットダウンロード処理完了後、次のPlayerを起動させる

image.png

環境

Unity6000.3.2f
MPPM v2.0.1
MackBook Pro(M4 Max) macOS 15.5

Run In Background

まずは、MPPMで使用するのに必須?の Run In Backgroundにチェックを入れましょう。
Unityメニュー Edit -> Project Settings...から変更できます。

スクリーンショット 2025-12-21 17.18.31.png

シナリオの作成

シナリオとは起動する各Playerの設定(Tagなど)をまとめたもの。

Unityメニュー Window -> Multiplayer -> Play Mode Scenarioからシナリオを作成する。

Initial SceneにMppmStartSceneを設定する。(後で説明)

Additional Editor InstancesでPlayer2,3,4を追加。

各EdtiorのStream Logs To Main Editorにチェック入れておくと、MainEditorにDebug.Logに出力が転送されて便利。

Log Colorも各Editor毎に変更しておくと見やすい。

スクリーンショット 2025-12-22 17.23.06.png

シナリオのInitial Scene

作成したシナリオは再生ボタンの横のボタンで選択ができます。
その状態で再生ボタンをクリックすると、Initial Sceneに設定したシーンが最初にロードされます。

スクリーンショット 2025-12-22 17.24.41.png

Initial SceneでMyGameTitleSceneのロードタイミングを制御する

MppmStartScene( Initial Scene )にはMppmStartコンポーネント(MppmStart.cs)が設定されており、MyGameTitleSceneのロードタイミングを制御します。

MppmStartScene.jpg

MppmStart.cs
public class MppmStart : MonoBehaviour
{
        :
    private void Start() => StartAsync().Forget();
    private async UniTask StartAsync()
    {
        // 初期化処理
        Initialize();
        // 自分の順番が来るまで待機
        await WaitForMyTurnAsync();
        // シーン遷移
        MoveMyGameTitleScene();
    }
        :
 }

WaitForMyTurnAsync 自分の順番が来るまで待機

自分の順番が来るまでの判定は、シンプルでLibrary/VP/MppmStart/START_P{PlayerIndex}のファイルが作成されるまで待機します。

MainEditor(Player1)の場合は待機せず、即シーン遷移させています。

Library/VP/MppmStart/START_P2 // Player2を起動トリガー用ファイル
Library/VP/MppmStart/START_P3 // Player3を起動トリガー用ファイル 
Library/VP/MppmStart/START_P4 // Player4を起動トリガー用ファイル

UnityEditor停止時の解放処理(Release)で起動トリガー用ファイルは削除していますが、コード説明は省略

MppmStart.cs
public class MppmStart : MonoBehaviour
{
        :
    private async UniTask WaitForMyTurnAsync()
    {
        // MainEditorの場合は待機しない(MainEditorはPlayerIndex==1)
        if (MppmHelper.IsMainEditor) return;
        
        // 自分のPlayerIndexの順番が来るまで待機する
        var startPlayerFile = Path.Combine(VpMppmStartDirPath, $"START_P{MppmHelper.PlayerIndex}");
        Debug.Log($"Waiting for my turn. Checking for file: {startPlayerFile}");
        while (true)
        {
            // VP/MppmStart/"START_P{MppmHelper.PlayerIndex}"ファイルが存在するか確認する
            if (File.Exists(startPlayerFile))
            {
                Debug.Log($"It's my turn! Found file: {startPlayerFile}");
                return;
            }
            // 500ms待機して再度確認
            await UniTask.Delay(500);
        }
    }
        :
 }

MppmHelper

MPPMのCurrentPlayerでMainEditor判定は可能ですが、PlayerIndexは取得できないので作成。

MppmHelper.cs
#nullable enable
public static class MppmHelper
{
        :
    private const string NameCommandLineArg = "-name";
    private static int? _playerIndex;

    public static int PlayerIndex
    {
        get
        {
            if (_playerIndex.HasValue) return _playerIndex.Value;
            
            // MainEditorの場合はplayerIndexは1とする
            if (IsMainEditor) 
            {
                _playerIndex = 1;
                return _playerIndex.Value;
            }
            
            // Environment.GetCommandLineArgsの -name "Player 2" プレイヤー名からIndexを取得する
            var result = HasCommandLineArgument(NameCommandLineArg);
            var playerName = result.nextArg;
            if (playerName == null || playerName.StartsWith("Player ") == false)
            {
                _playerIndex = -1;
                return _playerIndex.Value;
            }
            
            var indexStr = playerName.Substring("Player ".Length);
            if (int.TryParse(indexStr, out int index))
            {
                _playerIndex = index;
                return _playerIndex.Value;
            }
            
            _playerIndex = -1;
            return _playerIndex.Value; 
        }
    }
        :
}

起動トリガー用ファイルの作成

MyGameTitleSceneがロードされると、MyGameTitleコンポーネントの処理が実行されます。

アセットダウンロード完了後に MppmStart.StartNextPlayerメソッドを呼び出すことで、次のPlayer用の起動トリガーファイルが作成されます。

MyGameTitle.cs
public class MyGameTitle : MonoBehaviour
{
        :
    private void Start() => StartAsync().Forget();
    
    private async UniTask StartAsync()
    {
        // アセットのダウンロード処理
        await DownloadHeavyAssetDownloadAsync();
#if UNITY_EDITOR
        // 次のプレイヤーの処理を開始させる
        MppmStart.StartNextPlayer();
#endif
    }
        :    
}   
MppmStart.cs
public class MppmStart : MonoBehaviour
{
        :
    public static void StartNextPlayer()
    {
        if (IsStarted == false) return;
        
        // 次のPlayerを開始させるために
        // VP/MppmStart/"START_P{MppmHelper.PlayerIndex + 1}"ファイルを作成
        var nextPlayerIndex = MppmHelper.PlayerIndex + 1;
        var startPlayerFile = Path.Combine(VpMppmStartDirPath, $"START_P{nextPlayerIndex}");
        if (File.Exists(startPlayerFile)) return;
        
        var dir = Path.GetDirectoryName(startPlayerFile);
        if (Directory.Exists(dir) == false) Directory.CreateDirectory(dir!);
        File.WriteAllText(startPlayerFile, "START");
    }
        :
}       

シナリオ選択してPlay!!

スクリーンショット 2025-12-22 17.24.41.png

画面収録 2025-12-22 19.31.58.gif

無事意図した動作をすることができました。

まとめ

ロードタイミングの制御をどうするのか悩みました。
最初はmainEditorをサーバーにした方法も検討しましたが、なんだか大げさすぎるのでやめ、最終的にシンプルなファイルによる制御になりました。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?