PixiJSを使って簡単なゲームを作る
はじめに
こんにちは!今回はウェブゲーム開発の経験を元に、みなさんにPixiJSを使って簡単にH5ゲームを作る方法をご紹介します!
PixiJS
PixiJSは、ブラウザ上で軽量かつ高速な2Dレンダリングを実現するためのJavaScriptライブラリです。HTML5の<canvas>を使って、ゲームやインタラクティブなアプリケーションを手軽に構築することができます。今回はこれを使ってvampire survivors風ゲームを作ってみましょう。
準備
今回はシンプルにindex.htmlとmain.js、そしてPixiJSだけ用意します。
PixiJSはv7.4.2 CDN形式で使います。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Vampire Survivors-like Game</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/7.4.2/pixi.min.js"></script>
</head>
<body>
<canvas id="game-canvas"></canvas>
<script src="src/main.js"></script>
</body>
</html>
// ここにコードを作成していきます。
PixiJSの初期化
まずは、pixijsを初期化します。
const app = new PIXI.Application({
view: document.getElementById('game-canvas'), // canvas要素を指定
width: 800, // 横サイズ
height: 600, // 縦サイズ
backgroundColor: 0x000000 // 背景色
});
用意したcanvasを指定して、ゲームのサイズと背景色を設定します。
ゲームステージができました
プレイヤーを作成
プレイヤーのキャラクターを作ります。今回は画像なしでpixiの描画機能を使います。
const player = new PIXI.Graphics(); // pixi.jsの描画オブジェクト
player.beginFill(0xffffff); // 白色で塗り開始
player.drawCircle(0, 0, 10); // 中心(0, 0)に半径10の円を描画
player.endFill(); // 塗り終了
player.x = app.view.width / 2; // x座標を画面中央に
player.y = app.view.height / 2; // y座標を画面中央に
app.stage.addChild(player); // ステージに追加
プレイヤーができました
キャラクターを動かせるように
キーボードの矢印キーとWASDキーでキャラクターを移動させます。
const playerSpeed = 3; // プレイヤーの移動速度
const keys = { up: false, down: false, left: false, right: false };
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowUp' || e.key === 'w') keys.up = true;
if (e.key === 'ArrowDown' || e.key === 's') keys.down = true;
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
});
window.addEventListener('keyup', (e) => {
if (e.key === 'ArrowUp' || e.key === 'w') keys.up = false;
if (e.key === 'ArrowDown' || e.key === 's') keys.down = false;
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
});
// pixijsのアニメーションループに登録
app.ticker.add(() => {
// プレイヤー移動
if (keys.up) player.y -= playerSpeed;
if (keys.down) player.y += playerSpeed;
if (keys.left) player.x -= playerSpeed;
if (keys.right) player.x += playerSpeed;
});
キーボードの入力でキャラクターが移動します
フレームレートに依存しないように
既存のコードではユーザーのモニターのフレームレートによりキャラクターの移動速度が異なります。
これはpixijsのtickerが1フレームごとに更新されるためです。なので、1フレームごとの移動量ではなく1秒ごとの移動量に変更します。
+ const playerSpeed = 180; // プレイヤー速度1秒あたり180ピクセル移動
- const playerSpeed = 3; // プレイヤーの移動速度
// pixijsのアニメーションループに登録
app.ticker.add(() => {
+ const deltaMS = app.ticker.deltaMS; // 前フレームからの経過ミリ秒
+ const deltaSec = deltaMS / 1000; // 秒換算
// プレイヤー移動
+ let moveX = 0;
+ let moveY = 0;
+ if (keys.up) moveY -= playerSpeed * deltaSec;
+ if (keys.down) moveY += playerSpeed * deltaSec;
+ if (keys.left) moveX -= playerSpeed * deltaSec;
+ if (keys.right) moveX += playerSpeed * deltaSec;
- if (keys.up) player.y -= playerSpeed;
- if (keys.down) player.y += playerSpeed;
- if (keys.left) player.x -= playerSpeed;
- if (keys.right) player.x += playerSpeed;
+ player.x += moveX;
+ player.y += moveY;
});
敵を作る
画面の端付近からランダムに出現させます。そしてプレイヤー方向に移動するようにします。
const enemies = []; // 敵の配列
let spawnTimer = 0; // 敵出現の経過時間
const spawnInterval = 1000; // 1秒(1000ms)ごとに敵出現
const enemySpeed = 60; // 敵の移動速度1秒あたり60ピクセル移動
app.ticker.add(() => {
...省略
// 敵出現の時間更新
spawnTimer += deltaMS;
if (spawnTimer > spawnInterval) {
spawnTimer = 0;
const enemy = new PIXI.Graphics(); // 敵オブジェクト
enemy.beginFill(0xff0000); // 赤色で塗り開始
enemy.drawCircle(0, 0, 8); // 中心(0, 0)に半径8の円を描画
enemy.endFill();
// 画面外もしくは端付近からランダム出現
const side = Math.floor(Math.random() * 4);
if (side === 0) {
// 画面上端から出現
enemy.x = Math.random() * app.view.width;
enemy.y = -10;
} else if (side === 1) {
// 画面右端から出現
enemy.x = app.view.width + 10;
enemy.y = Math.random() * app.view.height;
} else if (side === 2) {
// 画面下端から出現
enemy.x = Math.random() * app.view.width;
enemy.y = app.view.height + 10;
} else {
// 画面左端から出現
enemy.x = -10;
enemy.y = Math.random() * app.view.height;
}
app.stage.addChild(enemy);
enemies.push(enemy);
}
// 敵移動
enemies.forEach((enemy) => {
const dx = player.x - enemy.x;
const dy = player.y - enemy.y;
const dist = Math.sqrt(dx*dx + dy*dy);
const ux = dx / dist;
const uy = dy / dist;
if (dist > 0) {
const vx = ux * enemySpeed * deltaSec;
const vy = uy * enemySpeed * deltaSec;
enemy.x += vx;
enemy.y += vy;
}
});
});
敵が出現し、プレイヤーに近づいてきます
弾丸を発射させる
プレイヤーが敵を倒すための弾丸を発射させます。弾丸は敵オブジェクトと衝突したら敵オブジェクトを破壊し、消えます。
const bullets = []; // 弾丸の配列
let bulletTimer = 0; // 弾発射の経過時間
const bulletInterval = 500; // ミリ秒(0.5秒)ごとに弾発射
const bulletSpeed = 300; // 弾丸の移動速度1秒あたり300ピクセル移動
app.ticker.add(() => {
...省略
// 弾発射
bulletTimer += deltaMS;
if (bulletTimer > bulletInterval) {
bulletTimer = 0;
const bullet = new PIXI.Graphics();
bullet.beginFill(0x00ff00); // 緑色で塗り開始
bullet.drawCircle(0, 0, 4); // 中心(0, 0)に半径4の円を描画
bullet.endFill();
bullet.x = player.x;
bullet.y = player.y;
app.stage.addChild(bullet);
bullets.push(bullet);
}
// 弾移動
bullets.forEach((b) => {
b.y -= bulletSpeed * deltaSec;
});
// 衝突判定(弾 vs 敵)
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
for (let j = bullets.length - 1; j >= 0; j--) {
const bullet = bullets[j];
const dx = enemy.x - bullet.x;
const dy = enemy.y - bullet.y;
const dist = Math.sqrt(dx*dx + dy*dy);
// 弾と敵の距離が8(弾半径4 + 敵半径8)未満なら衝突
if (dist < (8 + 4)) {
app.stage.removeChild(enemy); // 敵をステージから削除
enemies.splice(i, 1); // 配列から削除
app.stage.removeChild(bullet); // 弾をステージから削除
bullets.splice(j, 1); // 配列から削除
break;
}
}
}
});
弾丸が上に発射され、敵とぶつかったら敵が消えます
マウスの位置に弾丸を発射するように
// ターゲット位置(マウスで指定された位置)
// 初期値は画面中央上部
+ let targetX = app.view.width / 2;
+ let targetY = 0;
// マウスが動くたびにターゲット位置を更新
+ app.view.addEventListener('pointermove', (e) => {
+ const rect = app.view.getBoundingClientRect(); // canvasの位置とサイズを取得
+ const mouseX = e.clientX - rect.left; // canvas内のマウスのx座標
+ const mouseY = e.clientY - rect.top; // canvas内のマウスのy座標
+ targetX = mouseX;
+ targetY = mouseY;
+ });
app.ticker.add(() => {
...中略
// ターゲット位置に向かって弾を連射する
// 一定間隔で弾を生成
bulletTimer += deltaMS;
if (bulletTimer > bulletInterval) {
bulletTimer = 0;
// プレイヤー位置(player.x, player.y)から、ターゲット位置(targetX, targetY)へのベクトルを算出
+ const dx = targetX - player.x;
+ const dy = targetY - player.y;
+ const dist = Math.sqrt(dx*dx + dy*dy);
// ターゲット位置への単位ベクトル
+ const ux = dx / dist;
+ const uy = dy / dist;
// 弾の速度ベクトル
+ const vx = ux * bulletSpeed;
+ const vy = uy * bulletSpeed;
// 弾を生成
const bullet = new PIXI.Graphics();
bullet.beginFill(0x00ff00);
bullet.drawCircle(0, 0, 4);
bullet.endFill();
bullet.x = player.x;
bullet.y = player.y;
+ bullet.vx = vx;
+ bullet.vy = vy;
app.stage.addChild(bullet);
bullets.push(bullet);
}
// 弾移動
bullets.forEach((b) => {
// 1秒あたりの移動距離
+ const moveX = (b.vx * deltaMS) / 1000;
+ const moveY = (b.vy * deltaMS) / 1000;
+ b.x += moveX;
+ b.y += moveY;
});
...中略
});
マウスカーソルに向かって弾丸が発射されます
スコアを追加
ゲームで一番大事なスコア表示を追加してみましょう。
+ let score = 0; // スコア
+ const scoreText = new PIXI.Text('Score: 0', { fill: 0xffff00 }); // スコア表示用テキストオブジェクト、黄色
+ scoreText.x = 10;
+ scoreText.y = 10;
+ app.stage.addChild(scoreText);
...中略
// 衝突判定(弾 vs 敵)
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
for (let j = bullets.length - 1; j >= 0; j--) {
const bullet = bullets[j];
const dx = enemy.x - bullet.x;
const dy = enemy.y - bullet.y;
const dist = Math.sqrt(dx*dx + dy*dy);
// 弾と敵の距離が8(弾半径4 + 敵半径8)未満なら衝突
if (dist < (8 + 4)) {
+ score += 1; // スコア加算
+ scoreText.text = `Score: ${score}`; // スコア表示更新
app.stage.removeChild(enemy); // 敵をステージから削除
enemies.splice(i, 1); // 配列から削除
app.stage.removeChild(bullet); // 弾をステージから削除
bullets.splice(j, 1); // 配列から削除
break;
}
}
}
...中略
左上にスコアが表示され、敵を倒すたびに加算されていきます
全体のコード
main.jsの全体コードです。
const app = new PIXI.Application({
view: document.getElementById('game-canvas'), // canvas要素を指定
width: 800, // 横サイズ
height: 600, // 縦サイズ
backgroundColor: 0x000000 // 背景色
});
const playerSpeed = 180; // プレイヤー速度1秒あたり180ピクセル移動
const player = new PIXI.Graphics(); // pixi.jsの描画オブジェクト
player.beginFill(0xffffff); // 白色で塗り開始
player.drawCircle(0, 0, 10); // 中心(0, 0)に半径10の円を描画
player.endFill(); // 塗り終了
player.x = app.view.width / 2; // x座標を画面中央に
player.y = app.view.height / 2; // y座標を画面中央に
app.stage.addChild(player); // ステージに追加
const enemies = []; // 敵の配列
let spawnTimer = 0; // 敵出現の経過時間
const spawnInterval = 1000; // ミリ秒(1秒)ごとに敵出現
const enemySpeed = 60; // 敵の移動速度1秒あたり60ピクセル移動
const bullets = []; // 弾丸の配列
let bulletTimer = 0; // 弾発射の経過時間
const bulletInterval = 500; // ミリ秒(0.5秒)ごとに弾発射
const bulletSpeed = 300; // 弾丸の移動速度1秒あたり300ピクセル移動
// ターゲット位置(マウスで指定された位置)
// 初期値は画面中央上部
let targetX = app.view.width / 2;
let targetY = 0;
let score = 0; // スコア
const scoreText = new PIXI.Text('Score: 0', { fill: 0xffff00 }); // スコア表示用テキストオブジェクト、黄色
scoreText.x = 10;
scoreText.y = 10;
app.stage.addChild(scoreText);
const keys = { up: false, down: false, left: false, right: false };
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowUp' || e.key === 'w') keys.up = true;
if (e.key === 'ArrowDown' || e.key === 's') keys.down = true;
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
});
window.addEventListener('keyup', (e) => {
if (e.key === 'ArrowUp' || e.key === 'w') keys.up = false;
if (e.key === 'ArrowDown' || e.key === 's') keys.down = false;
if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
});
// マウスが動くたびにターゲット位置を更新
app.view.addEventListener('pointermove', (e) => {
const rect = app.view.getBoundingClientRect(); // canvasの位置とサイズを取得
const mouseX = e.clientX - rect.left; // canvas内のマウスのx座標
const mouseY = e.clientY - rect.top; // canvas内のマウスのy座標
targetX = mouseX;
targetY = mouseY;
});
app.ticker.add(() => {
const deltaMS = app.ticker.deltaMS; // 前フレームからの経過ミリ秒
const deltaSec = deltaMS / 1000; // 秒換算
// プレイヤー移動(秒基準)
let moveX = 0;
let moveY = 0;
if (keys.up) moveY -= playerSpeed * deltaSec;
if (keys.down) moveY += playerSpeed * deltaSec;
if (keys.left) moveX -= playerSpeed * deltaSec;
if (keys.right) moveX += playerSpeed * deltaSec;
player.x += moveX;
player.y += moveY;
// 敵出現の時間更新
spawnTimer += deltaMS;
if (spawnTimer > spawnInterval) {
spawnTimer = 0;
const enemy = new PIXI.Graphics();
enemy.beginFill(0xff0000); // 赤色で塗り開始
enemy.drawCircle(0, 0, 8); // 中心(0, 0)に半径8の円を描画
enemy.endFill();
// 画面外もしくは端付近からランダム出現
const side = Math.floor(Math.random() * 4);
if (side === 0) {
// 画面上端から出現
enemy.x = Math.random() * app.view.width;
enemy.y = -10;
} else if (side === 1) {
// 画面右端から出現
enemy.x = app.view.width + 10;
enemy.y = Math.random() * app.view.height;
} else if (side === 2) {
// 画面下端から出現
enemy.x = Math.random() * app.view.width;
enemy.y = app.view.height + 10;
} else {
// 画面左端から出現
enemy.x = -10;
enemy.y = Math.random() * app.view.height;
}
app.stage.addChild(enemy);
enemies.push(enemy);
}
// 敵移動
enemies.forEach((enemy) => {
const dx = player.x - enemy.x;
const dy = player.y - enemy.y;
const dist = Math.sqrt(dx*dx + dy*dy);
const ux = dx / dist;
const uy = dy / dist;
if (dist > 0) {
const vx = ux * enemySpeed * deltaSec;
const vy = uy * enemySpeed * deltaSec;
enemy.x += vx;
enemy.y += vy;
}
});
// ターゲット位置に向かって弾を連射する
// 一定間隔で弾を生成
bulletTimer += deltaMS;
if (bulletTimer > bulletInterval) {
bulletTimer = 0;
// プレイヤー位置(player.x, player.y)から、ターゲット位置(targetX, targetY)へのベクトルを算出
const dx = targetX - player.x;
const dy = targetY - player.y;
const dist = Math.sqrt(dx*dx + dy*dy);
// ターゲット位置への単位ベクトル
const ux = dx / dist;
const uy = dy / dist;
// 弾の速度ベクトル
const vx = ux * bulletSpeed;
const vy = uy * bulletSpeed;
// 弾を生成
const bullet = new PIXI.Graphics();
bullet.beginFill(0x00ff00);
bullet.drawCircle(0, 0, 4);
bullet.endFill();
bullet.x = player.x;
bullet.y = player.y;
bullet.vx = vx;
bullet.vy = vy;
app.stage.addChild(bullet);
bullets.push(bullet);
}
// 弾移動
bullets.forEach((b) => {
// 1秒あたりの移動距離
const moveX = (b.vx * deltaMS) / 1000;
const moveY = (b.vy * deltaMS) / 1000;
b.x += moveX;
b.y += moveY;
});
// 衝突判定(弾 vs 敵)
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
for (let j = bullets.length - 1; j >= 0; j--) {
const bullet = bullets[j];
const dx = enemy.x - bullet.x;
const dy = enemy.y - bullet.y;
const dist = Math.sqrt(dx*dx + dy*dy);
// 弾と敵の距離が8(弾半径4 + 敵半径8)未満なら衝突
if (dist < (8 + 4)) {
score += 1; // スコア加算
scoreText.text = `Score: ${score}`; // スコア表示更新
app.stage.removeChild(enemy); // 敵をステージから削除
enemies.splice(i, 1); // 配列から削除
app.stage.removeChild(bullet); // 弾をステージから削除
bullets.splice(j, 1); // 配列から削除
break;
}
}
}
});
最後に
今回はPixiJSを使ってゲームを作ってみました。簡単なゲームですが、誰でも作れるぐらい難しくないので、皆さんもぜひ1回作ってみましょう!
Discussion