Skip to content
Back to skills

vex-vector-art

ASecurity

Create 2D vector game assets (characters, enemies, items, icons, props, UI, backgrounds, scenes, animations, sprite atlases) with vex, a compact JavaScript DSL that exports SVG + PNG + atlas JSON and has a live drawing viewer. Use when asked to draw, design or iterate on vector / flat / cartoon-style game art.

  • 11 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 22, 2026
ai-agentsjavascriptgojavanodeawsapi

Works with

  • cli
  • api

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 17 files and shows the line behind each finding

Scanned October 22, 2026

npx -y skills add uilianks/vex --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of vex-vector-art?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for vex-vector-art
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/uilianks-vex-vector-art/badge)](https://www.skillsdirectory.com/skills/uilianks-vex-vector-art)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: vex-vector-art
description: Create 2D vector game assets (characters, enemies, items, icons, props, UI, backgrounds, scenes, animations, sprite atlases) with vex, a compact JavaScript DSL that exports SVG + PNG + atlas JSON and has a live drawing viewer. Use when asked to draw, design or iterate on vector / flat / cartoon-style game art.
---

# vex: vector game art as code

You write a small `.vx.js` file; vex exports SVG and PNG per asset, anim strips and atlases with
TexturePacker/Aseprite-style JSON, and previews you can look at. Shapes are smooth vectors in
abstract units (1 unit = 1 px at scale 1). Needs Node ≥ 22.18 and `npm install` once in this
skill's directory.

`VEX` below means `node <this skill's directory>/bin/vex.js`.

## Workflow

1. Write `art.vx.js` (start from the template below; more in `examples/`).
2. Build what you're working on: `VEX build art.vx.js --only slime,coin`
3. **Look at it**: read `out/_preview/<name>.png` (checkerboard = transparent, faint grid, rulers
   with unit coordinates on the top and left edges, so you can read positions off the image).
   Anims show every frame side by side. For exact geometry, `VEX svg art.vx.js <name>` prints the SVG.
4. Fix with small edits (move a point, change a radius) and rebuild. Repeat.
5. If the user wants to watch: start `VEX serve art.vx.js` in the background and give them the URL.
   It rebuilds on save and replays the edited asset step by step; it also writes `out/`.
   **Feedback**: the viewer's annotate mode (`a`) lets the user draw strokes and pin notes on the art
   and send them. When they say "check the feedback", read `out/_feedback/<asset>.json`: `comment`,
   `notes` (`x, y` in asset units, the same coordinates as the art file) and `strokes` (points in
   asset units; `kind: "palette"` with a `key` means "make this that color", `"markup"` points at
   something). Look at `<asset>.annotated.png` too. Comments persist between sends: check `time`.
6. Ship: `VEX build art.vx.js --scales 1,2` writes `name.png` and `name@2x.png` plus `.svg`.

Errors name file:line:col and the asset/op path, and suggest fixes ("did you mean 'g'?").
One broken asset doesn't stop the others from building.

## Template

```js
pal({ k: "#1b1630", g: "#7bd36a", G: "#3f8f4f", w: "#fff6e0", r: "#e2585e", R: "#a63a4c", y: "#ffd35c", o: "#f0913a" })

// blob = smooth closed curve through points. sym(axisX, ...) mirrors details. outline wraps it all.
sprite("slime", { w: 32, h: 26, outline: "k" },
  blob([16, 2, 24, 5, 29, 13, 30, 22, 16, 24, 2, 22, 3, 13, 8, 5], { fill: rad(["g", "G"], { cx: 0.4, cy: 0.3 }) }),
  sym(16, circle(11, 12, 3, "w"), circle(11.6, 12.4, 1.6, "k")),
  circle(9, 7, 1.6, { fill: "w", opacity: 0.7 }))
sprite("slime_red", { base: "slime", swap: { g: "r", G: "R" } })          // variant: 1 line

sprite("coin", { w: 20, h: 20, outline: "k", ow: 1.5 },
  circle(10, 10, 9, "o"), circle(10, 10, 7, "y"), rect(8.5, 5, 3, 10, { fill: "o", r: 1.5 }))

anim("slime_hop", { w: 36, h: 42, frames: 8, fps: 10 },
  (f) => ellipse(18, 39, 11, 2, { fill: "#1b163055" }),
  (f) => stamp("slime", 18, 38 - Math.max(0, f.sin(8)), { anchor: "bottom", scale: [1 + f.cos(0.08), 1 - f.cos(0.08)] }))

scene("meadow", { w: 160, h: 90, bg: lin(["#5ab6ff", "w"], 90) },
  circle(130, 20, 10, { fill: "y", glow: { r: 8, color: "y" } }),
  blob([0, 62, 40, 54, 90, 60, 130, 52, 160, 58, 160, 90, 0, 90], { fill: "g", smooth: 0.6 }),
  scatter("slime*", 10, 66, 140, 10, 5, { anchor: "bottom", scale: [0.6, 0.9], flip: "x" }))

sheet("items", ["slime*", "coin", "slime_hop"], { pad: 2, scale: 2 })
```

## Essentials

- **Colors**: palette keys from `pal` (any `[A-Za-z0-9_]` name), `"#rgb"`, `"#rrggbbaa"`, `"none"`.
  Default palette is sweetie16 (`0-f`); `use("pico8")` or `use([...hex])` for others.
- **Style**: last argument of a shape. A color string = fill (stroke for `line`/`curve`/`arc`), or
  `{ fill, stroke, sw, opacity, cap, join, dash, glow, blur }`. Fills can be gradients:
  `lin(colors, angleDeg)` and `rad(colors, { cx, cy, r })`, relative to the shape's box.
- **Hand-drawn look** (painted card-game style): `ink: { c, w, taper, wobble }` draws a wobbly ink line
  whose weight follows the light; `shade: { light, rim, hi, brush }` paints a form shadow, highlight and
  brush texture from one light direction (shadow color from the fill's ramp); `hatch: "k"` hatches the
  shadow. `anim(..., { boil: 3 })` redraws the wobble every 3 frames.
- **Shapes**: `rect circle ellipse poly blob line curve path star ngon arc text`. `blob`/`curve`
  pass smoothly through their points (`smooth: 0..1`); `path` takes raw SVG path data.
- **Structure**: `tf({ x, y, rot, scale, flip, origin, opacity, glow, blur }, ...items)` groups;
  `sym(x, ...items)` mirrors; `clip(maskShape, ...items)` keeps items inside a shape (shading,
  highlights, patterns on a body).
- **Reuse**: `stamp(name, x, y, { anchor, scale, rot, flip, swap, opacity })`, `tile`, `scatter`.
  Names can be groups or globs (`group("rocks", ["rock_a", "rock_a", "rock_b"])`, `"tree_*"`);
  each placement picks reproducibly. `_name` = part: usable, never exported.
- **Asset options**: `w, h` (required unless `base`), `outline` + `ow`, `mx`/`my` (mirror whole
  asset), `swap`, `flip`, `rot`, `bg`, `scale` (export px per unit). Order: items → mirror → swap →
  flip/rot → outline.
- **Skeletal animation**: `rig({ bone: { parent, pivot: [x, y], z, items } })` + `pose(rig, clip)` where a clip is
  `{ bone: { rot | move | scale: [[frame, value, ease?], ...] } }`. Check the anim preview strip frame by frame:
  rotations add up along the chain (a held sword's angle is arm + forearm + sword), and a jump needs headroom.
- **UI**: `slice: [l, t, r, b]` on a sprite exports 9-slice margins (check `_preview/<name>.9.png`: corners must
  stay crisp); `font(name, { family, weight, size, fill, outline, shadow })` exports a BMFont (.fnt + .xml + png).
- **Particles**: `particles({ at | area, rate | burst, life, speed, angle, gravity, drag, size, color, opacity, shape, glow })`
  inside an anim. Draw smoke before fire, check the strip on a dark stage too (light colors vanish on the checkerboard).
- **Levels**: `tileset(name, { size, tiles })` + `level(name, { tileset, legend, map })`. Legend chars are tile keys,
  autotile rules (`platformer({...})` or `(n) => key`) or `{ object, type, props }`; maps can be generated `(rng) => rows`.
  Exports Tiled .tmj/.tmx; check `out/levels/<name>.html` and that objects stand on ground, not one row above it.
- **Spine**: `spine(name, rig, { w, h, scale, outline, clips: { walk: { clip, frames, fps } } })` writes a Spine 4.1
  skeleton + atlas for game runtimes; check it in `out/spine/<name>.html` (served over HTTP).
- **Animation**: `anim(name, { frames, fps }, ...)`; function items `(f) => items` get `f.i`, `f.n`,
  `f.t` (0..1, loops), `f.sin(amp, phase, cycles)`, `f.cos(...)`, `f.rng(seed)`. Stamping an anim
  inside an anim follows its frames (`delay` to desync, `frame` to freeze).
- Function items work in still assets too (f.i = 0): loops, `Array.from`, conditionals.

Full API with every option: [reference.md](reference.md).

## Habits that give better vector game art

- One silhouette, clear read at small size: big shapes first, then 2–3 detail layers. Check the
  preview at 1x too (`out/<kind>s/<name>.png`), not just the big preview.
- Use `outline` instead of stroking each shape: it draws one clean silhouette outline, and
  `clip`ped detail never bulges it.
- Cel shading: base shape, then a darker shape and a lighter highlight `clip`ped to the base, all
  from the same material ramp (`ramps("kGgw")`, `dark("g")`, `lite("g")`). 2–3 tones per material.
- Light from one direction (usually top-left) across every asset in a set; highlights and shadows
  must agree. For the hand-drawn look, put it in one constant (`const LIGHT = 315`) and pass it to
  every `ink` and `shade`; give every material a ramp so the shading picks its colors.
- Hand-drawn line weight: heavier (`w` 4–6) on outer silhouettes, lighter (2–3) on inner detail,
  tapered open strokes (`taper` 0.3–0.6) for mouths, creases and claws.
- Variants are `base` + `swap`; repeated details are `_parts` + `stamp`; families are a loop:
  `for (const [i, c] of ["r", "b", "y"].entries()) sprite("gem_" + i, ...)`.
- Organic shapes: `blob` with 6–10 points beats hand-written Béziers. Hard-edged things (crates,
  swords, UI): `poly`, `rect` with `r`.
- Keep a consistent unit size per set (e.g. characters 32 high) so assets scale together.
- Build with `--only` while iterating; a full build also writes every preview.

Files in this skill

  • NOTICE360 B
  • SKILL.md8.5 KB
  • bin/vex.js560 B
  • examples/basics.vx.js3.1 KB
  • examples/props.vx.js3.2 KB
  • package-lock.json8.6 KB
  • package.json1002 B
  • reference.md16.1 KB
  • showcase/forest_kit.vx.js25.4 KB
  • showcase/hand_drawn.vx.js3.7 KB
  • showcase/levels.vx.js2.9 KB
  • showcase/noir_cafe.vx.js28.6 KB
  • showcase/rigged.vx.js8 KB
  • showcase/ui_kit.vx.js3.5 KB
  • showcase/vfx.vx.js5 KB
  • test/vex.test.ts23.9 KB
  • tsconfig.json387 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.