This document is about: QUANTUM 3
SWITCH TO

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フィールドにドロップして、船のプロトタイプに紐づけてください。

次に、設定ファイルを値を使用するように、AsteroidsShipSystemUpdateShipMovement関数を調整しましょう。

C#

var config = f.FindAsset(filter.AsteroidsShip->ShipConfig);
FP shipAcceleration = config.ShipAceleration;
FP turnSpeed = config.ShipTurnSpeed;

射撃

まず、AsteroidsShip.qtnFireIntervalフィールドを追加して、射撃間隔の時間を記録できるようにします。

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;
}

AsteroidsProjectileSystemAsteroidsShipShoot関数の調整として、弾を初期化する以下のコードを末尾に追加します。

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);

ここでAsteroidsProjectileSystemISignalAsteroidsShipShootインターフェースを実装します。

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と名付け、MeshRendererMeshFilterを削除します。CircleColliderradius0.16に調整して、PhysicsBody2Dにチェックを入れます。

QuantumEntityPrototypeEntity ComponentsリストにAsteroidsProjectileコンポーネントを追加してください。TTLOwnerフィールドは、実行時に設定ファイルから代入するので、空のままにしておきましょう。設定のために、新しいAsteroidsProjectileConfigアセットを作成して、DefaultProjectileConfigと名付けます。Projectile Initial Speed20に、Projectile TTL2にして、コンポーネントのフィールドに設定ファイルを紐づけます。

AsteroidsProjectileの子要素に3D > Cubeゲームオブジェクトを作成して、スケールを(0.17, 0.17, 0.17)にします。これが弾のビジュアルになります。Colliderは削除しておいてください。

ResourcesフォルダーにAsteroidsProjectileをドラッグしてプレハブを作成し、シーン上のものは削除します。DefaultShipConfigProjectileに、プレハブのprototypeを紐づけます。

生存時間

現時点でまだ弾は自動的に削除されません。弾を削除するケースは2つあって「生存時間を過ぎる」か「船かアステロイドに当たる」です。

生存時間(TTL:Time To Live)を過ぎた弾を削除するには、システムでフレーム毎にチェックする必要があります。AsteroidsProjectileSystemSystemSignalsOnlyのかわりに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秒後に自動的に削除されますが、まだ船やアステロイドに物理的に衝突することはありません。チュートリアルの次のパートでは、弾に衝突判定を追加していきましょう。

Back to top