Resource System¶
Heaps provides a powerful resource system that automatically imports and manages your game assets. Understanding this system is crucial for efficient asset management.
How It Works¶
Place assets in the res/
folder, and Heaps makes them available through hxd.Res
:
res/
├── sprites/
│ └── player.png → hxd.Res.sprites.player
├── sounds/
│ └── jump.wav → hxd.Res.sounds.jump
├── fonts/
│ └── pixel.fnt → hxd.Res.fonts.pixel
└── data/
└── levels.json → hxd.Res.data.levels
Basic Usage¶
Initialize Resources¶
class Main extends hxd.App {
override function init() {
// Required for embedded resources
hxd.Res.initEmbed();
// Now access resources
var playerSprite = hxd.Res.sprites.player.toTile();
}
}
Build Configuration¶
Add to your build.hxml
:
Image Resources¶
Loading Images¶
// Get as texture
var texture = hxd.Res.sprites.player.toTexture();
// Get as tile (more common)
var tile = hxd.Res.sprites.player.toTile();
// Create bitmap
var bitmap = new h2d.Bitmap(tile, s2d);
// Get image info
trace(hxd.Res.sprites.player.entry.width);
trace(hxd.Res.sprites.player.entry.height);
Tile Manipulation¶
// Get full tile
var fullTile = hxd.Res.sprites.tileset.toTile();
// Create sub-tiles
var grassTile = fullTile.sub(0, 0, 16, 16);
var stoneTile = fullTile.sub(16, 0, 16, 16);
var waterTile = fullTile.sub(32, 0, 16, 16);
// Animation frames
var frames = [
fullTile.sub(0, 0, 32, 32),
fullTile.sub(32, 0, 32, 32),
fullTile.sub(64, 0, 32, 32),
];
Texture Atlas¶
// Load atlas
var atlas = hxd.Res.atlas.character.toTile();
// Define animations
var anims = new Map<String, Array<h2d.Tile>>();
anims["idle"] = [
atlas.sub(0, 0, 32, 32),
atlas.sub(32, 0, 32, 32),
atlas.sub(64, 0, 32, 32),
atlas.sub(96, 0, 32, 32),
];
anims["walk"] = [
atlas.sub(0, 32, 32, 32),
atlas.sub(32, 32, 32, 32),
atlas.sub(64, 32, 32, 32),
atlas.sub(96, 32, 32, 32),
];
Audio Resources¶
Sound Effects¶
// Play once
hxd.Res.sounds.jump.play();
// With volume
hxd.Res.sounds.explosion.play(0.5);
// Get sound resource for more control
var sound = hxd.Res.sounds.hit;
var channel = sound.play(true); // loop
channel.volume = 0.7;
channel.pause = true;
Music¶
class MusicManager {
static var current : hxd.snd.Channel;
public static function play(music:hxd.res.Sound, volume=1.0) {
if (current != null) {
current.stop();
}
current = music.play(true); // loop
current.volume = volume;
}
public static function fadeOut(duration:Float) {
if (current == null) return;
var start = current.volume;
var elapsed = 0.0;
Main.instance.updates.push((dt) -> {
elapsed += dt;
var t = elapsed / duration;
if (t >= 1) {
current.stop();
current = null;
return false;
}
current.volume = start * (1 - t);
return true;
});
}
}
// Usage
MusicManager.play(hxd.Res.music.theme);
Font Resources¶
Bitmap Fonts¶
// Load font
var font = hxd.Res.fonts.pixel.toFont();
// Create text
var text = new h2d.Text(font, s2d);
text.text = "Hello World!";
text.textColor = 0xFFFFFF;
text.scale(2);
// With drop shadow
text.dropShadow = {
dx: 2,
dy: 2,
color: 0x000000,
alpha: 0.5
};
Font Generation¶
Create .fnt
files from TrueType fonts:
Data Resources¶
JSON Data¶
// res/data/enemies.json
{
"goblin": {
"health": 50,
"speed": 100,
"damage": 10
},
"orc": {
"health": 100,
"speed": 80,
"damage": 20
}
}
// Load in game
var enemyData = hxd.Res.data.enemies.toText();
var enemies = haxe.Json.parse(enemyData);
// Typed access
typedef EnemyData = {
health: Int,
speed: Float,
damage: Int
}
var data:Map<String, EnemyData> = haxe.Json.parse(enemyData);
var goblin = data.get("goblin");
XML Data¶
// res/data/items.xml
var xml = Xml.parse(hxd.Res.data.items.toText());
for (item in xml.firstElement().elementsNamed("item")) {
var id = item.get("id");
var name = item.get("name");
var price = Std.parseInt(item.get("price"));
}
Custom Binary¶
// Save level data
var bytes = haxe.io.Bytes.alloc(1000);
// ... write data
sys.io.File.saveBytes("res/levels/level1.dat", bytes);
// Load in game
var levelData = hxd.Res.levels.level1.entry.getBytes();
Dynamic Loading¶
Load at Runtime¶
class DynamicLoader {
public static function loadTexture(path:String, onLoaded:h3d.mat.Texture->Void) {
hxd.res.Loader.currentInstance.load(path).then((res) -> {
var tex = res.toTexture();
onLoaded(tex);
});
}
public static function loadSound(path:String, onLoaded:hxd.res.Sound->Void) {
hxd.res.Loader.currentInstance.load(path).then((res) -> {
var sound = cast(res, hxd.res.Sound);
onLoaded(sound);
});
}
}
Hot Reload¶
#if debug
class HotReload {
static var watched : Map<String, Float> = [];
public static function watch(path:String, onChange:Void->Void) {
watched.set(path, getModTime(path));
// Check periodically
haxe.Timer.delay(() -> {
checkFile(path, onChange);
}, 1000);
}
static function checkFile(path:String, onChange:Void->Void) {
var modTime = getModTime(path);
if (modTime > watched.get(path)) {
watched.set(path, modTime);
hxd.Res.loader.cleanCache();
onChange();
}
haxe.Timer.delay(() -> checkFile(path, onChange), 1000);
}
}
#end
Resource Management¶
Preloading¶
class Preloader {
var toLoad : Array<String>;
var loaded = 0;
public var onProgress : Float->Void;
public var onComplete : Void->Void;
public function new(resources:Array<String>) {
toLoad = resources;
}
public function start() {
loadNext();
}
function loadNext() {
if (loaded >= toLoad.length) {
if (onComplete != null) onComplete();
return;
}
var path = toLoad[loaded];
hxd.Res.loader.load(path).then((_) -> {
loaded++;
if (onProgress != null) {
onProgress(loaded / toLoad.length);
}
loadNext();
});
}
}
// Usage
var preloader = new Preloader([
"sprites/enemies.png",
"sounds/battle.ogg",
"data/level1.json"
]);
preloader.onProgress = (p) -> trace('Loading: ${Math.round(p * 100)}%');
preloader.onComplete = () -> startGame();
preloader.start();
Memory Management¶
class ResourceCache {
static var textures : Map<String, h3d.mat.Texture> = [];
static var sounds : Map<String, hxd.res.Sound> = [];
public static function getTexture(path:String) : h3d.mat.Texture {
if (!textures.exists(path)) {
textures.set(path, hxd.Res.loader.load(path).toTexture());
}
return textures.get(path);
}
public static function clearTextures() {
for (tex in textures) {
tex.dispose();
}
textures.clear();
}
public static function removeTexture(path:String) {
var tex = textures.get(path);
if (tex != null) {
tex.dispose();
textures.remove(path);
}
}
}
Asset Pipeline¶
Image Optimization¶
Create res/.heaps
configuration:
{
"convert": {
"format": "png",
"quality": 90,
"alpha": true
},
"props": {
"sprites": {
"filter": false,
"wrap": "clamp"
}
}
}
Automatic Atlas Packing¶
// Use texture packer tool
// Outputs: atlas.png + atlas.xml
// Load in code
var atlas = new hxd.res.Atlas(hxd.Res.sprites.atlas);
var tile = atlas.get("player_idle");
Best Practices¶
1. Resource Organization¶
res/
├── sprites/
│ ├── player/
│ ├── enemies/
│ └── environment/
├── audio/
│ ├── sfx/
│ └── music/
├── fonts/
├── data/
│ ├── levels/
│ └── config/
└── shaders/
2. Lazy Loading¶
class Assets {
static var _playerTile : h2d.Tile;
public static var playerTile(get, never) : h2d.Tile;
static function get_playerTile() {
if (_playerTile == null) {
_playerTile = hxd.Res.sprites.player.toTile();
}
return _playerTile;
}
}
3. Error Handling¶
function loadResource(path:String) : hxd.res.Any {
try {
return hxd.Res.loader.load(path);
} catch (e:Dynamic) {
trace('Failed to load resource: $path');
return null;
}
}
Next Steps¶
Understand the Entity System → to create game objects with Heaps.