2019-05-14に投稿

【Unity】Photon2でチームマッチメイキングをする方法【Photon2】

読了目安:16分

2019/3/13

制作中のゲーム 『Play the Fox』マッチングの機能を作った。

今回はその実装方法を書きます。

実装した内容は以下

  • 完全ランダムなマッチメイキング

  • 1vs1及び2vs2及び8人乱闘のゲームモードに対応

  • 2vs2ではランダムにチームを組んでマッチング

今回は、UnityPhoton2を使う。...無料だから。

仕組みを解説

20190313114119.png

雑な図解。

これは2vs2だけど、他もほぼ同じ仕組み。

  1. 最初に味方を見つけるためのマッチングをする。

  2. 必要人数見方が見つかったらそれをチームとする。

  3. そのチームのリーダーが相手を見つけるマッチングをする。

  4. プレイヤーが必要人数揃ったらゲーム開始

といった流れ。

実際のスクリプト

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using ExitGames.Client.Photon;
using Photon.Realtime;
using Photon.Pun;
using Enums;
using UnityEditor;

public class PTFNetworkManager : MonoBehaviourPunCallbacks
{
    private static PTFNetworkState state = PTFNetworkState.Idle;

    public  string leaderUserID;
    private string[] teamUserIDs;

    public static PTFNetworkState State
    {
        get { return state; }
    }

    void Update()
    {
        switch (state)
        {
            case PTFNetworkState.FindAlly:
                if (PhotonNetwork.InRoom)
                {
                    if (PhotonNetwork.CurrentRoom.PlayerCount == PhotonNetwork.CurrentRoom.MaxPlayers && PhotonNetwork.MasterClient.UserId != null)
                    {
                        //人数が揃ったらリーダーとチームメンバーを設定
                        leaderUserID = PhotonNetwork.MasterClient.UserId;
                        SetTeamUserIDsWithCurrentRoom();
                        //準備完了フラグを立てる
                        var properties  = new ExitGames.Client.Photon.Hashtable();
                        properties.Add( "foundAlly", true );
                        PhotonNetwork.LocalPlayer.SetCustomProperties(properties);
                        //ステート切り替え
                        state = PTFNetworkState.FoundAlly;
                    }
                }

                break;
            case PTFNetworkState.FoundAlly:
                //全員が設定を終えたら退室
                if (CheckAllPlayerFoundAlly())
                {
                    Debug.Log("Found Ally!");
                    PhotonNetwork.LeaveRoom();
                    PhotonNetwork.JoinLobby();
                    state = PTFNetworkState.FindOpponent;
                }
                break;
            case PTFNetworkState.FindOpponent:
                //リーダージャナイバアイ
                if (leaderUserID != PhotonNetwork.LocalPlayer.UserId)
                {
                    PhotonNetwork.FindFriends(new string[1]{ leaderUserID });
                }

                if (PhotonNetwork.InRoom)
                {
                    //部屋に入ったら準備完了
                    Debug.Log("Ready! Waiting others or start!");
                    state = PTFNetworkState.Ready;
                }

                break;
            case PTFNetworkState.Ready:
                //人数が揃ったら開始するとか。
                break;
        }
    }

    public override void OnPlayerPropertiesUpdate(Player target, ExitGames.Client.Photon.Hashtable changedProps)
    {
        Debug.Log("target is " + target + "\n changed props are" + changedProps);
    }

    /// <summary>
    /// 現在の設定でマッチメイキングを開始
    /// </summary>
    public static void StartMatchMaking()
    {
        //ステートは味方を探す
        state = PTFNetworkState.FindAlly;
        ConnectToMaster();
    }

    private static void ConnectToMaster(){
        string gameMode = PlayerPrefs.GetString("GameMode", "Skirmish");

        if (!PhotonNetwork.IsConnected)
        {
            PhotonNetwork.ConnectUsingSettings();
            Debug.Log("Connecting to master...");
        }else{
            PhotonNetwork.JoinLobby();
        }
    }

    public override void OnConnectedToMaster(){
        PhotonNetwork.JoinLobby();

        Debug.Log("Joining Lobby...");
    }

