Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 19 из 25 ~18 мин +50 XP

Векторная графика: линии и круги

Спрайты — не единственный способ рисовать: сетка уровня, рамки HUD и индикаторы перезарядки не требуют картинок и живут в векторной графике. В Ebitengine за неё отвечает пакет vector: готовые функции для кругов, линий и прямоугольников плюс тип vector.Path для фигур, собранных из сегментов. Мы нарисуем HUD из примитивов, полый круг кулдауна по углу и линию трассы, посчитаем цену кадра и закроем графику примитивы для хитбоксов — без единого файла ассетов.

Содержание урока · 10 разделов

У игры есть спрайты и текст, но между ними дыра: полоса здоровья, рамка экрана, сетка в редакторе и круг перезарядки не тянут на полноценные ассеты — ради них не заводят ни атлас, ни художника. Именно эту дыру закрывает пакет vector из поставки Ebitengine: векторная графика примитивов рождается прямо в коде, меняется константой и весит ноль байт. В этом уроке мы соберём из линий и кругов боевой HUD, поймём, когда примитив честнее спрайта, посчитаем цену каждого вызова и прикрутим к примитивам хитбоксы — геометрию, которую игра проверяет так же легко, как рисует. К концу урока прототип сможет выглядеть собранным ещё до появления настоящего арта.

Пакет vector: готовые функции#

Пакет vector открывается пятью функциями быстрого рисования: FillCircle заливает круг, StrokeCircle обводит окружность, StrokeLine проводит линию заданной ширины, FillRect и StrokeRect заливают и обводят прямоугольники. Общая схема аргументов одинаковая: холст, координаты в float32, цвет и последний флаг antialias — сглаживание краёв. Включённый флаг даёт мягкие края ценой дополнительных вычислений, выключенный рисует резко и дешевле: для статичного UI сглаживание включают, для сотен объектов на кадр — выключают. Координаты — пиксели холста, и круг задаётся центром плюс радиусом, а не прямоугольником: геометрия круга живёт прямо в аргументах. Массовость — главный аргумент в пользу готовых функций: сетка из сотни линий или облако частиц из кругов собираются циклом без единой картинки, и правка цвета — одна константа в коде, а не перерисовка ассета.

Имена честно отражают историю пакета: DrawFilledCircle из старых статей с версии 2.9 помечена устаревшей и стала алиасом FillCircle — в новом коде используйте актуальные имена, чтобы не тащить предупреждения и не путать читателя. Цвет — обычный color.Color из image/color, и прозрачность через альфу работает так же, как у спрайтов: полупрозрачный прямоугольник поверх экрана — это FillRect с альфой, а не отдельная картинка затемнения. Именно так собирается оверлей паузы из урока про сцены: затемнение плюс надпись — и никакой картинки не понадобилось.

HUD: рамка, здоровье и кулдаун

GO
func drawHUD(screen *ebiten.Image, g *Game) {
        // рамка экрана: контур прямоугольника шириной 2
        vector.StrokeRect(screen, 8, 8, 624, 464, 2,
                color.RGBA{0x80, 0x80, 0x80, 0xff}, false)

        // полоса здоровья: ширина зависит от hp
        w := float32(g.hp) / float32(g.maxHP) * 120
        vector.FillRect(screen, 16, 16, w, 14,
                color.RGBA{0xff, 0x40, 0x40, 0xff}, false)

        // контур индикатора перезарядки
        vector.StrokeCircle(screen, 600, 40, 16, 2,
                color.RGBA{0xff, 0xff, 0xff, 0xff}, false)
        // заливка растёт по мере готовности
        r := float32(12) * (1 - g.cooldown/g.cooldownMax)
        vector.FillCircle(screen, 600, 40, r,
                color.RGBA{0x40, 0x80, 0xff, 0xff}, true)
}

Рамка — один вызов StrokeRect с шириной линии два: ни картинки, ни четырёх вручную поставленных полосок. Полоса здоровья — FillRect, чья ширина считается из отношения hp к максимуму: ноль здоровья даёт пустую полосу, полный запас — сто двадцать пикселей. Индикатор перезарядки собран из двух кругов: StrokeCircle рисует постоянный контур, а радиус заливки FillCircle растёт по мере готовности — от нуля до почти полного радиуса. Заливка со сглаживанием ради мягкого края, рамка без него: на статичном HUD лишние вычисления не нужны.

