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

Bolt 104 - 이벤트

'Bolt 101' 과 'Bolt 102' 에서 Bolt를 받고 실행하고 속성을 받는 방법과 게임 오브젝트들을 네트워크상에서 복제하는 기본을 학습했습니다. 이 섹션에서는 Bolt의 이벤트가 무엇이고 사용하는 방법을 살펴볼 것 입니다.

먼저해야할 일은 새로운 이벤트를 생성하는 것으로 이전에 했던 새로운 상태를 생성하는 것과 동일한 방식으로 수행합니다. 'Bolt Assets' 윈도우를 열고 빈 영역에서 오른쪽 클릭을 하여 드롭다운 메뉴에서 'New Event' 를 선택합니다.

Create new Event
Create new Event.

이벤트를 생성했을 때 Bolt는 'Bolt Editor' 윈도우에서 이벤트가 열리게 되며, 우리가 생성할 첫 번째 기능은 누가 참여하고 떠난지를 나타내는 간단한 게임 로그를 생성하는 것 입니다.

이벤트를 'LogEvent' 로 이름을 변경하고 이름 필드옆에 있는 'New Property' 를 클릭합니다. 새로운 속성은 'Message' 로 이름을 부여하고 유형을 'String' 로 설정합니다. 'Encoding & Length' 설정을 'UTF8' 그리고 '64' 로 설정합니다.

Configure the new Event
Configure the new Event.

계속하기전에 이벤트 에셋 자체의 두 개 설정인 'Global Senders' 와 'Entity Senders' 를 확인 해 보겠습니다. Bolt 내의 이벤트는 두 가지 다른 방식으로 전송될 수 있습니다:전역적 또는 특정 엔티티로.

전역 이벤트는 'Bolt.GlobalEventListener' 에서 상속한 클래스에서 수신되며 목표 객체에 실제로 Bolt 엔티티가 없다라도 자유롭게 전송될 수 있습니다. 엔티티 이벤트는 'Bolt.EntityEventListener' 에서 상속한 엔티티의 이벤트 스크립트에서만 수신됩니다.

'Global Senders' 와 'Entity Senders' 옵션들은 누가 이벤트를 전송 할 수 있는지를 제어합니다. 우리의 'LogEvent' 는 글로벌 이벤트로 할 것이지만 서버만이 이벤트를 보내기를 원하므로 'Global Senders' 에서 'Only Server' 로 전환해주어야 합니다. 왜냐하면 우리는 이 이벤트를 'Entity Event' 로 전송하지 않아야 하기 때문에 'Entity Senders' 를 'None' 으로 설정합니다.

Configure Event Senders
Configure Event Senders.

중요: 이벤트를 Bolt에게 알리기 위해 'Bolt Assets' 윈도우내에 있는 녹색 '화살표' 버튼 또는 Assets/Bolt Engine/Compile Assembly 메뉴를 통해 다시 컴파일을 합니다.

컴파일 후에 'Tutorial/Scripts' 에서 새로운 C# 스크립트를 생성하여 ServerCallbacks 라고 이름을 부여합니다. Bolt.GlobalEventListener 에서 상속을 받고 [BoltGlobalBehaviour(BoltNetworkModes.Server)] 속성을 부여하여 Bolt 가 서버에서 이 클래스의 인스턴스 생성만을 수행하도록 알려줍니다. 글로벌 콜백의 상세 내용은 상세 해설 - 글로벌 콜백 을 참고하세요.

C#

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

