Bolt 105 - 객체 & 배열

Bolt는 객체들과 배열의 복잡한 타입의 내장 복제를 지원하며 C# 비교해 볼 때 매우 유사 하지만 몇가지 차이점이 있습니다. 객체들은 'Bolt Assets' 윈도우에서 정의되며 상태 또는 다른 객체의 'Array' 또는 'Object' 속성 유형으로써 사용될 수 있습니다.

객체들은 프로퍼티들을 집합으로 캡슐화해주는 쉬운 메카니즘을 제공하고 Bolt 내의 복잡한 계층 데이터를 구현하여 다른 Bolt 상태간에 쉽게 재사용을 할 수 있습니다. 간단한 슈팅 게임에서 무기 슬롯을 구현하는 것을 통해 Bolt에서 객체들의 사용방법을 설명하며 실제로 게임은 구축하지 않고 무기 복제를 위한 데이터구조와 상태만을 볼 것 입니다.

'Bolt Assets' 윈도우에서 우측 클릭 메뉴에서 새로운 'Object' 를 생성하여 시작합니다.

객체를 'WeaponSlot' 으로 이름을 정하고 두개의 속성을 줍니다: 'WeaponId' 와 'WeaponAmmo' 두 속성은 정수형이어야 합니다. 보시다시피 Bolt 내의 객체는 이벤트 및 상태와 같은 자신의 속성들을 가지고 있지 않는 다른 에셋들이 추가될 수 있는 데이터 컨테이너입니다.

'CubeState' 에서 새로운 속성으로 'Array' 타입인 'WeaponsArray' 를 추가할 것입니다. 'Element Type' 은 'Object' 로, 'Object Type' 은 'WeaponSlot' 으로 설정하고 'Element Count' 를 3으로 설정할 것 입니다. 그리고 'WeaponActiveIndex' 프로퍼티를 더 추가하여 'Integer' 형으로 설정합니다. 이 속성은 활성화 무기를 추적하기 위함입니다.

중요: 항상 'Bolt Assets' 와 'Bolt Editor' 의 변경된 이후에는 Bolt 컴파일 하는 것을 잊지 마세요.

'WeaponsArray' 속성은 지금까지 생성했던 것 중 가장 복잡한 것으로 Bolt는 3개의 'WeaponSlot' 객체들의 배열을 생성한 이후 변경할 수 있고 속성들의 데이터는 네트워크를 통해 자동으로 복제되어집니다.

여기에서 '배열' 을 말할 때 실제 C# 의 WeaponSlot[] 배열을 의미하지는 않습니다. Bolt는 배열과 같은 자체의 타입을 제공하여 Bolt가 객체들의 변경사항을 쉽게 추적할 수 있도록 합니다. 'WeaponSlots' 속성의 실제 타입은 Bolt.NetworkArray_Objects<WeaponSlot> 으로 일반적으로 동작 작업이 이 클래스상에서 이루어지기 때문에 대부분 무시할 수 있으며 Length 등과 같은 속성을 가지고 있습니다.

이제 'Cube' 프리팹에 아주 간단한 플레이스-홀더 무기를 설정할 때입니다. 프리팹 복사본을 빈 씬으로 드래그하여 위치가 씬의 (0, 0, 0) 이 되도록 해 주세요. 새로운 스피어, 캡슐과 실린더를 (0, 0, 0) 에 생성하여 'Cube'의 자식으로 만들어줍니다. 생성된 것들에서 디폴트 콜라이더도 제거합니다. 트랜스폼 설정은 다음과 같습니다:

  1. 스피어 & 실린더

    • Position: 0, 0, -0.75
    • Rotation: 0, 0, 0,
    • Scale: 0.4, 0.4, 0.4
  2. 캡슐

    • Position: 0, 0, -1
    • Rotation: 90, 0, 0
    • Scale: 0.4, 0.4, 0.4

현재 활성화한 무기들이 잘 보이게 하는 설정입니다. 'Cube' 가 선택되었을 때 인스펙터 상단에 있는 'Apply' 를 누르거나 씬에 있는 'Cube'를 드래그하여 'Project' 윈도우에 놓아 프리팹 변경사항을 적용하도록 해 주세요.

