Геймдизайн

Почему Фараон остаётся лучшей игрой в серии

26 сентября 202514 мин

В 90-х и 2000-х студия Impressions Games выпускала отличные исторические градостроительные симуляторы. Я играл во все игры этой серии — от незабываемого Caesar 3, который вообще был первой моей компьютерной игрой на отцовском компьютере, до Императора про древний Китай. Но египетский Pharaoh и греческий Zeus запомнились намного четче, а вот почему — сказать не берусь.

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

И всё же эти относительно незначительные различия в механиках в сумме делают Зевса более качественной игрой, но качественной в плане QoL — Quality of Life, т.е. мы вам, дорогие игроки, отсыпем изменений, но большая их часть просто сделает игру проще, не меняя в общем ничего. Это немного печалит, потому что от новой части ждёшь возможности построить другой красивый город, а не тот, что ты уже 50 миссий подряд строил в пустыне.

Но различия есть, и многие игроки, кто начал знакомство с серией с творения учеников гениального Саймона Бредбури (мэтр ушёл из студии после Цезаря делать Stronghold) — с Зевса, — считают её лучшей в серии. А те, кто начинал с Фараона, считают лучшей именно эту часть, как я например; про любителей римлян я уже и не говорю — у них там своя тусовка и свои боги. Ещё придётся немного затронуть ремейк Pharaoh: A New Era (коли уж он вышел и хотел покуситься на лавры предков), и сравнение будет, к сожалению, не в его пользу, так что не обижайтесь.

Первое различие в старом Фараоне — это распределение рабочих, и именно его в переиздании Pharaoh: A New Era изменили так, чтобы оно работало как в Zeus, но это была одна из тех мелких фишечек оригинального Фараона, которая делала развитие города достоверным. Не очень красиво, когда твои рабочие по волшебству добегают до самых уголков карты и работают там. В Pharaoh, когда ты строишь здание, которому нужны рабочие (а это почти любое здание), появляется специальный «рекрутёр», который начинает идти по дороге в поисках ближайших жилых домов, откуда можно забрать работников; причём он идёт не точно к домам, он идёт «просто так», а на перекрёстках поворачивает в «случайную» сторону. Случайность тут неслучайная, а генерится при старте уровня для каждого тайла, потому что компы были тогда не очень мощные и делать настоящую случайность было дорого — вот так её и зашили в каждый тайл карты.

Как это сделано в коде игры (у меня он включается опцией)

void building::common_spawn_labor_seeker(int min_houses) {
    if (g_city.population.current <= 0) {
        return;
    }

    // тут проверка что включен глобальный пул
    if (!!game_features::gameplay_change_global_labour) {
        // и просто проверяется что любые дома присоединены к дороге
        houses_covered = std::min(300, distance_from_entry ? 2 * min_houses : 0);
        return;
    }

    // а иначе используем логику с проверкой наличия домов рядом
    // со зданием
    if (houses_covered > min_houses) {
        return;
    }

    if (has_figure(BUILDING_SLOT_LABOR_SEEKER)) { // no figure slot available!
        return;
    }

    create_roaming_figure(FIGURE_LABOR_SEEKER, FIGURE_ACTION_125_ROAMING, BUILDING_SLOT_LABOR_SEEKER);
}

А в Зевсе жилые дома просто добавляют рабочих в глобальный пул, из которого остальные здания автоматически их черпают — никаких рекрутёров, само собой, нет, как отпадает и необходимость строить жильё рядом с рабочими зонами. Это значительно упрощает создание отдалённых производственных «форпостов» — например, можно построить серебряные шахты прямо у месторождения или торговые доки где-нибудь у подходящего изгиба реки.

Всё, что было нужно для этого в Pharaoh — это пара хижин первого уровня. Можно снабдить их водой и едой, если удобно, тогда туда заселится больше людей, что даст прирост населения (а это почти всегда было плюсом), но делать это не обязательно. Главное — просто обеспечить наличие жилья в пределах досягаемости от рабочего места.

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

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

Кстати, в Фараоне «бедные» дома не платят налоги, а в Зевсе платят все

int figure_tax_collector::provide_service() {
    int max_tax_rate = 0;
    int houses_serviced = figure_provide_service(tile(), &base, [&] (building *b, figure*) {
        // rtti без dynamic_cast, проверяем что здание это дом, а не
        // например библиотека
        auto house = b->dcast_house();
        if (!house) {
            return;
        }

        if (house->house_population() > 0) {
            // настройки сколько платит дом задаются через конфиг
            // в Зевсе дома платят просто x * уровень дома
            // упрощение не пошло игре на пользу, ибо очень снизило сложность
            int tax_multiplier = model_get_house(house->house_level()).tax_multiplier;
            if (tax_multiplier > max_tax_rate) {
                max_tax_rate = tax_multiplier;
            }

            if (house->house_level() < HOUSE_ORDINARY_COTTAGE) {
                runtime_data().poor_taxed++;
            } else if (house->house_level() < HOUSE_COMMON_MANOR) {
                runtime_data().middle_taxed++;
            } else {
                runtime_data().reach_taxed++;
            }

            // еще один игродевовский паттерн для
            // для хранения разных данных без аллокаций
            // в одном блоке памяти
            auto &housed = house->runtime_data();
            housed.tax_collector_id = home()->id;
            housed.tax_coverage = 50;
        }
    });
    base.min_max_seen = max_tax_rate;
    return houses_serviced;
}

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

Zeus определённо делает игру более приятной и лёгкой в управлении, но подход Pharaoh придаёт симуляции реалистичность, несовершенность, а попытки сделать всем хорошо ломают баланс и добавляют вызовов. Не скажу, что мне не нравится глобальный пул рабочих, но если игра отбирает у игрока такие мелкие радости, которые можно обнаружить посреди прохождения кампании, то дизайнер где-то профилонил свои прямые обязанности. Тем не менее, подход Zeus явно выигрывает, если мы играем кампанию именно как сюжет, а не как песочницу, как это сделано в Фараоне. Кстати, Pharaoh: ANA взял понемногу от обоих подходов, не поняв, однако, нюансов, докинул сверху миниквестов из мира мобильных игр, и в итоге получилась вообще каша.

Второе важное различие между играми — миссии, а точнее стиль подачи и ожидания от игрока. В Zeus кампания разбита на серии из нескольких «приключений», и в каждом игрок строит основной город и основывает несколько колоний. Каждая колония получает по отдельной миссии и развивается где-то до 1000–2000 жителей — это менее половины от размера главного города, но строится с определённой целью, т.е. это действительно миссия, а не отдельный город.

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

В Pharaoh же игра поделена на четыре периода: учебный период, а затем Древнее, Среднее и Новое царства. В каждом периоде несколько эпизодов, и каждый требует строить новый город с нуля; часть миссий похожи на колонии из Zeus — с простыми задачами и ограниченными картами, другие напоминают «основной» город — с высокими требованиями, но и с богатой картой; тут не очень понятно, это в Зевсе развили идею до колоний или просто стилистика миссий сама вела к такому ходу событий. Главное и, пожалуй, самое печальное отличие игрового дизайна миссий в том, что игра не сохраняет основной город между эпизодами, и каждый уровень — это полный рестарт, независимо от успехов на предыдущем. Для игр начала нулевых это было священной коровой, и даже ребята под руководством Саймона не решились это ломать, а вот ребята из ремейка сломать не побоялись и таки сломали — только это не комплимент: теперь у нас просто набор миссий, не связанных ни сюжетом, ни историей.

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

В Pharaoh аналогичная по сложности миссия начнётся на новой карте с пустыми берегами Нила, и снова придётся раскладывать блоки базового жилья, строить фермы, водопроводы и склады, но, на мой взгляд, это не является каким-то минусом, потому что игра за предыдущие миссии даёт перепробовать кучу вариантов, чтобы подобрать удобный лично тебе. Этот первый час каждого уровня проходит быстро, прежде чем можно будет перейти к более интересным и целенаправленным задачам, а начинает он надоедать аккурат к 40-й миссии, т.е. самому концу игры.

Что было серьёзно переработано — хотя «переработано» тут не очень уместное слово, скорее капитально сломано, — так это система жилых домов. В Зевсе жильё делится на два типа: обычное и элитное. Обычные дома всегда занимают площадь 2×2 клетки, а элитные — 4×4, и в принципе это всё: знай себе добавляй рядом нужные постройки для уровня, а иногда игра багует и просто поднимает или снижает уровень дома без видимых причин, и это не исправили даже в патчах, т.е. вы вообще могли залить всю карту домами первого уровня и пройти миссию, не испытывая нехватки рабочих.

В Pharaoh же жильё начинается с домика 1×1 и увеличивается в размерах по мере предоставления новых услуг. Переход от обычного жилья к элитному происходит при апгрейде с 2×2 до 3×3. Всего в Pharaoh 20 уровней развития жилья, в то время как в Zeus — 11 (или 12, если учитывать опустевшие элитные дома, потерявшие необходимый уровень обслуживания). И заливка карты домиками тут не только не помогает, но и начинает вредить: в какой-то момент срабатывает отрицательный коэффициент миграции, и люди начинают покидать город. Вот эту логику фактически убрали из Зевса, оставив только периодический приход новых поселенцев, и новый Фараон взял это за основу.

