iT邦幫忙

2021 iThome 鐵人賽

DAY 12
0
Modern Web

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

Chapter2 - 重構完了 還是覺得物件很複雜嗎?直接上圖,就明白物件讓你更輕鬆

  • 分享至 

  • xImage
  •  

前言

前天談到如何避免程式碼散落四處、維護困難,帶大家改寫了物件格式,過了兩天,我突然想到,當時花太多篇幅在解釋觀念和舉例子,結果沒有實際把程式碼秀給大家看,果然還是會有點難懂,對吧?於是我以改寫後的程式碼和改寫前的程式碼擺在一起,讓大家更直接看看物件模型的優勢在哪裡。

落葉的建構式

這一整段代碼是改寫後,可以針對落葉動畫修改和維護的地方,並且在下方的五張比較圖中,分別採用部分截圖,拿去做比較,因此先上完整代碼:

function leafMaker(x, y, lifeTime){
    // 基本屬性
    this.pointX = x;
    this.pointY = y;
    this.img = leafImg;
    this.width = 200;
    this.height = 200 * this.img.height / this.img.width;
    this.isWaitng = false;

    // 落葉跟隨滑鼠的屬性
    this.originX = x;
    this.originY = y;
    this.period = 90;
    this.timer = 0;

    // 落葉自然落下的屬性
    this.beginX = x;
    this.beginY = y;
    this.timestamp = Date.now();
    this.lifeTime = lifeTime;
    this.rotateTheta = 0 / 180 * Math.PI;
    this.rotateOmega = 40 / 180 * Math.PI;
    this.revolveTheta = 0 / 180 * Math.PI;
    this.revolveOmega = 90 / 180 * Math.PI;

    this.fall = function(context, dT){
        let rotateNow = this.rotateTheta + this.rotateOmega * dT;
        let revolveNow = this.revolveTheta + this.revolveOmega * dT;

        let A = Math.sin(revolveNow);
        let B = Math.cos(revolveNow);
        let C = Math.sin(revolveNow * 1.5);
        let D = Math.cos(revolveNow * 1.5);
        this.pointX = this.beginX + 500 * A;
        this.pointY = this.beginY + 200 * C
                                    + 100 * dT;
        if(this.img.complete){
            context.save();
            context.translate(this.pointX, this.pointY);
            context.font = '32px IBM Plex Sans Arabic';
            context.strokeStyle = 'rgba(179, 198, 213, 1)';
            let ts = Math.floor((this.lifeTime - dT)*10)/10;
            context.textAlign = 'center';
            context.strokeText(ts , 0, -150);
            context.rotate(rotateNow);
            context.drawImage(this.img, -this.width/2, -this.height/2, this.width, this.height);
            context.restore();
        }
    }
    this.Refollow = function(frames){
        this.originX = this.pointX;
        this.originY = this.pointY;
        this.timer = frames;
        this.period = frames;
    }
    this.follow = function(context, targetX, targetY){
        let dX = targetX - this.originX;
        let dY = targetY - this.originY;
        if(this.timer > 0){
            let t = this.timer;
            let p = this.period;
            let linear = 1/p;
            let easeout = Math.pow(t/p, 2) - Math.pow((t-1)/p, 2);
            let easein = Math.pow(1 - (t-1)/p, 2) - Math.pow(1 - t/p, 2);
            let a = input.linear;
            let b = input.easein;
            let c = input.easeout;
            this.pointX+= (a * linear + b * easein + c * easeout) * dX;
            this.pointY+= (a * linear + b * easein + c * easeout) * dY;
        }
        else{
            this.originX = this.pointX;
            this.originY = this.pointY;
        }
        context.drawImage(this.img, this.pointX-this.width/2, this.pointY-this.height/2, this.width, this.height);
        this.timer--;
    }
}

用物件方法(左) vs 單純用變數(右)

1.初始變數定義

首先從初始值來看,並沒有太大的不同,比較不明顯的好處就是,每一個落葉的相關變數都是它的屬性,比如說座標X就要用leaf.originX,可以一目了然,知道這個變數屬於leaf,相比之下,右邊的originX的名稱無法和落葉產生聯繫,有機會讓人混淆。
https://ithelp.ithome.com.tw/upload/images/20210919/20135197Na5cX231W6.jpg

