Bolt 103 - 프로퍼티와 콜백

'Bolt 101'에서 Bolt 실행과 네트워크를 통한 프리팹의 인스턴스하는 방법을 학습했습니다. 이 파트에서는 네트워크상에서 프로퍼티 복제(직렬화)에 대한 것을 살펴보겠습니다.

'Cube' 프리팹과 이 프리팹에 대한 'CubeState' 상태를 생성했을 때, 우리는 'CubeTransform' 이라는 프로퍼티를 추가했습니다. 이제 이 프로퍼티를 사용하고 Bolt 엔티티 코딩을 할 시간입니다.

'Tutorial/Scripts' 폴더에서 'CubeBehaviour' 스크립트를 생성하고 텍스트 편집기에서 이 스크립트를 여세요. 기본 Unity 메소드를 없애고 클래스를 일반적인 MonoBehaviour 대신 Bolt.EntityBehaviour<ICubeState> 에서 상속 받도록 해 주세요.

using UnityEngine;
using System.Collections;

public class CubeBehaviour : Bolt.EntityBehaviour<ICubeState> {

}

CubeBehaviour 에 코드를 추가하기 전에 Bolt.EntityBehaviour<ICubeState> 가 정확히 무엇을 하는지 살펴 보도록 하겠습니다. ICubeState 는 상태에 정의해 놓은 모든 프로퍼티들을 노출 시켜주는 Assets/Bolt Engine/Compile Assembly 를 실행할 때 Bolt가 자동적으로 생성해주는 C# 인터페이스 입니다. 이것은 상태의 모든 프로퍼티들에 대해 쉽고 정적으로 타입된 접근을 주게 됩니다.

Bolt 소스 코드에서 Bolt.EntityBehaviour<T> 로 정의되어 있는 상속한 클래스는 지네릭 파라미터로써 사용하기 원하는 상태의 타입을 받습니다. 이것은 Bolt에게 상태의 타입을 CubeBehaviour 내에서 접근하고 싶다고 알려주는 것입니다.

'Bolt 101'의 Bolt.GlobalEventListener 클래스에 대해서 Bolt.EntityBehaviour<T>MonoBehaviour 상속받았기 때문에 여기에서도 일반적인 모든 Unity 메소드를 사용할 수 있습니다.

이제 Attached 라고하는 Bolt의 특수한 메소드를 구현할 것입니다. 이것은 Unity에 있는 Start 메소드와 동일하다고 생각할 수 있습니다. 하지만 Bolt 내에서 게임 오브젝트가 설정이 완료된 이후 호출되고 네트워크 상에서 존재합니다.

using UnityEngine;
using System.Collections;

public class CubeBehaviour : Bolt.EntityBehaviour<ICubeState> {
  public override void Attached() {

  }
}

중요: 'Bolt 101' 의 SceneLoadLocalDone 메소드처럼 구현 메소드 버전의 public override ... 를 사용하며 Unity의 방식은 아닙니다.

Attached 내부에 라인 한 줄을 추가할것입니다: state.SetTransforms(state.CubeTransform, transform); 세부적으로 살펴보겠습니다.

  • state. - ICubeState 인 엔티티의 상태에 접근합니다.
  • transform - 게임 오브젝트의 트랜스폼
  • CubeTransform. - 'Bolt Assets' 과 'Bolt Editor' 윈도우 상태에서 정의한 CubeTransform 프로퍼티.
  • SetTransforms - 여기에서 Bolt에게 현재 게임 오브젝트에서 붙여진 CubeBehaviour 스크립트의 트랜스폼을 사용하고 네트워크 상에서 복제하라고 알려줍니다.

완성된 샘플은 다음과 같습니다.

using UnityEngine;
using System.Collections;

public class CubeBehaviour : Bolt.EntityBehaviour<ICubeState> {
  public override void Attached() {
    state.SetTransforms(state.CubeTransform, transform);
  }
}

CubeBehaviour 에 아주 간단한 이동 코드를 추가할 것 입니다. 이것을 하기 위해서 일반적인 Unity void Update() ... 를 사용할 수도 있으나 Bolt는 SimulateOwner 라고 하는 다른 메소드를 제공합니다. 우리는 Attached 에서 한것과 동일한 방식으로 SimulateOwner 메소드를 구현하여 이동 코드를 추가합니다.

using UnityEngine;
using System.Collections;

