Juggle & Watch - 3分ジャグリング

ゲームをプレイ

落ちてくるボールをキャッチして投げ返そう!

※ゲームは新しいウィンドウで開きます

ゲーム概要

レトロなゲーム&ウォッチ風のジャグリングゲーム

「Juggle & Watch」は、1980年代の携帯ゲーム機「ゲーム&ウォッチ」を彷彿とさせるレトロなビジュアルで楽しめるジャグリングゲームです。シンプルながら中毒性のあるゲームプレイで、反射神経と集中力を鍛えることができます。

画面上部から落ちてくるボールをパドルでキャッチし、自動的に投げ返すというシンプルなルールながら、スコアが上がるにつれて難易度が上昇し、最大3個のボールを同時にジャグリングする必要があります。

遊び方

基本ルール

  1. 画面上部から黒いボールが落ちてきます
  2. 下部のパドルを左右に動かしてボールをキャッチします
  3. キャッチしたボールは自動的に上に投げ返されます
  4. ボールを取り逃すとライフが1つ減ります(最大3つ)
  5. すべてのライフを失うとゲームオーバーです
  6. キャッチに成功するたびに1点獲得します

操作方法

デバイスごとの操作

デバイス 移動操作 開始/一時停止
PC ← → 矢印キー スペースキー
スマートフォン 画面タップまたは下部の○ボタン 画面タップ
タブレット 画面タップまたは下部の○ボタン 画面タップ

スマートフォンでは、画面をタップした位置にパドルが瞬時に移動します。また、画面下部に表示される左右の○ボタンを使って、従来のゲーム機のような操作も可能です。

攻略のコツ

高得点を目指すためのテクニック

1. 中央待機戦法

ボールが少ない序盤は、パドルを画面中央に置いて待機し、ボールの落下位置を見極めてから移動する戦法が有効です。

2. リズムを掴む

ボールの落下と跳ね返りには一定のリズムがあります。このリズムを体で覚えることで、複数のボールも効率的にジャグリングできます。

3. 予測移動

ボールの軌道を予測して、早めにパドルを移動させることで、余裕を持ってキャッチできます。特に複数のボールが同時に落ちてくる場合に重要です。

4. 画面端の活用

ボールは画面の左右の壁に当たると跳ね返ります。この特性を利用して、ボールの軌道をコントロールすることも可能です。

スコアシステム

難易度の変化

スコアが10点増えるごとに難易度レベルが上がります:

  • レベル1(0-9点):ボール1個、標準速度
  • レベル2(10-19点):ボール2個、速度10%アップ
  • レベル3(20-29点):ボール2個、速度20%アップ
  • レベル4(30点以上):ボール3個、速度30%アップ

ハイスコアとプレイ回数はブラウザのローカルストレージに自動保存されるため、次回プレイ時も記録が残ります。友達や家族と競い合って、最高記録を更新しましょう!

この記事を読んだ人におすすめ

📌 同じカテゴリの記事

🔥 イラレブックで人気の記事

