Содержание урока · 10 разделов
- Механика и контент: правило против данных
- Петля ядра: действие, награда, новая цель
- Кривая сложности: постепенно и с передышками
- Проблема, виноватая система и фикс
- Баланс: числа — это слайдеры
- Порядок настройки баланса одним списком
- Фидбек: игрок должен чувствовать событие
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Гейм дизайн звучит как профессия про идеи, но по сути это инженерия правил: игра — набор правил, по которым данные превращаются в опыт. Хорошая новость для программиста: половина этой работы уже у вас в руках — вы двигаете числа, строите циклы и обязаны понимать, почему игрок возвращается. В этом уроке игра раскладывается на механики и контент, собирается петля ядра, растягивается кривая сложности, а настройка превращается в процедуру: менять по одному числу и играть после каждого изменения. Всё это делается кодом, который вы уже умеете писать, — структурой Config и парой функций. Художественный дар не требуется: требуется дисциплина измерений и честная запись впечатлений после каждой партии. Отдельный договор урока: слово «скучно» — не оскорбление, а диагностический симптом, и у него есть короткий список виноватых систем; мы научимся по жалобе находить виновника — петлю, кривую или фидбек — и чинить изменением одного числа, а не переписыванием проекта.
Механика и контент: правило против данных#
Механика — это правило: двойной прыжок, урон от касания врага, монета, прибавляющая единицу к счёту. Контент — данные, которые живут по правилам: уровни, типы врагов, спрайты, звуки. Двойной прыжок — одна механика и несколько строк кода; площадки для прыжков — сотни единиц контента и ни одной новой строки. Механика добавляет поведение, контент наполняет его вариациями, и путаница между ними — самая дорогая ошибка новичка в гейм-дизайне: новое правило стоит дороже нового уровня в разы.
Разница видна в цене изменения: механику менять дорого — правится код, тестируется поведение, ломаются привычки игрока; контент менять дёшево — новая матрица тайлмапа, новый спрайт, другой интервал спавна. Отсюда первое правило объёма: механик мало, контента много. Три механики — бег, прыжок, сбор — кормят десятки уровней; десять механик на один уровень дают игру, в которой нечем играть, потому что ни одно правило не доведено до конца. Проверка на границе проста: фича описывается без глагола действия — контент; без неё ломается движение или счёт — механика. Практический вывод для планирования: новая механика попадает в план только тогда, когда старая выжала себя в контенте; до этого идеи новых правил лежат в заметках, а не в коде — так проект растёт наружу дёшево, а внутрь — редко и осознанно.
Двойной прыжок героя — это что?
Петля ядра: действие, награда, новая цель#
Петля ядра — то, что игрок делает каждые несколько секунд: действие, награда, новая цель. Аркадный минимум: двигайся — собери монету — счёт растёт — появились враги, уклоняйся — снова собирай. Каждое звено запускает следующее, и петля замыкается быстрее, чем игрок успевает заскучать: шаг занимает секунды, в крайнем случае десяток. Всё, что вне петли — меню, катсцены, настройки, — украшение, и пока петля не работает, украшать нечего.
Проверка петли примитивна: закройте свою игру через три минуты и честно ответьте, что тянуло вернуться. Если ответа нет — петля разомкнута: действие не даёт награды, награда не создаёт цели или цели стоят слишком далеко друг от друга. В коде петля живёт в вашем Update: движение — действие, счёт из урока про состояние — награда, следующая волна — новая цель. Если счёт растёт, но ни на что не влияет, звено разорвано: свяжите его с целью — каждые десять монет открывают волну посложнее. Обратите внимание на масштаб: петля измеряется секундами, а не миссиями. Если ваше «действие — награда» занимает пять минут, это не петля, а эпизод; найдите внутри него короткий цикл — шаг, сбор, щелчок счёта — и стройте удержание вокруг него.
Числа игры в одной структуре
GOtype Config struct {
EnemySpeed float64
EnemyHP int
EnemyDamage int
SpawnInterval int // тиков между волнами
PlayerHP int
}
var DefaultConfig = Config{
EnemySpeed: 40,
EnemyHP: 3,
EnemyDamage: 1,
SpawnInterval: 180, // три секунды при TPS 60
PlayerHP: 5,
}
func spawnEnemy(cfg Config, x, y float64) Enemy {
return Enemy{
x: x,
y: y,
speed: cfg.EnemySpeed,
hp: cfg.EnemyHP,
damage: cfg.EnemyDamage,
}
}Все настраиваемые числа живут в одной структуре Config, а DefaultConfig фиксирует стартовую точку баланса: её можно вернуть одной командой git. spawnEnemy читает только поля конфигурации и не знает других чисел — враг рождается с параметрами текущей волны. Ни одной магической константы внутри логики: цифры переехали туда, где их видно и удобно двигать. Полями Config балансируется всё — от скорости пуль до частоты спавна: структура растёт вместе с игрой, но числа не разбредаются по функциям.
Кривая сложности: постепенно и с передышками#
Кривая сложности — график давления на игрока по времени, и первые минуты на нём обязаны быть лёгкими: лёгкая победа в начале — самый дешёвый способ удержать внимание. Дальше вводите элементы по одному: первый враг медленный, второй появляется, когда первый освоен, новый тип — тогда, когда предыдущий перестал пугать. Между волнами нужны паузы: несколько секунд тишины, за которые игрок переводит дух и замечает, что стало чуть труднее.
Сплошной рост утомляет, а резкий скачок убивает интерес: потолок скорости врага существует не из доброты, а потому что реакция человека конечна — враг, которого нельзя догнать взглядом, не создаёт вызова, а создаёт обиду. Практически кривая живёт в функциях от номера волны: скорость и HP растут умеренно, каждые несколько волн добавляется один элемент, между волнами — передышка. Пауза между волнами — тоже часть кривой: без неё игра превращается в сплошное давление, а с ней — в ритм напряжения и отдыха. Хотите проверить кривую — сыграйте чужую версию своей игры: чужой Config, подставленный на минуту, покажет разницу быстрее любых рассуждений о балансе.
Кривая как функция от номера волны
GOfunc configForWave(base Config, wave int) Config {
cfg := base
cfg.EnemySpeed += float64(2 * (wave - 1))
if cfg.EnemySpeed > 80 {
cfg.EnemySpeed = 80 // потолок: иначе не уворачиваться
}
if wave%4 == 0 {
cfg.EnemyHP = base.EnemyHP + 1 // каждая четвёртая волна крепче
}
return cfg
}Функция строит конфигурацию волны из базовой: копия, прибавка к скорости, потолок как защита от нечеловеческой реакции и бонус HP раз в четыре волны. База не мутирует — каждая волна считается от DefaultConfig, поэтому эксперименты не накапливаются. Именно так выглядит кривая сложности в коде: арифметика от номера волны вместо ручного редактирования десятков чисел. Края функции проверяются на бумаге за минуту: волна первая, волна кратная четырём, волна за потолком — кап обязан держать любое значение.
Проблема, виноватая система и фикс#
| Жалоба игрока | Какая система виновата | Фикс |
|---|---|---|
| «Скучно на пятой минуте» | кривая сложности | волны с паузами, новый враг раз в пару минут |
| «Не понимаю, что убило» | фидбек | звук урона, вспышка, короткая тряска |
| «Монеты собираются в молчании» | фидбек | короткий звук и частицы на подбор |
| «Слишком легко с первых секунд» | баланс | стартовые HP и скорость врага ниже в Config |
| «К третьей волне невозможно» | баланс | потолок скорости, рост HP реже |
| «Не знаю, зачем собирать» | петля ядра | связать сбор с целью: счёт открывает новую волну |
Какие параметры получит враг четвёртой волны?
Прочитайте код и выберите, что он выведет:
base := Config{EnemyHP: 3, EnemySpeed: 50}
cfg := configForWave(base, 4)
// configForWave: +2 к скорости за каждую волну после первой,
// потолок 80, каждая четвёртая волна: HP = base.EnemyHP + 1Игрок говорит: «Скучно». Что проверять в первую очередь?
Частая картина: коллизия работает, HP уменьшается, в логе всё честно — а игрок продолжает бежать сквозь врагов, потому что за миг урона экран не дрогнул и звук не прозвучал. Что сломалось бы дальше: о смерти он узнаёт только по экрану поражения и решает, что умер внезапно и несправедливо, — а дальше игра закрывается. Минимальный фикс — один звук и вспышка на событии урона: два вызова, а читаемость боя меняется целиком. Обратная сторона той же ошибки — фидбек без события: вспышки и звуки на пустых триггерах учат игрока игнорировать эффекты, и настоящий урон теряется в шуме; каналы подтверждения подключаются только к реальным событиям логики.
Баланс: числа — это слайдеры#
Баланс игры — не математика равновесия, а дисциплина слайдеров: скорость врага, HP, урон, интервал спавна — числа, которые вы двигаете и слушаете результат. Правила процедуры: менять по одному числу за раз; после каждого изменения играть хотя бы пару минут; записывать впечатление одной строкой; возвращаться к DefaultConfig, если стало хуже. Два числа, сдвинутых одновременно, обнуляют выводы — вы никогда не узнаете, что именно сработало.
Держите числа в одном месте — структуре Config: прыжок в чужую функцию за константой — способ забыть, где лежит баланс. Отдельно цените точку возврата: коммит с рабочим балансом — страховка от того, что завтрашний эксперимент похоронит вчерашнюю играбельность. И помните: играбельность важнее симметрии — враг не обязан быть честным, он обязан быть понятным и преодолимым; игрок прощает сложность, но не прощает несправедливость. Отдельно про масштаб изменений: десять-двадцать процентов — не суеверие, а диапазон, в котором разница ощущается, но не ломает игру; удвоение скорости проверяет рефлексы, которых у игрока ещё нет, и выводы из такой партии ничего не говорят о балансе.
Порядок настройки баланса одним списком#
- Все игровые числа живут в структуре Config, ни одной константы в логике
- DefaultConfig — точка возврата: рабочие значения всегда восстановимы
- Одно изменение за раз: два сдвинутых числа обнуляют выводы
- После каждого изменения — играть и записывать впечатление строкой
- Кривая сложности считается от номера волны, а не правится руками по ходу
- У скорости врага есть потолок: реакция игрока конечна
- Каждое событие петли подтверждается минимум одним каналом фидбека
- «Скучно» — сигнал про фидбек и кривую, а не про количество контента
Соберите рождение врага из конфигурации
Восстановите два пропуска: поля Config, из которых враг берёт поведение.
Игрового окна в песочнице сайта нет, но Config и функция кривой — чистая математика над числами: тематическая программа печатает параметры врагов по волнам и следит за потолком скорости. Те же инварианты, которые вы проверяете, играя: изменение одного числа меняет поведение предсказуемо, без сюрпризов и без накопленных правок. Числа из примеров урока — стартовые, не эталонные: ваш проект назовет им другие значения, и это нормально, потому что баланс привязан к остальным числам игры.
Процедура изменения одного числа
Соберите дисциплину баланса по шагам.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Фидбек: игрок должен чувствовать событие#
Фидбек превращает событие в ощущение: без подтверждения игрок не верит, что что-то произошло. Урон герою — короткий звук из урока про звук, красная вспышка и тряска камеры; сбор монеты — звон и частицы; смена волны — вспышка экрана и обновлённая надпись. У каждого события петли обязан быть минимум один канал подтверждения, лучше два: слух и зрение работают в паре.
Дозируйте: тряска на каждое касание превращается в шум, частицы в тысячу экземпляров за кадр уронят частоту кадров. Фидбек — не украшение, а часть механики: урон без звука читается как «пронесло». Отсюда главный диагноз курса: «скучно» почти никогда не значит «мало контента» — это нет фидбека или нет кривой сложности. Десятый враг того же вида не спасёт; короткий звук при уроне — иногда спасает игру целиком. Иерархия каналов помогает дозировать: звук — для частых событий, вспышка — для важных, тряска — для редких и тяжёлых; чем чаще событие, тем короче и тише его подтверждение, иначе частое превращается в фоновый шум и перестаёт считываться.
После урока у игры появляется то, что не видно на скриншотах: замкнутая петля, растянутая кривая и конфигурация, которую можно крутить без страха. Это и есть гейм дизайн основы, доступные программисту без отдельной профессии: механики малочисленны, контент дёшев, числа меняются по одному, события подтверждаются фидбеком. Дальше остался итоговый проект, где петля, Config и кривая соберутся в одну мини-аркаду. Домашнее задание — первый настоящий сеанс балансировки: он займёт полчаса и расскажет о вашей игре больше, чем неделя добавления новых уровней. Помните контрольный вопрос урока: что тянуло вернуться? Ответ на него — план следующей итерации. И ещё одно умение, которое проверится в проекте: отличать «мне не нравится» от «игроку не понятно». Первое — вопрос вкуса и правится откатом числа; второе — вопрос ясности и правится фидбеком или кривой. Различать их — половина гейм-дизайна.
Термины урока
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Механика — правило поведения, контент — данные по правилам; механик мало, контента много.
- Петля ядра: действие, награда, новая цель; разрыв звена ощущается как скука при любом объёме контента.
- Кривая сложности начинается с лёгкой победы и вводит по одному элементу с передышками между волнами.
- Баланс — дисциплина слайдеров: одно число за раз, игра после каждого изменения, запись впечатлений.
- Config собирает числа в одном месте, DefaultConfig — точка возврата в играбельное состояние.
- Кривая считается функцией от номера волны: прибавки, потолок скорости и бонусы не накапливаются.
- «Скучно» почти всегда означает отсутствие фидбека или кривой сложности, а не мало контента.
Домашнее задание
Проведите первый сеанс балансировки над своим проектом. Вынесите скорость, HP и урон врагов в Config с DefaultConfig и убедитесь, что spawnEnemy не видит других чисел. Напишите configForWave с потолком скорости и бонусом раз в четыре волны; между волнами добавьте паузу в пару секунд. Прогоните процедуру дисциплины: пять изменений по одному числу, после каждого — две минуты игры и строка записи в файл заметок. Проверьте фидбек всех событий петли: урон, сбор, новая волна; где нет ни звука, ни частиц, ни вспышки — добавьте хотя бы один канал из уроков про звук и эффекты. Закройте игру через три минуты и честно ответьте, что тянуло вернуться; ответ запишите — это готовый список задач. Затем сравните первую и пятую версию баланса: разница впечатлений и есть материал итогового проекта. Если после пятого изменения игра не стала интереснее, верните DefaultConfig целиком и проверяйте петлю, а не числа. Правки держите в отдельных коммитах: откат эксперимента должен стоить одной команды, а не раскопок.
Личные заметкиПусто
Вопросы и ответы по уроку
Гейм-дизайн — это обязательно долго играть в собственную игру?
Да, и это самое дешёвое исследование на свете: две минуты игры после каждого изменения дают больше, чем час теоретизирования. Записи впечатлений переводят ощущения в наблюдения и делают баланс повторяемым процессом, а не везением. Больше всего пользы приносят короткие сессии сразу после изменения: память об ощущении свежее любых заметок.
Куда девать сложность для опытных игроков?
Сначала кривая должна работать для новичка: лёгкая победа в начале — не оскорбление, а вход. Режимы сложности — те же слайдеры: три пресета Config с разной скоростью и HP, без единой новой механики. Названия пресетов говорите честно: «лёгкий» обязан быть легче обычного, иначе шкала теряет смысл.
Как понять, что контента достаточно и пора полировать?
Когда петля замкнута и кривая держит: если с тремя видами врагов играют дольше, чем с десятью у соседа, контент хватает. Признак недобора — не скука, а «нечего открывать»: награда перестала создавать новую цель. Полировка в этот момент — звук, вспышки, частицы: она усиливает уже работающее, а не заменяет неработающее.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Chromedp в Go: автоматизация Chrome без Selenium
Курс chromedp для Go: навигация, селекторы, клики и формы, скриншоты и PDF, JS-оценка, вкладки, прокси, пул браузеров и скрапинг. 25 уроков.
- Запускать Chrome из Go и управлять страницами
- Находить элементы селекторами и ждать их появления
- Кликать, заполнять формы и извлекать данные
- Делать скриншоты, PDF и выполнять JavaScript