[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class ServerCallbacks : Bolt.GlobalEventListener
{
    // Your code here...
}

ConnectedDisconnected 콜백을 구현해야 합니다.

C#

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

[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class ServerCallbacks : Bolt.GlobalEventListener
{
    public override void Connected(BoltConnection connection)
    {

    }

    public override void Disconnected(BoltConnection connection)
    {

    }
}

ConnectedDisconnected 코드의 내부는 LogEvent 이벤트에서 보내는 Message 속성을 제외하고는 동일합니다.

C#

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

[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class ServerCallbacks : Bolt.GlobalEventListener
{
    public override void Connected(BoltConnection connection)
    {
        var log = LogEvent.Create();
        log.Message = string.Format("{0} connected", connection.RemoteEndPoint);
        log.Send();
    }

    public override void Disconnected(BoltConnection connection)
    {
        var log = LogEvent.Create();
        log.Message = string.Format("{0} disconnected", connection.RemoteEndPoint);
        log.Send();
    }
}

Bolt에서 새로운 이벤트를 생성하기 위해서는 EventName.Create(); 를 사용하고 원하는 속성들을 지정하여 eventObject.Send(); 를 호출하여 이벤트를 전송합니다. Create 메소드에는 다른 파라미터들을 가진 몇 가지 오버로딩 메소드가 있어 누가 이벤트를 전송하고 어떻게 전달하는지 등을 지정할 수 있습니다.

이제 마지막으로 해야할 부분은 이벤트를 리슨하는 것으로 이전 챕터에서 만든 NetworkCallbacks 스크립트를 엽니다. 이 스크립트에서 void OnEvent(LogEvent evnt) 라는 새로운 메소드를 생성하여 다음과 같이 구현합니다.:

C#

using UnityEngine;
using System.Collections;

[BoltGlobalBehaviour]
public class NetworkCallbacks : Bolt.GlobalEventListener {
  public override void SceneLoadLocalDone(string map) {
    // randomize a position
    var pos = new Vector3(Random.Range(-16, 16), 0, Random.Range(0, 16));

    // instantiate cube
    BoltNetwork.Instantiate(BoltPrefabs.Cube, pos, Quaternion.identity);
  }

  public override void OnEvent(LogEvent evnt) {
  }
}

클래스에 List<string> 타입의 새로운 변수를 추가하여 logMessages 메시지라고 부여 합니다. List<T> 클래스를 사용할 수 있도록 파일의 최상단에 using System.Collections.Generic; 이 있어야 합니다.

C#

List<string> logMessages = new List<string>();

OnEvent 내부 logMessages 리스트에 evnt.Message 속성의 값을 추가해야 합니다:

C#

logMessages.Insert(0, evnt.Message);

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

C#

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

[BoltGlobalBehaviour]
public class NetworkCallbacks : GlobalEventListener
{
    List<string> logMessages = new List<string>();

    public override void SceneLoadLocalDone(string scene)
    {
        // randomize a position
        var spawnPosition = new Vector3(Random.Range(-8, 8), 0, Random.Range(-8, 8));

        // instantiate cube
        BoltNetwork.Instantiate(BoltPrefabs.Cube, spawnPosition, Quaternion.identity);
    }

    public override void OnEvent(LogEvent evnt)
    {
        logMessages.Insert(0, evnt.Message);
    }
}

가장 마지막에 해야할 일은 로그를 표시하는 것이고, 표준 Unity OnGUI 메소드를 사용하여 표시할 것 입니다.

C#

void OnGUI()
{
    // only display max the 5 latest log messages
    int maxMessages = Mathf.Min(5, logMessages.Count);

    GUILayout.BeginArea(new Rect(Screen.width / 2 - 200, Screen.height - 100, 400, 100), GUI.skin.box);

    for (int i = 0; i < maxMessages; ++i)
    {
        GUILayout.Label(logMessages[i]);
    }

    GUILayout.EndArea();
}

화면의 하단 중앙에는 400 x 100 픽셀 크기의 상자를 그려주고 있습니다. 그리고 나서 로그에 있는 메세지를 최대 5개까지의 최근 메시지를 출력합니다. NetworkCallbacks 스크립트의 전체 내용은 다음과 같습니다.

C#

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

[BoltGlobalBehaviour]
public class NetworkCallbacks : GlobalEventListener
{
    List<string> logMessages = new List<string>();

    void OnGUI()
    {
        // only display max the 5 latest log messages
        int maxMessages = Mathf.Min(5, logMessages.Count);

        GUILayout.BeginArea(new Rect(Screen.width / 2 - 200, Screen.height - 100, 400, 100), GUI.skin.box);

        for (int i = 0; i < maxMessages; ++i)
        {
            GUILayout.Label(logMessages[i]);
        }

        GUILayout.EndArea();
    }

    public override void SceneLoadLocalDone(string scene)
    {
        // randomize a position
        var spawnPosition = new Vector3(Random.Range(-8, 8), 0, Random.Range(-8, 8));

        // instantiate cube
        BoltNetwork.Instantiate(BoltPrefabs.Cube, spawnPosition, Quaternion.identity);
    }

    public override void OnEvent(LogEvent evnt)
    {
        logMessages.Insert(0, evnt.Message);
    }
}

게임을 실행하고 두 클라이언트를 연결시키면 다음과 같이 보이게 됩니다:

Event logs on Gameplay
Event logs on Gameplay.

엔티티 이벤트가 방금전에 전송했던 글로벌 이벤트와의 차이점을 보여주기 위해 또 다른 하나의 이벤트를 추가적으로 구현할 것입니다. 'Bolt Assets' 윈도우에서 새로운 이벤트를 생성하여 'FlashColorEvent' 로 이름을 지정합니다. 'Global Senders' 를 'None' 으로 설정하고 'Entity Senders' 는 'Only Owner' 로 설정합니다. 새로운 속성을 생성하여 'FlashColor' 로 이름을 부여하고 타입을 'Color' 로 설정합니다.

Create and Configure the FlashColorEvent
Create and Configure the FlashColorEvent.

이 이벤트는 큐브를 붉은색으로 변하게 하여 피해를 받았다는 신호를 줄 수 있도록 하기 위한 이벤트 입니다.

중요: 이벤트를 생성 후에는 Bolt를 컴파일 해야 한다는 것을 기억해주세요.

'Tutorial/Scripts' 에 있는 CubeBehaviour 스크립트를 열고, 기본 클래스를 Bolt.EntityBehaviour<ICubeState> 에서 Bolt.EntityEventListener<ICubeState> 로 다음과 같이 변경할 것 입니다:

C#

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

public class CubeBehaviour : Bolt.EntityEventListener<ICubeState>
{
  // ...
}

Bolt.EntityEventListener<T> 는 실제로 Bolt.EntityBehaviour<T> 에서 상속 받았기 때문에 Bolt.EntityBehaviour<T> 의 모든 동일한 메소드는 사용가능 합니다.

SimulateOwner 마지막에 몇 줄의 라인을 추가하여 F 키를 누를 때 'FlashColorEvent' 를 전송할 수 있도록 하겠습니다.

C#

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

엔티티 이벤트 전송 방식은 글로벌 이벤트 전송 방식과 한가지만 빼고는 거의 동일합니다: Create 메소드내로 이벤트 전송을 원하는 엔티티 전송

CubeBehaviour 클래스에 몇 가지 사항을 추가하려고 하는데, 먼저 FlashColorEvent 에서 색을 반짝이게 하는것을 언제 멈출지 추적할 필요가 있으므로, resetColorTime 이라는 변수를 추가할 것 입니다. renderer 라는 변수도 추가하여 Attached 메소드안의 게임 오브젝트의 렌더러 컴포넌트 참조값을 저장하기 위하여 사용하게 됩니다. 이 방식으로 인해 GetComponent<Renderer> 메소드를 계속 반복해서 호출 할 필요가 없어집니다.

C#

// ...

private float _resetColorTime;
private Renderer _renderer;

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

    // ...
}

// ...

새로운 FlashColorEvent 로 Bolt를 컴파일 했을 때, Bolt.EntityEventListener<T> 클래스에서는 새로운 OnEvent(FlashColorEvent evnt) 메소드가 생성되며 CubeBehaviour 에서 구현할 것 입니다.

C#

public override void OnEvent(FlashColorEvent evnt)
{
    _resetColorTime = Time.time + 0.2f;
    _renderer.material.color = evnt.FlashColor;
}

여기에서 우리는 반짝이는 리셋-시간을 0.2초로 설정했고 머티리얼 색을 이벤트내에서 수신받는 색으로 변경합니다. 마지막으로 해야할 일은 표준 Unity 의 Update 메소드를 구현하여 시간이 경과 되었을 때 색을 재설정하는 것 입니다.

C#

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

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

C#

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

public class CubeBehaviour : Bolt.EntityEventListener<ICubeState>
{
    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);
        }

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

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

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

게임을 플레이하고 제어하고 있는 큐브 인스턴스에 F 를 누르면 인스턴스는 모든 화면에서 빨간색으로 잠깐 반짝일 것 입니다.

FlashColor Event on Gameplay
FlashColor Event on Gameplay.

반짝이는 이벤트가 전송되거나 다른 인스턴스에서 수신되는 것과 관련된 이벤트, F 를 누르는 중앙하단에 있는 것에 대한 이벤트에 대해서는 Bolt 콘솔에 강조를 했습니다.

왜 이벤트는 두개의 타입인가요?

다음에 다룰 질문 입니다. Bolt에서는 왜 두 유형의 이벤트가 있나요? Photon의 RPC 또는 Unity 네트워킹에서 동작하는 것처럼 하나가 아닌가요?

글로벌 이벤트는 Create 메소드에 다른 파라미터를 전달하여 신뢰비신뢰 모두 될 수 있습니다. 일반적으로 대부분의 글로벌 이벤트는 신뢰 이며 변경하기 위해서 아무것도 전달하지 않는다면 디폴트입니다. 글로벌 이벤트는 인증에 대해 다루거나 플레이어 인벤토리 등을 처리하는 것과 같이 게임 주위에 존재하는 것을 위해 만들어졌습니다.

엔티티 이벤트는 항상 비신뢰 로써, 엔티티 이벤트는 오래 지속되지 않고 플레이어가 이벤트를 놓치더라도 문제가 되지 않는 피해표시를 하거나 폭발을 표현해주는 것과 같은 작은 일회성 효과를 위한 것입니다.

[다음 장 >>]에서 계속 (./bolt-105-objects-and-arrays).

Back to top