내일배움캠프/프로젝트

팀 프로젝트 진행 - 멀티플레이 구현

서보훈 2024. 12. 4. 20:54

 

팀원이 제작한 플레이어 오브젝트를 네트워크 오브젝트화 하여 멀티플레이를 구현하였습니다.

 

캐릭터 생성 로직

네트워크 오브젝트를 스폰하기 위한 클래스를 생성해주었습니다.

이후 모든 오브젝트의 생성을 이 클래스가 당담할 예정입니다.

public class NetworkSpawnController : MonoSingleton<NetworkSpawnController>
{
    public Transform profassorSpawnPoint;
    public Transform studentSpawnPoint;

    public NetworkObject playerPrefab;

    protected override void Awake()
    {
        Create();

        if (Instance != this)
        {
            Destroy(gameObject);
        }
    }

    public void SpawnPlayer(ulong professorId, ulong studentId)
    {
        if (!NetworkManager.Singleton.IsHost) return;

        if(professorId == NetworkManager.Singleton.GetComponent<UnityTransport>().ServerClientId)
        {
            NetworkObject professor = Instantiate(playerPrefab, profassorSpawnPoint.position, Quaternion.identity);
            professor.SpawnAsPlayerObject(professorId);

            NetworkObject student = Instantiate(playerPrefab, studentSpawnPoint.position, Quaternion.identity);
            student.SpawnAsPlayerObject(studentId);
        }
        else
        {
            NetworkObject student = Instantiate(playerPrefab, studentSpawnPoint.position, Quaternion.identity);
            student.SpawnAsPlayerObject(studentId);

            NetworkObject professor = Instantiate(playerPrefab, profassorSpawnPoint.position, Quaternion.identity);
            professor.SpawnAsPlayerObject(professorId);
        }
    }
}

두 역할의 캐릭터를 당담할 클라이언트 ID를 가져온 뒤, 캐릭터를 생성하고 캐릭터의 소유권을 부여해줍니다.

아래는 역할을 분배하고 위 메서드를 호출하는 네트워크용 게임 매니저 클래스입니다.

public class NetworkGameManager : NetworkBehaviour
{
    private NetworkManager networkManager;
    private UnityTransport transport;

    private int isHostProfessor = -1;

    public ulong ProfessorId { get; private set; }
    public ulong StudentId { get; private set; }

    private void Start()
    {
        networkManager = NetworkManager.Singleton;
        transport = networkManager.GetComponent<UnityTransport>();
        RoleDicider();

        NetworkSpawnController.Instance.SpawnPlayer(ProfessorId, StudentId);
    }

    private void RoleDicider()
    {
        if (!IsHost) return;

        ulong clientId = 0;

        for(int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
        {
            if (networkManager.ConnectedClientsIds[i] == transport.ServerClientId) continue;

            clientId = networkManager.ConnectedClientsIds[i];
        }

        int random = Random.Range(0, 2);

        if(random == 0)
        {
            isHostProfessor = 0;
            ProfessorId = transport.ServerClientId;
            StudentId = clientId;
        }
        else
        {
            isHostProfessor = 1;
            ProfessorId = clientId;
            StudentId = transport.ServerClientId;
        }

        SetPlayerClientRpc(isHostProfessor);
    }

    [ClientRpc]
    private void SetPlayerClientRpc(int index)
    {
        isHostProfessor = index;

        SetPlayerRole();
    }

    private void SetPlayerRole()
    {
        if (networkManager.IsHost) return;

        if(isHostProfessor == 0)
        {
            ProfessorId = transport.ServerClientId;
            StudentId = networkManager.LocalClientId;
        }
        else if (isHostProfessor == 1)
        {
            ProfessorId = networkManager.LocalClientId;
            StudentId = transport.ServerClientId;
        }
        else
        {
            Debug.Log("실패");
        }
    }
}

이 클래스는 스폰되면 호스트에서 역할 분배를 한 뒤, 클라이언트들에게 역할 내용을 전달하는 역할을 합니다.

역할 분배와 전달이 끝나면 캐릭터 스폰 메서드를 호출합니다.

 

이후 네트워크 게임 진행에 필요한 메서드는 이 클래스에서 만들게될 예정입니다.

 


트러블 슈팅

  • 클라이언트 캐릭터가 잘못된 위치에 생성되는 문제 - 아직 해결하지 못했습니다. 일단 호스트 캐릭터를 먼저 생성했을때 문제가 해결되었었는데, 이후 다시 문제가 발생한 상태입니다.
  • 다수의 AudioListener 가 존재한다는 오류 발생

→ 멀티플레이 진행시, 플레이어가 각자 카메라를 가지게될경우 다른 플레이어의 카메라, AudioListener 를 비활성화 해줄 필요가 있다고 합니다.

따라서 생성시 소유자가 자신이 아니면 카메라가 부착된 자식 오브젝트를 비활성화 하도록 만들었습니다

private void Start()
{
    if (!IsOwner)
    {
        GetComponentInChildren<Camera>().gameObject.SetActive(false);
        return;
    }

    if (cameraTransform == null)
    {
        cameraTransform = Camera.main.transform; 
    }

    initialPosition = cameraTransform.localPosition;
}

이 내용 또한 아래의 내용에 맞추어 작동 위치를 바꾸어주어야 할 필요가 있다고 생각하고있습니다.


이번 작업에서는 NetworkObject를 Spawn() 메서드를 통해 생성할경우, 유니티 생명주기가 Awake 종류 후 시점이라는 점을 모르는 상태로 진행했다가 시간이 많이끌린감이 있습니다.

 

또한, 현재는 스폰시 OnNetworkSpawn() 메서드가 호출된다는것을 알게되어 Awake 관련 내용을 여기서 처리해줄 수 있다는걸 알게되어 NetworkGameManager 클래스 내용을 이에 맞추어 수정해줄 예정입니다.

//private void Awake()
//{
//    characterController = GetComponent<CharacterController>();

//    playerInput = new PlayerInput();
//    moveAction = playerInput.Player.Move;
//    runAction = playerInput.Player.Run;
//    lookAction = playerInput.Player.Look;
//    lightAction = playerInput.Player.Light;
//    inverntoryAction = playerInput.Player.Inventory;
//    Cursor.lockState = CursorLockMode.Locked;

//}

public override void OnNetworkSpawn()
{
    playerInput = new PlayerInput();

    if (!IsOwner)
    {
        playerInput.Disable();
        return;
    }

    characterController = GetComponent<CharacterController>();

    playerInput.Enable();
    lightAction.performed += OnLightAction;
}