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


画像

・テキストファイルを新規作成して、(index.html)
 以下のソースコードをコピペして保存し、ファイルをクリックすると、
 webブラウザが起動してゲームが遊べます。↓

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Image Booster 2.1 - Glass Road</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; z-index: 10; }
        
        #view-mode { position: absolute; top: 20px; right: 20px; font-size: 20px; color: #0f0; }
        #stage-info { position: absolute; top: 20px; left: 20px; font-size: 20px; color: #ff0; }

        /* 集中線エフェクト(加速時のみ表示) */
        #speed-lines {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background: repeating-conic-gradient(transparent 0deg, transparent 2deg, rgba(255, 255, 255, 0.1) 2.1deg, transparent 2.5deg);
            opacity: 0;
            transition: opacity 0.2s ease-out;
            pointer-events: none;
            z-index: 5;
            mix-blend-mode: screen;
        }

        #hud-bottom { position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%); width: 85%; display: none; flex-direction: row; justify-content: space-around; }
        .gauge-container { width: 30%; text-align: center; }
        .gauge-label { font-size: 18px; margin-bottom: 5px; }
        .bar-bg { width: 100%; height: 12px; border: 2px solid #fff; border-radius: 6px; overflow: hidden; }
        .bar-fill { height: 100%; width: 100%; transition: width 0.1s ease-out; }

        #hp-bg { background: #ff0000; }
        #hp-fill { background: #adff2f; }
        #speed-bg { background: #000080; }
        #speed-fill { background: #00ffff; }
        #time-bg { background: #b8860b; }
        #time-fill { background: #ffff00; }

        #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 1s forwards; transform: translate(-50%, -50%); z-index: 999; }
        @keyframes moveUpFade { 0% { transform: translate(-50%, 0); opacity: 1; } 100% { transform: translate(-50%, -150px); opacity: 0; } }
    </style>
    <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js" } } </script>
</head>
<body>
    <div id="speed-lines"></div>
    <div id="ui-layer">
        <div id="stage-info">STAGE: 1 / 8</div>
        <div id="view-mode">VIEW: 3RD PERSON</div>
        <div id="hud-bottom">
            <div class="gauge-container">
                <div class="gauge-label" id="hp-txt">HP: 10</div>
                <div class="bar-bg" id="hp-bg"><div id="hp-fill" class="bar-fill"></div></div>
            </div>
            <div class="gauge-container">
                <div class="gauge-label" id="speed-txt">SPEED: 0</div>
                <div class="bar-bg" id="speed-bg"><div id="speed-fill" class="bar-fill"></div></div>
            </div>
            <div class="gauge-container">
                <div class="gauge-label" id="time-txt">TIME: 60</div>
                <div class="bar-bg" id="time-bg"><div id="time-fill" class="bar-fill"></div></div>
            </div>
        </div>
        <div id="center-text"></div>
    </div>

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

        const MAX_HP = 10, TUBE_R = 15, PLANE_W = 12, MAX_TIME = 60, MAX_SPEED = 2.5;
        const TOTAL_STAGES = 8;
        
        let scene, camera, renderer;
        let gameState = "TITLE", viewMode = "F3"; 
        let score = 0, hp = MAX_HP, timeLeft = MAX_TIME;
        let currentStage = 1;
        
        // surge: 加速時の前後スライド量, bank: 旋回時の傾き
        let player = { 
            mesh: null, angle: 0, x: 0, z: 0, 
            vz: 0.2, vAngle: 0, vx: 0, 
            altitude: 0, jumpV: 0,
            surge: 0, bank: 0 
        };
        
        let worldObjects = [], debris = [], stars, groundTube, groundPlane;
        let keys = {};
        let nextSpawnZ = 50;
        let tubeMaterial; // スクロール用に保持

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

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

            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
            scene.add(new THREE.AmbientLight(0xffffff, 0.6));
            const sun = new THREE.DirectionalLight(0xffffff, 1.0);
            sun.position.set(0, 20, 10);
            scene.add(sun);

            createStars();

            // プレイヤー
            player.mesh = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.6, 2.5), new THREE.MeshPhongMaterial({ color: 0x00aaff }));
            scene.add(player.mesh);

            // ガラスの床(チューブ)
            // グリッドテクスチャをCanvasで動的生成
            const canvas = document.createElement('canvas');
            canvas.width = 512; canvas.height = 512;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = '#000000'; ctx.fillRect(0,0,512,512);
            ctx.strokeStyle = '#00ffff'; ctx.lineWidth = 4;
            // グリッドを描画
            ctx.beginPath();
            for(let i=0; i<=512; i+=64) {
                ctx.moveTo(i, 0); ctx.lineTo(i, 512);
                ctx.moveTo(0, i); ctx.lineTo(512, i);
            }
            ctx.stroke();

            const gridTex = new THREE.CanvasTexture(canvas);
            gridTex.wrapS = THREE.RepeatWrapping;
            gridTex.wrapT = THREE.RepeatWrapping;
            gridTex.repeat.set(12, 40); // チューブ全周と奥行きの繰り返し数

            // 半透明マテリアル
            tubeMaterial = new THREE.MeshPhongMaterial({ 
                color: 0x444455, 
                map: gridTex,
                side: THREE.BackSide, 
                transparent: true, 
                opacity: 0.6,
                shininess: 100
            });

            groundTube = new THREE.Mesh(
                new THREE.CylinderGeometry(TUBE_R, TUBE_R, 1000, 32, 1, true), // 長さは短くして追従させる手もあるが、今回は固定長でテクスチャスクロール
                tubeMaterial
            );
            groundTube.rotation.x = Math.PI / 2;
            // チューブ自体をカメラについてこさせるために、Z方向のスケールを大きく、または位置更新
            // ここではシンプルに超巨大円柱にしておく(前回の仕様踏襲)
            groundTube.geometry = new THREE.CylinderGeometry(TUBE_R, TUBE_R, 200000, 32, 1, true);
            scene.add(groundTube);

            // 平面モード用(F2)
            groundPlane = new THREE.Mesh(new THREE.PlaneGeometry(100, 200000), new THREE.MeshPhongMaterial({ color: 0x111111 }));
            groundPlane.rotation.x = -Math.PI / 2;
            groundPlane.visible = false;
            scene.add(groundPlane);

            window.addEventListener('keydown', (e) => {
                if(["F1", "F2", "F3"].includes(e.key) || ["F1", "F2", "F3"].includes(e.code)) {
                    e.preventDefault();
                    viewMode = e.code;
                    updateViewLabel();
                }
                keys[e.code] = true;
                if(e.code === 'Space') {
                    e.preventDefault();
                    handleSpace();
                }
            });
            window.addEventListener('keyup', (e) => keys[e.code] = false);
            
            updateViewLabel();
            changeState("TITLE");
            animate();
        }

        function createStars() {
            const starGeo = new THREE.BufferGeometry();
            const starCount = 2000;
            const posArray = new Float32Array(starCount * 3);
            for(let i=0; i<starCount * 3; i++) {
                posArray[i] = (Math.random() - 0.5) * 600; 
            }
            starGeo.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
            const starMat = new THREE.PointsMaterial({color: 0xffffff, size: 0.8, transparent: true});
            stars = new THREE.Points(starGeo, starMat);
            scene.add(stars);
        }

        function updateStars() {
            if(stars) {
                stars.position.z = player.z;
                stars.rotation.z += 0.001; 
            }
            // 床のテクスチャスクロール(疾走感)
            if(tubeMaterial) {
                // UVのY座標をずらすことでスクロール表現
                tubeMaterial.map.offset.y = -player.z * 0.02; 
            }
        }

        function updateViewLabel() {
            const labels = { "F1": "1ST PERSON", "F2": "PLANE MODE", "F3": "3RD PERSON" };
            document.getElementById('view-mode').innerText = "VIEW: " + labels[viewMode];
            groundTube.visible = (viewMode !== "F2");
            groundPlane.visible = (viewMode === "F2");
        }

        function handleSpace() {
            if (gameState === "TITLE" || gameState === "GAMEOVER" || gameState === "ALL_CLEAR") {
                score = 0; 
                currentStage = 1;
                changeState("STAGE_START");
            }
            else if (gameState === "STAGE_CLEAR") changeState("STAGE_START");
            else if (gameState === "STAGE_START") changeState("PLAYING");
            else if (gameState === "PLAYING" && player.altitude <= 0.01) {
                // ジャンプ力アップ(障害物飛び越え用)
                player.jumpV = 0.75; 
            }
        }

        function changeState(s) {
            gameState = s;
            document.getElementById('hud-bottom').style.display = (s === "PLAYING") ? "flex" : "none";
            const menu = document.getElementById('center-text');
            document.getElementById('stage-info').innerText = `STAGE: ${currentStage} / ${TOTAL_STAGES}`;
            
            // 集中線リセット
            document.getElementById('speed-lines').style.opacity = 0;

            if(s === "TITLE") menu.innerHTML = "<h1>Image Booster 2.1</h1><p>PRESS SPACE TO START</p>";
            else if(s === "STAGE_START") { 
                menu.innerHTML = "<h1>STAGE "+currentStage+"</h1><p>SPACE: GO!</p>"; 
                resetPlayerPos(); 
            }
            else if(s === "PLAYING") menu.innerHTML = "";
            else if(s === "STAGE_CLEAR") {
                if (currentStage >= TOTAL_STAGES) {
                    changeState("ALL_CLEAR");
                } else {
                    menu.innerHTML = "<h1 style='color:#0f0;'>STAGE CLEAR!</h1><p>SCORE: "+score+"</p><p>SPACE FOR NEXT STAGE</p>";
                    currentStage++;
                }
            }
            else if(s === "ALL_CLEAR") {
                menu.innerHTML = "<h1 style='color:#0ff;'>ALL CLEARED!</h1><p>FINAL SCORE: "+score+"</p><p>THANK YOU FOR PLAYING</p><p>SPACE TO TITLE</p>";
            }
            else if(s === "GAMEOVER") menu.innerHTML = "<h1 style='color:red;'>GAME OVER</h1><p>SCORE: "+score+"</p><p>SPACE TO RESTART</p>";
        }

        function resetPlayerPos() {
            player.angle = 0; player.x = 0; player.z = 0; player.vz = 0.5; player.altitude = 0; player.jumpV = 0;
            player.surge = 0; player.bank = 0;
            
            if(gameState === "TITLE" || gameState === "GAMEOVER") { hp = MAX_HP; }
            timeLeft = MAX_TIME;
            
            worldObjects.forEach(obj => scene.remove(obj.mesh));
            debris.forEach(d => scene.remove(d));
            worldObjects = [];
            debris = [];
            nextSpawnZ = 50;
        }

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

        function updatePhysics() {
            // 加速・減速処理
            if (keys['ArrowUp']) {
                player.vz += 0.04;
                // 加速中は集中線ON
                document.getElementById('speed-lines').style.opacity = 0.6;
                // 前方へスライド(surge)
                player.surge = THREE.MathUtils.lerp(player.surge, 2.0, 0.05);
            } else {
                player.vz *= 0.995;
                // 減速時は集中線OFF
                document.getElementById('speed-lines').style.opacity = 0;
                
                if (keys['ArrowDown']) {
                    player.vz *= 0.90;
                    // ブレーキ時は後退
                    player.surge = THREE.MathUtils.lerp(player.surge, -1.5, 0.05);
                } else {
                    // 何も押してない時は定位置へ戻る
                    player.surge = THREE.MathUtils.lerp(player.surge, 0, 0.05);
                }
            }
            player.vz = Math.max(0.1, Math.min(player.vz, MAX_SPEED));

            // ジャンプ重力
            player.altitude += player.jumpV;
            if (player.altitude > 0) player.jumpV -= 0.025; // 重力少し強め
            else { player.altitude = 0; player.jumpV = 0; }

            // 左右移動とバンク角
            let targetBank = 0;
            if (viewMode === "F2") {
                if (keys['ArrowLeft']) { player.vx = THREE.MathUtils.lerp(player.vx, 0.7, 0.1); targetBank = 0.5; }
                else if (keys['ArrowRight']) { player.vx = THREE.MathUtils.lerp(player.vx, -0.7, 0.1); targetBank = -0.5; }
                else player.vx *= 0.9;
                
                player.x = Math.max(-PLANE_W, Math.min(PLANE_W, player.x + player.vx));
                player.bank = THREE.MathUtils.lerp(player.bank, targetBank, 0.1);

                player.mesh.position.set(player.x, 0.5 + player.altitude, player.z + player.surge);
                player.mesh.rotation.set(0, 0, (player.vx * 0.3) + player.bank);

            } else {
                if (keys['ArrowLeft']) { player.vAngle = THREE.MathUtils.lerp(player.vAngle, 0.08, 0.1); targetBank = 0.8; }
                else if (keys['ArrowRight']) { player.vAngle = THREE.MathUtils.lerp(player.vAngle, -0.08, 0.1); targetBank = -0.8; }
                else player.vAngle *= 0.9;
                
                player.angle += player.vAngle;
                player.bank = THREE.MathUtils.lerp(player.bank, targetBank, 0.1);
                
                const r = TUBE_R - 0.5 - player.altitude;
                
                // チューブ座標計算
                player.mesh.position.set(Math.sin(player.angle)*r, -Math.cos(player.angle)*r, player.z + player.surge);
                
                // 回転:チューブに沿った角度(angle) + 左右移動時の傾き(bank)
                // チューブの内側に張り付くので、Z回転は angle が基本。そこに bank を足す。
                // 左入力(vAngle>0) -> 左翼を下げるには、Z軸回転を増やす方向 (時計回りが-Zなら...Three.jsは反時計が+)
                // 試行錯誤調整: angle + bank
                player.mesh.rotation.set(0, 0, player.angle + player.bank);
            }
            
            player.z += player.vz;
            timeLeft -= 1/60;
            if (timeLeft <= 0) changeState("STAGE_CLEAR");
            if (hp <= 0) changeState("GAMEOVER");

            updateUI();
        }

        function updateCamera() {
            player.mesh.visible = (viewMode !== "F1");
            // カメラは player.z (論理位置) を追うが、meshは player.z + player.surge にある。
            // これにより加速時に機体がカメラから離れ、減速時に近づく演出になる。
            
            if (viewMode === "F1") { 
                camera.position.copy(player.mesh.position);
                camera.up.set(-player.mesh.position.x, -player.mesh.position.y, 0).normalize();
                camera.lookAt(player.mesh.position.x, player.mesh.position.y, player.z + 100);
            } else if (viewMode === "F2") { 
                camera.up.set(0, 1, 0);
                camera.position.set(player.x, 6, player.z - 15);
                camera.lookAt(player.x, 1, player.z + 30);
            } else { 
                const camDist = 18, camHeight = 5;
                // カメラ位置計算用のダミー座標(機体のバンクやサージの影響を受けない中心点)
                const coreX = Math.sin(player.angle) * (TUBE_R - 0.5);
                const coreY = -Math.cos(player.angle) * (TUBE_R - 0.5);

                camera.position.x = coreX - Math.sin(player.angle) * camHeight;
                camera.position.y = coreY + Math.cos(player.angle) * camHeight;
                camera.position.z = player.z - camDist;
                camera.up.set(-coreX, -coreY, 0).normalize();
                camera.lookAt(coreX, coreY, player.z + 20);
            }
        }

        function updateObjects() {
            // 密度2倍:出現間隔を半分にする
            let spawnDist = Math.max(10, 25 - (currentStage * 2.5)); 
            
            if (player.z + 250 > nextSpawnZ) { 
                spawnObject(nextSpawnZ); 
                nextSpawnZ += Math.random() * 5 + (spawnDist / 2); // 間隔も狭く
            }

            for (let i = worldObjects.length - 1; i >= 0; i--) {
                const obj = worldObjects[i];
                if(obj.flying) {
                    obj.mesh.position.add(obj.vel);
                    obj.mesh.rotation.x += 0.1;
                    obj.mesh.rotation.y += 0.1;
                } else if (player.mesh.position.distanceTo(obj.mesh.position) < (obj.type === 'hurdle' ? 4.0 : 2.5)) { // 判定少し甘めに
                    handleCollision(obj, i);
                    if(!obj.flying && obj.type !== "block" && obj.type !== "hurdle") {
                        scene.remove(obj.mesh);
                        worldObjects.splice(i, 1);
                        continue;
                    }
                }
                if (obj.mesh.position.z < player.z - 50) {
                    scene.remove(obj.mesh);
                    worldObjects.splice(i, 1);
                }
            }
        }

        function spawnObject(z) {
            let hurdleRate = Math.min(0.4, 0.1 + (currentStage * 0.05));
            let type = (Math.random() < 0.1) ? "score" : (Math.random() < 0.05) ? "heal" : "block";
            if (Math.random() < hurdleRate) type = "hurdle";

            const colors = { block: 0x884422, hurdle: 0xff3300, score: 0xffff00, heal: 0x00ff00 };
            let geo;
            if(type === 'hurdle') geo = new THREE.BoxGeometry(4, 4, 4);
            else if(type === 'score' || type === 'heal') geo = new THREE.BoxGeometry(1, 1, 1);
            else geo = new THREE.BoxGeometry(2, 2, 2);

            const mesh = new THREE.Mesh(geo, new THREE.MeshPhongMaterial({ color: colors[type] }));
            
            // 一度に登場するブロック数を倍にする(2個同時沸き確率を追加)
            // ただし単純にループでspawnObjectを呼ぶと再帰するので、
            // ここでは「1回のspawnObject呼び出しで、追加でもう1個配置する」処理を入れるか、
            // updateObjectsの呼び出し頻度で調整済みとする。
            // 今回は updateObjects で spawnDist を半分にしたので、実質密度は倍になっている。

            if (viewMode === "F2") mesh.position.set((Math.random()-0.5)*PLANE_W*2, (type==='hurdle'?2:1), z);
            else {
                const a = Math.random()*Math.PI*2;
                const offset = (type==='hurdle') ? 2 : (type==='block' ? 1 : 0.5);
                mesh.position.set(Math.sin(a)*(TUBE_R - offset), -Math.cos(a)*(TUBE_R - offset), z);
                mesh.rotation.z = a;
            }
            scene.add(mesh);
            worldObjects.push({ mesh, type, flying: false, vel: new THREE.Vector3() });
        }

        function handleCollision(obj, index) {
            const isMaxSpeed = player.vz >= (MAX_SPEED - 0.2);

            if (obj.type === "block") {
                if (isMaxSpeed) {
                    createExplosion(obj.mesh.position, obj.mesh.material.color, 1);
                    scene.remove(obj.mesh);
                    worldObjects.splice(index, 1);
                    score += 20; showPopText("BREAK! +20", "#ffaa00");
                } else {
                    obj.flying = true;
                    obj.vel.set((Math.random()-0.5)*2, 2, 3);
                    score += 10; showPopText("+10", "#ffaa00");
                }
            } else if (obj.type === "hurdle") {
                // 障害物の新ルール
                if (isMaxSpeed) {
                    // 最高速ならHP-1(スコアなし)
                    hp -= 1; 
                    showPopText("OUCH! HP -1", "#ff5500");
                    // エフェクトとして弾く
                    obj.flying = true;
                    obj.vel.set((Math.random()-0.5)*2, 2, 3);
                    player.vz *= 0.8; // 少し減速
                } else {
                    // 通常ならHP-2
                    hp -= 2; 
                    showPopText("CRASH! HP -2", "#ff0000");
                    obj.flying = true;
                    obj.vel.set(0, 1, 1);
                    player.vz *= 0.5; // 大きく減速
                }
            } 
            else if (obj.type === "score") { score += 100; showPopText("+100", "#ffff00"); }
            else if (obj.type === "heal") { hp = Math.min(MAX_HP, hp + 1); showPopText("HP +1", "#00ff00"); }
        }

        function createExplosion(pos, color, scaleFactor) {
            const count = 8 * scaleFactor;
            const size = 0.5 * scaleFactor;
            for(let i=0; i<count; i++) {
                const geo = new THREE.BoxGeometry(size, size, size);
                const mat = new THREE.MeshPhongMaterial({ color: color });
                const mesh = new THREE.Mesh(geo, mat);
                mesh.position.copy(pos);
                mesh.position.x += (Math.random()-0.5)*2;
                mesh.position.y += (Math.random()-0.5)*2;
                scene.add(mesh);
                const vel = new THREE.Vector3((Math.random()-0.5)*1, Math.random()*1, 2.0+Math.random()*2);
                debris.push({ mesh, vel, life: 60 });
            }
        }

        function updateDebris() {
            for(let i = debris.length - 1; i >= 0; i--) {
                const d = debris[i];
                d.mesh.position.add(d.vel);
                d.mesh.rotation.x += 0.2; d.mesh.rotation.z += 0.2;
                d.life--;
                if(d.life <= 0) {
                    scene.remove(d.mesh);
                    debris.splice(i, 1);
                }
            }
        }

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

        function updateUI() {
            document.getElementById('hp-txt').innerText = "HP: " + hp;
            document.getElementById('hp-fill').style.width = Math.max(0, (hp / MAX_HP * 100)) + "%";
            
            document.getElementById('speed-txt').innerText = "SPEED: " + (player.vz * 40).toFixed(0);
            document.getElementById('speed-fill').style.width = (player.vz / MAX_SPEED * 100) + "%";
            if(player.vz >= MAX_SPEED - 0.1) document.getElementById('speed-fill').style.backgroundColor = "#fff";
            else document.getElementById('speed-fill').style.backgroundColor = "#00ffff";

            document.getElementById('time-txt').innerText = "TIME: " + Math.max(0, timeLeft).toFixed(0);
            document.getElementById('time-fill').style.width = (timeLeft / MAX_TIME * 100) + "%";
        }

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

