This document is about: FUSION 1
SWITCH TO

このページは編集中です。更新が保留になっている可能性があります。

Metaverse Picazoo


Available in the Industries Circle
Circle

概要

PicaZoo シーンは、プレイヤーがペイントガンを使用して動物の銅像を探すミニゲームです。

ここではネットワークを通してテクスチャ修正を同期する方法を説明しています。

シューティングゲームを開発する場合は、専用の技術サンプルを参照してください。

デスクトップモードの特定のコントローラを使用して、マウスによるガンのコントロールの精度を上げています。

Fusion Metaverse PicaZoo

PaintBlaster

シーンにはいくつかのペイントガンを使用しています。

  • 事前に定義した色の弾丸を撃つガン
  • ランダムな色の弾丸を撃つガン
Fusion Metaverse Guns

銃は以下の2つのリストを担当するPaintBlasterクラスによって管理されます。

  • ショットのリスト
  • インパクトのリスト

リストはネットワーク化されているため、リモートプレイヤーが新しいショット・インパクトが発生したかどうか確認し、表示することができます。
このおかげで、ネットワーク化した弾丸プレハブをスポーンさせる必要がなくなりました。

ショット

ショットのプロパティはネットワーク化された構造であるBulletShootに集められます。

C#

public struct BulletShoot : INetworkStruct
{
    public Vector3 initialPosition;
    public Quaternion initialRotation;
    public float shootTime;
    public int bulletPrefabIndex;
    public PlayerRef source;
}

まず、ユーザーの発射インプットは、IsUsedブーリアンの設定に使用されるInputActionProperty UseActionで処理されます。

C#

public bool IsUsed
{
    get
    {
        return UseAction.action.ReadValue<float>() > minAction;
    }
}

LateUpdate()で、ローカルプレイヤーが銃をもって発射しているかどうか確認します。

C#

public void LateUpdate()
{
...
    if (Object == null || Object.HasStateAuthority == false) return;

    if (useInput) // VR
    {
        if (IsGrabbedByLocalPLayer)
        {
            TriggerPressed(IsUsed);
        }
        else
            wasUsed = false;
    }
...

    PurgeBulletShoots();
    PurgeRecentImpacts();
}

C#

public void TriggerPressed(bool isPressed)
{
...
    if (isPressed)
        Shoot();
...
}

デスクトップリグの場合は、DesktopAimクラスでトリガーが検知されます。

C#

private void LateUpdate()
{
    if (!grabber) return;
    if (grabber.HasStateAuthority == false) return;

    blaster.TriggerPressed(Mouse.current.leftButton.isPressed);
...
    }

ショットの管理はネットワーク化されたBulletShootsリストに新しいショットを追加することで構成されます。

C#

public void Shoot()
{
    OnShooting(projectileOriginPoint.transform.position, projectileOriginPoint.transform.rotation);
}

C#

private void OnShooting(Vector3 position, Quaternion rotation)
{
...
    lastBullet.initialPosition = position;
    lastBullet.initialRotation = rotation;
    lastBullet.shootTime = Time.time;
    lastBullet.bulletPrefabIndex = bulletPrefabIndex;
    lastBullet.source = Object.StateAuthority;
    BulletShoots.Add(lastBullet);
...
}

ショットリストはネットワーク化されているため、度のプレイヤーが発射したかにかかわらず全てのプレイヤーが最新のデータを受け取ります。
そのため、新しいショットが発生した際にプレイヤーがローカルのグラフィックオブジェクトとスポーンするには、新しいデータを受信したらFixedUpdateNetwork()を確認するだけです。(CheckShoots())

C#

const int MAX_BULLET_SHOOT = 50;
[Networked]
[Capacity(MAX_BULLET_SHOOT)]
public NetworkLinkedList<BulletShoot> BulletShoots { get; }

C#

public override void FixedUpdateNetwork()
{
    base.FixedUpdateNetwork();
    CheckShoots();
    CheckRecentImpacts();
}

C#

void CheckShoots()
{
    foreach (var bullet in BulletShoots)
    {
        if (bullet.shootTime > lastSpawnedBulletTime && bullet.source == Object.StateAuthority)
        {
            var bulletPrefab = (bullet.bulletPrefabIndex == -1) ? defaultProjectilePrefab : projectilePrefabs[bullet.bulletPrefabIndex];
            var projectileGO = GameObject.Instantiate(bulletPrefab, bullet.initialPosition, bullet.initialRotation);
            var projectile = projectileGO.GetComponent<PaintBlasterProjectile>();
            projectile.sourceBlaster = this;
            lastSpawnedBulletTime = bullet.shootTime;
        }
    }
}

Please note that the projectile game object spawned registers itself on the gun.
By doing this, we will be able to ensure that only the state authority of the gun will check if the projectile hits a target.

In order to limit the list size, the PurgeBulletShoots() methods remove old shots from the list.
Indeed, it is useless to keep all shots indefinitely in the list because they were processed by all players as soon as they were added to the list and the propagation took place on the network.

In addition, the list of shots is cleared when the state authority changes, because the new authority probably does not have the same time reference as the previous one (IStateAuthorityChanged.StateAuthorityChanged()).

インパクト

ネットワーク化された構造体であるImpactInfoは、パラメータを保存するために作成されます。

C#

public struct ImpactInfo : INetworkStruct
{
    public Vector2 uv;
    public Color color;
    public float sizeModifier;
    public NetworkBehaviourId networkProjectionPainterId;
    public float impactTime;
    public PlayerRef source;
}

新しいインパクトは、プロジェクタイルがターゲットと衝突するとネットワーク化されたRecentImpactsリストに追加されます。
それから、オブジェクトテクスチャの修正のため、銃のステート権限を有するプレイヤーがFixedUpdateNetwork()にあるリストをCheckRecentImpacts()メソッドで確認します。

C#

const int MAX_IMPACTS = 50;
[Networked]
Capacity(MAX_IMPACTS)]
public NetworkLinkedList<ImpactInfo> RecentImpacts { get; }

C#

void CheckRecentImpacts()
{
    foreach (var impact in RecentImpacts)
    {
        if (impact.impactTime > lastRecentImpactTime && impact.source == Object.StateAuthority)
        {
            if (Runner.TryFindBehaviour<NetworkProjectionPainter>(impact.networkProjectionPainterId, out var networkProjectionPainter))
            {
                if (networkProjectionPainter.HasStateAuthority || Object.HasStateAuthority)
                {
                    networkProjectionPainter.PaintAtUV(impact.uv, impact.sizeModifier, impact.color);
                }
            }
        lastRecentImpactTime = impact.impactTime;
        }
    }
}

ショットのように、インパクトサイズを制限するため、PurgeRecentImpacts()メソッドはリストから古いメソッドを取り除きます。
実際に、インパクトはリストに加えられるとすぐに全てのプレイヤーによって処理され、ネットワーク上で伝搬されるため、永遠にリストにすべてのインパクトを残しておいても無意味です。

PaintBlasterProjectile

FixedUpdate()の間、銃のステート権限を有するプレイヤーはプロジェクタイルがオブジェクトと衝突したかどうかを確認します。

プロジェクタイルがオブジェクトと衝突していない場合は、一定のタイマー時間が経過した後、デスポーンされます。

C#

private void Update()
{
    if (Time.time > endOfLifeTime)
    {
        Destroy(gameObject);
    }
}

プロジェクタイルがオブジェクトと衝突している場合(レイキャストヒットが判断)は、以下が行われます。

  • インパクト位置が計算され、オブジェクトテクスチャが修正可能なもの( ProjectionPainterコンポーネントとNetworkProjectionPainterコンポーネントを持つオブジェクト)であれば、ネットワーク化されたRecentImpactsリストに追加される
  • プロジェクタイルがデスポーンされる
  • パーティクルシステムをを持つインパクトプレハブがスポーンされ、小さな視覚効果が生まれる

ネットワークであるImpact変数のuvフィールドがRayCastヒットtextureCoord フィールドで埋められます。
このtextureCoordフィールドに値を入れるには、以下のような条件が必要となります。

  • オブジェクトには、ペイントしたいメッシュと同様のメッシュを持つメッシュコライダーがあること
  • メッシュのあるFBXで、その "Meshes > read/write"パラメータがTrueに設定されていること

NetworkProjectionPainter & ProjectionPainter

Fusion Metaverse Animal

ネットワーク間でのテクスチャ修正の同期を要約すると以下のようになります。

  • PaintAtUV()メソッドでプロジェクタイルを発射した銃からNetworkProjectionPainterがテクスチャ修正リクエストを受け取る
  • そして、NetworkProjectionPainterがそのリクエストを、実際にテクスチャ修正を行うことになるローカルのProjectionPainterコンポーネントに送信する
  • テクスチャ修正が終了すると、コールバックによってNetworkProjectionPainter iに知らされる
  • オブジェクトのステート権限を有するプレイヤーがインパクトに関する全ての情報を含むネットワークリストを更新する
  • リモートプレイヤーは、ローカルのProjectionPainterコンポーネントを使用してオブジェクトのテクスチャを更新できる

