跳至正文
  • 8 views
  • 5 min read

趣味加法泡泡球(3-4岁适用)

新浪微博 豆瓣 QQ 百度贴吧 QQ空间

🛠️ 最佳使用建议

  1. 静音键提醒:如果玩的时候没声音,请检查手机侧面的物理静音开关(不要露出橙色)。
  2. 互动提问:你可以试着问小朋友:“你能用一个 2 和一个 3 变出一个 5 吗?”或者“这个 9 里面藏着哪两个好朋友?”

💡 游戏操作手册(家长版)

功能操作方式教学意义
合并加法拖动一个泡泡盖在另一个上面理解“合起来”就是加法,观察圆点数量变多
拆分减法快速连续双击较大的数字泡泡理解大数字是由小数字组成的,是减法的逆向思维
重整布局点击底部的“洗牌重新排列”保持画面整洁,重新开始数数练习
防止丢失泡泡会自动弹回屏幕内确保小朋友随时能看到所有数字,不产生挫败感
试玩网址
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
    <title>泡泡加法乐园</title>
    <style>
        * { -webkit-tap-highlight-color: transparent; }
        body, html { 
            margin: 0; padding: 0; width: 100%; height: 100%; 
            overflow: hidden; background: #f8fafc; 
            touch-action: none; position: fixed;
        }
        canvas { display: block; width: 100%; height: 100%; }
        
        #start-overlay {
            position: fixed; top:0; left:0; width:100%; height:100%;
            background: rgba(255,255,255,0.95);
            display: flex; justify-content: center; align-items: center;
            z-index: 9999;
        }
        .start-btn {
            padding: 16px 36px; font-size: 22px; background: #007AFF;
            color: white; border-radius: 50px; font-weight: bold;
        }

        #reset-btn {
            position: absolute; bottom: 30px; left: 50%;
            transform: translateX(-50%);
            padding: 12px 30px; font-size: 16px; font-weight: bold;
            background: #f43f5e; color: white; border: none; border-radius: 50px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.1); z-index: 100;
        }
    </style>
</head>
<body>

<div id="start-overlay" onclick="initGame()">
    <div class="start-btn">点这里开始 🎈</div>
</div>

<button id="reset-btn" onclick="resetGame()">洗牌重新排列 🔄</button>
<canvas id="gameCanvas"></canvas>

<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let audioCtx = null;
let bubbles = [];
let dragging = null;
let lastTap = 0;

function resize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();

function initGame() {
    if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    document.getElementById('start-overlay').style.display = 'none';
    resetGame();
    requestAnimationFrame(update);
}

function playPop() {
    if (!audioCtx) return;
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.type = 'sine';
    osc.frequency.setValueAtTime(500, audioCtx.currentTime);
    osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.1);
    gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
    osc.connect(gain); gain.connect(audioCtx.destination);
    osc.start(); osc.stop(audioCtx.currentTime + 0.1);
}

const COLORS = ['#FF5E5B', '#11999E', '#FFCC29', '#845EC2', '#D65DB1', '#4FFBDF', '#FF9671', '#93DEFF', '#00C9A7'];

class Bubble {
    constructor(x, y, value, dotColors = null, parentStates = null) {
        this.x = x; this.y = y; this.value = value;
        // 核心优化:减小基础尺寸,留出更多空白
        this.radius = 25 + (Math.sqrt(value) * 10); 
        this.parentStates = parentStates;
        this.dotColors = dotColors || Array(value).fill(COLORS[(value - 1) % 9]);
    }

    draw() {
        ctx.save();
        // 边界保护逻辑:确保不出界
        const margin = this.radius + 25;
        if (this.x < this.radius) this.x = this.radius;
        if (this.x > canvas.width - this.radius) this.x = canvas.width - this.radius;
        if (this.y < this.radius) this.y = this.radius;
        if (this.y > canvas.height - margin) this.y = canvas.height - margin;

        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        const grad = ctx.createRadialGradient(this.x-8, this.y-8, 0, this.x, this.y, this.radius);
        grad.addColorStop(0, '#fff');
        grad.addColorStop(1, 'rgba(186, 230, 253, 0.45)');
        ctx.fillStyle = grad;
        ctx.fill();
        ctx.strokeStyle = 'rgba(255,255,255,0.8)';
        ctx.lineWidth = 2;
        ctx.stroke();

        const count = this.dotColors.length;
        const cols = Math.ceil(Math.sqrt(count));
        const spacing = 14; // 缩小圆点间距
        const offset = ((cols - 1) * spacing) / 2;
        this.dotColors.forEach((color, i) => {
            const r = Math.floor(i / cols), c = i % cols;
            ctx.beginPath();
            ctx.arc(this.x + (c * spacing) - offset, this.y + (r * spacing) - offset, 5, 0, Math.PI * 2);
            ctx.fillStyle = color;
            ctx.fill();
        });

        ctx.fillStyle = '#0369a1';
        ctx.font = 'bold 18px Arial'; // 字体稍微缩小一点更精致
        ctx.textAlign = 'center';
        ctx.fillText(this.value, this.x, this.y + this.radius + 22);
        ctx.restore();
    }
}