【操作説明】

・左右キー … 自機を左右に移動。
・上キー … アクセル。
・下キー … ブレーキ。
・スペースキー … ジャンプ。

・F1キー … チューブモードの一人称視点に切り替え。
・F2キー … 平面モードに切り替え。
・F3キー … チューブモードの三人称視点に切り替え。

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

【おまけ】

・チューブ系のゲームを検索してみたところ、
 やはりいくつかあったので紹介しておきます。

1981 Atari Tempest Arcade 

・これはレースゲームというか、シューティングみたいな感じですね。

1983 ジャイラス コナミ

・これもシューティングゲームで、やや滑らかに回る感じ。

Bullfrog - Tube - 1995

・かなりレースゲームっぽい感じになってきました。

Ballistics (2001) - PC

これは障害物を避けていくパターンで、一応、レースゲームなのかな?

Tube Slider (2003) GameCube

・これは任天堂のレースゲームで、
 「F-ZERO」の後継シリーズとして開発されていたとのこと。

Space Giraffe(2007)Llamasoft

・これはまたシューティングゲームで、テンペストっぽい感じ。

「Race The Sun」Flippfly (2013)

・これはチューブ系ではないものの、平面モードはわりと近いかも。

「Thumper」Drool(2016)

・これはリズム系のゲームで、
 タイミングを合わせると障害物を回避できる。

「Tunnel Rush」(2024?)

・これも障害物を避けまくるゲーム。

とまあ、似たような雰囲気のゲームは
これまでにもあったみたいなんですが、
ぶっ壊しに主軸をおいたゲームでは無かったみたいなので、
まあまあセーフだったりするのかも知れない。(^^;

3Dのこういう真正面を向いたゲームは、
合わせるのが難しいので、
気軽に遊べるのがいいのではないだろうか。uu

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

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
買うたび 抽選 ※条件・上限あり \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