それでは、仕組みを詳細に見ていきましょう。

ステップ 1 : テクスチャ修正リクエスト

まず、PaintAtUV()メソッドでプロジェクタイルを発射した銃から、NetworkProjectionPainterがテクスチャ修正リクエストを受け取ります。
オブジェクトでステート権限を有するプレイヤーのみが決定性テクスチャステートの維持に責任を持っていますが、リモートプレイヤーがステート権限を持たずにオブジェクトを発射した場合、遅延を防ぐため、リモートプレイヤーによってPrePaintAtUV()メソッドで一時的なテクスチャ修正が行われます。

C#

public void PaintAtUV(Vector2 uv, float sizeModifier, Color color)
{
    if (Object.HasStateAuthority)
    {
        painter.PaintAtUV(uv, sizeModifier, color);
    }
    else
    {
        painter.PrePaintAtUV(uv, sizeModifier, color);
    }
}

ステップ2 : テクスチャ修正

ローカルのProjectionPainterは、受信したインパクトパラメータ(UV座標、プロ弱タイルのサイズおよび色)を使用してオブジェクトテクスチャ修正を行います。

テクスチャ修正を簡単に説明すると、以下のようになります。

  • ペイントのためのオブジェクトのテクスチャが一時的なカメラレンダリングテクスチャと置き換えられる
  • テクスチャの前にあるインパクトブラシが、各ヒットで適切なUV座標部分に表示される
  • それから、レンダリングテクスチャを更新するカメラによってキャプチャされ、プレイヤーは新しいシューティングインパクトを確認できるようになる
  • 定期的に、定義されたオブジェクトテクスチャが、これまでのインパクトを含む新しいテクスチャで更新される。この操作は、リソース消費を防ぐため、毎回のヒットで行われるわけではありません。

プロセス全体についての詳細な説明は、 Texture Paintingの記事でご確認いただけます。

ステップ3 : テクスチャ修正コールバック

Awake()の間、ProjectionPainterはリスナーを検索します。(NetworkProjectionPainterIProjectionListenerインタフェースを実装します。)

C#

private void Awake()
{
    listeners = new List<IProjectionListener>(GetComponentsInParent<IProjectionListener>());
}

そのため、テクスチャ修正の終了時にNetworkProjectionPainterに知らせることができます。

C#

public void PaintAtUV(Vector2 uv, float sizeModifier, Color color)
{
    var paintPosition = UV2CaptureLocalPosition(uv);
    Paint(paintPosition, sizeModifier, color);
    foreach (var listener in listeners)
    {
        listener.OnPaintAtUV(uv, sizeModifier, color);
    }
}

ステップ4 : リストに新しいいんぱぅとを追加してリモートプレイヤーに知らせる

オブジェクト上でステート権限を有するNetworkProjectionPainterは、インパクトに関するすべての情報を持つネットワーク配列を更新することができます。

C#

public void OnPaintAtUV(Vector2 uv, float sizeModifier, Color color)
{
    if (Object.HasStateAuthority)
    {
    ...
        ProjectionInfosLinkedList.Add(new ProjectionInfo { uv = uv, sizeModifier = sizeModifier, color = color, paintId = nextStoredPaintId });
    ...
    }
}

テクスチャの修正に必要なインパクトパラメータはネットワーク化されたProjectionInfo構造体に保存されます。
全てのインパクトは、ネットワーク化されたProjectionInfosLinkedListリストに保存されます。そのため、新しいインパクトがリストに追加されると、すべてのプレイヤーに知らされることになります。

C#

public struct ProjectionInfo:INetworkStruct
{
    public Vector2 uv;
    public Color color;
    public float sizeModifier;
    public int paintId;
}

C#

[Networked(OnChanged = nameof(OnInfosChanged))]
[Capacity(MAX_PROJECTIONS)]
public NetworkLinkedList<ProjectionInfo> ProjectionInfosLinkedList { get; }

ステップ 5 : リモートプレイヤーによってアップデートされたテクスチャ

プレイヤーは、ネットワークリストProjectionInfosLinkedListとローカルのProjectionPainterコンポーネントを使用してオブジェクトテクスチャをアップデートすることができるようになりました。

C#

static void OnInfosChanged(Changed<NetworkProjectionPainter> changed)
{
    changed.Behaviour.OnInfosChanged();
}

void OnInfosChanged()
{
...
    painter.PaintAtUV(info.uv, info.sizeModifier, info.color);
...
}
Back to top