GWT backend for libGDX enabling Java game development for web browsers through JavaScript compilation
—
The GWT backend includes a comprehensive asset preloading system designed specifically for web deployment. The preloader downloads and caches all game assets before the application starts, ensuring smooth gameplay without loading delays.
public class Preloader {
// Preloader state information
public static class PreloaderState {
public long loaded; // Bytes loaded
public long total; // Total bytes to load
public boolean hasEnded; // Loading complete
public float getProgress();
public boolean isComplete();
}
// Preloader callback interface
public interface PreloaderCallback {
void update(PreloaderState state);
void error(String file);
}
// Constructor
public Preloader(String assetsFileLocation);
public Preloader();
// Preloading control
public void preload(String assetFileUrl, PreloaderCallback callback);
public PreloaderState update();
// Asset access after preloading
public boolean contains(String path);
public int length(String path);
public boolean isText(String path);
public boolean isAudio(String path);
public boolean isImage(String path);
public boolean isBinary(String path);
// Asset reading
public String getText(String path);
public byte[] getBinary(String path);
public ImageElement getImage(String path);
// Asset management
public void loadText(String url, AssetDownloader.AssetLoaderListener<String> assetLoaderListener);
public void loadBinary(String url, AssetDownloader.AssetLoaderListener<Blob> assetLoaderListener);
public void loadImage(String url, AssetDownloader.AssetLoaderListener<ImageElement> assetLoaderListener);
public void loadAudio(String url, AssetDownloader.AssetLoaderListener<Void> assetLoaderListener);
}public class AssetDownloader {
// Asset loading interface
public interface AssetLoaderListener<T> {
void onProgress(double amount);
void onFailure();
void onSuccess(T result);
}
// Asset loading methods
public void load(Array<Asset> assets, LoaderCallback<AssetDownloader> callback);
public void loadText(String url, AssetLoaderListener<String> listener);
public void loadBinary(String url, AssetLoaderListener<Blob> listener);
public void loadImage(String url, AssetLoaderListener<ImageElement> listener);
public void loadAudio(String url, AssetLoaderListener<Void> listener);
// Asset information
public boolean contains(String path);
public boolean isText(String path);
public boolean isBinary(String path);
public boolean isAudio(String path);
public boolean isImage(String path);
// Asset access
public String getText(String path);
public ImageElement getImage(String path);
public Blob getBinary(String path);
}public interface AssetFilter {
boolean accept(String file, boolean isDirectory);
// Pre-defined filter types
AssetFilter IMAGE_FILTER = new AssetFilter() {
public boolean accept(String file, boolean isDirectory) {
return isDirectory || file.matches(".*\\.(png|jpg|jpeg|gif|bmp)$");
}
};
AssetFilter AUDIO_FILTER = new AssetFilter() {
public boolean accept(String file, boolean isDirectory) {
return isDirectory || file.matches(".*\\.(ogg|mp3|wav|m4a)$");
}
};
AssetFilter TEXT_FILTER = new AssetFilter() {
public boolean accept(String file, boolean isDirectory) {
return isDirectory || file.matches(".*\\.(txt|json|xml|csv)$");
}
};
}public class DefaultAssetFilter implements AssetFilter {
public boolean accept(String file, boolean isDirectory) {
if (isDirectory) return true;
// Accept common game asset formats
return file.matches(".*\\.(png|jpg|jpeg|gif|bmp|ogg|mp3|wav|m4a|txt|json|xml|fnt|pack|atlas)$");
}
}public class FileWrapper {
// File information
public String path;
public FileType type;
public long length;
public boolean isDirectory;
// File content access
public String read();
public String readString();
public byte[] readBytes();
public long length();
// Constructors
public FileWrapper(String path, FileType type);
public FileWrapper(String path, FileType type, long length, byte[] data);
}public class MyGameGwt extends GwtApplication {
@Override
public GwtApplicationConfiguration getConfig() {
return new GwtApplicationConfiguration(800, 600);
}
@Override
public ApplicationListener createApplicationListener() {
return new MyGame();
}
@Override
public Preloader createPreloader() {
// Create preloader with assets list file
return new Preloader("assets.txt");
}
@Override
public PreloaderCallback getPreloaderCallback() {
return new PreloaderCallback() {
@Override
public void update(PreloaderState state) {
// Update loading progress display
float progress = state.getProgress();
updateLoadingScreen(progress);
System.out.println("Loading: " + (int)(progress * 100) + "%");
}
@Override
public void error(String file) {
System.err.println("Failed to load asset: " + file);
}
};
}
private void updateLoadingScreen(float progress) {
// Update your custom loading screen UI
// This runs before your ApplicationListener.create() is called
}
}The assets.txt file lists all assets to be preloaded:
# Asset list for preloader
# Format: path:type:size (size is optional)
# Images
images/player.png:i
images/enemy.png:i
images/background.jpg:i
images/ui/button.png:i
# Audio files
audio/background.ogg:a
audio/jump.wav:a
audio/explosion.ogg:a
# Text/data files
data/levels.json:t
data/config.txt:t
fonts/arial.fnt:t
# Binary files
data/terrain.bin:b
# Type codes:
# i = image
# a = audio
# t = text
# b = binarypublic class GameAssetGwt extends GwtApplication {
@Override
public Preloader createPreloader() {
return new Preloader() {
@Override
protected AssetFilter getAssetFilter() {
return new AssetFilter() {
@Override
public boolean accept(String file, boolean isDirectory) {
if (isDirectory) return true;
// Only preload essential assets
if (file.startsWith("essential/")) return true;
if (file.endsWith(".png") || file.endsWith(".jpg")) return true;
if (file.endsWith(".ogg") || file.endsWith(".wav")) return true;
if (file.endsWith(".json") || file.endsWith(".fnt")) return true;
return false;
}
};
}
};
}
}public class LoadingScreenGwt extends GwtApplication {
private ProgressBar progressBar;
private Label statusLabel;
@Override
public PreloaderCallback getPreloaderCallback() {
return new PreloaderCallback() {
@Override
public void update(PreloaderState state) {
float progress = state.getProgress();
long loaded = state.loaded;
long total = state.total;
// Update progress bar
if (progressBar != null) {
progressBar.setValue(progress);
}
// Update status text
if (statusLabel != null) {
String status = String.format("Loading... %d/%d KB (%.1f%%)",
loaded / 1024, total / 1024, progress * 100);
statusLabel.setText(status);
}
// Custom loading stages
if (progress < 0.3f) {
setLoadingStatus("Loading textures...");
} else if (progress < 0.6f) {
setLoadingStatus("Loading audio...");
} else if (progress < 0.9f) {
setLoadingStatus("Loading data...");
} else {
setLoadingStatus("Initializing game...");
}
}
@Override
public void error(String file) {
System.err.println("Loading failed for: " + file);
setLoadingStatus("Error loading " + file);
}
};
}
private void setLoadingStatus(String status) {
System.out.println(status);
// Update your loading screen UI
}
}public class RobustPreloaderGwt extends GwtApplication {
private Set<String> failedAssets = new HashSet<>();
private int retryCount = 0;
private static final int MAX_RETRIES = 3;
@Override
public PreloaderCallback getPreloaderCallback() {
return new PreloaderCallback() {
@Override
public void update(PreloaderState state) {
if (state.hasEnded) {
if (!failedAssets.isEmpty() && retryCount < MAX_RETRIES) {
// Retry failed assets
retryCount++;
System.out.println("Retrying failed assets (attempt " + retryCount + ")");
retryFailedAssets();
} else {
// Proceed with missing assets or after max retries
proceedWithAvailableAssets();
}
}
}
@Override
public void error(String file) {
failedAssets.add(file);
System.err.println("Failed to load: " + file);
}
};
}
private void retryFailedAssets() {
// Create new preloader for failed assets only
Preloader retryPreloader = new Preloader();
for (String failedAsset : failedAssets) {
// Add failed assets to retry queue
// Implementation depends on your specific needs
}
}
private void proceedWithAvailableAssets() {
if (failedAssets.isEmpty()) {
System.out.println("All assets loaded successfully");
} else {
System.out.println("Proceeding with " + failedAssets.size() + " missing assets");
// Log missing assets for debugging
for (String missing : failedAssets) {
System.out.println("Missing: " + missing);
}
}
// Continue to game initialization
}
}// Organize assets for efficient preloading
public class AssetOrganizer {
// Group assets by loading priority
private static final String[] CRITICAL_ASSETS = {
"images/loading_bg.png", // Loading screen background
"images/progress_bar.png", // Progress bar graphics
"fonts/ui_font.fnt" // UI font
};
private static final String[] ESSENTIAL_ASSETS = {
"images/player.png",
"images/ui/buttons.pack",
"audio/ui_sounds.ogg"
};
private static final String[] GAMEPLAY_ASSETS = {
"images/enemies.pack",
"images/levels.pack",
"audio/music.ogg",
"data/levels.json"
};
public static void validateAssetStructure() {
// Verify critical assets are available
for (String asset : CRITICAL_ASSETS) {
if (!Gdx.files.internal(asset).exists()) {
throw new RuntimeException("Critical asset missing: " + asset);
}
}
// Check essential assets
int missingEssential = 0;
for (String asset : ESSENTIAL_ASSETS) {
if (!Gdx.files.internal(asset).exists()) {
System.err.println("Essential asset missing: " + asset);
missingEssential++;
}
}
if (missingEssential > 0) {
System.out.println("Warning: " + missingEssential + " essential assets missing");
}
}
}public class AssetOptimizer {
private static final long MAX_TOTAL_SIZE = 50 * 1024 * 1024; // 50MB limit
private static final long MAX_SINGLE_FILE = 5 * 1024 * 1024; // 5MB per file
public static void analyzeAssetSizes() {
long totalSize = 0;
int oversizedFiles = 0;
String[] assetDirs = {"images/", "audio/", "data/"};
for (String dir : assetDirs) {
FileHandle dirHandle = Gdx.files.internal(dir);
if (dirHandle.exists() && dirHandle.isDirectory()) {
for (FileHandle file : dirHandle.list()) {
long fileSize = file.length();
totalSize += fileSize;
if (fileSize > MAX_SINGLE_FILE) {
System.out.println("Oversized file: " + file.path() +
" (" + (fileSize / 1024) + " KB)");
oversizedFiles++;
}
}
}
}
System.out.println("Total asset size: " + (totalSize / 1024 / 1024) + " MB");
System.out.println("Oversized files: " + oversizedFiles);
if (totalSize > MAX_TOTAL_SIZE) {
System.out.println("WARNING: Total assets exceed recommended size limit");
}
}
public static void generateOptimizedAssetsList() {
// Create optimized assets.txt based on file sizes and importance
StringBuilder assetsList = new StringBuilder();
assetsList.append("# Optimized assets list\n");
assetsList.append("# Generated automatically\n\n");
// Add critical assets first
assetsList.append("# Critical assets (loaded first)\n");
addAssetsToList(assetsList, "critical/", "i");
// Add essential assets
assetsList.append("\n# Essential gameplay assets\n");
addAssetsToList(assetsList, "essential/", "i");
// Add optional assets
assetsList.append("\n# Optional enhancement assets\n");
addAssetsToList(assetsList, "optional/", "i");
// Write to file
Gdx.files.local("generated_assets.txt").writeString(assetsList.toString(), false);
}
private static void addAssetsToList(StringBuilder list, String directory, String type) {
FileHandle dir = Gdx.files.internal(directory);
if (dir.exists() && dir.isDirectory()) {
for (FileHandle file : dir.list()) {
if (!file.isDirectory()) {
list.append(file.path()).append(":").append(type).append("\n");
}
}
}
}
}// For very large games, implement lazy loading after initial preload
public class LazyAssetLoader {
private Map<String, Boolean> loadedChunks = new HashMap<>();
private AssetDownloader downloader = new AssetDownloader();
public void loadChunk(String chunkName, Runnable onComplete) {
if (loadedChunks.getOrDefault(chunkName, false)) {
// Chunk already loaded
if (onComplete != null) onComplete.run();
return;
}
String[] chunkAssets = getChunkAssets(chunkName);
loadAssetsBatch(chunkAssets, () -> {
loadedChunks.put(chunkName, true);
System.out.println("Chunk loaded: " + chunkName);
if (onComplete != null) onComplete.run();
});
}
private String[] getChunkAssets(String chunkName) {
switch (chunkName) {
case "level1":
return new String[]{"images/level1_bg.png", "audio/level1_music.ogg"};
case "level2":
return new String[]{"images/level2_bg.png", "audio/level2_music.ogg"};
case "powerups":
return new String[]{"images/powerups.pack", "audio/powerup_sfx.ogg"};
default:
return new String[0];
}
}
private void loadAssetsBatch(String[] assets, Runnable onComplete) {
// Implementation for batch loading assets after initial preload
// This would use HTTP requests to download additional assets
System.out.println("Loading " + assets.length + " additional assets...");
// Simulate async loading
Timer.schedule(new Timer.Task() {
@Override
public void run() {
if (onComplete != null) onComplete.run();
}
}, 1.0f); // 1 second delay simulation
}
}// The preloader works with browser caching for optimal performance
public class CacheAwarePreloader extends Preloader {
public CacheAwarePreloader(String assetsFile) {
super(assetsFile);
// Assets are automatically cached by browser
// Subsequent loads will be much faster
// Use cache-busting for asset updates
}
// Generate cache-busting URLs for asset updates
private String addCacheBuster(String assetUrl, String version) {
return assetUrl + "?v=" + version;
}
}// Preloader manages memory efficiently for web deployment
public class MemoryEfficientPreloader {
public static void optimizeForMemory() {
// The GWT preloader automatically:
// 1. Streams large files instead of loading entirely into memory
// 2. Compresses text assets
// 3. Uses efficient binary formats
// 4. Releases intermediate loading buffers
System.out.println("Preloader optimized for web memory constraints");
}
public static void monitorMemoryUsage() {
// Check available memory during preloading
long heapSize = Runtime.getRuntime().totalMemory();
long usedMemory = heapSize - Runtime.getRuntime().freeMemory();
System.out.println("Memory usage: " + (usedMemory / 1024 / 1024) + " MB");
if (usedMemory > heapSize * 0.8) {
System.out.println("WARNING: High memory usage during asset loading");
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-badlogicgames-gdx--gdx-backend-gwt