Сохранение в игре.

Темы, связанные с проектированием и программированием roguelike-игр

Модераторы: Sanja, Максим Кич

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Сохранение в игре.

Сообщение altmax » 07 фев 2017, 07:01

Возник вопрос - как сделать сохранение в игре? Да и просто при генерации следующего уровня как предыдущий сохранить в файл? Ведь наверняка уже есть наработанные решения поданному вопросу, не хотелось бы запросто изобретать велосипед. Вопрос относится к языку С++.
Сложность в том, что надо несколько массивов различных данных загнать в один файл, причем некоторые массивы - динамические,а кроме того еще есть и списки <list>. Пока с трудом представляю, как это сделать.
И еще вопрос - если каждый тайл карты будет содержать несколько полей плюс список вещей лежащих на клетке карты. Этот список может быть пустой, а может и содержать ID вещей. Т.е. получается что каждая клетка карты занимает разное место в памяти. Тогда выходит, что записать в файл то в принципе получится, а вот корректно всё это дело считать - уже будет сложно. Т.е. надо писать метод, который будет всю нужную информацию загонять в отдельный единый массив тех же char, а его уже писать в файл, ну и в начале файла добавить всю служебную информацию, списки вещей на клетках писать отдельно единым куском данных. Ну а при считывании проводить обратные преобразования.

Аватара пользователя
Jesus05
Сообщения: 1840
Зарегистрирован: 02 дек 2009, 07:50
Откуда: Норильск, сейчас Санкт-петербург.
Контактная информация:

Re: Сохранение в игре.

Сообщение Jesus05 » 07 фев 2017, 07:24

Я использовал JSON когда реализовывал сохранение, но мой JSON-велосипед получился медленным :)
А вообще читай про рефлексию в сях https://habrahabr.ru/post/277329/
Насколько я понимаю без кодогенерации тут не обойтись.
Пробовал когда-то разобраться с https://en.wikipedia.org/wiki/ODB_(C%2B%2B) но там тоже рефлексия, и свой дополнительный прекомпилятор который занимается кодогенерацией. (по идеи ODB + SQLite и бд в файле будет достаточно для сохранений)
Самый быстрый но самый неправильный способ это тупо сваливать данные класса в файл как есть.
write(file, &someCLassInstance, sizeof(someCLassInstance));
но надо помнить во время загрузки про таблицы виртуальных методов, про указатели\ссылки. в общем так можно хранить только простые данные.
как вариант можно отделить данные от поведения... делать классы:
someclassdata
{
int ...;
char ...;
someotherclassdata ...;
}

someclass
{
someclassdata ...;
поведение
}
и сохранять соответсвенно только классы данных и не трогать классы поведения.

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

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Сохранение в игре.

Сообщение altmax » 07 фев 2017, 07:39

JSON-велосипед писал построчно в файл?
Т.е. для записи состояния игры нужны были десятки тысяч обращений к жесткому диску? Ежели да, то это выходит весьма медленно.
Большие объемы данных лучше писать одним куском в бинарном режиме.

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Сохранение в игре.

Сообщение altmax » 07 фев 2017, 07:42

О, нашел вроде хорошую статью про сериализацию данных, изучаю.
http://procplusplus.blogspot.ru/2012/12/c.html

Аватара пользователя
Jesus05
Сообщения: 1840
Зарегистрирован: 02 дек 2009, 07:50
Откуда: Норильск, сейчас Санкт-петербург.
Контактная информация:

Re: Сохранение в игре.

Сообщение Jesus05 » 07 фев 2017, 09:00

altmax писал(а):
07 фев 2017, 07:39
JSON-велосипед писал построчно в файл?
Т.е. для записи состояния игры нужны были десятки тысяч обращений к жесткому диску? Ежели да, то это выходит весьма медленно.
Большие объемы данных лучше писать одним куском в бинарном режиме.
Все не настолько плохо :) я собирал данные в памяти, а потом уже ложил их на диск.
вот код... много кода :) последняя правка была в 15 году.
Скрытый текст: ПОКАЗАТЬ

Код: Выделить всё

