「ぼうけんうさぎ」のシューティングゲームをChatGPTと創る
Tazikuのイラストレーションプロジェクト「どうくつたんけん」のキャラクターの一人、「ぼうけんうさぎ」。このキャラクターを使って、今度はシューティングゲームを創ってみます。
ゴールは簡単なカジュアルゲームだけど、オリジナルのイラストと世界観をもった簡易ブラウザゲームを目指します。
ChatGPT4に与えた条件
「どうくつたんけん」という物語の中に出てくる、「ぼうけんうさぎ」のシューティングゲームを創りたいです。うさぎは人参を飛ばしてモンスターをやっつけていきます。
以下の前提条件を踏まえた上で、制作してください。
・HTML+JS+CSSで完結する
・極力シンプルなコード
・スコアを競うゲーム
初期のコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>どうくつたんけん - ぼうけんうさぎシューティングゲーム</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="game">
<div id="player"></div>
</div>
<script src="script.js"></script>
</body>
</html>
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: skyblue;
}
#game {
position: relative;
width: 800px;
height: 600px;
border: 2px solid black;
overflow: hidden;
}
#player {
position: absolute;
width: 50px;
height: 50px;
background-image: url('usagi.png');
background-size: cover;
bottom: 10px;
left: 375px;
}
.monster {
position: absolute;
width: 30px;
height: 30px;
background-image: url('monster.png');
background-size: cover;
top: 0;
}
.carrot {
position: absolute;
width: 10px;
height: 20px;
background-image: url('carrot.png');
background-size: cover;
}
const game = document.getElementById("game");
const player = document.getElementById("player");
let score = 0;
document.addEventListener("keydown", (event) => {
if (event.key === "ArrowLeft" && player.offsetLeft > 0) {
player.style.left = player.offsetLeft - 10 + "px";
} else if (event.key === "ArrowRight" && player.offsetLeft < game.clientWidth - player.clientWidth) {
player.style.left = player.offsetLeft + 10 + "px";
} else if (event.key === " ") {
shoot();
}
});
function shoot() {
const carrot = document.createElement("div");
carrot.className = "carrot";
carrot.style.left = player.offsetLeft + player.clientWidth / 2 - 5 + "px";
carrot.style.top = player.offsetTop - 20 + "px";
game.appendChild(carrot);
const shootInterval = setInterval(() => {
carrot.style.top = carrot.offsetTop - 10 + "px";
if (carrot.offsetTop < 0) {
game.removeChild(carrot);
clearInterval(shootInterval);
}
}, 100);
}
setInterval(() => {
const monster = document.createElement("div");
monster.className = "monster";
monster.style.left = Math.random() * (game.clientWidth - 30) + "px";
game.appendChild(monster);
const monsterInterval = setInterval(() => {
monster.style.top = monster.offsetTop + 10 + "px";
if (monster.offsetTop > game.clientHeight) {
game.removeChild(monster);
clearInterval(monsterInterval);
} else {
const carrots = document.getElementsByClassName("carrot");
for (let carrot of carrots) {
const isColliding = carrot.offsetLeft < monster.offsetLeft + 30 &&
carrot.offsetLeft + 10 > monster.offsetLeft &&
carrot.offsetTop < monster.offsetTop + 30 &&
carrot.offsetTop + 20 > monster.offsetTop;
if (isColliding) {
game.removeChild(monster);
game.removeChild(carrot);
clearInterval(monsterInterval);
score++;
console.log("スコア:", score);
break;
}
}
}
}, 100);
}, 2000);
取り急ぎノーチェックでも動作しました。ただプレイしていて色々修正したい点がでてきましたので、要望をまとめてChatGPTへ投げます。
・60秒の時間制限をつけて、制限時間を超えたらGAMEOVERと表示されスコアが出る
・Gameoverした時はゲームが止まるようにしたいです。またその時、再スタートのボタンを付けたい
・うさぎがモンスターに衝突するとゲームオーバーになるようにしたい
・残り時間はゲームオーバーした時に止めたい
・もう少しデザインを洗練させたい。 スコアや残り時間はプレイエリア内に入れ込みたい。
ChatGPTに修正依頼
修正されたjavascriptは以下
const game = document.getElementById("game");
let player = document.getElementById("player");
let score = 0;
let timeLeft = 60;
let timer;
let isGameOver = false;
const timeLimit = 60;
document.addEventListener("keydown", (event) => {
if (event.key === "ArrowLeft" && player.offsetLeft > 0) {
player.style.left = player.offsetLeft - 10 + "px";
} else if (event.key === "ArrowRight" && player.offsetLeft < game.clientWidth - player.clientWidth) {
player.style.left = player.offsetLeft + 10 + "px";
} else if (event.key === " ") {
shoot();
}
});
function shoot() {
const carrot = document.createElement("div");
carrot.className = "carrot";
carrot.style.left = player.offsetLeft + player.clientWidth / 2 - 5 + "px";
carrot.style.top = player.offsetTop - 10 + "px"; // ここを修正
game.appendChild(carrot);
const carrotInterval = setInterval(() => {
carrot.style.top = carrot.offsetTop - 10 + "px";
if (carrot.offsetTop < 0) {
game.removeChild(carrot);
clearInterval(carrotInterval);
}
}, 100);
}
function isCollidingWithPlayer(monster, player) {
return monster.offsetLeft < player.offsetLeft + player.clientWidth &&
monster.offsetLeft + monster.clientWidth > player.offsetLeft &&
monster.offsetTop < player.offsetTop + player.clientHeight &&
monster.offsetTop + monster.clientHeight > player.offsetTop;
}
function gameOver() {
isGameOver = true;
game.innerHTML = "";
const gameOverText = document.createElement("div");
gameOverText.innerText = "GAME OVER\nスコア: " + score;
gameOverText.style.position = "absolute";
gameOverText.style.top = "50%";
gameOverText.style.left = "50%";
gameOverText.style.transform = "translate(-50%, -50%)";
gameOverText.style.color = "red";
gameOverText.style.fontSize = "24px";
gameOverText.style.textAlign = "center";
game.appendChild(gameOverText);
const restartButton = document.createElement("button");
restartButton.innerText = "再スタート";
restartButton.style.position = "absolute";
restartButton.style.top = "60%";
restartButton.style.left = "50%";
restartButton.style.transform = "translate(-50%, -50%)";
restartButton.style.fontSize = "16px";
restartButton.onclick = restartGame;
game.appendChild(restartButton);
}
function restartGame() {
isGameOver = false;
timeLeft = timeLimit;
score = 0;
game.innerHTML = '<div id="player"></div><div class="score-timer-container"><div id="score">スコア: 0</div><div id="timer">残り時間: 60秒</div></div>';
player = document.getElementById("player");
player.style.left = "50%";
player.style.transform = "translateX(-50%)";
timer = setInterval(() => {
timeLeft--;
document.getElementById("timer").innerText = "残り時間: " + timeLeft + "秒";
if (timeLeft <= 0) {
clearInterval(timer);
gameOver();
}
}, 1000);
generateMonsters();
}
function generateMonsters() {
setInterval(() => {
if (isGameOver) return;
const monster = document.createElement("div");
monster.className = "monster";
monster.style.left = Math.random() * (
game.clientWidth - 30) + "px";
game.appendChild(monster);
const monsterInterval = setInterval(() => {
monster.style.top = monster.offsetTop + 10 + "px";
if (monster.offsetTop > game.clientHeight) {
game.removeChild(monster);
clearInterval(monsterInterval);
} else {
const carrots = document.getElementsByClassName("carrot");
for (let carrot of carrots) {
const isColliding = carrot.offsetLeft < monster.offsetLeft + 30 &&
carrot.offsetLeft + 10 > monster.offsetLeft &&
carrot.offsetTop < monster.offsetTop + 30 &&
carrot.offsetTop + 20 > monster.offsetTop;
if (isColliding) {
game.removeChild(carrot);
game.removeChild(monster);
clearInterval(monsterInterval);
score++;
document.getElementById("score").innerText = "スコア: " + score;
}
}
if (!isGameOver && isCollidingWithPlayer(monster, player)) {
clearInterval(monsterInterval);
clearInterval(timer);
gameOver();
}
}
}, 100);
}, 2000);
}
generateMonsters();
JSはかなり壁打ちが必要でした。一発ではなかなか細部の修正はまだ難しいようです。ある程度挙動はそれらしくなったので、ここからはイラストレーターに依頼していきます。
イラストレーターにパーツデザインを依頼
うさぎの、人参、モンスター、プレイエリア背景を依頼、cssの画像の名前にリネームして作成しアップロードします。
それなりになりましたね!
最終成果物
どうくつたんけん – ぼうけんうさぎシューティングゲーム
https://taziku.co.jp/app/usagi_game_v1/
前回のブロック崩しよりは遥かに精度の高いものができました。AIに指示を与えるコツも分かってきました。
さらにこのゲームも、もう1段階ブラッシュアップしたいと思います。また改善したV2をお届けします!