Вот тут немного про апдейт миграции

void city_migration_t::update_status() {
    auto& params = g_migration_params;

    const auto &sentiment = g_city.sentiment;
    auto it = std::find_if(g_migration_sentiment_influence.begin(), g_migration_sentiment_influence.end(), [&] (const auto& t) {
        return sentiment.value > t.s; 
    });

    // Определяем влияние настроения населения на миграцию
    // Устанавливаем процент миграции на основе настроения населения
    // Если найден подходящий порог, используем соответствующий процент влияния, иначе 0
    percentage_by_sentiment = (it != g_migration_sentiment_influence.end()) ? it->i : 0;
    percentage = percentage_by_sentiment;

    immigration_amount_per_batch = 0;
    emigration_amount_per_batch = 0;

    // Проверка лимита населения
    // Если достигнут лимит населения, останавливаем иммиграцию
    // иногда миссия ставит свой лимит
    if (population_cap > 0 && g_city.population.current >= population_cap) {
        percentage = 0;
        migration_cap = true;
        return;
    }

    // war scares immigrants away
    if (g_city.figures_total_invading_enemies() > 3 && percentage > 0) {
        percentage = 0;
        invading_cap = true;
        return;
    }

    // Обработка иммиграции (положительный процент)
    if (percentage > 0) {
        // immigration
        if (emigration_duration) {
            emigration_duration--;
        } else {
            // Рассчитываем количество иммигрантов за цикл на основе процента
            immigration_amount_per_batch = calc_adjust_with_percentage<int>(params.max_newcomers_per_update, percentage);
            immigration_duration = 2;
        }
    } 
    // Обработка эмиграции (отрицательный процент) 
    else if (percentage < 0) {
        // emigration
        if (immigration_duration) {
            immigration_duration--;
        } else if (g_city.population.current > 100) {
            emigration_amount_per_batch = calc_adjust_with_percentage<int>(params.max_leftovers_per_update, -percentage);
            emigration_duration = 2;
        }
    }
}

Требования к улучшению жилья в Pharaoh растут схожим образом с Zeus: требуется всё больше товаров и развлечений, но Pharaoh добавляет ещё больше уровней за счёт религиозных и бытовых служб. Чтобы добиться максимального уровня жилья в Pharaoh, игроку нужно обеспечить доступ к местам поклонения разным богам, а также множеству специфических служб — таких как стоматолог, школа писцов, морг, врач, суд и библиотека. А уместить их все рядом — задача из разряда «построить все пути в голове и поставить домики там, где они пересекаются». Кроме того, на высоких уровнях требуется наличие нескольких видов пищи одновременно.

Как в Фараоне дома потребляют еду

resource_list building_house::consume_food() {
    if (!hsize()) {
        return {};
    }

    // разделяемые данные, которые для любых зданий лежат рядом
    // с общими данными, но уникальна для каждого типа здания
    auto &d = runtime_data();

    int num_types = model().food_types;
    uint16_t amount_per_type = calc_adjust_with_percentage<short>(d.population, 35);
    if (num_types > 1) {
        amount_per_type /= num_types;
    }

    d.num_foods = 0;
    resource_list food_types_eaten;
    if (scenario_property_kingdom_supplies_grain()) {
        d.foods[0] = amount_per_type;
        food_types_eaten[RESOURCE_GRAIN] += amount_per_type;
        d.num_foods = 1;
        return food_types_eaten;
    }

    if (num_types <= 0) {
        return {};
    }

    // дом сьест сначала дешевую еду, а потом дорогую
    // В Зевсе дом ест по частям всю доступную еду, упрощение
    // но частично сломало механику роста домов
    for (int t = INVENTORY_MIN_FOOD; t < INVENTORY_MAX_FOOD && d.num_foods < num_types; t++) {
        const uint16_t exist_amount = std::min(d.foods[t], amount_per_type);
        d.foods[t] -= exist_amount;
        d.num_foods += (exist_amount > 0) ? 1 : 0;
        e_resource food_res = g_city.allowed_foods(t);
        food_types_eaten.push_back({ food_res, amount_per_type });
    }

    return food_types_eaten;
}

И как потребляются ресурсы

