惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
GRAHAM CLULEY
T
Tenable Blog
Know Your Adversary
Know Your Adversary
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
S
Security Affairs
NISL@THU
NISL@THU
O
OpenAI News
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
S
SegmentFault 最新的问题
S
Schneier on Security
G
Google Developers Blog
V
V2EX
C
Check Point Blog
U
Unit 42
Google DeepMind News
Google DeepMind News
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
T
The Exploit Database - CXSecurity.com
Recent Announcements
Recent Announcements
M
MIT News - Artificial intelligence
S
Secure Thoughts
博客园 - 司徒正美
Recorded Future
Recorded Future
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
K
Kaspersky official blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
AI
AI
博客园 - 聂微东
N
News and Events Feed by Topic
SecWiki News
SecWiki News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
The Hacker News
The Hacker News
The Last Watchdog
The Last Watchdog
Project Zero
Project Zero
W
WeLiveSecurity
博客园 - Franky

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Разработка карточной игры в Game Maker
butuzoff_ya · 2026-05-05 · via Все публикации подряд на Хабре

Простой

8 мин

8.1K

Всем привет! Меня зовут Яков, и я разработчик игр. Возможно, вы играли в мои предыдущие проекты: Dom Rusalok, Loretta и Anoxia Station. Сейчас я заканчиваю работу над новой игрой — Bonereader. И, поскольку я много думаю сейчас о балансе игры, я решил поделиться опытом, в свободной форме порассуждать обо разработке, и дать, надеюсь, полезные советы, которые помогут другим разрабам.

Костяной барон

Костяной барон

Bonereader — карточная игра с механикой покера на костях. Вас поджаривают на электрическом стуле, и вы оказываетесь в Чистилище, вдохновлённом романами Карлоса Кастанеды и Кормака Маккарти, где вынуждены играть в кости с разными духами за призрачный шанс на перерождение.

В основе механики лежит костяной покер (yatzy). В детстве мы с братом часто играли в одну из его вариаций. Когда работа над Anoxia Station подходила к концу, я, как и многие, увлёкся Balatro. А мне за короткое время нужно было придумать концепт для новой игры. И я вспомнил про покер «на костях».

Вот такое поле мы с братом рисовали, когда играли.

Вот такое поле мы с братом рисовали, когда играли.

Я не программист, а врач по образованию. Несмотря на то что занимаюсь этим уже лет восемь. Моя главная проблема в том, что у меня нет систематических знаний и я не знаю ни одного языка программирования, кроме GML. Если я нахожу элегантное решение какой‑то проблемы в чужом проекте на GitHub, я, конечно же, «заимствую» его, но всегда существенно переписываю. Хотя сейчас в коде для меня нет задачи, которая могла бы поставить меня в тупик. В крайнем случае, я всегда могу обратиться за советом к бесплатной версии Claude.

Пожалуй, единственное, чего я не освоил — сетевой код, но лишь потому, что такая задача никогда передо мной и не стояла. В остальном есть лишь два ограничения: невозможность работать в 3D и фантазия.

Код

По сути, в моём проекте есть три главных объекта: obj_dice, obj_combination и obj_controller, который отвечает за global.game_state (то есть за всё происходящее в игре).

/// [GAME STATES]
///  "start_round"
///  "rolling"
///  "in_play"
///  "tally_score"
///  "finalize_score"
///  "menu_screen"
///  "in_shop"
///  "in_hub"

Вы можете выбрать одну из стартовых колод, содержащую несколько карт‑комбинаций и россыпь разнообразных костей. Ваша цель: набрать необходимое количество очков за ограниченное количество попыток. Всё просто, верно? Ну, не совсем:)

Экран с выбором исходной колоды

Экран с выбором исходной колоды

После короткой беседы с тем или иным духом вам открывается магазин, в котором вы можете приобрести предметы и кости, продать или купить карты‑комбинации. У каждого монстра есть «правило дома» — специальное условие, которое необходимо выполнять.

Реролл магазина такой дорогой из-за выбранной колоды.

Реролл магазина такой дорогой из‑за выбранной колоды.

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

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

С каждым противником вы играете четыре партии, последняя проходит под случайным «Знамением» — по сути, дебаффом. Наконец, на каждый ход или бросок на случайную карту из вашей руки накладывается случайное: есть как выгодные, так и не очень, так и что‑то между.

