Saving and Reading from database file

Hi.
I use textfiles to save and load variable values from my game project.
But now I have problems with storing single values as a text and reading them back.
How can I do this or are there better ways in CGE to put single variables and game progression data values in a file?

There are several ways to.

I personally use XML containers for storing the read-only or read/write data.

In the simplest case scenario you can have a “save game” stored in your CastleConfig entity. E.g. see here code/gamesave.pas · master · EugeneLoza / Kryftolike · GitLab . So, what happens there, it creates a save_game node inside UserConfig object. And then saves data of all in-game entities as its children. E.g. Player.Save(Element); is code/gameplayer.pas · master · EugeneLoza / Kryftolike · GitLab i.e. it just calls ThisElement.AttributeSet('variable-name', variable);. Later, exactly the same way it reads those values code/gameplayer.pas · master · EugeneLoza / Kryftolike · GitLab by variable := Element.AttributeInteger('variable-name'); - the call will be different for every different data type, like AttributeString, AttributeFloat, etc.

Reading and writing “arrays” or “lists” of stuff is a bit more complicated. E.g. this loop saves all “marks” code/map/gamemap.pas · grandmaster · EugeneLoza / Vinculike · GitLab and this one uses ChildrenIterator to load the saved data code/map/gamemap.pas · grandmaster · EugeneLoza / Vinculike · GitLab

Read-only data (game data) is read the same way as loading the game: just iterate through the XML file using ChildrenIterator like here code/actors/inventory/gameitemdata.pas · grandmaster · EugeneLoza / Vinculike · GitLab where TItemData.Create(Iterator.Current); is a procedure that actually reads values from the current entry like here code/actors/inventory/gameitemdata.pas · grandmaster · EugeneLoza / Vinculike · GitLab . The data itself is human-readable and human-writable and in simple cases looks like this data/character/itemdata.xml · grandmaster · EugeneLoza / Vinculike · GitLab

There are also other ways: using JSON container (it’s more compact than XML, but I’m just used to XML + JSON doesn’t have ability to put in comments); using text files (even more compact than JSON, but very error-prone); using binary container (this one is very compact, but may create a serious problem to port data between game versions), etc.

1 Like

For a simple explanation how to store user data (like savegames) in the simplest way in CGE, see Persistent data (user preferences, savegames) | Manual | Castle Game Engine .

To answer this, you should really describe what problems do you have :slight_smile: Basically, you can save and write text files in CGE in a regular fashion, but for cross-platform games we advise you read/write them using routines based on TStream and our URLs.

If you describe to us exactly what do you do and where’s the problem, we can offer a more precise solution :slight_smile:

@eugeneloza Thanks, I will study your codes. :slight_smile:

@michalis The problem was saving a floating value as text, but I quite forgot this could be simply done with FloatToStr.
I will also study the CGE examples though, thanks.

A friendly reminder when dealing with floating point numbers: In many countries, decimal delimiter is comma not dot. Failing to deal with this will result in errors on systems that expect the delimiter to be different than the ones in your text files. So make sure to pass custom TFormatSettings variable when calling FloatToStr, for example:

var FS: TFormatSettings;


FS := FormatSettings;
FS.DecimalSeparator := ‘.’;
Result := FloatToStr(X, FS);

Yeah, it’s an easy mistake to make when dealing with floats. For good or bad, in Pascal – most floating point conversion routines by default take “locale” into account, and sometimes decimal delimiter is a dot (what most programmers and text formats would expect) sometimes a comma (e.g. on Polish Windows).

We already have helper routines in CGE to deal with it easier:

{ Like standard Format, but always uses dot (.) as a decimal separator
  for the floating point numbers, regardless of the user's locale settings.

  In general this ignores locale, so also ThousandSeparator doesn't matter
  (conversion is done like it is #0). }
function FormatDot(const Fmt: String; const Args: array of const): String;

{ Like standard FloatToStr, but always uses dot (.) as a decimal separator
  for the floating point numbers, regardless of the user's locale settings.

  In general this ignores locale, so also ThousandSeparator doesn't matter
  (conversion is done like it is #0). }
function FloatToStrDot(const Value: Extended): String;

{ Like standard FloatToStr, but always uses dot (.) as a decimal separator
  for the floating point numbers, regardless of the user's locale settings.

  In general this ignores locale, so also ThousandSeparator doesn't matter
  (conversion is done like it is #0). }
function FloatToStrFDot(const Value: Extended;
  const FloatFormat: TFloatFormat;
  const Precision: Integer;
  const Digits: Integer): String;

{ Like standard TryStrToFloat, but always uses dot (.) as a decimal separator
  for the floating point numbers, regardless of the user's locale settings.

  Also it doesn't Trim the argument (so whitespace around number is @italic(not) tolerated),
  and in general ignores locale (standard StrToFloat looks at ThousandSeparator). }
function TryStrToFloatDot(const S: String; out Value: Single): Boolean; overload;
function TryStrToFloatDot(const S: String; out Value: Double): Boolean; overload;
{$ifndef EXTENDED_EQUALS_DOUBLE}
function TryStrToFloatDot(const S: String; out Value: Extended): Boolean; overload;
{$endif}

{ Like standard StrToFloat, but always uses dot (.) as a decimal separator
  for the floating point numbers, regardless of the user's locale settings.

  Also it doesn't Trim the argument (so whitespace around number is @italic(not) tolerated),
  and in general ignores locale (standard StrToFloat looks at ThousandSeparator). }
function StrToFloatDot(const S: String): Extended;

{ Like standard StrToFloatDef, but always uses dot (.) as a decimal separator
  for the floating point numbers, regardless of the user's locale settings.

  Also it doesn't Trim the argument (so whitespace around number is @italic(not) tolerated),
  and in general ignores locale (standard StrToFloat looks at ThousandSeparator). }
function StrToFloatDefDot(Const S: String; const Default: Extended): Extended;

Actually our unit CastleUtils sets DecimalSeparator to dot in initialization, so it is really dot by default, if you use any CGE unit (and don’t overwrite it). But it’s better to not depend on this initialization (we may remove it at some point – as it may be unexpected if someone actually wants localized decimal separator) and use XxxDot routines.

@kagamma Thanks for the advice! :slight_smile:

Thanks for the explanation. :slight_smile: