Оглавление
- Подготовка к работе [
Вы тут
] - Введение
- (wip) Загрузка ресурсов
- (wip) Создание игрового мира
- (wip) Группы
- (wip) Мир физики
- (wip) Управление
- (wip) Добавление целей
- (wip) Последние штрихи
Эта серия статей научит вас основам и "хорошему тону" игрового фремворка Phaser. За данный курс, я постараюсь объяснить вам основные идеи и возможности фреймворка, а также покажу как его грамотно использовать в связке с TypeScript и Webpack.
Основной ход обучения взят из официального руководства, но это не дословный перевод, а адаптация руководства, с переписанными примерами с ES5 на TypeScript и измененной структурой проекта. Я также раскрыл некоторые моменты темы более развернуто.
Думаю стоит оговориться, что на момент написания данной статьи, я использую Phaser v2.6.2 и TypeScript v2.2.1.
Исходный код всех уроков вы найдете в данном репозитории. Обратите внимание, что тегами помечаются этапы развития проекта на конец определенной статьи, к примеру:
part-0
— состояние проекта на момент текущей статьиpart-1
— на момент статьи [#1 Введение][part-1-url]
и так далее.
В двух словах о Phaser
[Phaser]phaser-url] — бесплатный (MIT), кросс-браузерные HTML5 фреймворк для создания браузерных игр с использованием WebGL и Canvas. В отличии от других фреймворков, Phaser в первую очередь целится на мобильные платформы и оптимизирован под них.
Инструменты
Прежде всего вам потребуется склонировать репозиторий с проектом себе:
git clone https://github.com/SuperPaintman/phaser-typescript-tutorial.git
И установить Node.js для запуска сборщика и других NPM скриптов.
Структура проекта
Прежде чем перейти непосредственно к самому фремворку, рассмотрим структуру будущего приложения.
В качестве основы для нашего проекта, я взял данный Phaser TypeScript шаблон, который использует Webpack в качестве сборщика.
Давайте рассмотрим основные его файлы (внимание на комментарии):
webpack.config.js
Конфигурация для сборщика Webpack. В зависимости от переменной окружения NODE_ENV
соберет билд для разработки, или оптимизированный билд для продакшена.
'use strict'; /** Requires */ const path = require('path'); const webpack = require('webpack'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin; const ImageminPlugin = require('imagemin-webpack-plugin').default; const p = require('./package.json'); /** Constants */ const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const assetsPath = path.join(__dirname, 'assets/'); // папка с ресурсами игры const stylesPath = path.join(__dirname, 'styles/'); // папка с css стилями // Путь до папки с собранными билдами phaser. Из-за того, что phaser собирается // по-старинке, его (а также его зависимости) придётся подключать как // глобальный объект. const phaserRoot = path.join(__dirname, 'node_modules/phaser/build/custom/'); // Пути до библиотек phaser'а const phaserPath = path.join(phaserRoot, 'phaser-split.js'); const pixiPath = path.join(phaserRoot, 'pixi.js'); const p2Path = path.join(phaserRoot, 'p2.js'); // Папка, в которую у будет собран наш билд const outputPath = path.join(__dirname, 'dist'); // Путь до шаблона `index.html` файле const templatePath = path.join(__dirname, 'templates/index.ejs'); /** Helpers */ /** * Проверяет, содержит ли массив данный элемент * @param {T[]} array * @param {T} searchElement * * @return {boolean} */ function includes(array, searchElement) { return !!~array.indexOf(searchElement); } /** * Создает правила для `expose-loader`, который добавляет модуль к глобальному * объекту window по переданному имени * @param {string} modulePath * @param {string} name] * * @return {Object} */ function exposeRules(modulePath, name) { return { test: (path) => modulePath === path, loader: 'expose-loader', options: name }; } /** * Удаляет из массива все элементы равные `null` * @param {T[]} array * * @return {T[]} */ function filterNull(array) { return array.filter((item) => item !== null); } /** * Вызывает функцию `fn`, если `isIt` равет `true`, в противном случае будет * вызвана функция `fail`. * * @param {boolean} isIt * @param {function} fn * @param {function} fail * * @return {any} */ function only(isIt, fn, fail) { if (!isIt) { return fail !== undefined ? fail() : null; } return fn(); } /** * Хелпер на основе `only`. Вызывает первую функцию, если * `NODE_ENV` === 'production', т.е. если сборка производится для продакшена. * @param {function} fn * @param {function} fail * * @return {any} */ const onlyProd = (fn, fail) => only(IS_PRODUCTION, fn, fail); /** * Хелпер на основе `only`. Вызывает первую функцию, если * `NODE_ENV` !== 'production', т.е. если сборка производится для разработки. * @param {function} fn * @param {function} fail * * @return {any} */ const onlyDev = (fn, fail) => only(!IS_PRODUCTION, fn, fail); module.exports = { entry: { main: path.join(__dirname, 'src/index.ts') }, output: { path: outputPath, // На продакшене также добавим к именам файлов их хещ, чтобы обойти // проблему с кешированием версий filename: `js/[name]${onlyProd(() => '.[chunkhash]', () => '')}.js`, chunkFilename: `js/[name]${onlyProd(() => '.[chunkhash]', () => '')}.chunk.js`, sourceMapFilename: '[file].map', publicPath: '/' }, devtool: onlyDev(() => 'source-map', () => ''), // Отключим sourcemap'ы на проде. resolve: { extensions: ['.ts', '.js'], alias: { pixi: pixiPath, // сделаем возможным подключить 'pixi' библиотеку как обычный NPM пакет phaser: phaserPath, // сделаем возможным подключить 'phaser' библиотеку как обычный NPM пакет p2: p2Path, // сделаем возможным подключить 'p2' библиотеку как обычный NPM пакет assets: assetsPath, // алиас до папки `assets/` styles: stylesPath // алиас до папки `styles/` } }, plugins: filterNull([ /** DefinePlugin */ // Глобальные переменные, будт полезны для отключения каких-либо функций на // проде, или напротив включения оптимизаторов и пр. new webpack.DefinePlugin({ IS_PRODUCTION: JSON.stringify(IS_PRODUCTION), VERSION: JSON.stringify(p.version) }), /** JavaScript */ // Минимизирует JS для продовой сборки onlyProd(() => new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, comments: false })), /** Clean */ // Удалит `dist` папку перед каждой сборкой new CleanWebpackPlugin([outputPath]), /** TypeScript */ new CheckerPlugin(), /** Images */ // Оптимизирует изображения и svg'хи onlyProd(() => new ImageminPlugin({ test: /\.(jpe?g|png|gif|svg)$/ })), /** Template */ // Данный плагин автоматически сгенерирует для нас `index.html` файл // на основе `templatePath`, а также сам вставит в этот шаблон все // сгенерированные скрипты и стили new HtmlWebpackPlugin({ title: 'Phaser TypeScript boilerplate project', template: templatePath }), /** CSS */ // Экспортирует CSS import'ы в отдельный `.css` файл (по-умолчанию Webpack // вставляет CSS прямо в JS файлы). new ExtractTextPlugin({ filename: `css/[name]${onlyProd(() => '.[chunkhash]', () => '')}.css` }), /** Chunks */ // Разобьем нашу сборку на несколько файлов (т.к. вендорные файлы и файлы // самого phaser'а вряд ли будут меняться в процессе разработки, нет нужды // заставлять наших клиентов тянуть каждый раз эти данные заново. Чанки как // раз помогут в этом, браузер сможет доставать из кеша файлы, которые не // поменялись): // * Чанк для прочих модулей new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: (module) => /node_modules/.test(module.resource) }), // * Чанк для phaser модулей (p2, PIXI, phaser) new webpack.optimize.CommonsChunkPlugin({ name: 'phaser', minChunks: (module) => includes([p2Path, pixiPath, phaserPath], module.resource) }), // * Чанк для инициализационных функций webpack'а new webpack.optimize.CommonsChunkPlugin({ name: 'commons' }) ]), devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, port: 8080, inline: true, watchOptions: { aggregateTimeout: 300, poll: true, ignored: /node_modules/ } }, module: { rules: [ /** Assets */ // Скопирует файлы из asset'ов { test: (path) => path.indexOf(assetsPath) === 0, loader: 'file-loader', options: { name: `[path][name]${onlyProd(() => '.[sha256:hash]', () => '')}.[ext]` } }, /** CSS */ { test: /\.styl$/, exclude: /node_modules/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ 'css-loader', 'stylus-loader' ] }) }, /** JavaScript */ exposeRules(pixiPath, 'PIXI'), // добавит `PIXI` модуль в глобальный объект `window` exposeRules(p2Path, 'p2'), // добавит `p2` модуль в глобальный объект `window` exposeRules(phaserPath, 'Phaser'), // добавит `Phaser` модуль в глобальный объект `window` { test: /\.ts$/, exclude: /node_modules/, loader: 'awesome-typescript-loader' } ] } };
assets/
В эту директорию мы будем складывать все ресурсы нашего приложения: изображения, музыку, JSON и прочее.
styles/style.styl
В нем будут содержаться CSS стили (с использованием препроцессора Stylus) для нашей страницы. В рамках данной серии, будет достаточно этого:
body margin: 0px
templates/index.ejs
EJS шаблон для страницы игры (Webpack сам добавит в него загрузку всех скриптов и стилей):
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> </body> </html>
tsconfig.json
Конфиг для TypeScript:
{ "compilerOptions": { "target": "es5", // Для большей поддержки браузерами "module": "commonjs", "moduleResolution": "node", "sourceMap": true, "removeComments": false, "noImplicitAny": false, "pretty": true }, "files": [ // Нужно указать откуда тайпинги Phaser'а явно, т.к. в данной папке // содержатся несколько разных их версий, которые будут конфликтовать между // собой. "./node_modules/phaser/typescript/box2d.d.ts", "./node_modules/phaser/typescript/p2.d.ts", "./node_modules/phaser/typescript/phaser.comments.d.ts", "./node_modules/phaser/typescript/pixi.comments.d.ts" ], "include": [ // А так-же укажем откуда брать тайпинги по glob'у "./src/**/*.ts", "./node_modules/@types/**/*.ts" ] }
src/typings.d.ts
В данном файле мы должны объявить все глобальные переменные, которые создали в webpack.DefinePlugin
:
declare const IS_PRODUCTION: boolean; declare const VERSION: string;
src/index.ts
Это основной файл нашего приложение, он будет входной точной в него:
'use strict';
На этой основе мы будем создавать платформер.
ссылка на оригинал статьи https://habrahabr.ru/post/324894/
Добавить комментарий