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'의 자식으로 만들어줍니다. 생성된 것들에서 디폴트 콜라이더도 제거합니다. 트랜스폼 설정은 다음과 같습니다:
- 스피어 & 실린더
- Position: 0, 0, -0.75
- Rotation: 0, 0, 0,
- Scale: 0.4, 0.4, 0.4
- 캡슐
- Position: 0, 0, -1
- Rotation: 90, 0, 0
- Scale: 0.4, 0.4, 0.4
현재 활성화한 무기들이 잘 보이게 하는 설정입니다. 'Cube' 가 선택되었을 때 인스펙터 상단에 있는 'Apply' 를 누르거나 씬에 있는 'Cube'를 드래그하여 'Project' 윈도우에 놓아 프리팹 변경사항을 적용하도록 해 주세요.
CubeBehaviour
스크립트 상단에 Unity GameObject
배열을 갖게 되는 WeaponObjects
라는 변수를 추가할 것입니다.
C#
using UnityEngine;
using System.Collections;
using Bolt;
public class CubeBehaviour : Bolt.EntityEventListener<ICubeState>
{
public GameObject[] WeaponObjects;
// ...
}
'Cube' 프리팹의 인스펙터로 이동하여 세개의 객체를 드래그하여 인스펙터 필드 'WeaponObjects' 배열에 떨구어 줍니다.
세 개의 '스피어', '캡슐' 과 '실린더' 객체를 사용불가로 해서 디폴트로 꺼져있게 만들어 줍니다.
이제 무기 설정이 잘 되기 위한 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
, 2
와 3
을 사용하여 무기를 고를 수 있을 것이며 다음과 같이 보일 것 입니다.
큐브위에 마우스를 올려 놓으면 우리가 설정한 무기 속성들에 대한 상태를 볼 수 있을 것 입니다.
현재는 WeaponAmmo
를 전혀 사용하지 않았으나, 그 값을 볼 수 있습니다.
[다음 장 >>]에서 계속 (./bolt-104-events).
Back to top