Какая функция рисует залитый круг в актуальном API пакета vector?

Path: фигуры из сегментов#

Когда готовых фигур мало, включается vector.Path — собираемая фигура. Метод MoveTo ставит перо в точку, LineTo тянет сегмент до следующей, Arc рисует дугу по центру, радиусу, стартовому и конечному углу, Close замыкает контур. Готовый путь отдаётся в FillPath — заливку — или StrokePath — обводку — вместе с опциями: StrokeOptions задаёт ширину линии Width и стили концов и стыков, DrawPathOptions — сглаживание AntiAlias и цвет через ColorScale. Путь — просто список инструкций: его можно строить заново каждый кадр, а можно собрать один раз и переиспользовать, и от этого выбора зависит производительность следующего раздела. Кроме линий у пера есть кривые: QuadTo тянет квадратичную кривую через одну контрольную точку, CubicTo — кубическую через две, и на них строятся плавные траектории и органические контуры; в играх курса хватает линий и дуг, но знать про кривые стоит уже потому, что они объясняют, почему дуги в Path сегментированы.

Круг из готовой функции и круг из Path — разные вещи по цене: FillCircle внутри строит путь с дугой на две сотни сегментов, но делает это в оптимизированном виде с переиспользованием вершин. Свой Path нужен там, где фигура нестандартна: дуга перезарядки по углу, ломаная трассы, многоугольник препятствия. Для прямых и прямоугольников Path избыточен — готовые функции короче, быстрее и читаются в одну строку. Ещё один плюс собранного пути — переносимость: один и тот же Path можно обвести на любом холсте — на экране, на offscreen-изображении для предрендера или на карте, куда смотрит камера, — геометрия не знает, куда её рисуют, и это делает пути удобными заготовками для слоёв интерфейса.

Кулдаун-дуга и линия трассы

GO
// кулдаун-дуга: прогресс виден по углу
var ring vector.Path
start := float32(-math.Pi / 2) // старт сверху
end := start + 2*math.Pi*(1-g.cooldown/g.cooldownMax)
ring.Arc(600, 40, 16, start, end, vector.Clockwise)
vector.StrokePath(screen, &ring,
        &vector.StrokeOptions{Width: 3},
        &vector.DrawPathOptions{AntiAlias: true})

// линия трассы выстрела
vector.StrokeLine(screen, g.px, g.py, g.tx, g.ty, 2,
        color.RGBA{0xff, 0xff, 0x00, 0xff}, true)

Дуга перезарядки — Path с одним вызовом Arc: центр, радиус, стартовый угол сверху и конечный угол, привязанный к прогрессу, направление по часовой. StrokePath обводит путь линией шириной три со сглаживанием — получается полый индикатор, который отрастает по кругу без единого кадра анимации. Трасса выстрела — готовая StrokeLine: две точки, ширина два, жёлтый цвет. Путь ring строится заново каждый кадр, потому что угол меняется; статичные фигуры так рисовать дорого.

Где примитивы бьют спрайты#

ЗадачаИнструментПочему примитив
Сетка уровня в редактореStrokeLine в цикле по шагуспрайтам нужен тайл под каждую длину
Рамки и панели HUDStrokeRect и FillRectцвет меняется константой, атлас не растянешь
Индикаторы прогрессаArc и StrokePathугол считается кодом, а не режется из картинки
Прототип физикиFillCircle по точкам телколлизии видно сразу, до отрисовки спрайтов
Снаряды и пузыриFillCircleдесяток заготовок заменяет одна функция
Финальный арт игрыспрайты и атласычистой геометрии не хватает выразительности

Какой кусок кольца обведён при прогрессе перезарядки 0.25?

Прочитайте код и выберите, что он выведет:

// g.cooldown / g.cooldownMax == 0.25
start := float32(-math.Pi / 2)
end := start + 2*math.Pi*(1-g.cooldown/g.cooldownMax)
ring.Arc(600, 40, 16, start, end, vector.Clockwise)
vector.StrokePath(screen, &ring, ...)

Цена: строить путь каждый кадр дорого#

