手擼俄羅斯方塊——游戲設計
概述
上一章我們介紹俄羅斯方塊的基本信息,包括坐標點和方塊的基本概念,這一章節我們繼續介紹如何完成后續的游戲設計。
組成游戲的基本要素
俄羅斯方塊作為一個 2D 的平面游戲,我們可以將整個參與元素做如下抽象,如下圖:
-
游戲(Game):游戲的核心,負責控制游戲的開始、暫停、結束等。
-
舞臺(Stage):游戲的舞臺,是一個矩形的游戲區域,方塊在舞臺中移動和旋轉。
-
方塊(Block):游戲的基本元素,包括各種形狀的方塊,每個方塊由 4 個小方塊組成。
-
工廠(Factory):方塊的生成器,負責生成不同形狀的方塊。
-
-
控制器(Controller): 游戲控制器,用于注冊事件,如鍵盤事件、鼠標事件等;
-
渲染(Render):游戲的渲染器,負責將游戲的狀態渲染到屏幕上。
- 主題(Theme):游戲的主題,包括游戲的背景、方塊的顏色等。
簡單描述為:
-
通過
工廠(Factory)
產生方塊(Block)
。 -
方塊(Block)
在舞臺(Stage)
中移動和旋轉。 -
控制器(Controller)
監聽用戶輸入,方塊的移動和旋轉是通過游戲(Game)
實例做總體控制。 -
游戲的狀態通過
渲染(Render)
渲染到屏幕上,輸出樣式由主題(Theme)
進行控制。
Factory
工廠的目標是為了產生方塊,因此它只需要提供產生方塊的能力即可。可以抽象如下:
export abstract class AbstractFactory {abstract randomBlock(): Block;
}
沒錯,AbstractFactory
只需要提供一個randomBlock
,其隨機產生一個方塊。
Stage
Stage 作為整個游戲的舞臺,它將方塊進行組合,同時對外暴露一些能力,如:
- tick,步進能力,包含三個操作: 生產新的方塊、方塊向下移動和方塊結算。
- rotate,旋轉能力。
- move,移動能力
- reset,重置能力
除此之外,還需要提供幾個狀態的判斷能力:
- isOver,是否結束,
tick
無法繼續。
為了完成上述能力,我們需要對一些信息進行存儲,也是封裝在Stage
中。
- current: Block,當前正在移動的方塊。其要么是空,要么是一個固定的方塊。
- next: Block,下一個方塊。
- score: number,得分。
- points: Point[][],一個二位數組,坐標軸的填充情況。
- dimension: Dimension,定義坐標軸的可操作區域,所有的方塊不能操作該區域。
- factory: 方塊生成器。
type Direction = "up" | "left" | "down" | "right";
class Dimension {xSize: number; // x坐標軸的最大值ySize: number; // y坐標軸的最大值
}class Stage {current: null | Block;next: null | Block;score: number;points: Point[][];dimension: Demension;tick() {}rotate() {}move(direction: Direction) {}reset() {}
}
Game
Game 作為程序的主入口,將多個元素進行聚合,并控制游戲的開始和結束。
我們首先來分析它的屬性,它至少要包括如下屬性:
- status,游戲的狀態,分別是 Ready、Running、Pause、Stop、Over。
- stage,游戲的舞臺。
- render,渲染器
- controller,控制器
它的目標是通過定時器來控制 Stage 的步進(tick),通過 contoller 監聽用戶輸入,控制移動和旋轉。
邏輯如下:
export interface GameOptions {dimension: Dimension;canvas: AbstractCanvas;factory?: AbstractFactory;controller?: AbstractController;
}export enum GameStatus {UNREADY = 0,READY = 1,RUNNING = 2,PAUSE = 3,STOP = 4,OVER = 5
}export class Game {controller?: AbstractController;canvas: AbstractCanvas;options: GameOptions;dimension: Dimension;stage: Stage;status: GameStatus;// 開始start() {}// 暫停pause() {}// 停止stop() {}// 旋轉rotate() {}// 移動move(direction: Direction) {}toggle() {}destroy() {}
}
Render
對舞臺進行渲染。一般對于游戲渲染,主要包括兩個階段:
- 首次渲染,渲染基本場景;
- 更新渲染,舞臺發生變化時進行更新渲染。處于性能考量,更新時最好使用局部渲染。
定義如下:
class AbstractRender {theme: Theme;render(): void; // 首次渲染update(): void; // 更新渲染
}
Theme
為了確定游戲的背景,我們需要將游戲區域進行劃分。劃分如下圖:
從上到下,我們可以將整個操作界面分為如下區域:
-
外框區域: 包括外框的顏色和整體的背景,以及外框的樣式;
-
分數區域: 用于顯示分數;
-
當前圖形: 顯示當前正在移動的方塊;
-
下一圖形: 顯示接下來要出現的方塊;
-
游戲狀態: 顯示當前游戲的狀態,如: 游戲暫停;
-
已填充圖形: 顯示已填充的圖形;
按照上述劃分,我們可以將theme
抽象為如下:
abstract class AbstractTheme {/*** 設置外框的樣式,如外框的顏色、整體的背景等。* @param outer 指代外框對象的元素,通過修改其內容改變顯示樣式。*/abstract outStyle(outer: any): void;/*** 設置內框的樣式,如內框的顏色、整體的背景等。* @param inner 指代內框對象的元素,通過修改其內容改變顯示樣式。*/abstract innerStyle(inner: any): void;/*** 設置分數的樣式。* @param score 指代分數對象的元素,通過修改其內容改變顯示樣式。*/abstract scoreStyle(score: any): void;/*** 設置狀態欄的樣式* @param status 指代狀態對象的元素。*/abstract statusStyle(status: any): void;/*** 分數的格式化字符串,輸入一個分數的數字,將其轉換為目標的樣式;* @param score {number} 當前游戲的分數*/abstract scoreTemplate(score: number): string;abstract nextStyle(blocks: any): void;abstract currentStyle(current: any): void;/*** 設置方塊區域的樣式* @param block 指代當前方塊區域*/abstract blockStyle(block: any): void;/*** 設置current區域和已填充區域的小方塊的樣式* @param blockItem 當前小方塊,如一個IBlock會拆分成4各BlockItem。* @param point 當前小方塊的位置信息,包括`x`軸和`y`軸的坐標等信息*/abstract blockPointStyle(blockItem: any, point: Point): void;/*** 設置next區域的小方塊的樣式* @param blockItem* @param point*/abstract nextPointStyle(blockItem: any, point: Point): void;
}
小結
本章主要描述了俄羅斯方塊的幾個控制元素,也僅僅是幾個相關的骨架。后續章節將進一步介紹方塊的設計。