p5.play.jsの当たり判定はスプライトのコライダーを使って行われますが、このとき任意の点(マウスカーソルの位置)やスプライトの内の不透明なピクセルの点に対して衝突チェックを行うこともできます。
具体的にいうと、下図の左は1つのPNG画像ですが、これをスプライトのイメージとして追加し、中の赤丸部分をスプライトのコライダーに設定することで、たとえばマウスカーソルと重なったタイミングが検知できます。
また下図の右は背景が透明のPNG画像です。これをスプライトのイメージにすると、透明でない山の部分とほかのスプライトの点との重なりがチェックできます。
右の画像:「Segel Artwork」
目次
点に対する重なりを調べる
次のサンプルでは、「5_6:p5.play スプライトのドラッグ」で見た方法で、騎士スプライトのドラッグを可能にしています。騎士スプライトを画面センターの黄色い円の中にドラッグすると、騎士スプライトは剣の訓練を開始します。再度ドラッグし円の外に放すと訓練をやめます。また画面右上には、黄色い円にドラッグしたスプライトの情報を表示しています。
let idleAnim;
let attackAnim;
let knightSp;
const knightNum = 3;
let dojoImg;
let dojoSp;
let draggedSprite = null;
let infoText = ''
function preload() {
idleAnim = loadAnimation('assets/knight/idle/000.png', 'assets/knight/idle/007.png');
attackAnim = loadAnimation('assets/knight/attack/000.png', 'assets/knight/attack/007.png');
dojoImg = loadImage('assets/dojo.png')
}
function setup() {
createCanvas(500, 400);
// 道場スプライトを作成
dojoSp = createSprite();
dojoSp.addImage(dojoImg);
dojoSp.position.x = width / 2;
dojoSp.position.y = height / 2;
// 道場スプライトのコライダーは中にある黄色の円
dojoSp.setCollider("circle", 0, 0, 40);
dojoSp.debug = true;
// 左上に表示する情報用のテキスト設定
textSize(30);
fill(255, 0, 0);
// knightNum分だけ騎士スプライトを作成
for (let i = 0; i < knightNum; i++) {
const sp = createSprite();
// アニメーションは最初に名前付きで仕込んでおく
sp.addAnimation('idle', idleAnim);
sp.addAnimation('attack', attackAnim);
sp.scale = 0.8;
sp.position.x = random(20, width - 20);
sp.position.y = random(20, height - 20);
// 確認用のカスタムプロパティ
sp.myName = '騎士' + i;
sp.debug = true;
// スプライトがマウスダウンされたら
sp.onMousePressed = function() {
// draggedSpriteをドラッグしているように見せる
if (draggedSprite === null) {
draggedSprite = this;
offsetX = draggedSprite.position.x - mouseX;
offsetY = draggedSprite.position.y - mouseY;
}
}
// スプライトがマウスリリースされたら
sp.onMouseReleased = function() {
// 道場スプライトのコライダー内に、スプライトのxとy値があれば
if (dojoSp.overlapPoint(this.position.x, this.position.y)) {
// 当該騎士スプライトが円内にあることを表示
infoText = this.myName + '、円の中';
// attackアニメーションに変更
this.changeAnimation('attack');
// そうでなければ
}
else {
// 当該騎士スプライトが円外にあることを表示
infoText = this.myName + '、円の外';
// idleアニメーションに変更
this.changeAnimation('idle');
}
// draggedSpriteをnullに設定して、ドラッグ終了
if (draggedSprite === this) {
draggedSprite = null;
}
}
// 作成時に道場スプライトのコライダー内に当該スプライトが配置されたら場合の処理
if (dojoSp.overlapPoint(sp.position.x, sp.position.y)) {
sp.changeAnimation('attack');
}
else {
sp.changeAnimation('idle');
}
}
}
function draw() {
background(200);
showInfo();
update();
drawSprites();
}
function update() {
if (draggedSprite != null) {
draggedSprite.position.x = mouseX + offsetX;
draggedSprite.position.y = mouseY + offsetY;
}
}
function showInfo() {
text(infoText, 20, 40);
}
騎士スプライトのドラッグ先のdojo.png画像には、ちょうどセンターに黄色の円を作成しています。この円をスプライトのコライダーに設定します。
// 道場スプライトのコライダーは中にある黄色の円
dojoSp.setCollider("circle", 0, 0, 40);
ドラッグする騎士スプライトと、ドラッグ先のスプライト(黄色の円の部分をコライダーに設定したdojoSp)の重なりを調べているのは、次のsp(3つある騎士スプライト)のonMouseReleasedイベントハンドラです。
sp.onMouseReleased = function() {
// 道場スプライトのコライダー内に、スプライトのxとy値があれば
if (dojoSp.overlapPoint(this.position.x, this.position.y)) {
// 当該騎士スプライトが円内にあることを表示
infoText = this.myName + '、円の中';
// attackアニメーションに変更
this.changeAnimation('attack');
// そうでなければ
}
else {
// 当該騎士スプライトが円外にあることを表示
infoText = this.myName + '、円の外';
// idleアニメーションに変更
this.changeAnimation('idle');
}
...
}
リファレンスメモ
Sprite.overlapPoint()
overlapPoint ( pointX pointY ) Boolean
与えられた点が、そのスプライトのコライダー内にあるかどうかをチェックする。
パラメータ
pointX Number – チェックする点のX座標
x coordinate of the point to check
pointY Number – チェックする点のy座標
戻り値
Boolean: – 内部にある場合trueになる
不透明の点に対する重なりを調べる
次のサンプルでは、兵士スプライトをマウスの動きに追随させ、草むらのスプライトの不透明な部分に兵士スプライトの点が重なったときに、兵士スプライトと草むらスプライトの深度を交換しています。兵士スプライトは元は草むらスプライトの手前にありますが(兵士スプライトの深度は草むらスプライトの深度より1大きい)、深度を交換することで、兵士スプライトが草むらに隠れるようになります。
let bush1Img;
let bush2Img;
let bush1Sp;
let bush2Sp;
let soldierAnim;
let soldierSp;
function preload() {
bush1Img = loadImage('assets/Bush01.png');
bush2Img = loadImage('assets/Bush02.png');
soldierAnim = loadAnimation('assets/soldier/000.png', 'assets/soldier/009.png');
}
function setup() {
createCanvas(600, 400);
// 右下の小さな草むらスプライト => 深度1 一番奥
bush1Sp = createSprite();
bush1Sp.addImage(bush1Img);
// 確認用のカスタムプロパティ
bush1Sp.myName = 'smallBush';
setPos(bush1Sp, 216 + bush1Sp.width / 2, 277 + bush1Sp.height / 2);
// 左下の大きな草むらスプライト => 深度2 小さな草むらの1つ手前
bush2Sp = createSprite();
bush2Sp.addImage(bush2Img);
setPos(bush2Sp, -20 + bush2Sp.width / 2, 200 + bush2Sp.height / 2);
bush2Sp.myName = 'bigBush';
// 兵士スプライト => 深度3 一番手前
soldierSp = createSprite();
soldierSp.addAnimation('run', soldierAnim);
// 参考用にコライダーの外枠を表示するために設定。
// 大きな草むらとの重なりチェックには関係ない
soldierSp.setCollider("rectangle", 0, 0, soldierSp.width, soldierSp.height);
soldierSp.myName = 'soldier';
soldierSp.debug = true;
// 画面左上の情報表示用テキストの設定
textSize(25);
fill(255, 0, 0);
}
function draw() {
background(200);
update();
drawSprites();
}
function update() {
// 兵士スプライトをマウスに追随
soldierSp.position.x = mouseX;
soldierSp.position.y = mouseY;
// 大きな草むらスプライトのコライダーが兵士スプライトの(x,y)と重なったら
if (bush2Sp.overlapPixel(soldierSp.position.x, soldierSp.position.y)) {
print('兵士は草むらの中');
// 兵士スプライトを大きな草むらの手前にする
if (bush2Sp.depth < soldierSp.depth) {
swapDepths(bush2Sp, soldierSp);
print('深度交換');
}
}
else {
print('兵士は草むらの外');
// 元々の深度に戻す
if (bush2Sp.depth > soldierSp.depth) {
swapDepths(bush2Sp, soldierSp);
print('深度交換');
}
}
// 深度交換の確認 =>深度が変わると、allSprites内の順番が変わる
if (allSprites.length === 3) {
let txt = '深度' + allSprites[0].depth + ': ' + allSprites[0].myName + '\n';
txt += '深度' + allSprites[1].depth + ': ' + allSprites[1].myName + '\n';
txt += '深度' + allSprites[2].depth + ': ' + allSprites[2].myName;
text(txt, 30, 30, 220, 100);
}
}
function setPos(sp, xpos, ypos) {
sp.position.x = xpos;
sp.position.y = ypos;
sp.debug = true;
sp.mouseActive = true;
}
// 渡された2つのスプライトの深度を交換
function swapDepths(sp1, sp2) {
const depth1 = sp1.depth;
const depth2 = sp2.depth;
sp1.depth = depth2;
sp2.depth = depth1;
}
兵士スプライトを草むらに近づけます。草むらを囲むコライダーには反応せず、兵士スプライトの中心(+印)が緑の草むらに重なると、兵士スプライトが草むらの隠れます。
リファレンスメモ
Sprite.overlapPixel()
overlapPixel ( pointX pointY ) Boolean
与えられた点が、スプライトの現在のイメージの透明ピクセルに一致するかどうかを調べる。これは、スプライトの可視部分のみに対する点の衝突チェックに使用できる。
パラメータ
pointX Number – チェックする点のX座標
pointY Number – チェックする点のy座標
戻り値
Boolean:
不透明の場合true。
画面左上には、allSpritesプロパティに含まれるスプライトの深度の情報を、どのスプライトかを特定するカスタムプロパティの値といっしょに表示しています。これを見ると、深度が変わると、allSprites内の順番も変わることが分かります。
斜めに見えるスプライトに沿った落下
次のサンプルでは、キャンバスのクリックで、敵スプライトが落下を開始し、落下中斜めの板にぶつかると、それに沿って落ちていきます。
let enemyWalkAnim;
let enemySp;
let leftBarImg;
let leftBar1Sp;
let rightBarImg;
let rightBar1Sp;
let leftBar2Sp;
let rightBar2Sp;
let startButton;
// 起動時に再生しない
let isRunning = false;
function preload() {
enemyWalkAnim = loadAnimation('assets/enemy/walk/000.png', 'assets/enemy/walk/007.png');
leftBarImg = loadImage('assets/leftbar.png');
rightBarImg = loadImage('assets/rightbar.png');
}
function setup() {
const canvas = createCanvas(400, 500);
// メインスプライトを作成
enemySp = createSprite();
enemySp.addAnimation('walk', enemyWalkAnim);
enemySp.position.x = width / 2;
enemySp.position.y = 50;
enemySp.scale = 0.8;
enemySp.debug = true;
// 4つの板スプライトを作成して配置
leftBar1Sp = createSprite();
leftBar1Sp.addImage(leftBarImg);
setPos(leftBar1Sp, 160, 150);
rightBar1Sp = createSprite();
rightBar1Sp.addImage(rightBarImg);
setPos(rightBar1Sp, 330, 250);
leftBar2Sp = createSprite();
leftBar2Sp.addImage(leftBarImg);
setPos(leftBar2Sp, 130, 350);
rightBar2Sp = createSprite();
rightBar2Sp.addImage(rightBarImg);
setPos(rightBar2Sp, 330, 450);
// キャンバスのクリックでスタート
canvas.mouseClicked(() => {
isRunning = true;
// 少しだけ右方向にも移動
enemySp.setSpeed(1, 85);
});
}
function draw() {
background(200);
if (isRunning) {
update();
}
drawSprites();
}
function update() {
// 落下をつづける
enemySp.velocity.y = 1;
print('落下');
// overlapPixel()が調べる対象ポイント
const targetX = enemySp.position.x;
const targetY = enemySp.position.y + 30;
// 左上の板の不透明部分と重なったら、
if (leftBar1Sp.overlapPixel(targetX, targetY)) {
// 右方向に移動
enemySp.setSpeed(1, 0);
print('右へ');
}
// 右上の板の不透明部分と重なったら
if (rightBar1Sp.overlapPixel(targetX, targetY)) {
// 左方向に移動
enemySp.setSpeed(1, 180);
print('左へ');
}
// 左下の板の不透明部分と重なったら
if (leftBar2Sp.overlapPixel(targetX, targetY)) {
// 右方向に移動
enemySp.setSpeed(1, 0);
print('右へ');
}
// 右下の板の不透明部分と重なったら
if (rightBar2Sp.overlapPixel(targetX, targetY)) {
// 左方向へ移動
enemySp.setSpeed(1, 180);
print('左へ');
}
// 画面外まで落下したら、削除してdraw()を止める
// => サンプルでは削除が目で確認できるように、画面内で削除している
if (enemySp.position.y > height) {
enemySp.remove();
print('削除')
noLoop(); // 終了
}
}
function setPos(sp, xpos, ypos) {
sp.position.x = xpos;
sp.position.y = ypos;
sp.debug = true;
sp.immovable = true;
sp.mouseActive = true;
sp.restitution = 0.01;
}
敵スプライトは毎フレーム呼び出されるupdate()関数の次のコードによって、落下をつづけます。
enemySp.velocity.y = 1;
そして、斜めになった1つめの板スプライトに衝突すると、次のコードにより、右に移動します。すると落下しながら右に移動することになるので、板スプライトに沿って落ちていくように見えます。
if (leftBar1Sp.overlapPixel(targetX, targetY)) {
enemySp.setSpeed(1, 0);
}