NodeJS+Canvas实现21点桌时最后一张卡牌无法渲染的问题
问题分析与解决
核心原因
你调用drawCard异步函数时未使用await等待执行完成,导致生成Canvas的PNG流(canvas.createPNGStream())时,卡牌的加载与绘制操作还未完成,最终图片自然无法显示卡牌。
当你把drawCard内的代码直接移到循环里时,循环中的await loadImage会阻塞流程,确保每张卡牌加载并绘制完成后才继续执行,因此能正常渲染。
修复方案
在循环中调用drawCard时添加await,确保所有卡牌绘制完成后再生成图片流:
// 修改循环部分代码 for (let i = 0; i < joined.length; i++) { let plPic = await loadImage(joined[i].pic); let [x, y] = [ PORTIONS * (i + 1) - OFFSET_X, i % 2 === 0 && joined.length > 2 ? PLAYER_Y_ODD : PLAYER_Y ]; joined[i].pos = { x, y }; ctx.drawImage(plPic, x, y); // 添加await等待drawCard执行完成 await drawCard( joined[i], deck, Math.floor(x - OFFSET_X - 32 * joined[i].hand.length), y - 96 ); }
另外可检查client.draw方法是否正确实现了抽牌逻辑(从卡组中移除已抽卡牌),这虽不影响当前渲染问题,但会影响游戏逻辑的正确性。
完整优化代码(关键修改处已标注)
await int.deferReply(); interface Player { pic: string; hand: string[]; pos?: { x: number; y: number }; } const joinTable = (user: User): Player => ({ pic: user.displayAvatarURL({ extension: "png" }), hand: [] }); const joined = [ joinTable(int.user), joinTable(int.user), joinTable(int.user), joinTable(int.user), joinTable(int.user) ]; const [W, H] = [1920, 1080]; const [DEALER_Y, PLAYER_Y] = [H * 0.1, H * 0.75]; const PLAYER_Y_ODD = PLAYER_Y - 300; const [HALF_W, OFFSET_X] = [W * 0.5, 64]; const [CARD_DIMS, CARD_OFFSET_X] = [[52, 82] as [number, number], 32]; const PORTIONS = W / (joined.length + 1); const canvas = createCanvas(W, H); const ctx = canvas.getContext("2d"); const { img, cards } = PATHS; const drawCard = async ( p: Player, deck: string[], x: number, y: number, n: number = 1 ) => { for (let i = 0; i < n; i++) { let c = client.draw(deck, p.hand); p.hand.push(c); let cSprite = await loadImage(cards + c + ".png"); ctx.drawImage(cSprite, x, y, ...CARD_DIMS); // 可保留日志用于调试 //console.log(`${c}: (${x}, ${y})`); } }; let deck = client.deck(); let bg = await loadImage(img + "green.jpg"); let dProPic = await loadImage( client.user.displayAvatarURL({ extension: "png" }) ); ctx.drawImage(bg, 0, 0); ctx.drawImage(dProPic, HALF_W - OFFSET_X, DEALER_Y); for (let i = 0; i < joined.length; i++) { let plPic = await loadImage(joined[i].pic); let [x, y] = [ PORTIONS * (i + 1) - OFFSET_X, i % 2 === 0 && joined.length > 2 ? PLAYER_Y_ODD : PLAYER_Y ]; joined[i].pos = { x, y }; ctx.drawImage(plPic, x, y); // 关键修改:添加await等待drawCard完成 await drawCard( joined[i], deck, Math.floor(x - OFFSET_X - 32 * joined[i].hand.length), y - 96 ); } let atc = new AttachmentBuilder(canvas.createPNGStream()); await int.editReply({ files: [atc] });
额外说明
Canvas绘制是同步API,但图片加载是异步操作,必须确保所有异步的图片加载与绘制操作完成后,再将Canvas转换为流或图片。如果不等待异步函数完成,就会出现"代码执行到了但没生效"的情况——实际绘制逻辑还在等待图片加载,而此时已经生成了最终的图片流。
内容的提问来源于stack exchange,提问作者Saverio
相关产品推荐
相关产品推荐

