「人に危害を加えざるを得ない場合は、報復の恐れが必要ないくらい徹底的にやるべきである」- マキャベリ
「ダメージ」はゲームでは一般的なコンセプトです。そこで今回は、UE4 のゲームフレームワークに含まれているダメージ機能について簡単に解説したいと思います。
ダメージは、ベースの Actor クラスの機能であるため幅広く使用できます。結果が早くほしい場合は、一般的な機能に簡単にアクセスできます。必要に応じて独自のダメージモデルをカスタマイズできる拡張性もあります。また、ダメージに対する反応のしかたは想定されていません。だから、エンジンには「ヒットポイント」とか「戦闘不能」という概念がありません。これらの概念は、普通、ゲームそれぞれで固有のものであるからです。それらを一般化しても、かえって苦労をかけることになると考えたのです。
基本的なダメージのコンセプト
ダメージには、よく使われるコンセプトがいくつかありますので、簡単に紹介してみましょう。
DamageType
その名の通り DamageType は、ダメージ源とは無関係にダメージの「型」を記述できるオブジェクトです。これが役立つのは、ダメージのソースが多数あり、それらに共通の機能を持たせたい場合です。
簡単な例を使って説明しましょう。火のダメージがあるとします。キャラクターがそのダメージを受けると、「あっちー」と叫んで、近くの水のあるところまで駆け込ませるようします。その場合、キャラクターにやけどをさせることができるあらゆるアクタ (または、やけどを負う可能性があるあらゆる型のアクタ) にこのコードをコピーするよりも、火のためにダメージ型 (UDamageTypeFire) を定義して、そこに何らかの HandleDamagedCharacter() 関数を備え、適宜 TakeDamage() の呼び出しチェーンからその関数を呼び出すようにするのがよいでしょう。
Instigator
Instigator とは、ダメージを引き起こす者です。普通は、PlayerController や AIController が Instigator となります。火のダメージの場合は、プレイヤーか、火をつける AI がこれになるでしょう。
DamageCauser
causer とは、普通、ダメージを引き起こす物のことです。たとえば、ACampFire アクタの上を歩くと、それが causer となります。
C++ におけるダメージ
まず、ネーティブコードがダメージにどのように対応しているか見て行上できましょう。
この例の場合は、アクタにダメージを与えるのは簡単です。そのアクタ上で TakeDamage() を呼び出すだけです。
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, class AActor* DamageCauser);
また、ダメージに対して反応させるためには、ダメージを受けたアクタ上で TakeDamage() をオーバライドして、カスタムの操作を挿入するだけです。簡単ですよね!
ご覧のとおり、TakeDamage() のコールでは、引数として DamageEvent が渡されていますね。この FDamageEvent データ構造体には、ダメージを発生させるイベントの特定の状況に関するデータが含まれています。それによって、反応のためのコードが適切に対応できるようになります。UE4 には、次のような 3 種類の味のダメージ イベントがビルトインされています。
FPointDamageEvent
「その方向からうたれたため、顔のここが痛い」
ポイント ダメージ イベントは、被害者の特定の点に加えられたダメージ (弾丸やパンチなど) を具現化しています。このイベントには、攻撃が到来した方向と、サーフェスのインパクトを記述する FHitResult が含まれています。
FRadialDamageEvent
「あの大きな爆発のせいで、左半身全体が痛い」
ラジアル ダメージ イベントは、ある 1 点を源とする放射状のダメージを具現化したものです。代表的な例としては、爆発からダメージがあります。このダメージには、爆心地、および、空間におけるダメージの減衰を記述するためのデータ、影響を受けるコンポーネントのリストが含まれています。
FDamageEvent
「あ痛っ」
もっとも汎用的なダメージ モデルです。含まれているものは、オプションの DamageTypeClass だけです。
これらのビルトインされているイベントの型が、求めているものではない場合は、独自の構造体を FDamageEvent から派生させ、必要なデータを格納することができます。
ブループリントにおけるダメージ
ブループリントでダメージを扱う場合も同様です。ただし、ダメージの適用とダメージへの反応が、イベントの型によって用意されている点が異なります。ダメージを与える場合は、グローバルにアクセスできるノードが利用できます。ApplyDamage、ApplyPointDamage、ApplyRadialDamage などがそれです。ダメージ イベントに反応するためには、同様の took damage (ダメージを受けた) イベント群が用意されています。これらはアクタ クラスとレベル内のアクタ インスタンス両方のためのものです。
ご自分のプロジェクトのためにカスタムのダメージ イベントを定義した場合は、おそらく、ブループリントで使用するために、同様の関数およびデリゲートの集まりをエクスポーズする必要が出てくるでしょう。
さあ、ダメージを楽しんでください!そして、ダメージに関する質問や体験談などがありましたら、フォーラムをお訪ねください!