p5.playには、同種のスプライトをまとめて管理するのに便利なGroupオブジェクトがあります。
目次
リファレンスメモ
Group
p5.playのグループ(Group)は同様の振る舞いを持つスプライトの集まりで、グループにはたとえば、背景の全スプライトや、プレイヤーを攻撃する全スプライトなどを含める。グループは”拡張された”配列で、たとえばgroup.lengthなど、配列の全プロパティを継承している。
グループに含まれるのは参照なので、スプライトは複数のグループに所属でき、グループを削除しても、含まれるスプライト自体には影響しない。Sprite.remove()メソッドはまた、そのスプライトが属する全グループからそのスプライトを削除する。
次のサンプルでは、グループを2つ作成し、雲と騎士スプライトをそれぞれに追加しています。雲グループに属する雲のスプライトは右に移動し、キャンバスの外に出たら左から現れます。
また騎士スプライトをクリックするとそのスプライトは削除され、騎士グループからも削除されます。重なっている場合には、重なりの上にある(手前の)スプライトが削除されます。
let knightIdleAnim;
let cloudImg;
let backImg;
let treeImg;
let cloudGroup;
const cloundNum = 8;
let knightGroup;
const knightNum = 5;
function preload() {
knightIdleAnim = loadAnimation('assets/knight/000.png', 'assets/knight/007.png');
cloudImg = loadImage('assets/cloud.png');
backImg = loadImage('assets/background.png');
treeImg = loadImage('assets/tree.png');
}
function setup() {
createCanvas(500, 300);
frameRate(24);
// 雲グループを作成
cloudGroup = new Group();
// 雲のスプライトを作成して雲グループに追加
for (let i = 0; i < cloundNum; i++) {
const sp = createSprite();
sp.addImage(cloudImg);
sp.position.x = random(0, width);
sp.position.y = random(30, 130);
// カスタムプロパティ。雲ごとに異なるスピードに使用する
sp.mySpeed = random();
cloudGroup.add(sp);
}
// 騎士グループを作成
knightGroup = new Group();
// 騎士のスプライトを作成して騎士グループに追加
for (let i = 0; i < knightNum; i++) {
const sp = createSprite();
sp.addAnimation('knight_idle', knightIdleAnim);
sp.position.x = random(0, width);
sp.position.y = 250;
// 騎士スプライトのアニメーションを2fps(60/30)に下げる
sp.animation.frameDelay = 30;
// カスタムプロパティ。削除したときの確認テストに使用
sp.myName = 'knight' + i;
// スタートフレームをスプライトごとに変える
const f = round(random(0, sp.animation.getLastFrame()));
sp.animation.changeFrame(f);
sp.animation.play();
knightGroup.add(sp);
}
}
function draw() {
// 背景を描画
image(backImg, 0, 0);
// 雲の移動論理を更新
update();
// 雲グループを描画
cloudGroup.draw();
// 木は雲より前面に来させたいので、ここで描画
image(treeImg, 340, 110);
// 最後に騎士を描画 => 最前面
knightGroup.draw();
}
function update() {
for (let i = 0; i < cloundNum; i++) {
// 雲グループから雲のスプライトを特定
const cloudSp = cloudGroup[i];
// カスタムプロパティ値分だけ右に移動
cloudSp.velocity.x = cloudSp.mySpeed;
// キャンバスの外に出たら、右から登場
if (cloudSp.position.x > width + cloudSp.width) {
cloudSp.position.x = -cloudSp.width;
}
}
}
// ページのどこかをマウスダウンしたとき呼び出される
// 騎士スプライトを削除する(重なり対応)
function mousePressed() {
// 重なったスプライトを保持する配列
const overlappedList = [];
for (let i = 0; i < knightGroup.length; i++) {
const sp = knightGroup[i];
w = sp.width;
h = sp.height;
const spUpperLeftX = sp.position.x - w / 2;
const spUpperLeftY = sp.position.y - h / 2;
// マウスカーソルがスプライトの矩形内にあるなら
if (mouseX > spUpperLeftX && mouseX < spUpperLeftX + w && mouseY > spUpperLeftY && mouseY < spUpperLeftY + h) {
// そのスプライトを配列に追加
overlappedList.push(sp);
}
}
// 重なったスプライトがあるなら
if (overlappedList.length != 0) {
// 配列内のスプライトを、深度の大きいもの順(降順)に並び替え
spList = overlappedList.sort((a, b) => {
return (a.depth < b.depth ? 1 : -1);
});
// 深度の大きいスプライト(手前のもの)は0番めにあるので、それを削除
spList[0].remove();
// 確認
print(spList[0].myName + 'を削除した。');
print('残りの騎士の数 : ' + knightGroup.length);
}
}
update()関数では、雲グループを使って雲のスプライトを右に移動していますが、これはGroupオブジェクトでなくても通常の配列で対応できます。このサンプルで注目すべきは、draw()関数内で行っている描画の順番です。
下図に示すように、描画の最下層(最も奥)には背景(青空と草)を、その1つ上層(1つ手前)には雲のスプライトを、その1つ上層には木を(雲は木の向こうを流れるので)、最上層(最も手前)には騎士スプライトを描画したいので、drawSprites()は使えません(雲が木の手前に来てしまう)。
そこで使用しているのが、そのグループに属するスプライトを描画するGroup.draw()メソッドです。次の順番で描画することで、希望の重なりが実現できます。
- 背景を描画
- 雲グループを描画
- 木を描画
- 騎士グループを描画
重なりの上にあるスプライトの削除
スプライトはキャンバスに一瞬一瞬描画される”絵の一部”に過ぎないので、スプライト同士が重なっている場合の処理には工夫が必要になります。
上の例では、p5.jsのmousePressed()関数を使って、マウスプレス時にマウスカーソルがスプライトの矩形内にあるかどうかを調べ、ある場合にはスプライトを配列に追加しています。そして配列のsort()メソッドを使って、スプライトの深度(Sprite.depthプロパティ)の大きいもの順に並べ替え、一番大きい深度を持つスプライトを削除するという方法を取っています。
// 配列内のスプライトを、深度の大きいもの順(降順)に並び替え
spList = overlappedList.sort((a, b) => {
return (a.depth < b.depth ? 1 : -1);
});
リファレンスメモ
Group.add()
add ( s )
スプライトをグループに追加する。
パラメータ
s Sprite – 追加するスプライト
Animation.frameDelay
frameDelay Number
描画サイクル数におけるフレーム間の遅延。4に設定すると、アニメーションのフレームレートが、スケッチのフレームレートを4で割ったものになる(60fps / 4 = 15fps)。デフォルトは4。
Group.draw()
draw ()
グループの全スプライトを描画する。
Sprite.depth
depth Number
グループ内のレンダリング順を決める。低い深度のスプライトは、それより高い深度のスプライトの下(奥、向こう)に表示される。
注意:グループAを描画し、その後drawSprites()で別のグループBを描画すると、通常のp5キャンバスの描画と同様、グループAのメンバーはグループBの下(向こう)に表示される。
デフォルト:深度は、createSprite()を呼び出すときには、既存のスプライトの最大深度よりも1大きくなる。new Sprite()を直接呼び出すときには、0に初期化される(非推奨)。