public class CubeBehaviour : Bolt.EntityBehaviour<ICubeState> {
  public override void Attached() {
    state.SetTransforms(state.CubeTransform, transform);
  }

  public override void SimulateOwner() {
    var speed = 4f;
    var movement = Vector3.zero;

    if (Input.GetKey(KeyCode.W)) { movement.z += 1; }
    if (Input.GetKey(KeyCode.S)) { movement.z -= 1; }
    if (Input.GetKey(KeyCode.A)) { movement.x -= 1; }
    if (Input.GetKey(KeyCode.D)) { movement.x += 1; }

    if (movement != Vector3.zero) {
      transform.position = transform.position + (movement.normalized * speed * BoltNetwork.frameDeltaTime);
    }
  }
}

SimulateOwner 내부는 표준 Unity 와 같이 아주 단순합니다. Bolt에만 있는 것은 BoltNetwork.frameDeltaTime 프로퍼티 입니다. 현재 이것은 단순히 Unity에 내장된 Time.fixedDeltaTime 을 래핑하는 것이지만, 향후 호환성을 위해 이 Bolt 버전 사용을 권장합니다.

계속하기전에 잠시만 SimulateOwner 이 도대체 무엇인지 살펴보도록 하겠습니다. BoltNetwork.Instantiate 라고 하는 컴퓨터는 항상 "소유자"로 간주되고 SimulateOwner 가 호출될 곳으로 유일하게 그 컴퓨터에서 호출됩니다. 이 의미는 어떠한 networkView.isMine 체크 없이 프리팹을 인스턴스화한 그 컴퓨터에서만 수행되는 이동 또는 다른 것들에 대한 특정 코드를 작성할 수 있다는 것입니다.

게임을 시작하기전에 수행해야할 마지막 작업은 CubeBehaviour 스크립트를 'Cube' 프리팹에 붙이는 것입니다.

게임을 빌드하고 두 개의 인스턴스를 시작했다면 하나는 서버이고 하나(또는 그 이상)는 클라이언트로 큐브를 이동시킬 수 있고 네트워크상에서 올바르게 복제할 것 입니다.

원격 큐브가 약간 끊어진다는 것을 알 수 있습니다. 부드럽게 보간하지 않고 새로운 위치에 "스내핑"합니다. Bolt에는 이것을 처리 할 수 있는 기능이 내장되어 있으므로 사용 가능하도록 하십시오!

우리가 해야 할 첫 번째 일은 'CubeState' 에서 'CubeTransform' 속성에 대해 '보간'을 사용할 수 있도록 해 주는 것입니다. Bolt Assets 윈도우가 아직 열리지 않았다면 Window/Bolt Engine/Assets 윈도우를 열고 'CubeState' 를 클릭하여 편집하세요.

'CubeTransform' 프로퍼티의 설정 아래에 있는 'Smoothing Algorithm' 필드를 찾아 'None' 에서 'Interpolation' 로 변경합니다. 이제 Bolt 에게 변경사항을 알려주기 위해 'Bolt Assets' 윈도우의 녹색화살표를 클릭하거나 Assets/Bolt Engine/Compile Assembly 명령어를 실행합니다.

이제 게임을 다시 빌드하고 시작하면 큐브가 다른 플레이어에서도 부드럽게 보간됩니다. Bolt는 일반적으로 '마이크로 스터터 (micro stutter)'라고 불리는 것을 제거하기위한 고급 기능이 있습니다. 이제 우리는 이것이 필요 없기 때문에 이러한 것을 스킵할 것이며 게임 마무리를 위해 마지막 마무리 작업만을 추가할 것 입니다.

중요: 만약 큐브가 여전히 끊기면서 이동한다면 Compile Assembly 명령어를 수행했는지 확인해보세요.

현재 우리 게임내에서는 두 개의 큐브가 동일하게 생겨 구분할 수 있는 방법이 없으므로 어떤 큐브를 제어하고 있는지 확인하기가 약간 어렵습니다. 색상을 주어 이 문제를 해결해보죠!

'Bolt Editor' 의 'CubeState' 를 열어 'New Property' 를 클릭하여 'CubeColor' 로 이름을 짓고 'Color' 타입으로 설정합니다.

