This document is about: FUSION 2
SWITCH TO

Metaverse Picazoo


Available in the Industries Circle
Circle

概述

PicaZoo 場景是一個迷你遊戲,在其中玩家必須以一個漆彈槍來找到動物雕像。

這展示了透過網路來同步材質調整的方法。

如果您希望開發一個射擊遊戲,請參照專用技術範例:

我們在桌面模式中使用一個特定的控制器,以透過滑鼠有一個更好的槍控制。

Fusion Metaverse PicaZoo

油漆噴射器

場景含有多個漆彈槍:

  • 射擊預先定義的顏色的子彈的槍
  • 射擊隨機顏色的子彈的槍
Fusion Metaverse Guns

槍由PaintBlaster類別管理,其負責維護兩個清單:

  • 槍清單
  • 衝擊清單

因為清單是連網的,遠端玩家可以檢查新的射擊/衝擊是否發生,並且展示它們。
這樣做的話,就不需要生成一個已連網子彈預製件。

射擊

射擊的屬性被聚集在一個BulletShoot已連網架構之中。

C#

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

首先,使用者的射擊輸入由InputActionProperty UseAction處理,其用於設定IsUsed布林值。

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

因為射擊清單已連網,所有玩家接收到已更新資料,而不論是哪一個玩家發射。
因此玩家必須在Render()中檢查新的資料是否已經被接收,以在射擊發生時生成一個本機的圖像物件(CheckShoots())。

C#

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

C#

public override void Render()
{
    base.Render();
    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;
        }
    }
}

請注意,已生成的拋射物遊戲物件在槍上註冊它自己。
這樣做的話,我們將能夠確保只有槍的狀態授權將檢查拋射物是否命中一個目標。

為了限制清單大小,PurgeBulletShoots()方法從清單中移除舊的射擊。
實際上,在清單中無限地保持所有射擊是沒有意義的,因為在它們被新增到清單,而且在網路上傳播之後,所有玩家都會處理它們。

此外,在狀態授權改變時,將清除射擊清單,因為新的授權可能沒有與前一個授權相同的時間參照(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已連網清單中新增新的衝擊。
然後,有槍狀態授權的玩家以CheckRecentImpacts()方法來檢查在Render()中的清單,以請求物件材質調整。

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()方法從清單中移除舊的衝擊。
實際上,在清單中無限地保持所有衝擊是沒有意義的,因為在它們被新增到清單,而且在網路上傳播之後,所有玩家都會處理它們。

油漆噴射器拋射物

FixedUpdate()期間,有槍狀態授權的玩家檢查拋射物是否將碰撞一個物件。

如果拋射物不會碰撞一個物件,它將在一個預先定義的計時器之後被取消生成。

C#

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

如果拋射物碰撞一個物件(由一個射線命中來確定):

  • 如果物件材質應該是可以調整的(附有ProjectionPainterNetworkProjectionPainter元件的物件),則計算衝擊位置並且將其新增到已連網RecentImpacts清單之中
  • 取消生成拋射物
  • 生成一個附有粒子系統的衝擊預製件,以生成一個小的視覺效果

RayCast命中textureCoord欄位來填入網路Impact變數uv欄位。
為了在這個textureCoord欄位中有一個值,需要一些條件:

  • 物件必須有一個網格碰撞器,其與我們希望彩繪的網格有相同的網格
  • 在含有網格的FBX之中,它的「網格 > 讀取/寫入」參數必須設定為真

網路拋射噴射器及拋射噴射器

Fusion Metaverse Animal

透過網路的材質調整的同步,可以總結為下列步驟:

  • NetworkProjectionPainter從以PaintAtUV()方法發射拋射物的槍來接收一個材質調整請求,
  • 然後它發送請求到本機ProjectionPainter元件,其將實際上執行材質調整,
  • 當終止材質調整時,由一個回調來告知NetworkProjectionPainter
  • 附有物件狀態授權的玩家更新網路清單,其含有所有在衝擊上的資訊,
  • 遠端玩家可以使用它們的本機的ProjectionPainter元件來更新物件的材質。

現在,讓我們看看它工作的更多細節。

步驟1:材質調整請求

首先,NetworkProjectionPainter從以PaintAtUV()方法發射拋射物的槍來接收一個材質調整請求。
只有在物件上有狀態授權的玩家負責維持確定的材質狀態,但是為了避免沒有在物件上有狀態授權的遠端玩家命中物件時的任何延遲,由這個遠端玩家以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坐標之前顯示一個衝擊刷,
  • 然後它由更新轉譯材質的相機來捕捉,這樣玩家可以看見新的射擊衝擊,
  • 在常規的基礎上,由包含所有先前的衝擊的新的材質來更新確定的物件材質。這個操作不在每個命中執行,以避免資源耗用。

材質噴漆這個文章中可以找到更多關於總體流程的說明

步驟3:材質調整回調

Awake()期間,ProjectionPainter搜尋接聽者(NetworkProjectionPainter執行IProjectionListener介面)。

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]
[Capacity(MAX_PROJECTIONS)]
public NetworkLinkedList<ProjectionInfo> ProjectionInfosLinkedList { get; }

步驟5:由遠端玩家更新的材質

現在遠端玩家可以更新物件材質,方法是使用已連網清單ProjectionInfosLinkedList以及它們的本機ProjectionPainter元件。

C#

ChangeDetector changeDetector;

public override void Render()
{
    base.Render();
    foreach (var changedVar in changeDetector.DetectChanges(this))
    {
        if (changedVar == nameof(ProjectionInfosLinkedList))
        {
            OnInfosChanged();
        }
    }
}

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