본문 바로가기

- GameProgramming/- Unity 3D

★ 13. 포톤 클라우드 (Photon Cloud) Marco Polo 튜토리얼 [2/3]

반응형

Marco Polo 튜토리얼


지난번 [1/3]에 이어 [2/3]을 작성해보겠습니다.

# Marco Polo: 위치 정보 동기화

지금까지 바이킹이 뛰어다니는 것을 보았으니 동일한 것을 한번 해보겠습니다.
우선 아래의 링크에서 "Monster" 캐릭터를 다운로드 받겠습니다.

이름으로 프리팹의 인스턴스를 생성할 것인데 이 의미는 이 프리팹이 Resources 폴더 내에 있어야 한다는 것입니다. 에셋 "monsterprefab" 을 선택하고 PhotonView 의 컴포넌트를 추가해주세요. 컴포넌트 메뉴에 "Photon Networking" 카테고리가 추가될 것입니다.


# Photon View

유니티 네트워킹에 대한 지식이 있다면 PUN의 PhotonView는 NetworkView의 기능과 동등하다는 것을 아실겁니다. PUN은 네트워크 레퍼런스(ViewId로 알려져 있으며), 오브젝트의 소유자 및 "observed" 컴포넌트의 레퍼런스를 보관하기 위해서 인스턴스화된 프리팹마다 PhotonView 를 필요로 합니다.

PUN은 로컬에서 인스턴스화된 PhotonView와 실행시 다른 클라이언트들에게 변경 사항을 보낼 수 있는 관찰하고 있는 컴포넌트들을 추적합니다. 관찰되고 있는 객체가 더 많아지면 더 많은 작업 및 네트워크 트래픽이 증가됩니다.

트랜스레이션을 관찰하고 새로운 PhotonView 설정을 위해서 "monsterprefab" 의 Transform 컴포넌트를 "observed" 에 드래그앤 드롭하세요. PhotonView 의 다른 설정은 변경하지 않을 것입니다.

Photon Cloud Screenshot: Marco Polo Photon View



# 몬스터 추가하기

룸 안에 있는 모든 사람이 볼 수 있는 몬스터 인스턴스를 생성하기 위해서는 PhotonNetwork.Instantiate 메소드를 사용해야합니다. 이 메소드는 인스턴스에 ViewID 를 할당해주고 다른 플레이어들이 몬스터의 위치를 알 수 있도록 해줍니다. 이 사항을 작동하게 하려면 리소스 프리팹의 PhotonView 인스턴스 생성을 해야합니다.

유니티의 Instantiate 와 혼동하지 마세요. 유니티의 Instantiate 는 룸 안의 다른 플레이어를 갱신하지 않습니다.

룸에 들어갔을 때 할 것입니다. OnJoinedRoom 에서 아래처럼 PhotonNetwork.Instantiate 를 호출합니다.

Code Example C#:
1
GameObject monster = PhotonNetwork.Instantiate("monsterprefab", Vector3.zero, Quaternion.identity, 0);
cs

위 코드를 실행하면 몬스터가 공중에 떠서 나타나고 아래로 떨어지기 시작합니다.
불쌍한 몬스터입니다. 땅 위에 있는 몬스터를 생성해보겠습니다.
(새로운 Scene을 하나 생성후 작업하시면 됩니다. Script 오브젝트를 만들고 기존의 스크립트를 넣은 상태로 진행..)


# 지면 설정

다음 단계는 일반적인 "Unity work" 입니다. 씬에서 directional light(태양광)를 생성하고 태양이 비추는 것처럼 아래로 회전시킵니다.

플레인(Plane)을 추가하고 비율(Scale)을 10, 1, 10 으로 설정하고 0, 0, 0 의 위치로 이동시킵니다. 에셋 스토어에서 몇가지 물질을 받아보겠습니다. 에셋 스토어에서 "Free ArtskillZ Texture Pack 01" 을 검색하여 다운로드하고 임포트합니다.
마음에 드는 이미지를 선택하였으면 아래와 같이 "Plane"의 재질을 설정해줍니다.


Tiling에도 X, Y 가 각각 1, 1이 적혀 있을텐데 10과 10으로 바꾸어줍니다.


# 제어해보기

다시 진행 내용을 체크해보기 위해 2개의 클라이언트를 실행합니다. 명백하게 모든 몬스터들이 키 입력에 의해서 움직이고 카메라는 아무런 동작을 하지 않습니다. 입력과 카메라를 제어하는 모든 컴포넌트를 포함한 몬스터를 복제하였습니다.

이 문제를 해결하는 방법은 많이 있습니다. 간단한 방법은 "monsterprefab" 의 CharacterControl과 CharacterCamera 컴포넌트를 사용할 수 없게 설정하고 "우리의" 몬스터 인스턴스에게만 가능하도록 설정하는 것입니다.
PhotonNetwork.Instantiate는 생성된 게임 오브젝트를 리턴하기 때문에 우리의 몬스터를 변경하는 것은 매우 쉽습니다. 하나의 핸디캡은 별도로 하고 두 개의 스크립트는 UnityScript로 작성되어 있고 우리의 C# 코드에서는 아직 생성된 게임 오브젝트를 알 수 없습니다.

