Metaverse Picazoo
概要
PicaZoo シーンは、プレイヤーがペイントガンを使用して動物の銅像を探すミニゲームです。
ここではネットワークを通してテクスチャ修正を同期する方法を説明しています。
シューティングゲームを開発する場合は、専用の技術サンプルを参照してください。
デスクトップモードの特定のコントローラを使用して、マウスによるガンのコントロールの精度を上げています。
PaintBlaster
シーンにはいくつかのペイントガンを使用しています。
- 事前に定義した色の弾丸を撃つガン
- ランダムな色の弾丸を撃つガン
銃は以下の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
ネットワーク間でのテクスチャ修正の同期を要約すると以下のようになります。
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
はリスナーを検索します。(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(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