Цезарь Iii: Игровой Цикл

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

Отдельные компоненты математической модели города тоже интересно реализовать, но вращаться эти «шестеренки» будут только в собранном виде.

Большая часть игры происходит в «игровом цикле», в котором вычисляются параметры компонентов, перемещаются игровые объекты и создаются новые события и объекты.

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



Цезарь III: игровой цикл

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

Цезарь III: игровой цикл

Через каждые 50 тиков начинается новый день (16 дней в игровом месяце), для которого рассчитываются еще несколько функций, не требующих столь частой обработки, а именно:

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

ЭШаги расчета параметров города 1. Расчет настроения: - боги - Аборигены 2. Обновление параметров вторжения войск Цезаря 3. Расчет движения групп объектов 4. Сбор информации по сараям 5. Обновление данных о доступных услугах на дому 6. Обновление параметров склада 7. Обновление данных советника по народонаселению и поставкам пшеницы из Рима.

8. Обновление расхода товаров в цехах и материалов на горнодобывающих предприятиях.

9. Обновление путей к докам 10. Расчет производства товаров в цехах 11. Расчет доступности дороги до Рима.

12. Обновление населения домов 13. Подсчет появления бездомных из переполненных домов 14. Расчет распределения работников по предприятиям, расчет безработных, работающих предприятий.

15. Обновление зоны покрытия фонтанов и водоемов 16. Улучшение доступа домов к воде 17. Обновление состояния групп объектов 18. Расчет явки граждан из служебных зданий 19. Расчет внешнего вида торговцев 20. Подсчет типов и количества зданий в городе, расчет охвата объектами культуры.

21. Расчет распределения городской казны между Сенатом и форумами 22. Расчет снижения культурных показателей в домах 23. Расчет снижения услуг в домах 24. Расчет влияния построек на желательность земельного участка 25. Обновление уровня домов 26. Удаление с карты зданий, отмеченных к сносу.

27. Обновление параметров горящих руин.

28. Обновление статуса зданий вокруг пожаров.

29. Создание протестующих жителей 30. Расчет параметров собираемости налогов 31. Повышение уровня развлечений в домах.

Эшаги расчета параметров города (код)

  
  
  
  
   

void gameLoop() { while( game.run ) { gametime.ticks++; switch ( gametime.ticks ) { case 1: calculateGodHappiness(1); case 2: changeBackgroundMusic(); case 3: minimap_redraw = 1; case 4: tick_updateCaesarInvasion(); case 5: tick_updateFormations(0); case 6: tick_checkNativeLand(); case 7: determineRoadNetworkIds(); case 8: gatherGranaryStorageInfo(); case 9: ??? case 10: updateHighestInUseBuildingId(); case 11: ??? case 12: buildingDecayHousesCovered(); case 16: tick_resource_recalculateStock(); case 17: updateAdvisorFoodAndSupplyRomeWheat(); case 18: tick_updateCityInfoWorkshopRawMaterialsStored(); case 19: docksDetermineWaterAccess(); case 20: tick_updateIndustryProduction(); case 21: tick_checkPathingAccessToRome(); case 22: updatePopulationInHouses(); case 23: population(); case 24: evictPeopleFromOvercrowdedHouses(); case 25: calculateWorkersNeededPerCategory(); calculateUnemployment(); setBuildingWorkerPercentage(); setBuildingNumWorkersWater(); setBuildingNumWorkers(); case 27: recalculateReservoirAndFountainAccess(); case 28: gametick_updateHouseWaterAccess(); case 29: updateFormations(1); case 30: minimap_redraw = 1; case 31: generateWalkersForBuildings(); case 32: generateTraders(); case 33: countBuildingTypes(); calculateCultureCoverage(); case 34: distributeTreasuryOverForumsAndSenates(); case 35: decayService_culture(); case 36: determineHousingServicesForEvolve(); case 37: calculateDesirabilityOfBuildings(); calculateDesirabilityOfTerrain(); case 38: calculateBuildingDesirability(); case 39: evolveDevolveHouses(); case 40: clearDeletedBuildings(); case 43: updateBurningRuin(); case 44: updateCrimeFireDamage(); case 45: generateCriminal(); case 46: updateDoubleWheatProduction(); case 47: case 48: decayService_taxCollector(); case 49: gatherEntertainmentInfo(); } if( gametime.ticks >= 50 ) { gametime.ticks = 0; doGameDayTick(); } renderCity(); } }