resource_list building_house::consume_resources() {
    if (!hsize()) {
        return {};
    }
    
    resource_list good_types_consumed;
    auto &d = runtime_data();
    const model_house& model = model_get_house(house_level());

    auto consume_resource = [&] (int inventory, uint16_t amount) {
        if (amount <= 0) {
            return;
        }

        amount = std::min(amount, d.inventory[inventory]);
        d.inventory[inventory] -= amount;
        good_types_consumed.push_back({ (e_resource)inventory, amount });
    };

    // И вот такое потребление всех типов ресурсов сделано в Зевсе
    // есть ресурс - он используется, опять же упрощение
    // но сильно меняет баланс товаров в городе
    consume_resource(INVENTORY_GOOD1, model.pottery);
    consume_resource(INVENTORY_GOOD2, model.jewelry);
    consume_resource(INVENTORY_GOOD3, model.linen);
    consume_resource(INVENTORY_GOOD4, model.beer);

    return good_types_consumed;
}

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

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

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

Как это было сделано в коде игры

// большая часть данных в игре статическая
// динамических аллокаций практически нет, это нормально для игр тех лет
static grid_xx random_xx = {0, FS_UINT8};

void map_random_clear() {
    map_grid_clear(random_xx);
}

void map_random_init() {
    int grid_offset = 0;
    // в этом цикле раздаем случайные повороты на тайлах карты
    // при старте уровня, "египетская тупость"
    for (int y = 0; y < GRID_LENGTH; y++) {
        for (int x = 0; x < GRID_LENGTH; x++, grid_offset++) {
            random_generate_next();
            map_grid_set(random_xx, grid_offset, (uint8_t)random_short());
        }
    }
}

int map_random_get(int grid_offset) {
    return map_grid_get(random_xx, grid_offset);
}

io_buffer* iob_random_grid = new io_buffer([](io_buffer* iob, size_t version) {
    iob->bind(BIND_SIGNATURE_GRID, &random_xx);
});

На скриншоте показано, куда с большой вероятностью будут «случайно» поворачивать жители, но этого инструмента визуализации у игроков не было. Эта «случайность» где-то к середине игры начинает реально мешать. Pharaoh следует «условной» исторической прогрессии, и новые здания открываются постепенно — архаика, Старое, Среднее и Новое царства, — и по мере развития технологий в каждом сценарии получаешь доступ к чему-то новому. И каждый новый уровень приносит ещё одно здание, которое нужно как-то впихнуть в твой блок, что иногда приводит к полной пересборке устоявшейся схемы района.

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

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

Я помню миссию на строительство пирамид Гизы — тогда, в 2001-м, она заняла что-то около 100 игровых лет, или неделю вечеров по несколько часов. У меня была на тот момент очень несбалансированная экономика в городе (правда, своим подростковым умом я этого не видел), зато вижу сейчас, когда есть на руках восстановленные исходники и можно посмотреть и подкрутить разные ручки, но тогда это казалось супертрудной миссией. Это была миссия времён Старого царства, и она не позиционировалась как финальное испытание на мастерство в логистике, хотя по факту ею являлась, и по отзывам на форумах так и остаётся одной из самых трудных мирных миссий в игре.

Лучше ли Zeus, чем Pharaoh?

Это разные игры, сделаны разными командами и разными дизайнерами, пусть и в похожей упаковке, и играть в них надо по-разному. В Zeus есть цепочка из 5–8 эпизодов, связанных одним городом, который развивается постепенно. В Pharaoh же каждая из 50 миссий — это отдельная головоломка, которая начинается с чистого листа. Упрощённые жилые блоки Зевса помогали справляться с «египетской тупостью», а вот в Фараоне волей-неволей приходилось становиться мастером планировки.

P.S. Что мне действительно нравится, так это то, что получилось, пусть даже в урезанном виде, восстановить функционал этой легендарной — без преувеличения — игры. Если вам нравится эстетика Древнего Египта и очень старый код, приходите в Akhenaten (можно поиграть теперь и в браузере, всё ещё нужны ресурсы OG, за что отдельное спасибо Roman Turchin). А я продолжаю восстанавливать систему врагов и боёвку, и поверьте, там есть чему поучиться у старых игр.

P.P.S. Немного рекламы моего курса по программированию на Stepik.

Примерно полгода назад я опубликовал на Хабре цикл статей про игровую разработку (начинать можно отсюда), который был хорошо принят сообществом. Мои знакомые и некоторые Хабровчане просили выложить эту информацию в более удобном и концентрированном виде — в виде курса по C++ или одной большой статьи. Решил сделать пробный шар в виде небольшого курса по программированию на C++ без аллокаций; он действительно небольшой — всего 45 уроков — и захватил пару статей из цикла, но если вам понравится, можно попробовать сделать ещё один по интересным темам (Нескучное программирование. С++ без аллокаций памяти). Курс платный, дабы отсеять охотников за халявными сертификатами и любителей пошуметь в комментах. Тем, кто меня читает, — промокод (HABR50); если нужна скидка больше или бесплатный инвайт, напишите в личку.

← Все статьи