От нуля до единицы, выиграйте онлайн-арендодателя (часть 1)

внешний интерфейс

оригинал:От нуля до единицы, выиграйте онлайн-арендодателя (часть 1) | AlloyTeam
Автор: ТАТ.vorshen

Предыстория: Друзья приезжают в Шэньчжэнь, чтобы поиграть. Если в Шэньчжэне есть что-то веселое, то, конечно же, оставаться дома и сражаться с домовладельцами! Но это не так хорошо, как расчеты людей Я потерял несколько карт, и это неполные... В жаркий день, кто хочет пойти и купить карты. Но это не большая проблема.Как программист в эпоху мобильного Интернета, конечно, это мобильный телефон для замены физической карты.

адрес гитхаба:GitHub.com/vorsh/синий…

Обратите внимание перед чтением: Эта статья разделена на две части, в этой части рассказывается о подготовительной работе и некоторых знаниях, связанных с макетом интерфейса; в следующей части рассказывается об основной логике реализации веб-сборки и о серверной стороне.

Поскольку весь исходный код доступен на github, статья больше склонна объяснять идеи.

Свободное время ограничено, стиль игры уродлив, а некоторые детали не доработаны, прошу меня простить. Тем не менее, он все еще достиг замкнутого цикла, и не должно быть проблем с офлайн-развлечениями.

17张牌,你能秒我?

Примерный стиль игры

游戏大概样式

Подготовить

Выбор и подготовка технологии

typescript + canvas + webassembly + c++ (сервер) Прежде всего, это должен быть Интернет.У Renqi есть сервер локальной сети для запуска, а затем доступ к QQ, WeChat и браузеру, и он запускается напрямую. Поскольку это для Интернета, оно должно быть машинописным Я думаю, что если вы написали ts, вы никогда не захотите снова писать js в этой жизни...

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

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

перед кодированием

Так как переход от нуля к единице мой собственный, дизайн продукта и разработка должны быть моими собственными, так что давайте вкратце разберем процесс игры. Наша Дудижу отличается от QQ Дудижу QQ Дудижу случайным образом заходит в комнату и не может быть взломана. И то, что нам нужно, — это играть вместе, так что концепция игровой комнаты — это большая разница.

Вот простой список потоков нашей игры:

  1. Быстрый доступ, мгновенная игра, регистрация не требуется
  2. Создайте комнату или выполните поиск, чтобы присоединиться к комнате
  3. После входа в комнату традиционная логика борьбы с хозяином

Традиционная логика Доу Дичжу выглядит следующим образом:

传统斗地主逻辑

Хотя это было размещено здесь, когда я собственно начал писать, я вообще не разбирался, просто челнок, и я просто кодировал. В итоге нашел много логических коллизий и деталей.Вроде игра маленькая, но логика довольно сложная, вкупе с несамостоятельностью онлайна, напрочь заниженная сложность игры, горькая слеза.. . …

Про дизайн и говорить нечего, нашел в интернете несколько картинок и расценил их как базовые элементы (уродливые, уродливые... никак)

Ниже приводится официальное начало

макет

горизонтальный экран

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

Поскольку предел горизонтального экрана использовать непросто, можем ли мы напрямую использовать вертикальный экран для имитации горизонтального экрана? То есть мобильный телефон остается в состоянии вертикального экрана, а затем мы поворачиваем всю страницу, чтобы имитировать вертикальный экран.Очень удобно писать макет стиля и т. Д. Его можно написать на горизонтальном экране.

Принцип заключается в следующем:

模拟横屏的原理

примерный код

// 获取旋转元素父元素的宽高
let width = this._app.root.offsetWidth;
let height = this._app.root.offsetHeight;

this._box = document.createElement('div');
this._box.className = 'room-box';
// 宽高反转
this._box.style.width = `${height}px`;
this._box.style.height = `${width}px`;
this._box.style.transform = `translateX(${width}px) rotate(90deg)`;

Уведомление! Такой горизонтальный экран сделает невозможным прямое использование clientX/Y события клика, здесь также требуется преобразование., конкретный код находится в Stage.ts, который здесь не будет раскрываться.

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

标题栏无法横过来

Но я думаю, что это нормально и безвредно, поэтому я выбрал этот подход.