Примитивы дёшевы только на первый взгляд: каждый вызов FillCircle внутри собирает путь из дуги на десятки сегментов, триангулирует его и отдаёт видеокарте. Десяток кругов на HUD — незаметно, тысяча частиц-кругов в каждом кадре — уже ощутимо, и тут начинается экономика. Фигуры, которые не меняются между кадрами, обязаны кэшироваться: vector.Path собирается один раз и хранится в поле структуры, а Draw рисует готовый путь вызовом FillPath или StrokePath — геометрия не пересчитывается, платится только вывод. Если фигура всё-таки поменялась, Reset() чистит путь, и он собирается заново — редкое событие, а не каждый тик.

Второй рычаг — сглаживание: флаг antialias и AntiAlias в опциях включают дополнительную работу на каждый пиксель края, и на сотнях объектов разница в кадрах видна без замеров. Правило: сглаживание — крупному UI и фигурам на переднем плане, без сглаживания — массовым объектам и отладочным слоям. Полную карту таких цен и способы замера соберёт урок про производительность: примитивы стоят там в одном ряду с DrawImage и текстом, и кэш путей — первый пункт их оптимизации. Практический ориентир такой: HUD и отладочные слои живут без кэша спокойно, а всё, что рисуется циклом по десяткам объектов, обязано либо кэшировать пути, либо переходить на готовые функции, либо уходить в предрендер — один раз нарисованная в offscreen-картинку сетка дешевле векторной в разы.

Что делает последний аргумент bool в вызове vector.StrokeLine?

Хитбоксы на примитивах: круг против прямоугольника#

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

Такая геометрия ложится прямо поверх AABB-коллизий: там мир проверялся прямоугольниками, здесь к ним добавляются круги — снаряды, зоны взрыва, радиусы подбора. Практика прототипа физики: тела рисуются FillCircle по тем же координатам, по которым проверяются пересечения, поэтому логика и картинка не разъезжаются, а любая ошибка в движении видна на первом же кадре. Круговой хитбокс ещё и удобнее для геймплея: он не имеет углов, игрок прощает ему промахи на волосок, и вращение спрайта не ломает форму проверки — повёрнутый прямоугольник потребовал бы отдельной математики, круг от поворота не меняется. По этой формуле стоит устроить быструю проверку широким кругом перед точной: сначала грубый фильтр по расстоянию центров, потом детальная проверка — это тот же принцип грубого и точного прохода, на котором стоят все коллизии в играх.

Правила примитивов одним списком#

  • Готовые функции FillCircle, StrokeCircle, StrokeLine, FillRect, StrokeRect закрывают HUD и сетки
  • DrawFilledCircle и другие старые имена устарели с версии 2.9: используйте актуальные
  • Path собирается из MoveTo, LineTo, Arc и Close и отдаётся в FillPath или StrokePath
  • Флаг antialias — крупному UI, массовые объекты рисуются без сглаживания
  • Меняющаяся фигура строится заново каждый кадр, статичная кэшируется в поле структуры
  • Мутирующий кэш-путь начинается с Reset, иначе сегменты копятся и кадр дорожает
  • Хитбоксы: квадраты расстояний для кругов, зажим ближайшей точки для круга с прямоугольником
  • Прототип физики рисуется теми же координатами, что проверяются на пересечение

Соберите путь из сегментов

Восстановите два метода: постановка пера и сегмент линии.

var path vector.Path path.(x0, y0) // перо в стартовую точку path.(x1, y1) // сегмент до второй точки vector.StrokePath(screen, &path, &vector.StrokeOptions{Width: 2}, &vector.DrawPathOptions{})

Проверка круг-прямоугольник

Соберите функцию пересечения круга с прямоугольником.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Примитивы и итоговый проект#

В мини-аркаде, к которой курс идёт, примитивы займут место по силе: рамка HUD и полоса здоровья — StrokeRect и FillRect из этого урока, кулдаун способности — дуга Arc, отладочные коллизии при доводке — StrokeCircle вокруг каждого хитбокса, включаемые одной константой. Отладочный слой из примитивов — приём профессиональных студий: спрайты могут врать про границы тела, круги и прямоугольники вокруг тел — никогда, и расхождение физики с картинкой ловится мгновенно, до воспроизведения бага игроком. Тот же слой помогает настройке: радиус подбора, длина луча, зона взрыва — всё это числа в коде, но включённый слой превращает их в видимые окружности, и балансировка из гадания становится настройкой по линейке.

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

