ブラウザで遊べるドライブゲームImageBooster

【操作説明】

・左右キーで自機を左右に移動。
・上キーで加速。
・下キーで減速。
・スペースキーでジャンプ。

・障害物を避けて、高得点を目指しましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Image Booster 1.0</title>
    <style>
        body { margin: 0; overflow: hidden; background: #000; font-family: 'Arial Black', sans-serif; }
        #ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; color: white; text-shadow: 2px 2px 4px #000; }
        #hud { position: absolute; top: 20px; left: 20px; text-align: left; display: none; font-size: 24px; }
        #center-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; text-align: center; }
        h1 { font-size: 60px; margin: 0; color: #0ff; }
        .pop-text { position: absolute; font-weight: bold; font-size: 38px; animation: moveUpFade 0.8s ease-out forwards; white-space: nowrap; transform: translateX(-50%); }
        @keyframes moveUpFade { 0% { transform: translate(-50%, 0); opacity: 1; } 100% { transform: translate(-50%, -120px); opacity: 0; } }
        #speed-lines { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle, transparent 30%, rgba(255,255,255,0.3) 100%); display: none; mask-image: repeating-conic-gradient(from 0deg, #000 0deg 1deg, transparent 1deg 10deg); -webkit-mask-image: repeating-conic-gradient(from 0deg, #000 0deg 1deg, transparent 1deg 10deg); }
    </style>
    <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js" } } </script>
</head>
<body>
    <div id="ui-layer">
        <div id="speed-lines"></div>
        <div id="hud">
            <div id="hp-disp">HP: 10</div>
            <div id="score-disp">SCORE: 0</div>
            <div id="time-disp">TIME: 180.00</div>
        </div>
        <div id="center-text"></div>
    </div>

    <script type="module">
        import * as THREE from 'three';

        const MAX_HP = 10;
        const STAGE_TIME_LIMIT = 180;
        const LANE_WIDTH = 12;

        let scene, camera, renderer, clock, cameraHolder;
        let gameState = "TITLE";
        let score = 0, hp = MAX_HP, timeLeft = STAGE_TIME_LIMIT;
        let isBoost = false; // 今回の仕様:上キーを離すまで継続
        
        let player = { mesh: null, x: 0, y: 0, z: 0, vx: 0, vy: 0, vz: 0.2, rotZ: 0, rotX: 0 };
        let worldObjects = [], particles = [], stars;
        let keys = {};
        let nextSpawnZ = 50;

        function PlaySound(filename) { new Audio(`./Sound/${filename}`).play().catch(()=>{}); }

        function init() {
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x000005);
            scene.fog = new THREE.Fog(0x000005, 50, 250);

            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            // カメラ構造の改善 (逆さま防止)
            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            cameraHolder = new THREE.Group();
            cameraHolder.add(camera);
            scene.add(cameraHolder);

            scene.add(new THREE.AmbientLight(0xffffff, 0.5));
            const sun = new THREE.DirectionalLight(0xffffff, 1.2);
            sun.position.set(10, 20, 10);
            scene.add(sun);

            player.mesh = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.6, 2.5), new THREE.MeshPhongMaterial({ color: 0x00aaff }));
            scene.add(player.mesh);

            const groundTex = new THREE.TextureLoader().load('./BgImage/ground.png', (tex)=>{
                tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
                tex.repeat.set(5, 1000);
            });
            const ground = new THREE.Mesh(new THREE.PlaneGeometry(30, 10000), new THREE.MeshPhongMaterial({ map: groundTex, color: 0x222222 }));
            ground.rotation.x = -Math.PI / 2;
            scene.add(ground);

            createStarfield();

            window.addEventListener('keydown', (e) => { keys[e.code] = true; if(e.code === 'Space') handleSpace(); });
            window.addEventListener('keyup', (e) => keys[e.code] = false);
            
            clock = new THREE.Clock();
            changeState("TITLE");
            animate();
        }

        function createStarfield() {
            const starCoords = [];
            for(let i=0; i<2000; i++) starCoords.push((Math.random()-0.5)*500, (Math.random()-0.5)*500, Math.random()*500);
            const starGeo = new THREE.BufferGeometry().setAttribute('position', new THREE.Float32BufferAttribute(starCoords, 3));
            stars = new THREE.Points(starGeo, new THREE.PointsMaterial({ color: 0xffffff, size: 0.8 }));
            scene.add(stars);
        }

        function handleSpace() {
            if (gameState === "TITLE") changeState("STAGE_START");
            else if (gameState === "STAGE_START") changeState("PLAYING");
            else if (gameState === "PLAYING" && player.y <= 0.01) { player.vy = 0.6; PlaySound("jump_up.mp3"); }
        }

        function changeState(s) {
            gameState = s;
            document.getElementById('hud').style.display = (s === "PLAYING") ? "block" : "none";
            const menu = document.getElementById('center-text');
            if(s === "TITLE") menu.innerHTML = "<h1>Image Booster 1.0</h1><p>PRESS SPACE</p>";
            else if(s === "STAGE_START") { menu.innerHTML = "<h1>STAGE 1</h1><p>READY?</p>"; resetPlayer(); }
            else if(s === "PLAYING") menu.innerHTML = "";
        }

        function resetPlayer() {
            player.x = 0; player.y = 0; player.z = 0; player.vz = 0.2; player.rotZ = 0;
            hp = MAX_HP; score = 0; isBoost = false;
        }

        function showPopText(text, color) {
            const div = document.createElement('div');
            div.className = 'pop-text';
            div.innerText = text;
            div.style.color = color;
            div.style.left = "50%"; div.style.top = "40%";
            document.getElementById('ui-layer').appendChild(div);
            setTimeout(() => div.remove(), 800);
        }

        function animate() {
            requestAnimationFrame(animate);
            if (gameState === "PLAYING") {
                updatePhysics();
                updateObjects();
                updateParticles();
                stars.position.z = -(player.z % 500);
            }
            updateCamera();
            renderer.render(scene, camera);
        }

        function updatePhysics() {
            // 加速・減速ロジック
            if (keys['ArrowUp']) {
                player.vz += (isBoost) ? 0.06 : 0.02; // ブースト中は超加速
            } else {
                if (isBoost) isBoost = false; // 上キーを離すとブースト終了
                player.vz *= 0.992; // 緩やかな減速
            }

            if (keys['ArrowDown']) {
                player.vz *= 0.92; // 急ブレーキ
                if(player.vz > 0.3) PlaySound("break.mp3");
            }

            let maxSpd = isBoost ? 4.5 : 1.5;
            player.vz = Math.max(0.1, Math.min(player.vz, maxSpd));

            // 左右
            if (keys['ArrowLeft']) { player.vx = THREE.MathUtils.lerp(player.vx, 0.4, 0.1); player.rotZ += 0.15; }
            else if (keys['ArrowRight']) { player.vx = THREE.MathUtils.lerp(player.vx, -0.4, 0.1); player.rotZ -= 0.15; }
            else {
                player.vx = THREE.MathUtils.lerp(player.vx, 0, 0.1);
                player.rotZ = THREE.MathUtils.lerp(player.rotZ, Math.round(player.rotZ/(Math.PI*2))*(Math.PI*2), 0.1);
            }

            player.x = Math.max(-LANE_WIDTH, Math.min(LANE_WIDTH, player.x + player.vx));
            player.y += player.vy;
            if (player.y > 0) { player.vy -= 0.03; player.rotX = player.vy * 0.5; }
            else { player.y = 0; player.vy = 0; player.rotX = 0; }

            player.z += player.vz;
            player.mesh.position.set(player.x, player.y + 0.5, player.z);
            player.mesh.rotation.z = player.rotZ;
            player.mesh.rotation.x = -player.rotX;

            timeLeft -= 1/60;
            updateUI();
        }

        function updateCamera() {
            // ホルダーをプレイヤーの位置に
            cameraHolder.position.set(player.x * 0.7, player.y + 5, player.z - 12);
            // プレイヤーを注視
            camera.lookAt(player.x, player.y + 1, player.z + 10);
            // 左右の傾き(ホルダーを回すことで逆さまを防止)
            cameraHolder.rotation.z = player.rotZ * 0.2;
        }

        function updateObjects() {
            if (player.z + 150 > nextSpawnZ) { spawnObject(nextSpawnZ); nextSpawnZ += 12; }
            worldObjects.forEach((obj, idx) => {
                if(obj.flying) {
                    obj.mesh.position.add(obj.vel);
                    obj.mesh.rotation.x += 0.2; obj.vel.y -= 0.01;
                } else if (player.mesh.position.distanceTo(obj.mesh.position) < 2.5) {
                    handleCollision(obj);
                }
                if (obj.mesh.position.z < player.z - 30 || obj.mesh.position.y < -10) {
                    scene.remove(obj.mesh); worldObjects.splice(idx, 1);
                }
            });
        }

        function spawnObject(z) {
            const types = ["block", "hurdle", "score", "boost", "heal"];
            const type = types[Math.floor(Math.random() * types.length)];
            const colors = { block: 0x884422, hurdle: 0xff3300, score: 0xffff00, boost: 0x00ffff, heal: 0x00ff00 };
            const mesh = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), new THREE.MeshPhongMaterial({ color: colors[type] }));
            mesh.position.set((Math.random() - 0.5) * LANE_WIDTH * 2, 1, z);
            scene.add(mesh);
            worldObjects.push({ mesh, type, flying: false, vel: new THREE.Vector3() });
        }

        function handleCollision(obj) {
            if (obj.type === "block") {
                score += 10; PlaySound("block.mp3"); showPopText("+10", "#ffaa00");
                createExplosion(obj.mesh.position, 0xffaa00, 15);
                obj.flying = true; obj.vel.set((Math.random()-0.5)*1, 0.5, 2.0);
            } else if (obj.type === "hurdle") {
                if (isBoost) {
                    score += 50; PlaySound("block.mp3"); showPopText("+50", "#ff3300");
                    createExplosion(obj.mesh.position, 0xff3300, 20);
                    obj.flying = true; obj.vel.set(2, 0.5, 3.0); // ブースト時は激しく吹っ飛ぶ
                } else {
                    hp--; PlaySound("hurdle.mp3"); showPopText("HP-1", "#ff69b4");
                    createExplosion(obj.mesh.position, 0xffffff, 10);
                    obj.flying = true; obj.vel.set(0.5, 0.1, 0.5);
                }
            } else if (obj.type === "score") {
                score += 100; PlaySound("heal.mp3"); showPopText("+100", "#ffff00");
                scene.remove(obj.mesh);
            } else if (obj.type === "boost") {
                isBoost = true; PlaySound("boost.mp3"); showPopText("SUPER BOOST!", "#00ffff");
                scene.remove(obj.mesh);
            } else if (obj.type === "heal") {
                hp = Math.min(MAX_HP, hp + 1); showPopText("HP+1", "#00ff00");
                scene.remove(obj.mesh);
            }
        }

        function createExplosion(pos, color, count) {
            for (let i = 0; i < count; i++) {
                const p = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.3, 0.3), new THREE.MeshBasicMaterial({ color: color }));
                p.position.copy(pos);
                const vel = new THREE.Vector3((Math.random()-0.5)*0.8, Math.random()*0.8, (Math.random()-0.5)*0.8);
                scene.add(p);
                particles.push({ mesh: p, vel: vel, life: 40 });
            }
        }

        function updateParticles() {
            for (let i = particles.length - 1; i >= 0; i--) {
                const p = particles[i]; p.mesh.position.add(p.vel); p.vel.y -= 0.02;
                if (--p.life <= 0) { scene.remove(p.mesh); particles.splice(i, 1); }
            }
        }

        function updateUI() {
            document.getElementById('hp-disp').innerText = "HP: " + hp;
            document.getElementById('score-disp').innerText = "SCORE: " + score;
            document.getElementById('time-disp').innerText = "TIME: " + Math.max(0, timeLeft).toFixed(2);
            document.getElementById('speed-lines').style.display = isBoost ? 'block' : 'none';
        }

        init();
    </script>
</body>
</html>

いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
買うたび 抽選 ※条件・上限あり \note クリエイター感謝祭ポイントバックキャンペーン/最大全額もどってくる! 12.1 月〜1.14 水 まで
ブラウザで遊べるドライブゲームImageBooster|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1