приспособление

Игра разделена на три страницы сцены: домашняя страница, страница лобби, страница комнаты. Среди них домашняя страница и страница лобби на самом деле являются процессом, мы очень случайны,Страница комнаты связана с битвой и является самой сложной, вот страница номера. Ниже приведена страница комнаты классического QQ Fighting Alandlord:

QQ斗地主

Мы примерно разделили модуль, как показано на рисунке:

QQ斗地主,模块分析

Это относительно просто, если не рассматривать детали, видно, что в основном есть шесть областей:

  1. Верхняя область отображения информации
  2. Нижняя область отображения информации
  3. левая область игрока
  4. правая область игрока
  5. Область игрока основного вида
  6. Региональные эффекты

Спецэффекты карты не рассматриваем (убьет меня найти несколько базовых материалов), если она будет реализована с DOM, то директ флекс будет устроен понятно, следующим образом (просто пример, не использовать передний горизонтальный экран)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title></title>
    <style>
        html,
        body {
            margin: 0;
            padding: 0;
            height: 100%;
        }
        .root {
            height: 100%;

            display: flex;
            flex-direction: column;
            justify-content: space-between;
        }
        .top-area {
            height: 45px;
            background-color: #1ca4fc;

            display: flex;
            flex-grow: 0;
        }
        .side-player {
            height: 125px;

            display: flex;
            flex-direction: row;
            justify-content: space-between;
            flex-grow: 1;
        }
        .left-player {
            width: 266px;
            background-color: #f7b92b;

            display: flex;
        }
        .right-player {
            width: 266px;
            background-color: #f7b92b;

            display: flex;
        }
        .main-player {
            height: 187.5px;
            background-color: #fc6554;

            display: flex;
            flex-grow: 0;
        }
    </style>
</head>
<body>
    <div class="root">
        <div class="top-area"></div>
        <div class="side-player">
            <div class="left-player"></div>
            <div class="right-player"></div>
        </div>
        <div class="main-player"></div>
    </div>
</body>
</html>

如果用flex实现

Выше приведена реализация flex, которая очень проста, но мы используем рендеринг холста,Как адаптироваться к разным размерам экрана?Здесь можно рассмотреть два широких направления:

  1. холст имитирует эластичную компоновку
  2. решение для масштабирования

холст имитирует эластичную компоновку

Как мы все знаем, мы используем нативный интерфейс canvas для отрисовки элементов в виде абсолютного позиционирования и не поддерживаем flex. Глядя на некоторые движки рендеринга игр в отрасли, AllRender, erget и EaselJS также используют координаты x и y для управления положением отображаемых объектов.

Насколько я понимаю, поскольку вы используете холст,Естественно будут частые перекраски, гибкая верстка больше склоняется к статичной странице сцены, спрос на игру не большой, нет необходимости тратить много сил и неблагодарности. Однако наша Dou Dizhu — это игра со статической страницей. Заинтересованные учащиеся могут попробовать ее. Для вышеупомянутых пяти модулей используйте фиксированный размер + процентный метод для достижения гибкого макета. Из-за связи между временем и пространством визуализация и коды не будут размещены здесь.

Преимущество этого метода в том, что скорость использования экрана может быть полной, и деформации не будет;

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

решение для масштабирования

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

  1. Показать все + черная рамка
  2. Основной дисплей + без черных рамок

Принцип обоих следующий:

全部展示+黑边
核心展示+无黑边

Два сценария не совпадают

«Показать все + черная рамка»: должен отображаться весь контент, а черная рамка может быть закрыта большим фоном.

«Основной дисплей + без черных полос»: сцена может быть большой, пользователям нужно сосредоточиться только на основных областях.

Таким образом, мы должны использовать первый метод.

оказывать

Вся страница не очень сложна.Для практики мы не использовали зрелый движок рендеринга в отрасли. Но мы не можем использовать нативный метод написания canvas, поэтому сначала мы инкапсулируем несколько основных компонентов.

  • DisplayObject Базовый класс отображаемого объекта, поскольку объект должен отображаться, он должен наследовать этот класс.
  • Класс контейнера контейнера
  • Класс растрового изображения Bitmap
  • Текстовый текстовый класс

