【Swift】SpriteKitの使い方。カテゴリマスクと衝突マスクで衝突するノードを分ける(Swift 2.2、XCode 7.3)

2020年6月16日

ノードの衝突の設定

本記事では物理ボディが設定されたノードの衝突について説明する。

前回までの記事で、物理エンジンを使ってノードに重力をかける方法を説明してきた。⇒「物理エンジンとは

落下するノードの進行方向に、物理ボディが設定された別のノードがある場合、必ず衝突が発生した。

全部のノードを衝突対象とするのでは無く、例えばノード1はノード2とだけ衝突させ、ノード2はノード1とノード3の2つと衝突させるなど、衝突させたいノードを分けたいことがある。そこで利用されるのがカテゴリマスクと衝突マスクである。

 

カテゴリマスクを設定する

では、ノードにカテゴリマスクを設定して、衝突するノードを分けてみよう。

以降の手順を行う前のXcodeプロジェクトをGitHubに置いたので、試してみる方はご利用下さい。
⇒「テスト用プロジェクト

画面に3匹の鳥ノードを配置し、タップしたら重力を受けて落下するものを実装しておいた。鳥ノードは別の鳥ノードに衝突しながら落下する。

 

以下は変更前のTestScene.swift。

 

注目するのは、上記コード中のカテゴリを設定する箇所。

衝突有無を分けるにはcategoryBitMask(以下、カテゴリマスク)とcollisionBitMask(以下、衝突マスク)を利用する。衝突させたいノードのカテゴリマスクの値を衝突マスクに設定すれば、ノードが衝突するようになる。

以下コードはカテゴリマスク、衝突マスクがすべて1に設定されているので、すべてのノード同士がぶつかる。

 

ややこしくなってきたので1つ問題を解いてみよう。

以下のコードのように設定して茶鳥を落下させた場合、どうなるか。

 

正解はどの鳥にも衝突しない。なぜなら、茶鳥の衝突マスクは1に設定されているので、カテゴリマスクが1のノードと衝突することになるが、赤鳥のカテゴリマスクは2、青鳥は4のため衝突しない。

 

では以下に設定した場合はどうなるか。

 

茶鳥の衝突マスクは1なのに対し、赤鳥のカテゴリマスクは2、青鳥は3なので衝突しないと思ったかもしれないが、茶鳥は青鳥と衝突する。

 

衝突した理由は、衝突の有無はビットマスクによって決定されるためである。

ビットマスクとは、ビットごとにAND演算を行ってデータを抽出することを言う。今回の場合で言うと、カテゴリマスクと衝突マスクの2進数表現をAND演算して0にならなければ衝突が発生することになる。

実際に計算してみよう。

茶鳥の衝突マスクの「1」を2進数にすると「0001」
赤鳥のカテゴリマスクの「2」を2進数にすると「0010」

この2進数に対してAND演算を行う。具体的には「0001」と「0010」を上下に並べて上と下の数字が両方とも1だったら1を書き、それ以外は0を書く。

0001
0010
======
0000

演算結果は「0000」。0の場合は衝突しない仕様なので茶鳥と赤鳥は衝突しない。

 

次に、茶鳥と青鳥を計算する。

茶鳥の衝突マスクの「1」を2進数にすると「0001」
赤鳥のカテゴリマスクの「3」を2進数にすると「0011」

0001
0011
======
0001

演算結果は「0001」。1以上の場合は衝突する仕様なので茶鳥と青鳥は衝突する。

理解を確認するために、もう1つ例題を解いておこう。以下に設定した場合、茶鳥はどの鳥に衝突するか。

 

まずは茶鳥と赤鳥から計算する。

茶鳥の衝突マスクの「3」を2進数にすると「0011」
赤鳥のカテゴリマスクの「2」を2進数にすると「0010」

0011
0010
======
0010

よって、茶鳥と赤鳥は衝突する。次に茶鳥と青鳥を計算する。

茶鳥の衝突マスクの「3」を2進数にすると「0011」
青鳥のカテゴリマスクの「1」を2進数にすると「0001」

0011
0001
======
0001

よって、茶鳥と青鳥も衝突する。

 

イメージとしては、カテゴリマスクと衝突マスクの2進数表現を0がOFF、1がONを表すスイッチとして下図のように横にズラーっと並べる。上下のスイッチが両方ともONのものが1つでもあればノードが衝突すると考えれば良い。

ちなみにカテゴリマスクや衝突マスクは32ビットなので32個のスイッチを並べることができる。組み合わせパターンは4294967296通りだ。わぉ

スイッチが並んでいるイメージ

 

「カテゴリマスクや衝突マスクを確認したときに、どれとどれが衝突するのかパッとみ分からないのが辛いのでは。」と思ったかも知れない。

確かに、衝突マスクが3に設定されているのを見てカテゴリマスクが1のノードと衝突するとはすぐには判断できない。そんなときは以下のコードのように2進数で値を設定するのをお勧めする。

では、以下の設定で茶鳥はどの鳥と衝突するか。

 

茶鳥の衝突マスクは「0b0010」なので、カテゴリマスクの2桁目が1の鳥を探せばいい。赤鳥は「0b0010」、青鳥は「0b0100」なので、茶鳥は赤鳥と衝突する。

 

では、以下設定のカテゴリマスクは変更せずに、衝突マスクだけ変更して茶鳥を赤鳥と青鳥の両方と衝突させるにはどのようにすればいいか。

 

赤鳥のカテゴリマスクは「0010」(2桁目が1)、青鳥のカテゴリマスクは「0100」(3桁目が1)なので、茶鳥の衝突マスクの2桁目と3桁目を1に設定すれば赤鳥と青鳥の両方と衝突することになる。

 

しかし、上記書き方だと衝突させるカテゴリが増えてきたときにややこしくなるので、以下のようにビット和を使ったほうが分かりやすい。

 

さらに突き詰めると、以下のコードのように衝突させたいノードのカテゴリマスクを参照して設定すると一目でどのノードと衝突するのかが分かり、後から修正がしやすくなる。

 

以下は実際のプレイ動画