四月,很快的到了。说到四月,我想到了一句话“四月是你的谎言”,也不知道出处在哪里。其实四月果然还是花开花落的季节。三月月末在思考四月的banner的时候,联想到了一个游戏 Prune,中文翻译是“修剪艺术”。很喜欢里面的树的生长方式,于是就有了 四月,花开花落 的banner。为了实现这个banner,也参考了一些代码。

静态的树

前期在取材的时候从 Github 上找到一颗静态的树,看代码实现的很不错,基本上符合我的要求。

静态的树

然后想参照这个模板改成一颗动态的树。

动态的树

数据结构

树的单元为树干,树干的定义如下:

1
2
3
4
5
6
7
8
9
var newLine = {
childs: [], // 子树干
angle: angle, // 生长角度
thickness: thickness, // 树干半径
length: length, // 树干长度
time: 0, // 生长时间
depth: depth, //深度
growtime: growtime //生长时间
};

相比静态的树缺少了开始坐标和结束坐标,因为树干的开始位置需要根据父亲树干的结束位置确定,而结束位置根据开始位置和生长时间来确定。所以这两部分信息需要在生长过程中动态计算。

树干生长

树干的生长分为三个阶段:生长阶段、分枝阶段、开花阶段。

  • 生长阶段只是累积已经经过的时间到time中;
  • 若time超过growtime/2的时候,进入分枝阶段,这时候生成两个新的树干添加到树中;
  • 只有最外层的树干才有开花阶段,当最外层的树干生长完毕后,在树干结束位置画一个圆形作为花朵。

整个树干生长由一个循环控制完成。当然树干不能无限制的生长,需要限定层数,depth作为树干的层数,当depth==0时表示最外层的树干,这种树干只开花,不再进行分枝。

树干花朵绘制

由于树干的生长,无法确定所有树干的起始和结束位置,需要通过递归从根部开始逐步确定每个树干的开始和结束位置。伪代码如下:

1
2
3
4
5
6
7
8
9
10
function drawTree(树干, 开始x, 开始y) {
if (树的深度不为0) {
根据生长时间和总长度计算结束坐标 x 和 y
从开始和结束绘制树干
递归绘制子树干
} else {
绘制花朵
}
}

另外在计算结束坐标的时候,使用了 Circ.easeOut 缓动函数,使树干的生长呈现一种先快后慢的效果。缓动函数的选择部分,可以参考【参考文献】中提到的内容。

绘制树干只是在开始和结束位置画一条线。绘制花朵是画一个填充颜色的圆。

代码和效果

这里介绍了大体的思路,具体细节可以参考代码实现。

会开花的树

总结

大体实现的效果就是这样,但是感觉性能问题也比较突出,移动设备上有卡顿,而且CPU也无缘无故的爆转。如果看官有一些想法,欢迎与我进行交流。

参考文献