Utilities for management of assets and heavy resources.
While libGDX does a good job of helping you with assets through AssetManager and related APIs, is does not use
the full potential of the Kotlin features, especially when it comes to handling Class instances. This library
aims to provide Kotlin extensions and wrappers for the existing asset APIs to make assets usage more idiomatic
in Kotlin applications.
AssetManager.loadextension method can be used to schedule asynchronous loading of an asset. It returns an asset wrapper, which can be used as delegate property, as well as used directly to manage the asset. Usually the asset will not be available untilAssetManager.finishLoadingor loopedAssetManager.updateare called. You can use string file paths orAssetDescriptorinstances to load the asset. Usage example:
// Eagerly loading an asset:
val wrapper = load<Texture>("test.png")
wrapper.finishLoading()
val texture = wrapper.asset
// Delegate field:
class Test(assetManager: AssetManager) {
val texture by assetManager.load<Texture>("test.png")
// Type of texture property is Texture.
}AssetManager.getAssetutility extension method can be used to access an already loaded asset, without having to pass class to the manager to specify asset type. This is the preferred way of accessing assets from theAssetManager, provided that they were already scheduled for asynchronous loading and fully loaded. Note that this method will fail if asset is not loaded yet. Usage example:
val texture: Texture = assetManager.getAsset("test.png")AssetManager.loadOnDemandis similar to theloadutility method, but it provides an asset wrapper that loads the asset eagerly on first get call. It will not schedule the asset for asynchronous loading - instead, it will block current thread until the asset is loaded on the first access. Use for lightweight assets that should be (rarely) loaded only when requested. Usage example:
// Eagerly loading an asset:
val texture by assetManager.loadOnDemand<Texture>("test.png")
// Asset will be loaded upon first `texture` usage.
// Delegate field:
class Test(assetManager: AssetManager) {
val texture: Texture by assetManager.loadOnDemand("test.png")
// Asset will be loaded on first `texture` access.
}AssetManager.unloadSafelyis a utility method that attempts to unload an asset from theAssetManager. Contrary toAssetManager.unloadcall, this is a graceful method that will not throw any exceptions if the asset was not even loaded in the first place. Typical usage:assetManager.unloadSafely("test.png").AssetManager.unloadextension method consuming a exception handling block was added, so you can gracefully handle exception thrown during reloading. Note thatAssetManagercan throwGdxRuntimeExceptionif the asset was not loaded yet.AssetManager.getLoaderandsetLoaderextension methods with reified types added to ease handling ofAssetLoaderinstances registered in theAssetManager.- The
AssetGroupclass is provided for easily grouping together assets so they can be managed as a group through calls such asloadAll()orunloadAll(). The intended use is to subclassAssetGroupand list its member assets as properties usingAssetGroup.asset()orAssetGroup.delayedAsset(). It also allows for using a common prefix for the file names of the group in case they are stored in a specific subdirectory.
- Null-safe
Disposable.disposeSafely()method was added. Can be called on a nullableDisposable?variable. Ignores most thrown exceptions (except for internal JVMErrorinstances, which should not be caught anyway). Disposable.disposewith an exception catch block was added. Usingasset.dispose { exception -> doSomething() }syntax, you can omit a rather verbose try-catch block and handle exceptions with a Kotlin lambda.- Any
IterableorArraystoringDisposableinstances will havedispose,dispose { exception -> }anddisposeSafelymethods that dispose stored assets ignoring anynullelements. This is a utility for disposing collections of assets en masse. - All exceptions get a utility
ignore()method that you can switch at compile time (for debugging or logging) when needed. SeeThrowable.ignore()documentation for further details.
DisposableContainer is a Disposable implementation that stores a set of Disposable instances to be disposed
all at once.
When subclassed or used as a delegate via its DisposableRegistry interface, it provides an alsoRegister extension,
which allows to easily add items to the container so that they will be automatically disposed when the containing class is.
Poolinstances can be invoked like a function to provide new instances of objects. Basically, this syntax:pool()has the same effect as directly callingpool.obtain().Poolinstances can be invoked like a one argument function to free instances of objects. Basically, this syntax:pool(instance)has the same effect as directly callingpool.free(instance).- New instances of
Poolcan be easily created with Kotlin lambda syntax usingpoolmethod. For example, this pool would return new instances ofEntityonce empty:pool { Entity() }. Since this method is inlined, you should not worry about unnecessary extra method calls or extra objects - thePoolimplementations are prepared at compile time.
- Any Kotlin string can be quickly converted to a
FileHandleinstance usingtoClasspathFile,toInternalFile,toLocalFile,toExternalFileortoAbsoluteFile. This is basically a utility for accessingGdx.files.getFileHandlemethod with a pleasant Kotlin syntax. fileutility function allows to quickly obtain aFileHandleinstance. It features an optionaltypeparameter which allows to choose theFileType, while defaulting to the most commonInternal.
FileType.getResolverextension method was added to quickly constructFileHandleResolverinstances for the chosen file types.FileHandleResolver.withPrefixextension method was added to ease decoration ofFileHandleResolverinstances withPrefixFileHandleResolver.FileHandleResolver.forResolutionsextension method was added to ease decoration ofFileHandleResolverinstances withResolutionFileResolver.resolutionfactory function was added to constructResolutionFileResolver.Resolutioninstances with idiomatic Kotlin syntax.
TextAssetLoaderallows to read text files asynchronously via theAssetManager. Very useful if you want to leverageAssetManagerloading and management API for a notable number of text files.
Obtaining FileHandle instances:
import com.badlogic.gdx.Files.FileType.*
import ktx.assets.*
val fileHandle = "my/file.png".toInternalFile()
val internal = file("my/file.png")
val absolute = file("/home/ktx/my/file.png", type = Absolute)Working with libGDX Pool:
import ktx.assets.*
val pool = pool { "String." }
val obtained: String = pool() // "String."
pool(obtained) // Returned instance to the pool.Gracefully disposing assets:
import ktx.assets.*
texture.disposeSafely()
music.dispose { exception ->
println(exception.message)
}Disposing collections of assets:
import ktx.assets.*
val textures: Array<Texture> = getMyTextures() // Works with any Iterable, too!
textures.dispose() // Throws exceptions.
textures.disposeSafely() // Ignores exceptions.
textures.dispose { exception -> } // Allows to handle exceptions.Registering Disposable instances for disposal when the containing class is disposed:
import com.badlogic.gdx.Screen
import com.badlogic.gdx.assets.AssetManager
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import ktx.assets.*
class MyScreen: Screen, DisposableRegistry by DisposableContainer() {
val assetManager = AssetManager().alsoRegister()
val spriteBatch = SpriteBatch().alsoRegister()
}Manually disposing registered Disposable instances with custom error handling:
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.ScreenAdapter
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import ktx.assets.*
class MyScreen: ScreenAdapter(), DisposableRegistry by DisposableContainer() {
val spriteBatch = SpriteBatch().alsoRegister()
override fun dispose() {
// DisposableRegistry offers registeredDisposables list
// that can be accessed to perform custom disposal:
registeredDisposables.dispose { exception ->
Gdx.app.error("MyScreen.dispose()", exception.message)
}
}
}Resolving ambiguity given multiple dispose implementations - storing a reference to DisposableContainer to
call its dispose method explicitly:
import com.badlogic.gdx.ScreenAdapter
import ktx.assets.*
class MyScreen(
private val registry: DisposableRegistry = DisposableContainer()
): ScreenAdapter(), DisposableRegistry by registry {
// `dispose` has to be overridden, as it is provided by both
// DisposableRegistry and ScreenAdapter:
override fun dispose() {
// Calling registry explicitly:
registry.dispose()
}
}Scheduling assets loading by an AssetManager:
import ktx.assets.*
assetManager.load<Texture>("image.png")Using field delegate which will eventually point to a Texture (after it's fully loaded by an AssetManager):
import ktx.assets.*
import com.badlogic.gdx.assets.AssetManager
class MyClass(assetManager: AssetManager) {
val image by assetManager.load<Texture>("image.png")
// image is Texture == true
}Immediately extracting a fully loaded asset from an AssetManager:
import ktx.assets.*
val texture: Texture = assetManager.getAsset("image.png")
// Or alternatively:
val texture = assetManager.getAsset<Texture>("image.png")Using an asset loaded on the first getter call rather than scheduled for loading:
import ktx.assets.*
import com.badlogic.gdx.assets.AssetManager
class MyClass(assetManager: AssetManager) {
val loadedOnlyWhenNeeded by assetManager.loadOnDemand<Texture>("image.png")
// loadedOnlyWhenNeeded is Texture == true
}Unloading an asset from an AssetManager, ignoring exceptions:
import ktx.assets.*
assetManager.unloadSafely("image.png")Unloading an asset from an AssetManager, handling exceptions:
import ktx.assets.*
assetManager.unload("image.png") { exception -> exception.printStackTrace() }Managing AssetLoader instances of an AssetManager:
import ktx.assets.*
// Settings custom AssetLoader:
val myCustomLoader = CustomLoader()
assetManager.setLoader(myCustomLoader) // No need to pass class.
// Accessing an AssetLoader:
val loader = assetManager.getLoader<MyAsset>()Creating an InternalFileHandleResolver:
import ktx.assets.getResolver
import com.badlogic.gdx.Files.FileType
val resolver = FileType.Internal.getResolver()Decorating FileHandleResolver with PrefixFileHandleResolver:
import ktx.assets.*
import com.badlogic.gdx.Files.FileType
val resolver = FileType.Internal.getResolver().withPrefix("folder/")
Decorating FileHandleResolver with ResolutionFileResolver:
import ktx.assets.*
import com.badlogic.gdx.Files.FileType
val resolver = FileType.Internal.getResolver().forResolutions(
resolution(width = 800, height = 600),
resolution(width = 1024, height = 768)
)A simple application that registers TextAssetLoader, reads a file asynchronously, prints it and terminates:
import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.assets.AssetManager
import ktx.assets.TextAssetLoader
import ktx.assets.load
import ktx.assets.setLoader
class MyApp : ApplicationAdapter() {
private val manager = AssetManager()
override fun create() {
manager.setLoader(TextAssetLoader())
manager.load<String>("file.txt")
}
override fun render() {
if (!manager.update()) {
println("Loading...")
} else {
println(manager.get<String>("file.txt"))
Gdx.app.exit()
}
}
}Using AssetGroup to schedule loading and manage a group of assets:
/** Groups UI-related assets. */
class UIAssets(manager: AssetManager) : AssetGroup(manager, filePrefix = "ui/") {
// The assets are listed using asset(),
// so they are immediately queued for loading when the object is created.
val skin by asset<Skin>("skin.json")
val clickSound by asset<Sound>("mapScreen.wav")
}
val uiAssets = UIAssets(manager)
// No need to queue the assets. They are queued when creating the group object.
// Block until they are finished loading:
uiAssets.finishLoading()
// Note that classic incremental loading with update() is also available.
// Accessing assets - same as with regular properties:
uiAssets.skin
uiAssets.clickSound
// Disposing of the assets:
uiAssets.unloadAll()Using AssetGroup with assets loaded on demand once needed:
/** An asset groups that loads the assets on demand on first access. */
class MapScreenAssets(manager: AssetManager) : AssetGroup(manager, filePrefix = "mapScreen/") {
// The assets are listed using delayedAsset(),
// so they are loaded on demand on the first access
// or have to be scheduled for loading manually with loadAll().
val atlas by delayedAsset<TextureAtlas>("map.atlas")
val music by delayedAsset<Music>("mapScreen.ogg")
}
val mapScreenAssets = MapScreenAssets(manager)
// Assets are not queued for loading just yet.
// To load the assets, you can access them with properties:
mapScreenAssets.atlas
// Note that this will block until the asset is loaded.
// You can schedule them for asynchronous loading to avoid thread blocking:
mapScreenAssets.loadAll()
if (mapScreenAssets.update()) {
// The member assets are now ready to use.
} else {
// Continue showing loading prompt.
}
// Disposing of the assets:
mapScreenAssets.unloadAll()Create an enum with all assets of the selected type. Let's assume our application stores all images in assets/images
folder in PNG format. Given logo.png, player.png and enemy.png images, we would create a similar enum:
enum class Images {
logo,
player,
enemy;
val path = "images/${name}.png"
fun load() = manager.load<Texture>(path)
operator fun invoke() = manager.getAsset<Texture>(path)
companion object {
lateinit var manager: AssetManager
}
}Operator invoke() function brings asset accessing boilerplate to mininum: enumName(). Thanks to wildcard imports, we
can access logo, player and enemy enum instances directly:
import com.example.Images.*
// Setting AssetManager instance:
Images.manager = myAssetManager
// Scheduling logo loading:
logo.load()
// Getting Texture instance of loaded logo:
val texture = logo()
// Scheduling loading of all assets:
Images.values().forEach { it.load() }
// Accessing all textures:
val textures = Images.values().map { it() }ktx-assets-asyncprovides an alternative asset manager with non-blocking API based on coroutines. In contrary to libGDXAssetManager, KTXAssetStoragesupports concurrent loading of assets on multiple threads performing asynchronous operations.- libgdx-utils feature an annotation-based asset manager implementation which easies loading of assets (through internal reflection usage).
- Autumn MVC is a Spring-inspired model-view-controller framework built on top of libGDX. It features its own asset management module which loads and injects assets into annotated fields thanks to reflection.
- Kiwi library has some utilities for assets handling, like
graceful
Disposabledestruction methods and libGDX collections implementingDisposableinterface. It is aimed at Java applications though - KTX syntax should feel more natural when using Kotlin.