Приход нового дня

void doGameDayTick() { ++gametime.totalDays; ++gametime.day; if ( gametime_day > 15 ) { gametime.day = 0; cityinfo.newcomersThisMonth = 0; ++cityinfo.monthsSinceFestival; monthHandle(); ++gametime.month; if ( gametime_month <= 11 ) { updateRatings(0); } else { startNewYear(); } recordMonthlyPopulation(); holdFestival(); } if ( !gametime.day || gametime.day == 8 ) calculateCityHappinessAndCrime(); }

Приход нового месяца

void monthHandle() { calculateHealthRate(); handleRandomEvents(); collectMonthlyTaxes(); payMonthlyWages(); payMonthlyInterest(); payMonthlySalary(); housesConsumeMonthlyFood(); handleDistantBattleEvent(); handleInvasionEvent(); checkRequestsEvent(); checkDemandChangesEvent(); checkPriceChangesEvent(); decreaseMonthsLeftToGovernAfterWin(); tickMonth_updateLegionMorale(); playerMessages_updateMessageDelay(); determineGraphicIdsForRoads(); determineGraphicIdsForWater(0, 0, setting_map_width - 1, setting_map_height - 1); calculateOpenGroundCitizen(); sortAndCompactPlayerMessages(); }

Канун Нового года

void startNewYear() { gametime.month = 0; handleExpandEmpireEvent(); ++gametime.year; gametick_requestBirthsDeaths_calculateHousingTypes(); copyFinanceTaxesToLastYear(); copyFinanceWagesToLastYear(); copyFinanceImportExportToLastYear(); copyFinanceConstructionToLastYear(); copyFinanceInterestToLastYear(); copyFinanceSalaryToLastYear(); copyFinanceSundriesToLastYear(); calculateAndPayTribute(); resetTradeAmounts(); tick_updateFireSpreadDirection(); updateRatings(1); cityinfo.blessingNeptuneDoubleTradeActive = 0; }



Религия



Цезарь III: игровой цикл

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

Позже мифологический период сменился увлечением языческими культами.

Государство, взяв на себя организацию и проведение ритуалов, создало официальную религию, изменившую прежние представления о богах.

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

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

божество.



Цезарь III: игровой цикл

Расчет настроения богов и условий гнева/благословения

