建立太空遊戲 Part 1:簡介 影片 課前測驗 課前測驗 遊戲開發中的繼承(Inheritance)與組合(Composition) 在之前的課程中,因為專案較小的規模,我們不需要去擔憂應用程式的設計結構。然而,當你的應用程式規模越來越大時,結構的選擇就是一大課題。在 JavaScript 中,有兩種大方向來建立龐大的應用程式:組合(Composition)與繼承(Inheritance)。它們有各自的優缺點,我們會藉由遊戲內容來進行說明。 ✅ 其中一本有名的程式設計用書是有關於設計模式。 在遊戲中你會有 ,顯示在畫面中。這代表它們在笛卡爾座標系中有各自的位置,以 與 座標點定義。

在之前的課程中,因為專案較小的規模,我們不需要去擔憂應用程式的設計結構。然而,當你的應用程式規模越來越大時,結構的選擇就是一大課題。在 JavaScript 中,有兩種大方向來建立龐大的應用程式:組合(Composition)與繼承(Inheritance)。它們有各自的優缺點,我們會藉由遊戲內容來進行說明。
✅ 其中一本有名的程式設計用書是有關於設計模式。
在遊戲中你會有遊戲物件,顯示在畫面中。這代表它們在笛卡爾座標系中有各自的位置,以 x 與 y 座標點定義。當你在開發遊戲時,你會注意到所有的遊戲物件都有一套標準的規範,和大多數的遊戲相似,通常會有這些元素:
x 與 y。死亡或是被摧毀的布林訊號傳遞給遊戲引擎,告知物件不再需要被描繪出來。✅ 想想看遊戲小精靈(Pac-Man)。你能辨別出符合上述清單的其中四種物件嗎?
以上的敘述皆在表達遊戲物件所進行的行為。那我們該如何去編寫它們呢?我們可以使用方法(methods)連接 classes 或是物件(objects)來表達這些行為。
Classes
這個想法是結合 classes 與繼承的方式來在 class 中添加特定行為。
✅ 繼承是一個重要概念。在有關繼承的 MDN 文章中學習更多內容。
以程式碼來表達的話,一個遊戲物件通常會呈現這種形式:
//設定 class GameObject class GameObject { constructor(x, y, type) { this.x = x; this.y = y; this.type = type; } } //這個 class 會繼承 GameObject 中 class 內容 class Movable extends GameObject { constructor(x,y, type) { super(x,y, type) } //這個可移動物件可以在畫面上移動 moveTo(x, y) { this.x = x; this.y = y; } } //這是特定的 class 繼承 Movable class,它能使用所有繼承到的屬性內容 class Hero extends Movable { constructor(x,y) { super(x,y, 'Hero') } } //另一方面,這個 class 只繼承到 GameObject 的內容 class Tree extends GameObject { constructor(x,y) { super(x,y, 'Tree') } } //英雄可以移動...... const hero = new Hero(); hero.moveTo(5,5); //但樹木卻不能 const tree = new Tree();
✅ 花點時間重新構思小精靈(Pac-Man)的主角,或是 Inky、Pinky 與 Blinky 這幾隻鬼魂。它們該如何以 JavaScript 表現?
組合
另一種處理物件繼承的方式為組合(Composition)。物件以這種方式呈現它們的行為:
//建立常數 gameObject const gameObject = { x: 0, y: 0, type: '' }; //...與常數 movable const movable = { moveTo(x, y) { this.x = x; this.y = y; } } //常數 movableObject 是 gameObject 與 movable 的組合 const movableObject = {...gameObject, ...movable}; //利用函式建立新的英雄,繼承 movableObject 的內容 function createHero(x, y) { return { ...movableObject, x, y, type: 'Hero' } } //...與常駐物件只繼承 gameObject 的屬性 function createStatic(x, y, type) { return { ...gameObject x, y, type } } //建立可以移動的英雄 const hero = createHero(10,10); hero.moveTo(5,5); //和建立只能佇立於此的樹木 const tree = createStatic(0,0, 'Tree');
我該使用哪一種設計模式?
這都取決於你選擇何種設計模式。JavaScript 支援這兩種範例。
--
另一種在遊戲開發中常見的設計模式負責處理玩家的遊戲表現與遊戲體驗。
✅ Pub/Sub 全名為 'publish-subscribe'
這個設計模式將應用程式內不同的模組分開處理,讓彼此不知道彼此的行為。為何要這樣做?這讓我們總觀上更輕易地了解各個模組的行為。也可以在你想要時輕易地改變模組的行為模式。我們該如何實踐它呢?我們先建立這幾個概念:
KEY_PRESSED_ENTER。實踐方法雖小,但這是功能強大的設計方式。這是它的建立方式:
//設定 EventEmitter class 容納監聽者 class EventEmitter { constructor() { this.listeners = {}; } //當訊息接收時,讓監聽者處理它的負載 on(message, listener) { if (!this.listeners[message]) { this.listeners[message] = []; } this.listeners[message].push(listener); } //當訊息發出時,附上負載發給監聽者 emit(message, payload = null) { if (this.listeners[message]) { this.listeners[message].forEach(l => l(message, payload)) } } }
利用上述程式我們建立一套小型實作內容:
//設定訊息種類 const Messages = { HERO_MOVE_LEFT: 'HERO_MOVE_LEFT' }; //調用你設定的 eventEmitter const eventEmitter = new EventEmitter(); //設定英雄 const hero = createHero(0,0); //讓 eventEmitter 監聽有關英雄往左移的訊息,並執行動作 eventEmitter.on(Messages.HERO_MOVE_LEFT, () => { hero.move(5,0); }); //設定遊戲視窗來監聽鍵盤事件,當左方向鍵按壓時,發出英雄往左移的訊息 window.addEventListener('keyup', (evt) => { if (evt.key === 'ArrowLeft') { eventEmitter.emit(Messages.HERO_MOVE_LEFT) } });
我們連接了鍵盤事件 ArrowLeft 並傳遞 HERO_MOVE_LEFT 訊息。我們監聽該訊息並移動 hero 作為結果。這種開發方式讓事件監聽者與英雄區隔開來。你也可以將 ArrowLeft 換成 A 鍵。此外,我們能修改 eventEmitter 的 on 函式,讓 ArrowLeft 事件產生截然不同的行為。
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => { hero.move(5,0); });
當遊戲越來越豐富、物件越來越複雜時,這套設計方式能維持程式碼的整潔。由衷建議善用這套設計模式。
想想看發布訂閱模式可以如何增進一款遊戲。哪一個部份該發送事件,而遊戲又該如何回應事件?現在你有機會發揮你的創意,思考一款新遊戲和它運作的模組。
藉由閱讀此連結來認識更多關於發布與訂閱的設計模式。