Назад | Учебник TypeScript | Вперёд
Пишем скелет игры. Для начала определим класс доски. Вся игра происходит на поле, поделённое на квадраты. Это поле и будет представлять собой наш класс Board:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Board { private static TILE_TYPES_COUNT:number = 4; private tiles:TileState[][]; private width:number; private height:number; public constructor(width:number,height:number) { this.width = width; this.height = height; this.tiles = Array<Array<TileState>>(); for (let n:number = 0; n < width; n++) { this.tiles[n] = new Array<TileState>(height); for (let m:number = 0; m < height; m++) { this.tiles[n][m] = this.randomInteger(1, Board.TILE_TYPES_COUNT); } } } private randomInteger(min:number, max:number) { let rand = min + Math.random() * (max + 1 - min); rand = Math.floor(rand); return rand; } } |
Тут ничего сложного. Мы просто создаём класс Board с конструктором. Внутри класса у нас массив состояний каждой клетки, который мы инициализируем в конструкторе.
TileState — это перечисление, которое описывает возможные состояния клеток:
1 2 3 4 5 6 7 |
enum TileState { EMPTY, RED, GREEN, BLUE, YELLOW } |
Есть ещё одна маленькая деталь: нам нужно добавить canvas в HTML. В предыдущей статье мы забыли это сделать. Файл tscolorballs.html станет таким:
1 2 3 4 5 6 7 8 9 |
<!DOCTYPE html> <html> <head><title>TypeScript Color Balls</title></head> <body> <script src="tscolorballs.js"></script> <canvas id="tscolorballscanvas" width="320" height="384"> </canvas> </body> </html> |
Пора писать сам движок. В Engine мы будем обрабатывать основную логику.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Engine { ... public constructor() { this.board = new Board(Engine.BOARD_WIDTH, Engine.BOARD_HEIGHT); let engine = this; setTimeout(function() {engine.onStep();}, Engine.TICK_MILLISECONDS); } ... } |
Мы инициализируем экземпляр класса Board. Здесь Engine.BOARD_WIDTH и Engine.BOARD_HEIGHT — это константы. А Board — это переменная класса. Они объявлены в Engine вот так:
1 2 3 4 5 6 7 8 |
class Engine { public static BOARD_WIDTH:number = 10; public static BOARD_HEIGHT:number = 10; public static TICK_MILLISECONDS = 100; private board:Board; … } |
Обратите внимание, что внутри обработчика setTimeout мы используем не this, а сохранённую в переменную engine ссылку на this уровнем выше. Это делается из-за особенностей работы this JavaScript. Внутри функции, которую мы передаём в setTimeout, this будет ссылаться не на экземпляр engine, а скорее на саму функцию.
Теперь нам нужно написать реализацию метода onStep, в котором мы пока просто будем отрисовывать текущее состояние игрового поля:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class Engine { ... private onStep():void { this.onPaint(); let engine = this; setTimeout(function() {engine.onStep();}, Engine.TICK_MILLISECONDS); } private onPaint():void { let canvas = <HTMLCanvasElement>document .getElementById("tscolorballscanvas"); let ctx = canvas.getContext("2d"); for (let x:number = 0; x < Engine.BOARD_WIDTH; x++) { for (let y:number = 0; y < Engine.BOARD_HEIGHT; y++) { switch (this.board.getTileState(x, y)) { case TileState.EMPTY: this.drawRect(ctx, x, y, "#404040"); break; case TileState.RED: this.drawCircle(ctx, x, y, "#ba4747"); break; case TileState.GREEN: this.drawCircle(ctx, x, y, "#356637"); break; case TileState.BLUE: this.drawCircle(ctx, x, y, "#678beb"); break; case TileState.YELLOW: this.drawCircle(ctx, x, y, "#bebb70"); break; } } } } ... } |
Обратите внимание, что мы здесь используем this.board.getTileState(x, y). Пока у нас нет реализации подобного метода в Board. Добавим его:
1 2 3 4 5 6 7 |
class Board { ... public getTileState(x:number, y:number):TileState { return this.tiles[x][y]; } ... } |
Также в onPaint мы используем свои методы drawCircle и drawRect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class Engine { ... private drawRect(ctx: CanvasRenderingContext2D, x: number, y: number, color:string):void { ctx.fillStyle = color; ctx.fillRect(x * Engine.TILE_WIDTH, y * Engine.TILE_HEIGHT, x * Engine.TILE_WIDTH + Engine.TILE_WIDTH, y * Engine.TILE_HEIGHT + Engine.TILE_HEIGHT); } private drawCircle(ctx: CanvasRenderingContext2D, x:number, y: number, color:string):void { ctx.beginPath(); ctx.fillStyle = color; ctx.arc( x * Engine.TILE_WIDTH + Engine.TILE_WIDTH / 2, y * Engine.TILE_HEIGHT + Engine.TILE_HEIGHT / 2, Engine.TILE_WIDTH / 2, // radius 0, // startAngle 2 * Math.PI, // endAngle false // clockwise ); ctx.fill(); ctx.stroke(); } ... } |
Тут ничего особого нет. Мы просто вызываем стандартные методы CanvasRenderingContext2D для отрисовки закрашенных кругов и прямоугогльников.
В начало класса Engine нужно ещё добавить две новые константы, которые обозначаю ширину и высоту одной клетки поля соответственно:
1 2 3 4 5 6 |
class Engine { ... public static TILE_WIDTH:number = 32; public static TILE_HEIGHT:number = 32; ... } |
Ещё одна маленькая штучка. Что-то должно запускать код нашей игры после загрузки страницы. Создайте файл “main.ts” со следующим содержимым (думаю, что пояснения тут излишни):
1 2 3 4 5 |
(function () { document.addEventListener("DOMContentLoaded", function() { let engine:Engine = new Engine(); }); })(); |
Сейчас вы можете попробовать скомпилировать проект выполнив tsc в корне и запустить файл “tscolorballs.html” в браузере. Вы должны увидеть что-то вроде этого:
