9 - 射撃
ShipConfig
現在、船の旋回速度や加速度の値はAsteroidsShipSystemにハードコードされています。これらを含む船の動作を制御するような様々な値は、設定ファイルに置く方が良いでしょう。
新しいC#スクリプトを作成して、AsteroidsShipConfigと名付け、以下のコードを追加してください。
C#
using Photon.Deterministic;
using UnityEngine;
namespace Quantum
{
public class AsteroidsShipConfig : AssetObject
{
[Tooltip("The speed that the ship turns with added as torque")]
public FP ShipTurnSpeed = 8;
[Tooltip("The speed that the ship accelerates using add force")]
public FP ShipAceleration = 6;
[Tooltip("Time interval between ship shots")]
public FP FireInterval = FP._0_10;
[Tooltip("Displacement of the projectile spawn position related to the ship position")]
public FP ShotOffset = 1;
[Tooltip("Prototype reference to spawn ship projectiles")]
public AssetRef<EntityPrototype> ProjectilePrototype;
}
}
それからAsteroidsShip.qtnに、設定ファイルの参照を追加します。
Qtn
component AsteroidsShip
{
AssetRef<AsteroidsShipConfig> ShipConfig;
}
Resourcesフォルダーに設定ファイルの新しいインスタンスを作成して、DefaultShipConfigと名付けます。そして、AsteroidsShipゲームオブジェクトのShipConfigフィールドにドロップして、船のプロトタイプに紐づけてください。
次に、設定ファイルを値を使用するように、AsteroidsShipSystemのUpdateShipMovement関数を調整しましょう。
C#
var config = frame.FindAsset(filter.AsteroidsShip->ShipConfig);
FP shipAcceleration = config.ShipAceleration;
FP turnSpeed = config.ShipTurnSpeed;
射撃
まず、AsteroidsShip.qtnにFireIntervalフィールドを追加して、射撃間隔の時間を記録できるようにします。
Qtn
component AsteroidsShip
{
AssetRef<AsteroidsShipConfig> ShipConfig;
FP FireInterval;
}
次に、AsteroidsShipSystemを開き、射撃を制御する関数を追加します。
C#
private void UpdateShipFire(Frame frame, ref Filter filter, Input* input)
{
var config = frame.FindAsset(filter.AsteroidsShip->ShipConfig);
if (input->Fire && filter.AsteroidsShip->FireInterval <= 0)
{
filter.AsteroidsShip->FireInterval = config.FireInterval;
// TODO create projectile
}
else
{
filter.AsteroidsShip->FireInterval -= frame.DeltaTime;
}
}
この関数によって、射撃ボタンを押した時に、定期的な間隔で船が射撃できるようになります。TODOの行は、弾エンティティを生成するコードに置き換えます。
システムのUpdate関数内でUpdateShipMovementを呼び出した後に、この関数を呼び出しましょう。
C#
UpdateShipFire(f, ref filter, input);
新しいC#スクリプトを作成し、AsteroidsProjectileSystemと名付け、以下のコードを追加してください。
C#
using Photon.Deterministic;
using UnityEngine.Scripting;
namespace Quantum.Asteroids
{
[Preserve]
public unsafe class AsteroidsProjectileSystem : SystemSignalsOnly
{
public void AsteroidsShipShoot(Frame frame, EntityRef owner, FPVector2 spawnPosition, AssetRef<EntityPrototype> projectilePrototype)
{
EntityRef projectileEntity = frame.Create(projectilePrototype);
Transform2D* projectileTransform = frame.Unsafe.GetPointer<Transform2D>(projectileEntity);
Transform2D* ownerTransform = frame.Unsafe.GetPointer<Transform2D>(owner);
projectileTransform->Rotation = ownerTransform->Rotation;
projectileTransform->Position = spawnPosition;
}
}
}
そして、UnityのAsteroidsSystemConfigにこのシステムを追加します。
次に、C#スクリプトでAsteroidsProjectileConfigを作成して、弾の設定ファイルの機能を持たせます。
C#
using Photon.Deterministic;
using UnityEngine;
namespace Quantum
{
public class AsteroidsProjectileConfig : AssetObject
{
[Tooltip("Speed applied to the projectile when spawned")]
public FP ProjectileInitialSpeed = 15;
[Tooltip("Time until destroy the projectile")]
public FP ProjectileTTL = 1;
}
}
最後に、AsteroidsProjectile.qtnファイルを作成し、弾のデータコンポーネントにします。
Qtn
component AsteroidsProjectile
{
FP TTL;
EntityRef Owner;
AssetRef<AsteroidsProjectileConfig> ProjectileConfig;
}
AsteroidsProjectileSystemのAsteroidsShipShoot関数の調整として、弾を初期化する以下のコードを末尾に追加します。
C#
AsteroidsProjectile* projectile = frame.Unsafe.GetPointer<AsteroidsProjectile>(projectileEntity);
var config = frame.FindAsset(projectile->ProjectileConfig);
projectile->TTL = config.ProjectileTTL;
projectile->Owner = owner;
PhysicsBody2D* body = frame.Unsafe.GetPointer<PhysicsBody2D>(projectileEntity);
body->Velocity = ownerTransform->Up * config.ProjectileInitialSpeed;
シグナル
AsteroidsShipShootは船が射撃するたびに呼び出されるべきですが、この関数は異なるシステムに存在します。解決策の一つとして、関数をstaticにして直接呼び出す方法もありますが、システム同士が密結合してしまうため理想的ではありません(特にコードベースが大きい場合には)。
この解決策は「シグナル」を使用することです。シグナルはイベントベースでシグナル間の通信を行う方法で、任意の.qtnファイルで定義できます。今回はAsteroidsProjectile.qtnを開き、以下のようなシグナルを追加してください。
Qtn
component AsteroidsProjectile
{
FP TTL;
EntityRef Owner;
AssetRef<AsteroidsProjectileConfig> ProjectileConfig;
}
signal AsteroidsShipShoot(EntityRef owner, FPVector2 spawnPosition, AssetRef<EntityPrototype> projectilePrototype);
ここでAsteroidsProjectileSystemにISignalAsteroidsShipShootインターフェースを実装します。
C#
public unsafe class AsteroidsProjectileSystem : SystemSignalsOnly, ISignalAsteroidsShipShoot
このインターフェースによって、シグナルが発生するたびにAsteroidsShipShootが呼び出されるようになります。
シグナルを発生させるには、AsteroidsShipSystemの// TODO create projectile行を以下に置き換えます。
C#
var relativeOffset = FPVector2.Up * config.ShotOffset;
var spawnPosition = filter.Transform->TransformPoint(relativeOffset);
frame.Signals.AsteroidsShipShoot(filter.Entity, spawnPosition, config.ProjectilePrototype);
弾エンティティの作成
シーン上に新しくQuantum > 2D > Circle Entityゲームオブジェクトを作成して、AsteroidsProjectileと名付け、MeshRendererとMeshFilterを削除します。CircleColliderのradiusを0.16に調整して、PhysicsBody2Dにチェックを入れます。
QuantumEntityPrototypeのEntity ComponentsリストにAsteroidsProjectileコンポーネントを追加してください。TTLとOwnerフィールドは、実行時に設定ファイルから代入するので、空のままにしておきましょう。設定のために、新しいAsteroidsProjectileConfigアセットを作成して、DefaultProjectileConfigと名付けます。Projectile Initial Speedを20に、Projectile TTLを2にして、コンポーネントのフィールドに設定ファイルを紐づけます。
AsteroidsProjectileの子要素に3D > Cubeゲームオブジェクトを作成して、スケールを(0.17, 0.17, 0.17)にします。これが弾のビジュアルになります。Colliderは削除しておいてください。
ResourcesフォルダーにAsteroidsProjectileをドラッグしてプレハブを作成し、シーン上のものは削除します。DefaultShipConfigのProjectileに、プレハブのprototypeを紐づけます。
生存時間
現時点でまだ弾は自動的に削除されません。弾を削除するケースは2つあって「生存時間を過ぎる」か「船かアステロイドに当たる」です。
生存時間(TTL:Time To Live)を過ぎた弾を削除するには、システムでフレーム毎にチェックする必要があります。AsteroidsProjectileSystemはSystemSignalsOnlyのかわりにSystemMainThreadFilter<AsteroidsProjectileSystem.Filter>を継承して、以下を追加してください。
C#
public struct Filter
{
public EntityRef Entity;
public AsteroidsProjectile* Projectile;
}
public override void Update(Frame frame, ref Filter filter)
{
filter.Projectile->TTL -= frame.DeltaTime;
if (filter.Projectile->TTL <= 0)
{
frame.Destroy(filter.Entity);
}
}
ゲームを再生すると、spaceを押して船が射撃できるようになっています。弾は2秒後に自動的に削除されますが、まだ船やアステロイドに物理的に衝突することはありません。チュートリアルの次のパートでは、弾に衝突判定を追加していきましょう。