Игровой стол у каждого противника тоже свой собственный!

Игровой стол у каждого противника тоже свой собственный!

Все виды костей (пока что).

Все виды костей (пока что).

Математика подсчёта примерно такая:

function _check_card_effect_to_final_score(add_to_score_all, card_level) {
    var final_score = add_to_score_all; 
    var temp_mult = 1;
    var temp_bonus = 0;
    
    var basepips = ceil(card_base + global.dice_slot1 + global.dice_slot2 
    + global.dice_slot3 + global.dice_slot4 + global.dice_slot5);

    var base_score_with_pips = basepips;
    var item_bonus_score = global.item_bonus_score;
    var item_bonus_mult = global.item_bonus_mult;

Затем мы накладываем проклятие карты, если оно есть:

if (ENEMY_EFFECT_CARD_HAND_CURSE) {
    final_score = max(0, final_score - (100 * eff_power));
}

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

if (global.GET_BONUS_TATTOO_ODD_BONUS) {
    var countodd = 0;
    var dice = [global.dice_slot1, global.dice_slot2, 
    global.dice_slot3, global.dice_slot4, 
    global.dice_slot5];

    for(var i=0; i<5; i++) { if(dice[i] % 2 == 1) countodd += tattoo_odd_bonus; }
    temp_mult += countodd; 
}

if (global.GET_BONUS_TATTOO_EVEN_BONUS) {
    var counteven = 0;
    var dice = [global.dice_slot1, global.dice_slot2, 
    global.dice_slot3, 
    global.dice_slot4, global.dice_slot5];

    for(var i=0; i<5; i++) { if(dice[i] != 0 && dice[i] % 2 == 0) 
    counteven += tattoo_even_bonus; }
    
    temp_mult += counteven;
}

// --- ФИНАЛЬНЫЙ РАСЧЕТ ---
var result = (final_score + temp_bonus) * temp_mult;

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

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

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

Предметы работают по принципу:

if (sprite_index == spr_item_golden_tooth) 
{
    audio_play_sound(snd_item_golden_thoot, 0, false, global.SOUNDS_VOLUME_MAX)
    add_bonus_per_dice_value(6, 10); 
    ACTIVATED = true
}

Где add_bonus_per_dice_value ():

function add_bonus_per_dice_value(dice_value, bonus_per_die) {
    var count = 0;
    if (global.dice_slot1 == dice_value) count++;
    if (global.dice_slot2 == dice_value) count++;
    if (global.dice_slot3 == dice_value) count++;
    if (global.dice_slot4 == dice_value) count++;
    if (global.dice_slot5 == dice_value) count++;
    
    global.item_bonus_score += count * bonus_per_die;
}

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

Всех монстров художница сначала рисует от руки, а затем уже создает digital-версию.

Всех монстров художница сначала рисует от руки, а затем уже создает digital‑версию.

Баланс

Поскольку в проекте я работаю один, то не веду таблиц (кроме локализации) и документации. У меня есть файл _Balance (раньше в GM это называлось script, но я не хочу никого путать, и когда‑то он мог нести в себе только одну функцию, но сейчас туда можно записывать сколько угодно функций), куда я в виде #macro записал основные показатели: например, базовое количество очков, которое даёт карта, её цена покупки/продажи и так далее.

В игре два режима: сюжетный и роглайк. Если сюжетный более выверен с геймдизайнерской точки зрения — враги идут по очереди, мы примерно представляем себе кривую прогрессии, определяем пул бонусов для того или иного уровня игрока, — то в роглайт‑режиме многое зависит от рандома.

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

Вот как идёт подсчёт требуемых очков очки для противника. Для своего удобства я использовал термин «анте», который взял из Balatro и который и в самом Balatro, на самом деле, означает не своё первое смысловое значение, но мы это опустим.

function calculate_roguelite_level_settings () {
    
    // Текущий анте (противник) - от 0 до 5
    var _current_ante = array_length(global.DEFEATED_ENEMIES_CASINO);
    
    // Базовые очки для каждого раунда (уровня)
    var _base_points = 0;
    
    switch (global.level) {
        case 0: _base_points = LEVEL_ONE_TARGET_POINT;   break;  
        case 1: _base_points = LEVEL_TWO_TARGET_POINT;   break;  
        case 2: _base_points = LEVEL_THREE_TARGET_POINT; break;  
        case 3: _base_points = LEVEL_BOSS_TARGET_POINT;  break;  
        case 4: _base_points = LEVEL_BOSS_TARGET_POINT;  break;  
    }
    
    var _ante_multipliers = [
        1.0,   
        3.2,   
        6.0,   
        10.0,  
        14.0,  
    ];
	
    // Если антов больше 6, продолжаем увеличивать множитель
    var _ante_multiplier = 1.0;
    if (_current_ante < array_length(_ante_multipliers)) {
        _ante_multiplier = _ante_multipliers[_current_ante];
    } else {
        // Для антов больше 6: продолжаем экспоненциальный рост
        _ante_multiplier = _ante_multipliers[array_length(_ante_multipliers) - 1] * 
                          power(1.6, _current_ante - array_length(_ante_multipliers) + 1);
    }
    
    // Рассчитываем финальные очки
    global.target_score_base = floor(_base_points * _ante_multiplier);
    
    // Применяем бонус татуировки (-15% к требуемым очкам)
    if (global.GET_BONUS_TATTOO_MINUS_10_TARGET_POINTS) {
        global.target_score_base = floor(global.target_score_base * 0.85);
    }
    
    global.target_score = global.target_score_base;
    
    if (global.BOSS_DEBUFF == "HALF_COMBO" && (global.level == 3))
    {
        global.target_score = round(global.target_score/3)
    }

    // Назначаем "Знамение" для финального раунда
    if (global.level == 0) {
        SET_UP_THE_BOSS_EFFECT(); 
    }
}

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

В Bonreader же я гораздо больше потратил времени на создание «ощущения» от карт и кубиков. Их анимаций, наведения, шейдеров для проклятий.

Моя идея в том, чтобы игрок мог выбирать, как двигаться к огромным числовым значениям.  Выборов и синергий масса.

Моя идея в том, чтобы игрок мог выбирать, как двигаться к огромным числовым значениям. Выборов и синергий масса.

Советы разработчикам

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

Если вы тоже хотите создать игру на GM, вот немного сермяжной правды:

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

  2. Продумывайте текстурные и аудиогруппы заранее. Вообще, продумывайте архитектуру проекта (систему сохранений, контроллер). Это сэкономит много времени при создании уровней. Учитывайте ограничения выбранного движка и свои навыки ещё на этапе планирования.

  3. Создавать игры с поддержкой геймпада выгоднее.

  4. Не создавайте «hard‑сoded» текст. Используйте.csv‑таблицы. Если не знаете, как это сделать, обязательно научитесь.

  5. Сохраняйте лицензии на все используемые звуки и музыку. Делайте это сразу, чтобы потом не терять время на поиск источников.

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

  7. По возможности используйте меньше шейдеров. Особенно если вы не пишете их сами, а покупаете или находите и внедряете. Нет гарантии, что шейдер на одной платформе будет работать так же хорошо, как на другой. Keep it simple.

  8. Чаще рассказывайте о своей игре. Общайтесь с аудиторией, делитесь процессом и идеями.

  9. Я бы советовал держать код для API‑разных магазинов в пределах одного участка кода. Ну, к примеру, ачивки я делаю так:

// Achievement #1
function  Achievement_Unlock_1() 
{
	if (IS_DEMO_BUILD) {exit}
	
	switch (os_type)
	{
		case os_windows:
			
			switch(WINDOWS_TARGET_PLATFORM)
			{
				case WindowsTargetPlatforms.Steam:
				
					if !steam_get_achievement ("ach_01_win_shaman") 
                    steam_set_achievement ("ach_01_win_shaman");
					
				break;
				
				case WindowsTargetPlatforms.GOG:
				
					if !instance_exists(obj_gog_ach_1) 
                    {instance_create_depth(x, y, -999, obj_gog_ach_1)}
					
				break;
				
				case WindowsTargetPlatforms.EOS:
	
					//
					
				break;
			}
    		break;
		case os_ps4:
			//
		break;
		case os_ps5:
			//
		break;
		case os_gdk:
			// 		
		break;
	}
}

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

Но сейчас у меня ещё есть время, а глаз, откровенно говоря, замылен. Мне не хватает фидбека от реальных игроков и любителей жанра, потому с нетерпением жду ваши отзывы. А написать вы можете в наш тг или вк, да и просто оставить отзыв в Steam, GOG или Itch.

И спасибо за внимание!:)