Foundry's Canvas is the primary method by which Foundry fulfills its function as a Virtual Tabletop, providing a space to render maps full of rich details and complex interactions.
Official Documentation
Legend
Canvas.getRenderTexture // `.` indicates static method or property
Canvas#ready // `#` indicates instance method or property
The most performance-intensive aspect of Foundry Virtual Tabletop, the game canvas forms the third pillar of development, alongside Documents and Applications. The canvas uses PixiJS to build on both the database management of documents as well as the UI controls of applications; understanding the canvas will frequently require deeper knowledge of how each of those three contribute to the overall rendering.
Fortunately, while the Canvas is very complex, most developers do not need to worry about it; the core software team spends significant amounts of time each major release refining and upgrading the canvas capabilities. The downside of this is that the canvas API is not very stable, and so has major breaking un-deprecated changes every version. The top of this page has a version marker; even more so than the other pages in the wiki's API documentation, if you are developing for a different core version than what this documents it is unlikely the information here will be accurate.
Code for the Canvas class and its related classes can be found at yourFoundryInstallPath/resources/app/client/pixi
as well as yourFoundryInstallPath/resources/app/client-esm/canvas
.
This is the most essential information to understanding the canvas
The canvas is made up of a number of groups which define the drawing process. The top most is the "stage"
, which encompasses all of Foundry's core-defined groups. They are drawn in the following order with the designated hierarchy.
Almost all canvas layers are drawn as part of the interface
group, with the exception being the weather
layer which is drawn as part of the primary
group. These layers largely represent the various canvas documents - tiles
, notes
, lighting
, etc., with the additional ones being controls
, grid
, and weather
. As each layer is drawn, it draws any objects contained in it, such as the associated placeable object for the embedded documents in the viewed scene.
The canvas has many points of interaction for systems and modules that wish to modify its behavior.
Systems and modules have many ways to interact with the canvas by modifying the globally available CONFIG
object. You almost always modify this inside of an init
hook.
objectClass
, layerClass
, and hudClass
properties to replace and extend the relevant classes like you might for documentClass
. (e.g. CONFIG.Token.objectClass = MyToken
)
CONFIG.Wall.doorSounds
CONFIG.Canvas
has many configurable properties, including the groups
and layers
that are used to define new groups and layers or alter the existing onesAPI Reference
The controls on the left side of the screen can be modified in the getSceneControlButtons
hook. The only argument it passes is the current array of scene controls. To add new ones, you can use controls.push
to add new elements, or splice if you want to add your controls to a spot besides the bottom of the array.
title
property of the scene control as well as individual tools are automatically localized.icon
property is expected to be a valid FontAwesome class, e.g. "fa-solid fa-expand"
.onClick
callback is (toggled: boolean) => void
if the toggle: true
, otherwise it is just () => void
Here are some tips and tricks when working with the canvas.
// Grabbing the hovered grid's space
const p = canvas.mousePosition; // { x, y }
const { x, y } = canvas.grid.getTopLeftPoint({x: p.x, y: p.y });
// Grabbing the hovered grid's space
const p = canvas.mousePosition; // { x, y }
const coords = canvas.grid.getCenterPoint({ x: p.x, y: p.y }); // { x, y }
const cube = canvas.grid.getCube(coords); // { q, r, s }
const offset = canvas.grid.getOffset(cube); // { q, r, s }
const { x, y } = canvas.grid.getTopLeftPoint(offset);
// Registering the Highlight
const highlightId = "foo";
canvas.interface.grid.addHighlightLayer(highlightId);
const CONFIG = {
x, y, // The grid space's coordinates
color: 0xff0000, // The fill color
border: null, // The border color
alpha: 0.25, // The highlight's opacity
shape: null // A PIXI.Polygon, unused unless highlighting a gridless canvas
};
// Positioning the Highlight
canvas.interface.grid.highlightPosition(highlightId, CONFIG);
// Hiding the Highlight
canvas.interface.grid.clearHighlightLayer(highlightId);
// Destroying the Highlight
canvas.interface.grid.destroyHighlightLayer(highlightId);
Here are some tips and tricks to help with common pitfalls while developing for the canvas.
CONFIG.debug.polygons
// in type choose one of the values in WALL_RESTRICTION_TYPES: ["light", "sight", "sound", "move"]
ClockwiseSweepPolygon.create(point, {debug: true, type: CONST.WALL_RESTRICTION_TYPES})