Chpater3 今天来学习画一棵树(III)终於让树摇摆起来罗!原来递回与碎形可以塑造大自然之美

先补上Demo

将前两天画好的树枝骨干,搭配第二章学的动画效果,就能让树开始摆动了:
https://jerry-the-potato.github.io/Chapter3-demo-object/
当然,由於参数ab设置过头,滑鼠移动到边边角角时,整棵树变得很奇怪(我看像是稻穗!不,是棋盘!)

看完之後,是不是有发现卡卡的呢?(如果没有可能是你的绘图处理器太给力了XD)
原因就出在昨天卖的一个关子:「单纯用如此简单的物件结构,会导致过多重复的代码」
用Edge浏览器可以看到,JS占用了相当大的容量和CPU:

https://ithelp.ithome.com.tw/upload/images/20210924/20135197m0XNhUuFNT.png

已经会导致掉侦的程度了,真的是很母汤呢!

试试看Classes

把结构修改後:
https://ithelp.ithome.com.tw/upload/images/20210924/20135197CbG821zaG5.png
改动的地方非常少:
https://ithelp.ithome.com.tw/upload/images/20210924/20135197UPGX9sVcI5.jpg

Class把原本的建构式拆出来写,结构上可以较清楚在实例化的当下会建构哪些变数

回到物件

其实不是物件不好用,而是还没有跟大家说prototype的概念,这个下礼拜会有两个篇幅的故事来解释,今天就简单用现实生活比喻,糖尿病会遗传这个是大家都知道的,但是我现在又没有糖尿病,怎麽知道未来有没有机会得呢?医生会去看我的家族史,往上追本溯源,看有没有人得过糖尿病。

在这个案例中,我遗传(继承)了我家族的疾病,而为了评估得病的风险,就要去看生我的人(爸爸的prototype),父辈祖辈一直往上,因此,只要我家族有人得过遗传病,接下来好几代的子孙都有得病风险。

以程序的观点来说,就是当我要把整个家族的病历给归档的时候,或是上帝要决定谁会得病(虚拟世界!?),最麻烦的方式就是给每个个体做标记,一出生的时候就写说,这个人有10%、那个人有20%,如果是这样,光是一个糖尿病就要给每个人都贴过一次标签,要设定无数个变数。反过来讲,最简单的方式,就是只标记那个得糖尿病的爷爷,这样,虽然我没办法直接知道每个个体的得病机率,但是可以透过他们跟爷爷的血缘关系(继承链),来得知结果,这样一来就只需要在爷爷身上设定一个变数,就可以解决问题了。

观念说清楚了,就可以实作:
https://ithelp.ithome.com.tw/upload/images/20210924/20135197GiqmKS5zCE.png

效能也更好了
https://ithelp.ithome.com.tw/upload/images/20210924/20135197h7qbldhXAB.png

归根结柢也是因为给每节树枝设的变数太多,等到时候开始做游戏再来简化

写完之後发现关於效能议题观念错误,结果通篇废文(尴尬)

因为有不少变因,像是:
像是滑鼠点击时产生新的树,但是旧的树还没有触发JS的自动回收机制,假设一直连点滑鼠,JS堆积大小会来到20MB,过一阵子後,才会看到降回5MB左右,这是由於myTree这个变数不再指向那些旧的树,被JS自动回收机制认为是垃圾,才清除掉,这期间存在一个缓冲期。

以及如果只是重新整理页面会有残留的JS堆积,所以以上的图片数据皆不准!实际上实测的结果并没有什麽差异。

不过时间已经不够再深入研究了ww,日後再回来修改,继续我们的画树之旅吧!

来处理树枝的细节

帮树枝添加层次

这边我们用最懒的方法,直接让透明度渐变,使树干到树枝末端呈现透明度1~0.5的渐变:

let alpha = 0.5 + 0.5 * (r / (window.innerHeight/3));
context.strokeStyle = 'rgba(179, 198, 213, ' + alpha + ')';

让树慢慢长出来

为了看起来不那麽突兀,要让树从一个原点生出,当初原点是设置在(WIDTH/2, HEIGHT),因此减去这个位置,取得相对座标,接着设置一个this.grow参数,让树再建立之初以0.1开始,慢慢增加,若每次增加0.01会显得平铺直叙太无聊,因此模仿一下缓冲函式,让this.grow成长到一定程度後,就开始变慢:

let x = (this.startX - WIDTH / 2) * this.grow + WIDTH / 2,
    y = (this.startY - HEIGHT) * this.grow + HEIGHT,
    r = this.r * this.grow,
this.grow = Math.min(this.grow + 0.01 / (0.8 + 2 * this.grow), 1);

以上完整程序码

Tree.prototype.Draw = function () {
    let x = (this.startX - WIDTH / 2) * this.grow + WIDTH / 2,
        y = (this.startY - HEIGHT) * this.grow + HEIGHT,
        r = this.r * this.grow,
        theta = this.theta;
    context.beginPath();
    context.moveTo(x, y);
    context.lineTo(x + r * Math.cos(theta / 180 * Math.PI),
                   y + r * Math.sin(theta / 180 * Math.PI));
    context.lineWidth = 1 + r / 50;
    let alpha = 0.5 + 0.5 * (r / (window.innerHeight/3));
    context.strokeStyle = 'rgba(179, 198, 213, ' + alpha + ')';
    context.stroke();
    // 如果有子树枝,就继续呼叫所有的子树枝
    if (this.son) this.son.forEach(branch => branch.Draw()); // 递回
    this.grow = Math.min(this.grow + 0.01 / (0.8 + 2 * this.grow), 1);
};

<<:  Day 23:优与劣

>>:  D23 第九周作业的心得

day25_如何采购 ARM 版本的 Windows 电脑呢

ARM 版本的 Windows 该怎麽买呢? 主要的产品为 Surface Pro X ,这是一款微...

Swift 新手-如何使用 Xcode 建立专案?

Xcode 版本 12.5 介面 点选 ios app 建立专案范本,范本有内建预设程序码,协助快速...

Day28 ( 游戏设计 ) 吃角子老虎机

吃角子老虎机 教学原文参考:吃角子老虎机 这篇文章会介绍如何使用「函式」、「计次回圈」、「随机取数」...

Day 15 : PHP - 如何在phpMyAdmin手动建立资料表?char和varchar又该如何选择?

如标题,这篇想教大家如何在phpMyAdmin里「手动」建立资料表 还有char和varchar的差...

Day 03 - 环境安装(下) JDK & Spring Tool Suite

环境安装的最後一个环节,就是安装我们的开发工具,本篇教学使用Spring Tool Suite (S...