卡通生态拖动游戏-修复触屏拖动版edit icon

Fork(复制)
下载
嵌入
BUG反馈
index.html
md
README.md
现在支持上传本地图片了!
index.html
            
            <!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">
    <title>卡通生态拖动游戏</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="m-0 p-0 overflow-hidden bg-[#e6f7ff] select-none">
    <div id="game-container" class="w-screen h-screen relative"></div>

    <script>
        const elementsConfig = [
            { type: 'grass', name: '青草', count: 5, size: 40 },
            { type: 'apple', name: '苹果', count: 5, size: 36 },
            { type: 'rabbit', name: '兔子', count: 3, size: 48 },
            { type: 'cow', name: '牛', count: 1, size: 60 },
            { type: 'fox', name: '狐狸', count: 1, size: 56 },
            { type: 'human', name: '人', count: 1, size: 52 }
        ];

        const foodChain = {
            grass: ['rabbit', 'cow'],
            apple: ['cow', 'human'],
            rabbit: ['fox', 'human'],
            cow: ['human'],
            fox: [],
            human: []
        };

        const emojis = {
            grass: '🌿',
            apple: '🍎',
            rabbit: '🐇',
            cow: '🐄',
            fox: '🦊',
            human: '👨'
        };

        const elements = [];
        let activeDrag = null; // 当前正在拖拽的元素

        function initGame() {
            const container = document.getElementById('game-container');
            let id = 0;

            elementsConfig.forEach(config => {
                for (let i = 0; i < config.count; i++) {
                    const element = createElement(config.type, id++, config.size);
                    elements.push(element);
                    container.appendChild(element.element);
                }
            });

            enableDragging();
            startCollisionDetection();
        }

        function createElement(type, id, size) {
            const element = document.createElement('div');

            let left, top, attempts = 0;
            const spacing = 20;

            while (attempts < 100) {
                left = Math.random() * (window.innerWidth - size);
                top = Math.random() * (window.innerHeight - size);

                let overlap = false;
                for (const item of elements) {
                    const itemLeft = parseFloat(item.element.style.left);
                    const itemTop = parseFloat(item.element.style.top);
                    const dx = Math.abs(left - itemLeft);
                    const dy = Math.abs(top - itemTop);
                    if (dx < size + spacing && dy < size + spacing) {
                        overlap = true;
                        break;
                    }
                }
                if (!overlap) break;
                attempts++;
            }

            Object.assign(element.style, {
                position: 'absolute',
                left: `${left}px`,
                top: `${top}px`,
                width: `${size}px`,
                height: `${size}px`,
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                fontSize: `${size * 0.8}px`,
                cursor: 'move',
                transition: 'transform 0.3s, opacity 0.3s',
                userSelect: 'none',
                WebkitUserSelect: 'none',
                zIndex: 10,
                touchAction: 'none'
            });

            element.textContent = emojis[type];
            element.dataset.type = type;
            element.dataset.id = id;

            return { element, type, id, size, active: true };
        }

        function enableDragging() {
            // ---- 统一在 document 上处理移动和抬起,避免闭包泄漏 ----

            document.addEventListener('mousemove', onDragMove);
            document.addEventListener('mouseup', onDragEnd);
            document.addEventListener('touchmove', onDragMove, { passive: false });
            document.addEventListener('touchend', onDragEnd);
            document.addEventListener('touchcancel', onDragEnd);

            elements.forEach(item => {
                const el = item.element;

                el.addEventListener('mousedown', e => startDrag(e, item));
                el.addEventListener('touchstart', e => startDrag(e, item), { passive: false });
            });
        }

        function getXY(e) {
            // 同时兼容鼠标和触摸
            if (e.touches && e.touches.length > 0) {
                return { x: e.touches[0].clientX, y: e.touches[0].clientY };
            }
            return { x: e.clientX, y: e.clientY };
        }

        function startDrag(e, item) {
            if (!item.active) return;
            e.preventDefault(); // 阻止默认滚动 / 文本选中

            const { x, y } = getXY(e);
            const rect = item.element.getBoundingClientRect();

            activeDrag = {
                item,
                offsetX: x - rect.left,
                offsetY: y - rect.top
            };

            item.element.style.zIndex = 20;
            // 拖动时去掉 transition,避免延迟
            item.element.style.transition = 'opacity 0.3s';
        }

        function onDragMove(e) {
            if (!activeDrag) return;
            e.preventDefault();

            const { x, y } = getXY(e);
            const { item, offsetX, offsetY } = activeDrag;
            const maxX = window.innerWidth - item.size;
            const maxY = window.innerHeight - item.size;

            item.element.style.left = `${Math.max(0, Math.min(x - offsetX, maxX))}px`;
            item.element.style.top  = `${Math.max(0, Math.min(y - offsetY, maxY))}px`;
        }

        function onDragEnd() {
            if (!activeDrag) return;
            activeDrag.item.element.style.zIndex = 10;
            activeDrag.item.element.style.transition = 'transform 0.3s, opacity 0.3s';
            activeDrag = null;
        }

        // ---- 碰撞检测 ----
        function startCollisionDetection() {
            setInterval(checkAllCollisions, 50);
        }

        function checkAllCollisions() {
            for (let i = 0; i < elements.length; i++) {
                const a = elements[i];
                if (!a.active) continue;

                for (let j = i + 1; j < elements.length; j++) {
                    const b = elements[j];
                    if (!b.active) continue;

                    if (isColliding(a, b)) {
                        handleCorrectCollision(a, b);
                    }
                }
            }
        }

        function isColliding(a, b) {
            const r1 = a.element.getBoundingClientRect();
            const r2 = b.element.getBoundingClientRect();
            return !(r1.right < r2.left || r1.left > r2.right || r1.bottom < r2.top || r1.top > r2.bottom);
        }

        function handleCorrectCollision(a, b) {
            if (foodChain[a.type]?.includes(b.type)) {
                vanish(a);
            } else if (foodChain[b.type]?.includes(a.type)) {
                vanish(b);
            }
        }

        function vanish(item) {
            if (!item.active) return;
            item.active = false;
            item.element.style.transform = 'scale(0)';
            item.element.style.opacity = '0';
            setTimeout(() => item.element.remove(), 300);
        }

        window.addEventListener('load', initGame);
    </script>
</body>
</html>

        
编辑器加载中
预览
控制台