From a2d4085bf45a42ea510be2cd106e442814f13561 Mon Sep 17 00:00:00 2001 From: gouki Date: Fri, 7 Aug 2026 20:08:34 +0000 Subject: [PATCH] =?UTF-8?q?fix(wish-tree):=20=E5=8F=82=E8=80=83=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E5=9B=BE=E9=87=8D=E6=96=B0=E7=BB=98=E5=88=B6=E5=A4=A7?= =?UTF-8?q?=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 祈福树:粗壮扭曲树干、螺旋纹理、卷曲树枝、蓬松树冠、彩色装饰、发光效果 - 姻缘树:纤细树干、分散树枝、粉色蓬松树冠、飘落樱花 - 事业树:多根竹竿、竹节、摇摆竹叶、许愿条挂饰 - 添加草丛地面装饰 - 树枝使用三次贝塞尔曲线,带卷曲效果 --- mini/pages/wish-tree/wish-tree.js | 506 ++++++++++++++++++++---------- 1 file changed, 337 insertions(+), 169 deletions(-) diff --git a/mini/pages/wish-tree/wish-tree.js b/mini/pages/wish-tree/wish-tree.js index aa2252a..f648210 100644 --- a/mini/pages/wish-tree/wish-tree.js +++ b/mini/pages/wish-tree/wish-tree.js @@ -230,36 +230,41 @@ Page({ // 获取树枝位置 getTreeBranches(treeType, width, height) { const centerX = width / 2; - const baseY = height * 0.75; + const baseY = height * 0.78; // 根据树类型返回不同的树枝配置 const configs = { pine: [ - { x: centerX - 60, y: baseY - 180 }, - { x: centerX + 55, y: baseY - 190 }, - { x: centerX - 35, y: baseY - 220 }, - { x: centerX + 40, y: baseY - 230 }, - { x: centerX, y: baseY - 250 }, - { x: centerX - 80, y: baseY - 150 }, - { x: centerX + 75, y: baseY - 160 }, - { x: centerX - 20, y: baseY - 200 }, - { x: centerX + 25, y: baseY - 210 }, + { x: centerX - 70, y: baseY - 260 }, + { x: centerX + 65, y: baseY - 270 }, + { x: centerX - 45, y: baseY - 300 }, + { x: centerX + 50, y: baseY - 310 }, + { x: centerX, y: baseY - 330 }, + { x: centerX - 85, y: baseY - 220 }, + { x: centerX + 80, y: baseY - 230 }, + { x: centerX - 25, y: baseY - 280 }, + { x: centerX + 30, y: baseY - 290 }, + { x: centerX - 55, y: baseY - 340 }, + { x: centerX + 60, y: baseY - 350 }, ], sakura: [ - { x: centerX - 70, y: baseY - 180 }, - { x: centerX + 65, y: baseY - 190 }, - { x: centerX - 45, y: baseY - 220 }, - { x: centerX + 50, y: baseY - 230 }, - { x: centerX, y: baseY - 250 }, - { x: centerX - 30, y: baseY - 200 }, - { x: centerX + 35, y: baseY - 210 }, + { x: centerX - 60, y: baseY - 240 }, + { x: centerX + 55, y: baseY - 250 }, + { x: centerX - 40, y: baseY - 280 }, + { x: centerX + 45, y: baseY - 290 }, + { x: centerX, y: baseY - 310 }, + { x: centerX - 30, y: baseY - 260 }, + { x: centerX + 35, y: baseY - 270 }, ], bamboo: [ - { x: centerX - 30, y: baseY - 160 }, - { x: centerX, y: baseY - 200 }, - { x: centerX + 30, y: baseY - 140 }, - { x: centerX - 25, y: baseY - 120 }, - { x: centerX + 25, y: baseY - 180 }, + { x: centerX - 40, y: baseY - 100 }, + { x: centerX - 15, y: baseY - 140 }, + { x: centerX + 10, y: baseY - 120 }, + { x: centerX + 35, y: baseY - 80 }, + { x: centerX - 40, y: baseY - 160 }, + { x: centerX - 15, y: baseY - 200 }, + { x: centerX + 10, y: baseY - 180 }, + { x: centerX + 35, y: baseY - 140 }, ], }; @@ -396,7 +401,7 @@ Page({ // 绘制大树 drawTree(ctx, tree, width, height) { const centerX = width / 2; - const baseY = height * 0.75; + const baseY = height * 0.78; // 根据树类型绘制不同风格的树 const treeType = tree.type || 'pine'; @@ -410,65 +415,92 @@ Page({ } }, - // 绘制松树(祈福树) + // 绘制祈福树(装饰性大树,参考第一张图) drawPineTree(ctx, centerX, baseY) { - // 树干 - 用贝塞尔曲线画自然弯曲 - ctx.setFillStyle('#3d2914'); + const time = Date.now() / 1000; + + // 地面装饰 - 草丛 + this.drawGrass(ctx, centerX, baseY); + + // 粗壮树干 - 参考第三张图的扭曲感 + ctx.setFillStyle('#5a3a2a'); ctx.beginPath(); - ctx.moveTo(centerX - 20, baseY); - // 左边缘曲线 + ctx.moveTo(centerX - 30, baseY); + // 左边缘 - 扭曲向上 ctx.bezierCurveTo( - centerX - 18, baseY - 50, - centerX - 12, baseY - 100, - centerX - 8, baseY - 140 + centerX - 35, baseY - 40, + centerX - 25, baseY - 80, + centerX - 20, baseY - 120 ); - // 树干顶部 - ctx.lineTo(centerX + 8, baseY - 140); - // 右边缘曲线 ctx.bezierCurveTo( - centerX + 12, baseY - 100, - centerX + 18, baseY - 50, - centerX + 20, baseY + centerX - 15, baseY - 160, + centerX - 10, baseY - 180, + centerX - 5, baseY - 200 + ); + // 顶部 + ctx.lineTo(centerX + 5, baseY - 200); + // 右边缘 + ctx.bezierCurveTo( + centerX + 10, baseY - 180, + centerX + 15, baseY - 160, + centerX + 20, baseY - 120 + ); + ctx.bezierCurveTo( + centerX + 25, baseY - 80, + centerX + 35, baseY - 40, + centerX + 30, baseY ); ctx.closePath(); ctx.fill(); - // 树皮纹理 - ctx.setStrokeStyle('#2a1d0f'); - ctx.setLineWidth(1); - for (let i = 0; i < 5; i++) { - const y = baseY - 20 - i * 30; + // 树干纹理 - 螺旋感 + ctx.setStrokeStyle('#4a2a1a'); + ctx.setLineWidth(2); + for (let i = 0; i < 6; i++) { + const y = baseY - 30 - i * 35; + const offset = Math.sin(i * 0.8) * 8; ctx.beginPath(); - ctx.moveTo(centerX - 15 + i * 2, y); - ctx.quadraticCurveTo(centerX, y - 10, centerX + 15 - i * 2, y); + ctx.moveTo(centerX - 20 + offset, y); + ctx.quadraticCurveTo( + centerX + offset, y - 15, + centerX + 20 + offset, y + ); ctx.stroke(); } - // 主枝干 - this.drawBranch(ctx, centerX, baseY - 140, -60, 80, 3); - this.drawBranch(ctx, centerX, baseY - 140, 60, 80, 3); - this.drawBranch(ctx, centerX, baseY - 160, -30, 60, 2); - this.drawBranch(ctx, centerX, baseY - 160, 30, 60, 2); - this.drawBranch(ctx, centerX, baseY - 180, 0, 50, 2); - - // 树冠 - 用多个不规则圆形叠加,模拟松针层次 - const crownLayers = [ - { offsetY: -200, radiusX: 100, radiusY: 60, color: '#1a3d1f', alpha: 0.9 }, - { offsetY: -240, radiusX: 85, radiusY: 55, color: '#2d5a2d', alpha: 0.85 }, - { offsetY: -280, radiusX: 70, radiusY: 50, color: '#3d7a3d', alpha: 0.8 }, - { offsetY: -320, radiusX: 55, radiusY: 45, color: '#4d9a4d', alpha: 0.75 }, - { offsetY: -350, radiusX: 40, radiusY: 35, color: '#5dba5d', alpha: 0.7 }, + // 主枝干 - 曲线分散,参考第一张图 + const branches = [ + { angle: -70, length: 100, width: 4, curl: 20 }, + { angle: 70, length: 100, width: 4, curl: -20 }, + { angle: -45, length: 90, width: 3, curl: 15 }, + { angle: 45, length: 90, width: 3, curl: -15 }, + { angle: -20, length: 80, width: 3, curl: 10 }, + { angle: 20, length: 80, width: 3, curl: -10 }, + { angle: 0, length: 70, width: 2, curl: 0 }, ]; - crownLayers.forEach(layer => { + branches.forEach(b => { + this.drawCurlyBranch(ctx, centerX, baseY - 200, b.angle, b.length, b.width, b.curl); + }); + + // 树冠 - 多层蓬松感,参考第一张图 + const crownLayers = [ + { offsetY: -280, radiusX: 110, radiusY: 70, color: '#2a5a1a', alpha: 0.85 }, + { offsetY: -320, radiusX: 95, radiusY: 65, color: '#3a7a2a', alpha: 0.8 }, + { offsetY: -360, radiusX: 80, radiusY: 60, color: '#4a9a3a', alpha: 0.75 }, + { offsetY: -400, radiusX: 65, radiusY: 50, color: '#5aba4a', alpha: 0.7 }, + { offsetY: -430, radiusX: 50, radiusY: 40, color: '#6ada5a', alpha: 0.65 }, + ]; + + crownLayers.forEach((layer, idx) => { ctx.save(); ctx.setGlobalAlpha(layer.alpha); ctx.setFillStyle(layer.color); - // 绘制不规则椭圆形 + // 蓬松不规则形状 ctx.beginPath(); - for (let angle = 0; angle < Math.PI * 2; angle += 0.1) { - const noise = Math.sin(angle * 5) * 8 + Math.cos(angle * 3) * 5; + for (let angle = 0; angle < Math.PI * 2; angle += 0.08) { + const noise = Math.sin(angle * 6 + idx) * 12 + Math.cos(angle * 4 + idx * 2) * 8; const rx = layer.radiusX + noise; const ry = layer.radiusY + noise * 0.6; const x = centerX + Math.cos(angle) * rx; @@ -484,80 +516,174 @@ Page({ ctx.restore(); }); - // 松针细节 - ctx.setStrokeStyle('#6dca6d'); - ctx.setLineWidth(1.5); - for (let i = 0; i < 30; i++) { - const angle = (i / 30) * Math.PI * 2; - const r = 50 + Math.random() * 40; - const x = centerX + Math.cos(angle) * r; - const y = baseY - 280 + Math.sin(angle) * r * 0.5; - const len = 8 + Math.random() * 8; + // 装饰性卷曲枝条 - 参考第一张图的藤蔓感 + ctx.setStrokeStyle('#3a6a2a'); + ctx.setLineWidth(2); + for (let i = 0; i < 12; i++) { + const angle = (i / 12) * Math.PI * 2; + const startR = 60; + const startX = centerX + Math.cos(angle) * startR; + const startY = baseY - 320 + Math.sin(angle) * startR * 0.5; ctx.beginPath(); - ctx.moveTo(x, y); - ctx.lineTo(x + Math.cos(angle) * len, y + Math.sin(angle) * len); + ctx.moveTo(startX, startY); + // 卷曲线条 + for (let t = 0; t < 1; t += 0.1) { + const r = startR + t * 40; + const a = angle + t * 2; + const x = centerX + Math.cos(a) * r; + const y = baseY - 320 + Math.sin(a) * r * 0.5 - t * 30; + ctx.lineTo(x, y); + } ctx.stroke(); } - // 装饰果实(松果) - ctx.setFillStyle('#8b6914'); - for (let i = 0; i < 6; i++) { - const angle = (i / 6) * Math.PI * 2 + 0.5; - const r = 45 + Math.random() * 30; + // 彩色装饰物 - 参考第一张图的彩色叶子/果实 + const decorations = [ + { color: '#ff6b6b', size: 8 }, // 红 + { color: '#ffd93d', size: 7 }, // 黄 + { color: '#6bcf7f', size: 7 }, // 绿 + { color: '#4d96ff', size: 6 }, // 蓝 + { color: '#ff9f43', size: 7 }, // 橙 + { color: '#a55eea', size: 6 }, // 紫 + ]; + + for (let i = 0; i < 25; i++) { + const dec = decorations[i % decorations.length]; + const angle = (i / 25) * Math.PI * 2 + Math.random() * 0.5; + const r = 50 + Math.random() * 60; const x = centerX + Math.cos(angle) * r; - const y = baseY - 260 + Math.sin(angle) * r * 0.4; + const y = baseY - 350 + Math.sin(angle) * r * 0.6 + Math.sin(time * 2 + i) * 3; - // 画松果形状 + ctx.setFillStyle(dec.color); ctx.beginPath(); - ctx.ellipse(x, y, 5, 8, angle, 0, Math.PI * 2); + // 画叶子形状 + ctx.ellipse(x, y, dec.size, dec.size * 1.5, angle, 0, Math.PI * 2); ctx.fill(); + + // 高光 + ctx.setFillStyle('rgba(255,255,255,0.3)'); + ctx.beginPath(); + ctx.ellipse(x - 2, y - 2, dec.size * 0.3, dec.size * 0.5, angle, 0, Math.PI * 2); + ctx.fill(); + } + + // 发光效果 - 参考第三张图 + ctx.save(); + ctx.setGlobalAlpha(0.15 + Math.sin(time) * 0.05); + const glowGradient = ctx.createRadialGradient( + centerX, baseY - 350, 0, + centerX, baseY - 350, 150 + ); + glowGradient.addColorStop(0, '#ffff00'); + glowGradient.addColorStop(0.5, '#88ff00'); + glowGradient.addColorStop(1, 'transparent'); + ctx.setFillStyle(glowGradient); + ctx.beginPath(); + ctx.arc(centerX, baseY - 350, 150, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + }, + + // 绘制草丛 + drawGrass(ctx, centerX, baseY) { + ctx.setStrokeStyle('#3a6a2a'); + ctx.setLineWidth(2); + for (let i = 0; i < 15; i++) { + const x = centerX - 100 + i * 15; + const h = 15 + Math.random() * 20; + const sway = Math.sin(Date.now() / 500 + i) * 3; + + ctx.beginPath(); + ctx.moveTo(x, baseY); + ctx.quadraticCurveTo(x + sway, baseY - h * 0.5, x + sway * 2, baseY - h); + ctx.stroke(); } }, - // 绘制樱花树(姻缘树) - drawSakuraTree(ctx, centerX, baseY) { - // 树干 - 更纤细优雅 - ctx.setFillStyle('#4a3728'); + // 绘制卷曲树枝 + drawCurlyBranch(ctx, startX, startY, angle, length, width, curl) { + const rad = (angle * Math.PI) / 180; + const endX = startX + Math.sin(rad) * length; + const endY = startY - Math.cos(rad) * length; + + ctx.setStrokeStyle('#5a3a2a'); + ctx.setLineWidth(width); + ctx.setLineCap('round'); + ctx.beginPath(); - ctx.moveTo(centerX - 12, baseY); + ctx.moveTo(startX, startY); + // 三次贝塞尔曲线,带卷曲 + const cp1x = startX + Math.sin(rad) * length * 0.3 + curl; + const cp1y = startY - Math.cos(rad) * length * 0.3; + const cp2x = startX + Math.sin(rad) * length * 0.7 - curl; + const cp2y = startY - Math.cos(rad) * length * 0.7; + ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY); + ctx.stroke(); + + // 小分支 + if (width > 2) { + const midX = (startX + endX) / 2; + const midY = (startY + endY) / 2; + this.drawCurlyBranch(ctx, midX, midY, angle - 30, length * 0.5, width - 1, curl * 0.5); + this.drawCurlyBranch(ctx, midX, midY, angle + 30, length * 0.5, width - 1, curl * 0.5); + } + }, + + // 绘制姻缘树(樱花树) + drawSakuraTree(ctx, centerX, baseY) { + const time = Date.now() / 1000; + + // 地面装饰 + this.drawGrass(ctx, centerX, baseY); + + // 纤细树干 + ctx.setFillStyle('#4a3a2a'); + ctx.beginPath(); + ctx.moveTo(centerX - 15, baseY); ctx.bezierCurveTo( - centerX - 10, baseY - 60, - centerX - 6, baseY - 120, - centerX - 4, baseY - 160 + centerX - 12, baseY - 60, + centerX - 8, baseY - 120, + centerX - 5, baseY - 180 ); - ctx.lineTo(centerX + 4, baseY - 160); + ctx.lineTo(centerX + 5, baseY - 180); ctx.bezierCurveTo( - centerX + 6, baseY - 120, - centerX + 10, baseY - 60, - centerX + 12, baseY + centerX + 8, baseY - 120, + centerX + 12, baseY - 60, + centerX + 15, baseY ); ctx.closePath(); ctx.fill(); - // 主枝干 - 更分散 - this.drawBranch(ctx, centerX, baseY - 160, -70, 90, 2); - this.drawBranch(ctx, centerX, baseY - 160, 70, 90, 2); - this.drawBranch(ctx, centerX, baseY - 180, -40, 70, 2); - this.drawBranch(ctx, centerX, baseY - 180, 40, 70, 2); - this.drawBranch(ctx, centerX, baseY - 200, 0, 60, 2); - - // 樱花树冠 - 粉色系 - const sakuraLayers = [ - { offsetY: -220, radiusX: 90, radiusY: 50, color: '#ffb7c5', alpha: 0.6 }, - { offsetY: -260, radiusX: 75, radiusY: 45, color: '#ffc9d4', alpha: 0.55 }, - { offsetY: -300, radiusX: 60, radiusY: 40, color: '#ffdbe3', alpha: 0.5 }, - { offsetY: -330, radiusX: 45, radiusY: 35, color: '#ffedf2', alpha: 0.45 }, + // 分散树枝 + const branches = [ + { angle: -60, length: 90, width: 3 }, + { angle: 60, length: 90, width: 3 }, + { angle: -35, length: 80, width: 2 }, + { angle: 35, length: 80, width: 2 }, + { angle: 0, length: 70, width: 2 }, ]; - sakuraLayers.forEach(layer => { + branches.forEach(b => { + this.drawCurlyBranch(ctx, centerX, baseY - 180, b.angle, b.length, b.width, 15); + }); + + // 樱花树冠 - 粉色蓬松 + const sakuraLayers = [ + { offsetY: -260, radiusX: 100, radiusY: 60, color: '#ffb7c5', alpha: 0.5 }, + { offsetY: -300, radiusX: 85, radiusY: 55, color: '#ffc9d6', alpha: 0.45 }, + { offsetY: -340, radiusX: 70, radiusY: 50, color: '#ffdbe5', alpha: 0.4 }, + { offsetY: -370, radiusX: 55, radiusY: 40, color: '#ffedf4', alpha: 0.35 }, + ]; + + sakuraLayers.forEach((layer, idx) => { ctx.save(); ctx.setGlobalAlpha(layer.alpha); ctx.setFillStyle(layer.color); ctx.beginPath(); - for (let angle = 0; angle < Math.PI * 2; angle += 0.1) { - const noise = Math.sin(angle * 7) * 10 + Math.cos(angle * 4) * 6; + for (let angle = 0; angle < Math.PI * 2; angle += 0.08) { + const noise = Math.sin(angle * 8 + idx) * 10 + Math.cos(angle * 5 + idx) * 6; const rx = layer.radiusX + noise; const ry = layer.radiusY + noise * 0.5; const x = centerX + Math.cos(angle) * rx; @@ -573,94 +699,136 @@ Page({ ctx.restore(); }); - // 樱花花瓣 - ctx.setFillStyle('#fff0f5'); - for (let i = 0; i < 20; i++) { - const angle = Math.random() * Math.PI * 2; - const r = 40 + Math.random() * 50; - const x = centerX + Math.cos(angle) * r; - const y = baseY - 260 + Math.sin(angle) * r * 0.5; - const size = 3 + Math.random() * 4; + // 飘落樱花 + for (let i = 0; i < 15; i++) { + const x = centerX - 80 + Math.random() * 160; + const y = baseY - 350 + Math.random() * 100 + Math.sin(time + i) * 10; + const size = 4 + Math.random() * 4; + const rotation = Math.random() * Math.PI; - // 画花瓣形状 + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rotation); + ctx.setFillStyle('#fff0f5'); ctx.beginPath(); - ctx.ellipse(x, y, size, size * 1.5, angle, 0, Math.PI * 2); + // 樱花花瓣形状 + ctx.moveTo(0, -size); + ctx.bezierCurveTo(size, -size, size, size * 0.5, 0, size); + ctx.bezierCurveTo(-size, size * 0.5, -size, -size, 0, -size); ctx.fill(); + ctx.restore(); } }, - // 绘制竹子(事业树) + // 绘制事业树(许愿竹,参考第二张图) drawBambooTree(ctx, centerX, baseY) { - // 竹竿 - 多根 - const bambooPositions = [ - { offsetX: -30, height: 200 }, - { offsetX: 0, height: 240 }, - { offsetX: 30, height: 180 }, + const time = Date.now() / 1000; + + // 地面装饰 - 小石子 + ctx.setFillStyle('#8a8a7a'); + for (let i = 0; i < 8; i++) { + const x = centerX - 60 + i * 15; + const size = 3 + Math.random() * 4; + ctx.beginPath(); + ctx.arc(x, baseY - 5, size, 0, Math.PI * 2); + ctx.fill(); + } + + // 竹竿 - 多根,参考第二张图 + const bambooStalks = [ + { offsetX: -40, height: 220, width: 8 }, + { offsetX: -15, height: 260, width: 10 }, + { offsetX: 10, height: 240, width: 9 }, + { offsetX: 35, height: 200, width: 7 }, ]; - bambooPositions.forEach(pos => { - const x = centerX + pos.offsetX; - const topY = baseY - pos.height; + bambooStalks.forEach((stalk, idx) => { + const x = centerX + stalk.offsetX; + const topY = baseY - stalk.height; + const sway = Math.sin(time * 2 + idx) * 3; - // 竹竿主体 - ctx.setFillStyle('#5a8a3a'); + // 竹竿主体 - 带摇摆 + ctx.setFillStyle('#6aaa4a'); ctx.beginPath(); - ctx.moveTo(x - 6, baseY); - ctx.lineTo(x - 5, topY); - ctx.lineTo(x + 5, topY); - ctx.lineTo(x + 6, baseY); + ctx.moveTo(x - stalk.width / 2, baseY); + ctx.quadraticCurveTo( + x - stalk.width / 2 + sway, baseY - stalk.height / 2, + x - stalk.width / 2 + sway * 2, topY + ); + ctx.lineTo(x + stalk.width / 2 + sway * 2, topY); + ctx.quadraticCurveTo( + x + stalk.width / 2 + sway, baseY - stalk.height / 2, + x + stalk.width / 2, baseY + ); ctx.closePath(); ctx.fill(); // 竹节 - ctx.setStrokeStyle('#4a7a2a'); + ctx.setStrokeStyle('#5a9a3a'); ctx.setLineWidth(2); - for (let y = baseY - 40; y > topY; y -= 40) { + for (let y = baseY - 30; y > topY + 20; y -= 35) { + const nodeSway = sway * (1 - (baseY - y) / stalk.height); ctx.beginPath(); - ctx.moveTo(x - 6, y); - ctx.lineTo(x + 6, y); + ctx.moveTo(x - stalk.width / 2 + nodeSway, y); + ctx.lineTo(x + stalk.width / 2 + nodeSway, y); ctx.stroke(); } - // 竹叶 - ctx.setFillStyle('#6aaa4a'); - for (let i = 0; i < 8; i++) { - const leafY = topY + 20 + i * 20; + // 竹叶 - 更自然 + ctx.setFillStyle('#7aba5a'); + for (let i = 0; i < 6; i++) { + const leafY = topY + 15 + i * 25; const side = i % 2 === 0 ? 1 : -1; + const leafSway = sway * 0.5 + Math.sin(time * 3 + i) * 2; + + ctx.save(); + ctx.translate(x + side * 5, leafY); + ctx.rotate(side * 0.3 + leafSway * 0.02); ctx.beginPath(); - ctx.moveTo(x, leafY); - ctx.quadraticCurveTo( - x + side * 25, leafY - 10, - x + side * 40, leafY + 5 - ); - ctx.quadraticCurveTo( - x + side * 25, leafY + 15, - x, leafY + 10 - ); + ctx.moveTo(0, 0); + ctx.quadraticCurveTo(side * 30, -15, side * 50, 0); + ctx.quadraticCurveTo(side * 30, 15, 0, 10); ctx.closePath(); ctx.fill(); + + ctx.restore(); } }); - }, - - // 绘制树枝 - drawBranch(ctx, startX, startY, angle, length, width) { - const rad = (angle * Math.PI) / 180; - const endX = startX + Math.sin(rad) * length; - const endY = startY - Math.cos(rad) * length; - ctx.setStrokeStyle('#3d2914'); - ctx.setLineWidth(width); - ctx.setLineCap('round'); - - ctx.beginPath(); - ctx.moveTo(startX, startY); - // 用曲线让树枝更自然 - const midX = startX + Math.sin(rad) * length * 0.5 + (Math.random() - 0.5) * 10; - const midY = startY - Math.cos(rad) * length * 0.5; - ctx.quadraticCurveTo(midX, midY, endX, endY); - ctx.stroke(); + // 许愿条挂在竹子上 - 参考第二张图 + if (this.ribbons && this.ribbons.length > 0) { + this.ribbons.forEach((ribbon, idx) => { + const stalk = bambooStalks[idx % bambooStalks.length]; + const x = centerX + stalk.offsetX; + const y = baseY - 80 - (idx % 5) * 30; + + // 画许愿条 - 长方形纸片 + ctx.save(); + ctx.translate(x, y); + ctx.rotate(Math.sin(time * 2 + idx) * 0.1); + + // 纸片 + ctx.setFillStyle(ribbon.color); + ctx.fillRect(-8, 0, 16, 40); + + // 顶部挂绳 + ctx.setStrokeStyle('#8a6a4a'); + ctx.setLineWidth(1); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, -10); + ctx.stroke(); + + // 文字 + ctx.setFillStyle('#ffffff'); + ctx.setFontSize(10); + ctx.setTextAlign('center'); + ctx.fillText(ribbon.wish.content.substring(0, 4), 0, 25); + + ctx.restore(); + }); + } }, // 绘制丝带