본문 바로가기
유니티/Photon

Photon3

by SUGI_ 2022. 9. 11.

Photon PhotonView, IsMine, PhotonTransformView

  • 소켓 통신을 통한 플레이어 생성 구조

클라이언트 -> 게임서버로 신호를 보냄

게임서버- > 클라이언트로 답신신호를 보내어 캐릭터 등을 생성가능

(각각의 클라이언트가 송/수신을 하는것)

 

1) Photon을 이용한 씬 전환

PhotonNetwork.LoadLevel(“씬 이름”);

ConnectionManager Script ->

PhotonNetwork.LoadLevel(“LobbyScene”);

LobbyManager Script -> 

PhotonNetwork.LoadLevel("GameScene");

우리가 사용하는 ScnenManager로 씬 전환을 사용하고 있지만 씬이 로드 되는 순간 들어오는 네트워크가 유실 되지 않기 위해서 사용 (씬이 바뀌는 도중에 바뀌었다는 메시지를 다른 pc도 받아야하는데 전환되는 순간이면 해당 메시지를 잊어버릴 수 있는 상황이 생겨버린다. 그걸 막아주기 위한 함수가 LoadLevel)

 

2) Player Prefabs 생성

1. Prefabs 만들기 - 꼭 ! Resources 폴더에 있어야 함

2. Photon View Component 추가

- 내가 생성한 것인지 남이 생성한 것인지 판별 가능 / 서로 데이터 연결해주는 기능 / 동기화 처리 가능

3. GameScene Player는 삭제 - 방에 들어왔을 때 생성 될 수 있게

 

3) GameScene

GameManager - Script 생성 

GameManager - Create Empty 생성

 

1. 플레이어를 생성 PhotonNetwork.Instantiate("Prefab 이름", 위치, 회전)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;

public class GameManager : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 플레이어를 생성한다.
        PhotonNetwork.Instantiate("Player", Vector3.zero, Quaternion.identity);
    }

우리가 사용했던 Instantiate (= 내 PC에서만 볼 수 있음) 그래서 포톤에서 제공해주는 기능을 사용해야 함
접속되어 있는 PC들에게 다 똑같이 생성되게 만듬

내가 생성했기 때문에 IsMine = True
아니면 False

=> 생성만 동기화 시킨 상태

 

2. 내 캐릭터만 움직일 수 있게 변경 -> Photon View의 IsMine == True일 때만!

  • PlayerMove Script 변경

- using Photon.Pun 추가

- MonoBehaviourPun 클래스 변경 (자기한테 photonView가 붙어있으면 가져오게 하는 기능 - GetComponent 안해도 됨)

 

위처럼 사용도 가능하지만 MonoBehaviourPun 클래스를 사용함으로써 이 기능을 하지 않아도 됨!

- if(photonView.IsMine == false) return;

  • PlayeRot Script 변경 (위와 같이 적용하거나 내것이 아닐 때 비활성화 시켜도 됨)

- 내 카메라만 실행 될 수 있게 설정

Player Prefab > CamPos를 내것일 때만 켜지게 코드 추가 (초기 - 비활성화 상태)

2. 움직임 / 회전 / 크기 를 동기화 시켜주는 Component

- Player Prefab > Photon Transform View 

무엇을 동기화 할 것인지 체크

Photon View는 전체적으로 네트워크를 관리해줌 / Photon View가 우선적으로 붙어있어야지 동기화 가능

Photon Transfrom View를 대신한 다른 방법을 사용

(인터넷 상황이 안좋아 신호를 못 받는 경우도 생김 그래서 다른 방법을 사용해보도록 함)

Photon 닉네임 설정

1. Player Prefab > UI text 추가

- NickName Scal 줄이기 / Canvas의 위치를 올리기 (캐릭터 머리 위에 위치하겠끔)

2. 시작할 때 이름 셋팅
PlayerMove >

using UnityEngine.UI;

public class PlayerMove : MonoBehaviourPun
{
  // 닉네임 UI
    public Text nickName;
    
    void Start()
    {
        // 닉네임 설정
        nickName.text = photonView.Owner.NickName;
    }
}

nickName.text = photonView.Owner.NickName;

(Owner = 생성한 사람 / Player 주인)

 

3. JoinLobby 진입 전에 내 닉네임 설정 

PhotonNetwork.NickName = "김현진_" + Random.Range(1, 10000);

(Random.Range 하는 이유는 Test하기 위해서)

추후 게임 접속화면에서 닉네임 설정 하면 넣어지게 할 것임

Photon OnPhotonSerializeView 동기화

Photon Transform View  대신 코드로!

1. PlayerMove > IPunObservable 상속받기 

Interface는 함수에 대한 정의가 들어가 있는데 꼭 구현해야 함
내용 없더라도 함수가 존재해야지 에러가 안남
그렇기 때문에 Photon Transform View도 같은 개념임

2. Photon Transform View 삭제 / PlayerMove에서 구현할 것임

    // 1초에 몇번 보내기 설정가능
    // stream에는 value 타입만 넣을 수 있음
    // 게임오브젝트, Transform 넘기기 X
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        // 데이터 보내기 //내 PC
        if(stream.IsWriting) //isMine == true
        {
            //position, rotation
            //SendNext는 List로 구성되어 있음 //다른 데이터도 보낼 수 있음
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }

        // 데이터 받기 //다른 사람 PC에서 호출됨
        else if(stream.IsReading) //isMine == false
        {
            //보낸 순서대로 받음
            //오브젝트형으로 되어있기 때문에 강제 형변환 필수
            transform.position = (Vector3)stream.ReceiveNext();
            transform.rotation = (Quaternion)stream.ReceiveNext();
        }
    }
}

단점! 1초에 몇번 보내는지 데이터 설정이 가능하기 때문에 호출 빈도가 바뀜

그래서 텀이 느려지면 뚝뚝 끊기는 현상이 일어남

=> 다른 플레이어들의 부드러움 움직임을 위해 Lerp로직 사용

Photon OnPhotonSerializeView 동기화 (Lerp 보정)

전역변수 담아두기

//도착 위치
Vector3 receivePos;
//회전되야 하는 값
Quaternion receiveRot;
// 보간 속력
public float lerpSpeed = 100;
  void Update()
    {
        // 만약에 내것이 아니라면 함수를 나가겠다
        // if (photonView.IsMine == false) return;
        // 변경!

        // 만약 내것이라면 움직임
        if(photonView.IsMine)
        {
            // 사용자의 입력에 따라
            // Input.GetAxisRaw -> 정확하게 바로 적용
            float h = Input.GetAxisRaw("Horizontal");
            float v = Input.GetAxisRaw("Vertical");

            // 방향을 정하고
            // = Vector3 dir = new Vector3(h, 0, v);
            // 내 방향을 앞방향으로 하고 싶다면
            Vector3 dir = transform.forward * v + transform.right * h;
            dir.Normalize();

            // 만약에 바닥에 닿아있다면 yVelocity를 0으로 하자
            if (cc.isGrounded)
            {
                yVelocity = 0;
            }

            // 만약 점프키를 누른다면 
            if (Input.GetButtonDown("Jump"))
            {
                // yVelocity에 jumpPower를 셋팅
                yVelocity = jumpPower;
            }

            //yVelocity값을 중력으로 감소시킨다.
            yVelocity += gravity * Time.deltaTime;

            // dir.y에 yVelocity값을 셋팅
            dir.y = yVelocity;

            // 이동하고 싶다.
            cc.Move(dir * speed * Time.deltaTime);
        }

        // 내것이 아니라면
        else
        {
            //Lerp를 이용해서 목적지, 목적방향까지 이동 및 회전
            transform.position = Vector3.Lerp(transform.position, receivePos, lerpSpeed * Time.deltaTime);
            transform.rotation = Quaternion.Lerp(transform.rotation, receiveRot, lerpSpeed * Time.deltaTime);
        }
       
    }
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if(stream.IsWriting) //isMine == true
        {
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
        }

        else if(stream.IsReading) //isMine == false
        {
            receivePos = (Vector3)stream.ReceiveNext();
            receiveRot = (Quaternion)stream.ReceiveNext();
        }
    }

호출 빈도

GameManager>

public class GameManager : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 1. OnPhotonSerializeView 호출빈도
        PhotonNetwork.SerializationRate = 60;
        
        // 2. Rpc(원격 프로시저 호출)호출빈도 //단발성 원할 때 한번
        PhotonNetwork.SendRate = 60;

        // 플레이어를 생성한다.
        PhotonNetwork.Instantiate("Player", Vector3.zero, Quaternion.identity);
    }

그래도 약간 끊기기에 OnPhotonSerializeView 호출 빈도와 Rpc 호출 빈도를 높여준다.  (기본값이 100)

 

OnPhotonSerializeView VS Rpc

공통점 : 데이터를 보내고 받기

차이점

OnPhotonSerializeView : 연속적인 데이터

Rpc : 단발성 원할 때 한번 (ex) 총알 발사 / Update에서 계속 호출할 수 있지만 비효율

Photon 총알발사(PhotonNetwork.Instantiate), 닉네임 Billboard 적용

1. PlayerFire>

PlayerMove 방식과 동일