class CSaver
{
  private:
    explicit CSaver();
  public:
    static inline class CSerializer Save(const class CMap &map, const CVariableNames &varName);
    static inline class CSerializer Save(const CMapBlockArray &mapBlock, const CVariableNames &varName);
    static inline class CSerializer Save(const class CCoordinate &place, const CVariableNames &varName);
    static inline class CSerializer Save(const class CSize &size, const CVariableNames &varName);
    static inline class CSerializer Save(const class CRect &rect, const CVariableNames &varName);
    static inline class CSerializer Save(const signed int &value, const CVariableNames &varName);
    static inline class CSerializer Save(const NSMapBlock::CMapBlockType &value, const CVariableNames &varName);
    static inline class CSerializer Save(const unsigned int &value, const CVariableNames &varName);
    static inline class CSerializer Save(const double &value, const CVariableNames &varName);
    static inline class CSerializer Save(const long double &value, const CVariableNames &varName);
    static inline class CSerializer Save(const bool &value, const CVariableNames &varName);
    static inline class CSerializer Save(const class CHeroParamValue &value, const CVariableNames &varName);
    static inline class CSerializer Save(const class CGoldBag &goldBag, const CVariableNames &varName);
    static inline class CSerializer Save(const class CInventory &inventory, const CVariableNames &varName);
    static inline class CSerializer Save(const CItems &items, const CVariableNames &varName);
    static inline class CSerializer Save(const class CItem &item, const CVariableNames &varName);
    static inline class CSerializer Save(const class CMeasurement &measurement, const CVariableNames &varName);
    static inline class CSerializer Save(const CMeasures &measures, const CVariableNames &varName);
    static inline class CSerializer Save(const class CHero &hero, const CVariableNames &varName);
    static inline class CSerializer Save(const class CGame &game, const char *varName);
    static inline class CSerializer Save(const class CMapBlock &mapBlock, const CVariableNames &varName);
    static inline class CSerializer Save(const CBlockDatas &blockDatas, const CVariableNames &varName);
    static inline class CSerializer Save(const class CBlockData &blockData, const CVariableNames &varName);
    static inline class CSerializer Save(const CBuildings &buildings, const CVariableNames &varName);
    static inline class CSerializer Save(const class CBuilding &building, const CVariableNames &varName);
    static inline class CSerializer Save(const class CHouse &house, const CVariableNames &varName);
    static inline class CSerializer Save(const class CTown &town, const CVariableNames &varName);
    static inline class CSerializer Save(const class CPriceList &price, const CVariableNames &varName);
    static inline class CSerializer Save(const CPriceMap &priceMap, const CVariableNames &varName);
    static inline class CSerializer Save(const ::CPricePair &pricePair, const CVariableNames &varName);
    static inline class CSerializer Save(const class CPriceItem &priceItem, const CVariableNames &varName);
    static inline class CSerializer Save(const class CBrewer &brewer, const CVariableNames &varName);
    static inline class CSerializer Save(const CReceiptMap &receiptMap, const CVariableNames &varName);
    static inline class CSerializer Save(const ::CReceiptPair &receiptPair, const CVariableNames &varName);
    static inline class CSerializer Save(const class CReceipt &receipt, const CVariableNames &varName);
    static inline class CSerializer Save(const class CItemQuality &quality, const CVariableNames &varName);
    static inline class CSerializer Save(const class CValue &value, const CVariableNames &varName);
};

CSaver::CSaver()
{
}

CSerializer CSaver::Save(const CMap &map, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CMap, varName)
    << Save(map.m_rect, m_rect)
    << Save(map.m_blocks, m_blocks)
    << CHasher::Hash(map)
    << "}";

  return t;
}

CSerializer CSaver::Save(const CMapBlockArray &mapBlock, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  CMapBlocksList blocks = CMap::GetUniqueBlocks(mapBlock);

  t << t.StartClassVariable(NSClassName::CMapBlockArray, varName);

  for (CMapBlocksList::const_iterator it = blocks.begin(); it != blocks.end(); it++)
  {
    FUNC_MEASURE;
    const ::CMapBlock* item = *it;
    t << Save(*item, NSVariableName::CMapBlock);
  }
  t << CHasher::Hash(mapBlock)
    << "}";

  return t;
}

CSerializer CSaver::Save(const ::CCoordinate &place, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CCoordinate, varName)
    << Save(place.x, x)
    << Save(place.y, y)
    << CHasher::Hash(place)
    << "}";
  return t;
}

CSerializer CSaver::Save(const ::CSize &size, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CSize, varName)
    << Save(size.width, width)
    << Save(size.height, height)
    << CHasher::Hash(size)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CRect &rect, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CRect, varName)
    << Save((::CCoordinate)rect, NSVariableName::CCoordinate)
    << Save((::CSize)rect, NSVariableName::CSize)
    << CHasher::Hash(rect)
    << "}";
  return t;
}

CSerializer CSaver::Save(const signed int &value, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::signed_int, varName)
    << value
    << CHasher::Hash(value)
    << "}";
  return t;
}

CSerializer CSaver::Save(const NSMapBlock::CMapBlockType &value, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CMapBlockType, varName)
    << value
    << CHasher::Hash(value)
    << "}";
  return t;
}

CSerializer CSaver::Save(const unsigned int &value, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::unsigned_int, varName)
    << value
    << CHasher::Hash(value)
    << "}";
  return t;
}

CSerializer CSaver::Save(const double &value, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;

  //  D("dbl saver value", value);

  signed long long temp = *(signed long long*)&value;

  t << t.StartClassVariable(NSClassName::_double, varName)
    << temp
    << CHasher::Hash(value)
    << "}";
  return t;
}

CSerializer CSaver::Save(const long double &value, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;

  //  D("dbl saver value", value);

  const void *pvalue = &value;

  const signed long long *ptemp1 = (signed long long*)pvalue;
  const signed long *ptemp2 = (signed long *)(ptemp1 + 1);

  signed long long temp1 = *ptemp1;
  signed long temp2 = *ptemp2;

  t << t.StartClassVariable(NSClassName::_long_double, varName)
    << temp1
    << temp2
    << CHasher::Hash(value)
    << "}";
  return t;
}

CSerializer CSaver::Save(const bool &value, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::_bool, varName)
    << value
    << CHasher::Hash(value)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CHeroParamValue &value, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CHeroParamValue, varName)
    << Save(value.value, NSVariableName::value)
    << Save(value.force, force)
    << CHasher::Hash(value)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CGoldBag &goldBag, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CGoldBag, varName)
    << Save(goldBag.m_amount, m_amount)
    << CHasher::Hash(goldBag)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CInventory &inventory, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CInventory, varName)
    << Save(inventory.m_items, m_items)
    << CHasher::Hash(inventory)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CItems &items, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CItems, varName);

  for (CItems::const_iterator it = items.begin(); it != items.end(); it++)
  {
    const std::shared_ptr<::CItem> item = *it;
    t << Save(*item, NSVariableName::CItem);
  }

  t << CHasher::Hash(items)
    << "}";
  return t;
}

CSerializer CSaver::Save(const ::CItem &item, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CItem, varName)
    << Save(item.m_itemID, m_itemID)
    << Save(item.m_quality, m_quality)
    << CHasher::Hash(item)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CMeasurement &measurement, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CMeasurement, varName)
    << Save(measurement.m_seenTiles, m_seenTiles)
    << Save(measurement.m_seenTowns, m_seenTowns)
    << Save(measurement.m_seenIngredients, m_seenIngredients)
    << Save(measurement.m_seenIngredient, m_seenIngredient)
    << Save(measurement.m_createdIngredients, m_createdIngredients)
    << Save(measurement.m_createdIngredient, m_createdIngredient)
    << Save(measurement.m_heroMoves, m_heroMoves)
    << Save(measurement.m_heroMove, m_heroMove)
    << Save(measurement.m_catchTryItems, m_catchTryItems)
    << Save(measurement.m_catchTryItem, m_catchTryItem)
    << Save(measurement.m_catchItems, m_catchItems)
    << Save(measurement.m_catchItem, m_catchItem)
    << Save(measurement.m_brewTryPotions, m_brewTryPotions)
    << Save(measurement.m_brewTryPotion, m_brewTryPotion)
    << Save(measurement.m_brewPotions, m_brewPotions)
    << Save(measurement.m_brewPotion, m_brewPotion)
    << Save(measurement.m_selledItems, m_selledItems)
    << Save(measurement.m_selledItem, m_selledItem)
    << Save(measurement.m_sellTryItems, m_sellTryItems)
    << Save(measurement.m_sellTryItem, m_sellTryItem)
    << Save(measurement.m_buyedItems, m_buyedItems)
    << Save(measurement.m_buyedItem, m_buyedItem)
    << Save(measurement.m_buyTryItems, m_buyTryItems)
    << Save(measurement.m_buyTryItem, m_buyTryItem)
    << Save(measurement.m_goldGained, m_goldGained)
    << Save(measurement.m_goldPaid, m_goldPaid)
    << CHasher::Hash(measurement)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CMeasures &measures, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;

  t << t.StartClassVariable(NSClassName::CMeasures, varName);
  for (CMeasures::const_iterator it = measures.begin(); it != measures.end(); it++)
  {
    const ::CMeasure item = *it;
    t << Save(item, NSVariableName::CMeasure);
  }
  t << CHasher::Hash(measures)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CHero &hero, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CHero, varName)
    << Save(hero.m_place, m_place)
    << Save(hero.m_inventory, m_inventory)
    << Save(hero.m_hunting, m_hunting)
    << Save(hero.m_entomology, m_entomology)
    << Save(hero.m_herbalism, m_herbalism)
    << Save(hero.m_perception, m_perception)
    << Save(hero.m_gold, m_gold)
    << Save(hero.m_brewer, m_brewer)
    << CHasher::Hash(hero)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CGame &game, const char * varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CGame, varName)
    << Save(*game.m_hero, m_hero)
    << Save(*game.m_measurer, m_measurer)
    << Save(*game.m_map, m_map)
    << Save(game.m_brewer, m_brewer)
    << CHasher::Hash(game)
    << "}";
  return t;
}

