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들에게 다 똑같이 생성되게 만듬
=> 생성만 동기화 시킨 상태
2. 내 캐릭터만 움직일 수 있게 변경 -> Photon View의 IsMine == True일 때만!
- PlayerMove Script 변경
- using Photon.Pun 추가
- MonoBehaviourPun 클래스 변경 (자기한테 photonView가 붙어있으면 가져오게 하는 기능 - GetComponent 안해도 됨)
- 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 상속받기
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 : 나중에 들어왔을때 한번에
누구의 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 재생 시 오류 발생 ※ => 왜?