- 祈福树:粗壮扭曲树干、螺旋纹理、卷曲树枝、蓬松树冠、彩色装饰、发光效果 - 姻缘树:纤细树干、分散树枝、粉色蓬松树冠、飘落樱花 - 事业树:多根竹竿、竹节、摇摆竹叶、许愿条挂饰 - 添加草丛地面装饰 - 树枝使用三次贝塞尔曲线,带卷曲效果
This commit is contained in:
+334
-166
@@ -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 x = centerX + Math.cos(angle) * r;
|
||||
const y = baseY - 260 + Math.sin(angle) * r * 0.4;
|
||||
// 彩色装饰物 - 参考第一张图的彩色叶子/果实
|
||||
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 - 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;
|
||||
// 许愿条挂在竹子上 - 参考第二张图
|
||||
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.setStrokeStyle('#3d2914');
|
||||
ctx.setLineWidth(width);
|
||||
ctx.setLineCap('round');
|
||||
// 画许愿条 - 长方形纸片
|
||||
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(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.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();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 绘制丝带
|
||||
|
||||
Reference in New Issue
Block a user