- using Photon.Pun 추가

- MonoBehaviourPun 

- 내것이 아니라면 함수를 끝낸다 if (photonView.IsMine == false) return;

=> 나만 쏠 수 있게 처리

 

총알 동기화 (상대방 PC에서도 보일 수 있도록)

  //1. 왼쪽 알트키를 누르면
        if(Input.GetKeyDown(KeyCode.LeftAlt))
        {
            //2. 총알공장 총알 만든다
            //GameObject bullet = Instantiate(bulletFactory);
            //3. 총구에 위치 시킨다
            //bullet.transform.position = firePos.position;
            //4. 총알의 앞방향을 총구방향으로 한다
            //bullet.transform.forward = firePos.forward;

            PhotonNetwork.Instantiate("Bullet", firePos.position, firePos.rotation);
        }

Bullet Prefab > Photon View 추가 / Resources 폴더에 추가

 

2. 다른 사람의 닉네임도 카메라 앞방향을 계속 볼 수 있도록

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

public class Billboard : MonoBehaviour
{
    void Update()
    {
        //나의 앞방향을 카메라 앞방향으로 셋팅하자
        transform.forward = Camera.main.transform.forward;
    }
}

Player > Canvas에 스크립트 추가

Photon Ray총알발사 (PunRPC)

FireRay bulletImpact 효과를 Instantiate방식으로 해도 되지만 RPC방식을 사용!

- 맞은 지점의 P / normal 정보를 Server에게 전달  (Show함수의 매개변수로 들어감) 

- RPC를 이용하여 함수를 호출 -> 서버에 전달하기 (총알자국) 함수를 사용했을때 효과가 나오는게아니라 서버에서 호출되었을때 출력됨

 

1. 기존 효과 재생을 함수로 지정

- photonView.RPC("함수 이름", RpcTarget.All, 매개변수 값);

- RPC로 실행되는 함수는 반드시 [PunRPC] 붙여줘야 한다

<RpcTarget 속성> -> PC를 이야기 하는 것임

- All : 모두 (우리는 거의 이거 사용)

- MasterClient : 방장만

- Others : 나빼고 다

- AllBuffered : 나중에 들어왔을때 한번에

 

RpcShowBulletImpact Rpc로 등록됨

누구의 photonView를 사용하느냐에 따라 실행되는 대상이 달라진다.

 

PhotonNetwork.Instantiate (자기 PC에서 생성) / RPC (모든 PC에서 자체 생성) 중 무엇을 사용할 지 고민 필요!

 

 

오늘의 정리

1.닉네임
PhotonNetwork.NickName = “이름”;

2. 씬전환
PhotonNetwork.LoadLevel(“씬이름”);

3. MonoBehaviourPun 상속
- photonView 변수에 자동으로 PhotonView가 담긴다.
→ MonoBehaviourPun 클래스에 PhotonView를 GetComponent

4. PhotonView (5 / 6 전제조건)
- 데이터를 보내고/ 받고
- 플레이어간의 데이터를 연결 시켜주는 기능
- 내것 아닌지 판단 (isMine)

5. 플레이어 생성 
-  Player Prefab을 Resources 폴더에 꼭!
-  PhotonNetwork.Instantiate(“Player”, 생성위치, 생성 회전값);

6. Photon Transform View
- 위치, 크기, 회전 값을 동기화

7. IpunObservable -> OnPhotonSerializeView(중요, 인터페이스이므로 반드시 구현)  
- stream.IsWriting == true 데이터를 보낸다. = IsMine 
→ value(position, rotation) 값만 가능, 클래스는 못보낸다.
- stream.IsReading == true 데이터를 받는다.
→ IsWriting 순서대로 받아야한다
→ 오브젝트 형식으로 들어오므로 강제적 형변환 필수

8. OnPhotonSerializeView 호출빈도
- PhotonNetwork.SerializationRate = 60;
- 100이 최대
- 연속적인 데이터(포지션, 로테이션값)

9. RPC 호출빈도(Remote Procedure call :원격프로시저호출)
-  PhotonNetwork.SendRate : 60;
- 단발성(총알을 한번 발사해줘!), 1초에 몇번씩 RPC로 호출된 애를 보내겠다
- 이벤트 받아서 동기화 처리 (클릭 , 버튼,  충돌 등)  

 

※ 빌드파일 -> 내 PC 재생 시 오류 발생   => 왜?

728x90

'유니티 > Photon' 카테고리의 다른 글

8 / 17  (0) 2023.08.17
8 / 16  (0) 2023.08.16
Photon Voice  (0) 2022.11.07
Photon 2  (0) 2022.09.10
Photon 1  (0) 2022.09.10