    /// <summary>
    /// ロビーに入ったらとりあえず見方を探す
    /// </summary>
    public override void OnJoinedLobby()
    {
        Debug.Log("Connected Master Server!");

        if (state == PTFNetworkState.FindAlly)
        {
            FindAlly();
        }else if (state == PTFNetworkState.FindOpponent)
        {
            FindOpponent();
        }
    }

    private void FindAlly()
    {        
        string gameMode = PlayerPrefs.GetString("GameMode", "Skirmish");

        //ここから見方を探す
        //味方のプレイヤーの人数
        byte expectedMaxPlayers = 1;
        //想定プレイヤー
        //GameMode(gm)とFindType(ft)でソート
        ExitGames.Client.Photon.Hashtable expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "skirmish" }, {"ft", "ally"} };

        switch (gameMode)
        {
            case "Skirmish":
                expectedMaxPlayers = 1;
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "skirmish" }, {"ft", "ally"} };
                break;
            case "Solo":
                expectedMaxPlayers = 1;
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "solo" }, {"ft", "ally"}  };
                break;
            case "Duo":
                expectedMaxPlayers = 2;
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "duo" }, {"ft", "ally"}  };
                break;
        }

        PhotonNetwork.JoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers);
    }

    /// <summary>
    ///相手を探す
    /// リーダーのみ利用。
    /// </summary>
    private void FindOpponent()
    {
        Debug.Log("leaderUserID : " + leaderUserID);
        Debug.Log("myUserID : " + PhotonNetwork.LocalPlayer.UserId);
        if (leaderUserID != PhotonNetwork.LocalPlayer.UserId) return;
        Debug.Log("I am leader! I start finding opponent!!");
        string gameMode = PlayerPrefs.GetString("GameMode", "Skirmish");

        //ここから相手を探す
        //全体のプレイヤーの人数
        byte expectedMaxPlayers = 1;
        //想定プレイヤー
        //GameMode(gm)とFindType(ft)でソート
        ExitGames.Client.Photon.Hashtable expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "skirmish" }, {"ft", "opponent"} };

        switch (gameMode)
        {
            case "Skirmish":
                expectedMaxPlayers = 8;
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "skirmish" }, {"ft", "opponent"} };
                break;
            case "Solo":
                expectedMaxPlayers = 2;
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "solo" }, {"ft", "opponent"}  };
                break;
            case "Duo":
                expectedMaxPlayers = 4;
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "duo" }, {"ft", "opponent"}  };
                break;
        }

        //ランダムマッチメイキング
        PhotonNetwork.JoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers, MatchmakingMode.FillRoom, TypedLobby.Default, null, teamUserIDs);

        //ステートは相手を探す
        state = PTFNetworkState.FindOpponent;
    }

    /// <summary>
    /// ルーム入室失敗時、ステートにあった部屋を作成する
    /// </summary>
    /// <param name="returnCode"></param>
    /// <param name="message"></param>
    public override void OnJoinRandomFailed(short returnCode, string message){
        Debug.Log("Faild to Join Room!");

        Debug.Log(message);

        string gameMode = PlayerPrefs.GetString("GameMode", "Skirmish");

        //ルームオプションの設定
        string findType = "";
        if (state == PTFNetworkState.FindAlly)
        {
            findType = "ally";
        }else if (state == PTFNetworkState.FindOpponent)
        {
            findType = "opponent";
        }
        byte expectedMaxPlayers = 1;
        ExitGames.Client.Photon.Hashtable expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "skirmish" } };

        switch (gameMode)
        {
            case "Skirmish":
                if (state == PTFNetworkState.FindAlly)
                {
                    expectedMaxPlayers = 1;
                }else if (state == PTFNetworkState.FindOpponent)
                {
                    expectedMaxPlayers =8;
                }
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "skirmish" }, {"ft", findType} };
                break;
            case "Solo":
                if (state == PTFNetworkState.FindAlly)
                {
                    expectedMaxPlayers = 1;
                }else if (state == PTFNetworkState.FindOpponent)
                {
                    expectedMaxPlayers =2;
                }
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "solo" }, {"ft", findType} };
                break;
            case "Duo":
                if (state == PTFNetworkState.FindAlly)
                {
                    expectedMaxPlayers = 2;
                }else if (state == PTFNetworkState.FindOpponent)
                {
                    expectedMaxPlayers =4;
                }
                expectedCustomRoomProperties = new ExitGames.Client.Photon.Hashtable() { { "gm", "duo" }, {"ft", findType} };
                break;
        }

        RoomOptions roomOptions = new RoomOptions();
        roomOptions.CustomRoomPropertiesForLobby = new string[]{"gm", "ft"};
        roomOptions.CustomRoomProperties = expectedCustomRoomProperties;
        roomOptions.MaxPlayers = expectedMaxPlayers;
        roomOptions.PublishUserId = true;

        Debug.Log(roomOptions.CustomRoomProperties);

        //ルームの作成
        PhotonNetwork.CreateRoom("", roomOptions, null, teamUserIDs);

        Debug.Log("Created Room!");
    }

    public override void OnJoinedRoom()
    {
        Debug.Log("Joined Room!");
        Debug.Log(PhotonNetwork.CurrentRoom.CustomProperties);

        if (state == PTFNetworkState.FindAlly)
        {
            var properties  = new ExitGames.Client.Photon.Hashtable();
            properties.Add( "foundAlly", false );
            PhotonNetwork.SetPlayerCustomProperties(properties);
        }
    }

    private void SetTeamUserIDsWithCurrentRoom()
    {
        teamUserIDs = new string[PhotonNetwork.PlayerListOthers.Length];
        for (int i=0; i<PhotonNetwork.PlayerListOthers.Length; i++)
        {
            teamUserIDs[i] = PhotonNetwork.PlayerListOthers[i].UserId;
        }
    }

    public override void OnFriendListUpdate(List<FriendInfo> friendList)
    {
        foreach (FriendInfo friendInfo in friendList)
        {
            if (friendInfo.UserId == leaderUserID && state == PTFNetworkState.FindOpponent)
            {
                PhotonNetwork.JoinRoom(friendInfo.Room);
            }
        }
    }

    private bool CheckAllPlayerFoundAlly()
    {
        Player[] playerList = PhotonNetwork.PlayerList;
        foreach (Player player in playerList)
        {
            if (player.CustomProperties["foundAlly"] == null)
            {
                return false;
            }

            if (!(bool)player.CustomProperties["foundAlly"])
            {
                return false;
            }
        }

        return true;
    }
}

