Bomber
概要
Quantum Bomberサンプルは完全なソースコード付きで提供され、Quantumでボンバーマンのようなゲームプレイを構築する方法を示しています。
ダウンロード
バージョン | リリース日 | Download | ||
---|---|---|---|---|
2.1.6 | 2023年7月18日 | Quantum Bomber 2.1.6 Build 268 |
はじめに
オンラインマルチプレイヤーモードでサンプルを実行するには、まずPhotonEngine DashboardでQuantum AppIdを作成し、PhotonServerSettings
アセットのAppId
フィールドに貼り付けます。
次に、シーンメニューのMenu
シーンを読み込み、Play
を押してください。
技術情報
- Unity: 2021.3.13f1 or higher
- プラットフォーム: PC (Windows)
ハイライト
技術
- カスタム
Movement
コンポーネントとシステムは、トップダウングリッドベースのゲームに 適しています。 - 半手順的なマップとパワーアップの生成
- キャラクターのカスタマイズ
- 時間ベースの爆発拡散
- ゼロイベント・シミュレーション・アプローチ
- 比較的純粋な ECS シミュレーションアーキテクチャ
ゲームプレイ
- バトルロイヤル ボンバー
- 爆弾を置く
- 爆弾の量、爆発のリーチ、移動速度の修正の形でパワーアップ。
コントロール
- WASDで移動
- 爆弾を置くスペース
Cell
Cell
はDSLで定義された構造体で、CellType
フラグという形で1つの値を保持する。これらの値にアクセスや 変更をより便利に行うために、Cell.User.cs
スクリプトでは、この構造体をプロパティとメソッドで拡張しています。
各セルは、現在その位置にあるオブジェクトのタイプが何であるかは知っているが、 エンティティ自体への参照は持っていない。システムがエンティティを反復処理するとき、そのセルに関心のあるものがあるかどうかを確認します。例えば、セルが IsBurning
であれば、そのセルが担当するコンポーネントに適切な 応答をトリガーします。
CellType
はフラグであるので、すべてのプロパティはビット演算として構築することができ、非常に効率的です。
Grid
Grid
は通常のCSharpクラスで、GridSettings
構造体、Cell
構造体の配列、ポインタを含んでいます。
Frame.User
通常のクラスとして Grid
を定義し、そのインスタンスを Frame.User
に持つことで、通常の Frame.XYZ
API から利用できるようにすると同時に、関連するすべてのメソッドを Frame.Grid.XYZ
にカプセル化することができます。
Gridが通常のクラスであることの欠点は、そのデータが通常のDSLが生成するメモリの外に存在することです。そのため、データの初期化、シリアライズ、コピー、クリア、ダンプを手動で行う必要があります。これは、Frame.User
内の以下のメソッドにフックすることで行います:
InitUser()
: フレームが最初に作成されるときに1回呼び出される。FreeUser()
: Frameが破棄されるときに呼び出される。CopyUser()
: 前のフレームの状態が次のフレームにコピーされるときに呼び出される。SerializeUser()
: フレームがシリアライズされるときに呼び出される(例えば、遅れて参加した人のためのスナップショットとして)。DumpFrameUser()
: 非同期が起こったときに呼び出される。
グリッドの設定
グリッドの生成と設定に関する情報は GridSettings
構造体にあります。この情報を使って、実行時に1D-arrayを設定し、固定ブロックと破壊可能ブロックの位置を入力したり、SpawnPoints
の位置を推測したりすることができます。
セルの配列、別名グリッド
グリッドセルは Cell
の1次元配列に含まれます。原理はTilemap Pathfinder Tech Sampleのタイルマップアセットと同じです。1次元配列はDSLでサポートされています。しかし、DSLはコンパイル時に配列のサイズを把握している必要があり、グリッドはプレイヤーによって要求されたサイズに基づいて実行時に生成されるため、別の方法で処理する必要があります。
配列は実行時に Frame.User
の InitUser()
メソッドにフックして作成されます。
Frame.User
内の CopyUser()
メソッドと SerializeUser()
メソッドにフックすることで、Grid
内の 1D-array は、predict-rollback と late-join serialization に対応します。
パフォーマンス
Cell
はバイトフラグしか持ちませんが、フレームのコピーによって行われる読み込みと書き込みの量がパフォーマンスの大きなボトルネックになる可能性があります。これを防ぐために、Grid.cs
ではグリッドとそのセルに対していくつかの get-set メソッドが定義されています。これらのメソッドはポインタ計算を使ってセルへのポインタを返すので、構造体への直接の読み書き両方が可能になります。
ブロードフェイズのセットとクリア
グリッドにメモリーを書き込むシステムは2つあります:
SetBroadphaseSystem
: すべてのゲームプレイシステムの__前に__実行され、それが現在含んでいるエンティティに基づいてセルタイプを設定する。ClearBroadphaseSystem
: すべてのゲームプレイシステムの__後に__実行され、破壊されようとしているエンティティのセル内の情報をクリアする。
入力
入力構造は5つのボタンで構成されている。
C#
input {
Button MoveUp;
Button MoveDown;
Button MoveLeft;
Button MoveRight;
Button PlaceBomb;
}
これは入力構造体が取り得る最も凝縮された形です。シミュレーションでは Button
型の入力は1バイトですが、電線上では1ビットに圧縮されます。
InputSystem
はプレイヤーの入力をゲームプレイのシステムで消費する準備をするために、移動ボタンを使って Direction
を構築し、PlaceBomb
ボタンの値に基づいて AbilityPlaceBomb
に WantsToPlaceBomb
boolean を設定します。
Bomber
Bomber
コンポーネントは、エンティティを識別してフィルターするための flag-component です。
このように、関連する BomberSystem
は比較的単純です。その唯一の仕事は、与えられた Update()
の間に、爆撃機タイプのエンティティが燃えているセルに立っているかどうかを確認することだからです。
C#
public override void Update(Frame f, ref BomberFilter filter)
{
var gridPosition = filter.Transform->Position.RoundToInt(Axis.Both);
var isInvincible = false;
#if DEBUG
// Used for debugging purposes
isInvincible = f.RuntimeConfig.IsInvincible;
#endif
if (isInvincible == false && f.GetCellRef(gridPosition).IsBurning)
{
// Death animation is triggered from OnEntityDestroyed
f.Destroy(filter.Entity);
}
}
Bomb
爆弾はプレイヤーのアクションによって生成され、Timer
が切れると爆発する一時的なエンティティである。
Bomb
コンポーネントには2つのシステムがあります:
AbilityPlaceBombSystem
: プレイヤーがPlaceBomb
ボタンを押したかどうか、そして爆弾の設置条件が満たされているかどうかを確認します。BombSystem
: 爆弾のTimer
をスタートさせ、連鎖反応を処理し、爆弾が破壊されたときに爆発を誘発します(タイマーが切れたか、他の爆弾の爆発が触れたかのどちらかです)。
Explosion
爆発は一定時間続くものです。したがって、単なるレイキャストではなく、エンティティとして存在する必要があります。
ExplosionSystem
は爆発の寿命とセルからセルへの爆発の広がりを扱います。
爆発エンティティは2つのコンポーネントで構成さ れます:
Explosion
: フラグコンポーネントとして、また爆発コンフィグに関する情報を保持するために使用されるコンポーネントです。Timer
: タイマーは爆発が次の隣接セルに広がるまでの残り時間を計算するために使用されます。
PowerUp
パワーアップの機能は、主に以下の2つのコンポーネントによって提供されます。
PowerUp
: パワーアップをタイプと修飾量によって定義します。PowerUpManager
:スポーン可能なパワーアップのリストとスポーン確率を保持します。
パワーアップは、破壊可能なブロックがグリッドから取り除かれたときにスポーンする確率を持っています。ブロックが破壊されると、PowerUpManager
に新しくクリアされた位置が通知されます。PowerUpManagerSystem
は新しく利用可能になった場所を繰り返し、そのセルが空で現在燃えていなければランダムなパワーアップをスポーンしようとします。
Movement
移動はマスの中では自由で、グリッドを読み込んでキャラクターがどこに行けるかを決定します。
Direction
Direction
は現在押されている移動ボタンを表すバイトフラグです。
Movement コンポーネント
Movement
コンポーネントはフレーム間の関連する移動の値を記録します。
FP CurrentSpeed
: キャラクターの現在の移動速度です。FP MaxSpeed
: キャラクターが移動できる最大速度です。Boolean IsMoving
: 最後の移動入力が移動につながったかどうかを記録します。Boolean LastMoveWasHorizontal
: 最後の移動の向きを保持します。Direction LastNewInput
: プレイヤーが入力した最新の入力を垂直方向と水平方向の両方で保持します。Direction CurrentInput
: 現在押されている移動方向です。FPVector2 MoveDirection
: 最後の移動方向です。FP StartRotation
: 方向を変更する前の最後のルック回転。FP TargetRotation
: 方向転換後にキャラクタが持つべきルック回転です。int RotationStartTick
: 新しい方向への回転を開始した刻みです。FP RotationDuration
: 回転にかかる最大時間です。FP RotationTimeMultiplier
: 与えられた時間内に回転を完了するために必要な回転速度の倍率です。
Movement System
Movement Systemは3つのコア機能を果たしています。
- 現在押されている移動キーから可能な移動を計算し(
GetMovementResult()
)、その値を含むMoveResult
構造体を返す。2. 現在の移動速度を更新する (UpdateCurrentSpeed()
) - キャラクターを移動する (
UpdateMovement()
) - キャラクタを回転させる (
UpdateRotation()
)
MovementSystem
は2つの機能を実装しており、グリッドを斜めにスムーズに移動することができるようにします:
CanMoveInDirection()
はコーナーのスライドを考慮します。CanMoveInDirection()
はコーナーのスライドを考慮します。これにより、斜めの方向が押されたときに、キャラクターがスムーズにコーナーを移動できるようになります。GetMoveVector()
には、どちらの方向(垂直または水平)を最初に確認するかを交互に切り替える方向トグルがあります。これにより、キャラクターはグリッドの中を連続的に斜めに移動することができます。
MoveResult
MoveResult
はユーティリティ構造体です。これは、現在の Movement.CurrentInput
を、キャラクターが移動するグリッドセルに対して処理した結果を保持します。これらの値はフレーム状態の一部として使用されないため、DSLではなく通常のCSharp構造体として定義されています。
FPVector2 Direction
: 移動したい方向。FP MaxDistance
: キャラクターが希望する方向に移動できる最大距離。FPVector2 LookDirection
: この移動の結果としてキャラクターが見る方向。FP RotationTimeMultiplier
: 現在の視線方向と希望する視線方向の間の角度に基づいてキャラクターが視線方向を切り替える速度で、事前に定義された時間内に回転を完了します。
サードパーティのアセット
Bomber サンプルには、それぞれの作成者から提供されたいくつかのアセットが含まれています。 独自のプロジェクト用の完全なパッケージは、それぞれのサイトで入手できます。
- Epic Toon FX 作者 Archanor VFX
- HSV color picker for Unity UI 作者 Judah4
__ 重要__:商用プロジェクトで使用するには、各クリエイターからライセンスを購入する必要があります。
Back to top