Art, Sound, Models, and the Pipeline That Puts Them in Your Game
2026-11-04
The loop deck ended on feedback — what the game shows, sounds, and feels back. Almost all of that feedback is assets.
Assets do three jobs, and they are not the same job:
A game with placeholder art can be legible, tonally clear, and juicy. A game with beautiful art can be none of those. Assets are not decoration — they are the interface.
| Family | What it includes | Job in the game | Typical format |
|---|---|---|---|
| 2D art | sprites, sheets, tilesets, backgrounds | the visible world | PNG |
| 3D art | meshes, materials, texture maps, props | the visible world, with depth | glTF / GLB, FBX |
| Animation | sprite frames, skeletal clips, blends | motion and feedback | PNG sheets, GLB clips |
| Audio | SFX, ambience, music, voice | feedback and tone | OGG, WAV, MP3 |
| Text | fonts, dialogue, localization | legibility and voice | TTF/OTF, JSON, CSV |
| Data | levels, tilemaps, atlases, configs | structure and content | JSON, YAML, TMX |
| Shaders + video | effects, cutscenes, menus | look and spectacle | GLSL/WGSL, WebM |
The families have different pipelines, but the same questions: where does it come from, what does it cost, and can you replace it?

A sheet is just an image; the layout is the contract.
| Grid sheet | Atlas | |
|---|---|---|
| Frame rects | computed from a cell size | listed explicitly |
| Ships with | nothing extra | a sidecar file |
| Example | the tileset: 70×70 cells, 2 px margin | the player sheet: a .txt of rects |
Kenney ships a text sidecar, one line per frame:
p1_walk01 = 0 0 72 97
p1_walk02 = 73 0 72 97
p1_walk03 = 146 0 72 97
Sidecar formats vary: TexturePacker ships JSON, Starling ships XML, Tiled ships TMX. Encodings vary too — this pack’s JSON is UTF-16, so JSON.parse chokes on the BOM until you strip it.

Autotiling — choosing the right edge and corner tile from its neighbours — is your job. The engine gives you a tile grid, not a terrain solver.

Scale and units are a contract. glTF is metres, Y-up; FBX is often centimetres, Z-up. Import at the wrong scale and your hero is 100× too big — or too small to see.
The 3D pipeline: model → UV → texture → rig → animate → export → import. Every handoff is a file, and every handoff can lose something.
Cost multiplies: characters × clips × blends × LODs. A 3D game with ten characters and forty clips is a different production problem from a 2D game with one sprite sheet.
Texture memory is the quiet budget. A 2048×2048 RGBA texture is ~16 MB uncompressed; mipmaps add about a third. Ten of them is a phone-sized problem.
Our engine is 2D: a 3D model has to become a sprite sheet (pre-rendered) or move to a 3D renderer. Decide which before you fall in love with a model.
| Role | Length | Count | Job |
|---|---|---|---|
| SFX | 50 ms – 2 s | dozens | feedback: the hit, the jump, the pickup |
| UI | 20 – 200 ms | a handful | confirmation: the click, the hover |
| Ambience | 10 s – 2 min loop | one per area | place: wind, crowd, dripping water |
| Music | 1 – 4 min loop | one per scene | tone and pacing |
| Voice | 1 – 30 s | story-dependent | character and story |
Memory is the real constraint. 44.1 kHz stereo 16-bit is about 176 KB per second — roughly 10 MB per minute. Stream music; preload SFX.
Not all assets are pictures or sound. The rest are files the game reads:
Data is content, so it needs the same discipline as art: a schema, a validator, and a version. A level that crashes the game is a bad asset, not a bad player.
1 · Find
search, tags, license filter
2 · License
credit? share-alike?
3 · Inspect
open the zip, measure the grid
4 · Organize
source vs runtime, credits
5 · Import
load once, at startup
6 · Configure
anchor, scale, filtering
7 · Integrate
animate, tile, mix
8 · Playtest
does it read? does it fit?
Playtest sends you back to Find. A pipeline is a loop — and this is a loop deck.
Every step has a failure mode: the wrong license, a missing file, the wrong grid, the wrong path, the wrong scale.
The pipeline’s real job is to make an asset cheap to replace. If swapping a sprite is a two-minute change, you can iterate on art. If it is a two-hour change, the art freezes — and so does the design.
| Source | Good for | Watch out for |
|---|---|---|
| OpenGameArt | 2D, 3D, and audio; mixed licenses | quality varies; check each submission |
| Kenney | coherent CC0 packs; prototyping | a recognisable house style |
| itch.io packs | cheap, stylish indie art | per-pack licenses; often paid |
| Sketchfab, Poly Pizza, Quaternius | 3D models and kits | CC terms vary; attribution |
| freesound | SFX and ambience | quality varies; license per file |
| Commission or make it | fits your game exactly | costs money or time |
AI-generated assets are fast placeholders, but provenance and licensing are unsettled. Check the tool’s terms and the course policy before shipping one.
Free does not mean coherent. The cheapest path to a good-looking game is usually one pack that fits, not five that do not.
| License | Credit? | Share-alike? | What it means for your game |
|---|---|---|---|
| CC0 | no | no | Do anything. Credit as a courtesy. |
| CC-BY 3.0 / 4.0 | yes | no | Credit, link, and say what you changed. |
| CC-BY-SA 3.0 / 4.0 | yes | yes | Your changes to the art stay CC-BY-SA. |
| OGA-BY 3.0 / 4.0 | yes | no | CC-BY without the anti-DRM clause. |
| GPL 2.0 / 3.0 | yes | yes | Avoid unless the whole project is GPL. |
CC0 is the easy button. CC-BY is the normal case. CC-BY-SA and GPL are the ones that can bite.
For every asset, record five things: title, author, license, URL, and what you changed.
On an OpenGameArt page, that is:
| Field | What you take from it |
|---|---|
| Author | the name in your credits |
| License(s) | what you may do; multiple means choose one |
| Copyright / Attribution Notice | the artist’s exact credit instructions |
| Files | what you actually get — the preview is not always in the zip |
Save the page — as a PDF, a screenshot, or an archived link. Pages disappear and packs get re-uploaded. Write the credit line when you download, not at the end of the project.
Platformer Art Complete Pack/
license.txt <- the license, in the pack
Base pack/
preview.png
Player/
p1_spritesheet.png <- the sheet
p1_spritesheet.txt <- the frame rects
p1_walk/p1_walk.json <- an atlas (UTF-16)
p1_walk/PNG/p1_walk01.png <- individual frames
Vector/characters.svg <- source art
Tiles/tiles_spritesheet.png
Extra animations and enemies/
...
Thumbs.db <- junk; don't commit it
Ask of every pack: does the preview match the files? What is the frame size? Is there alpha? What is the tile size? Is there a README or license file? What is junk?
assets/source/ <- masters; never served
character.blend ui.psd jump.wav
public/assets/ <- runtime; served at /assets/
sprites/ player.png enemy.png
tiles/ platformer.png
models/ dungeon.glb
audio/ jump.ogg theme.ogg
ui/ heart.png
CREDITS.md
.blend; you ship a .glb. You edit a .wav; you ship an .ogg.public/ is served at the site root: public/assets/x.png becomes assets/x.png in your code.player-run.png, not p1_spritesheet (1).png.The test: a teammate should be able to guess where a file lives, and find the license, without asking you.
The asset you author is not the asset you ship.
| Asset | Authored as | Shipped as | Cost at runtime |
|---|---|---|---|
| Sprite | PSD, Aseprite | PNG, atlas | 4 bytes/pixel in VRAM |
| Texture | PSD, 4K | PNG/KTX2, 1K | 1024² ≈ 4 MB; 2048² ≈ 16 MB |
| Model | .blend, .ma | GLB + Draco | triangles, draw calls |
| Audio | WAV, 48 kHz | OGG | ~10 MB per stereo minute |
| Level | editor scene | JSON/TMX | parse time, memory |


