Zemeroth - двухмерная пошаговая игра на движке ggez


#23

Шанс попасть, зоны расстановки и упрощение конфигов

Итак, вот запоздалое обновление за последние две недели.

  • Наконец-то реализован рассчет шансов попадания (взамен вечного 50% как было до этого).

    Пока остановился на таком подходе, вроде он не сильно сложный и довольно гибкий:

    • у бойцов появляются параметры “точность атаки”, “сила атаки”, “уклонение”;
    • эффективность_атаки = точность + сила - уклонение;
    • кидается куб с 10 гранями;
    • из эффективность_атаки вычитаем результат броска;
    • если получается <= 0 - значит это полный промах;
    • если получилось больше “силы атаки”, то срезаем до нее;
    • если посередине - значит такой урон и уходит на вражескую броню (типа, удар пришелся по касательной, но таки был ощутим);
    • значение брони просто вычитается из этого урона для подсчета итогового урона цели.

    В эту формулу интуитивно вставляются всякие коэффициенты, типа “если атакующий ранен, то вычесть силу его ран из эффективности атаки” (тоже реализовано).

    Пока я два недостатка описанной выше схемы знаю:

    1. Сходу в ней не показать оружие, у которого нет градации урона. Хз что это именно за оружие должно быть и нужно ли оно мне (вряд ли), но штуки вида “или попал и нанес 4 урона, или не попал совсем” непредставимы без дополнительных костылей.
    2. Отравляющий демон наносит 0 урона при атках - т.е. его шанс попасть ниже остальных демонов. Тут вбил костыль в виде повышения его точности атаки.

    Вживую выглядит так сейчас:

    Из визуала:

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

    Какие изменния случились с балансом:

    • Теперь первоочередная цель это ранить врага, добивать уже может быть меньшим приоритетом - иногда удобно, что бы практически неспособный попасть по твоим бойцам враг занимал клетку и не давал его более здоровым друзьям подойти;
    • Важность способности лечения у алхимика возросла, потому что толку от своих раненных бойцов становится сильно меньше.
  • Добавлены зоны начального построения (lines) и генератор больше не создает агентов в упор к врагам.

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

    А насчет зон, добавлено перечисление pub enum Line { Any, Front, Middle, Back }, позволяющее указывать в сценарии где мы какие виды агентов хотим видить. Теперь демоны-вызываетли всегда сощдаются в дальнем конце карты за жвым щитом, т.е. застрахованы от быстрой расправы на первом ходу.

    Снимок тестовой карты, в которую специально нагнана прям куча демонов что бы четко были видны зоны и отступы:

    (Вот еще до кучи пример начальной расстановки на огромной карте).

  • Кстати об огромных картах, я тут из интереса провел небольшой стресс-тест с реальным боем на огромной карте:

    ИИ думает секунд по 10, а анимации его хода минуты две занимают. Играть в это, конечно, не реально, но хорошо что игра хоть как-то функционирует.

  • Убрал дублирование информации из конфигов.

      Strength((
    -     base_strength: 4,
          strength: 4,
      )),
    

    Поскольку начальное значение силы, очков здоровья/атаки, т.п. всегда равно их полному значению, нет никакого смысла в конфигах указывать и то, и то. Вставил всякой serde магии и функций-оберток, вроде стало почище.

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

(Извиняюсь, многовато получилось текста. Надо почаще писать.)


#24

Выложили отчет о недавнем инди-стендапе в индиспейсе, куда я решлся заглянуть:

UPD: Просили описание проекта родить (не техническое), смог вот такое:

Описание: минималистичная фэнтезийная пошаговая тактика, механика которой вдохновлена Into the Breach, Banner Saga, Hoplite и Auro.

Процесс разработки разделен на два этапа:

В первом этапе будут реализованы: тактическое ядро, дерево улучшений бойцов, уникальные действия классов, короткая кампания на пару часов в виде линейной серии боев с вечной смертью (permadeath) и финальный босс - демон Земерот. Разные виды врагов будут иметь простые, но отличающиеся шаблоны поведения, которые дополняют друг друга и создают сложные тактические задачи.

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


#25

@ozkriff Молодец! :+1:
А может быть нам еще чисто игровые растосходки наладить в Индиспейсе? Раз в месяц или хотя бы раз в два месяца.


#26

Думаешь, имеет смысл? Ржавая игровая экосистема пока в таком состоянии, что только для готовых бороться с неудобствами годится. В целом, практичный игродельческий народ смеется даже когда говоришь на godot’е проект писать, а до такого уровня Аметисту (он же у нас самый зрелый и серьезный из движков все еще?) еще просто куча человеколет предстоит. Да и прям проектов-то игровых в питере на расте не то что бы много. Я боюсь что мы (пока?) из своих субботних сходок не выросли особо, что бы сильно уж народ зазывать.


#27

раз в год самое то)))


#28

Ладно, тогда сначала игрового опыта поднаберемся. Хотя мне бы хотелось как-то отдельно темы про игры обозначать, может быть и в рамках субботних сходок.


#29

Базовый режим кампании

Нашел в себе силы влить PR c базовой кампанией.

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