Выше приведены базовые классы, связанные с рендерингом, которые необходимо использовать в этой игре.Наши определенные отображаемые объекты (игральные карты) или контейнеры (ручные карты) наследуют их и делают некоторые расширения.. Конкретный код можно увидеть на github. На следующем рисунке показаны компоненты всего проекта.

项目中组件情况

Здесь предполагается, что мы собираемся формально разрабатывать игру, с помощью движка рендеринга это означает, что базовую часть рассматривать не нужно. Тогда приблизительный процесс выглядит следующим образом.

  1. Сначала мы должны спланировать сцену и убедиться, что сцен несколько.
  2. 1 для сцены, чтобы определить, что каждая сцена основана на базе верхней сборки
  3. Оценка повторного использования абстракции компонентов (могут ли компоненты с похожими сценариями в разных сценариях быть абстрагированы в один)
  4. Библиотека инструментов, определение сторонних библиотек

Процесс в принципе такой.

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

BasePukesContainer — очень важный компонент, как следует из его названия, он отвечает за отображение игральных карт. Карта руки игрока (HandPukes) и карта игрока (DesktopPukes) наследуются от него, поэтому абстракция BasePukesContainer очень важна

Во-первых, мы определяем, какие методы необходимы для BasePukesContainer в качестве контейнера-носителя дисплея игральных карт.

  1. Можно отображать с помощью игральных карт (элементы дочерних элементов)
  2. Можно добавлять или удалять игральные карты партиями
  3. Покерные карты поддерживают несколько выравниваний, многострочное отображение и т. д.

Перечислите картинку, посмотрите, что уже есть в BasePukesContainer, а что нужно добавить

BasePukesContainer分析

Красная часть отсутствует в текущей базе наследования, тогда нам нужно расширить

Последний код похож на это (см. GitHub для полного исходного кода)

class BasePukesContainer extends Container {
    // 扑克牌宽度
    protected _pukeWidth: number;
    // 扑克牌高度
    protected _pukeHeight: number;
    // 扑克牌水平对齐方式
    protected _horizontalAlign: PUKE_HORIZONTAL_ALIGN;
    // 扑克牌垂直对齐方式
    protected _verticalAlign: PUKE_VERTICAL_ALIGN;
    // 扑克牌之间两两的覆盖大小
    private _interval: number;

    /**
     * 移除某张扑克牌
     * @param {*} object 
     */
    protected _deletePuke(object: BasePuke) {}

    /**
     * 加入单张扑克牌
     * @param {*} puke 
     */
    protected _postPuke(puke: BasePuke, zIndex?: number) {}

    /**
     * 触发更新维护的扑克牌的位置
     */
    protected _updatePukes() {}

    constructor(options: i_BasePukesContainerOptions) {}

    /**
     * 移除部分扑克牌
     * @param {string[]} pukes
     */
    deletePukes(pukes: string[]) {}

    /**
     * 添加部分扑克牌
     * @param {string[]} pukes
     */
    postPukes(pukes: string[]) {}

    /**
     * 删除所有牌
     */
    deleteAll() {}
}

Компоненты и идеи использования движка рендеринга закончены.Конечно, деталей и основных компонентов гораздо больше, таких как анимация, частицы и т. д. Если вам интересно, вы можете посмотреть исходный код рендеринга. двигатель в промышленности, и читать его с пониманием.

взаимодействовать

Со статичным рендерингом покончено, давайте поговорим о взаимодействии в разработке игр.

вопрос

Отрисовывается расположение карт, и игрок берет карту. Как touchstart, так и touchmove должны вызывать выбор карты. Проблема в том, что канвас не DOM, что бы ни отображалось, по идее, если он не заполнен, или по нему обведены, нет возможности привязать интерактивные события.

На самом деле эта проблема не проблема, в принципе решение должен знать каждый.

Хотя мы не можем привязать события к материалу, который мы заполняем, мы можем привязать события к тегу холста. Затем в соответствии с положением clientX/Y события относительно холста найдите соответствующий отображаемый элемент.

Конкретный принцип заключается в следующем

Canvas中点击判断

(x3, y3) клиентX/Y

Это глобальная координата, мы сначала вычитаем (x1, y1), чтобы получить координаты (x', y') относительно сцены холста.

На данный момент все относительно системы координат сцены холста.Мы используем (x', y'), чтобы сравнить его с прямоугольником [x2, y2, w, h], чтобы определить, находится ли точка в прямоугольнике. Если да, значит нажали на элемент

Если страница относительно проста, она ее решает. Тогда некоторые вещи не так просты...

Элементы перекрываются

元素重叠
Есть два элемента (покер), которые перекрываются, и игрок нажимает на перекрывающуюся область, как мне реагировать?

Компонент вложен

组件嵌套
На данный момент есть только две системы координат, система координат экрана и система координат холста.Если вводится другой контейнер, будет ли другая относительная координата? Бесконечная вложенность, что делать?

неправильная форма

不规则图形
Легко судить, находится ли точка в прямоугольнике, также легко судить, находится ли она в круге, но что, если это неправильная фигура?

решить

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

/**
 * touchstart,touchmove的时候触发
 */
private _touch = (data: { x: number, y: number }) => {
    let {
        x, y
    } = data;
    let len = this._children.length;
    let i;
    let temp: BasePuke;
    let puke: BasePuke | undefined;

    for (i = len - 1; i >= 0; i--) {
        temp = <BasePuke>this._children[i];
        if (temp.contain(x, y)) {
            puke = temp;
            break;
        }
    }

    if (puke) {
        this._choosePuke(puke);
    }
}

Вложение компонентов немного сложнее,Основной конфликт здесь заключается в том, что позиция щелчка мыши является абсолютной координатой, в то время как все элементы на этапе холста являются относительными координатами.. Для сравнения либо преобразуйте абсолютные координаты в относительные, либо преобразуйте относительные в абсолютные координаты.

Здесь мы используем преобразование абсолютных координат в относительные. Например, когда координаты щелчка (x1, y1), необходимо оценить, был ли нажат прямоугольник [x2, y2, w, h] (примечание: это x2, y2 вложены слоями)

Нам нужно найти глобальную координату (x1, y2), преобразовать ее в матрицу системы координат (x2, y2), а затем изменить ее код показывает, как показано ниже:

// DisplayObject.ts
/**
 * 判断是否在AABB中
 * 注意,这里x,y是global的坐标,没有经过transform
 * 所以要进行逆矩阵计算
 * @param {*} x 
 * @param {*} y 
 */
contain(x: number, y: number) {
    let point = new Point(x, y);
    let matrix: Matrix2D;

    // 先求出完整的矩阵
    if (this._parent) {
        matrix = this._parent._getGlobalMatrix();
    } else {
        matrix = new Matrix2D();
    }

    // 再求逆矩阵
    matrix.invert();
    
    // 点进行矩阵变换
    point.transformWithMatrix(matrix);

    let rect = this._getAABB();

    return rect.contains(point);
}

Матрица изменений — это элемент, который следует оценивать в соответствии с потребностями,Сначала получите его глобальную матрицу преобразования, а затем найдите обратную матрицу. Если вы знаете матрицу, вы должны хорошо ее понимать. Для тех, кто не знает, вы можете проверить соответствующую информацию. Причина этого пробела не будет подробно объясняться.

Абсолютное вращение является относительным, и относительное вращение также является аналогичным подходом.

Последний - неправильная графика.Для обычной графики мы можем использовать геометрические или даже алгебраические методы, чтобы определить, находятся ли они внутри элемента.На самом деле, ядро ​​суждения лежит в «крае». Однако для нерегулярной графики слишком сложно просто судить по «краю», поэтому существует метод оценки на уровне пикселей: алгоритм анти-рисовальщика. Это все еще вопрос места, здесь он не будет расширяться, желающие могут проверить это самостоятельно (наша игра Доу Дичжу также не используется).

Суммировать

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

Итак, как вы получаете сообщения от других? Как выглядит синхронизация игры? Каковы меры предосторожности для пользователей, входящих и выходящих из комнаты? Как написать основную логическую часть карты? Где и как используется Webassembly?

Пожалуйста, ждите следующего.


AlloyTeam приглашает отличных друзей присоединиться.
Возобновление доставки:Alloyteam@qq.com
Нажми для деталейTencent AlloyTeam набирает веб-дизайнеров (социальный набор)