物理演算を使ったボール跳ね返しゲームを作ってみた【AI生成ゲーム実験 Vol.6】 ★コピペで遊べるソースコード、プロンプトを公開中★

2025年7月16日水曜日

オリジナルゲーム プロンプト 暇つぶしにどうぞ 無料ゲーム

t f B! P L

 こんにちは、AM0500です。

作成するゲームのネタが思いつかなかったのでお任せで作ってみました。物理演算の機能が使えると知ったので、物理演算の機能を使って何か作ってもらい、そこからアイデアを広げようという企画になります。

🔗 実際にプレイしてみる

▼ ブラウザでそのまま遊べます:

👉 物理演算を使ったボール跳ね返しゲーム


👉 使用したAI Claude(claude 4 sonnet)はこちら


🎮 ゲーム画面(スクリーンショット)



画像
クリックしてボールを飛ばすゲーム

📝 一言コメント

具体的なアイデアが無い状態で試してみた企画です。
そして完成したゲームから適切なプロンプトを吐き出してもらい、どんな指定が行われるのかを確認した企画になります。


🧠 使用したClaudeへのプロンプト

今回、使用したプロンプトです。

物理演算を使用して、PCとスマホのどちらでも遊べるようなシンプルなゲームを作ってもらえますか?
ジャンルはアクションでボタン一つで遊べる内容を希望します。

最初に作成に使ったプロンプトは上記の通りです。
このままでは再現性が無いので、このゲームを作ったあとにClaudeから再現させるためのプロンプトを作ってもらいました。

プロンプト
PCとスマホの両方で遊べる物理演算アクションゲームを作ってください。以下の詳細仕様に従って実装してください:
基本仕様

ゲーム名: ボール跳ね返しゲーム
プラットフォーム: PC(クリック操作)とスマホ(タップ操作)両対応
ゲームサイズ: 400px × 600px(レスポンシブ対応)
言語: 日本語UI

操作方法

メイン操作: 画面上の任意の位置をクリック/タップすると、その位置に向かってボールが飛ぶ
キーボード: スペースキーで上方向にジャンプ(PC用補助操作)
タッチ操作: タップした位置に向かってボールが飛ぶ

ゲームルール

プレイヤーは赤いボールを操作する
上から赤い長方形の障害物が落ちてくる
障害物を避けてスコアを稼ぐ(障害物が画面外に出ると10点獲得)
障害物に当たるとライフが1減る(初期ライフ3)
ライフが0になるとゲームオーバー
時間経過とともにゲームスピードが上昇

物理演算の要件

重力: 常時下向きに作用(0.5程度)
衝突判定:

地面:弾性衝突(反発係数0.6)
左右の壁:弾性衝突(反発係数0.7)
天井:弾性衝突(反発係数0.3)


摩擦: 水平方向の速度に摩擦を適用(0.98倍)
ジャンプ力: クリック位置への方向に一定の力(15程度)を適用

ビジュアル要件

背景: 空色から緑色へのグラデーション
ボール:

