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
に、設定ファイルの参照を追加します。
C#
component AsteroidsShip
{
AssetRef<AsteroidsShipConfig> ShipConfig;
}
Resources
フォルダーに設定ファイルの新しいインスタンスを作成して、DefaultShipConfig
と名付けます。そして、AsteroidsShip
ゲームオブジェクトのShipConfig
フィールドにドロップして、船のプロトタイプに紐づけてください。
次に、設定ファイルを値を使用するように、AsteroidsShipSystem
のUpdateShipMovement
関数を調整しましょう。
C#
var config = f.FindAsset(filter.AsteroidsShip->ShipConfig);
FP shipAcceleration = config.ShipAceleration;
FP turnSpeed = config.ShipTurnSpeed;
射撃
まず、AsteroidsShip.qtn
にFireInterval
フィールドを追加して、射撃間隔の時間を記録できるようにします。
C#
component AsteroidsShip
{
AssetRef<AsteroidsShipConfig> ShipConfig;
FP FireInterval;
}
次に、AsteroidsShipSystem
を開き、射撃を制御する関数を追加します。
C#
private void UpdateShipFire(Frame f, ref Filter filter, Input* input)
{
var config = f.FindAsset(filter.AsteroidsShip->ShipConfig);
if (input->Fire && filter.AsteroidsShip->FireInterval <= 0)
{
filter.AsteroidsShip->FireInterval = config.FireInterval;
// TODO create projectile
}
else
{
filter.AsteroidsShip->FireInterval -= f.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 f, EntityRef owner, FPVector2 spawnPosition, AssetRef<EntityPrototype> projectilePrototype)
{
EntityRef projectileEntity = f.Create(projectilePrototype);
Transform2D* projectileTransform = f.Unsafe.GetPointer<Transform2D>(projectileEntity);
Transform2D* ownerTransform = f.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
ファイルを作成し、弾のデータコンポーネントにします。
C#
component AsteroidsProjectile
{
FP TTL;
EntityRef Owner;
AssetRef<AsteroidsProjectileConfig> ProjectileConfig;
}
AsteroidsProjectileSystem
のAsteroidsShipShoot
関数の調整として、弾を初期化する以下のコードを末尾に追加します。
C#
AsteroidsProjectile* projectile = f.Unsafe.GetPointer<AsteroidsProjectile>(projectileEntity);
var config = f.FindAsset(projectile->ProjectileConfig);
projectile->TTL = config.ProjectileTTL;
projectile->Owner = owner;
PhysicsBody2D* body = f.Unsafe.GetPointer<PhysicsBody2D>(projectileEntity);
body->Velocity = ownerTransform->Up * config.ProjectileInitialSpeed;
シグナル
AsteroidsShipShoot
は船が射撃するたびに呼び出されるべきですが、この関数は異なるシステムに存在します。解決策の一つとして、関数をstaticにして直接呼び出す方法もありますが、システム同士が密結合してしまうため理想的ではありません(特にコードベースが大きい場合には)。
この解決策は「シグナル」を使用することです。シグナルはイベントベースでシグナル間の通信を行う方法で、任意の.qtn
ファイルで定義できます。今回はAsteroidsProjectile.qtn
を開き、以下のようなシグナルを追加してください。
C#
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);
f.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 f, ref Filter filter)
{
filter.Projectile->TTL -= f.DeltaTime;
if (filter.Projectile->TTL <= 0)
{
f.Destroy(filter.Entity);
}
}
ゲームを再生すると、space
を押して船が射撃できるようになっています。弾は2
秒後に自動的に削除されますが、まだ船やアステロイドに物理的に衝突することはありません。チュートリアルの次のパートでは、弾に衝突判定を追加していきましょう。