Skip to content

Textures

A texture is an image on the GPU. You reference one by project path, load it into a handle, and put that handle on a component — Sprite.texture, a material sampler, a bitmap font page. This page is about the image itself: what it can be, what you can set on it, and how to find out how big it is.

For sprites — the component that draws a texture — see Sprites. For the general asset system (refs, groups, the manifest), see Assets.

Extension Notes
.png The default for art with transparency.
.jpg / .jpeg No alpha channel; smaller for photographic art.
.webp Alpha + good compression. Decoded by the browser/runtime.
.gif First frame only — this is a still image, not an animation.
.bmp Uncompressed; works, but rarely what you want to ship.
.ktx2 Basis Universal, already GPU-compressed. What the cook step produces — you can also author one directly.

An .aseprite / .ase file is not a texture — importing one produces a sheet .png (plus a clip per tag) and that is what a scene references. See Sprite animation.

.png, .jpg and .webp are the ones to author in. A build’s cook step turns them into .ktx2 when compression is on (see Import settings).

assets.loadTexture(ref) resolves the ref, uploads the image, and returns the handle with the image’s pixel dimensions — this is the answer to “how do I get the width and height”:

import { defineSystem, Res, Assets } from 'esengine';
const measure = defineSystem([Res(Assets)], async (assets) => {
const tex = await assets.loadTexture('assets/textures/player.png');
tex.handle; // TextureHandle — what a component field takes
tex.width; // the image's pixel width
tex.height; // the image's pixel height
});

The width and height are the source image’s pixels, not the size anything draws at. A Sprite drawing that texture has its own size in world units, which is a separate number you can set to anything (it starts out matching — see Sizing a sprite to its texture).

Putting one on a sprite at its own pixel size:

import { defineSystem, Query, Mut, Res, Sprite, Assets } from 'esengine';
const showArt = defineSystem([Query(Mut(Sprite)), Res(Assets)], async (q, assets) => {
const tex = await assets.loadTexture('assets/textures/player.png');
for (const [entity, sprite] of q) {
sprite.texture = tex.handle;
sprite.size = { x: tex.width, y: tex.height }; // 1 image pixel = 1 world unit
}
});

A sprite placed in the editor already has its texture: the scene file named it, the loader resolved it, and your code never saw a TextureResult. To measure that one, ask the resource manager for the handle’s dimensions:

import { defineSystem, Query, Sprite } from 'esengine';
import { getTextureDimensions } from 'esengine/tilemap';
const measurePlaced = defineSystem([Query(Sprite)], (q) => {
for (const [entity, sprite] of q) {
const dims = getTextureDimensions(sprite.texture); // { width, height } | null
if (dims) { /* … */ }
}
});

It returns null until the texture is actually uploaded — a scene’s textures load asynchronously, so a system running on the first frame can legitimately see nothing.

loadTextureRaw(ref) uploads the image without the vertical flip loadTexture applies. Flipped and unflipped are genuinely different GPU objects, so they are cached separately. You want loadTexture unless you are feeding a pipeline that already expects bottom-up rows.

Import settings belong to the image, not to any entity using it. They are stored in the .meta file next to it and edited by selecting the image in the Content Browser — every sprite that uses the texture inherits them.

Setting Default What it does
Max Size 2048 Downscale cap applied at cook (a power of two). A source larger than this on its longest side is box-filtered down; a smaller source is untouched.
Compression Auto GPU compressed ships a KTX2 (Basis Universal) that stays compressed in video memory — about a quarter of the decoded image — and transcodes per device. Original image ships the file as it is: exact, and for flat art far smaller to download, but decoded in full on the GPU. Auto is GPU compressed for a texture a mesh draws (directly or through its material) and the original image for sprites and UI. A build does what this row says; it never swaps one for the other because a file came out smaller.
Compress Format uastc uastc — high quality, larger. etc1s — much smaller, lower quality (good for photographic art). Only used when the texture is GPU compressed.
Filter linear Sampling filter. nearest keeps pixel art crisp; linear smooths.
Wrap repeat How UVs outside [0,1] are addressed: repeat, clamp, mirror.
Premultiply Alpha false Multiply RGB by alpha at import.
sRGB Color true The image stores sRGB-encoded color (albedo, UI). Disable for authored-linear data — normal maps, masks. Only meaningful when the project renders in linear color.
9-Slice Border 0,0,0,0 Left/right/top/bottom border in texture pixels: where the image’s corners end. Authored once here, inherited by every UIVisual set to Sliced.

Select several images (ctrl/cmd-click, shift-click, or Select All) and the inspector edits all of them. A setting they already agree on shows its value; one they disagree on shows a dash — type into it and every selected image takes that value. Save writes only the settings you touched, so the ones each image had its own answer for are left alone. Sprites and textures share a schema and batch together; a selection that mixes in an audio clip or a model says so instead, since there is no field they all have.

The 9-slice border editor stays on a single selection — it draws one image, and there is no honest picture of forty.

Max Size, Compression and Compress Format can be overridden per platform — the tabs beside Default in Import Settings. This is the “encode once, transcode per GPU” pipeline: ship the desktop build a 2048 UASTC texture and the WeChat build a 1024 ETC1S one from the same source file. See Cooking & compression.

Sprite.size is in world units and deliberately independent of the image — that is what lets you scale art without touching the file. But nobody wants to type the image’s dimensions, so the editor fills them in:

  • Dragging an image from the Content Browser into the viewport spawns a sprite already sized to the image’s pixels.
  • Assigning a texture to an existing sprite sizes it to that texture too, and a later swap follows the new image.

It stops as soon as the number is yours: once you set size by hand, changing the texture leaves it alone. (Undo history shows the fit as its own Fit Sprite To Texture step, so you can keep a size and drop only the fit.)

In code there is no such helper: a Sprite given only a texture keeps its default size, so pass tex.width/tex.height yourself as shown above.

A sprite can draw part of a texture with uvOffset / uvScale, both normalized fractions of the whole image:

// The 32×32 frame at column 2, row 1 of a 256×256 sheet.
sprite.uvOffset = { x: 64 / 256, y: 32 / 256 };
sprite.uvScale = { x: 32 / 256, y: 32 / 256 };

For sheet animation, author a flipbook instead of computing these by hand — see Animation. For tile art, see Tilemaps.

Textures are the largest thing most 2D games hold, and they are reference-counted and evictable rather than resident forever. Budgets, the LRU, and reading actual usage are covered in Texture memory & budgets.

The short version: a texture nothing references can be evicted under memory pressure and is revived transparently on the next load of the same path.

  • Sprites — the component that draws a texture.
  • Assets — refs, groups, the manifest, lifetime.
  • Materials — binding textures to your own shaders.
  • UI — 9-slice images and UI sprites.