`; // Blobを作成してURLを生成 const blob = new Blob([gameHTML], { type: 'text/html' }); const url = URL.createObjectURL(blob); // 新しいウィンドウで開く const newWindow = window.open(url, 'JuggleWatch', 'width=540,height=960'); if (!newWindow) { alert('ポップアップブロッカーが有効になっています。ポップアップを許可してください。'); } // URLをクリーンアップ(少し待ってから) setTimeout(() => URL.revokeObjectURL(url), 1000); } catch (error) { console.error('ゲームを開けませんでした:', error); alert('ゲームを開くことができませんでした。ブラウザの設定を確認してください。'); } } // ゲームのJavaScriptコード(文字列として) const gameScript = ` (() => { 'use strict'; // ====== 基本設定 ====== const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d', { alpha: false }); let W = 0, H = 0, DPR = Math.max(1, window.devicePixelRatio || 1); const STORAGE_KEYS = { HI: 'jw_highScore', PC: 'jw_playCount' }; // 状態管理 const STATE = { TITLE:'title', PLAY:'play', PAUSE:'pause', OVER:'over' }; let state = STATE.TITLE; // ゲームパラメータ(難易度はスコアで自動上昇) let score = 0, highScore = Number(localStorage.getItem(STORAGE_KEYS.HI) || 0); let lives = 3; let playCount = Number(localStorage.getItem(STORAGE_KEYS.PC) || 0); // プレイヤー(パドル) const player = { x: 0, y: 0, w: 0, h: 0, speed: 4120, // px/s targetX: null }; // ボール const balls = []; const MAX_BALLS = 3; // 物理 let gravityBase = 900; // px/s^2 let throwBase = 520; // 初速(上向き) let gravity = gravityBase; let throwV = throwBase; // FPS制御 let lastT = 0, running = true; // 入力 let keyL = false, keyR = false; let holdingLeft = false, holdingRight = false; let pointerDown = false, pointerId = null; // オンスクリーン十字キー(Canvas内に描画&ヒット判定) const dpad = { size: 84, pad: 16, left:{x:0,y:0,r:0}, right:{x:0,y:0,r:0} }; // Web Audio(効果音) let audioCtx = null, masterGain = null, unlocked = false; function ensureAudioUnlocked(){ if(!audioCtx){ audioCtx = new (window.AudioContext || window.webkitAudioContext)(); masterGain = audioCtx.createGain(); masterGain.gain.value = 0.2; masterGain.connect(audioCtx.destination); } if(audioCtx.state === 'suspended'){ audioCtx.resume(); } unlocked = true; } function beep({type='sine', f=440, t=0.08, vol=0.25, sweep=0} = {}){ if(!unlocked) return; const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.type = type; o.frequency.setValueAtTime(f, audioCtx.currentTime); if(sweep){ o.frequency.linearRampToValueAtTime(f+sweep, audioCtx.currentTime + t*0.8); } g.gain.setValueAtTime(0, audioCtx.currentTime); g.gain.linearRampToValueAtTime(vol, audioCtx.currentTime + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + t); o.connect(g); g.connect(masterGain); o.start(); o.stop(audioCtx.currentTime + t + 0.02); } // ====== レイアウト ====== function resize(){ // 画面比は 9:16 を目安に、収まり優先で決定 const vw = Math.min(window.innerWidth * 0.98, 900); let cw = vw; let ch = cw * 16/9; if(ch > window.innerHeight * 0.9){ ch = window.innerHeight * 0.9; cw = ch * 9/16; } DPR = Math.max(1, window.devicePixelRatio || 1); canvas.style.width = \`\${cw|0}px\`; canvas.style.height= \`\${ch|0}px\`; canvas.width = Math.round(cw * DPR); canvas.height = Math.round(ch * DPR); ctx.setTransform(DPR,0,0,DPR,0,0); W = cw; H = ch; // プレイヤーとDPADの位置・サイズ更新 player.y = H * 0.86; player.w = Math.max(60, W * 0.18); player.h = Math.max(10, H * 0.02); if(player.x===0) player.x = W/2; dpad.left.r = dpad.right.r = dpad.size/2; dpad.left.x = dpad.pad + dpad.size/2; dpad.right.x = W - dpad.pad - dpad.size/2; dpad.left.y = dpad.right.y = H - dpad.pad - dpad.size/2 - 2; // ほんの少し上げる } window.addEventListener('resize', resize, { passive:true }); // ====== 入力(PC) ====== window.addEventListener('keydown', (e) => { if(['ArrowLeft','ArrowRight',' '].includes(e.key)) e.preventDefault(); if(e.repeat) return; if(e.key === 'ArrowLeft') keyL = true; if(e.key === 'ArrowRight') keyR = true; if(e.key === ' '){ if(state === STATE.TITLE){ startGame(); } else if(state === STATE.PLAY){ pauseGame(); } else if(state === STATE.PAUSE){ resumeGame(); } else if(state === STATE.OVER){ startGame(true); } } ensureAudioUnlocked(); }, { passive:false }); window.addEventListener('keyup', (e) => { if(['ArrowLeft','ArrowRight',' '].includes(e.key)) e.preventDefault(); if(e.key === 'ArrowLeft') keyL = false; if(e.key === 'ArrowRight') keyR = false; }, { passive:false }); // ====== 入力(タッチ/ペン/マウス) ====== function canvasToLocal(e){ const r = canvas.getBoundingClientRect(); const x = (e.clientX - r.left) * (W / r.width); const y = (e.clientY - r.top ) * (H / r.height); return { x, y }; } canvas.addEventListener('pointerdown', (e) => { e.preventDefault(); ensureAudioUnlocked(); pointerDown = true; pointerId = e.pointerId; const p = canvasToLocal(e); if(state === STATE.TITLE || state === STATE.OVER){ startGame(state === STATE.OVER); return; } // DPAD ヒット if(hitCircle(p.x,p.y,dpad.left)) { holdingLeft = true; holdingRight = false; } else if(hitCircle(p.x,p.y,dpad.right)){ holdingRight = true; holdingLeft = false; } else{ player.targetX = p.x; } }, { passive:false }); canvas.addEventListener('pointermove', (e) => { if(!pointerDown || e.pointerId !== pointerId) return; const p = canvasToLocal(e); if(state !== STATE.PLAY) return; if(hitCircle(p.x,p.y,dpad.left)) { holdingLeft = true; holdingRight = false; } else if(hitCircle(p.x,p.y,dpad.right)){ holdingRight = true; holdingLeft = false; } else{ holdingLeft = holdingRight = false; player.targetX = p.x; } }, { passive:true }); const endPointer = (e) => { if(e.pointerId !== pointerId) return; pointerDown = false; pointerId = null; holdingLeft = holdingRight = false; player.targetX = null; }; canvas.addEventListener('pointerup', endPointer, { passive:true }); canvas.addEventListener('pointercancel', endPointer, { passive:true }); canvas.addEventListener('pointerleave', endPointer, { passive:true }); function hitCircle(x,y,c){ const dx=x-c.x, dy=y-c.y; return dx*dx+dy*dy <= c.r*c.r; } // ====== ゲーム制御 ====== function startGame(fromOver=false){ // プレイ回数カウント playCount++; localStorage.setItem(STORAGE_KEYS.PC, String(playCount)); state = STATE.PLAY; score = 0; lives = 3; balls.length = 0; updateDifficulty(); spawnIfNeeded(true); if(!fromOver) beep({type:'triangle', f:660, t:.08, vol:.25}); lastT = performance.now(); } function pauseGame(){ state = STATE.PAUSE; beep({type:'sine', f:400, t:.05, vol:.18}); } function resumeGame(){ state = STATE.PLAY; lastT = performance.now(); beep({type:'sine', f:500, t:.05, vol:.18}); } function gameOver(){ state = STATE.OVER; if(score > highScore){ highScore = score; localStorage.setItem(STORAGE_KEYS.HI, String(highScore)); // ささやかなファンファーレ beep({type:'triangle', f:740, t:.09, vol:.26}); setTimeout(()=>beep({type:'triangle', f:880, t:.10, vol:.26}), 90); setTimeout(()=>beep({type:'triangle', f:988, t:.12, vol:.26}), 180); }else{ beep({type:'square', f:180, t:.18, vol:.22}); } } function updateDifficulty(){ // スコアに応じて段階上昇(控えめ → クセになる速度へ) const level = 1 + Math.floor(score / 10); gravity = gravityBase + (level-1) * 90; throwV = throwBase + (level-1) * 36; // レベルアップ効果音 if(score>0 && score % 10 === 0){ beep({type:'sine', f:660, t:.06, vol:.22}); setTimeout(()=>beep({type:'sine', f:880, t:.06, vol:.22}), 70); } } function spawnBall(){ const r = Math.max(7, Math.min(W,H)*0.012); const x = W*0.15 + Math.random() * W*0.70; const y = -r - Math.random()*H*0.25; const vx = (Math.random()*2-1) * (60 + Math.random()*60); const vy = 20 + Math.random()*40; balls.push({ x, y, vx, vy, r, lastY: y - 1 }); } function spawnIfNeeded(initial=false){ // スコアしきい値で最大ボール数を増やしていく const target = Math.min(MAX_BALLS, score >= 30 ? 4 : score >= 15 ? 3 : score >= 5 ? 2 : 1); while(balls.length < target) spawnBall(); // 初期は1個だけ静かに出す if(initial && balls.length===0) spawnBall(); } // ====== ループ ====== function loop(t){ if(!running){ requestAnimationFrame(loop); return; } const dt = Math.min(0.05, (t - lastT) / 1000); // 最大50ms(安定化) lastT = t; // 更新 if(state === STATE.PLAY){ handlePlayer(dt); handleBalls(dt); } // 描画 draw(); requestAnimationFrame(loop); } function handlePlayer(dt){ // キー or DPAD let vx = 0; if(keyL || holdingLeft) vx -= player.speed; if(keyR || holdingRight) vx += player.speed; // タップ移動(目標Xに吸い付くように) if(player.targetX != null){ const dir = Math.sign(player.targetX - player.x); vx = dir * Math.max(260, player.speed); // 直感的に速く寄る if(Math.abs(player.targetX - player.x) < Math.abs(vx*dt)){ player.x = player.targetX; player.targetX = null; vx = 0; } } player.x += vx * dt; // 端で停止 const half = player.w/2; if(player.x < half) player.x = half; if(player.x > W-half) player.x = W-half; } function handleBalls(dt){ // 生成調整 spawnIfNeeded(); for(let i=balls.length-1;i>=0;i--){ const b = balls[i]; b.lastY = b.y; // 物理 b.vy += gravity * dt; b.x += b.vx * dt; b.y += b.vy * dt; // 壁反射(シンプル) if(b.x < b.r){ b.x=b.r; b.vx *= -1; } if(b.x > W-b.r){ b.x=W-b.r; b.vx *= -1; } // キャッチ判定(下向き&パドル上辺を跨いだ瞬間) const top = player.y - player.h/2; const withinX = Math.abs(b.x - player.x) <= (player.w/2 + b.r*0.8); if(b.vy>0 && b.lastY + b.r <= top && b.y + b.r >= top && withinX){ // キャッチ&スロー score++; updateDifficulty(); // 上向きに投げ返す。オフセットで左右へ散らす const offset = (b.x - player.x) / (player.w/2); b.vy = - (throwV * (0.96 + Math.abs(offset)*0.18)); b.vx += offset * 90; b.y = top - b.r - 0.01; beep({type:'sine', f: 520 + Math.random()*120, t:0.05, vol:0.22, sweep: -90}); } // 取り逃し if(b.y - b.r > H){ balls.splice(i,1); // 失ったボールは消して補充 lives--; beep({type:'square', f:140, t:0.15, vol:0.22}); if(lives <= 0){ gameOver(); return; } // 少し間を置いて再投入(演出最小限) setTimeout(spawnBall, 160); } } } // ====== 描画 ====== function draw(){ // 背景(薄いグリッドで視線を安定) ctx.fillStyle = '#eef6da'; ctx.fillRect(0,0,W,H); ctx.strokeStyle = 'rgba(0,0,0,.04)'; ctx.lineWidth = 1; for(let y=60;y ctx.fillText(tx, W/2, H*0.46 + i*H*0.05)); // 実用性:ハイスコアとプレイ回数 ctx.font = \`700 \${Math.max(16,H*0.035)}px ui-monospace, SFMono, Menlo, monospace\`; ctx.fillText(\`HI \${highScore} PLAY \${playCount}\`, W/2, H*0.70); ctx.font = \`800 \${Math.max(16,H*0.035)}px system-ui, sans-serif\`; ctx.fillText('Space / Tap でスタート', W/2, H*0.80); } function drawPause(){ panelOverlay(); ctx.fillStyle = '#111'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = \`800 \${Math.max(28,H*0.07)}px system-ui, sans-serif\`; ctx.fillText('PAUSE', W/2, H*0.42); ctx.font = \`600 \${Math.max(16,H*0.035)}px system-ui, sans-serif\`; ctx.fillText('Space / Tap で再開', W/2, H*0.56); } function drawGameOver(){ panelOverlay(); ctx.fillStyle = '#111'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = \`800 \${Math.max(28,H*0.07)}px system-ui, sans-serif\`; ctx.fillText('GAME OVER', W/2, H*0.34); ctx.font = \`700 \${Math.max(18,H*0.04)}px ui-monospace, SFMono, Menlo, monospace\`; ctx.fillText(\`SCORE \${score}\`, W/2, H*0.48); ctx.fillText(\`HI \${highScore}\`, W/2, H*0.56); // 「もう一回!」 ctx.font = \`800 \${Math.max(16,H*0.035)}px system-ui, sans-serif\`; ctx.fillText('Tap / Space でもう一回!', W/2, H*0.72); } function panelOverlay(){ ctx.save(); ctx.fillStyle = 'rgba(255,255,255,.75)'; const w = Math.min(W*0.86, 580), h = Math.min(H*0.66, 520); const x = (W - w)/2, y = (H - h)/2; roundRect(ctx, x, y, w, h, 18, null, true); ctx.restore(); } function roundRect(ctx, x,y,w,h,r, fill='#111', stroke=false){ ctx.beginPath(); ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); if(fill){ ctx.fillStyle = fill; ctx.fill(); } if(stroke){ ctx.strokeStyle = 'rgba(0,0,0,.25)'; ctx.lineWidth = 2; ctx.stroke(); } } // ====== 初期化と開始 ====== resize(); player.x = W/2; // 初回描画(タイトル) draw(); requestAnimationFrame((t)=>{ lastT = t; loop(t); }); // ページが非表示になったら自動一時停止(レスポンス重視) document.addEventListener('visibilitychange', () => { if(document.hidden && state === STATE.PLAY) pauseGame(); }); })(); `;