PUN Classic (v1), PUN 2, Bolt는 휴업 모드입니다. Unity2022에 대해서는 PUN 2에서 서포트하지만, 신기능의 추가는 없습니다. 현재 이용중인 고객님의 PUN 및 Bolt 프로젝트는 중단되지 않고, 퍼포먼스나 성능이 떨어지는 일도 없습니다. 앞으로의 새로운 프로젝트에는 Photon Fusion 또는 Quantum을 사용해 주십시오.

Bolt 105 - 객체 & 배열

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

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

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

Create a new Object Asset
Create a new Object Asset.

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

Configure the new Object Asset
Configure the new Object Asset.

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

Add WeaponSlot to Cube State
Add WeaponSlot to Cube State.

중요: 항상 '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
  1. 캡슐
  • Position: 0, 0, -1
  • Rotation: 90, 0, 0
  • Scale: 0.4, 0.4, 0.4

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

Add Weapon representations to Cube Prefab
Add Weapon representations to Cube Prefab.

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

C#

using UnityEngine;
using System.Collections;
using Bolt;

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

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

Drag Weapon object to Cube Behavior Script
Drag Weapon object to Cube Behavior Script.

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

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

C#

// ...

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 want to setup the weapons, the Id is set just as the index
        // and the Ammo is randomized between 50 to 100
        for (int i = 0; i < state.WeaponArray.Length; ++i)
        {
            state.WeaponArray[i].WeaponId = i;
            state.WeaponArray[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);
}

// ...

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

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

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

C#

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

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

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

C#

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

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

C#

  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 스크립트는 다음과 같습니다.

C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Bolt;

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

    private float _resetColorTime;
    private 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);

            // NEW: On the owner, we want to setup the weapons, the Id is set just as the index
            // and the Ammo is randomized between 50 to 100
            for (int i = 0; i < state.WeaponArray.Length; ++i)
            {
                state.WeaponArray[i].WeaponId = i;
                state.WeaponArray[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);
    }

    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 + 0.2f;
        _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;
        }
    }

    void ColorChanged()
    {
        GetComponent<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.WeaponArray[state.WeaponActiveIndex].WeaponId;
            WeaponObjects[objectId].SetActive(true);
        }
    }
}

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

Change Weapons Gameplay
Change Weapons Gameplay.

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

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

[다음 장 >>]에서 계속 (./bolt-104-events).

Back to top