CSerializer CSaver::Save(const ::CMapBlock &mapBlock, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CMapBlock, varName)
    << Save(mapBlock.m_type, m_type)
    << Save(mapBlock.m_rect, m_rect)
    << Save(mapBlock.m_ID, m_ID)
    << Save(mapBlock.m_datas, m_datas)
    << CHasher::Hash(mapBlock)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CBlockDatas &blockDatas, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CBlockDatas, varName);

  for (CBlockDatas::const_iterator it = blockDatas.begin(); it != blockDatas.end(); it++)
  {
    t << Save(*it, NSVariableName::CBlockData);
  }

  t << CHasher::Hash(blockDatas)
    << "}";
  return t;
}

CSerializer CSaver::Save(const ::CBlockData &blockData, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CBlockData, varName)
    << Save(blockData.visible, visible)
    << Save(blockData.itemIDs, itemIDs)
    << Save(blockData.buildings, buildings)
    << CHasher::Hash(blockData)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CBuildings &buildings, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CBuildings, varName);
  for (CBuildings::const_iterator it = buildings.begin(); it != buildings.end(); it++)
  {
    t << Save(*it, NSVariableName::CBuilding);
  }
  t << CHasher::Hash(buildings)
    << "}";
  return t;
}

CSerializer CSaver::Save(const ::CBuilding &building, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CBuilding, varName);
  if (building.house) t << Save(*building.house, house);
  if (building.town) t << Save(*building.town, town);
  t << CHasher::Hash(building)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CHouse &house, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CHouse, varName)
    << CHasher::Hash(house)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CTown &town, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CTown, varName)
    << Save(town.m_gold, m_gold)
    << Save(town.m_price, m_price)
    << Save(town.m_quality, m_quality)
    << CHasher::Hash(town)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CPriceList &price, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CPriceList, varName)
    << Save(price.m_map, m_map)
    << CHasher::Hash(price)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CPriceMap &priceMap, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CPriceMap, varName);
  for (CPriceMap::const_iterator it = priceMap.begin(); it != priceMap.end(); it++)
  {
    t << Save(*it, NSVariableName::CPricePair);
  }
  t << CHasher::Hash(priceMap)
    << "}";
  return t;
}

CSerializer CSaver::Save(const ::CPricePair &pricePair, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CPricePair, varName)
    << Save(pricePair.first, first)
    << Save(pricePair.second, second)
    << CHasher::Hash(pricePair)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CPriceItem &priceItem, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CPriceItem, varName)
    << Save(priceItem.sell, sell)
    << Save(priceItem.buy, buy)
    << CHasher::Hash(priceItem)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CBrewer &brewer, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CBrewer, varName);
  //  if (brewer.m_parent) t << Save(*brewer.m_parent, m_parent); //Сохранение родителя не требуется т.к. он един и при загрузки он будет установлен загрузчиком.
  t << Save(brewer.m_receipts, m_receipts)
    << CHasher::Hash(brewer)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CReceiptMap &receiptMap, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CReceiptMap, varName);
  for (CReceiptMap::const_iterator it = receiptMap.begin(); it != receiptMap.end(); it++)
  {
    t << Save(*it, NSVariableName::CReceiptPair);
  }
  t << CHasher::Hash(receiptMap)
    << "}";
  return t;
}

