6 - アステロイド
概要
船のプレイヤーエンティティが準備できたので、次はアステロイド(小惑星)をスポーンしましょう。
アステロイドのスポーン設定
ウェーブ毎に複数のアステロイドがスポーンします。アステロイドのスポーンに関する変数はシステム内にハードコードするのではなく、パラメーターを保持するアセットオブジェクトを設定ファイルとして使用するのが良い方法です。QuantumUser > Simulation
フォルダーで新しくAsteroidGameConfig
スクリプトを作成し、以下のコードを追加してください。
C#
using Photon.Deterministic;
using UnityEngine;
namespace Quantum.Asteroids
{
public class AsteroidsGameConfig: AssetObject
{
[Header("Asteroids configuration")]
[Tooltip("Prototype reference to spawn asteroids")]
public AssetRef<EntityPrototype> AsteroidPrototype;
[Tooltip("Speed applied to the asteroid when spawned")]
public FP AsteroidInitialSpeed = 8;
[Tooltip("Minimum torque applied to the asteroid when spawned")]
public FP AsteroidInitialTorqueMin = 7;
[Tooltip("Maximum torque applied to the asteroid when spawned")]
public FP AsteroidInitialTorqueMax = 20;
[Tooltip("Distance to the center of the map. This value is the radius in a random circular location where the asteroid is spawned")]
public FP AsteroidSpawnDistanceToCenter = 20;
[Tooltip("Amount of asteroids spawned in level 1. In each level, the number os asteroids spawned is increased by one")]
public int InitialAsteroidsCount = 5;
}
}
AssetObject
を継承したクラスはScriptableObject
になり、前のチュートリアルで紹介したMapData
やSystemConfig
アセットのように、Quantumのシミュレーションにデータを注入できるようになります。
AssetRef<EntityPrototype>
は特別な型で、QuantumからアセットデータベースのAssetObject
を参照する方法がAssetRef
です。他のフィールドは単なる設定値で、アステロイドのスポーンを担当するシステムで使用されます。
アセットのインスタンスを作成するには、Resources
フォルダーを右クリックしてCreate > Quantum > Asset.. > AsteroidsGameConfig
を選択してください。すべての値はデフォルト値のままで、AsteroidPrototype
フィールドにはAsteroidLargeEntityPrototype
をドロップしましょう。

設定の注入
Quantumのシミュレーション中にAssetObject
を取得する一般的な方法は2つあります。
- シーンオブジェクトかプレハブのエンティティコンポーネントに
AssetRef
フィールドを持たせる RuntimeConfig
からAssetObject
をグローバルなオブジェクトとして紐づける
今回は後者のアプローチを使用して、グローバルな設定として複数のシステムから使用できるようにします。
QuantumUser > Simulation
フォルダーで新しいスクリプトを作成して、RuntimeConfig.Asteroids
と名付け、以下のコードを追加してください。
C#
namespace Quantum
{
public partial class RuntimeConfig
{
public AssetRef<Asteroids.AsteroidsGameConfig> GameConfig;
}
}
このクラスは既存のRuntimeConfig
クラスを拡張する部分クラスとして使用されるため、ネームスペースはQuantum.Asteroids
ではなくQuantum
であることに注意してください。
Unityに戻り、シーン上のQuantumDebugRunner
ゲームオブジェクトを選択します。RuntimeConfig
項目に新しくGameConfig
フィールドが表示されるので、先ほど作成したAsteroidGameConfig
をドロップしてください。