2.動畫框架(流程)

接著我們從框架去看這件事,因為在閱讀程式碼時,一定從流程步驟開始,去了解「動畫會怎麼循環?」以及「步驟分別有哪些?」,因此這個區域的可讀性相當重要,我們可以看到左側位於32-47行,內容相當簡潔,另一方面單純用變數的做法,右側卻佔據了32-91行,閱讀上較花時間,雖然可以再包成一個函式拉出去寫,然而,有一部分的落葉動畫被寫在這裡面,未來每每要修改落葉的動畫,就必須到框架這一個區塊的程式碼,尋找它,會有額外的搜尋成本。
https://ithelp.ithome.com.tw/upload/images/20210919/20135197lbTwQTDcqQ.jpg

3.自然落下方法

這邊,我們可以看到,左側的方法是緊接著初始變數定義後,查找方便,而右側是寫在剛剛說的「動畫框架」裡面,相對凌亂,即使如剛剛所說的再包成一個函式,若要查找初始變數的定義,還得去翻開另一份js檔案。
https://ithelp.ithome.com.tw/upload/images/20210919/20135197ZeB8HPoWml.jpg

4.跟隨滑鼠方法

同上所述,左側的方法還是在物件的定義裡面,佔據優勢
https://ithelp.ithome.com.tw/upload/images/20210919/20135197w3WPlx64pM.jpg

5.事件監聽

事件監聽也是流程的一環,在UX的階段可能去探討User story、設計functional map等等,會有很豐富的使用者的體驗等著去處理,因此對於流程的設計,若能一眼看出每個部件代表的意義,可以避免浪費時間在探討細節。左側可以看到mousemove對應了leaf.Refollow,而click又對應了new leafmaker,相當簡潔;而右側只看到了一大串對於變數的賦值。
https://ithelp.ithome.com.tw/upload/images/20210919/20135197U9zwx8atLy.jpg

結論

左側相當漂亮的利用物件的設計,把落葉的相關程式碼集中在一起,還保留了擴充的彈性;而右側把落葉的動畫分別散佈到各處,每次要修改,就要一次打開三個文件,可謂圖窮匕見,更不用說現在還只是一個落葉、兩種動畫,未來如果有一個角色、兩個寵物、三個道具、四種動畫,不知道右邊的程式碼會有多亂了!

雖然說是重構,不過我簡化了滑鼠的監聽事件(取消長壓的功能),主要功能都一樣,可參考:
https://jerry-the-potato.github.io/Chapter2-demo2-object/

後記

至此,第二章順利落幕了,其實一開始就帶大家用物件撰寫程式碼也不是不行,只是過往的經驗告訴我,一開始就給予太多,只會是揠苗助長,畢竟寫程式響往的是一種自由,若要求大家從物件學起,會成為一種限制,結果就是大家背下物件的用法,卻不知道這麼做有什麼意義,還覺得很麻煩,這樣也是有可能的。

很多人寫文章都認為他懂得你也懂,是有預設條件的,當然這樣也沒問題,畢竟就是程式領域的彼此作交流,只是對於入門者來說,就不太友善了,在我的自學階段,也是中間某個時候開始,我才逐漸知道可以去看mdn文件,學習讀文件的報酬率更大,因為他會預設你還不太懂,講解得清楚又相對容易,如果看不懂,建議去問人,雖然沒有人教怎麼讀文件,但是問著問著,就會找到方法了!

這10天以來也不確定最後我能幫到誰,也許,是那個,花時間複習官方文件和仿間說法的我自己吧!


上一篇
Chapter2 - 用物件看真實世界(II)仍然對物件感到疑惑嗎?用你最愛點的豚骨拉麵做比喻
下一篇
Chapter3 - 動感DJ續篇 進一步操作陣列,讓音樂嗨起來
系列文
從零開始打造網頁遊戲-造輪子你也辦的到!31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言