void calculateGodHappiness(int includeBlessingsAndCurses) { maxTemples = 0; maxGod = 10; minTemples = 100000; minGod = 10; cityinfo.maxHappinessCeres = pctReligionCoverageCeres; cityinfo.maxHappinessNeptune = pctReligionCoverageNeptune; cityinfo.maxHappinessMercury = pctReligionCoverageMercury; cityinfo.maxHappinessMars = pctReligionCoverageMars; cityinfo.maxHappinessVenus = pctReligionCoverageVenus; for ( i = 0; i < 5; ++i ) { if ( i ) { switch ( i ) { case 1: numTemples = numLargeTemplesNeptune + numSmallTemplesNeptune; break; case 2: numTemples = numLargeTemplesMercury + numSmallTemplesMercury; break; case 3: numTemples = numLargeTemplesMars + numSmallTemplesMars; break; case 4: numTemples = numLargeTemplesVenus + numSmallTemplesVenus; break; } } else { numTemples = numLargeTemplesCeres + numSmallTemplesCeres; } if ( numTemples >= maxTemples ) { if ( numTemples == maxTemples ) maxGod = 10; else maxGod = i + 1; maxTemples = numTemples; } if ( numTemples <= minTemples ) { if ( numTemples == minTemples ) minGod = 10; else minGod = i + 1; minTemples = numTemples; } } for ( j = 0; j < 5; ++j ) { monthsGodSinceFestival = cityinfo.monthsGodSinceFestival[j]; if ( monthsGodSinceFestival > 40 ) monthsGodSinceFestival = 40; cityinfo.maxGodHappiness[j] += 12; cityinfo.maxGodHappiness[j] -= monthsGodSinceFestival; } if( maxGod ) { if( maxGod < 5 ) { if ( cityinfo.monthsGodSinceFestival[maxGod + 3] >= 50 ) cityinfo.monthsGodSinceFestival[maxGod + 3] = 100; else cityinfo.monthsGodSinceFestival[maxGod + 3] += 50; } } if ( minGod ) { if ( minGod < 5 ) cityinfo.monthsGodSinceFestival[minGod + 3] -= 25; } if ( cityinfo.population >= 100 ) { if ( cityinfo.population >= 200 ) { if ( cityinfo.population >= 300 ) { if ( cityinfo.population >= 400 ) { if ( cityinfo.population >= 500 ) min = 0; else min = 10; } else { min = 20; } } else { min = 30; } } else { min = 40; } } else { min = 50; } for ( k = 0; k < 5; ++k ) { if( cityinfo.maxGodHappiness[k] > 100 ) cityinfo.maxGodHappiness[k] = 100; if( cityinfo.maxHappinessCeres[k] < min ) cityinfo.maxGodHappiness[k] = min; } if ( includeBlessingsAndCurses ) { for ( l = 0; l < 5; ++l ) { if ( cityinfo.godHappiness[l] <= cityinfo.maxGodHappiness[l] ) { if ( cityinfo.godHappiness[l] < cityinfo.maxGodHappiness[l] ) ++cityinfo.godHappiness[l]; } else { --cityinfo.godHappiness[l]; } } for ( m = 0; m < 5; ++m ) { if( cityinfo.godHappiness[m] > 50 ) cityinfo.godSmallCurseDone[m] = 0; if ( cityinfo.godHappiness[m] < 50 ) cityinfo.godBlessingDone[m] = 0; } god = random_7f_1 & 7; if ( god <= 4 ) { if ( cityinfo.godHappiness[god] < 50 ) { if ( cityinfo.godHappiness[god] < 40 ) { if ( cityinfo.godHappiness[god] < 20 ) { if ( cityinfo.godHappiness[god] < 10 ) cityinfo.numBoltsGod[god] += 5; else cityinfo.numBoltsGod[god] += 2; } else { ++cityinfo.numBoltsGod[god]; } } } else { cityinfo.numBoltsGod[god] = 0; } if ( cityinfo.numBoltsGod[god] >= 50 ) cityinfo.numBoltsGod[god] = 50; } if ( !gametime.day ) { for ( n = 0; n < 5; ++n ) ++cityinfo.monthsGodSinceFestival[n]; if ( god > 4 ) { if( determineAngriestGod() ) god = cityinfo.religionAngryGod - 1; } if ( setting.godsOn ) { if ( god <= 4 ) { if( cityinfo.godHappiness[god] < 100 || cityinfo.godBlessingDone[god] ) { if ( cityinfo.numBoltsGod[god] < 20 || cityinfo.godSmallCurseDone[god] || cityinfo.monthsGodSinceFestival[god] <= 3 ) { if ( cityinfo.numBoltsGod[god] >= 50 && cityinfo.monthsGodsSinceFestival[ god ] > 3 ) { cityinfo.numBoltsGod[god] = 0; cityinfo.godHappiness[god] += 30; message.usePopup = 1; if ( god ) // large curse { switch ( god ) { case God_Neptune: if ( cityinfo.numOpenSeaTradeRoutes <= 0 ) { postMessageToPlayer(42, 0, 0); return; } postMessageToPlayer(81, 0, 0); neptuneSinkAllShips(); cityinfo.seaTradeProblemDuration = 80; cityinfo.godCurseNeptuneSankShips= 1; break; case God_Mercury: postMessageToPlayer(43, 0, 0); removeGoodsFromStorageForMercury(1); break;

Теги: #caesar iii remake #разработка игр #oldschool #старые добрые игры #caesaria #с открытым исходным кодом #C++ #разработка игр

Вместе с данным постом часто просматривают: