Как собрать браузерную игру? [Итерация 2] Графический интерфейс

Нажмите здесь, чтобы опубликовать эту статью в LinkedIn »

Поскольку цель этой серии - изучить как можно больше аспектов веб-разработки, поиграть с новыми технологиями и узнать что-то новое и интересное. И сделаем это, создав текстовую браузерную игру.

Если вы не читали предыдущие посты - вот они:

Пришло время графического интерфейса. К тому же я ужасно рисую, я никогда не был большим игроком, поэтому было действительно сложно придумать графический интерфейс для этой игры. Я просмотрел несколько игр, много погуглил, проверил ресурсы на сайте разработчиков игр и сделал каркас, используя отличный инструмент https://wireframe.cc/

Затем я использовал другой отличный инструмент https://coolors.co/ для создания цветовой схемы.

Теперь о ReactJS. Это будет базовое руководство по созданию пользовательского интерфейса на ReactJS.

Я использую webpack для сборки и babel для компиляции JS.

Итак, чтобы создать интерфейс с реакцией, вы можете либо использовать службу response-create-app, либо просто инициализировать проект npm в своей общедоступной папке (./web в случае фреймворка Symfony).

После этого добавьте несколько зависимостей:

npm install --save babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom webpack

Теперь у нас есть webpack для сборки, babel для компиляции и реакции на кодирование.

Далее необходимо указать, какие пресеты JS вы будете использовать, поэтому создайте файл .babelrc и добавьте те, которые вы будете использовать (в моем случае это es2015 и реакция jsx):

{
  "presets": [
    "react",
    "es2015"
  ]
}

Отлично, теперь babel знает, как скомпилировать наше приложение.

Пришло время создать код пользовательского интерфейса, поэтому я создал еще одну папку (я назвал ее library, не знаю почему, вы можете назвать ее src) и добавил новый файл под названием Game.js

Game.js будет моей точкой входа в интерфейс.

Чтобы веб-пакет работал, я создал файл webpack.config.js в корневой папке npm и добавил общий код:

const path = require('path');

module.exports = {
    entry: './libraries/Game.js',
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'game.js'
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            }
        ]
    }
};

Я использую библиотеку NodeJS path, чтобы упростить путь к файлу ввода. Код внутри этого модуля довольно прост: вы устанавливаете входной файл, устанавливаете выходной файл и способы загрузки модулей.

Я ненавижу запускать webpack через папку node_modules, поэтому добавляю этот скрипт в package.json:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "webpack -wd"
},

Это запустит webpack в режиме разработки и начнет следить за любыми изменениями, поэтому теперь я могу просто запустить:

npm run webpack

И он должен скомпилировать ваш пакет. О, но он пуст, потому что наш Game.js пуст. Давайте создадим наш новый компонент!

Как вы знаете, ReactJS основан на компонентах, которые очень дружелюбно наследуют, взаимодействуют и просто живут вместе.

Чтобы создать Компонент, вам нужно использовать этот синтаксис (есть несколько способов, но он называется наиболее правильным):

import React from "react";
import ReactDOM from "react-dom";

class Game extends React.Component {

    render() {
        return (
            <div>
                <div className="container-fluid">
                    <div className="row">
                        <div className="col-md-12">
                            <CharacterWidget />
                            <HeaderWidget />
                        </div>
                    </div>
                </div>
                <div className="container-fluid">
                    <div className="row">
                        <div className="col-md-6 text-center">
                            <Map />
                        </div>
                        <div className="col-md-6">
                            <MapDetails />
                        </div>
                    </div>
                </div>
                <Inventory />
            </div>
        )
    }
};

var mountNode = document.getElementById("game");
if (mountNode) {
    ReactDOM.render(<Game />, mountNode);
}

Компоненту React требуется только 1 метод с именем render (), который возвращает шаблон этого конкретного компонента. Как видите, я создал какой-то тупой шаблон с помощью bootstrap. Поскольку мы используем JSX, вместо class мы должны использовать className. Но что это за ‹CharacterWidget /› ‹HeaderWidget /› ‹Map /› ‹MapDetails /›? Ну, это другие компоненты, которые мы создадим. На данный момент я создал все эти компоненты и все, что у них есть, как шаблон:

<div className=”c-character-widget”>Character Widget</div>
<div className=”c-header-widget”>Header Widget</div>
<div className=”c-map”>Map</div>
<div className=”c-map-details”>Map Details</div>

Как я уже упоминал о классе c-%, давайте немного поговорим о css.

Я использую SASS для этого приложения, и есть 2 способа связать наш css. Либо я создаю все файлы scss отдельно для каждого компонента, добавляю файл scss к каждому компоненту в качестве зависимости, а затем просто добавляю sass-compiler в webpack (что является лучшим решением для веб-сайтов, потому что тогда webpack будет генерировать стили по мере необходимости для каждой страницы в зависимости от на странице компонентов с помощью) или создайте main.scss, куда вы добавите все файлы scss компонентов и создадите 1 пакет css (более предпочтительно для моего случая, потому что в игре не так много страниц).

Я использую «своего рода» методологию БЭМ для определения моих классов css. Вот пример блока с дополнительными функциями, которые предоставляет конкретный элемент:

.c-extra {
  margin-top: 10px;
  .c-extra--title {
    font-size: 13px;
    color: $color-gold-primary;
    text-shadow: 0 0 5px #000;
  }

  .c-extra--item {
    font-size: 12px;
    color: $color-gray-light-primary;

    &.c-extra--item__required {
      color: $color-red-primary;
    }

    .c-extra--item-stat {
      display: inline-block;
      margin-right: 5px;
    }
    .c-extra--item-value {
      display: inline-block;
    }
  }
}

А вот и шаблон jsx:

<div className="c-extra">
    <div className="c-extra--title">Extras</div>
    {this.props.item.extras.map((item, i) => {
        return (
            <div key={i} className="c-extra--item">
                <div className="c-extra--item-stat">
                  {item.attribute}
                </div>
                <div className="c-extra--item-value">
                  {item.value}
                </div>
            </div>
        )
    })}
</div>

Как вы можете видеть, даже несмотря на то, что эти классы стали немного длиннее - очень легко понять структуру компонента + его многоразовое использование, и если вы переместите его в любое другое место приложения - он должен остаться прежним. Идея компонента - быть без состояния, независимо от того, где вы его включаете - он должен выглядеть одинаково.

Это пример очень уродливого, но работающего виджета персонажа:

Я пока не касался данных, потому что все, что нам нужно, - это просто создать пользовательский интерфейс, а затем мы будем двигаться вперед.

Я думаю, что на данный момент этого достаточно, если у вас есть какие-либо вопросы относительно компонентов ReactJS, sass, webpack или чего-либо еще, о чем говорится в этой статье - не стесняйтесь спрашивать в разделе комментариев ниже.

Что я могу сказать: я изменил свое отношение к ReactJS, потому что нашел его очень тонким и мощным инструментом для создания пользовательских интерфейсов на основе компонентов и использования всей мощи VirtualDOM.

В следующей статье мы поговорим о Map!