You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决Kaboom.js异步资源加载引发的地图与精灵显示问题

Kaboom.js部署后精灵/地图不显示的问题解决

问题诊断

控制台的drawImage错误说明Kaboom尝试使用的图片资源无效,结合本地正常、部署异常的现象,核心原因有两个:

  • loadSprite接收了URL对象而非字符串URL,部署环境下资源解析失败
  • 资源加载流程和场景初始化的逻辑顺序错误,导致资源未加载完成就尝试渲染

修复步骤

1. 传递字符串URL给loadSprite

Kaboom的loadSprite期望接收字符串格式的资源路径,而非URL对象。将new URL(...)改为new URL(...).href,确保传递正确的字符串路径:

// 原代码
k.loadSprite ("spritesheet", new URL ('../spritesheet.png', import.meta.url), { ... })
// 修改后
k.loadSprite ("spritesheet", new URL ('../spritesheet.png', import.meta.url).href, { ... })

// 地图资源同理
k.loadSprite ("map", new URL ('../map.png', import.meta.url).href);

2. 修正场景初始化逻辑

场景内部调用k.go("main")会导致无限递归跳转,应该将场景跳转放在k.load()的回调末尾,确保所有资源加载完成后再启动场景:

k.load(() => {
    k.setBackground(k.Color.fromHex("#311047"));

    // 定义场景(移除内部的k.go("main"))
    k.scene("main", async () => {
        // ... 原场景代码
        // 移除这里的k.go("main")
    });

    // 所有资源加载完成后启动场景
    k.go("main");
});

3. 合并重复的DOMContentLoaded监听

代码中有两个DOMContentLoaded监听,合并为一个,避免逻辑重复执行:

document.addEventListener("DOMContentLoaded", () => {
    // Kaboom资源加载和场景初始化逻辑
    console.log('import.meta.url:', import.meta.url);

    k.loadSprite ("spritesheet", new URL ('../spritesheet.png', import.meta.url).href, {
        sliceX: 39,
        sliceY: 31,
        anims: {
            "idle-down": 936,
            "walk-down": { from: 936, to: 939, loop: true, speed: 8 },
            "idle-side": 975,
            "walk-side": { from: 975, to: 978, loop: true, speed: 8 },
            "idle-up": 1014,
            "walk-up": { from: 1014, to: 1017, loop: true, speed: 8 },
            "bag-open": 128,
        },
    });

    k.loadSprite ("map", new URL ('../map.png', import.meta.url).href);

    k.load(() => {
        k.setBackground(k.Color.fromHex("#311047"));

        k.scene("main", async () => {
            const mapJsonUrl = new URL('../map.json', import.meta.url).href;
            console.log('Map JSON URL:', mapJsonUrl);
            const mapData = await (await fetch(mapJsonUrl)).json();
            console.log('Map data:', mapData);
            const layers = mapData.layers;
            console.log('Layers:', layers);

            const map = k.add ([
                k.sprite ("map"),
                k.pos (0, 0),
                k.scale (scaleFactor)
            ]);

            const player = k.make ([
                k.sprite ("spritesheet", { anim: "idle-down" }),
                k.area ({ 
                    shape: new k.Rect (k.vec2(0, 3), 10, 10),
                }),
                k.body (),
                k.anchor ("center"),
                k.pos (),
                k.scale (scaleFactor),
                {
                    speed: 250,
                    direction: "down",
                    isInDialog: false,
                },
                "player",
            ]);

            for (const layer of layers) {
                if (layer.name == "Boundaries") {
                    for (const boundary of layer.objects) {
                        map.add ([
                            k.area ({
                                shape: new k.Rect (k.vec2(0), boundary.width, boundary.height),
                            }),
                            k.body ({isStatic: true}),
                            k.pos (boundary.x, boundary.y),
                            boundary.name,
                        ]);

                        if (boundary.name) {
                            player.onCollide(boundary.name, () => {
                                player.isInDialog = true;

                                displayDialog(
                                    dialogData[boundary.name],
                                    () => (player.isInDialog = false)
                                );
                            });
                        }
                    }
                    
                    continue;
                }

                if (layer.name == "Spawnpoint") {
                    for (const entity of layer.objects) {
                        if (entity.name == "player") {
                            player.pos = k.vec2(
                                (map.pos.x + entity.x) * scaleFactor,
                                (map.pos.y + entity.y) * scaleFactor
                            );
                            k.add(player);
                            continue;
                        }
                    }
                }
            }

            setCamScale(k)

            k.onResize(() => {
                setCamScale(k);
            });

            k.onUpdate(() => {
                k.camPos(player.pos.x, player.pos.y + 100);
            });

            // Player movement
            k.onMouseDown((mouseBtn) => {
                if (mouseBtn !== "left" || player.isInDialog)
                    return;

                const worldMousePos = k.toWorld(k.mousePos());
                player.moveTo(worldMousePos, player.speed);

                const mouseAngle = player.pos.angle(worldMousePos);
                const lowerBound = 50;
                const upperBound = 125;

                // Up
                if (mouseAngle > lowerBound && 
                    mouseAngle < upperBound && 
                    player.curAnim() !== "walk-up") {

                    player.play("walk-up");
                    player.direction = "up";
                    return;
                }

                // Down
                if (mouseAngle < -lowerBound &&
                    mouseAngle > -upperBound &&
                    player.curAnim() !== "walk-down") {

                    player.play("walk-down");
                    player.direction = "down";
                    return;
                    }

                // Right
                if (Math.abs(mouseAngle) > upperBound) {
                    player.flipX = false;

                    if (player.curAnim() !== "walk-side") {
                        player.play("walk-side");
                        player.direction = "right";
                        return;
                    }
                }

                // Left
                if (Math.abs(mouseAngle) < lowerBound) {
                    player.flipX = true;

                    if (player.curAnim() !== "walk-side") {
                        player.play("walk-side");
                        player.direction = "left";
                        return;
                    }
                }
            });

            // Stops walking animation
            k.onMouseRelease(() => {
                if (player.direction === "down") {
                    player.play("idle-down");
                    return;
                }

                if (player.direction === "up") {
                    player.play("idle-up");
                    return;
                }

                player.play("idle-side");
            });
        });

        k.go("main");
    });

    // 联系表单逻辑
    closeContactForm();
    
    const openButton = document.querySelector('.ui-popup-button');
    const closeButton = document.querySelector('#closeContactFormButton');

    if (openButton) {
        openButton.addEventListener('click', openContactForm);
    } else {
        console.error("Element with class '.ui-popup-button' not found");
    }

    if (closeButton) {
        closeButton.addEventListener('click', closeContactForm);
    } else {
        console.error("Element with id 'closeContactFormButton' not found");
    }
});

4. 额外验证点

  • 确认部署后的资源路径:检查浏览器控制台输出的URL是否正确,确保资源能通过该URL访问
  • 启用Kaboom的调试模式:添加k.debug()到初始化代码,查看资源加载状态

内容的提问来源于stack exchange,提问作者glitchedb0t

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 04:19:54