🔬 AI発明ギャラリー kizu ボール投げゲーム
🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
HTML / CSS / JS
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>3D日本庭園:大鬼決戦</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <style>
        body { margin: 0; overflow: hidden; background: #000; touch-action: none; user-select: none; }
        #ui-layer {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            pointer-events: none; display: none; flex-direction: column;
            justify-content: space-between; padding: 20px;
            color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
            font-family: 'serif';
        }
        .gauge-container {
            width: 200px; height: 15px; background: rgba(0,0,0,0.5);
            border-radius: 10px; overflow: hidden; border: 2px solid #d4af37;
            box-shadow: 0 0 15px rgba(212, 175, 55, 0.5);
        }
        #power-fill { width: 0%; height: 100%; background: linear-gradient(90deg, #d4af37, #ff4500); }
        
        .overlay {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.85); display: flex; align-items: center;
            justify-content: center; z-index: 200; pointer-events: auto;
        }
        .modal-content {
            background: rgba(20, 20, 20, 0.95); color: #e0e0e0; padding: 40px;
            border: 2px solid #b22222; border-radius: 2px; text-align: center;
            box-shadow: 0 0 50px rgba(178, 34, 34, 0.3); max-width: 90%;
        }
        .char-card {
            cursor: pointer; transition: all 0.3s;
            border: 1px solid #444; padding: 20px; border-radius: 4px; background: rgba(255,255,255,0.05);
        }
        .char-card:hover { transform: translateY(-10px); border-color: #d4af37; background: rgba(212, 175, 55, 0.1); }
        .btn {
            background: #b22222; color: white; padding: 12px 40px;
            border-radius: 2px; font-weight: bold; cursor: pointer; border: none;
            display: inline-block; margin-top: 20px; letter-spacing: 0.2em;
        }
        #vignette {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            pointer-events: none; transition: opacity 0.3s;
            box-shadow: inset 0 0 150px rgba(255, 0, 0, 0); z-index: 100;
        }
        .critical-flash { animation: flash-red 0.5s infinite alternate; }
        @keyframes flash-red {
            from { box-shadow: inset 0 0 100px rgba(255, 0, 0, 0.3); }
            to { box-shadow: inset 0 0 200px rgba(255, 0, 0, 0.6); }
        }
    </style>
