iT邦幫忙

2021 iThome 鐵人賽

DAY 17
0
Modern Web

從零開始打造網頁遊戲-造輪子你也辦的到!系列 第 17

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);
};

上一篇
Chpater3 今天來學習畫一棵樹(II)以有規律的隨機畫出擬真的樹枝 原來畫一顆樹不難嘛!
下一篇
Chpater3 今天來學習畫一棵樹(IV)淺談效能和演算法,以迭代取代遞迴吧!
系列文
從零開始打造網頁遊戲-造輪子你也辦的到!31

1 則留言

1
Mizok
iT邦新手 5 級 ‧ 2021-09-25 12:56:09

可以敲碗效能議題的部分嗎XD

剛好我昨天晚上發文後在看尾遞迴、迭代和Stack,今天就實作了XD,不是非常專業,麻煩鞭小力一點。
https://ithelp.ithome.com.tw/articles/10272091

我要留言

立即登入留言