Урок закрыл дыру между спрайтами и текстом: игра умеет рисовать геометрию — от рамки HUD до дуги перезарядки — не тратя ни одного файла ассетов. Пять готовых функций покрывают типовой UI, Path собирает нестандартные фигуры, кэш путей бережёт кадр, а хитбоксы на кругах и прямоугольниках делают прототип физики честным. Приёмы урока складываются в рабочий набор: статичный HUD без кэша, динамичные фигуры на готовых функциях, отладочный слой на одной константе. Дальше курс считает кадры: урок про производительность покажет, как замерять цену этих вызовов и где примитивам придётся уступить атласам. Домашнее задание соберёт HUD вашего проекта из примитивов — и он будет выглядеть готовым раньше, чем появятся настоящие спрайты.

Термины урока

Термин

vector.Path

нажмите, чтобы перевернуть
Определение

Собираемая фигура: MoveTo, LineTo, Arc и Close. Отдаётся в FillPath для заливки или StrokePath для обводки с опциями.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Пакет vector рисует UI без картинок: пять готовых функций для кругов, линий и прямоугольников.
  • DrawFilledCircle устарела с версии 2.9 — актуальные имена начинаются с Fill и Stroke.
  • Path собирает нестандартные фигуры из MoveTo, LineTo и Arc и отдаётся в FillPath или StrokePath.
  • Сглаживание — последний аргумент готовых функций и AntiAlias в опциях: оно не бесплатное.
  • Меняющиеся фигуры строятся заново, статичные кэшируются в поле структуры с Reset при мутации.
  • Кулдаун-дуга — Arc по углу прогресса; трасса выстрела — StrokeLine по двум точкам.
  • Хитбоксы примитивов: квадраты расстояний для кругов, зажим ближайшей точки для прямоугольников.

Домашнее задание

Соберите HUD проекта из примитивов: рамку экрана через StrokeRect, полосу здоровья через FillRect с шириной от hp, круг перезарядки способности через Arc и StrokePath с углом от прогресса. Добавьте отладочный слой на клавишу F3: StrokeCircle вокруг каждого хитбокса и StrokeLine между парами сталкивающихся тел — и проверьте, что круги сидят точно на физике. Реализуйте circleHitsRect для снарядов и зон подбора, сравните с прямоугольными проверками из урока про коллизии: расхождение на углах нормально, круг честнее. Заметите подтормаживание — кэшируйте статичные пути в полях структуры и сверьте TPS до и после. В конце включите и выключите antialias на массовом объекте: разница в кадрах должна быть видна на сотнях кругов.

Личные заметкиПусто
Хранятся только в вашем браузере (Ebitengine · Векторная графика: линии и круги)

Вопросы и ответы по уроку

Почему координаты примитивов float32, а не float64?

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

Чем FillCircle отличается от Path с дугой на два пи?

Результат один: готовая функция внутри строит ровно такой путь, но в оптимизированном виде с переиспользованием вершин. Практическое правило: стандартная фигура — готовая функция, нестандартная или составная — собственный Path.

Можно ли примитивами нарисовать весь прототип игры?

Да, и это рабочий приём: круги-игроки, прямоугольники-стены, линии-лучи проверяют механику до появления арта. Прототип на примитивах живёт днями, а не часами, потому что правка цвета и формы — константа в коде, а не перерисовка ассета.

Термины урока

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

Следующая ступень учебного пути

Chromedp25 уроков~11 чпрактика25 уроков уже доступны

Chromedp в Go: автоматизация Chrome без Selenium

Курс chromedp для Go: навигация, селекторы, клики и формы, скриншоты и PDF, JS-оценка, вкладки, прокси, пул браузеров и скрапинг. 25 уроков.

  • Запускать Chrome из Go и управлять страницами
  • Находить элементы селекторами и ждать их появления
  • Кликать, заполнять формы и извлекать данные
  • Делать скриншоты, PDF и выполнять JavaScript