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

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

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

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

Гейм-дизайн: механики и баланс

Ассеты загружены, FPS ровный, сцены переключаются — а через две минуты в игру не хочется возвращаться: виноват не движок, а несобранный баланс игры. В этом уроке — гейм дизайн для программиста: механика против контента, петля ядра, кривая сложности, числа как слайдеры и фидбек, из-за которого «скучно» почти всегда означает «не чувствуется», а не «мало уровней».

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

Гейм дизайн звучит как профессия про идеи, но по сути это инженерия правил: игра — набор правил, по которым данные превращаются в опыт. Хорошая новость для программиста: половина этой работы уже у вас в руках — вы двигаете числа, строите циклы и обязаны понимать, почему игрок возвращается. В этом уроке игра раскладывается на механики и контент, собирается петля ядра, растягивается кривая сложности, а настройка превращается в процедуру: менять по одному числу и играть после каждого изменения. Всё это делается кодом, который вы уже умеете писать, — структурой Config и парой функций. Художественный дар не требуется: требуется дисциплина измерений и честная запись впечатлений после каждой партии. Отдельный договор урока: слово «скучно» — не оскорбление, а диагностический симптом, и у него есть короткий список виноватых систем; мы научимся по жалобе находить виновника — петлю, кривую или фидбек — и чинить изменением одного числа, а не переписыванием проекта.

Механика и контент: правило против данных#

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

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

Двойной прыжок героя — это что?

Петля ядра: действие, награда, новая цель#

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

Проверка петли примитивна: закройте свою игру через три минуты и честно ответьте, что тянуло вернуться. Если ответа нет — петля разомкнута: действие не даёт награды, награда не создаёт цели или цели стоят слишком далеко друг от друга. В коде петля живёт в вашем Update: движение — действие, счёт из урока про состояние — награда, следующая волна — новая цель. Если счёт растёт, но ни на что не влияет, звено разорвано: свяжите его с целью — каждые десять монет открывают волну посложнее. Обратите внимание на масштаб: петля измеряется секундами, а не миссиями. Если ваше «действие — награда» занимает пять минут, это не петля, а эпизод; найдите внутри него короткий цикл — шаг, сбор, щелчок счёта — и стройте удержание вокруг него.

Числа игры в одной структуре

GO
type 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, подставленный на минуту, покажет разницу быстрее любых рассуждений о балансе.

Кривая как функция от номера волны

GO
func 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, урон, интервал спавна — числа, которые вы двигаете и слушаете результат. Правила процедуры: менять по одному числу за раз; после каждого изменения играть хотя бы пару минут; записывать впечатление одной строкой; возвращаться к DefaultConfig, если стало хуже. Два числа, сдвинутых одновременно, обнуляют выводы — вы никогда не узнаете, что именно сработало.

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

Порядок настройки баланса одним списком#

  • Все игровые числа живут в структуре Config, ни одной константы в логике
  • DefaultConfig — точка возврата: рабочие значения всегда восстановимы
  • Одно изменение за раз: два сдвинутых числа обнуляют выводы
  • После каждого изменения — играть и записывать впечатление строкой
  • Кривая сложности считается от номера волны, а не правится руками по ходу
  • У скорости врага есть потолок: реакция игрока конечна
  • Каждое событие петли подтверждается минимум одним каналом фидбека
  • «Скучно» — сигнал про фидбек и кривую, а не про количество контента

Соберите рождение врага из конфигурации

Восстановите два пропуска: поля Config, из которых враг берёт поведение.

func spawnEnemy(cfg Config) Enemy { return Enemy{ speed: cfg., hp: cfg., } }

Процедура изменения одного числа

Соберите дисциплину баланса по шагам.

Строки

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

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

Фидбек: игрок должен чувствовать событие#

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

