원클릭으로
playcanvas-asset-art
Replace, fetch, and process texture/image assets in static PlayCanvas templates
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Replace, fetch, and process texture/image assets in static PlayCanvas templates
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Three.js Polygon Streaming .xrg integration and debugging playbook for @polygon-streaming/web-player-threejs, including correct wrapper events, asset publishing, model fitting, and fallback replacement policy
Bundle entry for the exported VIVERSE PlayCanvas Toolkit workflow pack. Use when you want bundled VIVERSE PlayCanvas Toolkit skills, prompts, catalogs, and helper assets in one standalone package.
Collision detection patterns for Three.js games without physics engines
Protect third-party API keys by moving secret-bearing calls into VIVERSE Play Lambda scripts and keeping only non-secret user data in VIVERSE Storage.
Build or fix CPU racing drivers for Three.js or VIVERSE racing games. Use for waypoint loops, tile-to-route reconstruction, lap completion, recovery logic, and low-overhead debug workflows.
VIVERSE Login SDK integration for user authentication and SSO
| name | playcanvas-asset-art |
| description | Replace, fetch, and process texture/image assets in static PlayCanvas templates |
| prerequisites | ["PlayCanvas static template workspace","sips available (macOS)","Python PIL available"] |
| tags | ["playcanvas","texture","image","asset","art","symbol","overlay","sprite"] |
If the user wants different symbols/images on different colored endpoints:
Step 1: For each of the 6 colors, decide the source:
ls files/assets/symbol_library/ for pre-made PNGs:
bird, flame, star, heart, diamond, cross, moon, lightning, circle_ring, trianglecpStep 2a: Copy from library (for symbols that exist):
cp files/assets/symbol_library/bird.png files/assets/283500002/1/endpoint_blue.png
cp files/assets/symbol_library/bird.png files/assets/283500012/1/block_blue.png
Step 2b: Generate with PIL (for symbols NOT in library):
python3 - <<'PY'
from PIL import Image, ImageDraw
SZ = 256
BG = (153, 153, 153, 255) # gray background
FG = (255, 255, 255, 255) # white foreground (symbol)
img = Image.new('RGBA', (SZ, SZ), BG)
d = ImageDraw.Draw(img)
# Draw the requested shape — be creative and make it recognizable
# Example: tree (trunk + triangular canopy)
d.rectangle([118, 180, 138, 240], fill=FG) # trunk
d.polygon([(128, 30), (50, 180), (206, 180)], fill=FG) # canopy
img.save('files/assets/283500003/1/endpoint_green.png')
img.save('files/assets/283500013/1/block_green.png')
PY
Use gray
(153,153,153)background + white(255,255,255)foreground. White areas = full color brightness, gray areas = dimmer (still colored).
Step 3: Verify — file files/assets/283500002/1/endpoint_blue.png → must say "PNG image data"
DONE. No config.json edits. No 2453710.json edits. No script patching. No downloading.
| Color | Endpoint path | Block path |
|---|---|---|
| red | files/assets/283500001/1/endpoint_red.png | files/assets/283500011/1/block_red.png |
| blue | files/assets/283500002/1/endpoint_blue.png | files/assets/283500012/1/block_blue.png |
| green | files/assets/283500003/1/endpoint_green.png | files/assets/283500013/1/block_green.png |
| yellow | files/assets/283500004/1/endpoint_yellow.png | files/assets/283500014/1/block_yellow.png |
| purple | files/assets/283500005/1/endpoint_purple.png | files/assets/283500015/1/block_purple.png |
| teal | files/assets/283500006/1/endpoint_teal.png | files/assets/283500016/1/block_teal.png |
| Symbol | Drawing approach |
|---|---|
| tree | Rectangle trunk + triangle polygon canopy |
| fish | Ellipse body + triangle tail polygon |
| cat | Circle head + triangle ears + dot eyes |
| house | Rectangle base + triangle roof polygon |
| car | Rectangle body + two circle wheels |
| crown | Rectangle base + three triangle points |
| flower | Central circle + ellipse petals around it |
| shield | Wide top arc + pointed bottom polygon |
Always save to BOTH the endpoint AND block path for each color.
textureBlock or textureEndpointCircle values in 2453710.json — these are GLOBAL defaults that affect ALL cells. Changing them breaks all obstacles or all endpoints.config.json asset entries for existing per-color assets — they are already registered with correct format and preload: true.__game-scripts.js for per-color — it's already patched with _getPerColorEndpointTexture()..png file — PlayCanvas requires real raster PNG data.2453710.json attribute values for per-color work — they're pre-wired.| In-game element | Frame type | User might call it |
|---|---|---|
| Start/end nodes (colored circles where paths begin/end) | endpoint_circle | "colored blocks", "color blocks", "start blocks", "end dots" |
| Obstacle cells (X-shaped, cannot be filled) | block | "cross", "X", "wall", "blocker" |
"color blocks" = start/end nodes, NOT obstacles. When user says "change color blocks", they mean the colored endpoint circles, not the X-shaped obstacles.
| Request | Technique |
|---|---|
| Change block/cell shape | Set shapeType enum in scene JSON |
| Replace a texture with a custom image | Overwrite files/assets/<id>/1/<filename> |
| Different texture per color | Copy from symbol library, or generate with PIL, to per-color asset paths |
| Fetch a symbol/icon from the web | curl from Iconify/Twemoji, then convert to PNG with sips |
| Resize/convert an image | sips (macOS built-in) or python3 PIL |
| Add a watermark or logo overlay | Composite with python3 PIL or sips |
| Change background/UI colors | Edit styles.css or __settings__.js |
| Request | Approach |
|---|---|
| Add new script components | Compiled bundle — very complex; prefer runtime hooks or patching existing scripts |
| Per-entity custom shaders | Compiled engine — no runtime shader injection; consider material property changes instead |
When the user requests something in the ⚠️ list, explain the difficulty and offer the closest ✅ alternative first.
Understand this before modifying any rendering code:
Every cell is painted with a frame type that determines its texture:
| Frame | Used for | Texture attribute | When used |
|---|---|---|---|
cell_empty | Unoccupied cells | textureCellEmpty | Default state |
block | Obstacle/wall cells | textureBlock | Grid obstacles |
endpoint_circle | Start/end nodes | textureEndpointCircle | Colored endpoints where paths begin/end |
pipe_straight | Path segments | texturePipeStraight | Connected path cells |
pipe_corner | Path corners | texturePipeCorner | Path turns |
The game engine rotates pipe_corner by 0°/90°/180°/270° to produce all four turn directions.
The base sprite (0° rotation) must connect RIGHT and DOWN:
┌────────────┐
│ │
│ ╔═══════─┤ → exits RIGHT
│ ║ │
│ ║ │
└───╨────────┘
↓ exits DOWN
⚠️ PIL vs PlayCanvas UV flip: PlayCanvas loads textures with UV origin at bottom-left, but PIL draws with origin at top-left. This means the image appears rotated 180° in-game relative to how it looks in an image viewer. To produce a sprite that connects RIGHT+DOWN in-game, you must draw it connecting LEFT+UP in PIL coordinates (i.e., exits at x=0 and y=0).
# Correct corner pipe generation — appears as right→down IN-GAME:
from PIL import Image, ImageDraw
SZ = 256
PIPE_W = 80 # pipe width
img = Image.new('RGBA', (SZ, SZ), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
# Horizontal bar: center-Y to LEFT edge (appears as right exit in-game)
d.rectangle([0, SZ//2 - PIPE_W//2, SZ//2 + PIPE_W//2, SZ//2 + PIPE_W//2], fill=(255,255,255,255))
# Vertical bar: center-X to TOP edge (appears as down exit in-game)
d.rectangle([SZ//2 - PIPE_W//2, 0, SZ//2 + PIPE_W//2, SZ//2 + PIPE_W//2], fill=(255,255,255,255))
img.save('pipe_corner.png')
Verification: Open the saved PNG in an image viewer — the elbow should be in the upper-left quadrant with openings pointing LEFT and UP. In-game (after PlayCanvas UV flip) it will display as RIGHT+DOWN.
pipe_straight base sprite (0° rotation) must be a horizontal bar (left↔right). The engine rotates it 90° for vertical segments.
red, blue, green, etc.) mapped to a hex via GridRenderer.PATH_COLOR_HEX_paintCellFrame(row, col, frameType, parsedColor, rotation) applies the frame texture + color tintmaterial.emissive — the per-color textures act as brightness modulators (white=full color, gray=dimmer)endpoint_circle frame, not block_paintCellFrame → _createMaterialForGridFrame → _applyGridFrameToCell → _getTextureAssetForFrame(frameName)
→ TEXTURE_ATTR_BY_FRAME[frameName] → attribute name → this[attrName].resource
The IIFE in _paintCellFrame does:
var _pt = this._getPerColorEndpointTexture(i); // i = pc.Color
if (!_pt) _pt = this._getPerColorBlockTexture(i);
if (_pt) {
n.diffuseMap = _pt; // texture as diffuse
n.emissiveMap = _pt; // texture as emissive (multiplied by emissive color)
n.blendType = pc.BLEND_NORMAL; // enable alpha blending
n.alphaTest = 0.05; // discard near-transparent pixels
}
_getPerColorEndpointTexture(colorObj) converts the pc.Color to hex, fuzzy-matches (distance < 30) to the closest PATH_COLOR_HEX, and returns this[attrName].resourceWhen adding NEW texture assets to config.json, each entry must follow this exact structure.
Missing or incorrect fields (especially preload: true) will cause .resource to be null at runtime.
{
"name": "endpoint_red.png",
"type": "texture",
"file": {
"filename": "endpoint_red.png",
"size": 850,
"hash": "unique_hash_string",
"variants": {},
"url": "files/assets/283500001/1/endpoint_red.png"
},
"data": {
"addressu": "repeat",
"addressv": "repeat",
"minfilter": "linear_mip_linear",
"magfilter": "linear",
"anisotropy": 1,
"rgbm": false,
"srgb": true,
"mipmaps": true
},
"preload": true,
"tags": [],
"i18n": {},
"id": "283500001"
}
| Field | Required | What happens if wrong |
|---|---|---|
preload: true | YES | Asset never loaded → .resource is null → texture invisible |
file.url | YES | Must match actual file path exactly |
file.filename | YES | PlayCanvas uses this for type detection |
file.variants: {} | YES | Missing = asset registry skips it |
data.minfilter | Use strings | Numeric values (5) break — use "linear_mip_linear" |
data.magfilter | Use strings | Numeric values (1) break — use "linear" |
data.srgb: true | Recommended | Without it, colors may look washed out |
id | YES | Must match the key in assets object |
⚠️ Common mistake: Using numeric enum values (
minFilter: 5) instead of string names ("linear_mip_linear"). The built-in PlayCanvas assets always use string names.
python3 -c "
import json
data = json.load(open('2453710.json'))
for eid, ent in data['entities'].items():
sc = ent.get('components', {}).get('script', {}).get('scripts', {})
for sname, sdef in sc.items():
for aname, aval in (sdef.get('attributes') or {}).items():
if isinstance(aval, int):
print(f'{ent[\"name\"]}.{sname}.{aname} = {aval}')
"
python3 -c "
import json
asset_id = 282971613 # example: textureBlock
cfg = json.load(open('config.json'))
asset = cfg['assets'].get(str(asset_id), {})
print(asset.get('filename'), asset.get('url'))
"
The URL field gives the relative path, e.g. files/assets/282971613/1/block.png.
Option A — Direct PNG from Twemoji (preferred — no conversion needed):
# Twemoji provides pre-rendered PNG files. Find the unicode codepoint for the emoji.
# Bird: U+1F426 → 1f426, Fire: U+1F525 → 1f525, Star: U+2B50 → 2b50
curl -L "https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/1f426.png" -o /tmp/symbol.png
Option B — Iconify SVG (must convert to PNG):
# Download SVG
curl -L "https://api.iconify.design/mdi/bird.svg?width=256&height=256&color=%23ffffff" -o /tmp/symbol.svg
# CRITICAL: Convert SVG to PNG. SVG files CANNOT be used as PlayCanvas textures.
# Use sips on macOS (reads SVG, exports PNG):
sips -s format png /tmp/symbol.svg --out /tmp/symbol.png 2>/dev/null \
|| python3 -c "import cairosvg; cairosvg.svg2png(url='/tmp/symbol.svg', write_to='/tmp/symbol.png', output_width=256, output_height=256)" 2>/dev/null \
|| rsvg-convert -w 256 -h 256 -o /tmp/symbol.png /tmp/symbol.svg
Option C — Copy from the pre-bundled symbol library (fastest, no network needed):
# See "Symbol Library" section above for available symbols
cp files/assets/symbol_library/star.png /tmp/symbol.png
Option D — Generate with Python PIL (for custom symbols not in library):
from PIL import Image, ImageDraw
SZ = 256
BG = (153, 153, 153, 255) # gray background (0.6 brightness)
FG = (255, 255, 255, 255) # white symbol (full brightness)
img = Image.new('RGBA', (SZ, SZ), BG)
d = ImageDraw.Draw(img)
# Draw your shape in white on gray — white areas will be bright, gray areas dimmer
# Example: cross / plus sign
d.rectangle([108, 20, 148, 236], fill=FG)
d.rectangle([20, 108, 236, 148], fill=FG)
img.save('/tmp/symbol.png')
Important: Use gray
(153,153,153)background + white(255,255,255)foreground. The rendering engine multiplies this texture by the block's emissive color, so:
- White areas → full color brightness (symbol stands out)
- Gray areas → dimmer color (still shows color, just darker)
NEVER do this:
# WRONG — creates an SVG file with .png extension. PlayCanvas will fail to load it.
echo '<svg ...>' > files/assets/123/1/symbol.png # ← BROKEN
# macOS built-in (no dependencies):
sips -z 128 128 /tmp/symbol.png --out files/assets/282971613/1/block.png
# Or Python PIL:
python3 -c "
from PIL import Image
img = Image.open('/tmp/symbol.png').convert('RGBA').resize((128, 128), Image.LANCZOS)
img.save('files/assets/282971613/1/block.png')
"
sips -g all files/assets/282971613/1/block.png | grep -E "pixelWidth|pixelHeight|format"
When the user wants a shape change rather than a custom image:
| User says | Set value |
|---|---|
| plus, cross, + | "cross" |
| diamond, rhombus | "diamond" |
| wide diamond | "diamondWide" |
| thick cross | "thickCross" |
| L-shape | "lshape" |
| narrow L | "lshapeNarrow" |
| wide L | "lshapeWide" |
| hourglass | "hourglass" |
| zigzag, wave | "zigzag" |
| comb | "comb" |
| bone | "bone" |
| arrow | "arrow" |
| letter A/B/E/F/G/Q | "letterA" etc. |
| square, box, default | "square" |
import json
from pathlib import Path
p = Path('2453710.json')
data = json.loads(p.read_text())
for ent in data['entities'].values():
sc = ent.get('components', {}).get('script', {}).get('scripts', {})
if 'gridRenderer' in sc:
sc['gridRenderer']['attributes']['shapeType'] = 'cross' # your chosen value
p.write_text(json.dumps(data, ensure_ascii=False, separators=(',', ':')))
print('shapeType updated')
See ★ QUICK START at the top of this skill. Everything below is reference only.
If the user just wants one symbol on ALL endpoints (same symbol, no per-color), replace the textureEndpointCircle asset file directly at its path in files/assets/. Do NOT change the attribute value in 2453710.json.
| Source | Best for | URL pattern |
|---|---|---|
| Iconify | SVG icons (1000s of sets) | https://api.iconify.design/<set>/<icon>.svg?width=128 |
| Twemoji | Emoji as PNG | https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/<unicode>.png |
| Feather Icons | Clean line icons | https://raw.githubusercontent.com/feathericons/feather/master/icons/<name>.svg |
| SVG Repo | General SVG | https://www.svgrepo.com/ (manual browse) |
files/assets/<id>/1/<filename>)sips -g format)sips -g pixelWidth pixelHeight)shapeType value is from the valid enum list (not invented)__game-scripts.js was patched: verify game still loads (no syntax errors in patch)If per-color symbol textures are correctly placed as PNG files but don't appear in-game:
| Symptom | Cause | Fix |
|---|---|---|
| Symbol invisible, no errors | preload: true missing in config.json | Add "preload": true to the asset entry |
| Symbol invisible, no errors | config.json file structure wrong | url must be inside file: { ... }, not at root level |
| Symbol invisible, no errors | config.json data uses numeric enums | Use "linear_mip_linear" not 5, "linear" not 1 |
| Symbol invisible, no errors | Only emissiveMap set | Must set BOTH diffuseMap and emissiveMap + blend mode |
| Colors washed out | srgb: false in asset data | Set "srgb": true |
| PNG file exists but shows white | File is all-white placeholder | Replace with actual symbol from symbol_library |
| "not a PNG" error | SVG saved with .png extension | Convert with sips -s format png first |
Add to _getPerColorEndpointTexture temporarily:
console.log('color hex:', h, 'attr:', a, 'asset:', this[a], 'resource:', this[a] && this[a].resource);
If asset is null → attribute not wired in 2453710.json.
If asset exists but resource is null → config.json entry is malformed or preload: true is missing.