Custom Paintings
Paintings in LCE are entities that hang on walls. Each painting has a “motive” that controls what image it shows and how big it is. This guide covers how the whole system works and how to add your own paintings.
How Paintings Work
Section titled “How Paintings Work”The painting system has a few moving parts:
- Motive - Defines the painting’s name, pixel size, and position on the texture atlas
- Painting entity - A
HangingEntitythat holds a reference to one motive - Placement logic - When a player places a painting, the game figures out which motives fit the available wall space and picks one at random
- Renderer - Reads the motive’s UV offsets and draws the right chunk of the texture atlas
The class hierarchy looks like this:
Entity└── HangingEntity # Wall-mounted entities (direction, survives check) ├── Painting # The painting entity └── ItemFrame # Item frames use the same baseThe Motive Class
Section titled “The Motive Class”Painting::Motive is a small inner class defined in Minecraft.World/Painting.h. Each motive has five fields:
class Motive{public: static const Motive *values[]; static const int MAX_MOTIVE_NAME_LENGTH;
const wstring name; // motive name, saved to NBT const int w, h; // pixel dimensions (16 = 1 block) const int uo, vo; // UV offset into the texture atlas
Motive(wstring name, int w, int h, int uo, int vo) : name(name), w(w), h(h), uo(uo), vo(vo) {};};The w and h values are in pixels, not blocks. So a 1x1 block painting is 16, 16 and a 4x4 block painting is 64, 64.
The uo and vo values tell the renderer where to find this painting’s image on the 256x256 texture atlas (art/kz.png).
All 26 Vanilla Motives
Section titled “All 26 Vanilla Motives”There are exactly 26 motives registered in the Motive::values[] array. The MotiveEnum in Painting.h lists them all with a LAST_VALUE sentinel at the end.
1x1 Paintings (16x16 pixels)
Section titled “1x1 Paintings (16x16 pixels)”7 paintings that fit in a single block space.
| Name | UV Offset (uo, vo) | Atlas Position |
|---|---|---|
Kebab | 0, 0 | Row 0, column 0 |
Aztec | 16, 0 | Row 0, column 1 |
Alban | 32, 0 | Row 0, column 2 |
Aztec2 | 48, 0 | Row 0, column 3 |
Bomb | 64, 0 | Row 0, column 4 |
Plant | 80, 0 | Row 0, column 5 |
Wasteland | 96, 0 | Row 0, column 6 |
2x1 Paintings (32x16 pixels)
Section titled “2x1 Paintings (32x16 pixels)”5 paintings that are 2 blocks wide and 1 block tall.
| Name | UV Offset (uo, vo) | Atlas Position |
|---|---|---|
Pool | 0, 32 | Row 2, column 0 |
Courbet | 32, 32 | Row 2, column 2 |
Sea | 64, 32 | Row 2, column 4 |
Sunset | 96, 32 | Row 2, column 6 |
Creebet | 128, 32 | Row 2, column 8 |
1x2 Paintings (16x32 pixels)
Section titled “1x2 Paintings (16x32 pixels)”2 paintings that are 1 block wide and 2 blocks tall.
| Name | UV Offset (uo, vo) | Atlas Position |
|---|---|---|
Wanderer | 0, 64 | Row 4, column 0 |
Graham | 16, 64 | Row 4, column 1 |
2x2 Paintings (32x32 pixels)
Section titled “2x2 Paintings (32x32 pixels)”6 paintings that fit in a 2x2 block space.
| Name | UV Offset (uo, vo) | Atlas Position |
|---|---|---|
Match | 0, 128 | Row 8, column 0 |
Bust | 32, 128 | Row 8, column 2 |
Stage | 64, 128 | Row 8, column 4 |
Void | 96, 128 | Row 8, column 6 |
SkullAndRoses | 128, 128 | Row 8, column 8 |
Wither | 160, 128 | Row 8, column 10 |
4x2 Paintings (64x32 pixels)
Section titled “4x2 Paintings (64x32 pixels)”1 painting that is 4 blocks wide and 2 blocks tall.
| Name | UV Offset (uo, vo) | Atlas Position |
|---|---|---|
Fighters | 0, 96 | Row 6, column 0 |
4x3 Paintings (64x48 pixels)
Section titled “4x3 Paintings (64x48 pixels)”2 console-exclusive paintings that are 4 blocks wide and 3 blocks tall.
| Name | UV Offset (uo, vo) | Atlas Position |
|---|---|---|
Skeleton | 192, 64 | Row 4, column 12 |
DonkeyKong | 192, 112 | Row 7, column 12 |
4x4 Paintings (64x64 pixels)
Section titled “4x4 Paintings (64x64 pixels)”3 paintings that fit in a 4x4 block space.
| Name | UV Offset (uo, vo) | Atlas Position |
|---|---|---|
Pointer | 0, 192 | Row 12, column 0 |
Pigscene | 64, 192 | Row 12, column 4 |
BurningSkull | 128, 192 | Row 12, column 8 |
Size Summary
Section titled “Size Summary”| Size (blocks) | Size (pixels) | Count | Paintings |
|---|---|---|---|
| 1x1 | 16x16 | 7 | Kebab, Aztec, Alban, Aztec2, Bomb, Plant, Wasteland |
| 2x1 | 32x16 | 5 | Pool, Courbet, Sea, Sunset, Creebet |
| 1x2 | 16x32 | 2 | Wanderer, Graham |
| 2x2 | 32x32 | 6 | Match, Bust, Stage, Void, SkullAndRoses, Wither |
| 4x2 | 64x32 | 1 | Fighters |
| 4x3 | 64x48 | 2 | Skeleton, DonkeyKong |
| 4x4 | 64x64 | 3 | Pointer, Pigscene, BurningSkull |
| Total | 26 |
How Placement Picks a Motive
Section titled “How Placement Picks a Motive”When a player right-clicks a wall with a painting item, HangingEntityItem::useOn() creates a Painting and calls PaintingPostConstructor(). This is where the magic happens:
void Painting::PaintingPostConstructor(int dir){ vector<Motive *> *survivableMotives = new vector<Motive *>(); for (int i = 0; i < LAST_VALUE; i++) { this->motive = (Motive *)Motive::values[i]; setDir(dir); if (survives()) { survivableMotives->push_back(this->motive); } } if (!survivableMotives->empty()) { this->motive = survivableMotives->at( random->nextInt((int)survivableMotives->size()) ); } setDir(dir);}It loops through every motive (all 26 of them, from index 0 up to LAST_VALUE), temporarily assigns it, calls setDir() to calculate the bounding box at that size, and then calls survives() to check if the painting actually fits. Every motive that fits gets added to a list, and then one is picked at random.
The Survives Check
Section titled “The Survives Check”HangingEntity::survives() in Minecraft.World/HangingEntity.cpp does two things:
- Solid wall check - Every block behind the painting (based on
w/16xh/16block grid) must be solid material - No entity overlap - The painting’s bounding box can’t overlap with any other
HangingEntity(no stacking paintings or overlapping item frames)
bool HangingEntity::survives(){ if (level->getCubes(shared_from_this(), bb)->size() != 0) { return false; } else { int ws = max(1, getWidth() / 16); int hs = max(1, getHeight() / 16); // ...checks every block position for solid material... // ...checks for overlapping HangingEntities... } return true;}So if you have a 3-block wide, 2-block tall wall of solid blocks, the game will only offer motives that are 3x2 blocks or smaller.
The Texture Atlas
Section titled “The Texture Atlas”All painting textures live in one 256x256 atlas file loaded as art/kz.png (referenced in code as TN_ART_KZ). The renderer in PaintingRenderer.cpp binds this texture before drawing:
bindTexture(TN_ART_KZ); // loads art/kz.pngEach motive’s uo and vo tell the renderer which region of the atlas to sample. The UV math in the renderer divides by 256.0 to convert pixel offsets to texture coordinates:
float fu0 = (uo + w - (xs) * 16) / 256.0f;float fu1 = (uo + w - (xs + 1) * 16) / 256.0f;float fv0 = (vo + h - (ys) * 16) / 256.0f;float fv1 = (vo + h - (ys + 1) * 16) / 256.0f;The back side of every painting uses a fixed texture region at pixel column 192 (12 * 16) on the atlas. That is the brown “back of canvas” look.
Atlas Layout Map
Section titled “Atlas Layout Map”Here is what the 256x256 atlas looks like, by row:
Row 0 (vo=0): 7 small 1x1 paintings (columns 0-6) Column 12 (x=192): back-of-painting texture (RESERVED)Row 2 (vo=32): 5 wide 2x1 paintings (columns 0-8)Row 4 (vo=64): 2 tall 1x2 paintings (columns 0-1) + 2 console-exclusive 4x3s at x=192 (Skeleton, DonkeyKong)Row 6 (vo=96): 1 wide 4x2 painting (Fighters, columns 0-3)Row 8 (vo=128): 6 medium 2x2 paintings (columns 0-10)Row 12 (vo=192): 3 large 4x4 paintings (columns 0-11)Column 192 (x=192) row 0 is reserved for the back-of-painting texture. Don’t put art there.
Adding a New Painting
Section titled “Adding a New Painting”Let’s add a custom 2x2 painting called “MyArt”.
Step 1: Add the Enum Value
Section titled “Step 1: Add the Enum Value”In Minecraft.World/Painting.h, add your new motive to the MotiveEnum before LAST_VALUE:
enum MotiveEnum { Kebab = 0, Aztec, Alban, // ...existing motives... DonkeyKong,
MyArt, // your new painting
LAST_VALUE};LAST_VALUE is used as the loop bound in PaintingPostConstructor, so putting your entry before it is all you need to include it in placement.
Step 2: Register the Motive
Section titled “Step 2: Register the Motive”In Minecraft.World/Painting.cpp, add a new entry to the Motive::values[] array. The position in the array must match the enum order:
const _Motive *Painting::Motive::values[] = { // ...existing motives... new _Motive(L"DonkeyKong", 64, 48, 12 * 16, 7 * 16),
new _Motive(L"MyArt", 32, 32, 0 * 16, 10 * 16), // 2x2, at (0, 160) on atlas};The constructor args are: name, width, height, uo, vo.
Pick a uo/vo that points to an unused region of the atlas. The vanilla paintings leave some gaps you can use, or you can expand into empty space.
Step 3: Update MAX_MOTIVE_NAME_LENGTH If Needed
Section titled “Step 3: Update MAX_MOTIVE_NAME_LENGTH If Needed”If your motive name is longer than 13 characters (the length of “SkullAndRoses”), update the constant:
const int Painting::Motive::MAX_MOTIVE_NAME_LENGTH = 13;// Change to your name's length if it's longerThis is used by AddPaintingPacket when reading the motive name from the network. If your name is too long, it gets truncated and the painting won’t load right.
Step 4: Add the Texture
Section titled “Step 4: Add the Texture”Edit the art/kz.png texture atlas and draw your painting at the UV offset you picked. For a 2x2 painting (32x32 pixels), you need a 32x32 pixel region at your chosen offset.
Step 5: Build and Test
Section titled “Step 5: Build and Test”Place a painting on a wall that is at least 2x2 blocks. Keep breaking and replacing it until the game randomly picks your motive. Since selection is random from all motives that fit, you might need a few tries.
The Painting Entity
Section titled “The Painting Entity”The Painting class (Minecraft.World/Painting.h) extends HangingEntity. Here are the key pieces:
Construction
Section titled “Construction”There are three constructors:
// Basic - used by EntityIO when loading from save dataPainting(Level *level);
// Placement - used by HangingEntityItem, then call PaintingPostConstructorPainting(Level *level, int xTile, int yTile, int zTile, int dir);
// Specific motive - used by client when receiving AddPaintingPacketPainting(Level *level, int x, int y, int z, int dir, wstring motiveName);The third constructor searches through all motives by name to find a match. This is how the client creates the right painting when the server tells it which motive was selected.
Save/Load
Section titled “Save/Load”Paintings save their motive name as an NBT string tag called "Motive". On load, if the name doesn’t match any known motive, it falls back to Kebab:
void Painting::readAdditionalSaveData(CompoundTag *tag){ wstring motiveName = tag->getString(L"Motive"); for (int i = 0; i < LAST_VALUE; i++) { if (Motive::values[i]->name.compare(motiveName) == 0) { this->motive = (Motive *)Motive::values[i]; } } if (this->motive == NULL) motive = (Motive *)Motive::values[Kebab];
HangingEntity::readAdditionalSaveData(tag);}This fallback means if you remove a custom motive later, old worlds won’t crash. They’ll just show Kebab instead.
Networking
Section titled “Networking”The AddPaintingPacket sends the painting across the network with the motive name as a string, plus the tile position and direction. The client receives this in ClientConnection::handleAddPainting() and creates the entity using the name-based constructor:
void ClientConnection::handleAddPainting(shared_ptr<AddPaintingPacket> packet){ shared_ptr<Painting> painting = shared_ptr<Painting>( new Painting(level, packet->x, packet->y, packet->z, packet->dir, packet->motive) ); level->putEntity(packet->id, painting);}Rendering
Section titled “Rendering”PaintingRenderer in Minecraft.Client/PaintingRenderer.cpp handles drawing. It binds the art/kz.png atlas, reads the motive’s w, h, uo, vo, and tessellates a textured quad for each 16x16 block section of the painting. It also draws the back, top, bottom, and side faces using the back-of-canvas texture region.
The renderer iterates in a grid of 16x16 pixel chunks, so a 2x2 painting generates 4 quads for the front face (plus the back/edge quads for each).
Entity Registration
Section titled “Entity Registration”The painting entity is registered in EntityIO::staticCtor() with entity ID 9:
setId(Painting::create, eTYPE_PAINTING, L"Painting", 9);You don’t need to touch this when adding new motives. Entity registration is for the Painting entity type itself, not for individual motives.
- Odd sizes work - You are not limited to power-of-two sizes. The console-exclusive
SkeletonandDonkeyKongpaintings are 64x48 (4x3 blocks). You could make a 48x16 (3x1) painting if you wanted. - The 256px atlas limit - The atlas is 256x256 and the UV math divides by 256.0. If you need more space than the atlas has, you’d need to either change the atlas size and update the UV divisor in
PaintingRenderer, or replace existing paintings you don’t want. - Motive names must be unique - The name is used for NBT save/load and network sync. Duplicate names will cause the wrong painting to show up.
- Random selection is uniform - Every motive that fits has an equal chance of being picked. If you add 20 custom 1x1 paintings, the vanilla 1x1s become much rarer on small walls.
- The MAX_MOTIVE_NAME_LENGTH matters - The
AddPaintingPacketuses this as a buffer size when reading motive names over the network. If your name exceeds it, the name gets truncated and the client won’t find the motive.
Key Files
Section titled “Key Files”| File | What it does |
|---|---|
Minecraft.World/Painting.h | Motive class, MotiveEnum (26 entries + LAST_VALUE), Painting entity |
Minecraft.World/Painting.cpp | Motive::values[] array, PaintingPostConstructor, save/load |
Minecraft.World/HangingEntity.cpp | survives() check for wall solidity and entity overlap |
Minecraft.World/HangingEntityItem.cpp | Placement logic when player right-clicks wall |
Minecraft.Client/PaintingRenderer.cpp | Renders paintings using the kz.png atlas |
Minecraft.World/AddPaintingPacket.h/.cpp | Network packet for syncing paintings to clients |
art/kz.png | The 256x256 texture atlas containing all painting images |