雑なコード解説(需要あれば書き足します)

StartMatchMaking() を呼び出すとマッチングが開始する。

Photonに関してだけ使っている順番に書くと、

PhotonNetwork.ConnectUsingSettings();

PhotonNetwork.JoinLobby();

ゲームモードが同じ、見方探し中のルームを探す。

PhotonNetwork.JoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers);

参加可能なルームが無い場合

PhotonNetwork.CreateRoom("", roomOptions, null, teamUserIDs);

味方が全員揃ったら

PhotonNetwork.LeaveRoom();

PhotonNetwork.JoinLobby();

ゲームモードが同じ、相手探し中のルームを探す。

PhotonNetwork.JoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers, MatchmakingMode.FillRoom, TypedLobby.Default, null, teamUserIDs);

参加可能なルームが無い場合

PhotonNetwork.CreateRoom("", roomOptions, null, teamUserIDs);

ルームに入室したらステートをReadyに変えてマッチング完了

後は人が揃うのを待ったり強制的にゲーム始めたりお好きにどうぞ。

あとがき

これは完全にランダムでしかない

今後、フレンドとチームを組んだり出来るようにする予定。

その辺に関しては先にフレンド関係のシステムを作ってからやるのでもう少し先になりそう。

やり方としては、最初の味方探しのJoinRandomRoomに条件を加えるだけのはず。

Originally published at questgames.hatenablog.com

QuestGames

全ては5年前に始まった。 QuestGamesはゲームを作る3人組です。 僕らの物語は現在執筆中です。ブログをご覧ください。

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント