포톤은 무작위 입장 함수인 JoinRandomRoom()와 아무방도 없다면 OnJoinRandomFailed() 콜백함수를 제공한다. PhotonManager 스크립트를 다음과 같이 수정한다.

// 로비에 접속 후 호출되는 콜백 함수
public override void OnJoinedLobby() {
    Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
    PhotonNetwork.JoinRandomRoom();        
}

// 랜덤한 룸 입장이 실패했을 경우 호출되는 콜백 함수
public override void OnJoinRandomFailed(short returnCode, string message) {
    Debug.Log($"JoinRandom Filed {returnCode}:{message}");
}

실행해보면 방이 없기 때문에 당연히 OnJoinRandomFailed() 콜백함수가 실행되면 입장할수 없다는 메시지가 나올것이다

 

CreateRoom함수

입장할 수 없다는 메시지 아래 방을 만드는 코드를 추가해보자. 만들 룸의 옵션을 설정후 CreateRoom()을 이용해 만든다.

// 랜덤한 룸 입장이 실패했을 경우 호출되는 콜백 함수
public override void OnJoinRandomFailed(short returnCode, string message) {
    Debug.Log($"JoinRandom Filed {returnCode}:{message}");
      // 룸의 속성 정의
    RoomOptions ro = new RoomOptions();
    ro.MaxPlayers = 20;     // 룸에 입장할 수 있는 최대 접속자 수
    ro.IsOpen = true;       // 룸의 오픈 여부
    ro.IsVisible = true;    // 로비에서 룸 목록에 노출시킬 여부

    // 룸 생성
    PhotonNetwork.CreateRoom("My Room", ro);
}

생성이 완료되는 OnCreatedRoom 콜백함수가 호출되며 실패하면 OnCreateRoomFailed()가 호출된다

// 룸 생성이 완료된 후 호출되는 콜백 함수
public override void OnCreatedRoom() {
    Debug.Log("Created Room");
    Debug.Log($"Room Name = {PhotonNetwork.CurrentRoom.Name}");
}

룸을 만든 유저는 자동으로 방장이되어 방에 입장하면서 OnJoinedRoom콜백 함수가 호출된다. PlayerCount는 1번이다.

// 룸에 입장한 후 호출되는 콜백 함수
public override void OnJoinedRoom() {
    Debug.Log($"PhotonNetwork.InRoom = {PhotonNetwork.InRoom}");
    Debug.Log($"Player Count = {PhotonNetwork.CurrentRoom.PlayerCount}");
}

OnJoinedRoom()함수를 다음과 같이 변경해 룸에 접속한 사용자 정보를 얻어오자 NickName은 동일할수 있고 ActorNumber는 고유하다.

// 룸에 입장한 후 호출되는 콜백 함수
public override void OnJoinedRoom() {
    Debug.Log($"PhotonNetwork.InRoom = {PhotonNetwork.InRoom}");
    Debug.Log($"Player Count = {PhotonNetwork.CurrentRoom.PlayerCount}");
    foreach (var player in PhotonNetwork.CurrentRoom.Players) {
        Debug.Log($"{player.Value.NickName} , {player.Value.ActorNumber}");
    }
}

실행해보면 방을 잘 만들어서 입장하는 걸 볼 수 있다.

PhotonManager스크립트의 전체코드

using UnityEngine;
using TMPro;
using Photon.Pun;  //포톤 API를 위한 네임스페이스
using Photon.Realtime; //포톤 API를 위한 네임스페이스
using System.Collections.Generic;

public class PhotonManager : MonoBehaviourPunCallbacks {
    private readonly string version = "1.0"; // 게임의 버전
    private string userId = "Zack"; // 유저의 닉네임  

    void Awake() {      
        PhotonNetwork.AutomaticallySyncScene = true;  // 마스터 클라이언트의 씬 자동 동기화 옵션
        PhotonNetwork.GameVersion = version; // 게임 버전 설정
        PhotonNetwork.NickName = userId;  //접속 유저의 닉네임 설정
        Debug.Log(PhotonNetwork.SendRate);// 포톤 서버와의 데이터의 초당 전송 횟수
        PhotonNetwork.ConnectUsingSettings(); // 포톤 서버 접속
    }

    public override void OnConnectedToMaster() {
        Debug.Log("Connected to Master!");
        Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
        PhotonNetwork.JoinLobby();
    }

    // 로비에 접속 후 호출되는 콜백 함수
    public override void OnJoinedLobby() {
        Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
        PhotonNetwork.JoinRandomRoom();        
    }

    // 랜덤한 룸 입장이 실패했을 경우 호출되는 콜백 함수
    public override void OnJoinRandomFailed(short returnCode, string message) {
        Debug.Log($"JoinRandom Filed {returnCode}:{message}");
          // 룸의 속성 정의
        RoomOptions ro = new RoomOptions();
        ro.MaxPlayers = 20;     // 룸에 입장할 수 있는 최대 접속자 수
        ro.IsOpen = true;       // 룸의 오픈 여부
        ro.IsVisible = true;    // 로비에서 룸 목록에 노출시킬 여부

        // 룸 생성
        PhotonNetwork.CreateRoom("My Room", ro);
    }

    // 룸 생성이 완료된 후 호출되는 콜백 함수
    public override void OnCreatedRoom() {
        Debug.Log("Created Room");
        Debug.Log($"Room Name = {PhotonNetwork.CurrentRoom.Name}");
    }

    // 룸에 입장한 후 호출되는 콜백 함수
    public override void OnJoinedRoom() {
        Debug.Log($"PhotonNetwork.InRoom = {PhotonNetwork.InRoom}");
        Debug.Log($"Player Count = {PhotonNetwork.CurrentRoom.PlayerCount}");
        foreach (var player in PhotonNetwork.CurrentRoom.Players) {
            Debug.Log($"{player.Value.NickName} , {player.Value.ActorNumber}");
        }
    }
}

 

네트워크 게임에 참여하려면 맨 먼저 포톤 서버에 접속해야한다. 포톤서버는 로비와 룸의 개념이 존재한다.

함수를 통해 로비에 입장하면 룸들의 리스트를 얻을수 있고 특정 룸을 선택해 입장해서 그 방에 있는 유저들과 게임을 함께 할 수 있다.

 

포톤 서버에 접속해 룸을 생성하는 절차를 처리할 스크립트를 만들어 보자.  하이라키뷰에 빈게임오브젝트를 만들고 이름을 PhotonManager라고 한다. 동일 이름으로 스크립트를 만들고 연결한다.

포톤 API를 사용하기 위한 네임스페이스를 선언해야한다

using Photon.Pun;  //포톤 API를 위한 네임스페이스
using Photon.Realtime; //포톤 API를 위한 네임스페이스

PhotonManager 스크립트는 포톤의 다양한 기능을 사용하기 위해 MonoBehaviourPunCallbacks Class를 베이스 클래스로 변경한다.

public class PhotonManager : MonoBehaviourPunCallbacks {
}

Awake() 함수에서 포톤의 기본적 설정을 한후 접속하는 코드이다.

룸을 생성한 유저는 자동으로 입장하고 방장이 된다. 

PhotonNetwork.AutomaticallySyncScene = true;  // 마스터 클라이언트의 씬 자동 동기화 옵션

동일 버전의 유저끼리만 접속을 허용해준다.

PhotonNetwork.GameVersion = version; // 게임 버전 설정
PhotonNetwork.NickName = userId;  //접속 유저의 닉네임 설정

포톤서버와의 초당 통신회수를 정한다 디폴트는 30회이다

Debug.Log(PhotonNetwork.SendRate);// 포톤 서버와의 데이터의 초당 전송 횟수

설정을 마치면 서버에 접속한다.

PhotonNetwork.ConnectUsingSettings(); // 포톤 서버 접속

서버에 접속되면 제일 먼저 OnConnectedToMaster() 콜백함수가 호출된다. 여기서 로비에 입장했는지 체크해보자

Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");

포톤은 로비에 자동으로 입장할수 없기 때문에 False가 출력된다. JoinLobby로 입장해보자

PhotonNetwork.JoinLobby();

제대로 입장되었다면 OnJoinedLobby()콜백이 호출된다.

public override void OnJoinedLobby() {
    Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
    // 수동으로 접속하기 위해 자동 입장은 주석처리
    // PhotonNetwork.JoinRandomRoom();        
}
using UnityEngine;
using TMPro;
using Photon.Pun;  //포톤 API를 위한 네임스페이스
using Photon.Realtime; //포톤 API를 위한 네임스페이스
using System.Collections.Generic;

public class PhotonManager : MonoBehaviourPunCallbacks {
    private readonly string version = "1.0"; // 게임의 버전
    private string userId = "Zack"; // 유저의 닉네임  

    void Awake() {      
        PhotonNetwork.AutomaticallySyncScene = true;  // 마스터 클라이언트의 씬 자동 동기화 옵션
        PhotonNetwork.GameVersion = version; // 게임 버전 설정
        PhotonNetwork.NickName = userId;  //접속 유저의 닉네임 설정
        // 포톤 서버와의 데이터의 초당 전송 횟수
        Debug.Log(PhotonNetwork.SendRate);
        PhotonNetwork.ConnectUsingSettings(); // 포톤 서버 접속
    }

    public override void OnConnectedToMaster() {
        Debug.Log("Connected to Master!");
        Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
        PhotonNetwork.JoinLobby();
    }

    // 로비에 접속 후 호출되는 콜백 함수
    public override void OnJoinedLobby() {
        Debug.Log($"PhotonNetwork.InLobby = {PhotonNetwork.InLobby}");
        // 수동으로 접속하기 위해 자동 입장은 주석처리
        // PhotonNetwork.JoinRandomRoom();        
    }
}

실행해보면 한국 서버인 kr로 접속되고 Master에 연결된후 InLobby된다.

Asset 스토어에서 PUN2-FREE를 검색해 추가하고 Package-Manager에서 다운로드후 임포트한다.

PUN 패키지 설치

임포트가 끝나면 AppID를 넣으라고 한다 Email은 넣어봤더니 안된다. Done이라고 나온다

PUN Wizard창은 메뉴에서 [Window]-[Photon Unity Networking]-[PUN  Wizard]를 선택해 열수 있다.

PUN 패키지는 프로젝트뷰의 Photon폴더에 설치되는데 PhotonSererSetting를 선택하면 AppID등 다양한 정보를 볼수 있다.

 

아직 카메라가 주인공을 따라가는 로직이 구현되지 않았다.

메뉴 [Windows]-[Package Manager]를 열고 Package:Unity Registry를 선택하고, Cinemachine 패키지를 설치한다.

이제 메뉴[GameIObject]를 열면 [Cinemachine]-Virtual Camera를 선택해서 하이라키뷰에 가상카메라를 추가하면

CM vcam1이 추가되는데 이를 선택하고 인스펙터뷰에서 Follow와 Look At에 하이라키뷰의 Player를 끌어다 놓는다.

Cinemachine Virtual Camera 컴포넌트의 설정을 다음과 같이 한다.

Body > Binding Mode는 Lock To Target On Assign, Follow offset (0,9,-4)

Aim > Tracked Object Offset (0,2,0), Dead Zone Width 0.1, Dead Zone Height 0.1로 설정한다.

이 숫치는 실행해보면 탑다운 방식 이다.   WASD키를 눌러보면 캐릭터가 이동하는데 카메라는 따라간다. 마우스를 움직여보면 캐릭터는 회전하지만 카메라는 회전하지 않는다.

AngryBotResources/Prefabs/Player을 씬뷰로 끌어다 놓는다.

Player와 Main Camera의 transform을 다음과 같이 조정한다.

 

Player Prefab은 이미 Character Controller컴포넌트가 이미 설정되어 있고 실행하면  Idle 애니메이션을 취하고 있다.

 

주인공 캐릭터 이동및 회전

프로젝트뷰에서 Scripts폴더안에 새로운 스크립트를 하나 만들고 이름을 Movement로 변경한다. 작성후 Player에 적용한다.

Player의 이동처리는 CharacterController의 SimpleMove함수를 이용한다.

        // 주인공 캐릭터 이동처리
        controller.SimpleMove(moveDir * moveSpeed);

