Setup files and working with them

Hi!
I want to discuss ways to work with files with game settings. I have worked example from Lazarus.
For game world generation i made unit with two functions, who read *.txt file.
First function read file string by string and get text from they.
Second function get clear text from first function and use it for fill world array cells.
I use this basic Pascal functions:

...  
  AssignFile(f, 'comands.txt');
  Reset(f);
  while not EOF(f) do
...

And this example of map configuration from my file:

____________________LOCATION-1____________________
____________________Border____________________
	Fill>
	1 0 13 0 19
		Bor
	Fill>
	1 1 12 1 18
		Mea

____________________Zone-1____________________
	Set>
	1 1 3 1 5
		>	>	>	>
		>	>	>	>
		>	>	>	>

If program read “Fill>”, they go to the next string and look, what map cells they must edit. After then, function 1 extract cells modifier (Bor - border blocks of my map, Mea - Meadow ground in this example). This modifier uses by my second function to edit cells of map (they have special type). For example, they define what texture, name and access has this cell of map. In a future i want to add many cells parameters in this function.
Command “Set>” uses for filling map like 2D array.
Thus, the program reads all file line by line and builds a map.

I want to use this method for keep configuration and edit game map from text files.
How i can make it with CGE? Is “AssignFile” work here correct? Or i can choose some better way to use my configuration files here?

Hello!

Usually you indeed can just use “the old method” and it’ll work. No magic :slight_smile:

However, it’ll work the way it was working before - that is on Desktop platforms (Linux, Windows). If you need to go to Android, iOS or anything else exotic, you’ll need a bit more complex approach to work with game data/assets.

For a safer and more universal way of handling text files you can use Download from CastleDownload unit and read the file to a TStringList. E.g. like here wordsgenerator/castlewordsgenerator.pas · master · EugeneLoza / Words Generator · GitLab After that you just work with TStringList line-by-line, same way as you were processing your text file by ReadLn.

1 Like

Adding to Eugene’s answer:

In general you can indeed just use your existing code to manipulate files (whether it’s user preferences or game data). It will work – on the platforms where you made it work :slight_smile:

What we recommend though is that you use CGE way to manipulate files. This means:

Strings := TStringList.Create;
Strings.LoadFromUrl('castle-data:/commands.txt');

Why all this?

  • Because of some platforms, the “game data” is no longer a set of regular files. For example on Android, your game data packed to APK/AAB is accessed through a special “Android assets API”. These are not regular files (well maybe they are, internally, but you should not access them using regular file access). Accessing them using AssignFile(F, ...) would not work – you do not even know what filename to use there.

    When you use the CGE approach to read this data, then it will “just work” and you will not even notice a difference. You just access castle-data:/commands.txt and read it using TTextReader and it will work on Android just like it works on desktop.

  • Similarly on some platforms the “user preferences” storage has special API to access it. Using UserConfig or ApplicationConfig makes it “just work”.

1 Like

Good, thanks, it look effectively! Will trying.
:upside_down_face:

Today I tryed TTextReader get little problem with ReadLn.
When i use Eof or ReadLn(f, …), i get Error: Can't read or write variables of this type

If i use dot notation, it work ok, until move to next ReadLn:

But with dot notation i loose the opportunity to move 3 numbers from string to 3 variables to set array length and need to write a little code to do it (or not?.. I can’t use “z, y, x := f.ReadLn” like with one variable).

It means, i use TTextReader wrong?


uses
  Classes, SysUtils, CastleDownload;
...
var 
  f: Text;
...
  f:= TTextReader.Create('castle-data:/comands.txt');
  while not f.EOF do
  begin
    ReadLn(metka, f);
    if pos(starter, metka)>0 then
    begin
      ReadLn(f, z, y, x);
      SetLength(Mir, z, y, x);

You have to use the dot notation, that is you have to call F.Readln, using the Readln method of TTextReader.

Using the standard Pascal Readln(...) will not work, i.e. the F (instance of TTextReader) is indeed not a valid parameter for standard Pascal Readln(...), as it is not a TextFile type.

You indeed need to write a bit of different code as the TTextReader.Readln and friends (like TTextReader.ReadSingle) work a bit differently than build-in Readln. But we provide a few ways to help with this :slight_smile:

  { using ReadSingle }
  T := TTextReader.Create('castle-data:/a.txt');
  try
    X := T.ReadSingle;
    Y := T.ReadSingle;
    Z := T.ReadSingle;
    Writeln('Got vector: ', X:1:2, ' ', Y:1:2, ' ', Z:1:2);
    X := T.ReadSingle;
    Y := T.ReadSingle;
    Z := T.ReadSingle;
    Writeln('Got vector: ', X:1:2, ' ', Y:1:2, ' ', Z:1:2);
    X := T.ReadSingle;
    Y := T.ReadSingle;
    Z := T.ReadSingle;
    Writeln('Got vector: ', X:1:2, ' ', Y:1:2, ' ', Z:1:2);
  finally FreeAndNil(T) end;

  { alternative version using ReadVector3 }
  T := TTextReader.Create('castle-data:/a.txt');
  try
    V := T.ReadVector3;
    Writeln('Got vector: ', V.ToString);
    V := T.ReadVector3;
    Writeln('Got vector: ', V.ToString);
    V := T.ReadVector3;
    Writeln('Got vector: ', V.ToString);
  finally FreeAndNil(T) end;
  • You can use TTextReader.Readln and convert the String to TVector3 using Vector3FromStr. Like this:
  { alternative version using Readln + Vector3FromStr }
  T := TTextReader.Create('castle-data:/a.txt');
  try
    V := Vector3FromStr(T.Readln);
    Writeln('Got vector: ', V.ToString);
    V := Vector3FromStr(T.Readln);
    Writeln('Got vector: ', V.ToString);
    V := Vector3FromStr(T.Readln);
    Writeln('Got vector: ', V.ToString);
  finally FreeAndNil(T) end;
  • You can use TTextReader.Readln and extract the 3x Single values using DeFormat. Like this:
  { alternative version using Readln + DeFormat }
  T := TTextReader.Create('castle-data:/a.txt');
  try
    DeFormat(T.Readln, '%.single. %.single. %.single.', [@X, @Y, @Z]);
    Writeln('Got vector: ', X:1:2, ' ', Y:1:2, ' ', Z:1:2);
    DeFormat(T.Readln, '%.single. %.single. %.single.', [@X, @Y, @Z]);
    Writeln('Got vector: ', X:1:2, ' ', Y:1:2, ' ', Z:1:2);
    DeFormat(T.Readln, '%.single. %.single. %.single.', [@X, @Y, @Z]);
    Writeln('Got vector: ', X:1:2, ' ', Y:1:2, ' ', Z:1:2);
  finally FreeAndNil(T) end;

I’m attaching a quick demo I made of all these approaches :slight_smile:

Personally I would choose to express vectors as TVector3 and use ready utilities to parse such vectors. So I would choose T.ReadVector3 or V := Vector3FromStr(T.Readln) approach. But of course it depends on your case, all these snippets do really the same, so it’s a question of what you find matches the rest of your code.

test_text_reader.zip (1.5 KB)

2 Likes

Big thanks for so detailed answers :relieved: