게임 방 접속 후 / 플레이어 / 테이블

  1. 제 게임상, 게임방에 접속하고 스킬을 고르는데 이 스킬을 고르는 시간의 리밋이 20초입니다. 그래서 [SerializeField] private float SkillChoiceTime; 을 선언 하고, 업데이트 문에
            TimeText.text = "" + (int)SkillChoiceTime + "초";
            if ((int)SkillChoiceTime > 0)
            {
                SkillChoiceTime -= Time.deltaTime; //<< 이 코드
                Debug.Log("스킬 초 세리는중...../...");
            }

이렇게 했는데요. 로그를 보니 스킬초 세리는 중 디버그는 잘 찍혔지만 SkillChoiceTime이 델타타임에 의해 내려가는 코드가 작동되지 않습니다.

  1. 플레이어
    public GameObject playerPrefeb;

스킬 선택이 끝나면
Instantiate(playerPrefeb, new Vector3(Random.Range(-3.62f, 16f), -1.55f, 0), Quaternion.identity); 으로 플레이어를 생성했는데, 혹시 이거 말고 다른 좋은 방법이 있으면 알려주시면 감사하겠습니다. 혹은 저 방법으로 플레이어를 생성하면 PC1, PC2의 각각의 플레이어가 잘 생성되는지도 궁금합니다.

그리고 플레이어간의 동기화는 어떻게 하는지도 잘 궁금하네요. 바이너리 데이터 뭐 어쩌고 하는데 구체적으로 알려주시면 감사하겠습니다. 예제게임을 참고하려고 했는데 버튼의 OnClick함수가 해당 스크립트엔 존재하지 않거나 직접 설명이 필요한 부분도 많았 던 것 같습니다.

  1. 테이블에 관해서는 상세한 질문이 아니라, 테이블이 무엇이고 어떤 역할을 하는지 궁금합니다

안녕하세요 개발자님.

  1. 해당 경우는 그 외 선언 및 오브젝트를 잘 몰라 정확한 답변을 드릴 수 없지만, Update문이 오브젝트에 없어 작동을 하지 않거나 skillCouceTime의 값을 설정을 해주지 않아 발생하는 문제로 보입니다. Debug.Log(skillChoiceTime)을 체크해가시면서 테스트하는것을 추천드립니다.

  2. 네 해당 방법으로 생성을 하셔도 무관합니다. 뒤끝매치는 실시간으로 데이터를 보내는 역할만 수행하므로, 그 외 클라이언트에 제한을 두는 부분은 없으니 싱글 게임 만드셨던 것처럼 만드시면 됩니다.
    캐릭터의 생성이 무조건 위치 고정이면 각자 클라이언트에서 PC1,2를 만들면 되지만 랜덤일 경우에는 슈퍼게이머가 생성한 후에 위치를 다른 클라이언트들에게 알려주고, 다른 클라이언트들은 그 메세지를 받은 후에 해당 값에 맞게 유저를 생성하도록 작업을 하셔야 합니다.

플레이어의 동기화의 경우, 뒤끝 매치를 통해 보낸 데이터를 모두 관리하는 슈퍼게이머를 하나 지정하여 데이터를 모든 유저에게 보내는 것이 딜레이 감소가 용이합니다.

슈퍼게이머가 유저1, 그 외 유저가 2,3,4 라면 유저 2가 총을 쐈을 경우 유저 2의 위치, 총알 정보, 총알 방향, 어떤 행동을 했는지(총알 발사) 를 Json으로 만든 후, 바이너리 데이터로 변환하여 SendDataToInGameRoom로 전송하면 됩니다.
그러면 모든 유저는 OnMatchRelay에서 데이터를 받게 되지만 슈퍼게이머를 제외한 다른 유저에게서 온 메세지는 무시합니다. 슈퍼게이머는 다른 유저가 보낸 데이터를 읽고 자신의 클라이언트에서 데이터를 가공하여 다른 유저들에게 다시 전달합니다. 다른 유저들은 슈퍼게이머에게서 온 메세지에기에 이 데이터를 열어 자신의 클라이언트에 적용합니다.

  1. 테이블은 뒤끝 매치가 아닌 뒤끝 베이스에 해당하는 기능으로, 게임정보를 저장하는 기능입니다. 만약 리그오브레전드처럼 자신이 돈을 주고 구매하여 영구적으로 가지고 있을 수 있는 아이템 또는 캐릭터가 존재하거나, 다른 FPS류 게임처럼 자신이 산 무기를 사용할 수 있는 경우, 이 구매한 아이템을 저장할 수 있어야하기 떄문에 이런 데이터 저장에 사용합니다.
  • 아이템 구매 → 테이블에 구매 데이터 저장 → Equip 테이블에 longSword 데이터 추가
  • 게임 시작 시 아이템 불러오기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using BackEnd;