赤色のグラデーション(#ff8a8a → #ff4444)
半径20px
トレイル効果(8フレーム分の軌跡)


障害物:

赤色のグラデーション(#ff6666 → #cc3333)
サイズ:60px × 30px
影付き


パーティクル効果:

地面着地時:緑色パーティクル
ジャンプ時:水色パーティクル
障害物衝突時:赤色パーティクル


背景装飾: 流れる白い雲

UI要件

スタート画面: タイトル、操作説明、開始ボタン
ゲーム画面:

左上にスコア表示
右上にライフ表示
下部に操作説明


ゲームオーバー画面: 最終スコア表示、リトライボタン
デザイン: モダンなグラデーション、ボタンホバー効果

技術要件

実装: HTML5 Canvas + JavaScript
レスポンシブ: さまざまなデバイスサイズに対応
パフォーマンス: 60FPS で滑らかな動作
ゲームループ: requestAnimationFrame を使用
状態管理: start/playing/gameOver の3状態

追加要素

難易度調整: 時間経過で障害物の速度と生成頻度が増加
視覚効果: 美しいグラデーション、影、アニメーション
音響効果: 不要(視覚効果のみ)

このゲームは直感的な操作で楽しめ、物理演算によるリアルな動きが特徴です。完全に動作するHTMLファイルとして作成してください。

🕹️ Claudeが返してくれた説明文

ゲームの特徴:
🏀 リアルな物理演算: 重力、摩擦、衝突などの物理法則を実装
📱 クロスプラットフォーム: PC(クリック/スペースキー)とスマホ(タップ)の両方で操作可能
🎯 シンプル操作: ボタン一つ(タップ/クリック)でボールを跳ね上げる
🌈 視覚効果: トレイル、パーティクル、グラデーションなどの美しいエフェクト
📊 スコアシステム: 障害物を避けるたびにスコアが増加し、ゲームスピードも徐々に上昇

ゲームルール:
画面をタップ/クリックしてボールを跳ね上げる
上から落ちてくる赤い障害物を避ける
障害物に当たるとライフが減る(3回でゲームオーバー)
障害物を避けるたびにスコアが増加
時間が経つにつれてゲームスピードが上がる

物理演算の実装:
重力によるボールの自然な落下
地面や壁との弾性衝突
摩擦による速度の減衰
障害物との衝突による反発力

レスポンシブデザインで様々なデバイスサイズに対応しており、直感的で楽しいゲームプレイを提供します!


💡 ソースコード(HTML)

下記がゲーム本体のhtmlの中身になります。ご自由にご利用ください。もちろん改変などもOKです。リンクや紹介していただけたら嬉しいです。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ボール跳ね返しゲーム</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            font-family: Arial, sans-serif;
            touch-action: none;
        }
        
        .game-container {
            position: relative;
            width: 400px;
            height: 600px;
            background: linear-gradient(180deg, #87CEEB 0%, #98FB98 100%);
            border: 3px solid #333;
            border-radius: 10px;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
        }
        
        .game-info {
            position: absolute;
            top: 10px;
            left: 10px;
            right: 10px;
            z-index: 100;
            display: flex;
            justify-content: space-between;
            color: #333;
            font-weight: bold;
            font-size: 18px;
        }
        
        .start-screen, .game-over-screen {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0,0,0,0.8);
            color: white;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            z-index: 200;
            text-align: center;
        }
        
        .start-screen h1, .game-over-screen h1 {
            font-size: 28px;
            margin-bottom: 20px;
        }
        
        .start-screen p, .game-over-screen p {
            font-size: 16px;
            margin-bottom: 30px;
            line-height: 1.5;
        }
        
        .btn {
            padding: 15px 30px;
            font-size: 18px;
            background: #ff6b6b;
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            transition: all 0.3s;
            box-shadow: 0 5px 15px rgba(255,107,107,0.4);
        }
        
        .btn:hover {
            background: #ff5252;
            transform: translateY(-2px);
            box-shadow: 0 7px 20px rgba(255,107,107,0.6);
        }
        
        .instructions {
            position: absolute;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            color: #666;
            font-size: 14px;
            text-align: center;
        }
        
        canvas {
            display: block;
            background: transparent;
        }
    </style>
</head>
<body>
    <div class="game-container">
        <div class="game-info">
            <div>スコア: <span id="score">0</span></div>
            <div>ライフ: <span id="lives">3</span></div>
        </div>
        
        <canvas id="gameCanvas" width="400" height="600"></canvas>
        
        <div class="start-screen" id="startScreen">
            <h1>🏀 ボール跳ね返しゲーム</h1>
            <p>タップ/クリックした位置に<br>ボールが飛んでいきます!<br><br>赤い障害物を避けて<br>スコアを稼ごう!</p>
            <button class="btn" onclick="startGame()">ゲーム開始</button>
        </div>
        
        <div class="game-over-screen" id="gameOverScreen" style="display: none;">
            <h1>ゲームオーバー</h1>
            <p>最終スコア: <span id="finalScore">0</span></p>
            <button class="btn" onclick="restartGame()">もう一度プレイ</button>
        </div>
        
        <div class="instructions">
            タップ/クリックした位置にボールが飛ぶ
        </div>
    </div>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const scoreElement = document.getElementById('score');
        const livesElement = document.getElementById('lives');
        const startScreen = document.getElementById('startScreen');
        const gameOverScreen = document.getElementById('gameOverScreen');
        const finalScoreElement = document.getElementById('finalScore');
        
        let gameState = 'start'; // 'start', 'playing', 'gameOver'
        let score = 0;
        let lives = 3;
        let gameSpeed = 1;
        
        // 物理演算の定数
        const GRAVITY = 0.5;
        const JUMP_FORCE = -12;
        const FRICTION = 0.98;
        
        // ボール
        const ball = {
            x: canvas.width / 2,
            y: canvas.height - 100,
            radius: 20,
            vx: 0,
            vy: 0,
            color: '#ff6b6b',
            trail: []
        };
        
        // 障害物
        let obstacles = [];
        
        // パーティクル
        let particles = [];
        
        function createObstacle() {
            obstacles.push({
                x: Math.random() * (canvas.width - 60),
                y: -30,
                width: 60,
                height: 30,
                speed: 2 + gameSpeed * 0.5,
                color: '#ff4444'
            });
        }
        
        function createParticle(x, y, color) {
            for (let i = 0; i < 8; i++) {
                particles.push({
                    x: x,
                    y: y,
                    vx: (Math.random() - 0.5) * 10,
                    vy: (Math.random() - 0.5) * 10,
                    life: 30,
                    maxLife: 30,
                    color: color
                });
            }
        }
        
        function updateBall() {
            // 重力適用
            ball.vy += GRAVITY;
            
            // 速度適用
            ball.x += ball.vx;
            ball.y += ball.vy;
            
            // 摩擦(水平方向)
            ball.vx *= FRICTION;
            
            // 壁との衝突(左右)
            if (ball.x - ball.radius < 0 || ball.x + ball.radius > canvas.width) {
                ball.vx *= -0.7;
                ball.x = Math.max(ball.radius, Math.min(canvas.width - ball.radius, ball.x));
            }
            
            // 地面との衝突
            if (ball.y + ball.radius > canvas.height) {
                ball.y = canvas.height - ball.radius;
                ball.vy *= -0.6;
                ball.vx *= 0.8;
                
                // 地面に着地時のパーティクル
                if (Math.abs(ball.vy) > 3) {
                    createParticle(ball.x, ball.y + ball.radius, '#98FB98');
                }
            }
            
            // 天井との衝突
            if (ball.y - ball.radius < 0) {
                ball.y = ball.radius;
                ball.vy *= -0.3;
            }
            
            // トレイル効果
            ball.trail.push({x: ball.x, y: ball.y});
            if (ball.trail.length > 8) {
                ball.trail.shift();
            }
        }
        
        function updateObstacles() {
            for (let i = obstacles.length - 1; i >= 0; i--) {
                const obstacle = obstacles[i];
                obstacle.y += obstacle.speed;
                
                // 画面外に出たら削除してスコア増加
                if (obstacle.y > canvas.height) {
                    obstacles.splice(i, 1);
                    score += 10;
                    gameSpeed += 0.02;
                }
                
                // ボールとの衝突判定
                if (ball.x + ball.radius > obstacle.x && 
                    ball.x - ball.radius < obstacle.x + obstacle.width &&
                    ball.y + ball.radius > obstacle.y && 
                    ball.y - ball.radius < obstacle.y + obstacle.height) {
                    
                    // 衝突時の処理
                    createParticle(ball.x, ball.y, '#ff6b6b');
                    obstacles.splice(i, 1);
                    lives--;
                    
                    // ボールを押し返す
                    const centerX = obstacle.x + obstacle.width / 2;
                    const centerY = obstacle.y + obstacle.height / 2;
                    const dx = ball.x - centerX;
                    const dy = ball.y - centerY;
                    const distance = Math.sqrt(dx * dx + dy * dy);
                    
                    if (distance > 0) {
                        ball.vx += (dx / distance) * 8;
                        ball.vy += (dy / distance) * 8;
                    }
                    
                    if (lives <= 0) {
                        gameState = 'gameOver';
                    }
                }
            }
        }
        
        function updateParticles() {
            for (let i = particles.length - 1; i >= 0; i--) {
                const particle = particles[i];
                particle.x += particle.vx;
                particle.y += particle.vy;
                particle.vx *= 0.98;
                particle.vy *= 0.98;
                particle.life--;
                
                if (particle.life <= 0) {
                    particles.splice(i, 1);
                }
            }
        }
        
        function drawBall() {
            // トレイル描画
            ctx.globalAlpha = 0.3;
            for (let i = 0; i < ball.trail.length; i++) {
                const trail = ball.trail[i];
                const alpha = i / ball.trail.length;
                ctx.globalAlpha = alpha * 0.5;
                
                ctx.beginPath();
                ctx.arc(trail.x, trail.y, ball.radius * alpha, 0, Math.PI * 2);
                ctx.fillStyle = ball.color;
                ctx.fill();
            }
            
            // ボール本体
            ctx.globalAlpha = 1;
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
            
            // グラデーション
            const gradient = ctx.createRadialGradient(
                ball.x - ball.radius/3, ball.y - ball.radius/3, 0,
                ball.x, ball.y, ball.radius
            );
            gradient.addColorStop(0, '#ff8a8a');
            gradient.addColorStop(1, '#ff4444');
            
            ctx.fillStyle = gradient;
            ctx.fill();
            
            // 影
            ctx.beginPath();
            ctx.arc(ball.x, ball.y, ball.radius - 2, 0, Math.PI * 2);
            ctx.strokeStyle = '#cc3333';
            ctx.lineWidth = 2;
            ctx.stroke();
        }
        
        function drawObstacles() {
            obstacles.forEach(obstacle => {
                // 影
                ctx.fillStyle = 'rgba(0,0,0,0.3)';
                ctx.fillRect(obstacle.x + 2, obstacle.y + 2, obstacle.width, obstacle.height);
                
                // 本体
                const gradient = ctx.createLinearGradient(
                    obstacle.x, obstacle.y,
                    obstacle.x, obstacle.y + obstacle.height
                );
                gradient.addColorStop(0, '#ff6666');
                gradient.addColorStop(1, '#cc3333');
                
                ctx.fillStyle = gradient;
                ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
                
                // ハイライト
                ctx.fillStyle = 'rgba(255,255,255,0.3)';
                ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, 5);
            });
        }
        
        function drawParticles() {
            particles.forEach(particle => {
                const alpha = particle.life / particle.maxLife;
                ctx.globalAlpha = alpha;
                ctx.beginPath();
                ctx.arc(particle.x, particle.y, 3, 0, Math.PI * 2);
                ctx.fillStyle = particle.color;
                ctx.fill();
            });
            ctx.globalAlpha = 1;
        }
        
        function drawBackground() {
            // 雲
            ctx.fillStyle = 'rgba(255,255,255,0.8)';
            for (let i = 0; i < 3; i++) {
                const x = (Date.now() * 0.02 + i * 150) % (canvas.width + 100) - 50;
                const y = 50 + i * 30;
                drawCloud(x, y);
            }
        }
        
        function drawCloud(x, y) {
            ctx.beginPath();
            ctx.arc(x, y, 20, 0, Math.PI * 2);
            ctx.arc(x + 20, y, 30, 0, Math.PI * 2);
            ctx.arc(x + 40, y, 20, 0, Math.PI * 2);
            ctx.arc(x + 10, y - 20, 20, 0, Math.PI * 2);
            ctx.arc(x + 30, y - 20, 20, 0, Math.PI * 2);
            ctx.fill();
        }
        
        function jump(targetX, targetY) {
            if (gameState === 'playing') {
                // クリック/タップ位置への方向を計算
                const dx = targetX - ball.x;
                const dy = targetY - ball.y;
                const distance = Math.sqrt(dx * dx + dy * dy);
                
                // 正規化して適切な力を適用
                const force = 15;
                if (distance > 0) {
                    ball.vx = (dx / distance) * force;
                    ball.vy = (dy / distance) * force;
                }
                
                // ジャンプ時のパーティクル
                createParticle(ball.x, ball.y + ball.radius, '#87CEEB');
            }
        }
        
        function gameLoop() {
            if (gameState === 'playing') {
                // 更新
                updateBall();
                updateObstacles();
                updateParticles();
                
                // 新しい障害物を生成
                if (Math.random() < 0.02 + gameSpeed * 0.01) {
                    createObstacle();
                }
                
                // UI更新
                scoreElement.textContent = score;
                livesElement.textContent = lives;
            }
            
            // ゲームオーバー判定(ゲームループの外で処理)
            if (gameState === 'gameOver') {
                finalScoreElement.textContent = score;
                gameOverScreen.style.display = 'flex';
            }
            
            // 描画(常に実行)
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            drawBackground();
            drawParticles();
            drawObstacles();
            drawBall();
            
            requestAnimationFrame(gameLoop);
        }
        
        function startGame() {
            gameState = 'playing';
            startScreen.style.display = 'none';
            gameOverScreen.style.display = 'none';
            
            // 初期化
            score = 0;
            lives = 3;
            gameSpeed = 1;
            obstacles = [];
            particles = [];
            
            ball.x = canvas.width / 2;
            ball.y = canvas.height - 100;
            ball.vx = 0;
            ball.vy = 0;
            ball.trail = [];
            
            // UI更新
            scoreElement.textContent = score;
            livesElement.textContent = lives;
        }
        
        function restartGame() {
            startGame();
        }
        
        // イベントリスナー
        canvas.addEventListener('click', (e) => {
            const rect = canvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;
            jump(x, y);
        });
        
        canvas.addEventListener('touchstart', (e) => {
            e.preventDefault();
            const rect = canvas.getBoundingClientRect();
            const touch = e.touches[0];
            const x = touch.clientX - rect.left;
            const y = touch.clientY - rect.top;
            jump(x, y);
        });
        
        // キーボード操作(上方向にジャンプ)
        document.addEventListener('keydown', (e) => {
            if (e.code === 'Space' || e.code === 'ArrowUp') {
                e.preventDefault();
                jump(ball.x, ball.y - 100);
            }
        });
        
        // ゲームループ開始
        gameLoop();
    </script>
</body>
</html>

🔔 補足・注意事項

このコードや説明は、Claudeのバージョンや環境によって出力内容が異なる可能性があります。
同じ命令をしても、まったく違うコードが返ってくることもあるので、再現性は参考程度にご覧ください。


🔔 リンク

👉 AM0500 作品一覧
👉 AM0500 GAME LAB

このブログを検索

アクションゲームメーカーでタイトル画面からゲーム本編へ切り替える基本手順(画面遷移のやり方)

 こんにちは、AM0500です。 今回は、アクションゲームメーカー(AGM)で タイトル画面から実際のゲームシーンへ移動させる手順 をまとめます。 AGMでは、シーンの移動を個別のプログラムで書くのではなく、 「SceneTransition」という専用の画面 で一括管理します。...

最近の投稿

記事まとめ

自己紹介

自分の写真
飼い猫のモイちゃんに朝5時に起こされる生活が続いたのでAM0500と名付けました。モイちゃんへの愛でゲーム制作にも挑戦しはじめて、素人ながらも、モイちゃんを主役にしたゲームを作り上げた時の喜びは忘れられません。そこから趣味でゲーム開発を始めました。勉強中の身ですがよろしくお願いします。

QooQ