
Write your own character
Source:vignettes/articles/write-your-own-character.Rmd
write-your-own-character.RmdYou can read and check a character file using
mascot_read_character() and
mascot_check_character(). Both functions are experimental,
and their arguments, results and diagnostics may change in a later
release.
Saving and registering characters and palettes are planned for a
later release. The mascot studio does not take custom characters yet,
but you can preview your own character offline with
mascot_preview(), also experimental, and use it directly in
Shiny apps.
Find the example character
mascot ships with a sample character recipe,
study_bot.json. Start by copying it to a temporary file in
tempdir() so you can inspect and modify it without touching
package files:
path <- system.file("examples", "characters", "study_bot.json", package = "mascot")
copy <- tempfile(fileext = ".json")
file.copy(path, copy)
#> [1] TRUERead and check the file
Call mascot_read_character() with the file path. It
reads the JSON file, checks every field against the character format
rules, and returns an opaque, checked character object:
bot <- mascot_read_character(copy)
bot
#> study_bot: Study Bot (visorfolk)
#> Moods: idle, thinking, happy, worried, celebrating
#> Gestures: rock, nod, wave, greet
#> Detail overrides: micro
#> From: /tmp/RtmpVf6X2c/file22442d270012.jsonPrinting the object shows a concise summary: the character identifier, display name, family, supported moods, gestures, detail overrides, and the source path.
A checked character object cannot be edited: package functions refuse an object that has been changed. Always edit the source JSON or plain list, then read or check it again.
Inside the character format
study_bot.json holds a single JSON object, and the
checker tests every field against the format rules:
-
format_version: The integer1. It says which version of the file format the file uses. -
id: A unique lowercase identifier ("study_bot"). Built-in character ids ("tessa","sela","knurl","neer") are reserved and refused. -
name: The human-readable name shown in titles and summaries ("Study Bot"). -
description: One sentence about the character. -
family: The character family ("visorfolk"). Every character names one, and a custom character also gives the family’s definition infamily_definition: itsid,name,descriptionand optionaldefaults, such as gestures and palettes the character takes unless it defines its own. -
colours: Named colour roles and their default hex values (body,outline,screen,feature, andaccent). -
ground: Where the character stands: its centre height (centre_height: 60). -
outline: The width of the outline and the colour role it uses. -
parts: The pieces the character is built from (in this filehead,antennaandbadge_disc). A part can sit inside another part, and each part holds drawn items, such as theshellandvisorshapes insidehead. -
anchors: Named points on parts for attaching accessories or badges. -
face: Which part carries the face (part), the facekit("screen"), where it sits (at), aclip, and theeyesandmouthsettings. -
moods: Configurations for the five core moods that every character must support:idle,thinking,happy,worried, andcelebrating. -
gestures: Named motion tracks. Every character must define movingwaveandnodgestures:wavemust move at least one non-root part (such as a hand or antenna), whilenodmay move any channel, includingroot. In this filewaveswings theantennaandnodtilts thehead. -
palettes: Alternative colour collections (such as"sea_glass"). -
contrast: Accessibility rules that verify contrast between foreground and background colour roles. -
lod: Level-of-detail rules for smaller sizes, such as hiding fine details likebadge_discin the micro detail band.
study_bot has no requires field, and your
character needs none either: every version-1 mascot draws the eight
version-1 capabilities (parts, paints,
clip, outline, glow,
face_kit, tracks and lod).
requires is for capabilities added after version 1; a name
this version of mascot does not know stops the check with an error that
names it. Older files that list the eight names keep loading.
Drawing coordinates are design units in a 300 by 300 frame. The point
where the character stands is (0, 0), 150 units from the
left edge and 270 from the top, and y grows downwards: a
part above the ground has a negative y.
Keys for other tools
Editors and other tools can add their own information to a character file, and mascot keeps it without reading it:
- A top-level
"$schema"string, so an editor can link the file to a schema. - Any key that starts with
x_in an object whose fields the format fixes: the character itself, a part, an item, a mood, a gesture, the face and so on. Its value can be any JSON value, and it stays in the file, as written, when the character is checked and saved. It counts towards the file’s size limits. In an object whose keys are ids or names, such ascolours,moodsorpalettes, a key that starts withx_is an ordinary id. - A
"note"string in any of those objects, as a comment.
The start of such a file, shortened:
What you can draw
Changing a colour is only the start. Each part holds a list of drawn items, and there are nine kinds of item. Together they can draw any silhouette:
| Item | What it draws |
|---|---|
circle, ellipse, rect,
round_rect
|
The basic shapes. A circle or ellipse has
a centre and radii; a rect or round_rect has
its top-left corner, a width and a height. A round_rect has
rounded corners. |
polygon, hexagon
|
A regular shape with 3 to 12 equal sides (a hexagon
always has 6), around a centre, with optional rounded corners. |
polyline |
A line through a list of points, joined by straight segments. Good for whiskers, antennae, cracks and hair strands. |
arc |
A piece of a circle between two angles (in radians). Good for smiles, eyebrows, rings, or a spanner head with a gap. |
path |
Free-form outlines written as SVG path data (M,
L, Q, C, Z):
straight lines and smooth curves. This is the one for organic shapes,
and the built-in characters use path items for their curved
bodies. |
So the nine kinds are building blocks, not a limit on the variety of
characters: a leafy creature, a fox or a robot are all drawn by
combining items, with path doing the shaping. Items take a
fill or a stroke from your colour roles, can be clipped, and can be
hidden at small sizes (see lod). Here are three items as
they would appear in a part:
{"shape": "path", "d": "M-30 0Q0 -40 30 0Z", "fill": "body", "outline": true}
{"shape": "polyline", "points": [[-10, 0], [0, 10], [10, 0]], "stroke": "outline", "width": 2}
{"shape": "arc", "x": 0, "y": 0, "r": 20, "start": 0.5, "end": 5.78, "stroke": "outline", "width": 3}The format reserves some capabilities, and the checker refuses them. Examples are morphing between shapes, particles, blending, outfits and accessories that any character can wear, and connecting items to each other. A character in format version 1 is drawn from the items above and animated with gestures (movement tracks on its parts).
Make a small edit
To customise your character, read the JSON into R using
jsonlite, edit the fields, and write it back:
data <- jsonlite::read_json(copy)
# Change the identifier and display name
data$id <- "sparky_bot"
data$name <- "Sparky Bot"
# Change the primary body colour from blue to warm coral
data$colours$body <- "#ff6b4a"
# Save the modified JSON back to disk
jsonlite::write_json(data, copy, auto_unbox = TRUE, pretty = TRUE)
# Read and check the edited character
sparky <- mascot_read_character(copy)
sparky
#> sparky_bot: Sparky Bot (visorfolk)
#> Moods: idle, thinking, happy, worried, celebrating
#> Gestures: rock, nod, wave, greet
#> Detail overrides: micro
#> From: /tmp/RtmpVf6X2c/file22442d270012.jsonmascot_read_character() validates the modified file and
returns the updated character object.
Break it on purpose
The checker stops at the first problem and raises an error of class
mascot_error_character (which also inherits from
mascot_error). The error carries these fields:
-
code: A stable problem identifier (such as"version"or"input_type"). -
path: The JSON path where the error occurred (for example$.format_version). -
value: The invalid value that was rejected. -
fix: Plain-words advice on how to fix the problem. For a value out of range it gives the range or the choices, for a drawing budget the count and the limit, and for a character too big for its frame the part that reaches furthest outside it.
Change format_version to an unsupported number to see
the diagnostic:
data$format_version <- 2
jsonlite::write_json(data, copy, auto_unbox = TRUE, pretty = TRUE)
err <- tryCatch(
mascot_read_character(copy),
mascot_error_character = function(e) e
)
class(err)
#> [1] "mascot_error_character" "mascot_error" "rlang_error"
#> [4] "error" "condition"
err$code
#> [1] "version"
err$path
#> [1] "$.format_version"
err$value
#> [1] 2
err$fix
#> [1] "This file is format version 2; this mascot version supports 1. Update the package, or use a version-1 file."
conditionMessage(err)
#> [1] "\033[1m\033[22m$.format_version: This file is format version 2; this mascot version\nsupports 1. Update the package, or use a version-1 file."The message names the format version this mascot supports:
"This file is format version 2; this mascot version supports 1. Update the package, or use a version-1 file."
Preview and use in an app
Reset the format version to 1 before using the character:
data$format_version <- 1
jsonlite::write_json(data, copy, auto_unbox = TRUE, pretty = TRUE)
sparky <- mascot_read_character(copy)
# Generate an offline comparison preview across moods and sizes
preview <- mascot_preview(character = sparky)Assigning mascot_preview(character = sparky) to an
object generates an offline preview page comparing moods, sizes, and
light and dark backgrounds without opening an interactive viewer.
To use your character in a Shiny application, pass the checked object
directly to the character argument of
mascot(). You can also style your character’s own colour
roles using mascot_theme():
library(shiny)
library(mascot) # nolint: undesirable_function_linter.
ui <- fluidPage(
mascot(
"helper",
character = sparky,
theme = mascot_theme(light = list(screen = "#051b20"))
)
)mascot_theme() checks colour role names against the
selected character when the mascot renders, so custom role names such as
screen work as well.
What you can rely on
The format is experimental and its future direction is open. These points hold either way:
- Additions stay in version 1: new optional keys, wider ranges and new categories keep the format at version 1, so existing files keep working.
-
Clear errors on older versions: a file that uses an
addition lists it in
requires; an older version of mascot then stops with an error naming it, instead of drawing it wrongly. The version-1 capabilities never need listing. -
Room for other tools:
$schemaandx_keys are kept and never read, so an editor can store its own information in a character file. - Breaking changes mean version 2: only a change that would break existing files introduces format version 2. Retiring an older format version is announced in NEWS at least one release ahead.
- Predictable validation: the checker only becomes stricter in a new format version, except for documented security or resource-safety fixes, which come with a diagnostic and migration guidance.
- No silent migrations: character files are never modified or upgraded silently.