ブラウザで遊べるドライブゲーム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: 32px;
            pointer-events: none;
            animation: moveUpFade 0.8s ease-out forwards;
            white-space: nowrap;
        }
        @keyframes moveUpFade {
            0% { transform: translateY(0); opacity: 1; }
            100% { transform: translateY(-100px); 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.2) 100%);
            display: none;
            pointer-events: 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 = 8;

        let scene, camera, renderer, clock;
        let gameState = "TITLE";
        let score = 0, hp = MAX_HP, timeLeft = STAGE_TIME_LIMIT;
        let isBoost = false, boostTimer = 0;
        
        let player = { mesh: null, x: 0, y: 0, z: 0, vx: 0, vy: 0, vz: 0.5, rotZ: 0, rotX: 0 };
        let worldObjects = [];
        let particles = [];
        let keys = {};
        let nextSpawnZ = 50;

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

        function init() {
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x050505);
            scene.fog = new THREE.Fog(0x050505, 30, 160);

            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            const ambient = new THREE.AmbientLight(0xffffff, 0.6);
            scene.add(ambient);
            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.8, 3), new THREE.MeshPhongMaterial({ color: 0x00ccff }));
            scene.add(player.mesh);

            // 地面
            const ground = new THREE.Mesh(new THREE.PlaneGeometry(30, 10000), new THREE.MeshPhongMaterial({ color: 0x111111 }));
            ground.rotation.x = -Math.PI / 2;
            scene.add(ground);

            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 handleSpace() {
            if (gameState === "TITLE") { changeState("STAGE_START"); PlaySound("pageup.mp3"); }
            else if (gameState === "STAGE_START") { changeState("PLAYING"); PlaySound("pageup.mp3"); }
            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 TO START</p>";
            else if(s === "STAGE_START") { menu.innerHTML = "<h1>STAGE 1</h1><p>READY? PRESS SPACE</p>"; resetPlayer(); }
            else if(s === "PLAYING") menu.innerHTML = "";
        }

        function resetPlayer() {
            player.x = 0; player.y = 0; player.z = 0; player.vz = 0.5;
            hp = MAX_HP; score = 0;
        }

        // --- ポップアップテキスト表示関数 ---
        function showPopText(text, color = "white") {
            const div = document.createElement('div');
            div.className = 'pop-text';
            div.innerText = text;
            div.style.color = color;
            // 画面中央付近にランダムに配置
            div.style.left = (50 + (Math.random()*20-10)) + "%";
            div.style.top = (40 + (Math.random()*20-10)) + "%";
            document.getElementById('ui-layer').appendChild(div);
            setTimeout(() => div.remove(), 800);
        }

        function animate() {
            requestAnimationFrame(animate);
            if (gameState === "PLAYING") {
                updatePhysics();
                updateObjects();
                updateParticles();
            }
            updateCamera();
            renderer.render(scene, camera);
        }

        function updatePhysics() {
            if (keys['ArrowUp']) player.vz += 0.015;
            else if (keys['ArrowDown']) { player.vz -= 0.03; if(player.vz > 0.3) PlaySound("break.mp3"); }
            else player.vz *= 0.995;

            player.vz = Math.max(0.2, Math.min(player.vz, isBoost ? 2.5 : 1.3));

            if (keys['ArrowLeft']) { player.vx = THREE.MathUtils.lerp(player.vx, 0.4, 0.1); player.rotZ = THREE.MathUtils.lerp(player.rotZ, 0.4, 0.1); }
            else if (keys['ArrowRight']) { player.vx = THREE.MathUtils.lerp(player.vx, -0.4, 0.1); player.rotZ = THREE.MathUtils.lerp(player.rotZ, -0.4, 0.1); }
            else { player.vx = THREE.MathUtils.lerp(player.vx, 0, 0.1); player.rotZ = THREE.MathUtils.lerp(player.rotZ, 0, 0.1); }

            player.y += player.vy;
            if (player.y > 0) { player.vy -= 0.03; player.rotX = player.vy * 0.5; }
            else { if (player.y < 0) PlaySound("jump_down.mp3"); player.y = 0; player.vy = 0; player.rotX = 0; }

            player.x += player.vx;
            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() {
            camera.position.set(player.x * 0.5, player.y + 5, player.z - 12);
            camera.lookAt(new THREE.Vector3(player.x, player.y + 1, player.z + 10));
            camera.rotation.z += player.rotZ * 0.3;
        }

        function updateObjects() {
            if (player.z + 150 > nextSpawnZ) { spawnObject(nextSpawnZ); nextSpawnZ += 12; }
            worldObjects.forEach((obj, idx) => {
                if (player.mesh.position.distanceTo(obj.mesh.position) < 2.5) {
                    handleCollision(obj);
                    scene.remove(obj.mesh);
                    worldObjects.splice(idx, 1);
                }
                if (obj.mesh.position.z < player.z - 20) { 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 });
        }

        function handleCollision(obj) {
            if (obj.type === "block") {
                score += 10; PlaySound("block.mp3");
                showPopText("+10", "#ffaa00");
                createExplosion(obj.mesh.position, 0xffaa00, 15);
            } else if (obj.type === "hurdle") {
                if (isBoost) {
                    score += 50; PlaySound("block.mp3");
                    showPopText("+50", "#ff3300");
                    createExplosion(obj.mesh.position, 0xff3300, 20);
                } else {
                    hp--; PlaySound("hurdle.mp3");
                    showPopText("HIT! HP-1", "#ffffff");
                    createExplosion(obj.mesh.position, 0xffffff, 10);
                }
            } else if (obj.type === "score") {
                score += 100; PlaySound("heal.mp3");
                showPopText("+100", "#ffff00");
            } else if (obj.type === "boost") {
                isBoost = true; boostTimer = 300;
                PlaySound("boost.mp3");
                showPopText("BOOST!", "#00ffff");
            } else if (obj.type === "heal") {
                hp = Math.min(MAX_HP, hp + 1);
                PlaySound("heal.mp3");
                showPopText("HP+1", "#00ff00");
            }
        }

        function createExplosion(pos, color, count) {
            for (let i = 0; i < count; i++) {
                const p = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.4, 0.4), 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);
            
            // 集中線の表示切り替え
            const speedLines = document.getElementById('speed-lines');
            if (isBoost) {
                speedLines.style.display = 'block';
                if (--boostTimer <= 0) isBoost = false;
            } else {
                speedLines.style.display = '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