Gemini 2.5 Pro の Canvas モードで作ってもらいました。スマホだと操作は厳しいかもしれません。
スコア: 0
ゲームオーバー
生成したコードはこれ
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>3D障害物避けゲーム</title> </head> <body> <!-- スタイルシート --> <style> /* ゲームを格納するコンテナ */ #game-container { width: 100%; max-width: 800px; aspect-ratio: 16 / 9; /* 16:9の比率を維持 */ position: relative; /* 子要素の絶対配置の基準点 */ border-radius: 12px; overflow: hidden; /* 角丸を適用するため */ box-shadow: 0 10px 25px rgba(0,0,0,0.5); } /* three.jsが生成するcanvasのスタイル */ canvas { display: block; width: 100%; height: 100%; } #ui-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: flex-start; /* スコアを上部に配置 */ align-items: center; pointer-events: none; } #score { margin-top: 20px; padding: 5px 15px; background-color: rgba(0,0,0,0.3); border-radius: 20px; font-size: 1.5rem; color: white; text-shadow: 1px 1px 2px #000000; } #game-over-container { display: none; flex-direction: column; justify-content: center; align-items: center; background-color: rgba(0, 0, 0, 0.7); width: 100%; height: 100%; position: absolute; top: 0; left: 0; pointer-events: all; } #game-over-text { font-size: 3rem; color: white; margin-bottom: 20px; } #restart-button { padding: 15px 30px; font-size: 1.5rem; color: white; background-color: #4a90e2; border: none; border-radius: 10px; cursor: pointer; transition: background-color 0.3s; } #restart-button:hover { background-color: #357abd; } </style> <!-- ゲームのコンテナDIV --> <div id="game-container"> <!-- UI要素 --> <div id="ui-container"> <div id="score">スコア: 0</div> </div> <div id="game-over-container"> <div id="game-over-text">ゲームオーバー</div> <button id="restart-button">リスタート</button> </div> <!-- three.jsのcanvasはここにJavaScriptによって挿入されます --> </div> <!-- three.jsライブラリとゲームロジックのスクリプト --> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script> // --- グローバル変数 --- let scene, camera, renderer; let player, playerBoundingBox; let obstacles = []; let score = 0; let isGameOver = false; let obstacleSpawnInterval; // --- DOM要素の取得 --- const gameContainer = document.getElementById('game-container'); const scoreElement = document.getElementById('score'); const gameOverContainer = document.getElementById('game-over-container'); const restartButton = document.getElementById('restart-button'); // --- 初期化処理 --- function init() { // シーンの作成 scene = new THREE.Scene(); scene.background = new THREE.Color(0x2d3748); // 少し明るいダークブルー // カメラの作成 (コンテナのサイズに合わせる) camera = new THREE.PerspectiveCamera(75, gameContainer.clientWidth / gameContainer.clientHeight, 0.1, 1000); camera.position.z = 30; camera.position.y = 5; // レンダラーの作成 renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(gameContainer.clientWidth, gameContainer.clientHeight); renderer.shadowMap.enabled = true; // レンダラーが生成したcanvasをコンテナに追加 gameContainer.appendChild(renderer.domElement); // ライトの追加 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 20, 10); directionalLight.castShadow = true; scene.add(directionalLight); // 地面の作成 const groundGeometry = new THREE.PlaneGeometry(100, 100); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x4a5568 }); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; ground.position.y = -5; ground.receiveShadow = true; scene.add(ground); // プレイヤーの作成 const playerGeometry = new THREE.BoxGeometry(2, 2, 2); const playerMaterial = new THREE.MeshStandardMaterial({ color: 0x4299e1 }); player = new THREE.Mesh(playerGeometry, playerMaterial); player.position.y = -3; player.castShadow = true; scene.add(player); // プレイヤーの当たり判定用ボックス playerBoundingBox = new THREE.Box3(new THREE.Vector3(), new THREE.Vector3()); playerBoundingBox.setFromObject(player); // イベントリスナーの設定 window.addEventListener('keydown', onPlayerMove); window.addEventListener('resize', onWindowResize, false); restartButton.addEventListener('click', restartGame); // ゲーム開始 startGame(); } // --- ゲーム開始 --- function startGame() { isGameOver = false; score = 0; updateScore(); gameOverContainer.style.display = 'none'; obstacles.forEach(obstacle => scene.remove(obstacle.mesh)); obstacles = []; player.position.x = 0; obstacleSpawnInterval = setInterval(spawnObstacle, 1000); animate(); } // --- 障害物の生成 --- function spawnObstacle() { if (isGameOver) return; const geometry = new THREE.BoxGeometry(2, 2, 2); const material = new THREE.MeshStandardMaterial({ color: 0xe53e3e }); const obstacleMesh = new THREE.Mesh(geometry, material); obstacleMesh.position.x = (Math.random() - 0.5) * 40; obstacleMesh.position.y = 20; obstacleMesh.castShadow = true; const boundingBox = new THREE.Box3(new THREE.Vector3(), new THREE.Vector3()); boundingBox.setFromObject(obstacleMesh); obstacles.push({ mesh: obstacleMesh, boundingBox: boundingBox }); scene.add(obstacleMesh); } // --- アニメーションループ --- function animate() { if (isGameOver) return; requestAnimationFrame(animate); playerBoundingBox.setFromObject(player); for (let i = obstacles.length - 1; i >= 0; i--) { const obstacle = obstacles[i]; obstacle.mesh.position.y -= 0.15; obstacle.boundingBox.setFromObject(obstacle.mesh); if (playerBoundingBox.intersectsBox(obstacle.boundingBox)) { endGame(); return; } if (obstacle.mesh.position.y < -10) { scene.remove(obstacle.mesh); obstacles.splice(i, 1); score++; updateScore(); } } renderer.render(scene, camera); } // --- ゲームオーバー処理 --- function endGame() { isGameOver = true; clearInterval(obstacleSpawnInterval); gameOverContainer.style.display = 'flex'; } // --- ゲームリスタート処理 --- function restartGame() { startGame(); } // --- プレイヤーの移動 --- function onPlayerMove(event) { if (isGameOver) return; const moveDistance = 1; switch (event.key) { case 'ArrowLeft': case 'a': player.position.x -= moveDistance; break; case 'ArrowRight': case 'd': player.position.x += moveDistance; break; } player.position.x = Math.max(-24, Math.min(24, player.position.x)); } // --- スコア更新 --- function updateScore() { scoreElement.textContent = `スコア: ${score}`; } // --- ウィンドウリサイズ処理 --- function onWindowResize() { // コンテナのサイズに基づいてカメラとレンダラーを更新 const width = gameContainer.clientWidth; const height = gameContainer.clientHeight; camera.aspect = width / height; camera.updateProjectionMatrix(); renderer.setSize(width, height); } // --- 実行 --- init(); </script> </body> </html>