function resetGame() {
    bubbles = [];
    const padding = 60;
    for (let i = 1; i <= 9; i++) {
        let attempts = 0;
        let placed = false;
        while (!placed && attempts < 100) {
            let tx = padding + Math.random() * (canvas.width - padding * 2);
            let ty = padding + Math.random() * (canvas.height - padding * 3);
            let tr = 25 + (Math.sqrt(i) * 10);
            let overlap = bubbles.some(b => Math.sqrt((tx-b.x)**2 + (ty-b.y)**2) < (tr + b.radius + 10));
            if (!overlap) {
                bubbles.push(new Bubble(tx, ty, i));
                placed = true;
            }
            attempts++;
        }
        if(!placed) bubbles.push(new Bubble(canvas.width/2, canvas.height/2, i));
    }
    if(audioCtx) playPop();
}

// 物理辅助:松手后如果太挤,自动推开一点点
function softPhysics() {
    for (let i = 0; i < bubbles.length; i++) {
        for (let j = i + 1; j < bubbles.length; j++) {
            const b1 = bubbles[i];
            const b2 = bubbles[j];
            if (b1 === dragging || b2 === dragging) continue;

            const dx = b2.x - b1.x;
            const dy = b2.y - b1.y;
            const dist = Math.sqrt(dx * dx + dy * dy);
            const minDist = b1.radius + b2.radius + 5;
            if (dist < minDist) {
                const angle = Math.atan2(dy, dx);
                const force = 0.5; // 很柔和的力
                b1.x -= Math.cos(angle) * force;
                b1.y -= Math.sin(angle) * force;
                b2.x += Math.cos(angle) * force;
                b2.y += Math.sin(angle) * force;
            }
        }
    }
}

function handleInput(e) {
    const pos = e.touches ? e.touches[0] : e;
    const px = pos.clientX;
    const py = pos.clientY;
    const now = Date.now();

    if (e.type === 'touchstart' || e.type === 'mousedown') {
        for (let i = bubbles.length - 1; i >= 0; i--) {
            const b = bubbles[i];
            if (Math.sqrt((px - b.x)**2 + (py - b.y)**2) < b.radius + 20) {
                if (now - lastTap < 300 && b.parentStates) {
                    const p = b.parentStates;
                    bubbles.splice(i, 1);
                    bubbles.push(new Bubble(b.x-40, b.y, p[0].val, p[0].colors, p[0].parents));
                    bubbles.push(new Bubble(b.x+40, b.y, p[1].val, p[1].colors, p[1].parents));
                    playPop();
                    dragging = null;
                    return;
                }
                dragging = b;
                bubbles.push(bubbles.splice(i, 1)[0]);
                break;
            }
        }
        lastTap = now;
    } else if (dragging) {
        dragging.x = px;
        dragging.y = py;
    }
}

function handleEnd() {
    if (!dragging) return;
    for (let i = bubbles.length - 2; i >= 0; i--) {
        const other = bubbles[i];
        if (Math.sqrt((dragging.x - other.x)**2 + (dragging.y - other.y)**2) < dragging.radius + other.radius) {
            playPop();
            const p = [{val:dragging.value, colors:dragging.dotColors, parents:dragging.parentStates},
                       {val:other.value, colors:other.dotColors, parents:other.parentStates}];
            bubbles = bubbles.filter(b => b !== dragging && b !== other);
            bubbles.push(new Bubble(other.x, other.y, p[0].val + p[1].val, [...p[0].colors, ...p[1].colors], p));
            break;
        }
    }
    dragging = null;
}

canvas.addEventListener('touchstart', handleInput, {passive: false});
canvas.addEventListener('touchmove', handleInput, {passive: false});
canvas.addEventListener('touchend', handleEnd);
canvas.addEventListener('mousedown', handleInput);
window.addEventListener('mousemove', handleInput);
window.addEventListener('mouseup', handleEnd);

function update() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    softPhysics();
    bubbles.forEach(b => b.draw());
    requestAnimationFrame(update);
}
</script>
</body>
</html>

发表回复

联系站长