Дозируйте: тряска на каждое касание превращается в шум, частицы в тысячу экземпляров за кадр уронят частоту кадров. Фидбек — не украшение, а часть механики: урон без звука читается как «пронесло». Отсюда главный диагноз курса: «скучно» почти никогда не значит «мало контента» — это нет фидбека или нет кривой сложности. Десятый враг того же вида не спасёт; короткий звук при уроне — иногда спасает игру целиком. Иерархия каналов помогает дозировать: звук — для частых событий, вспышка — для важных, тряска — для редких и тяжёлых; чем чаще событие, тем короче и тише его подтверждение, иначе частое превращается в фоновый шум и перестаёт считываться.

После урока у игры появляется то, что не видно на скриншотах: замкнутая петля, растянутая кривая и конфигурация, которую можно крутить без страха. Это и есть гейм дизайн основы, доступные программисту без отдельной профессии: механики малочисленны, контент дёшев, числа меняются по одному, события подтверждаются фидбеком. Дальше остался итоговый проект, где петля, Config и кривая соберутся в одну мини-аркаду. Домашнее задание — первый настоящий сеанс балансировки: он займёт полчаса и расскажет о вашей игре больше, чем неделя добавления новых уровней. Помните контрольный вопрос урока: что тянуло вернуться? Ответ на него — план следующей итерации. И ещё одно умение, которое проверится в проекте: отличать «мне не нравится» от «игроку не понятно». Первое — вопрос вкуса и правится откатом числа; второе — вопрос ясности и правится фидбеком или кривой. Различать их — половина гейм-дизайна.

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

Термин

Механика

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

Правило поведения: двойной прыжок, урон от касания, подбор монеты. Требует кода; меняется дорого.

1 / 6

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

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

Вывод

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

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

  • Механика — правило поведения, контент — данные по правилам; механик мало, контента много.
  • Петля ядра: действие, награда, новая цель; разрыв звена ощущается как скука при любом объёме контента.
  • Кривая сложности начинается с лёгкой победы и вводит по одному элементу с передышками между волнами.
  • Баланс — дисциплина слайдеров: одно число за раз, игра после каждого изменения, запись впечатлений.
  • Config собирает числа в одном месте, DefaultConfig — точка возврата в играбельное состояние.
  • Кривая считается функцией от номера волны: прибавки, потолок скорости и бонусы не накапливаются.
  • «Скучно» почти всегда означает отсутствие фидбека или кривой сложности, а не мало контента.

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

Проведите первый сеанс балансировки над своим проектом. Вынесите скорость, HP и урон врагов в Config с DefaultConfig и убедитесь, что spawnEnemy не видит других чисел. Напишите configForWave с потолком скорости и бонусом раз в четыре волны; между волнами добавьте паузу в пару секунд. Прогоните процедуру дисциплины: пять изменений по одному числу, после каждого — две минуты игры и строка записи в файл заметок. Проверьте фидбек всех событий петли: урон, сбор, новая волна; где нет ни звука, ни частиц, ни вспышки — добавьте хотя бы один канал из уроков про звук и эффекты. Закройте игру через три минуты и честно ответьте, что тянуло вернуться; ответ запишите — это готовый список задач. Затем сравните первую и пятую версию баланса: разница впечатлений и есть материал итогового проекта. Если после пятого изменения игра не стала интереснее, верните DefaultConfig целиком и проверяйте петлю, а не числа. Правки держите в отдельных коммитах: откат эксперимента должен стоить одной команды, а не раскопок.

Личные заметкиПусто
Хранятся только в вашем браузере (Ebitengine · Гейм-дизайн: механики и баланс)

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

Гейм-дизайн — это обязательно долго играть в собственную игру?

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

Куда девать сложность для опытных игроков?

Сначала кривая должна работать для новичка: лёгкая победа в начале — не оскорбление, а вход. Режимы сложности — те же слайдеры: три пресета Config с разной скоростью и HP, без единой новой механики. Названия пресетов говорите честно: «лёгкий» обязан быть легче обычного, иначе шкала теряет смысл.

Как понять, что контента достаточно и пора полировать?

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

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

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

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

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

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

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

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