</head>
<body>

    <div id="vignette"></div>

    <div id="char-select-overlay" class="overlay">
        <div class="modal-content">
            <h1 class="text-4xl font-black mb-8 border-b border-red-900 pb-4 italic tracking-widest text-red-600">血戦:大鬼の庭園</h1>
            <p class="mb-8 text-gray-400">大鬼を討つための器を選べ</p>
            <div class="grid grid-cols-3 gap-6">
                <div class="char-card" onclick="selectCharacter('swordsman')">
                    <div class="w-16 h-20 bg-blue-900 mx-auto mb-4 rounded relative border border-blue-400">
                        <div class="absolute top-1 w-8 h-8 bg-orange-200 rounded-full left-1/2 -translate-x-1/2"></div>
                    </div>
                    <p class="font-bold text-blue-400">剣士</p>
                </div>
                <div class="char-card" onclick="selectCharacter('shrine_maiden')">
                    <div class="w-16 h-20 bg-red-800 mx-auto mb-4 rounded relative border border-red-200">
                        <div class="absolute top-1 w-8 h-8 bg-orange-200 rounded-full left-1/2 -translate-x-1/2"></div>
                    </div>
                    <p class="font-bold text-red-400">巫女</p>
                </div>
                <div class="char-card" onclick="selectCharacter('ninja')">
                    <div class="w-16 h-20 bg-gray-900 mx-auto mb-4 rounded relative border border-purple-900">
                        <div class="absolute top-1 w-8 h-8 bg-gray-800 rounded-full left-1/2 -translate-x-1/2"></div>
                    </div>
                    <p class="font-bold text-purple-400">忍者</p>
                </div>
            </div>
        </div>
    </div>

    <div id="ui-layer">
        <div class="flex justify-between items-start w-full px-10 pt-5">
            <div class="w-1/3">
                <div id="player-name" class="text-lg font-bold tracking-tighter text-blue-400 uppercase">PLAYER</div>
                <div class="w-full h-3 bg-gray-900 border border-gray-700 rounded-sm overflow-hidden">
                    <div id="player-hp" class="h-full bg-blue-500 w-full transition-all duration-300"></div>
                </div>
            </div>
            <div class="w-2/5 text-right">
                <div class="text-lg font-bold tracking-tighter text-red-500 uppercase">DAIONI - THE BOSS</div>
                <div class="w-full h-4 bg-gray-900 border border-red-900 rounded-sm overflow-hidden relative">
                    <div id="oni-hp" class="h-full bg-gradient-to-r from-red-800 to-red-600 w-full transition-all duration-300"></div>
                </div>
                <div id="hp-text" class="text-[10px] text-red-400 font-bold mt-1">HP: 200 / 200</div>
            </div>
        </div>

        <div class="flex flex-col items-center mb-16">
            <div id="turn-indicator" class="text-4xl font-black mb-4 italic tracking-widest uppercase">BOSS BATTLE</div>
            <div class="gauge-container">
                <div id="power-fill"></div>
            </div>
            <div class="text-[10px] mt-2 font-bold text-gray-400 tracking-widest uppercase">Space / Mouse to Slay</div>
        </div>
    </div>

    <div id="result-overlay" class="overlay" style="display: none;">
        <div class="modal-content">
            <h2 id="msg-title" class="text-5xl font-black mb-4 italic"></h2>
            <p id="msg-body" class="mb-8 text-xl text-gray-400"></p>
            <button class="btn" onclick="location.reload()">再戦に挑む</button>
        </div>
    </div>

    <script>
        let scene, camera, renderer, clock;
        let player, oni, ground;
        let balls = [];
        let particles = [];
        let screenShake = 0;
        
        const characterData = {
            swordsman: { name: "剣士", color: 0x001d3d, ballColor: 0x00b4d8, trail: 0x90e0ef, skin: 0xffdbac },
            shrine_maiden: { name: "巫女", color: 0x780000, ballColor: 0xff4d6d, trail: 0xffb3c1, skin: 0xffdbac },
            ninja: { name: "忍者", color: 0x1a1a1a, ballColor: 0x9d4edd, trail: 0xe0aaff, skin: 0x5d4037 }
        };

        const gameState = {
            selectedChar: null,
            currentTurn: 'player',
            isCharging: false,
            chargePower: 0,
            playerHp: 100,
            oniHpMax: 200,
            oniHp: 200,
            isGameOver: false,
            mouse: new THREE.Vector2(),
            gravity: -20.0
        };

        const UI = {
            layer: document.getElementById('ui-layer'),
            playerHp: document.getElementById('player-hp'),
            playerName: document.getElementById('player-name'),
            oniHp: document.getElementById('oni-hp'),
            hpText: document.getElementById('hp-text'),
            power: document.getElementById('power-fill'),
            turn: document.getElementById('turn-indicator'),
            resultOverlay: document.getElementById('result-overlay'),
            charSelectOverlay: document.getElementById('char-select-overlay'),
            msgTitle: document.getElementById('msg-title'),
            msgBody: document.getElementById('msg-body'),
            vignette: document.getElementById('vignette')
        };

        function selectCharacter(type) {
            gameState.selectedChar = characterData[type];
            UI.charSelectOverlay.style.display = 'none';
            UI.layer.style.display = 'flex';
            UI.playerName.innerText = gameState.selectedChar.name;
            initGame();
        }

        function initGame() {
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x050510);
            scene.fog = new THREE.Fog(0x050510, 30, 120);

            camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 5, 12);

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

            clock = new THREE.Clock();

            // Lights
            scene.add(new THREE.AmbientLight(0x202020, 0.5));
            const mainLight = new THREE.DirectionalLight(0xff0000, 0.6);
            mainLight.position.set(15, 30, 15);
            mainLight.castShadow = true;
            scene.add(mainLight);

            const bossLight = new THREE.PointLight(0xff0000, 2, 30);
            bossLight.position.set(0, 5, -35);
            scene.add(bossLight);

            // Ground
            const groundGeo = new THREE.PlaneGeometry(500, 500);
            const groundMat = new THREE.MeshPhongMaterial({ color: 0x0a0a0a, shininess: 5 });
            ground = new THREE.Mesh(groundGeo, groundMat);
            ground.rotation.x = -Math.PI / 2;
            ground.receiveShadow = true;
            scene.add(ground);

            // Characters
            player = createHumanoid(gameState.selectedChar, false);
            player.position.set(0, 0, 10);
            scene.add(player);

            // Create BOSS ONI
            oni = createBossOni();
            oni.position.set(0, 0, -40);
            scene.add(oni);

            addDecorations();

            window.addEventListener('mousedown', startCharging);
            window.addEventListener('mouseup', releaseCharging);
            window.addEventListener('keydown', (e) => { if (e.code === 'Space') startCharging(); });
            window.addEventListener('keyup', (e) => { if (e.code === 'Space') releaseCharging(); });
            window.addEventListener('mousemove', onMouseMove);
            window.addEventListener('resize', onWindowResize);

            animate();
        }

        function createHumanoid(data, isOni) {
            const group = new THREE.Group();
            const matBody = new THREE.MeshPhongMaterial({ color: data.color });
            const matSkin = new THREE.MeshPhongMaterial({ color: data.skin });

            const torso = new THREE.Mesh(new THREE.BoxGeometry(1.2, 1.8, 0.7), matBody);
            torso.position.y = 1.9;
            torso.castShadow = true;
            group.add(torso);

            const head = new THREE.Mesh(new THREE.BoxGeometry(0.85, 0.85, 0.85), matSkin);
            head.position.y = 3.2;
            head.castShadow = true;
            group.add(head);

            return group;
        }

        function createBossOni() {
            const group = new THREE.Group();
            const matBody = new THREE.MeshPhongMaterial({ color: 0x400000 });
            const matSkin = new THREE.MeshPhongMaterial({ color: 0x111111 });

            // Massive Torso
            const torso = new THREE.Mesh(new THREE.BoxGeometry(4, 5, 2.5), matBody);
            torso.position.y = 4.5;
            torso.castShadow = true;
            group.add(torso);

            // Massive Head
            const head = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), matSkin);
            head.position.y = 8.5;
            head.castShadow = true;
            group.add(head);

            // BOSS Horn (Massive One Horn)
            const hGeo = new THREE.ConeGeometry(0.5, 3, 4);
            const hMat = new THREE.MeshPhongMaterial({color: 0x000000, emissive: 0x330000});
            const horn = new THREE.Mesh(hGeo, hMat);
            horn.position.set(0, 11, 0.5);
            horn.rotation.x = -0.3;
            group.add(horn);

            // Glowing Red Eyes
            const eyeGeo = new THREE.SphereGeometry(0.3, 16, 16);
            const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff0000 });
            const e1 = new THREE.Mesh(eyeGeo, eyeMat); e1.position.set(-0.6, 8.8, 1.1); group.add(e1);
            const e2 = new THREE.Mesh(eyeGeo, eyeMat); e2.position.set(0.6, 8.8, 1.1); group.add(e2);
            
            // Eye Flare
            const flare = new THREE.PointLight(0xff0000, 5, 15);
            flare.position.set(0, 8.8, 1.5);
            group.add(flare);

            // Massive Arms
            const armGeo = new THREE.BoxGeometry(1.2, 4, 1.2);
            const armL = new THREE.Mesh(armGeo, matBody); armL.position.set(-2.8, 5, 0); group.add(armL);
            const armR = new THREE.Mesh(armGeo, matBody); armR.position.set(2.8, 5, 0); group.add(armR);

            return group;
        }

        function addDecorations() {
            const torii = new THREE.Group();
            const mat = new THREE.MeshPhongMaterial({ color: 0x220000 });
            const p1 = new THREE.Mesh(new THREE.BoxGeometry(1.5, 25, 1.5), mat); p1.position.set(-15, 12.5, 0); torii.add(p1);
            const p2 = new THREE.Mesh(new THREE.BoxGeometry(1.5, 25, 1.5), mat); p2.position.set(15, 12.5, 0); torii.add(p2);
            const top = new THREE.Mesh(new THREE.BoxGeometry(40, 2, 2), mat); top.position.y = 25; torii.add(top);
            torii.position.set(0, 0, -60);
            scene.add(torii);

            // Red Mist Ground
            const circle = new THREE.Mesh(
                new THREE.CircleGeometry(60, 32),
                new THREE.MeshBasicMaterial({ color: 0x220000, transparent: true, opacity: 0.3 })
            );
            circle.rotation.x = -Math.PI/2;
            circle.position.y = 0.02;
            scene.add(circle);
        }

        function startCharging() {
            if (gameState.isGameOver || gameState.currentTurn !== 'player' || balls.some(b => b.active) || gameState.isCharging) return;
            gameState.isCharging = true;
            gameState.chargePower = 0;
        }

        function releaseCharging() {
            if (!gameState.isCharging) return;
            gameState.isCharging = false;
            const power = 20 + (gameState.chargePower / 100) * 50;
            const aimX = (gameState.mouse.x) * 15;
            throwBall(player.position.clone().add(new THREE.Vector3(0, 3, -1)), new THREE.Vector3(aimX, power * 0.35, -power), 'player');
            screenShake = 0.4;
            gameState.chargePower = 0;
            updateUI();
        }

        function onMouseMove(event) {
            gameState.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            gameState.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        }

        function throwBall(pos, vel, owner) {
            const isBoss = owner === 'oni';
            const size = isBoss ? 1.5 : 0.6;
            const geo = new THREE.SphereGeometry(size, 16, 16);
            const color = owner === 'player' ? gameState.selectedChar.ballColor : 0xff0000;
            const mat = new THREE.MeshStandardMaterial({ 
                color, emissive: color, emissiveIntensity: isBoss ? 4 : 2, roughness: 0, metalness: 1
            });
            const mesh = new THREE.Mesh(geo, mat);
            mesh.position.copy(pos);
            mesh.castShadow = true;
            scene.add(mesh);
            
            const light = new THREE.PointLight(color, isBoss ? 4 : 2, 15);
            mesh.add(light);

            balls.push({ mesh, velocity: vel, owner, active: true, life: 0, color });
        }

        function oniTurn() {
            if (gameState.isGameOver) return;
            setTimeout(() => {
                const distZ = Math.abs(player.position.z - oni.position.z);
                const vz = distZ * 1.2;
                const vy = 12 + Math.random() * 12;
                const vx = (player.position.x - oni.position.x) + (Math.random() - 0.5) * 8;
                throwBall(oni.position.clone().add(new THREE.Vector3(0, 7, 3)), new THREE.Vector3(vx, vy, vz), 'oni');
                screenShake = 1.0; // BOSS THROW SHAKE
            }, 1000);
        }

        function update() {
            if (!gameState.selectedChar) return;
            const dt = clock.getDelta();
            const time = clock.getElapsedTime();

            if (gameState.isCharging) {
                gameState.chargePower = Math.min(gameState.chargePower + 85 * dt, 100);
                updateUI();
            }

            if (screenShake > 0) {
                camera.position.x += (Math.random() - 0.5) * screenShake;
                camera.position.y += (Math.random() - 0.5) * screenShake;
                screenShake *= 0.92;
            }

            balls.forEach(ball => {
                if (ball.active) {
                    if (Math.random() > 0.2) createParticle(ball.mesh.position, ball.color, 0.5);
                }
            });

            balls.forEach((ball, idx) => {
                if (!ball.active) return;
                ball.velocity.y += gameState.gravity * dt;
                ball.mesh.position.add(ball.velocity.clone().multiplyScalar(dt));
                ball.life += dt;
                
                if (ball.mesh.position.y < (ball.owner === 'oni' ? 1.5 : 0.6)) {
                    ball.mesh.position.y = (ball.owner === 'oni' ? 1.5 : 0.6);
                    ball.velocity.y *= -0.3;
                    if (Math.abs(ball.velocity.y) < 1.5) deactivateBall(ball);
                }

                const target = ball.owner === 'player' ? oni : player;
                const hitRadius = ball.owner === 'player' ? 5.0 : 2.5; // Boss has bigger hit box
                const targetBox = target.position.clone().add(new THREE.Vector3(0, ball.owner === 'player' ? 5 : 2, 0));
                
                if (ball.mesh.position.distanceTo(targetBox) < hitRadius) {
                    if (ball.owner === 'player') {
                        gameState.oniHp -= 20;
                    } else {
                        gameState.playerHp -= 40; // Boss deals more damage
                        triggerDamageUI();
                    }
                    createImpact(ball.mesh.position, ball.color, ball.owner === 'oni' ? 80 : 40);
                    screenShake = ball.owner === 'oni' ? 1.5 : 0.8;
                    deactivateBall(ball);
                    checkGameOver();
                }
                if (ball.mesh.position.z < -120 || ball.mesh.position.z > 60 || ball.life > 6) deactivateBall(ball);
            });

            particles.forEach((p, idx) => {
                p.mesh.position.add(p.vel);
                p.life -= dt * 2;
                p.mesh.scale.setScalar(p.life);
                if (p.life <= 0) {
                    scene.remove(p.mesh);
                    particles.splice(idx, 1);
                }
            });

            // Camera behavior
            let targetCamPos;
            if (gameState.currentTurn === 'player') {
                const zoom = gameState.isCharging ? 4 : 0;
                targetCamPos = new THREE.Vector3(gameState.mouse.x * 3, 6, 20 - zoom);
            } else {
                targetCamPos = new THREE.Vector3(0, 10, -15);
            }
            camera.position.lerp(targetCamPos, 0.08);
            camera.lookAt(0, 4, -15);

            // Boss Aura
            if (Math.random() > 0.5) createParticle(oni.position.clone().add(new THREE.Vector3((Math.random()-0.5)*8, Math.random()*10, (Math.random()-0.5)*4)), 0xff0000, 0.3);
        }

        function createParticle(pos, color, life = 1.0) {
            const p = new THREE.Mesh(
                new THREE.BoxGeometry(0.3, 0.3, 0.3), 
                new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.8 })
            );
            p.position.copy(pos).add(new THREE.Vector3((Math.random()-0.5), (Math.random()-0.5), (Math.random()-0.5)));
            scene.add(p);
            particles.push({
                mesh: p,
                vel: new THREE.Vector3((Math.random()-0.5)*0.15, Math.random()*0.15, (Math.random()-0.5)*0.15),
                life: life
            });
        }

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

        function triggerDamageUI() {
            UI.vignette.style.opacity = '1';
            UI.vignette.style.boxShadow = 'inset 0 0 150px rgba(255, 0, 0, 0.9)';
            setTimeout(() => { if (gameState.playerHp > 30) UI.vignette.style.opacity = '0'; }, 500);
        }

        function deactivateBall(ball) {
            if (!ball.active) return;
            ball.active = false;
            setTimeout(() => {
                scene.remove(ball.mesh);
                balls = balls.filter(b => b !== ball);
                if (balls.length === 0 && !gameState.isGameOver) switchTurn();
            }, 200);
        }

        function switchTurn() {
            gameState.currentTurn = gameState.currentTurn === 'player' ? 'oni' : 'player';
            updateUI();
            if (gameState.currentTurn === 'oni') oniTurn();
        }

        function checkGameOver() {
            updateUI();
            if (gameState.oniHp <= 0) endGame("大金星", "伝説の大鬼を討ち果たしたり!");
            else if (gameState.playerHp <= 0) endGame("力及ばず", "大鬼の圧倒的な力の前に沈む...");
            if (gameState.playerHp <= 30) UI.vignette.classList.add('critical-flash');
        }

        function endGame(title, body) {
            gameState.isGameOver = true;
            UI.msgTitle.innerText = title;
            UI.msgBody.innerText = body;
            UI.resultOverlay.style.display = 'flex';
        }

        function updateUI() {
            UI.playerHp.style.width = gameState.playerHp + '%';
            UI.oniHp.style.width = (gameState.oniHp / gameState.oniHpMax * 100) + '%';
            UI.hpText.innerText = `HP: ${Math.max(0, gameState.oniHp)} / ${gameState.oniHpMax}`;
            UI.power.style.width = gameState.chargePower + '%';
            UI.turn.innerText = gameState.currentTurn === 'player' ? "好機!撃て!" : "回避せよ!";
            UI.turn.style.color = gameState.currentTurn === 'player' ? "#00b4d8" : "#ff0000";
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {
            requestAnimationFrame(animate);
            update();
            if (renderer) renderer.render(scene, camera);
        }
    </script>
</body>
</html>

ボール投げゲーム

by kizu

スペースキーでボールを投げれます

🤖 使用したAI
Gemini
👁 7 回閲覧
📅 投稿:2026年3月21日 🔄 更新:2026年4月7日
応援スタンプを押してみよう!