Consistency beats quality. A coherent simple style reads better than a mismatched fancy one — in 2D and in 3D.
| Family | Minimum for a small game | Multiplies when you add |
|---|---|---|
| 2D art | player, one enemy, tileset, background | enemies, biomes, effects |
| 3D art | one character, one kit, props | rigs, LODs, lighting |
| Animation | idle, move, hit, die | blending, retargeting, cinematics |
| Audio | four SFX, one loop | ambience, voice, adaptive music |
| UI / text | health, menu, font | localization, accessibility |
| Data | one level format | editors, tools, live content |
That table is a complete small game’s worth of assets — and roughly one afternoon of integration if the pipeline is clean, or a week of fighting if it is not.
Better art will not save a bad loop. A good loop survives placeholder art.
This is the order the lab expects: game first, assets second, polish third.
OGA’s recommended credit line:
“Asset Name” by Author, licensed CC-BY 3.0: https://opengameart.org/content/…
CREDITS.md and on a credits screen. The license is a promise you keep in the build.The artist is a person. A credit and a thank-you cost nothing — and the open-asset ecosystem runs on visible credit.
const sheetImage = new ex.ImageSource('assets/sprites/player.png');
const loader = new ex.Loader([sheetImage]);
const game = new ex.Engine({ width: 960, height: 540, pixelArt: true });
game.start(loader).then(() => {
const sheet = ex.SpriteSheet.fromImageSource({
image: sheetImage,
grid: { rows: 4, columns: 4, spriteWidth: 32, spriteHeight: 32 },
});
const run = ex.Animation.fromSpriteSheet(
sheet, [0, 1, 2, 3], 100, ex.AnimationStrategy.Loop
);
player.graphics.use(run, { anchor: ex.vec(0.5, 1) });
});The loader is a gate: nothing draws until every resource resolves. getSprite(x, y) is (column, row), while Animation.fromSpriteSheet takes row-major indices — mixing them up is the classic bug.
Irregular atlas? Build the frames yourself: new ex.Animation({ frames: sprites.map(g => ({ graphic: g })), frameDuration: 100 }).
const tiles = ex.SpriteSheet.fromImageSource({
image: tilesImage,
grid: {
rows: 13, columns: 12,
spriteWidth: 70, spriteHeight: 70,
},
spacing: { margin: { x: 2, y: 2 } },
});
const map = new ex.TileMap({
tileWidth: 70, tileHeight: 70,
rows: 15, columns: 40,
});
for (let y = 0; y < level.length; y++) {
for (let x = 0; x < level[y].length; x++) {
const i = level[y][x];
const tile = map.getTile(x, y);
if (!tile || i === 0) continue;
const col = i % 12;
const row = Math.floor(i / 12);
tile.addGraphic(tiles.getSprite(col, row));
tile.solid = SOLIDS.has(i);
}
}
scene.add(map);Do this
public/assets/.ex.Loader; replace the placeholder player with an animated sprite.ex.TileMap.CREDITS.md with the exact license lines.Done means
pnpm dev and pnpm build both workCommon failures: wrong grid → sliced sprites · wrong path → invisible actor · wrong anchor → floating player · no gesture → silence · missing credit → an academic honesty problem, not a style note.
Exercise: before the lab, write the credit line for the pack you intend to use — name, author, license, URL. If you cannot write it, you have not finished choosing.
GD 2000 · Game Assets