Paint
Usage guide of custom.paint: colours and patterns on named parts of the character, buildings, vehicles and any paintable entity, palettes, locked patterns, presets, the paint screen, settings and defaults, service, events, hooks and verbs.
Paint and patterns (custom.paint) #
What it is for #
custom.paint lets the player make things their own: colours and patterns on the character, on buildings they placed, on the vehicle they drive, on any entity the game marks as paintable. A paintable thing has named parts (body, trim, roof…); a look per part (a colour, a pattern, a second colour) is kept on the entity and saved with it. Palettes and patterns come from your project, and patterns can start locked and be unlocked by progress. The player saves looks as presets and applies them elsewhere. A paint screen picks the thing faced, its part and a swatch. A model with a paint mask keeps its texture and takes the colour only on its paintable panels.
Turn it on #
Install the kit in a project and set its settings with the tools below; gripforge kit api custom.paint lists every setting, and gripforge docs custom-paint prints this guide offline.
gripforge_gamekit_install { project_id: "gkp_…", id: "custom.paint" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "custom.paint",
config: { "reach": 6, "playerParts": ["body", "hat"], "maxPresets": 20 } }
{ "id": "hut", "kind": "building", "components": { "paintable": { "parts": ["walls", "roof"] } } }
First example #
<!-- test: kits/game_engine/host/tests/parity.rs::rust_host_matches_the_typescript_traces (fixture custom.paint.looks) --> A hut with paintable walls and roof. A colour is accepted on a known part, a locked pattern is refused until unlockPattern reveals it, and an unknown part, an unknown pattern or a malformed colour is refused.
custom.paint.paint(hut, "walls", { color: "#AA0000" }) // ok
custom.paint.paint(hut, "walls", { pattern: "stripes", secondary: "#00AA00" }) // ok
custom.paint.paint(hut, "roof", { pattern: "camo" }) // refused: camo is locked
custom.paint.unlockPattern("camo") // paint.pattern_unlocked
custom.paint.paint(hut, "roof", { pattern: "camo" }) // ok, paint.changed { part: "roof" }
custom.paint.paint(hut, "door", { color: "#000000" }) // refused: not a part of the hut
Settings and defaults #
| Setting | Default | Meaning |
|---|---|---|
reach | 5 | the paint screen targets a paintable thing faced within this distance (metres, 1 to 30), else the player; it closes when the player walks further |
paintPlayer | true | {{s_paintPlayer}} |
playerParts | ["body"] | {{s_playerParts}} |
maxPresets | 12 | {{s_maxPresets}} |
Services #
custom.paint(service):palettes(),patterns(),paintables(),parts(entity),look(entity),paint(entity, part, { color?, pattern?, secondary? })answers{ ok, reason? },copy(from, to),unlockPattern(id),presets(),savePreset(name, look),applyPreset(entity, name),target(),open(entity?),close()andcurrent().entityis an id or"player".- Content:
paintableis a component{ parts: [...] }(default["body"]) that marks an entity paintable; a mesh named after a part takes that part's colour, every other mesh takes the body colour. Thepalettescollection holds{ id, name, colors }with colours as#rrggbb(1 to 48 swatches). Thepatternscollection holds{ id, name, texture?, unlocked? }:textureis a variant of thepaint_patternasset slot, a tileable texture, andunlocked: falselocks the pattern until it is unlocked. The look of each part is kept on the entity and saved with it.
Events #
paint.changed: a part of an entity got a new look (entity,part).paint.pattern_unlocked: a locked pattern became available (pattern).paint.opened(the paint screen opened onentity) andpaint.closed.
Hooks and modding #
paint.apply: a part is about to take a look (entity,part,color,pattern,secondary,sourceofpaint,copyorpreset), after the kit accepted the paint, once per part;colormay be changed to another#rrggbb, and cancelling keeps the old look and emits nopaint.changed(paintanswers the reason of the tap,refusedby default).paint.unlock: a locked pattern is about to become available (pattern); cancelled, it stays locked and nopaint.pattern_unlockedis emitted.
{ "id": "no_black_roofs", "hook": "paint.apply", "match": { "part": "roof", "color": "#000000" }, "cancel": true, "reason": "too_dark" }
Using it from content #
Script verbs: the actions paint_entity (entity as player or an id, part, color, pattern; defaults to the body of the player) and unlock_pattern (pattern). The input paint_menu (key KeyP) opens the paint screen. Variables: paint.open, paint.patterns.
{ "type": "paint_entity", "entity": "player", "part": "body", "color": "#c0392b" }
{ "unlock_pattern": "stripes" }
When it does not do what you expect #
| Symptom | Cause and fix |
|---|---|
paint answers ok: false | The entity is not paintable, the part is not one of its parts, the colour is not #rrggbb, the pattern is unknown or still locked, or a tap of paint.apply cancelled it. |
| A mesh does not take the colour of its part | Name the mesh after the part (roof, trim…); a mesh with no part name takes the body colour. A model with a paint mask is tinted only on its masked panels. |
| The paint screen targets the player, not the thing faced | The thing is further than reach or has no paintable component. |
Limits #
- Looks are cosmetic colours and patterns on materials, not new meshes or decals.
- The paint screen and the material tinting are web display features; on other targets the service, the verbs and the events work and the look is data your renderer applies.
- The kit does not decide who may paint what: put rules in a
paint.applytap.
Versions #
Kit custom.paint 1.2.0 (2026-10-10: the hooks paint.apply and paint.unlock; 1.1.0 added paint masks; 1.0.0 was the first release). It needs input.actions and game.engine ^1.