CSerializer CSaver::Save(const ::CReceiptPair &receiptPair, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CReceiptPair, varName)
    << Save(receiptPair.first, first)
    << Save(receiptPair.second, second)
    << CHasher::Hash(receiptPair)
    << "}";
  return t;
}

CSerializer CSaver::Save(const CReceipt &receipt, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CReceipt, varName)
    << Save(receipt.m_items, m_items)
    << CHasher::Hash(receipt)
    << "}";
  return t;

}

CSerializer CSaver::Save(const class CItemQuality &quality, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CItemQuality, varName)
    << Save(quality.m_moon, m_moon)
    << Save(quality.m_sun, m_sun)
    << Save(quality.m_magnetics, m_magnetics)
    << Save(quality.m_hardness, m_hardness)
    << Save(quality.m_divinity, m_divinity)
    << Save(quality.m_quality, m_quality)
    << CHasher::Hash(quality)
    << "}";
  return t;
}

CSerializer CSaver::Save(const class CValue &value, const CVariableNames &varName)
{
  FUNC_MEASURE;
  CSerializer t;
  t << t.StartClassVariable(NSClassName::CValue, varName)
       /*		<< Save(value.m_min, m_min)
              << Save(value.m_max, m_max)
              << Save(value.m_range, m_range)*/ //Max, Min, Range is constant and not changed in game
    << Save(value.m_value, m_value)
    << CHasher::Hash(value)
    << "}";
  return t;
}

CGameSaver::CGameSaver()
{
}

CSerializer CGameSaver::SaveGame(const CGame &game, const char *saveName)
{
  FUNC_MEASURE;

  CSerializer t = CSaver::Save(game, saveName);

  return t;
}
и да... это пример как делать не надо.


именно для JSON по работе использую RapidJson.

Аватара пользователя
Максим Кич
Администратор
Сообщения: 1642
Зарегистрирован: 03 дек 2006, 20:17
Откуда: Витебск, Беларусь
Контактная информация:

Re: Сохранение в игре.

Сообщение Максим Кич » 07 фев 2017, 09:10

Ни разу не сишник, но думаю, что решения как минимум на каком-то уровне примерно схожие.
altmax писал(а):
07 фев 2017, 07:01
Сложность в том, что надо несколько массивов различных данных загнать в один файл, причем некоторые массивы - динамические,а кроме того еще есть и списки <list>. Пока с трудом представляю, как это сделать.
Сериализация в JSON/XML. Рекурсивно. Каждый игровой объект должен уметь сериализовать себя с обходим принадлежащих ему объектов и десериализовать себя (и/или своё добро) из строки. Куда потом писать это — не принципиально. Можно на диск, можно в базу, можно на сервер отправлять.
Jesus05 писал(а):
07 фев 2017, 07:24
по идеи ODB + SQLite и бд в файле будет достаточно для сохранений
Имхо, оверкилл. Реляционная БД, как мне кажется, имеет смысл на сервере, если между игроками есть возможность обмениваться вещами/персонажами и т.д. А так смысла нет от слова «никакого». Вложенные структуры из реляционной БД восстанавливать — тоже удовольствие не для слабых духом. Даже если делать удалённое сохранение, но при этом между пользователями никаких взаимодействий не планируется от слова «никогда» — имеет смысл брать NoSQL типа Redis-а или MongoDB и тупо туда сгружать одной записью/документом JSON.
Dump the screen? [y/n]

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Сохранение в игре.

Сообщение altmax » 07 фев 2017, 09:16

Jesus05 писал(а):
07 фев 2017, 09:00

и да... это пример как делать не надо.
Да это просто великолепно )))) Чего стоят только 40 перегруженных методов.

altmax
Сообщения: 173
Зарегистрирован: 15 сен 2012, 11:59

Re: Сохранение в игре.

Сообщение altmax » 07 фев 2017, 09:22

Максим Кич писал(а):
07 фев 2017, 09:10

