Player 동기화
- PhotonTransformView 동기화
- OnPhotonSerializeView 동기화
- PhotonAnimatorView동기화
구현 방법
PhotonTransformView 동기화
1. 컴포넌트 추가 및 설정
Player 프리팹에 PhotonView 컴포넌트와 PhotonTransformView 컴포넌트를 추가, 설정. 특별한 경우가 아니면 위와 같이 설정한다.
* Synchronize Options : 동기화할 Transform 항목을 설정 (불필요한 항목은 서버에 부담이 되기 때문에 체크 해제)
* Use Local : 위치 및 회전 값을 로컬 좌표계, 전역 좌표계 설정
https://kkln2486.tistory.com/429
2. 이동 스크립트 수정
void Update()
{
if(_photonView.IsMine)
{
float x = Input.GetAxis("Horizontal");
float y = Input.GetAxis("Vertical");
transform.position += new Vector3(x, y, 0) * Time.deltaTime;
}
}
IsMine이 true인 캐릭터만 움직이도록 설정. 그렇지 않으면 상대 캐릭터도 함께 조작되기 때문에 나의 캐릭터만 조작 되도록 설정해야 한다.
3. 객체 동기화
// PhotonView를 사용하여 플레이어를 생성하는 간단한 로직
private void OnJoinPlayer()
{
PhotonNetwork.Instantiate("Player", new Vector3(0, 0, 0), Quaternion.identity);
}
// 프리팹 파괴
PhotonNetwork.Destroy();
PhotonView 컴포넌트와 PhotonTransformView 컴포넌트를 추가한 Player 프리팹을 생성. 위 함수로 Player를 생성하면 나의 PC에는 IsMine이 True인 Player, 상대 PC에는 IsMine이 false인 동일한 Player가 생성된다.
- 반드시, 유니티 내장 Instantiate()가 아닌 해당 메소드를 사용해야 한다.
- Room을 생성하거나, 참여한 뒤, 해당 메소드를 사용해야 한다.
- 프리팹은 Resources 폴더에 있어야 한다.
이렇게 동기화를 완료하면 캐릭터의 Position, Ratation만 동기화 되고 상대 Player의 애니메이션은 idle 상태만 유지한다.
OnPhotonSerializeView 동기화
※ PhotonTransformView 끊김 현상이 발생할 경우?
유니티 3D의 빠른 이동이나 회전을 구현하는 경우, PhotonTransformView를 사용하면 끊김 현상이 심하게 발생한다. 그럴때는 PhotonTransformView를 사용하지 않고 OnPhotonSerializeView 함수를 통해 Player의 Position, Rotation 데이터를 직접 서버에 전송하고, 전송 받은 데이터를 통해 각각 클라이언트에서 직접 이동, 회전을 구현해야 한다.
* 즉, 유니티 2D나 간단한 이동, 회전을 동기화 할 때는 PhotonTransformView를 사용하고, 그 외는 OnPhotonSerializeView 함수를 통해 구현해야 자연스러운 움직임을 동기화 가능!
스크립트 작성
public Vector3 _playerPosition;
public Quaternion _playerRotation;
private void Update()
{
if(_photonView.IsMine) Move();
else
{
transform.position = Vector3.Lerp(transform.position, PlayerPosition, 100 * Time.deltaTime);
transform.rotation = Quaternion.Lerp(transform.rotation, PlayerRotation, 100 * Time.deltaTime);
}
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
// 데이터 보내기 (isMine == true)
if (stream.IsWriting)
{
stream.SendNext(transform.position);
stream.SendNext(transform.rotation);
}
// 데이터 받기 (isMine == false)
else
{
_playerPosition = (Vector3)stream.ReceiveNext();
_playerRotation = (Quaternion)stream.ReceiveNext();
}
}
IsMine이 true인 경우는 기존 이동 코드를 사용하고, false인 경우에는 전송 받은 데이터를 통해 이동, 회전을 위와 같이 구현. Lerp 함수(선형보간)를 사용해서 자연스럽게 감속해서 부드러운 움직임을 구현한다.
* Lerp 함수에 Time.deltaTime에 100을 곱한 이유는 Time.deltaTime의 값이 너무 작으면 목적지까지 천천히 변하기 때문. 부드러운 움직임이 구현되는 적당한 값을 찾아서 곱해야 함. (필자는 100을 곱하니 부드러운 움직임이 구현 됨)
private void Awake()
{
PhotonNetwork.SendRate = 60;
PhotonNetwork.SerializationRate = 60;
}
위와 같이 OnPhotonSerializeView와 RPC의 호출 빈도를 수정하면 더욱 자연스러운 움직임을 동기화할 수 있다.
애니메이션 동기화
1. PhotonAnimatorView 컴포넌트 추가
Player의 최상위 부모 오브젝트에 PhotonView와 PhotonTransformView 컴포넌트가 추가되어 있고, 그 자식 오브젝트 중, Animator가 있는 오브젝트에 PhotonAnimatorView 컴포넌트를 추가
PhotonView 컴포넌트의 Observed Components에 PhotonAnimatorView가 잘 연결되어 있는지 확인 (Observable Search가 Auto Find All이기 때문에 자동으로 연결된다.)
2. PhotonAnimatorView 설정
Synchronize Parameters
- Disabled : 동기화하지 않음
- Discrete : 초당 10번 동기화
- Continuous : 매 프레임 동기화
* Update문에서 SetBool을 통해 Run Parameter를 변경하고 있기 때문에 Discrete 설정 시, 애니메이션 동기화가 정상적으로 작동하지 않음. Continuous로 설정해야 한다.
* 그 외, 일반적으로 Update문에서 판별하는 경우가 아니라면, Discrete로 설정해도 충분하다.
* 현재 필자가 개발하고 있는 게임은 Trigger Parameter를 Discrete나 Continuous로 설정하니, 제대로 모든 애니메이션이 동기화 되지 않았다. 그래서 모든 항목을 Disabled로 설정하고, RPC 함수를 통해 Trigger를 실행해야 한다!
3. 스크립트 수정
public void PlayerAttack()
{
if (_photonView.IsMine) _playerAnimator.SetTrigger("Attack");
}
위 코드와 같이 IsMine이 true일 경우에만 애니메이션이 실행할 수 있도록 수정. 수정하지 않으면, 나의 캐릭터와 적 캐릭터 모두 동시에 애니메이션이 실행된다.