Файл описания демо-кампании выглядит примерно так:

initial_agents: ["swordsman", "alchemist"],
nodes: [
    (
        scenario: (
            map_radius: (4),
            rocky_tiles_count: 8,
            objects: [
                (owner: Some((1)), typename: "imp", line: Front, count: 3),
                (owner: Some((1)), typename: "imp_bomber", line: Middle, count: 2),
            ],
        ),
        award: (
            recruits: ["hammerman", "alchemist"],
        ),
    ),
    (
        scenario: (
            rocky_tiles_count: 10,
            objects: [
                (owner: None, typename: "boulder", line: Any, count: 3),
                (owner: None, typename: "spike_trap", line: Any, count: 3),
                (owner: Some((1)), typename: "imp", line: Front, count: 4),
                (owner: Some((1)), typename: "imp_toxic", line: Middle, count: 2),
                (owner: Some((1)), typename: "imp_bomber", line: Back, count: 1),
                (owner: Some((1)), typename: "imp_summoner", line: Back, count: 2),
            ],
        ),
        award: (
            recruits: ["swordsman", "spearman", "hammerman"],
        ),
    ),
]

^ Вначале идет список бойцов, с которым мы начинаем самый первый бой, потом перечисляется список сценариев боев, где задаются свойства карты, списки объектов (врагов и просто булыжников всяких) и варианты награды за победу.

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

Тестов в коде игры пока крайне мало, но для разнообразия логическое ядро кампании я немного обмазал тестами в духе:

#[test]
fn short_happy_path() {
    let mut state = State::from_plan(campaign_plan_short());
    assert!(state.aviable_recruits().is_empty());
    assert_eq!(state.mode(), Mode::ReadyForBattle);
    let battle_result = BattleResult {
        winner_id: PlayerId(0),
        survivor_types: initial_agents(),
    };
    state.report_battle_results(&battle_result).unwrap();
    assert_eq!(state.mode(), Mode::Won);
}

Сейчас есть косяк с тем что если бой пошел неудачно, то можно в любой момент выйти из него в меню кампании и начать бой заново. Уже завел задачу на то что бы пресечь это безобразие - “вечная смерть” наше все.

Game Planet

Сходил на этих выходных на выставку Game Planet, никогда на такие штуки не ходил. И не зря, видимо - мероприятие в целом сильно не про меня оказалось. Куча школьников, фортнайта и странных косплееров. Убежал оттуда через пару часов, но хоть позалипал в секции с инди-стендами, посмотрел всякое про настолки и авторские комиксы. Сделал вывод: можно смело идти шоукейсить поделку - ничего дико страшного, в худшем случае просто все проигнорят. :slight_smile:

Твиторы

И да, если кому интересно, я тут психанул и завел две твитерных учетки дополнительных:

  • @ozkriff_ru - для всего, что будет интересно только русскоговорящим;
  • @rust_gamedev - для ретвитов всего подряд про ржавый игрострой.

#30

Пыль

Запилил простенькую пыль при прыжках-бросках всяких:

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

Следующее на очереди: квадратные брызги крови при попаданиях.


UPD: еще внезапно решил заглянуть в /r/gamedev на субботний скриншотник, давно там уже ничего не писал.


#31

Исчезающие брызги крови и улучшенные анимации атак

Продолжаю лениться выкладывать сюда частые обновления.

  • Добавил разлетающихся брызг крови и следов от оружия при атаках для оживления визуального ряда.

    Количество капель крови пропорционально нанесенному урону.

    Добавил бойцам параметр WeaponType. Пока есть четыре вида: smash, slash, pierce и claw и они чисто визуальные - для выбора подходящей текстурки. Некоторые спрайты атаки под углами смотрятся странно (копейщик, я на тебя смотрю), надо будет потом дополнительные варианты добавить и зеркалировать все это хозяйство по ситуации.

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

    Может потом еще для чего-то декоративного этот же механизм использую.

  • Первел все пакеты в репозитории на Rust 2018:

    • везде прописал edition = "2018"
    • обновил все импорты на новые пути (привет, вездесущий crate::);
    • удалил все extern crate;
    • заменил все #[macro_use] на use конкретных макросов;
    • перешел на стабильный rustfmt;
    • подчистил некоторые ставшие лишними явные ВЖ в паре мест;
    • выкинул все mod.rs.

    О переходе не жалею, в целом все неплохо, но некоторые вспомогательные штуки (например, cargo-outdated) пока со скрипом на Rust 2018 смотрят еще.

  • Выкинул прямую зависимость от serde_derive;

  • Отключил кэш тревиса, потому что Levans’ workshop: “Beware the rust cache on Travis”. А то замерил, что кэш после сборки иногда минуты по три собирается, а толку от него и правда не так что бы прям много.

  • Починил вертикальную позицию декоративной травы;

  • Обновил картинки в ридми. Изначально задумка была в том, что в ридми показываются картинки только с последней выпущенной версии, а не из мастера. Но 0.5 я выпущу только после перехода на ggez 0.5, который все маячит на горизонте, но не появляется. А картинки от 0.4 совсем уж сильно устарели, больше полугода уже прошло таки.