PUN Classic (v1)、PUN 2、Boltはメンテナンスモードとなっております。Unity2022についてはPUN 2でサポートいたしますが、新機能が追加されることはありません。お客様のPUNプロジェクトおよびBoltプロジェクトが停止することはなく、将来にわたってパフォーマンス性能が落ちることはありません。 今後の新しいプロジェクトについては、Photon FusionまたはQuantumへ切り替えていただくようよろしくお願いいたします。

Bolt 105 - オブジェクトと配列

Boltは、オブジェクト配列 という複雑なタイプの組み込み式レプリケーションをサポートしています。これは素のコードC#のそれに非常に近しいものですが、いくつかの点で異なります。オブジェクトBolt Assetsウィンドウで定義され、ステートまたはその他のオブジェクトにおける配列またはオブジェクトのプロパティに対するタイプとして用いられます。

オブジェクトにより、一群のプロパティのカプセル化のための簡易なメカニズムが得られます。またこれにより、Bolt内の、様々なBoltのステートで再利用できる複雑な階層データを実装できます。Bolt内のオブジェクトの使い方について、シンプルなシューティングゲームへの武器スロットの導入を例として説明しましょう。ここでは、実際にゲームを作成するわけではありません。武器を複製するために、データ構造とステートを作成します。
右クリックメニューを使って、Bolt Assetsウィンドウで新しいオブジェクトを作成しましょう。

Create a new Object Asset
新しいオブジェクトアセットの作成

オブジェクトにWeaponSlotと名前をつけて、WeaponIdWeaponAmmoの2つのプロパティを付与してください。これらはどちらも整数でなければなりません。Bolt内のオブジェクトは、イベントやステートのようなそれ自体のプロパティを有していません。 オブジェクトは、その他のアセットにも含まれることがあるデータのコンテナとして考えてください。

Configure the new Object Asset
新しいオブジェクトアセットの設定

CubeState上に、WeaponsArrayという新たなプロパティを追加します。このプロパティのタイプはArrayに、Element TypeObjectに、Object TypeWeaponSlotに設定します。また、Element Count3に設定します。もう1つ、WeaponActiveIndexという名前のプロパティを追加し、これのタイプはIntegerに設定します。このプロパティはどの武器がアクティブであるかのトラッキングに用いられます。

Add WeaponSlot to Cube State
CubeステートにWeaponSlotを追加

重要: Bolt AssetsおよびBolt Editorのウィンドウでの変更を完了したら、Boltをコンパイルするのを忘れないでください。

WeaponsArrayプロパティは、これまで作成してきたものの中でも最も複雑です。Boltは3つの'WeaponSlot'オブジェクトからなる配列を1つ作成します。この配列を調整することで、その3つのオブジェクト内のすべてのデータをネットワーク上に自動的に複製することができます。

ここでの 配列 とは、実際のC# WeaponSlot[] 配列のことではありません。Boltには、独自の配列に似たタイプがあり、これによりBoltはオブジェクトに対して行われた変更を容易にトラッキングすることができます。WeaponSlotsプロパティの実際のタイプはBolt.NetworkArray_Objects<WeaponSlot>です。一般的には通常のインデックス化オペレーションはこのクラスでも動作し、同じLengthプロパティを持つことなどから、多くの場合に無視しても問題ありません。

それでは、Cubeプレハブに簡単なプレースホルダー武器を設定しましょう。プレハブのコピーを何もないシーンにドラッグし、それがシーンの(0, 0, 0)に位置していることを確認してください。新たな spherecapsule および cylinder を(0, 0, 0)の位置に作成し、それらをCubeの子に設定してください。また、デフォルトのコライダーを削除し、必ず3つのオブジェクトを非有効化して、デフォルトでこれらすべてがオフになるようにしてください。これに用いた変換設定は以下の通りです:

Add Weapon representations to Cube Prefab
Cubeプレハブに武器表示を追加

これにより、現在アクティブとなっている武器を簡単に見分けることができます。Cubeが選択されている状態でApplyを押すか、シーン内のCubeProjectウィンドウ内のCubeプレハブにドラッグし、変更をプレハブにも適用してください。

スクリプトの修正を開始します。CubeBehaviourスクリプトの最上部に、UnityGameObjectの配列であるWeaponObjectsという名前の新しい変数を追加します。

C#

using UnityEngine;
using System.Collections;
using Bolt;

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

Cubeプレハブのインスペクターに進み、3つの子オブジェクトをWeaponObjects配列インスペクター欄にドラッグしてください。

Drag Weapon object to Cube Behavior Script
WeaponオブジェクトをCube Behavior Scriptにドラッグ

これから、 CubeBehaviour スクリプトに多くの新規コードを追加します。武器のセットアップを適切に行うため Attached 内で、それぞれのプレイヤーへの武器をオーナー上で設定する必要があります。また、コールバックに接続し、アクティブな武器スロットが変更になった際に通知を受けられるようにする必要があります。

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ウィンドウ内の状態上で定義したWeaponArrayプロパティを用います。
お分かりいただけるとおり、このプロパティは通常の配列とほぼ同様に機能します。武器オブジェクトモデルの1つを.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になっていることを確認します。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();
    }
}

完成した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);
        }
    }
}

ゲームをビルドしサーバーとクライアントを起動すると、キーボード上の12および3のキーを用いることで武器を選択できます。以下のようになります。

Change Weapons Gameplay
ゲームプレイで武器を変更

現時点では、何に対してもWeaponAmmoプロパティを使用していませんが、有用であることがおわかりいただけたと思います。

次章 >>に続きます。

Back to top