팀원이 제작한 플레이어 오브젝트를 네트워크 오브젝트화 하여 멀티플레이를 구현하였습니다.
캐릭터 생성 로직
네트워크 오브젝트를 스폰하기 위한 클래스를 생성해주었습니다.
이후 모든 오브젝트의 생성을 이 클래스가 당담할 예정입니다.
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;
}'내일배움캠프 > 프로젝트' 카테고리의 다른 글
| 팀 프로젝트 - 오브젝트 스폰 시점 수정, 로딩 씬 추가 (0) | 2024.12.09 | 
|---|---|
| 팀 프로젝트 - 리팩토링 대상 정리 (0) | 2024.12.05 | 
| 팀프로젝트 진행 - 재시작 문제 해결 (0) | 2024.12.03 | 
| 팀 프로젝트 - Vivox 조작 UI 제작 (0) | 2024.12.02 | 
| 팀프로젝트 - 프로토타입 로비 완성, Vivox 연동 시작 (0) | 2024.11.29 |