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 frame, AssetRef<EntityPrototype> childPrototype)
{
AsteroidsGameConfig config = frame.FindAsset(frame.RuntimeConfig.GameConfig);
EntityRef asteroid = frame.Create(childPrototype);
Transform2D* asteroidTransform = frame.Unsafe.GetPointer<Transform2D>(asteroid);
asteroidTransform->Position = GetRandomEdgePointOnCircle(frame, config.AsteroidSpawnDistanceToCenter);
asteroidTransform->Rotation = GetRandomRotation(frame);
if (frame.Unsafe.TryGetPointer<PhysicsBody2D>(asteroid, out var body))
{
body->Velocity = asteroidTransform->Up * config.AsteroidInitialSpeed;
body->AddTorque(frame.RNG->Next(config.AsteroidInitialTorqueMin, config.AsteroidInitialTorqueMax));
}
}
public static FP GetRandomRotation(Frame frame)
{
return frame.RNG->Next(0, 360);
}
public static FPVector2 GetRandomEdgePointOnCircle(Frame frame, FP radius)
{
return FPVector2.Rotate(FPVector2.Up * radius , frame.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 frame)
{
AsteroidsGameConfig config = frame.FindAsset(frame.RuntimeConfig.GameConfig);
for (int i = 0; i < frame.Global->AsteroidsWaveCount + config.InitialAsteroidsCount; i++)
{
SpawnAsteroid(frame, config.AsteroidPrototype);
}
frame.Global->AsteroidsWaveCount++;
}
この関数は、ウェーブ数に基づいて複数のアステロイドをスポーンし、カウンターを増やします。
最後に、シミュレーション開始時に各システムで一度だけ呼び出されるOnInit関数を使用して、最初のウェーブのスポーンを実行します。
C#
public override void OnInit(Frame frame)
{
SpawnAsteroidWave(frame);
}
スポーンシステムの実装が完了したので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ファイルを作成して、以下のような空コンポーネントを追加します。
Qtn
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つのアステロイドが最初のウェーブにスポーンします。