概要
ワールド制作が高度になるにつれて、扱うデータの数も増えていきます。例えば、ゲームに参加している全プレイヤーのスコア、複数のスポーン地点の座標、パズルの各ピースの状態など、これらを一つ一つの独立した変数として管理するのは非効率的で、拡張性もありません。
このような場面で強力なツールとなるのが配列 (Array) です。配列を使うことで、同じ種類の複数のデータを一つの変数にまとめて、効率的に管理・操作することができます 。
本記事では、UdonSharpにおける配列の基本的な使い方から、List<T>やDictionary<T, K>といったC#の標準的なデータ構造が使えないという制約の中で、どのようにして複雑なデータを管理していくかについて解説します。
1. 配列の基本
配列は、同じデータ型の要素が連番のインデックス(添え字)で並んだものです。インデックスは0から始まります。
配列の宣言
データ型[] 配列名; のように、データ型の後に[]を付けて宣言します。
// int型の配列を宣言
int[] scores;
// Transform型の配列を宣言
public Transform[] spawnPoints;
// UdonSharpBehaviour型の配列も可能
public PlayerCard[] playerCards;
配列の初期化と値へのアクセス
配列を使用するには、宣言後に要素数を指定して初期化する必要があります。
void Start()
{
// 5つの要素を持つint型の配列を作成
scores = new int[5];
// インデックスを使って各要素に値を代入
scores[0] = 100;
scores[1] = 85;
scores[2] = 92;
scores[3] = 78;
scores[4] = 88;
// インデックスを使って値を取得
Debug.Log($"2番目のプレイヤーのスコア: {scores[1]}"); // 出力: 85
// publicな配列はInspectorでサイズと内容を設定できる
// spawnPointsの最初の要素の位置にテレポート
Networking.LocalPlayer.TeleportTo(spawnPoints[0].position, spawnPoints[0].rotation);
}
forループとの組み合わせ
配列の真価は、forループと組み合わせることで発揮されます。配列のLengthプロパティで要素数を取得し、全要素に対して同じ処理を繰り返し実行できます。
// 全プレイヤーのスコアを合計する
int totalScore = 0;
for (int i = 0; i < scores.Length; i++)
{
totalScore += scores[i];
}
Debug.Log($"合計スコア: {totalScore}");
// --- 遅延イベントを使った巡回の例 ---
// 注意: SendCustomEventDelayedSecondsは引数を渡せないため、
// 以下のようにインデックスをフィールドで管理します
private int currentPointIndex = 0;
public void StartTour()
{
currentPointIndex = 0;
MoveToNextPoint();
}
public void MoveToNextPoint()
{
if (currentPointIndex < spawnPoints.Length)
{
// 現在のポイントへ移動
Debug.Log($"ポイント {currentPointIndex} へ移動");
currentPointIndex++;
// まだ次のポイントがあれば、5秒後に次へ
if (currentPointIndex < spawnPoints.Length)
{
SendCustomEventDelayedSeconds(nameof(MoveToNextPoint), 5.0f);
}
}
}
2. 配列の同期
配列も[UdonSynced]属性を付けることで、ネットワーク同期の対象にすることができます。これにより、インスタンス内の全プレイヤーで同じ配列データを共有できます。
[UdonSynced]
private int[] playerScores = new int[8];
public void UpdateScore(int playerId, int newScore)
{
// playerIdをインデックスとしてスコアを更新
if (playerId >= 0 && playerId < playerScores.Length)
{
Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
playerScores[playerId] = newScore;
RequestSerialization();
}
}
public override void OnDeserialization()
{
// 同期されたスコア配列を使ってUIなどを更新
UpdateScoreboard();
}
注意: 配列全体を同期するのは、データ量が多くなる可能性があります。特に要素数が大きい配列や、内容が頻繁に変わる配列を同期させる場合は、ネットワーク負荷に注意が必要です。
3. UdonSharpにおける高度なデータ構造
C#では通常、サイズの可変なList<T>や、キーと値のペアを格納するDictionary<T, K>といった便利なデータ構造が多用されます。しかし、UdonSharpではジェネリクスがサポートされていないため、これらを直接使用することは できません。しかし、配列を工夫することで、その機能の一部を再現することは可能です。
List<T>の代替: 配列とカウンタ
リストのように、後から要素を追加していくような機能は、大きな配列をあらかじめ確保しておき、現在いくつの要素が使われているかを別のint変数で管理することで擬似的に実装できます。
// 最大100人まで名前を記録できるリストの代替
private string[] nameList = new string[100];
private int nameCount = 0;
public void AddName(string newName)
{
// 配列に空きがある場合のみ追加
if (nameCount < nameList.Length)
{
nameList[nameCount] = newName;
nameCount++;
}
}
Dictionary<T, K>の代替: 2本の配列
辞書(連想配列)のように、特定のキー(例: プレイヤー名)に対応する値(例: スコア)を管理したい場合は、キー用の配列と値用の配列を2本用意し、同じインデックスで関連付けます。
// プレイヤー名とスコアを関連付ける辞書の代替
private string[] playerNames = new string[8];
private int[] playerScores = new int[8];
// プレイヤー名からスコアを取得する
public int GetScoreByName(string targetName)
{
for (int i = 0; i < playerNames.Length; i++)
{
if (playerNames[i] == targetName)
{
return playerScores[i]; // 対応するインデックスのスコアを返す
}
}
return -1; // 見つからなかった場合
}
これは効率的な方法ではありませんが、UdonSharpの制約下で辞書的な機能を実現するための一つのアプローチです。
まとめ
- 配列は、同じ型の複数のデータをまとめて扱うための基本的なデータ構造です。
- 配列は
forループと組み合わせることで、全要素に対する反復処理を効率的に記述できます。 - 配列に
[UdonSynced]を付けることで、内容をネットワーク同期できますが、データ量には注意が必要です。 - UdonSharpでは
List<T>やDictionary<T, K>が使えないため、配列と他の変数を組み合わせることで、その機能を擬似的に実装する必要があります。 - 配列のサイズは一度作成すると変更できないため、必要な要素数をあらかじめ想定して大きめに確保しておく、といった設計上の工夫が求められます。
配列を使いこなすことは、プレイヤー管理、アイテム管理、ステージ管理など、複雑なシステムを構築する上で避けては通れない道です。UdonSharpの制約を理解し、配列を駆使してデータを巧みに操る方法をマスターしましょう。