GnP 2: Message broadcasting with Vikja

By leveraging the Vikja module, developers can broadcast messages to all players (a.k.a. participants) in the session. We'll use it to broadcast the game state, so that any game start or game over event will be synchronized across all participants.

Here is the complete script for ^GameEventController.cs^:

using System;
using Auki.ConjureKit;
using Auki.ConjureKit.Vikja;
using Auki.Util;
using ConjureKitShooter.Models;
using UnityEngine;


public class GameEventController
{
    private IConjureKit _conjureKit;
    private Vikja _vikja;

    private uint _myEntityId;

    private const string NotifyGameState = "NOTIFY.GAME.STATE";
    private const string NotifySpawnerPos = "NOTIFY.SPAWNER.POS";

    public Action OnGameOver, OnGameStart;
    public Action<Pose> OnSpawnerMove;

    #region Public Methods
    public void Initialize(IConjureKit conjureKit, Vikja vikja)
    {
        _conjureKit = conjureKit;
        _vikja = vikja;
        _vikja.OnEntityAction += OnEntityAction;
        _conjureKit.OnParticipantEntityCreated += SetMyEntityId;
    }

    public void SendGameState(bool start)
    {
        _vikja.RequestAction(_myEntityId, NotifyGameState , start.ToJsonByteArray(), null, null);
    }

    public void SendSpawnerPos(Pose pose)
    {
        _vikja.RequestAction(_myEntityId, NotifySpawnerPos, new SPose(pose).ToJsonByteArray(), action =>
        {
            OnSpawnerMove?.Invoke(action.Data.FromJsonByteArray<SPose>().ToUnityPose());
        }, null);
    }
    #endregion

    #region Private Methods
    private void SetMyEntityId(Entity entity)
    {
        _myEntityId = entity.Id;
    }
    private void OnEntityAction(EntityAction obj)
    {
        switch (obj.Name)
        {
            case NotifyGameState:
                var gameOn = obj.Data.FromJsonByteArray<bool>();
                if (gameOn)
                    OnGameStart?.Invoke();
                else
                    OnGameOver?.Invoke();
                break;
            case NotifySpawnerPos:
                OnSpawnerMove?.Invoke(obj.Data.FromJsonByteArray<SPose>().ToUnityPose());
                break;
        }
    }
    #endregion
}

With the script above, anyone in the session can broadcast game state changes using ^SendGameState(bool start)^. Now let's instantiate ^GameEventController.cs^ in ^Main.cs^:

private GameEventController _gameEventController = new();

And initialize it in the ^Start()^ method:

_gameEventController.Initialize(_conjureKit, _vikja);

Then assign the ^GameStart^ and ^GameOver^ callbacks in the ^OnJoined()^ method:

private void OnJoined(Session session)
{
    _myId...
    _ses...
    
~~    _gameEventController.OnGameStart = GameStart;
    _gameEventController.OnGameOver = GameOver;~~
    uiMa...
}

Next we need to define the SendGameStart and SendGameOver events. We can do that by adding a new bool field called ^_spawner^, and creating two new methods in ^Main.cs^:

private bool _spawner;
private void SendGameStart()
{
    _spawner = true;
    OnGameStart?.Invoke();
    _gameEventController.SendGameState(true);
    GameStart();
}
private void SendGameOver()
{
    _gameEventController.SendGameState(false);
    GameOver();
}

Remove ^OnGameStart?.Invoke()^ from the ^GameStart()^ method, since we only want the host/spawner to invoke the event.

Now we want to pass the ^SendGameStart()^ method into the ^uiManager.Initialize()^ call inside the ^Start()^ method. Replace this line:

uiManager.Initalize(GameStart, PlaceSpawner, null, OnNameSet, ToggleAudio);

with this:

uiManager.Initalize(SendGameStart, PlaceSpawner, null, OnNameSet, ToggleAudio);

And whenever a player dies, we want to call ^SendGameOver()^ in the ^HitByHostile()^ method, so change the ^GameOver()^ call:

if (_health <= 0)
{
~~    GameOver();~~
}

to this:

if (_health <= 0)
{
~~    SendGameOver();~~
}

We have also implemented a method that will broadcast the new Spawner position if it's being moved, so let's change the ^PlaceSpawner()^ method in ^Main.cs^ from this:

private void PlaceSpawner()
{
    if (!_planeHit) return;

    var pose = ...
~~    hostileController.transform.position = pose.position;~~
}

to this:

private void PlaceSpawner()
{
    if (!_planeHit) return;

    var pose = ...
~~    _gameEventController.SendSpawnerPos(pose);~~
}

And add these lines inside the ^EventInit()^ method in ^Main.cs^:

_gameEventController.OnSpawnerMove += pose =>
{
    hostileController.transform.position = pose.position;
};

So whenever a participant changes the spawner (beam) position, Vikja will broadcast it and all participants in the session will receive the message and see their own spawner position updated.

Lastly, let's also call the ^GameOver()^ method whenever we've left a session:

private void OnLeft(Session lastSession)
{
    GameOver();
}

We will need a way to toggle the QR code which other participants scan to join the session, so let's add this method to ^Main.cs^:

private void ToggleLighthouse()
{
    _isSharing = !_isSharing;
    _manna.SetLighthouseVisible(_isSharing);
}

Then pass the ^ToggleLighthouse()^ method into the third argument of ^uiManager.Initialize()^ called in the ^Start()^ method:

uiManager.Initalize(SendGameStart, PlaceSpawner, ToggleLighthouse, OnNameSet, ToggleAudio);

Now we can test this, just to see if the Game start and Game over events are being correctly sent across participants in the session.

start-note

You can emulate multiple participants by using the Editor plus a phone. While the calibration might not be accurate due to the incorrect QR size when shown via Editor, you should be able to test the Game State synchronization at this point.

end-note

Need a refresher on the essentials?

Check out more lessons, DIY kits and essentials reading material at the developer learning centre homepage.

ポーズメッシュを構築するのにサポートが必要ですか?

プロジェクトをスタートさせるためにAUKIトークンの助成金を申請し、Auki Labsチームと直接連携して、あなたのクリエイションをマーケットへ。選ばれた申請者は最大10万米ドル相当のAUKIトークンの助成を受け、アウキラボチームによる開発、マーケティング支援を受けることができます。