하나의 언어로 작성된 스크립트를 다른 언어의 스크립트에서 사용하려면 그 스크립트를 "Plugins" 폴더로 이동시키면 됩니다. 따라서 Monster 패키지에 있는 "Scripts" 폴더의 이름을 "Plugins" 로 이름을 변경하고 프로젝트의 루트로 이동하세요(에디터에서 드래그 & 드롭 기능을 사용해서.)

우리 몬스터를 인스턴스한 후에 몬스터에 대해서 CharacterControl 과 CharacterCamera 컴포넌트를 모두 제어할 수 있습니다.

Code Example C#:
1
2
3
4
5
6
7
8
public override void OnJoinedRoom()
{
    GameObject monster = PhotonNetwork.Instantiate("monsterprefab", Vector3.zero, Quaternion.identity, 0);
    CharacterControl controller = monster.GetComponent<CharacterControl>();
    controller.enabled = true;
    CharacterCamera camera = monster.GetComponent<CharacterCamera>();
    camera.enabled = true;
}
cs


# 부드러운 이동

지금까지 다른 플레이어들의 몬스터들은 움직이지만 걷지는 않습니다. 애니메이션이 아직 없습니다. 몬스터의 위치는 텔레포트 하는 상태로 갱신되고 있습니다. 이 사항은 코드를 통해 수정이 되어야합니다.

추가적인 스크립트가 필요합니다. Marco Polo 폴더에 "NetworkCharacter" 라는 C# 스크립트를 생성합니다. 이 스크립트를 "monsterprefab" 에 추가하고 PhotonView의 observed 컴포넌트로 만들어주시기 바랍니다.(드래그 & 드롭)

스크립트가 관찰되면 PhotonView 는 주기적으로 OnPhotonSerializeView 메소드를 호출합니다. 이 작업은 누가 PhotonView 를 생성 했는지에 따라 정보를 생성, 다른 곳에 전달하고, 수신된 정보를 관리할 수 있게 합니다.

PhotonStream 이 OnPhotonSerializeView 메소드로 전달되고 isWriting 의 값을 통해 PhotonStream의 원격 데이터 값을 쓸 것인지 읽을 것인지 판단할 수 있습니다.
먼저 위치와 회전 정보를 전송하고 수신해보겠습니다. 스크립트가 없는 PhotonView 처럼 같은 효과를 냅니다.

Code Example C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.Collections;
using UnityEngine;
 
public class NetworkCharacter : MonoBehaviour
{
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.isWriting)
        {
            // We own this player: send the others our data
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            // Network player, receive data
            this.transform.position = (Vector3)stream.ReceiveNext();
            this.transform.rotation = (Quaternion)stream.ReceiveNext();
        }
    }
}
cs

간단한 방식은 올바른 위치에 몬스터를 시간에 따라 시작위치에서 목적지까지 점진적으로 보정(Lerp)하며 부드럽게 위치를 이동시키는 것입니다. 우리가 원하는 것은 리모트 몬스터의 위치를 부드럽게 이동시키는 것입니다.(우리 몬스터는 우리가 움직이니 아무런 문제가 없습니다.)
Photon.MonoBehaviour(Photon 네임스페이스에 주의하세요) 스크립트를 작성하면 게임 오브젝트의 PhotonView에 접근할 수 있으며 "isMine" 프로퍼티를 제공합니다.

NetworkCharacter 에 Vector3 correctPlayerPos 와 Quaternion
correctPlayerRot 를 추가합니다. OnPhotonSerializeView 에서 새로운 값을 저장하고 Update에서 조금씩 적용합니다.

Code Example C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using UnityEngine;
using Photon;
 
public class NetworkCharacter : Photon.MonoBehaviour
{
    private Vector3 correctPlayerPos;
    private Quaternion correctPlayerRot;
 
    // Update is called once per frame
    void Update()
    {
        if (!photonView.isMine)
        {
            transform.position = Vector3.Lerp(transform.position, this.correctPlayerPos, Time.deltaTime * 5);
            transform.rotation = Quaternion.Lerp(transform.rotation, this.correctPlayerRot, Time.deltaTime * 5);
        }
    }
 
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.isWriting)
        {
            // We own this player: send the others our data
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }
        else
        {
            // Network player, receive data
            this.transform.position = (Vector3)stream.ReceiveNext();
            this.transform.rotation = (Quaternion)stream.ReceiveNext();
        }
    }
}
cs

다른 플레이어들의 몬스터들은 동일한 속도를 갖고 있지 않지만 짧은 시간안에 동일한 위치로 이동할 것입니다. 어떤 게임에서는 어떻게 해당 위치로 가는 것이 중요하기보다 정확한 위치에 캐리터가 있는 것이 더 중요한 경우도 있습니다.



다음 포스팅에서 나머지 내용과 관련되어 올리도록 하겠습니다.


참고한 사이트 : https://doc.photonengine.com/ko-kr/pun/current/tutorials/tutorial-marco-polo


반응형