Сериализация в JSON/XML. Рекурсивно. Каждый игровой объект должен уметь сериализовать себя с обходим принадлежащих ему объектов и десериализовать себя (и/или своё добро) из строки. Куда потом писать это — не принципиально. Можно на диск, можно в базу, можно на сервер отправлять.
Да, это я уже прочитал и в других источниках - что каждый класс должен сам уметь себя сериализовать и собирать обратно. Что в общем то логично.
В общем то уже что-то намечается, до разработки самого сохранения еще далеко, думаю к тому времени уже надумаю рабочий алгоритм.

Аватара пользователя
Xecutor
Мастер
Сообщения: 758
Зарегистрирован: 25 мар 2008, 08:32

Re: Сохранение в игре.

Сообщение Xecutor » 07 фев 2017, 12:05

На самом деле самое сложное в сериализации это циклические зависимости.
Я делал свой велосипед. Бинарный. Получается очень быстро :)

Аватара пользователя
Anfeir
Сообщения: 876
Зарегистрирован: 14 дек 2007, 09:29
Контактная информация:

Re: Сохранение в игре.

Сообщение Anfeir » 07 фев 2017, 12:49

sqlite кстати неплохо подходит для сохранения в файл. Даже больше подходит именно для сохранения в файл, нежели в роли клиент-серверной реляционной субд. Но в целом - нафиг он нужен, если можно просто сериализовать бинарно.

В редакторе сериализовал в xml, чтоб быть уверенным, что если весь сейв навернётся, то я хоть что-то смогу вытащить. А обычные сейв-файлы вполне можно в бинарном виде. Особенно если не сильно париться вопросами совместимости между версиями.
Как у меня было сделано : есть класс Serializator, а каждый класс имеет метод Serialize(Serializator *sler), который рекурсивно будет всё содержимое сериализовать, ну, как обычно. Загрузка и сохранение проходят в два этапа. Это нужно, чтобы сериализовать указатели. На этапе сохранения в первом проходе каждому объекту присваивается serId, который увеличивается на единицу каждый раз, на втором проходе собственно сохраняются указатели. при загрузке первым проходом все объекты создаются, а указатели инициализируются на втором проходе. Разумеется, нужна рефлексия, что-то типа NewObjectById(int obId).

Главное, чётко определить, где нужно сериализовать сам объект, а где просто указатель на него.

Как это выглядит:
Скрытый текст: ПОКАЗАТЬ

Код: Выделить всё

	sler->SerCheck(0x0E0E0E0D);
	sler->SerInt(&id);	
	sler->SerInt(&templateId);
	sler->SerInt(&addExitIndex);
	sler->SerPtr(&terrain);
...
void Serializer::SerPtr(Entity **p)
{
	int serid;
	switch (pass)
	{
	case LOAD_PASS_1: 
		*p = NULL; 
		break;
	case LOAD_PASS_2: 
		 serid = *((int*)(contents+index)); 
		 if (serid > 0) *p = (Entity*)serIdMap[serid];
		break;
	case SAVE_PASS_1: 
		break;
	case SAVE_PASS_2: 
		if (*p) serid = (*p)->SerId();
		else serid = 0;
		*((int*)(contents+index)) = serid;
		break;
	}
	index += 4;
}

Сериализация внутреннего содержимого например так:
sler->SerInt(&numContents);

	if (sler->IsSaving())
	{
		InnerObject *ob = f_Contents;
		while (ob)
		{
			sler->SerializeEntityInstance(ob);
			ob = ob->GetNextOb();
		}
	}
	else
	{
		InnerObject *prevOb = NULL;
		for (int i = 0; i < numContents; i++)
		{
			ob = (InnerObject*)sler->SerializeEntityInstance(ob);
			ob->prevOb = prevOb;
			prevOb = ob;
			if (i == 0) this->f_Contents = ob;
		}
	}


----------------
	Serializer *sler = new Serializer(filename);
	sler->SetPass(LOAD_PASS_1);
	Serialize(sler);
	sler->SetPass(LOAD_PASS_2);
	Serialize(sler);
	sler->Finish();


Работает как часы, без нареканий. (Периодически что-то где-то ломалось или забывалось изза внутренних косяков, но быстро чинилось.)

Аватара пользователя
Cfyz
Сообщения: 776
Зарегистрирован: 30 ноя 2006, 10:03
Откуда: Санкт-Петербург
Контактная информация:

Re: Сохранение в игре.

Сообщение Cfyz » 07 фев 2017, 13:10

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

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

Алсо,
altmax писал(а):JSON-велосипед писал построчно в файл? Т.е. для записи состояния игры нужны были десятки тысяч обращений к жесткому диску?
Ни одна ОС не будет делать тысячи обращений к диску, даже если писать по одному байту (ну если только не попросить ее об этом явно). Накладные расходы на запись меркнут на фоне подготовки записываемой информации (составления строк, построения таблиц и пр.).
Пытается раскуклиться

Аватара пользователя
Oreyn
Сообщения: 297
Зарегистрирован: 07 авг 2013, 14:59

Re: Сохранение в игре.

Сообщение Oreyn » 07 фев 2017, 18:31

Cfyz писал(а):
07 фев 2017, 13:10
Раз речь идет о сохранении состояния игры, несправедливо было бы не упомянуть альтернативный подход: сохранение начального состояния ГСЧ и всего списка действий с начала игры. При загрузке действия применяются и на выходе должно получиться сохраненное состояние.
О да! И как фичу, в процессе самой загрузки убыстренно прокручивать все происходящее перед глазами игрока. Так чтобы вложилось секунд в 10.
Если механизм откатать, можно даже игровые фичи таким образом сделать. Эдакий амулет воскрешения, то есть возврата времени. При гибели лог пишет - "нет, нет, нет, все было не так" и возвращает пошагово игрока на 50 ходов назад.

Аватара пользователя
Максим Кич
Администратор
Сообщения: 1642
Зарегистрирован: 03 дек 2006, 20:17
Откуда: Витебск, Беларусь
Контактная информация:

Re: Сохранение в игре.

Сообщение Максим Кич » 07 фев 2017, 18:44

Cfyz писал(а):
07 фев 2017, 13:10
Раз речь идет о сохранении состояния игры, несправедливо было бы не упомянуть альтернативный подход: сохранение начального состояния ГСЧ и всего списка действий с начала игры. При загрузке действия применяются и на выходе должно получиться сохраненное состояние.
Тут главное не применять это ни к чему вроде Ангбанда. В эндшпиле «краткое содержание предыдущей серии» может занять чуть дольше, чем игрок сможет вынести.
Dump the screen? [y/n]

Аватара пользователя
Jesus05
Сообщения: 1840
Зарегистрирован: 02 дек 2009, 07:50
Откуда: Норильск, сейчас Санкт-петербург.
Контактная информация:

Re: Сохранение в игре.

Сообщение Jesus05 » 08 фев 2017, 06:51

Максим Кич писал(а):
07 фев 2017, 09:10
Jesus05 писал(а):
07 фев 2017, 07:24
по идеи ODB + SQLite и бд в файле будет достаточно для сохранений
Имхо, оверкилл. Реляционная БД ...
Не в БД дело. Скорее мое предложение в уходе от ручной рефлексии. у ODB как и возможно и других ORM`ок есть свой прекомпилер который сам формирует "таблицы" из классов. Сам учитывает зависимости построенные на shared_pointer сам разруливает циклические зависимости(хотя мне не удалось тогда растащить пример с циклической зависимостью из 1 хедера в 2-ва, но думаю я просто плохо читал документацию).
Т.е. суть предложение не в том что использовать БД, а в том что-бы использовать OMR-ки способные сами накодогенерить сериализацию классов сохранив\загрузив все поля без переписывания кода при каждом добавлении\изменении поля.

Аватара пользователя
Jesus05
Сообщения: 1840
Зарегистрирован: 02 дек 2009, 07:50
Откуда: Норильск, сейчас Санкт-петербург.
Контактная информация:

Re: Сохранение в игре.

Сообщение Jesus05 » 08 фев 2017, 07:57

Максим Кич писал(а):
07 фев 2017, 09:10
Ни разу не сишник, но думаю, что решения как минимум на каком-то уровне примерно схожие.
...
По поводу JSON у меня сложилось впечатление, что "не сишники" считаю, что в Сях JSON реализуеться как-то так:

Код: Выделить всё

json_encode(get_object_vars($class));
или так

Код: Выделить всё

public function toJSON(){
    return json_encode($this);
}
Только в сях все чуточку сложнее...

Ответить

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 41 гость