완료 후에 Compile Assembly 명령어를 다시 수행해주세요. CubeBehaviour 클래스와 Attached 메소드 내에서 각 큐브의 색을 무작위로 결정해주는 몇 줄의 코드를 추가하겠습니다.

  // ...

  public override void Attached() {
    state.SetTransforms(state.CubeTransform, transform);

    if (entity.isOwner) {
      state.CubeColor = new Color(Random.value, Random.value, Random.value);
    }
  }

  // ...

새로운 코드는 if 문장과 그 안의 코드 입니다. entity.isOwner 를 체크하는 이유는 모든 사람이 아닌 객체를 인스턴스화한 사람에게만 수행할 필요가 있기 때문입니다.

질문 하나 하겠습니다: 그러면 왜 SimulateOwner 을 사용하지 않을까요? 소유자에서만 수행하는 코드를 작성하도록 할 것입니다. 하지만 매 프레임마다 실행되는 것으로 이렇게는 필요하지 않습니다. 붙여진 대부분의 코드는 모든 사람에게 똑같을 것이므로 특별한 AttachedOwner 콜백은 없습니다.

지금까지 잘해주셨습니다. 우리는 소유자에게만 색을 설정했지만 현재 이에 대한 것을 아무것도 하고 있지 않습니다. 이제 우리는 다른 기능을 살펴볼 때가 되었습니다 : 프로퍼티 콜백.

프로퍼티 콜백은 프로퍼티 값이 변경될 때마다 호출되는 메소드를 후킹할 수 있도록 해줍니다. 먼저 ColorChanged 라는 새로운 메소드를 생성하여 state.CubeColor 의 색을 칠하고 렌더러의 material.color 속성에 지정하도록 합니다.

  // ...

  void ColorChanged() {
    GetComponent<Renderer>().material.color = state.CubeColor;
  }

  // ...

간단하죠. Attached 메소드의 끝에 ColorChanged 를 후킹하여 state.CubeColor 프로퍼티가 변경될 때 마다 호출되도록 합니다.

  // ...

  public override void Attached() {
    state.SetTransforms(state.CubeTransform, transform);

    if (entity.isOwner) {
      state.CubeColor = new Color(Random.value, Random.value, Random.value);
    }

    state.AddCallback("CubeColor", ColorChanged);
  }

  // ...

Attached 메소드의 가장 마지막 라인인 state.AddCallback("CubeColor", ColorChanged); 이 키 입니다. 현재 프로퍼티 명을 입력해야 합니다. 이 경우에서는 문자열로 "CubeColor" 입니다(현재 정적 타입으로도 만드는 작업중에 있습니다).

게임을 다시 실행하기 전에 CubeBehaviour 내부에 Unity 의 OnGUI 메소드를 마지막으로 추가할 것 입니다. 코드는 다음과 같습니다.

  void OnGUI() {
    if (entity.isOwner) {
      GUI.color = state.CubeColor;
      GUILayout.Label("@@@");
      GUI.color = Color.white;
    }
  }

다음은 CubeBehaviour 의 전체 소스코드입니다.

using UnityEngine;
using System.Collections;

public class CubeBehaviour : Bolt.EntityBehaviour<ICubeState> {
  public override void Attached() {
    state.SetTransforms(state.CubeTransform, transform);

    if (entity.isOwner) {
      state.CubeColor = new Color(Random.value, Random.value, Random.value);
    }

    state.AddCallback("CubeColor", ColorChanged);
  }

  void ColorChanged() {
    GetComponent<Renderer>().material.color = state.CubeColor;
  }

  public override void SimulateOwner() {
    var speed = 4f;
    var movement = Vector3.zero;

    if (Input.GetKey(KeyCode.W)) { movement.z += 1; }
    if (Input.GetKey(KeyCode.S)) { movement.z -= 1; }
    if (Input.GetKey(KeyCode.A)) { movement.x -= 1; }
    if (Input.GetKey(KeyCode.D)) { movement.x += 1; }

    if (movement != Vector3.zero) {
      transform.position = transform.position + (movement.normalized * speed * BoltNetwork.frameDeltaTime);
    }
  }

  void OnGUI() {
    if (entity.isOwner) {
      GUI.color = state.CubeColor;
      GUILayout.Label("@@@");
      GUI.color = Color.white;
    }
  }
}

게임을 빌드하고 실행했을 때 아래 그림과 같이 보일 것 입니다.

기술문서 TOP으로 돌아가기