CubeBehaviour 스크립트 상단에 Unity GameObject 배열을 갖게 되는 WeaponObjects 라는 변수를 추가할 것입니다.

using UnityEngine;
using System.Collections;

public class CubeBehaviour : Bolt.EntityEventListener<ICubeState> {
  public GameObject[] WeaponObjects;

  // ... snip

'Cube' 프리팹의 인스펙터로 이동하여 세개의 객체를 드래그하여 인스펙터 필드 'WeaponObjects' 배열에 떨구어 줍니다.

세 개의 '스피어', '캡슐' 과 '실린더' 객체를 사용불가로 해서 디폴트로 꺼져있게 만들어 줍니다.

이제 무기 설정이 잘 되기 위한 Attached 메소드 내에 소유자의 각 플레이어 무기를 설정해 주는 꽤 긴 코드를 CubeBehaviour 스크립트에 추가할 것 입니다. 또한 활성 무기 슬롯이 변경 될때 알림을 받기 위한 콜백 후킹을 해야할 필요도 있습니다.

  // ... snip

  public override void Attached() {
    renderer = GetComponent<Renderer>();

    state.SetTransforms(state.CubeTransform, transform);

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

      // NEW: on the owner we also want to setup the weapons, we randomize one weapon from the available ones and also ammo between 50 to 100
      for (int i = 0; i < state.WeaponsArray.Length; ++i) {
        state.WeaponsArray[i].WeaponId = Random.Range(0, WeaponObjects.Length);
        state.WeaponsArray[i].WeaponAmmo = Random.Range(50, 100);
      }

      // NEW: by default we don't have any weapon up, so set index to -1
      state.WeaponActiveIndex = -1;
    }

    state.AddCallback("CubeColor", ColorChanged);

    // NEW: we also setup a callback for whenever the index changes
    state.AddCallback("WeaponActiveIndex", WeaponActiveIndexChanged);
  }

  // ... snip

if(entity.isOwner) 블록내에 새로운 for 루프가 있고 이 루프에서는 3개의 무기 슬롯을 초기화 해주는데 여기에서는 'Bolt Assets' 윈도우에서 상태를 정의해놓았던 WeaponsArray 속성을 사용하고 있습니다. 일반적인 배열의 기능과 같다는 것을 볼 수 있습니다. 무기 객체 모델중의 하나를 .WeaponId 에 무작위로 할당했고 무기 갯수를 50개에서 100개 사이로 선택하여 .WeaponAmmo 에 할당했습니다.

또한 .WeaponActiveIndex-1 로 설정했는데, 기본적으로 무기를 가지고 있지 않음을 의미합니다. 마지막으로 수행한 것은 "WeaponActiveIndex" 프로퍼티에 콜백을 추가하여 활성 무기가 변경되었을 때 알림을 받게 해주었습니다.

WeaponActiveIndexChanged 메소드는 다음과 같습니다.

  void WeaponActiveIndexChanged() {
    for (int i = 0; i < WeaponObjects.Length; ++i) {
      WeaponObjects[i].SetActive(false);
    }

    if (state.WeaponActiveIndex >= 0) {
      int objectId = state.WeaponsArray[state.WeaponActiveIndex].WeaponId;
      WeaponObjects[objectId].SetActive(true);
    }
  }

우리가 첫 번째로 모든 무기 객체들을 사용불가로 한 후 인덱스가 >= 0 이면 .WeaponId 를 취득하여 이에 대한 객체를 사용가능으로 만들어 줍니다. 마지막으로 해준것은 SimulateOwner 내위 표준 Unity 입력을 폴링하는 것으로 다음과 같습니다:

    if (Input.GetKeyDown(KeyCode.Alpha1)) state.WeaponActiveIndex = 0;
    if (Input.GetKeyDown(KeyCode.Alpha2)) state.WeaponActiveIndex = 1;
    if (Input.GetKeyDown(KeyCode.Alpha3)) state.WeaponActiveIndex = 2;
    if (Input.GetKeyDown(KeyCode.Alpha0)) state.WeaponActiveIndex = -1;

