projectフォルダ/ ├── img/ │ ├── ari.gif │ ├── arikui-attack.gif │ ├── arikui-stand.gif │ ├── arikui-walk-1.gif │ └── arikui-walk-2.gif ├── index.html ├── index.js └── style.css

たかまる
このゲームは、3つのファイル+画像でできているよ。
・index.html
・style.css
・index.js
index.jsのコードを見てみよう

たかまる
index.htmlとstyle.cssについては省略するね。
希望が多ければ書くけど、知りたい人はWebサイト作りを勉強してみてね。

イカクくん
ふむ。
以下がindex.jsの中身か。
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
// プレイヤー画像の読み込み
const playerImgs = [
new Image(),
new Image(),
new Image()
];
playerImgs[0].src = "img/arikui-stand.gif";
playerImgs[1].src = "img/arikui-walk-1.gif";
playerImgs[2].src = "img/arikui-walk-2.gif";
const attackImg = new Image();
attackImg.src = "img/arikui-attack.gif";
const enemyImg = new Image();
enemyImg.src = "img/ari.gif";
let playerFrame = 0;
let playerAnimCount = 0;
// プレイヤー情報
let player = {
x: 50,
y: 250,
width: 40,
height: 40,
velocityX: 0,
velocityY: 0,
isJumping: false,
};
const gravity = 0.5;
const jumpPower = -12;
let worldX = 0;
const screenMid = 200;
let keys = {};
let jumpKeyPressed = false;
let isAttacking = false;
let attackTimer = 0;
let canAttack = true;
// ステージデータ
const stages = [
{ // ステージ1
blocks: [
{ x: 0, y: 500, width: 1000, height: 200 },
{ x: 1100, y: 500, width: 1000, height: 200 },
{ x: 250, y: 380, width: 100, height: 40 },
{ x: 400, y: 300, width: 100, height: 40 },
{ x: 500, y: 150, width: 50, height: 40 },
],
hiddenBlocks: [
{ x: 450, y: 200, width: 40, height: 40, visible: false },
{ x: 890, y: 400, width: 40, height: 40, visible: false },
{ x: 990, y: 350, width: 40, height: 40, visible: false },
],
enemies: [
{ x: 600, y: 460, width: 40, height: 40, vx: 2, stopTimer: 0 },
{ x: 1200, y: 460, width: 40, height: 40, vx: -2, stopTimer: 0 }
],
goal: { x: 1800, y: 440, width: 40, height: 60 }
},
{ // ステージ2
blocks: [
{ x: 0, y: 500, width: 800, height: 200 },
{ x: 900, y: 300, width: 300, height: 40 },
{ x: 1300, y: 200, width: 100, height: 40 },
{ x: 1500, y: 120, width: 200, height: 40 },
{ x: 1800, y: 400, width: 400, height: 100 },
],
hiddenBlocks: [
{ x: 750, y: 400, width: 40, height: 40, visible: false }
],
enemies: [
{ x: 1000, y: 260, width: 40, height: 40, vx: 2, stopTimer: 0 }
],
goal: { x: 2100, y: 340, width: 40, height: 60 }
},
{ // ステージ3
blocks: [
{ x: 0, y: 500, width: 300, height: 200 },
{ x: 400, y: 400, width: 100, height: 40 },
{ x: 600, y: 350, width: 100, height: 40 },
{ x: 800, y: 300, width: 100, height: 40 },
{ x: 1000, y: 400, width: 100, height: 40 },
{ x: 1200, y: 500, width: 400, height: 100 },
],
hiddenBlocks: [
{ x: 900, y: 170, width: 40, height: 40, visible: false }
],
enemies: [
{ x: 800, y: 260, width: 40, height: 40, vx: 2, stopTimer: 0 }
],
goal: { x: 1500, y: 440, width: 40, height: 60 }
}
];
let currentStage = 0;
let blocks = [];
let hiddenBlocks = [];
let enemies = [];
let goal = {};
// ゲーム状態フラグ
let gameStarted = false;
let gameCleared = false;
let gameOver = false;
// ステージ切り替え
function loadStage(stageIndex) {
const stage = stages[stageIndex];
blocks.length = 0;
blocks.push(...stage.blocks.map(b => ({ ...b })));
hiddenBlocks.length = 0;
hiddenBlocks.push(...stage.hiddenBlocks.map(b => ({ ...b })));
enemies.length = 0;
enemies.push(...stage.enemies.map(e => ({ ...e })));
goal.x = stage.goal.x;
goal.y = stage.goal.y;
goal.width = stage.goal.width;
goal.height = stage.goal.height;
player.x = 50;
if (stageIndex === 1) {
player.y = 360;
} else if (stageIndex === 2) {
player.y = 460;
} else {
player.y = 250;
}
player.velocityX = 0;
player.velocityY = 0;
player.isJumping = false;
worldX = 0;
}
// 最初のステージをロード
loadStage(currentStage);
// 入力イベント
window.addEventListener("keydown", (e) => {
keys[e.code] = true;
if ((e.code === "Space") && !player.isJumping && !jumpKeyPressed) {
player.velocityY = jumpPower;
player.isJumping = true;
jumpKeyPressed = true;
}
if (e.code === "ArrowUp" && !isAttacking && canAttack) {
isAttacking = true;
canAttack = false;
attackTimer = 10;
}
});
window.addEventListener("keyup", (e) => {
keys[e.code] = false;
if (e.code === "ArrowUp") canAttack = true;
if (e.code === "Space") jumpKeyPressed = false;
});
// 左
const leftBtn = document.getElementById("left");
leftBtn.addEventListener("touchstart", (e) => { e.preventDefault(); keys["ArrowLeft"] = true; });
leftBtn.addEventListener("touchend", (e) => { e.preventDefault(); keys["ArrowLeft"] = false; });
leftBtn.addEventListener("mousedown", () => keys["ArrowLeft"] = true);
leftBtn.addEventListener("mouseup", () => keys["ArrowLeft"] = false);
leftBtn.addEventListener("mouseleave", () => keys["ArrowLeft"] = false);
// 右
const rightBtn = document.getElementById("right");
rightBtn.addEventListener("touchstart", (e) => { e.preventDefault(); keys["ArrowRight"] = true; });
rightBtn.addEventListener("touchend", (e) => { e.preventDefault(); keys["ArrowRight"] = false; });
rightBtn.addEventListener("mousedown", () => keys["ArrowRight"] = true);
rightBtn.addEventListener("mouseup", () => keys["ArrowRight"] = false);
rightBtn.addEventListener("mouseleave", () => keys["ArrowRight"] = false);
// ジャンプ
const jumpBtn = document.getElementById("jump");
jumpBtn.addEventListener("touchstart", () => {
if (!player.isJumping && !jumpKeyPressed) {
player.velocityY = jumpPower;
player.isJumping = true;
jumpKeyPressed = true;
}
keys["Space"] = true;
});
jumpBtn.addEventListener("touchend", () => {
keys["Space"] = false;
jumpKeyPressed = false;
});
jumpBtn.addEventListener("mousedown", () => {
if (!player.isJumping && !jumpKeyPressed) {
player.velocityY = jumpPower;
player.isJumping = true;
jumpKeyPressed = true;
}
keys["Space"] = true;
});
jumpBtn.addEventListener("mouseup", () => {
keys["Space"] = false;
jumpKeyPressed = false;
});
jumpBtn.addEventListener("mouseleave", () => {
keys["Space"] = false;
jumpKeyPressed = false;
});
// 攻撃
const attackBtn = document.getElementById("attack");
attackBtn.addEventListener("touchstart", () => {
if (!isAttacking && canAttack) {
isAttacking = true;
canAttack = false;
attackTimer = 10;
}
keys["ArrowUp"] = true;
});
attackBtn.addEventListener("touchend", () => {
keys["ArrowUp"] = false;
canAttack = true;
});
attackBtn.addEventListener("mousedown", () => {
if (!isAttacking && canAttack) {
isAttacking = true;
canAttack = false;
attackTimer = 10;
}
keys["ArrowUp"] = true;
});
attackBtn.addEventListener("mouseup", () => {
keys["ArrowUp"] = false;
canAttack = true;
});
attackBtn.addEventListener("mouseleave", () => {
keys["ArrowUp"] = false;
canAttack = true;
});
// 描画関数
function drawBlocks() {
ctx.fillStyle = "brown";
blocks.forEach(block => {
ctx.fillRect(block.x - worldX, block.y, block.width, block.height);
});
}
function drawHiddenBlocks() {
hiddenBlocks.forEach(block => {
if (block.visible) {
ctx.fillStyle = "orange";
ctx.fillRect(block.x - worldX, block.y, block.width, block.height);
}
});
}
function drawGoal() {
ctx.fillStyle = "gold";
ctx.fillRect(goal.x - worldX, goal.y, goal.width, goal.height);
}
function drawPlayer() {
let img;
let drawWidth = player.width;
if (isAttacking) {
img = attackImg;
drawWidth = 50;
} else if (player.isJumping) {
img = playerImgs[0];
playerFrame = 0;
playerAnimCount = 0;
} else if (Math.abs(player.velocityX) > 1) {
playerAnimCount++;
if (playerAnimCount % 8 === 0) {
playerFrame = (playerFrame + 1) % 3;
}
img = playerImgs[playerFrame];
} else {
img = playerImgs[0];
playerFrame = 0;
playerAnimCount = 0;
}
if (player.velocityX < -1) {
ctx.save();
ctx.translate(player.x + drawWidth / 2, player.y + player.height / 2);
ctx.scale(-1, 1);
ctx.drawImage(img, -drawWidth / 2, -player.height / 2, drawWidth, player.height);
ctx.restore();
} else {
if (img.complete) {
ctx.drawImage(img, player.x, player.y, drawWidth, player.height);
} else {
ctx.fillStyle = "red";
ctx.fillRect(player.x, player.y, drawWidth, player.height);
}
}
}
function drawEnemies() {
enemies.forEach(enemy => {
if (enemy.vx && enemy.vx > 0) {
ctx.save();
ctx.translate(enemy.x - worldX + enemy.width / 2, enemy.y + enemy.height / 2);
ctx.scale(-1, 1);
ctx.drawImage(
enemyImg,
-enemy.width / 2,
-enemy.height / 2,
enemy.width,
enemy.height
);
ctx.restore();
} else {
ctx.drawImage(
enemyImg,
enemy.x - worldX,
enemy.y,
enemy.width,
enemy.height
);
}
});
}
// スタート画面
function drawStartScreen() {
ctx.fillStyle = "#222";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#fff";
ctx.font = "32px sans-serif";
ctx.textAlign = "center";
ctx.fillText("ありくいアクション", canvas.width / 2, canvas.height / 2 - 100);
ctx.font = "16px sans-serif";
ctx.fillText("←→ 移動 | スペース ジャンプ | ↑ 攻撃", canvas.width / 2, canvas.height / 2 + 50);
ctx.fillText("スペースキーまたは画面をタップでスタート", canvas.width / 2, canvas.height / 2 + 150);
ctx.font = "12px sans-serif";
ctx.fillText("© 2025 たかまる", canvas.width / 2, canvas.height / 2 + 250);
}
// ゲームオーバー画面
function drawGameOver() {
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#fff";
ctx.font = "bold 48px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("ゲームオーバー", canvas.width / 2, canvas.height / 2 - 20);
ctx.font = "24px sans-serif";
ctx.fillText("スペースキーまたは画面をタップで再スタート", canvas.width / 2, canvas.height / 2 + 40);
}
function mainLoop() {
if (gameStarted) return;
drawStartScreen();
requestAnimationFrame(mainLoop);
}
mainLoop();
window.addEventListener("keydown", (e) => {
if (e.code === "Space") {
startOrRestart();
}
});
// 画面クリック・タップ対応
canvas.addEventListener("click", startOrRestart);
canvas.addEventListener("touchstart", (e) => {
e.preventDefault();
startOrRestart();
});
// スペースキー or 画面クリック/タップでスタート&リスタート
function startOrRestart() {
if (!gameStarted) {
gameStarted = true;
gameOver = false;
update();
} else if (gameCleared || gameOver) {
currentStage = 0;
loadStage(currentStage);
gameCleared = false;
gameOver = false;
gameStarted = true;
update();
}
}
function update() {
if (gameCleared) {
ctx.fillStyle = "#222";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#fff";
ctx.font = "bold 36px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("クリア!", canvas.width / 2, canvas.height / 2 - 20);
ctx.font = "bold 24px sans-serif";
ctx.fillText("おめでとう!", canvas.width / 2, canvas.height / 2 + 40);
ctx.font = "16px sans-serif";
ctx.fillText("スペースキーまたは画面をタップで最初からプレイ", canvas.width / 2, canvas.height / 2 + 80);
ctx.font = "12px sans-serif";
ctx.fillText("© 2025 たかまる", canvas.width / 2, canvas.height / 2 + 150);
return;
}
if (gameOver) {
drawGameOver();
return;
}
ctx.fillStyle = "#87ceeb";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (keys["ArrowRight"]) player.velocityX = 5;
if (keys["ArrowLeft"]) player.velocityX = -5;
player.velocityX *= 0.5;
if (keys["ArrowRight"] && player.x >= screenMid) {
worldX += player.velocityX;
player.x = screenMid;
} else if (keys["ArrowLeft"] && worldX > 0 && player.x <= screenMid) {
worldX -= Math.abs(player.velocityX);
player.x = screenMid;
if (worldX < 0) worldX = 0;
} else {
player.x += player.velocityX;
}
if (player.x < 0) {
player.x = 0;
player.velocityX = 0;
}
player.velocityY += gravity;
player.y += player.velocityY;
if (player.y > canvas.height) {
gameOver = true;
drawGameOver(); // ←ここを追加
return;
}
blocks.forEach(block => {
const blockLeft = block.x - worldX;
const blockRight = block.x + block.width - worldX;
const blockTop = block.y;
const blockBottom = block.y + block.height;
const playerLeft = player.x;
const playerRight = player.x + player.width;
const playerTop = player.y;
const playerBottom = player.y + player.height;
if (
playerRight > blockLeft &&
playerLeft < blockRight &&
playerBottom > blockTop &&
playerTop < blockBottom
) {
if (
player.velocityY > 0 &&
playerBottom - player.velocityY <= blockTop
) {
player.y = blockTop - player.height;
player.velocityY = 0;
player.isJumping = false;
} else if (
player.velocityY < 0 &&
playerTop - player.velocityY >= blockBottom
) {
player.y = blockBottom;
player.velocityY = 0;
} else if (player.velocityX > 0) {
player.x = blockLeft - player.width;
player.velocityX = 0;
} else if (player.velocityX < 0) {
player.x = blockRight;
player.velocityX = 0;
}
}
});
hiddenBlocks.forEach(block => {
if (!block.visible) return;
const blockLeft = block.x - worldX;
const blockRight = block.x + block.width - worldX;
const blockTop = block.y;
const blockBottom = block.y + block.height;
const playerLeft = player.x;
const playerRight = player.x + player.width;
const playerTop = player.y;
const playerBottom = player.y + player.height;
if (
playerRight > blockLeft &&
playerLeft < blockRight &&
playerBottom > blockTop &&
playerTop < blockBottom
) {
if (
player.velocityY > 0 &&
playerBottom - player.velocityY <= blockTop
) {
player.y = blockTop - player.height;
player.velocityY = 0;
player.isJumping = false;
} else if (
player.velocityY < 0 &&
playerTop - player.velocityY >= blockBottom
) {
player.y = blockBottom;
player.velocityY = 0;
} else if (player.velocityX > 0) {
player.x = blockLeft - player.width;
player.velocityX = 0;
} else if (player.velocityX < 0) {
player.x = blockRight;
player.velocityX = 0;
}
}
});
// ゴール判定
if (
player.x + player.width > goal.x - worldX &&
player.x < goal.x + goal.width - worldX &&
player.y + player.height > goal.y &&
player.y < goal.y + goal.height
) {
if (currentStage < stages.length - 1) {
currentStage++;
loadStage(currentStage);
update();
return;
} else {
gameCleared = true;
}
}
hiddenBlocks.forEach(block => {
const blockLeft = block.x - worldX;
const blockRight = block.x + block.width - worldX;
const blockTop = block.y;
const blockBottom = block.y + block.height;
const playerLeft = player.x;
const playerRight = player.x + player.width;
const playerTop = player.y;
const playerBottom = player.y + player.height;
if (
playerRight > blockLeft &&
playerLeft < blockRight &&
playerTop < blockBottom &&
playerBottom > blockTop &&
player.velocityY < 0 &&
playerTop - player.velocityY >= blockBottom
) {
block.visible = true;
player.y = blockBottom;
player.velocityY = 0;
}
});
if (isAttacking) {
attackTimer--;
if (attackTimer <= 0) {
isAttacking = false;
}
}
if (isAttacking) {
enemies.forEach((enemy, i) => {
const attackRange = 20;
if (
player.x + player.width + attackRange > enemy.x - worldX &&
player.x - attackRange < enemy.x + enemy.width - worldX &&
player.y + player.height > enemy.y &&
player.y < enemy.y + enemy.height
) {
enemies.splice(i, 1);
}
});
}
enemies.forEach((enemy, i) => {
const enemyLeft = enemy.x - worldX;
const enemyRight = enemy.x + enemy.width - worldX;
const enemyTop = enemy.y;
const enemyBottom = enemy.y + enemy.height;
const playerLeft = player.x;
const playerRight = player.x + player.width;
const playerTop = player.y;
const playerBottom = player.y + player.height;
if (
playerRight > enemyLeft &&
playerLeft < enemyRight &&
playerBottom > enemyTop &&
playerTop < enemyBottom
) {
if (
player.velocityY > 0 &&
playerBottom - player.velocityY <= enemyTop + 5
) {
enemies.splice(i, 1);
player.velocityY = jumpPower / 2;
} else if (!isAttacking) {
gameOver = true;
return;
}
}
});
enemies.forEach(enemy => {
if (enemy.stopTimer === 0 && Math.random() < 0.01) {
enemy.stopTimer = Math.floor(Math.random() * 60) + 30;
}
if (enemy.stopTimer > 0) {
enemy.stopTimer--;
return;
}
if (Math.random() < 0.01) {
enemy.vx *= -1;
}
const nextX = enemy.x + enemy.vx;
const enemyBottom = enemy.y + enemy.height;
let onBlock = false;
blocks.forEach(block => {
const blockLeft = block.x;
const blockRight = block.x + block.width;
const blockTop = block.y;
if (
nextX + enemy.width / 2 > blockLeft &&
nextX + enemy.width / 2 < blockRight &&
Math.abs(enemyBottom - blockTop) < 2
) {
onBlock = true;
}
});
if (!onBlock) {
enemy.vx *= -1;
} else {
enemy.x += enemy.vx;
}
if (enemy.x < 0) {
enemy.x = 0;
enemy.vx *= -1;
}
if (enemy.x > 2000) {
enemy.x = 2000;
enemy.vx *= -1;
}
});
drawBlocks();
drawHiddenBlocks();
drawGoal();
drawEnemies();
drawPlayer();
requestAnimationFrame(update);
}
// タッチデバイス判定
function isTouchDevice() {
return (
"ontouchstart" in window ||
navigator.maxTouchPoints > 0 ||
navigator.userAgent.indexOf("iPhone") > -1 ||
navigator.userAgent.indexOf("Android") > -1
);
}
// タッチデバイスのときだけタッチ操作用ボタン表示
function updateTouchControlsVisibility() {
const controls1 = document.getElementById("touch-controls");
const controls2 = document.getElementById("touch-controls-2");
if (isTouchDevice()) {
if (controls1) controls1.style.display = "flex";
if (controls2) controls2.style.display = "flex";
} else {
if (controls1) controls1.style.display = "none";
if (controls2) controls2.style.display = "none";
}
}
window.addEventListener("DOMContentLoaded", updateTouchControlsVisibility);
window.addEventListener("resize", updateTouchControlsVisibility);
// キャンバスのリサイズ
function resizeCanvas() {
const aspect = 800 / 600; // アスペクト比
let w = window.innerWidth;
let h = window.innerHeight;
if (w / h > aspect) {
w = h * aspect;
} else {
h = w / aspect;
}
if (!isTouchDevice()) {
// PCのみ最大サイズ制限
w = Math.min(w, 800);
h = Math.min(h, 600);
}
canvas.width = 800; // 描画解像度は固定
canvas.height = 600;
canvas.style.width = w + "px";
canvas.style.height = h + "px";
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();

たかまる
長いけど、上から順番に解説するね。

イカクくん
わ〜い!
次のページへレッツゴー!
コメント