using BackEnd.Tcp;

    public GameObject SkillCanvas;
    public GameObject frontCanvas;

    public Text TimeText;
    public float SkillChoiceTime;

    public bool IsStart;
    public GameObject playerPrefeb;

    bool a = true;
    private void Awake()
    {
        frontCanvas.SetActive(false);
        SkillCanvas.SetActive(true);
        IsStart = false;
        SkillChoiceTime = 20f;
    }

    private void Update()
    {
        TimeText.text = "" + (int)SkillChoiceTime + "초";
        if ((int)SkillChoiceTime > 0)
        {
            SkillChoiceTime -= Time.deltaTime;
            Debug.Log(SkillChoiceTime);
        }
        if ((int)SkillChoiceTime <= 0 && a)
        {
            SkillCanvas.SetActive(false);
            Instantiate(playerPrefeb, new Vector3(Random.Range(-3.62f, 16f), -1.55f, 0), Quaternion.identity);
            a = false;
     }


1번에 대한 스크립트입니다.

해당 로직이라면 정상적으로 돌아갈 것으로 보입니다.

Update문이 정상적으로 호출되고 있는지 중단점 혹은 Debug.Log()를 통해 확인해주세요.

Time.TimeScale = 1f; 를 적용하니 되었네요. 감사합니다
추가질문들좀 조심스럽게 여쭤봅니다.

  1. 윗글 2번에 대한 구체적인 예시가 필요할 것 같습니다. Json 생성부분도 그렇고 윗글에서
        if ((int)SkillChoiceTime <= 0 && a)
        {
            SkillCanvas.SetActive(false);
            Instantiate(playerPrefeb, new Vector3(Random.Range(-3.62f, 16f), -1.55f, 0), Quaternion.identity);
            a = false;
     }

이렇게 했더니 동기화가 되지 않고 그냥 각 PC 개개인의 플레이어가 생성됐습니다 (공유X)
동기화의 구체적인 방법과 예제문이 있을까요? 예제게임을 참고해도 잘 모르겠습니다.

또한 닉네임도

public Text NameText;

void Update() {

   NameText.text = Backend.UserNickName;
}
했더니 불러와지지 않습니다 닉네임을 불러오는 방법도 있을까요?
  1. 게임 로딩화면부분도 예제게임의 LoadGameUI를 참고해서
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using BackEnd;
using BackEnd.Tcp;

public class LoadTheGame : MonoBehaviour
{
    public GameObject[] userObject = new GameObject[10];

    [SerializeField]
    private int numOfClient = -1;
    private const string MMR_RECORD = "MMR : {0}";
    private const string POINT_RECORD = "POINT : {0}";
    private const string NUM_RECORD = "매치수 : {0}";
    private const string WIN_RECORD = "승 : {0}";
    private const string DEFEAT_RECORD = "패 : {0}";

    void Start()
    {
        Time.timeScale = 1f;
        Invoke("JoinGame", 7f);
        var matchInstance = BackEndMatchManager.GetInstance();
        if (matchInstance == null)
        {
            return;
        }

        numOfClient = matchInstance.gameRecords.Count;

        if (numOfClient <= 0)
        {
            Debug.LogError("numOfClient가 0이하입니다.");
            return;
        }
        if (numOfClient == 2)
        {
            userObject[3].SetActive(false);
            userObject[4].SetActive(false);
            userObject[5].SetActive(false);
            userObject[6].SetActive(false);
            userObject[7].SetActive(false);
            userObject[8].SetActive(false);
            userObject[9].SetActive(false);
            userObject[10].SetActive(false);
        }
        if (numOfClient == 5)
        {
            userObject[6].SetActive(false);
            userObject[7].SetActive(false);
            userObject[8].SetActive(false);
            userObject[9].SetActive(false);
            userObject[10].SetActive(false);
        }

        byte index = 0;
        foreach (var record in matchInstance.gameRecords.OrderByDescending(x => x.Key))
        {
            var name = record.Value.m_nickname;
            string score = string.Empty;

            if (matchInstance.nowMatchType == MatchType.MMR)
            {
                score = string.Format(MMR_RECORD, record.Value.m_mmr);
            }
            else if (matchInstance.nowMatchType == MatchType.Point)
            {
                score = string.Format(POINT_RECORD, record.Value.m_points);
            }

            var data = userObject[index++].GetComponentsInChildren<Text>();
            data[0].text = name;
            data[1].text = score;
            data[2].text = string.Format(NUM_RECORD, record.Value.m_numberOfMatches);
            data[3].text = string.Format(WIN_RECORD, record.Value.m_numberOfWin);
            data[4].text = string.Format(DEFEAT_RECORD, record.Value.m_numberOfDefeats);

            if (matchInstance.nowModeType == MatchModeType.TeamOnTeam)
            {
                var teamNumber = matchInstance.GetTeamInfo(record.Key);
                var mySession = Backend.Match.GetMySessionId();
                var myTeam = matchInstance.GetTeamInfo(mySession);

                if (teamNumber == myTeam)
                {
                    data[0].color = new Color(0, 0, 1);
                }
                else
                {
                    data[0].color = new Color(1, 0, 0);
                }
            }
        }

    }
    public void JoinGame() => SceneManager.LoadScene("game");
}

이렇게 구성했는데 잘 안되는것 같네요…

먼저 뒤끝매치는 그저 실시간으로 같은방 안에 있는 유저끼리 데이터를 공유하는 기능만을 제공하고 있으며, 게임에 대해서는 영향을 주지 않습니다.

캐릭터가 생성되거나, 이동하거나, 총알을 쓰거나 하는 모든 동작을 일치시키려면 모든 행동에 대해 데이터를 전송해야합니다.

많이들 사용하시는 json의 경우, 로컬에 데이터를 저장할때 사용하지만 뒤끝 매치 같은 경우에는 클래스를 json 형태로 파싱한 후, 파싱한 json을 byte로 만들어 전송합니다.

아래는 유저들의 현재 위치를 보내는 함수입니다.

public class Message
{
   string type;
   public ​string sessionId; // 유저들을 구별하기 위한 값
   Message(string _type, string _sessionId)
   { 
      type = _type;
      seesionId = _sessionId;
   }
}

public class PlayerCreateClass : Message("PlayerCreate")
{
  ​public Vector3 createPos;
}

void Start()
{
  
  ​Backend.Match.OnMatchRelay(args) += { // 받은 데이터의 메세지 읽기
 
  ​var strByte = Encoding.Default.GetString(args.BinaryUserData);
  ​Message msg = JsonUtility.FromJson<Message>(strByte);

  if(msg.type == "PlayerCreate") // 메세지의 type이 캐릭터생성 관련일 경우에 공유받은 위치를 기반으로 캐릭터 생성
  {
  ​   PlayerCreateClass player = JsonUtility.FromJson<PlayerCreateClass>(strByte);
     Instantiate(playerPrefeb, player.createPos, Quaternion.identity);
  }

 ​};

}

void Update()
{
   if ((int)SkillChoiceTime <= 0 && a)
        {
            SkillCanvas.SetActive(false);
            if(isHost == true) // 호스트일 경우만 함수 실행
            {
               PlayerCreate();
            }
            a = false;
     }
} 

//랜덤으로 캐릭터 생성 위치를 지정하여 모두에게 공유
public void PlayerCreate()
{
  PlayerCreateClass player = new PlayerCreateClass();
  player.createPos = new Vector3(Random.Range(-3.62f, 16f), -1.55f, 0);

  ​var jsonData = JsonUtility.ToJson(player); // 클래스를 json으로 변환해주는 함수
  ​var byte = Encoding.UTF8.GetBytes(jsonData); // json을 byte[]로 변환해주는 함수
  ​Backend.Match.SendDataToInGameRoom(byte);
}

  1. 닉네임은 로그인 이후에 불러올 수 있으며, Debug.Log(Backend.UserNickName)을 통해 확인해주시기 바랍니다.

  2. 불러오는데 실패하는 값에 대한 Debug.Log나 args를 확인하여 로직이 작동될 수 있도록 값이 들어왔는지 확인해주시며 개발해주시면 될 것 같습니다.

서버개발을 처음 접해보기도 하고 개발을 시작한지 얼마 되지 않아서
계속 질문드리는게 죄송스럽네요

error1
아래 처럼 코드를 작성했습니다. 그랬더니 오류로

error2
라는 오류를 받았습니다. 어떻게 해야할까요?

제공한 예제코드에 잘못된 코드가 있었습니다.
수정된 코드 다시 제공드리겠습니다.

슈퍼게이머 방식의 경우, 유저2 → 슈퍼게이머(유저1) → 유저3,4 형식으로 되어있기에 처음 개발을 시작하실때에는 어려우실 수 있습니다.

그러므로 처음에는 슈퍼게이머가 관리하는 것 없이 그저 결과와 시작값만 넘겨주는 유저라 생각하고, 유저 2 → 유저1, 3,4로 보내는 구조와 게임 시작후 자신이 슈퍼게이머라면 유저1 → 유저2,3,4에게 유저 생성의 메세지를 보내는 식으로 구상해주는 것이 좋을 것 같습니다.

    public class Message
    {
        public Type type;
        public Message(string _type)
        {
            type = _type;
        }
    }
    public class PlayerCreateClass : Message
    {
        Vector3 createPos;
        public PlayerCreateClass(Vector3 pos) : base("PlayerCreateClass")
        {
            createPos = pos;
        }
    }

js```
public void PlayerCreate()
{
Vector3 createPos = new Vector3(Random.Range(-3.62f, 16f), -1.55f, 0);
PlayerCreateClass player = new PlayerCreateClass(createPos);

​var jsonData = JsonUtility.ToJson(player); // 클래스를 json으로 변환해주는 함수
​var byte = Encoding.UTF8.GetBytes(jsonData); // json을 byte[]로 변환해주는 함수
​Backend.Match.SendDataToInGameRoom(byte);
}

Ishost는 BackEndMatchManager.GetInstance().IsHost()를 쓰면 되나요?

네 뒤끝매치 튜토리얼에서는 해당 함수를 사용하시면 됩니다.

또는 아래 핸들러를 통해 값을 받아 설정할 수 있습니다.

Backend.Match.OnMatchInGameAccess += (args) => {

isHost = args.GameRecord.m_isSuperGamer;

}

항상 친절하게 답변해주셔서 감사합니다.
일단 제 게임의 과정은

  1. 매칭서버에 접속하여 대기방에 입장
  2. 매칭을 돌리고 매칭이 성사되면 Backend.Match.OnMatchMakingResponse를 통해 roomtoken을 받아옴
  3. Backend.Match.OnMatchMakingResponse이 콜백되면 JoinInGameServer(); 함수 호출
  4. JoinGameServer()
    void JoinInGameServer() // 임의의 함수
    {
        bool isReconnect = false;
        ErrorInfo errorInfo = null;

        if (Backend.Match.JoinGameServer(serverAddress, serverPort, isReconnect, out errorInfo) == false)
        {
            // 에러 확인
            return;
        }
    }
  1. Backend.Match.OnSessionJoinInServer += (args)로 콜백
    5-1. Backend.Match.JoinGameRoom(roomToken);
    SceneManager.LoadScene(3);
  2. 3번씬은 나와 상대 플레이어의 전적, 정보등을 보여주는 로딩창으로 사용하고 있습니다(현재는 미구현). 3번씬은 7초정도 보여주고 Invoke를 이용하여 4번씬으로 이동.
  3. 게임 씬 스크립트 입니다. 해당 스크립트를 해당 씬의 empty object에 넣어뒀습니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using BackEnd;
using BackEnd.Tcp;
using System.IO;
using System.Text;
using System;
public class Message
{
    public string type;
    public Message(string _type)
    {
        type = _type;
    }
}
public class PlayerCreateClass : Message
{
    public Vector3 createPos;
    public PlayerCreateClass(Vector3 pos) : base("PlayerCreateClass")
    {
        createPos = pos;
    }
}
public class SkillManager : MonoBehaviour
{ 
    public GameObject SkillCanvas;
    public GameObject frontCanvas;

    public Text TimeText;
    public float SkillChoiceTime;

    public bool IsStart;
    [SerializeField] private bool IsConnect = false;

    public GameObject playerPrefeb;

    [SerializeField]
    private bool IsHost;

    bool a = true;
    private void Start()
    {
        Debug.Log("game: start");
        frontCanvas.SetActive(false);
        SkillCanvas.SetActive(true);
        SkillChoiceTime = 20f;
        Time.timeScale = 1f;
        Backend.Match.OnMatchRelay += (args) =>
        {
            var strByte = Encoding.Default.GetString(args.BinaryUserData);
            Message msg = JsonUtility.FromJson<Message>(strByte);
            Debug.Log("OnMatchRelay_1");
            if (msg.type == "PlayerCreateClass")
            {
                PlayerCreateClass player = JsonUtility.FromJson<PlayerCreateClass>(strByte);
                Instantiate(playerPrefeb, player.createPos, Quaternion.identity);
                Debug.Log("OnMatchRelay_2");
            }
        };

        Backend.Match.OnMatchInGameAccess += (args) =>
        {
            IsHost = args.GameRecord.m_isSuperGamer;
            Debug.Log("OnMatchInGameAccess");
        };
    }

    private void Update()
    {
        TimeText.text = "" + (int)SkillChoiceTime + "초";
        if ((int)SkillChoiceTime > 0   )
        {
            SkillChoiceTime -= Time.deltaTime;
            //Debug.Log(SkillChoiceTime);
        }
        if ((int)SkillChoiceTime <= 0 && a)
        {
            SkillCanvas.SetActive(false);
            if(IsHost)
            {
                PlayerCreate();
                Debug.Log("IsHost");
            }
            a = false;
        }

    }
    public void PlayerCreate()
    {
        Vector3 createPos = new Vector3(UnityEngine.Random.Range(-3.62f, 16f), -1.55f, 0);
        PlayerCreateClass player = new PlayerCreateClass(createPos);

        Debug.Log("PlayerCreate");
        var jsonData = JsonUtility.ToJson(player);
        var byte_ = Encoding.UTF8.GetBytes(jsonData);
        Backend.Match.SendDataToInGameRoom(byte_);
    }
 
}

결과 : playerprefeb은 생성되지 않았고 로그는 Debug.Log(“game: start”);만 찍혔습니다

추측되는 상황으로는 유저가 게임에 접속될 때 호출되는 핸들러에서 문제가 발생하는 것으로 추측됩니다.

OnMatchInGameAccess는 유저가 게임방에 접속을 할 때 발생되는 핸들러로, 자신이 게임방에 들어갔을 때와 다른 유저가 게임방에 들어갔을 때 호출이 됩니다.

그러므로 추가적인 로직을 통해 자신의 세션아이디를 가져오고 OnMatchInGameAccess에서 리턴되는 args의 세션 id와 비교하며 자기 자신이 슈퍼게이머인지 확인하는 로직을 추가해주시면 될 거 같습니다.


SessionId mySessionId;
Backend.Match.OnSessionJoinInServer += (args) => {
   if(args.IsRemote)
  {
     mySessionId = args.SessionId;
  }
};

Backend.Match.OnMatchInGameAccess += (args) =>
{
    Debug.Log("OnMatchInGameAccess : " + args.GameRecord.SessionId);

    if(args.GameRecord.SessionId == mySessionId)
    {
        IsHost = args.GameRecord.m_isSuperGamer;
       Debug.Log("자신이 호스트가 되었습니다");
    }
};

답변 주신 내용으로 수정 시도 했습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using BackEnd;
using BackEnd.Tcp;
using System.IO;
using System.Text;
using System;
public class Message
{
    public string type;
    public Message(string _type)
    {
        type = _type;
    }
}
public class PlayerCreateClass : Message
{
    public Vector3 createPos;
    public PlayerCreateClass(Vector3 pos) : base("PlayerCreateClass")
    {
        createPos = pos;
    }
}
public class SkillManager : MonoBehaviour
{ 
    public GameObject SkillCanvas;
    public GameObject frontCanvas;

    public Text TimeText;
    public float SkillChoiceTime;

    [SerializeField] private bool IsConnect = false;

    public GameObject playerPrefeb;

    [SerializeField]
    private bool IsHost;

    bool a = true;
    SessionId mySessionId;
    private void Start()
    {
        Debug.Log("game: start"); 
        frontCanvas.SetActive(false);
        SkillCanvas.SetActive(true);
        SkillChoiceTime = 20f;
        Time.timeScale = 1f;
        Backend.Match.OnMatchRelay += (args) =>
        {
            var strByte = Encoding.Default.GetString(args.BinaryUserData);
            Message msg = JsonUtility.FromJson<Message>(strByte);
            Debug.Log("OnMatchRelay_1");
            if (msg.type == "PlayerCreateClass")
            {
                PlayerCreateClass player = JsonUtility.FromJson<PlayerCreateClass>(strByte);
                Instantiate(playerPrefeb, player.createPos, Quaternion.identity);
                Debug.Log("OnMatchRelay_2");
            }
        };

        Backend.Match.OnMatchInGameAccess += (args) =>
        {
            Debug.Log("OnMatchInGameAccess : " + args.GameRecord.m_sessionId);
            if(args.GameRecord.m_sessionId == mySessionId)
            {
                IsHost = args.GameRecord.m_isSuperGamer;
                Debug.Log("내가 호스트");
            }
        };
        Backend.Match.OnSessionJoinInServer += (args) =>
        {
            if (args.Session.IsRemote == true)
            {
                mySessionId = args.Session.SessionId;
            }
        };
    }

    private void Update()
    {
        TimeText.text = "" + (int)SkillChoiceTime + "초";
        if ((int)SkillChoiceTime > 0   )
        {
            SkillChoiceTime -= Time.deltaTime;
            //Debug.Log(SkillChoiceTime);
        }
        if ((int)SkillChoiceTime <= 0 && a)
        {
            SkillCanvas.SetActive(false);
            if(IsHost)
            {
                PlayerCreate();
                Debug.Log("IsHost");
            }
            a = false;
        }

    }
    public void PlayerCreate()
    {
        Vector3 createPos = new Vector3(UnityEngine.Random.Range(-3.62f, 16f), -1.55f, 0);
        PlayerCreateClass player = new PlayerCreateClass(createPos);

        Debug.Log("PlayerCreate");
        var jsonData = JsonUtility.ToJson(player);
        var byte_ = Encoding.UTF8.GetBytes(jsonData);
        Backend.Match.SendDataToInGameRoom(byte_);
    }
 
}

결과: 전과 같이 playerprefeb은 생성되지 않았고, 로그는 game: start만 찍혔습니다

코드에는 이상이 없는것으로 확인이 됩니다. 허나 아래의 로그까지 발생하지 않는 것이라면 이전에 매칭을 진행하는 로직에서 어느 핸들러가 호출이 안되어 멈춘 것으로 추측됩니다.

Debug.Log("OnMatchInGameAccess : " + args.GameRecord.m_sessionId);

혹시 해당 핸들러의 등록이 잘 되어있고 각 핸들러마다 Debug.Log()를 통해 어느정도 진행되었는지 확인이 가능하실까요?

Backend.Match.OnJoinMatchMakingServer += (args) => {
    Debug.Log("OnJoinMatchMakingServer  : " + args);
};

Backend.Match.OnMatchMakingResponse += (args) => { // 2번 호출됨
    Debug.Log("OnMatchMakingResponse : " + args);
};

Backend.Match.OnSessionJoinInServer += (args) => {
    Debug.Log("OnSessionJoinInServer : " + args);
};

Backend.Match.OnMatchInGameAccess += (args) => {
    Debug.Log("OnSessionJoinInServer : " + args);
};

Backend.Match.OnMatchInGameStart += () =>
    Debug.Log("OnMatchInGameStart : " + args);
};

시도해봤는데 답변주신 모든 핸들러의 로그가 찍히지 않았습니다.

그래서 10번쨰 댓글에서 매칭할때의 스크립트입니다.

참고로 10번째 댓글에서 6번-해당 씬에서는 아무 스크립트가 들어가 있지 않습니다.


public class MatchManager : MonoBehaviour

{

    [Header("Online")]

    public ErrorInfo errorInfo;

    [Header("Choice Mods")]

    public Button btn_1_1;

    public Button btn_5;

    public Button btn_10;

    public Button CancelMatchingbtn;

    [SerializeField] private bool Is_1_1;

    [SerializeField] private bool Is_5;

    [SerializeField] private bool Is_10;

    public GameObject text_1_1;

    public GameObject text_5;

    public GameObject text_10;

    public Text Mod_1;

    public Text StatusText;

    [Header("Objectives")]

    public GameObject MainCanvas;

    public GameObject Matchinging;

    [SerializeField]

    string serverAddress;

    [SerializeField]

    ushort serverPort;

    public static string roomToken;

    public GameObject ErrorBox;

    public Text ErrorText;

    private void Start()

    {

        Backend.Match.OnMatchMakingResponse += (args) => //매칭이 성사됐을 때

        {

            string serverAddress = args.RoomInfo.m_inGameServerEndPoint.m_address;

            ushort serverPort = args.RoomInfo.m_inGameServerEndPoint.m_port;

            roomToken = args.RoomInfo.m_inGameRoomToken; //룸 토큰을 클라이언트에 요청, 획득

            StatusText.text = "플레이어 찾음!";

            Debug.Log("핸들러 테스트");

            CancelMatchingbtn.interactable = false;

            JoinInGameServer();

        };

        Backend.Match.OnSessionJoinInServer += (args) => { //JoinInGameServer() 콜백

            GoGame();

        };

    }

    private void Awake()

    {

        Is_1_1 = true;

        Is_5 = false;

        Is_10 = false;

        MainCanvas.SetActive(true);

        Matchinging.SetActive(false);

        StatusText.text = "플레이어 찾는 중...";

    }

   

    void JoinInGameServer() // 임의의 함수

    {

        bool isReconnect = false;

        ErrorInfo errorInfo = null;

        Debug.Log("2");

        if (Backend.Match.JoinGameServer(serverAddress, serverPort, isReconnect, out errorInfo) == false)

        {

            // 에러 확인

            return;

        }

    }

    public void GoGame()

    {

        Debug.Log("Game");

        SceneManager.LoadScene(3);

        Backend.Match.JoinGameRoom(roomToken);

       

    }

    public void goback()

    {

        Backend.Match.LeaveMatchRoom();

        SceneManager.LoadScene(1);

    }

    public void StartMaching()

    {

        StatusText.text = "플레이어 찾는 중...";

        CancelMatchingbtn.interactable = true;

        if (Is_1_1)

        {

            Backend.Match.RequestMatchMaking(MatchType.Point, MatchModeType.OneOnOne, "indate");

            MainCanvas.SetActive(false);

            Matchinging.SetActive(true);

        }

        if (Is_5)

        {

            Backend.Match.RequestMatchMaking(MatchType.Point, MatchModeType.Melee, "indate");

            MainCanvas.SetActive(false);

            Matchinging.SetActive(true);

        }

        if (Is_10)

        {

            Backend.Match.RequestMatchMaking(MatchType.Point, MatchModeType.Melee, "indate");

            MainCanvas.SetActive(false);

            Matchinging.SetActive(true);

        }

    }

}

해당 핸들러는 게임이 시작되고 인게임으로 들어가기 전까지 호출되야하는 핸들러이며, 모든씬에서 호출되지는 않습니다.

또한 핸들러에 등록한 로직이 이루어지기 위해서는 해당 씬에 스크립트가 있는 것을 권장합니다.
예제게임의 경우, DonDestroyOnLoad를 통해 모든 씬에서 사용가능하게 끔 설정하였습니다.
(핸들러속 로직에서 지정한 object등을 찾지 못해 에러가 발생하는 경우가 많음)

뒤끝 매치의 경우, 개발자문서에 게시된 사이드 메뉴 순서대로 함수를 호출해야 정상적으로 매칭을 진행할 수 있습니다.

우선 추천드리는 방법은 예제게임과 같이 뒤끝 매치를 관리하는 오브젝트를 모든 씬에서 이용가능하게끔 DonDestroyOnLoad로 만들고 거기에 뒤끝매치와 관련된 기능(신청 함수 호출, 핸들러 등록)을 통합하는 것입니다.

그 이후에 모든 씬 합해서 제공한 핸들러의 응답이 발생했는지 확인 후, 만약 호출이 안되어있을 경우에는 해당 핸들러와 함수부터 차근차근 보시는 것을 추천드립니다.(제공한 핸들러와 만약 이미 작성하신 핸들러가 있을 경우에는 작성하신 핸들러로 테스트해주세요)

매칭 서버 접속

Backend.Match.JoinMatchMakingServer(out errorInfo);
Backend.Match.OnJoinMatchMakingServer += (args) => {
    // TODO
};

대기방 접속

Backend.Match.CreateMatchRoom();
Backend.Match.OnMatchMakingRoomCreate += (args) => {
    // TODO
};

매칭 신청


Backend.Match.RequestMatchMaking(matchType, modeType, inDate);
Backend.Match.OnMatchMakingResponse += (args) => {
    switch(args.ErrInfo)
   {
            case ErrorCode.Success:
                // 매칭 성공했을 때
            case ErrorCode.Match_InProgress:
                // 매칭 신청 성공했을 때 or 매칭 중일 때 매칭 신청을 시도했을 때
            case ErrorCode.Match_MatchMakingCanceled:
                // 매칭 신청이 취소되었을 때
   }
};

인게임 서버 접속

bool isReconnect = false;
ErrorInfo errorInfo = null;
if(Backend.Match.JoinGameServer(serverAddress, serverPort, isReconnect, out errorInfo) == false)
{
    Debug.Log(errorInfo)
    return;
}
//핸들러
Backend.Match.OnSessionJoinInServer += (args) => {
    // TODO
};

게임방 접속 요청

Backend.Match.JoinGameRoom(roomToken);
Backend.Match.OnSessionListInServer += (args) => {
    // TODO
};
Backend.Match.OnMatchInGameAccess += (args) => {
    // TODO
};

게임시작핸들러

Backend.Match.OnMatchInGameStart += () =>
    // TODO
};
  1. 스크립트는 글자수가 너무 많아서 txt파일로 업로드 하겠습니다.

  2. 로비에서의 스크립트 핸들러 요청
    skirpt_1.txt (2.0 KB)

    결과 : 로그는 “함수실행”, OnClick으로 받은 GameStart의 "OnMatchMakingRoomCreateBackEnd.Tcp.MatchMakingInteractionEventArgs"로그가 찍혔고 그 외의 로그는 찍히지 않았습니다.

  3. 매치 대기방의 스크립트
    skript_2.txt (4.0 KB)
    결과 : “OnMatchMakingResponseBackEnd.Tcp.MatchMakingResponseEventArgs”, “Ga,e”, “2” 외에는 다른 로그가 찍히지 않았습니다.

  4. 매칭이 성사된 후 게임방으로 이동하여 실행되는 스크립트
    skript_3.txt (3.3 KB)
    결과 : "OnMatchInGameAccess In Scene: Game"외에는 찍히지 않았습니다.

핸들러 호출 순서 및 코드 작성에는 문제가 없는 것으로 확인되지만,
핸들러의 등록이 씬 변환 중에 이루어지는 것이 문제가 될 수 있습니다.

스크립트 2번의 GoGame 함수에서 씬의 이동과 동시에 게임룸 입장 함수를 호출하지만,
씬 이동이 기기에 따라 길어지거나 리소스가 많을 경우, 로드하는 속도가 느려 씬 변경 이후에 Start부분의 함수 호출이 늦어져 핸들러가 등록되지 않을 수 있습니다.

그렇기 때문에 OnDestroyOnLoad 오브젝트로 씬 이동시에도 (roomToken) 을 공유한 후, 씬 이동이 끝났을 때 Backend.Match.JoinGameRoom을 호출하는 식으로,
코드를 작성해주시기 바랍니다.

스크립트 2

    private void Start()
    {
        Backend.Match.GetMatchList();
        Backend.Match.OnMatchMakingResponse += (args) => //매칭이 성사됐을 때
        {
	// BackendMatchManager.instance 는 DontDestroyOnLoad로 값을 공유하도록 하는 전역 클래스입니다.
            BackendMatchManager.instance.serverAddress = args.RoomInfo.m_inGameServerEndPoint.m_address;
            BackendMatchManager.instance.serverPort = args.RoomInfo.m_inGameServerEndPoint.m_port;
            BackendMatchManager.instance.roomToken = args.RoomInfo.m_inGameRoomToken; //룸 토큰을 클라이언트에 요청, 획득
            StatusText.text = "플레이어 찾음!";
            Debug.Log("OnMatchMakingResponse" +  args);
            CancelMatchingbtn.interactable = false;
            JoinInGameServer();
        };
       // 이하 생략(코드 그대로)
   }

    public void GoGame()
    {
        Debug.Log("Ga,e");
        SceneManager.LoadScene(3);
        // Backend.Match.JoinGameRoom(roomToken); 삭제
        
    }

스크립트 3

    private void Start()
    {
        Debug.Log("OnMatchInGameAccess In Scene: Game"); // <<호출이 되지 않은 상태
        frontCanvas.SetActive(false);
        SkillCanvas.SetActive(true);
        IsStart = false;
        SkillChoiceTime = 20f;
        Time.timeScale = 1f;

        Backend.Match.JoinGameRoom(BackendMatchManager.instance.roomToken); // 

        Backend.Match.OnMatchRelay += (args) =>
        {
            var strByte = Encoding.Default.GetString(args.BinaryUserData);
            Message msg = JsonUtility.FromJson<Message>(strByte);
            Debug.Log("OnMatchRelay_1");
            if (msg.type == "PlayerCreateClass")
            {
                PlayerCreateClass player = JsonUtility.FromJson<PlayerCreateClass>(strByte);
                Instantiate(playerPrefeb, player.createPos, Quaternion.identity);
                Debug.Log("OnMatchRelay_2");
            }
        };

        Backend.Match.OnMatchInGameAccess += (args) =>
        {
            Debug.Log("OnMatchInGameAccess : " + args.GameRecord.m_sessionId);
            if(args.GameRecord.m_sessionId == mySessionId)
            {
                IsHost = args.GameRecord.m_isSuperGamer;
                Debug.Log("내가 호스트");
            }
        };
        Backend.Match.OnSessionJoinInServer += (args) =>
        {
            if (args.Session.IsRemote == true)
            {
                mySessionId = args.Session.SessionId;
            }
        };
        Backend.Match.OnMatchInGameStart += () =>
        {
            Debug.Log("OnMatchInGameStart");
        };
        // 이하 생략
}
            BackEndMatchManager.instance.serverAddress = args.RoomInfo.m_inGameServerEndPoint.m_address;
            BackEndMatchManager.instance.serverPort = args.RoomInfo.m_inGameServerEndPoint.m_port;
            BackEndMatchManager.instance.roomToken = args.RoomInfo.m_inGameRoomToken; //룸 토큰을 클라이언트에 요청, 획득

부분에서,

심각도 코드 설명 프로젝트 비표시 오류(Suppression) 상태
오류 CS0122 '보호 수준 때문에 'BackEndMatchManager.instance’에 액세스할 수 없습니다. Assembly-CSharp 51 활성

라는 오류가 나왔습니다. (각 3개의 코드에 대한 3개의 오류)

안녕하세요 개발자님.

BackEndMatchManager.instance.

해당 클래스는 매치 예제 게임에서 사용되는 매치 관리용 씬 이동에도 계속 사용되는 전역 클래스를 의미하고 있습니다.

현재 개발자님의 코드에서는 해당 클래스가 없어 에러가 발생할 것이며, 해당 클래스 생성후 아래와 같이 전역 변수 및 DontDestroyOnLoad를 통해 언제 어디서든 접근 가능하게 해주시고, 변수를 추가해주세요.

만약 각 클래스에서 생성되는 핸들러의 관리가 어려울 경우에는 뒤끝 매치 예제 게임처럼 모든 핸들러의 설정을 해당 클래스에서 완수해도 됩니다.(에러가 가장 적게 발생하므로 추천하는 방법입니다)

class BackEndMatchManager : MonoBehaviour
{
    public static BackEndMatchManager instance = null; // 나중에 Getter화 하는 것을 추천드립니다.

   public string serverAddress;
   public string serverPort ;
   public string roomToken ;

    void Awake()
    {
        if (instance != null)
        {
            Destroy(instance);
        }
        instance = this;

        DontDestroyOnLoad(this.gameObject);
    }

    void Start()
    {
    }
}