이후 SimulateController 에 있는 WSAD 키를 폴링하는 코드를 추가했습니다:

  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; }

    // NEW: Input polling for weapon selection
    if (Input.GetKeyDown(KeyCode.Alpha1)) state.WeaponActiveIndex = 0;
    if (Input.GetKeyDown(KeyCode.Alpha2)) state.WeaponActiveIndex = 1;
    if (Input.GetKeyDown(KeyCode.Alpha3)) state.WeaponActiveIndex = 2;
    if (Input.GetKeyDown(KeyCode.Alpha0)) state.WeaponActiveIndex = -1;

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

    if (Input.GetKeyDown(KeyCode.F)) {
      var flash = FlashColorEvent.Create(entity);
      flash.FlashColor = Color.red;
      flash.Send();
    }
  }

전체 CubeBehaviour 스크립트는 다음과 같습니다.

using UnityEngine;
using System.Collections;

public class CubeBehaviour : Bolt.EntityEventListener<ICubeState> {
  public GameObject[] WeaponObjects;

  float resetColorTime;
  Renderer renderer;


  public override void Attached() {
    renderer = GetComponent<Renderer>();

    state.SetTransforms(state.CubeTransform, transform);

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

      // on the owner we also want to setup the weapons, we randomize one weapon from the available ones and also ammo between 50 to 100
      for (int i = 0; i < state.WeaponsArray.Length; ++i) {
        state.WeaponsArray[i].WeaponId = Random.Range(0, WeaponObjects.Length);
        state.WeaponsArray[i].WeaponAmmo = Random.Range(50, 100);
      }

      // by default we don't have any weapon up, so set index to -1
      state.WeaponActiveIndex = -1;
    }

    state.AddCallback("CubeColor", ColorChanged);

    // we also setup a callback for whenever the index changes
    state.AddCallback("WeaponActiveIndex", WeaponActiveIndexChanged);
  }

  void ColorChanged() {
    renderer.material.color = state.CubeColor;
  }

  void WeaponActiveIndexChanged() {
    for (int i = 0; i < WeaponObjects.Length; ++i) {
      WeaponObjects[i].SetActive(false);
    }

    if (state.WeaponActiveIndex >= 0) {
      int objectId = state.WeaponsArray[state.WeaponActiveIndex].WeaponId;
      WeaponObjects[objectId].SetActive(true);
    }
  }

  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; }

    // NEW: Input polling for weapon selection
    if (Input.GetKeyDown(KeyCode.Alpha1)) state.WeaponActiveIndex = 0;
    if (Input.GetKeyDown(KeyCode.Alpha2)) state.WeaponActiveIndex = 1;
    if (Input.GetKeyDown(KeyCode.Alpha3)) state.WeaponActiveIndex = 2;
    if (Input.GetKeyDown(KeyCode.Alpha0)) state.WeaponActiveIndex = -1;

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

    if (Input.GetKeyDown(KeyCode.F)) {
      var flash = FlashColorEvent.Create(entity);
      flash.FlashColor = Color.red;
      flash.Send();
    }
  }


  public override void OnEvent(FlashColorEvent evnt) {
    resetColorTime = Time.time + 4f;
    renderer.material.color = evnt.FlashColor;
  }

  void Update() {
    if (resetColorTime < Time.time) {
      renderer.material.color = state.CubeColor;
    }
  }

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

게임을 빌드하여 서버와 클라이언트를 시작하면, 키보드에서 1, 23을 사용하여 무기를 고를 수 있을 것이며 다음과 같이 보일 것 입니다.

큐브위에 마우스를 올려 놓으면 우리가 설정한 무기 속성들에 대한 상태를 볼 수 있을 것 입니다.

현재는 WeaponAmmo 를 전혀 사용하지 않았으나, 그 값을 볼 수 있습니다.

기술문서 TOP으로 돌아가기