「ポン」(Pong)は「ゲームカタログ@Wiki」によると、次のように説明されています。
目次
システム
- 対戦プレイ専用
- 互いにダイヤル状のツマミ(パドル)を使って縦長のバー(自機)を操作し、ドット(ボール)を弾きあってプレイする
- バーは上下にしか動かす事が出来ない
- バーで弾かれたボールは一定の法則に基づいて角度を変えて弾かれる
- ボールが相手のバーを超え、センターラインを突破すると勝利となり、スコアが加算される
- 現代風に例えるとエアホッケーのビデオゲーム版といった感じ
以下は、このPongもどきの、p5.playのサンプルにある「Pong」を見ていきます(なお、細かな部分でコードを書き替えています)。
// 「ポン」(卓球ゲーム)もどき
// マウスで両方のパドルを走査する
let leftPaddleSp, rightPaddleSp, ballSp, wallTopSp, wallBottomSp;
const MAX_SPEED = 10;
function setup() {
createCanvas(800, 400);
//frameRate(6);
const w = 10;
const h = 100;
const offset = 30;
// 左のパドルスプライト
leftPaddleSp = makeSprite(offset, height / 2, w, h, true, color(255, 0, 0));
// 右のパドルスプライト
rightPaddleSp = makeSprite(width - offset, height / 2, w, h, true, color(255, 0, 255));
// 上の壁スプライト
wallTopSp = makeSprite(width / 2, -10, width, offset, true, color(0, 255, 0));
// 下の壁スプライト
wallBottomSp = makeSprite(width / 2, height + 10, width, offset, true, color(0, 255, 0));
// ボールスプライト
ballSp = makeSprite(width / 2, height / 2, 10, 10, false, color(255));
// 速くなりすぎないように制限を設ける
ballSp.maxSpeed = MAX_SPEED;
// ボールは最初、キャンバス中央から左へ進む
ballSp.setSpeed(MAX_SPEED, -180);
}
// スプライトを作成し、与えられた引数でプロパティを設定したスプライトを返す
function makeSprite(xpos, ypos, w, h, isImmovable, col) {
const sp = createSprite();
sp.width = w;
sp.height = h;
sp.position.x = xpos;
sp.position.y = ypos;
sp.immovable = isImmovable;
sp.shapeColor = col;
return sp;
}
function draw() {
background(0);
update();
drawSprites();
}
function update() {
// パドルがキャンバスから出ないように、上下の動きを制限し、
// 右パドルを左パドルの動きに同期させる。
leftPaddleSp.position.y = constrain(mouseY, leftPaddleSp.height / 2, height - leftPaddleSp.height / 2);
rightPaddleSp.position.y = constrain(mouseY, leftPaddleSp.height / 2, height - leftPaddleSp.height / 2);
// ボールは上の壁に当たったら跳ね返る
ballSp.bounce(wallTopSp);
// ボールは下の壁に当たったら跳ね返る
ballSp.bounce(wallBottomSp);
// 入射角=反射角とする => ballSp.getDirection()
// 「反射の法則」 https://exam.fukuumedia.com/rika1-13/#i-3
// ただし、ボールの芯とパドルの芯のずれが大きいと、反射角も大きくなる
// ボールが左パドルに当たったら
if (ballSp.bounce(leftPaddleSp)) {
// ボールの芯とパドルの芯のずれ。
const swing = (ballSp.position.y - leftPaddleSp.position.y) / 3;
// 左パドルの場合、角度は時計回りに大きくなるので、角度を大きくするにはswingを足す
ballSp.setSpeed(MAX_SPEED, ballSp.getDirection() + swing);
print(ballSp.getDirection())
}
// ボールが右パドルに当たったら
if (ballSp.bounce(rightPaddleSp)) {
const swing = (ballSp.position.y - rightPaddleSp.position.y) / 3;
// 右パドルの場合、角度は反時計回りに大きくなるので、角度を大きくするにはswingを引く
ballSp.setSpeed(MAX_SPEED, ballSp.getDirection() - swing);
}
// ボールがキャンバス左端から外に出たら、真ん中に再配置し右へ動く
if (ballSp.position.x < 0) {
ballSp.position.x = width / 2;
ballSp.position.y = height / 2;
ballSp.setSpeed(MAX_SPEED, 0);
}
// ボールがキャンバス右端から外に出たら、真ん中に再配置し左へ動く
if (ballSp.position.x > width) {
ballSp.position.x = width / 2;
ballSp.position.y = height / 2;
ballSp.setSpeed(MAX_SPEED, 180);
}
}
次のリンクのクリックで実際にゲームサンプルがプレイできます。「ポン」
スプライトは左右のパドル2つと、上下の壁、ボールの合計5つです。ボールは上と下の壁に当たったら跳ね返ります。また、パドルに当たっても跳ね返ります。
ボールとパドルとの跳ね返りは、「反射の法則」と呼ばれる方法で表現できます。これは、入って来る入射角と出て行く反射角が等しくなる、という法則です。
p5.play.jsのSprite.bounce()メソッドにも、この法則が実装されていると思われるので、たとえば20度の入射角で入って来たボールは、何もしなくても、Sprite.bounce()メソッドの働きによって、20度の反射角で出ていきます。下図はその説明です。
たとえば次のコードで、ボールスプライトを160度の方向へ移動させたとします。160度というのは、X軸方向、つまり真右を0度として時計回りに160度回転した方向です。
ballSp.setSpeed(MAX_SPEED, 160);
このボールがパドルに当たると、そのときのballSp.getDirection()は20になります。これが反射角です。
ただし上記サンプルではSprite.bounce()をそのまま使うのではなく、少し工夫を加えています。
// ボールが左パドルに当たったら
if (ballSp.bounce(leftPaddleSp)) {
// ボールの芯とパドルの芯のずれ。
const swing = (ballSp.position.y - leftPaddleSp.position.y) / 3;
// 左パドルの場合、角度は時計回りに大きくなるので、角度を大きくするにはswingを足す
ballSp.setSpeed(MAX_SPEED, ballSp.getDirection() + swing);
print(ballSp.getDirection())
}
// ボールが右パドルに当たったら
if (ballSp.bounce(rightPaddleSp)) {
const swing = (ballSp.position.y - rightPaddleSp.position.y) / 3;
// 右パドルの場合、角度は反時計回りに大きくなるので、角度を大きくするにはswingを引く
ballSp.setSpeed(MAX_SPEED, ballSp.getDirection() - swing);
}
変数swingに代入されるのは、ボールの芯とパドルの芯のずれを表す数値です。簡単に言うと、パドルの端で打った場合には、ボールの跳ね返る角度が大きくなり、変な方向にボールが飛び出す、ということです。この工夫によって、パドルがいつも同じ角度でボールを返さなくなるので、プレイヤーはできるだけパドルのセンターでボールを受けようと、ゲームに集中するようになります。