UNITY

[Unity] Network : Photon2 정리 - 2

Early_Birdy 2021. 6. 4. 18:49
728x90

그렇다면 이제는 캐릭터 오브젝트의 스크립트들이 네트쿼으용으로 어떻게 래핑 되는지를 알아보자.

앞서 캐릭터의 Input을 관리하는 스크립트를 보면 네트워크 요소 코드가 없기에 무빙입력을 하면 로컬 오브젝트와 리모트 오브젝트도 함께 움직이게 된다.

 

1. PlayerInput to Photon

코드를 다시 보게 되면 아래와 같다.

using UnityEngine;

public class PlayerInput : MonoBehaviour
{
  public string moveAxis = "Vertical";
  public string rotateAxis = "Horizontal";
  public string interactiveKey = "F";

  public float move { get; private set; }
  public float rotate { get; private set; }

  void Start(){}
  
  void Update()
  {
    move = Input.GetAxis(moveAxis);
    rotate = Input.GetAxis(rotateAxis);
  }
}

 

이 코드를 PhotonView props를 통해 Photon View 컴포에 접근하기위해 클래스 상속도 바꾸고 로컬 캐릭터만을 움직일 수 있게 바꾸면

using Photon.Pun;
using UnityEngine;

public class PlayerInput : MonoBehaviourPun
{
  public string moveAxis = "Vertical";
  public string rotateAxis = "Horizontal";
  public string interactiveKey = "F";

  public float move { get; private set; }
  public float rotate { get; private set; }

  void Start(){}
  
  private void Update()
  {
    if (!photonView.IsMine) { return; }

    move = Input.GetAxis(moveAxis);
    rotate = Input.GetAxis(rotateAxis);
  }
}

이렇게 된다.

간단하게 상속을 바꿔주고 매 프레임마다 로컬인지 아닌지를 확인해주면 된다.

로컬이 아니라면 계속해서 입력을 받지 않게 함수를 끝내버리고 로컬이라면 조건문에 들어가지 않으니 입력을 받게된다.

 

2. PlayerMove to Photon

입력을 받았으니 해당 입력으로 움직임을 구현하는 스크립트도 동일하다.

로컬 옵젝으로만 받은 입력으로 로컬 옵젝만 움직여야한다. 수정사항도 위와 같이 상속을 바꾸고, 조건문으로 로컬 여부를 검사한다.

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

public class PlayerMove : MonoBehaviourPun
{
  public float moveSpeed = 5f;
  public float rotateSpeed = 180f;
  private PlayerInput playerInput;
  private Rigidbody playerRigidbody;
  private Animator playerAnim;
  void Start()
  {
    playerInput = GetComponent<PlayerInput>();
    playerRigidbody = GetComponent<Rigidbody>();
    playerAnim = GetComponent<Animator>();
  }


  void FixedUpdate()
  {
    if (!photonView.IsMine)
    {
      return;
    }
    Move();
    Rotate();
  }

  private void Move()
  {
    if (playerInput.move != 0)
    {
      playerAnim.SetBool("isWalking", true);
    }
    else
    {
      playerAnim.SetBool("isWalking", false);
    }
    Vector3 moveDistance = playerInput.move * transform.forward * moveSpeed * Time.fixedDeltaTime;
    playerRigidbody.MovePosition(playerRigidbody.position + moveDistance);
  }

  private void Rotate()
  {
    float turn = playerInput.rotate * rotateSpeed * Time.fixedDeltaTime;
    playerRigidbody.rotation = playerRigidbody.rotation * Quaternion.Euler(0, turn, 0);
  }
}

 

3. RPC

호스트에 처리를 위임하고 호스트가 처리 결과를 클라들에게 전파하려면 RPC(remote procedure call)가 필요하다.

특정 함수나 메서드나 여러 처리를 네트워크를 넘어서 다른 클라이언트에서 실행하도록 하게하는 것이다.

예를들어 FPS멀티 게임에서 두 참가자 A,B가 있다고하자.

방장 호스트를 A라고 할때, 클라 B의 캐릭터 오브젝트 b가 총을 쏘는 처리인 shot()을 자신의 클라이언트에서 직접 실행하는 것이 아니라

RPC를 통해 호스트 A의 월드 안에있는 캐릭터 오브젝트 b에서 shot()을 실행하도록 요청하는 것이다.

그러면 호스트 A는 자신의 월드에 있는 캐릭터 b에서 shot()을 실행하고 그 결과를 다른 클라들에게 동기화를 시키는 것이다.

 

캐릭터의 이동, 애니메이션은 메서드라기 보다는 컴포넌트로 직접 변화, 전파를 할 수 있기에 RPC로 처리하지 않아도 되는데

임의의 메서드들은 RPC를 통해 동기화처리를 해야한다.

 

예를들어 메타버스 게임에서 ChangeName() 메서드를 통해 자신의 캐릭터 이름을 바꾼다면 RPC로 호스트에게 보내 처리를 동기화 시키는 식이다.

 

예시 코드로 LivingEntity 에 쓰이는 스크립트들을 RPC로 변환하는 과정을 보면

원래코드는 아래와 같다.

using System;
using UnityEngine;

public class LivingEntity : MonoBehaviour, IDamageable
{
  public float startingHealth = 100f;
  public float health { get; protected set; }
  public bool dead { get; protected set; }
  public event Action onDeath;

  protected virtual void OnEnable()
  { // virtual 사용으로 이 클래스를 상속하는 자식들은 OnEnable 메서드를 오버라이드로 사용가능 함.
    dead = false; // 죽지 않은 상태로 시작
    health = startingHealth;
  }

  public virtual void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal)
  {
    health - +damage;
    if (health <= 0 && !dead) { Die(); }
  }

  public virtual void RestoreHealth(float newHealth)
  {
    if (dead) { return; }
    health += newHealth;
  }

  public virtual void Die()
  {
    if (onDeath != null) { onDeath(); } // onDeath 이벤트의 등록된 메서드 실행
    dead = true;
  }
}

이런 메서드들을 RPC를 통해 호스트에 위임시켜 실행결과를 클라들에게 전파해줘야한다.

그러면 RPC 메서드로 다시 래핑해야한다.

using System;
using UnityEngine;
using Photon.Pun;

public class LivingEntity : MonoBehaviourPun, IDamageable
{
  public float startingHealth = 100f;
  public float health { get; protected set; }
  public bool dead { get; protected set; }
  public event Action onDeath;

  [PunRPC]
  // 호스트 --> 모든 클라 방향으로 체력과 사망상태를 동기화하는 메서드
  public void ApplyUpdatedHealth(float newHealth, bool newDead)
  {
    health = newHealth;
    dead = newDead;
  }
  protected virtual void OnEnable()
  { // virtual 사용으로 이 클래스를 상속하는 자식들은 OnEnable 메서드를 오버라이드로 사용가능 함.
    dead = false; // 죽지 않은 상태로 시작
    health = startingHealth;
  }

  [PunRPC]
  public virtual void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal)
  {
    if (PhotonNetwork.IsMasterClient)
    { //호스트 라면
      health - +damage;

      //호스트에서 클라로 동기화
      photonView.RPC("ApplyUpdatedHealth", RpcTarget.Others, health, dead);

      // 다른 클라도 OnDamage 실행하도록 하게함
      photonView.RPC("OnDamage", RpcTarget.Others, damage, hitPoint, hitNormal);
    }

    if (health <= 0 && !dead) { Die(); }
  }

  [PunRPC]
  public virtual void RestoreHealth(float newHealth)
  {
    if (dead) { return; }
    if (PhotonNetwork.IsMasterClient) // 호스트라면
    {
      health += newHealth;
      
      // 호스트에서 클라로 동기화
      photonView.RPC("ApplyUpdatedHealth", RpcTarget.Others, health, dead);

      // 다른 클라이언트도 아래 함수 실행하도록 함
      photonView.RPC("RestoreHeadlth", RpcTarget.Others, newHealth);
    }

  }

  public virtual void Die()
  {
    if (onDeath != null) { onDeath(); } // onDeath 이벤트의 등록된 메서드 실행
    dead = true;
  }
}

[PunRPC] 로 선언된 메서드는 다른 클라이언트에서 원격으로 실행할 수 있다.

자신의 월드에서 메서드를 실행하지 않고 다른 클라에서 실행하거나 자신을 포함한 모든 클라에서 메서드를 실행할 수도 있다.

원격으로 실행할 때는 Phton View 컴포의 RPC()메서드를 사용하는데

RPC()의 매개변수로는 메서드 이름, 대상 클라이언트, 원격 실행할 메서드에 전달할 값(optional) 이 있다.

 

ApplyUpdatedHealth 메서드는 새로운 체력값과 새로운 사망 상태값을 받아서 기존의 값들을 갱신해준다.

호스트가 다른 클라이언트들에게 특정 캐릭터의 체력이 몇이 남았고 죽었는지 살았는지를 알려주기위해 PunRPC속성으로 선언해주고

photonView.RPC("ApplyUpdatedHealth", RpcTarget.Others, health, dead) 를 통해알려주는 식이다.

그러면 호스트에서의 a의 체력과 사망상태가 다른 모든 클라이언트 월드의 a에게 적용된다.

 

Ondamage도 PunRPC로 선언되어서 다른 클라이언트가 원격으로 실행할 수 있다.

IsMasterClient를 통해 호스트만 조건문 내의 코드를 실행할 수 있는데

체력을 깎는 health -= damage는 호스트에서 계산하고 ApplyUpdatedHealth를 원격 실행하여 변경된 체력들을 다른 클라에게 적용한다.

순서를 살표보면

1. 호스트에서의 LivingEntity가 공격을 당해 OnDamage()가 실행됨.

2. 호스트에서 체력을 변경하고 다른 클라에게 체력을 동기화.

3. 호스트가 다른 클라들의 LivingEntity들의 OnDamage()를 원격으로 실행됨

 

여기서 주목할 부분은 3번이다.

호스트에서의 OnDamage()는 IsMasterClient로 RPC구문이 실행이 되는데

클라이언트에서의 OnDamage()는 밑에 조건문만을 실행하게 된다. 그래서 health가 0일때 죽었는지 안죽었는지 판단을 각자 클라에게 맡기는 방식이다.