2 minute read

  • 프로젝트 명: Photon TPS
  • 개발 기간: 약 2주
  • Photon을 사용하여 멀티 플레이 게임 구현해보기

게임 조작

  • WASD로 캐릭터를 이동한다.
  • 마우스로 조준 및 사격한다.
  • Space Bar로 점프할 수 있다.

구조

  • 일반적으로 시작 > 로비 > 방 > 게임 순서를 갖고 있다.

  • 시작 시 닉네임을 입력한다. Login

  • 로비에서 플레이어는 방을 개설하거나 참여할 수 있다.
  • 사용자 1의 시점 Lobby01
  • 사용자 1이 방을 개설한 후 사용자 2의 시점 Lobby02

  • 방에서는 플레이어간 채팅이 가능하고 게임을 시작할 수 있다. Room

  • 게임 중에는 체력, 탄창 및 사격을 구현하였다. Game

코드

  • 시작: 포톤에서 제공하는 콜백 함수를 사용하여 멀티 플레이를 시작한다.
public void Connect()
{
    if (nicknameField.text.Equals(string.Empty))
    {
        PhotonNetwork.NickName = "User " + Random.Range(0, 1000);
    }
    PhotonNetwork.NickName = nicknameField.text;
    PhotonNetwork.ConnectUsingSettings();
}

// 포톤 서버에 접속 후 호출되는 콜백 함수
public override void OnConnectedToMaster()
{
    PhotonNetwork.JoinLobby();
}
    
// 로비에 접속 후 호출되는 콜백 함수
public override void OnJoinedLobby()
{
    PhotonNetwork.LoadLevel("Lobby");
}
  • 로비: 플레이어가 개설한 방을 로비에서 확인할 수 있도록 갱신하는 함수를 ILobbyCallbacks를 사용하여 구현한다.
    public override void OnRoomListUpdate(List<RoomInfo> roomList)
    {
        int roomCount = roomList.Count;
        for (int i = 0; i < roomCount; i++)
        {
            if (!roomList[i].RemovedFromList)
            {
                if (!_myList.Contains(roomList[i])) _myList.Add(roomList[i]);
                else _myList[_myList.IndexOf(roomList[i])] = roomList[i];
            }
            else if (_myList.IndexOf(roomList[i]) != -1) _myList.RemoveAt(_myList.IndexOf(roomList[i]));
        }
        MyListRenewal();
    }
  • 방: 플레이어간 채팅이 가능하도록 RPC를 이용하여 구현한다.
    public void Send()
    {
        if (sendField.text == "")
        {
            return;
        }
        pv.RPC("ChatRPC", RpcTarget.All, "<color=green>" + PhotonNetwork.NickName + "</color>" + " : " + sendField.text);
        sendField.text = "";
    }

    [PunRPC] // RPC는 플레이어가 속해있는 방 모든 인원에게 전달한다
    void ChatRPC(string msg)
    {
        bool isInput = false;
        for (int i = 0; i < text.Length; i++)
            if (text[i].text == "")
            {
                isInput = true;
                text[i].text = msg;
                break;
            }
        if (!isInput) // 꽉차면 한칸씩 위로 올림
        {
            for (int i = 1; i < text.Length; i++) text[i - 1].text = text[i].text;
            text[text.Length - 1].text = msg;
        }
    }

RPC란?

원격 클라이언트에 있는 메소드를 호출 하는 것입니다. [PunRPC] 속성을 적용해야 합니다. RPC로 표기된 함수를 호출하려면 PhotonView 컴포넌트가 필요합니다.
출처

  • 게임: 캐릭터 조작은 Input System을 통해 구현하고, 사격과 재장전은 RPC로 선언하여 모든 플레이어가 확인할 수 있도록 한다.
        public void OnShot(InputAction.CallbackContext context)
        {
            if (!photonView.IsMine) return;
            if (_isRunning) return;
            if (context.performed) // Action type이 "Button"일 경우 키가 눌렸는지 확인함
            {
                _animator.SetBool("isFire", true);
                pv.RPC("Fire", RpcTarget.All);
            }
            else
            {
                _animator.SetBool("isFire", false);
            }
        }

        [PunRPC]
        private void Fire()
        {
            if (playerInfo.curAmmo > 0)
            {
                playerInfo.curAmmo--;
                GameObject spawnedBullet = Instantiate(bullet, muzzle.position, muzzle.rotation);
                spawnedBullet.GetComponent<Rigidbody>().AddForce(aimPoint.forward * 2500f);
            }
        }

                public void OnReload(InputAction.CallbackContext context)
        {
            if (!photonView.IsMine) return;
            if (_isRunning) return;
            pv.RPC("Reload", RpcTarget.All);
        }

        [PunRPC]
        public void Reload()
        {
            if (playerInfo.remainedAmmo > 0)
            {
                if (playerInfo.curAmmo >= playerInfo.MaxAmmo) return;
                
                int insertAmmo = playerInfo.MaxAmmo - playerInfo.curAmmo;
                if (playerInfo.remainedAmmo <= insertAmmo)
                {
                    playerInfo.curAmmo += playerInfo.remainedAmmo;
                    playerInfo.remainedAmmo = 0;
                }
                else
                {
                    playerInfo.curAmmo += insertAmmo;
                    playerInfo.remainedAmmo -= insertAmmo;
                }
                _animator.SetTrigger("isReload");
            }
        }

마치며

Photon을 이용하여 실시간 채팅과 멀티 플레이를 구현하였다.