AsteroidSpawnerSystem
QuantumUser > Simulation
フォルダーで新しいC#スクリプトを作成し、名前をAsteroidsWaveSpawnerSystem
にします。そして以下のコードを追加してください。
C#
using Photon.Deterministic;
using UnityEngine.Scripting;
namespace Quantum.Asteroids
{
[Preserve]
public unsafe class AsteroidsWaveSpawnerSystem : SystemSignalsOnly
{
public void SpawnAsteroid(Frame f, AssetRef<EntityPrototype> childPrototype)
{
AsteroidsGameConfig config = f.FindAsset(f.RuntimeConfig.GameConfig);
EntityRef asteroid = f.Create(childPrototype);
Transform2D* asteroidTransform = f.Unsafe.GetPointer<Transform2D>(asteroid);
asteroidTransform->Position = GetRandomEdgePointOnCircle(f, config.AsteroidSpawnDistanceToCenter);
asteroidTransform->Rotation = GetRandomRotation(f);
if (f.Unsafe.TryGetPointer<PhysicsBody2D>(asteroid, out var body))
{
body->Velocity = asteroidTransform->Up * config.AsteroidInitialSpeed;
body->AddTorque(f.RNG->Next(config.AsteroidInitialTorqueMin, config.AsteroidInitialTorqueMax));
}
}
public static FP GetRandomRotation(Frame f)
{
return f.RNG->Next(0, 360);
}
public static FPVector2 GetRandomEdgePointOnCircle(Frame f, FP radius)
{
return FPVector2.Rotate(FPVector2.Up * radius , f.RNG->Next() * FP.PiTimes2);
}
}
}
このシステムは現時点では、アステロイドをスポーンするために呼び出される関数のみを持ちます。この関数では、RuntimeConfig
に紐づいたGameConfig
アセットから、スポーンするアステロイドのエンティティのプロトタイプを取得します。
FindAsset
は非常に効率的なので、アステロイドをスポーンするたびに繰り返し呼び出しても問題ありません。
さらに、アステロイドのエンティティを生成する関数では、アステロイドをランダムな位置と回転で初期化し、GameConfig
の値に基づいた速度とトルクを適用します。
システムへの状態の追加
SpawnAsteroid
関数ができたので、ウェーブを実装しましょう。アステロイドはウェーブ毎にスポーンして、各ウェーブでは前のウェーブより多い1つ以上のアステロイドをスポーンします。そのために、ウェーブカウンターを記録することが必要です。
ECSのシステムはステートレスなので、AsteroidsWaveSpawnerSystem
に通常の変数を追加することはできません。これはQuantumの単なる良いプラクティスではなく、シミュレーションの予測/ロールバックを正しく実行するために必須です。Quantumでは、絶対にシステムに状態を持たせないでください。
システムに状態を持たせるかわりに、以下2つのアプローチが使用できます。
- グローバルフレームに状態を持たせる。グローバルフレームは、すべてのシステムからアクセス可能なシングルトンの値を含みます。
- シングルトンコンポーネントに状態を持たせる。シングルトンコンポーネントは通常のエンティティコンポーネントに似ていますが、便利なAPIから簡単に取得できるようになっています。シングルトンコンポーネントの詳細はこちらをご覧ください。
このチュートリアルでは簡単なグローバルフレームを使用します。QuantumUser > Simulation
フォルダーに新しくGlobal.qtn
ファイルを作成し、以下のコードを追加してください。
global
{
Int32 AsteroidsWaveCount;
}
これによってグローバルフレームにint
が追加され、frame.Global->AsteroidsWaveCount
を呼び出すことで、どのシステムからもアクセスできるようになります。
ウェーブの追加
ウェーブカウンターが実装されたので、AsteroidWaveSpawnerSystem
に戻り、以下の関数を追加してください。
C#
private void SpawnAsteroidWave(Frame f)
{
AsteroidsGameConfig config = f.FindAsset(f.RuntimeConfig.GameConfig);
for (int i = 0; i < f.Global->AsteroidsWaveCount + config.InitialAsteroidsCount; i++)
{
SpawnAsteroid(f, config.AsteroidPrototype);
}
f.Global->AsteroidsWaveCount++;
}
この関数は、ウェーブ数に基づいて複数のアステロイドをスポーンし、カウンターを増やします。
最後に、シミュレーション開始時に各システムで一度だけ呼び出されるOnInit
関数を使用して、最初のウェーブのスポーンを実行します。
C#
public override void OnInit(Frame f)
{
SpawnAsteroidWave(f);
}
スポーンシステムの実装が完了したのでUnityに戻りましょう。最後のステップとして、このシステムが実行されるようにするため、AsteroidSystemConfig
のシステムリストにシステムを追加しましょう。
船の識別
ゲームを再生すると、何も変更していないはずのAsteroidsShipSystem
で、多くの「null reference error」がコンソールに表示されます。原因はこのシステムのフィルターにあります。
C#
public struct Filter
{
public EntityRef Entity;
public Transform2D* Transform;
public PhysicsBody2D* Body;
}
船もアステロイドもTransform2D
とPhysicsBody2D
を持っているため、どちらのオブジェクトもフィルタリングされ、すべてのアステロイドに対して船の更新処理が実行されてしまいます。これを修正する簡単な方法は、船固有のコンポーネントを追加することです。
QuantumUser > Simulation
フォルダーに新しいAsteroidsShip.qtn
ファイルを作成して、以下のような空コンポーネントを追加します。
C#
component AsteroidsShip
{
}
備考: ECSの空コンポーネントは、タグコンポーネントと呼ばれます。主なユースケースは、エンティティをタグで識別することになります。
Unityに戻り、AsteroidsShip
プレハブを開きます。QuantumEntityPrototype
コンポーネントのEntity Components
リストの+
ボタンを押して、AsteroidsShip
コンポーネントを選択します。

最後に、このコンポーネントをAsteroidsShipSystem
のフィルターに含めて調整します。
C#
public struct Filter
{
public EntityRef Entity;
public Transform2D* Transform;
public PhysicsBody2D* Body;
public AsteroidsShip* AsteroidsShip;
}
Unityに戻りゲームを再生しましょう。プレイヤーオブジェクトに加えて、5つのアステロイドが最初のウェーブにスポーンします。
