Phaser3 遠近の表現 depth(z-index)
Posted
Phaser3 遠近の表現 depth(z-index)
前回の記事
https://mijinc0.github.io/blog/post/20200820_pahser3_scene/
基本とDisplayList
Phaser3で描写されるオブジェクトは基本、順番を決める(重なり順を決める)depth
と呼ばれるプロパティを持つ(一般的にはz-index
などと呼ばれたりもする)。このプロパティを一切触らなかった場合、オブジェクトの描写はScene
クラス内でthis.add.XXX
した順番に描写される。つまり、後にシーンに追加されたオブジェクトが手前に描写される。
先にScene
クラス内でthis.add.XXX
したオブジェクトを後に追加したオブジェクトよりも手前に表示したければ、手前に表示したいオブジェクトのdepth
の値を後方に表示したいオブジェクトよりも大きな値に設定してあげることで達成できる。では実際のところ、depth
の値を設定した時に何が起きているのかという話。
実際に描写の順番を決めているのはDisplayList
と呼ばれる配列構造を持つオブジェクトで、そのシーンに描写されるオブジェクトは全てこの配列の中に入っている。depth
を設定するとDisplayList
がdepth
の値順にソートされるように作られているので、depth
値を設定すると描写順が変わるという仕組みになっている。
DisplayList
Depth.depth
ちなみに、各オブジェクトのdepthをセットする度にDisplayListソートするのではなく、オブジェクトのdepthをセットすると “このフレームでソートする” というフラグが立ち、つぎの描写時にソートされるようになっているので、一つのフレーム内で何度depthの値を設定しても一回の描写で一度しかソートされない。
DisplayListは各シーン毎に管理される。シーン管理の記事で複数シーンが稼働ている時はそれぞれのシーンの描写がされることを書いたが、DisplayListは各シーン毎に管理されているのであるシーン内のオブジェクトのdepthは他のシーンのオブジェクトのdepthに影響を与えない。例えば、先に描写されるシーンAのオブジェクトObjectA.depth = 100
と後に描写されるシーンBのオブジェクトObjectB.depth = 0
がある時、手前に表示されるのは後から描写されるシーン内にあるObjectB
ということになる。
遠近の表現
depth
の調整で2Dゲームの遠近を表現するのは"おきまり"の話。
平面的なタイルマップの上を歩くオブジェクトは画面の上にあるオブジェクトはそれよりも下にあるオブジェクトよりも “奥に” あると思うのが普通だ。 奥にあるということは、重なった時に後ろに隠れるということ。
キャラクターがマップ上を移動する場合、そのy座標の位置によってdepthが変化する(マップの上に行けば行くほどdepthが小さくなる)ようにすれば、自然な遠近が付いているように見える。より具体的に言えば、キャラクターの下部のy座標とdepthが連動するようにすれば良いということ。
キャラクターの下部のy座標とdepthを連動させるには、いくつも方法があるが自然なのはy(座標)のセッターにdepth値も変更する処理を加える方法だと思う。処理を加えるにはそのゲームオブジェクトのクラスを継承したカスタムクラスを作るか、Object.defineProperty
でその場にあるインスタンスを作り変えてしまうかになると思うが、正直好きな方で良いと思う。(ダックタイピングは用法用量を守って正しく使ってください)
import * as Phaser from 'phaser';
// PhysicsにArcadeを使っている場合
export class CustomSprite extends Phaser.Physics.Arcade.Sprite {
private _y: number;
constructor(
scene: Phaser.Scene,
x: number,
y: number,
texture: string,
frame?: string | number,
) {
super(scene, x, y, texture, frame);
this.y = y;
}
get y(): number {
return _y;
}
set y(v: number) {
this._y = v;
this.depth = v + this.height;
}
}
こんな感じとか…
import * as Phaser from 'phaser';
function syncBottomAndDepth(sprite: Phaser.Sprite): void {
let _y = sprite.y;
Object.defineProperty(sprite, 'y', {
get: () => {
return _y;
},
set: (newY: number) => {
_y = newY;
const bottom = newY + sprite.height;
sprite.depth = bottom;
},
});
}
こんな感じにすれば良い。
タイルマップについて
タイルマップを扱っていると、このタイルはキャラクラーの上に表示したい、このタイルはキャラクターのしたに表示したい などの要求がでることもあると思う。結論から言うと、タイルマップの各タイルのdepth
を設定することは出来ない。それらが所属するレイヤーオブジェクトにdepth
を設定する。つまり、
- キャラクターの上に描写したいタイルマップレイヤー
- キャラクターの下に描写したいタイルマップレイヤー
と分けて描写を操作する必要がある。もし、それ以上の要求が出る場合は、スプライトなどのオブジェクトとしてシーンに追加し、タイルマップとは別に管理する必要がある。