Plane객체는 지정한 지점에 가상의 바닥을 생성한다.

        // 가상의 바닥을 주인공의 위치를 기준으로 생성
        plane = new Plane(transform.up, transform.position);

Turn() 함수에서는 ScreenPointToRay 함수를 이용해 마우스 커서의 위치값을 이용해 Ray를 생성한다.

        // 마우스의 2차원 좌푯값을 이용해 3차원 광선(레이)를 생성
        ray = camera.ScreenPointToRay(Input.mousePosition);

생성된 ray는 Plane.Raycast를 사용해 가상의 바닥으로 광선을 투사해 Ray.GetPoint로 닿은 지점을 계산한다.

// 가상의 바닥에 레이를 발사해 충돌한 지점의 거리를 enter 변수로 반환
plane.Raycast(ray, out enter);
// 가상의 바닥에 레이가 충돌한 좌푯값 추출
hitPoint = ray.GetPoint(enter);

가상의 바닥과 닿은 지점과 Player의 위치를 차이는 회전해야할 방향벡터이고 마우스 커서가 있는 지점으로 회전하게 된다.

// 회전해야 할 방향의 벡터를 계산
Vector3 lookDir = hitPoint - transform.position;
lookDir.y = 0;
// 주인공 캐릭터의 회전값 지정
transform.localRotation = Quaternion.LookRotation(lookDir);

이 방법은 가상의 바닥의 충돌지점을 얻어 오기 때문에 지상의 구조물을 포인팅하면 엉뚱한 곳으로 돈다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class Movement : MonoBehaviour {
    // 컴포넌트 캐시 처리를 위한 변수
    private CharacterController controller;
    private new Transform transform;
    private Animator animator;
    private new Camera camera;

    // 가상의 Plane에 레이캐스팅하기 위한 변수
    private Plane plane;
    private Ray ray;
    private Vector3 hitPoint;

    // 이동 속도
    public float moveSpeed = 10.0f;

    void Start() {
        controller = GetComponent<CharacterController>();
        transform = GetComponent<Transform>();
        animator = GetComponent<Animator>();
        camera = Camera.main;

        // 가상의 바닥을 주인공의 위치를 기준으로 생성
        plane = new Plane(transform.up, transform.position);
    }

    void Update() {
        // 자신이 생성한 네트워크 객체만 컨트롤
            Move();
            Turn();
    }

    // 키보드 입력값 연결
    float h => Input.GetAxis("Horizontal");
    float v => Input.GetAxis("Vertical");

    // 이동 처리하는 함수
    void Move() {
        Vector3 cameraForward = camera.transform.forward;
        Vector3 cameraRight = camera.transform.right;
        cameraForward.y = 0.0f;
        cameraRight.y = 0.0f;

        // 이동할 방향 벡터 계산
        Vector3 moveDir = (cameraForward * v) + (cameraRight * h);
        moveDir.Set(moveDir.x, 0.0f, moveDir.z);

        // 주인공 캐릭터 이동처리
        controller.SimpleMove(moveDir * moveSpeed);

        // 주인공 캐릭터의 애니메이션 처리
        float forward = Vector3.Dot(moveDir, transform.forward);
        float strafe = Vector3.Dot(moveDir, transform.right);

        animator.SetFloat("Forward", forward);
        animator.SetFloat("Strafe", strafe);
    }

    // 회전 처리하는 함수
    void Turn() {
        // 마우스의 2차원 좌푯값을 이용해 3차원 광선(레이)를 생성
        ray = camera.ScreenPointToRay(Input.mousePosition);

        float enter = 0.0f;

        // 가상의 바닥에 레이를 발사해 충돌한 지점의 거리를 enter 변수로 반환
        plane.Raycast(ray, out enter);
        // 가상의 바닥에 레이가 충돌한 좌푯값 추출
        hitPoint = ray.GetPoint(enter);

        // 회전해야 할 방향의 벡터를 계산
        Vector3 lookDir = hitPoint - transform.position;
        lookDir.y = 0;
        // 주인공 캐릭터의 회전값 지정
        transform.localRotation = Quaternion.LookRotation(lookDir);
    }
}

 

+ Recent posts