The first approach to file handling in Libgdx

As our first contact with the Libgdx filesystem, we will learn about the types of files available to us. We will also show you how to read from and write to both binary and text files. Moreover, this recipe will show you how to extract information from entries such as whether it is a file or a directory as well as its size, name, and extension. Finally, you will also discover how to traverse a directory tree hierarchy.

This will settle the grounds for you to include features such as save game states and configuration files in your projects.

Getting ready

Once again, make sure the sample projects are in your Eclipse workspace before continuing.

How to do it…

Jumping straight into the topic at hand, everything explained throughout this recipe is illustrated in the FileHandlingSample class within the samples/core project.

In order for us to work with a file in Libgdx, we need a FileHandle instance that points to said file or folder. This class takes care of all the platform-specific details, offering us a clean, simple API to carry out a wide set of operations.

To obtain an appropriate FileHandle, we can call getFileHandle(), which is a static method in the Files interface. Such an interface is accessible through Gdx.files:

FileHandle getFileHandle(java.lang.String path, Files.FileType type)

FileType is an enum listing the different kinds of files available in Libgdx. All of them will be covered in detail in this recipe:

public enum FileType { Absolute, Classpath, External, Internal, Local};

The Files interface also provides convenient methods to retrieve file handles:

FileHandle handle = Gdx.files.absolute("test.txt");
FileHandle handle = Gdx.files.classpath("test.txt");
FileHandle handle = Gdx.files.external("test.txt");
FileHandle handle = Gdx.files.internal("test.txt");
FileHandle handle = Gdx.files.local("test.txt");

Note

Asking for a handle to an invalid or nonexistent file will not result in an exception being thrown. This allows us to obtain a handle to a file that does not exist, write to it, and thus effectively create the file.

Retrieving basic information from files

The FileHandle class provides a few methods to easily retrieve information from the files they point to:

  • handle.name(): This returns the name of the file, including its extension but excluding the path to it, for example, sfx_01.wav
  • handle.nameWithoutExtension(): This returns the name of the file with neither the extension nor the path to the parent folder, for example, sfx_01
  • handle.extension(): This returns the extension of the file without the dot, for example, wav
  • handle.path(): This returns the path to the file, for example, data/sfx/sfx_01.wav
  • handle.pathWithoutExtension(): The same as the preceding method excluding the extension, for example, data/sfx/sfx_01
  • handle.lastModified(): This returns the date and time the file was modified for the last time, in milliseconds, since epoch
  • handle.length(): This returns the size of the file in bytes
  • handle.isDirectory(): This indicates whether the handle points to a directory or a plain file

Traversing tree structures

The FileHandle class provides a list() method that returns an array of FileHandle instances containing its children. Using this, we can walk a tree hierarchy with a very simple recursive function. If the handle points to a regular file and not a directory, list() will return an empty array:

private void traverseTree(FileHandle handle) {
  doSomething(handle);

  for (FileHandle child : handle.list()) {
    traverseTree(child);
  }
}

Note

Listing a directory is not supported in desktop platforms; take this into consideration when developing your games. It works during development when running from your IDE. However, it will not work from a packed .jar file. A quick-and-dirty solution will be to call and parse the output of commands, such as ls or dir.

Writing to and reading from files

File writing can be achieved through any of the write() method variants the FileHandle class has; we will only cover a couple of them. Creating a new file and adding a string to it is as easy as is shown in the following code. The second parameter indicates whether to append at the end or write at the beginning (truncating the file):

Gdx.files.external("test.txt").writeString("This is a test file", false);

On the other hand, you might want to treat the file as binary and write a stream of bytes to it:

byte[] bytes = new byte[] {'T', 'e', 's', 't'};
Gdx.files.external("test").writeBytes(bytes, false);

The same options to read from a file are also available. We can put all the contents of a file on a String object with the following snippet:

String string = Gdx.files.external("test.txt").readString();

We can also get the whole content of the file as an array of bytes by executing this:

String bytes = Gdx.files.external("test.txt").readBytes();

Check out the official API documentation for a comprehensive list of mechanisms for file reading and writing, available at http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/files/FileHandle.html.

Copying and deleting files and directories

Libgdx offers additional file and directory manipulation through the following FileHandle functions.

To copy a file or folder, use copyTo() to pass the handle of the destination handle:

void copyTo(FileHandle dest)

You can erase files or directories with delete(). The operation will fail if the handle points to a non-empty directory or a location where the user cannot delete files. If you want to delete a directory and its contents recursively, call the deleteDirectory() method instead:

boolean delete()
boolean deleteDirectory()

Lastly, you might want to erase the contents of a directory without removing the directory itself. Use the emptyDirectory() function in this case. The preserveTree parameter tells the handle whether to delete recursively, or delete only the files at this level:

void emptyDirectory(boolean preserveTree)

How it works…

We mentioned the different file types briefly earlier in this recipe; it is really important to understand their differences and quirks because there are many platform-specific details to take into account. Here is a more detailed description of each one of them:

  • Absolute: These are fully qualified paths. They should be avoided whenever possible for the sake of portability as it is harder to rely on users that have specific directory structures. Absolute files are not available in the HTML backend.
  • Classpath: These are files inside the source directory of your Java application, which are strictly read only and cannot be used within HTML applications.
  • Internal: This is relative to the working directory on desktop platforms, the assets folder on Android, and the war/assets directory in HTML projects. It's available on all platforms, but is also only read only. This is where we typically store all our game files, such as audio, graphics, and gameplay data.
  • External: This is relative to the home directory of the current user on desktop platforms and the root of the SD card on Android (requires special permissions in the manifest file). It can be written as well as read, although not from the HTML backend. It is best suited for saving downloaded files and other similar activities.
  • Local: This is basically equivalent to internal files on a desktop, but relative to private storage on Android. It can be both read only and write files, but it's not supported from the HTML backend. It is typically used for small game-save data.

As we just mentioned, some kinds of files are not available on all platforms. To check for this, you can make use of the following methods. Keep in mind that it's best not to use absolute or classpath files:

Gdx.files.isExternalStorageAvailable();
Gdx.files.isLocalStorageAvailable();

There's more…

As you have seen already, the Files interface is quite powerful. Here are a few quick extra things you can do with it.

Creating temporary files

We often need disposable files with unique names for certain operations so that they do not collide with other files in the system. We can create a temporary directory or file in the FileHandle class with the following static methods. The parameter is used as the prefix for the name:

FileHandle tempDir = FileHandle.tempDirectory("temp");
FileHandle tempFile = FileHandle.tempFile("temp");

Filtering directory listings

Another common situation is to iterate over the files in a directory, but only be interested in files that match a certain criteria. Rather than adding an if condition every time you traverse a tree, why not just pass a FileFilter or FileNameFilter instance to the list() function? This approach is cleaner and avoids code duplication.

FileFilter and FileNameFilter are interfaces in the Java standard library. Provide your own implementation with the required criteria in each case:

public interface FileFilter {
  boolean accept(File pathname);
}

public interface FileNameFilter {
  boolean accept(File dir, String name);
}

File streaming

Libgdx supports streaming for large files. Take a look at the read() and write() method overloads that take or return streams at http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/files/FileHandle.html.

See also

  • Move on to the Using preferences to store game states and options recipe to learn more about files in Libgdx.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.178.237