Rendering Pipeline
Relevant source files
The following files were used as context for generating this wiki page:
The rendering pipeline in Farm Siege is a custom 2D game engine built with TypeScript, designed to render graphics onto an HTML <canvas> element. It employs a component-based architecture, inspired by the declarative UI syntax of Flutter, where the entire visual scene is constructed from a tree of Component objects. The pipeline is initiated and managed by the Canvas class, which orchestrates the main render loop.
At its core, the system recursively traverses a tree of components, starting from a single root component. During each frame, it calculates necessary transformations and passes down a RenderingContext object containing the canvas context, scaling information, and timing data. This allows individual components to draw themselves correctly without needing to be aware of the global state.
Core Architecture
The rendering pipeline is built around three main classes: Canvas, Component, and RenderingContext. They work together to manage the game loop, the scene graph, and the drawing state.
Sources: lib/Canvas.ts, lib/Component.ts, lib/RenderingContext.ts
Class Diagram
This diagram shows the relationship between the core rendering classes.
Canvas Class
The Canvas class is the entry point and controller for the entire rendering process. It is initialized with a configuration object that specifies the target <canvas> element, the logical grid dimensions, and the root Component of the scene.
Key responsibilities include:
- Acquiring the
2drendering context from the canvas. - Handling window resize events to adjust canvas resolution and scaling (
adjustToScreen). - Setting up the main render loop using
window.requestAnimationFrame. - Propagating user input events (click, keypress, etc.) to the root component.
- Optionally rendering an FPS counter.
Sources: lib/Canvas.ts:13-20, index.html:39
Component Class
The Component class is an abstract base class for all renderable elements in the game. Each component defines its visual structure through a template property, which is an array of child components and their associated properties and positions.
Key features:
template: An array defining child components, their positions, props, and conditional visibility.render(): The main method called by the parent component or theCanvasclass. It processes the component's logic (onTick) and then iterates through itstemplateto render children.onInit(): A lifecycle method called once, on the first render, for initialization tasks.onTick(): A lifecycle method called on every frame, allowing for state updates based ontimeDifference.
Sources: lib/Component.ts:10, 15, 21, 46
RenderingContext Class
This class is a data container that holds all the necessary information for a component to render itself. A new RenderingContext is created at the start of each frame by the Canvas class and is passed down through the component tree. When a parent component renders a child, it creates a new context with updated parent coordinates.
The context contains the following properties:
| Property | Type | Description |
|---|---|---|
grid | Dimensions | The logical dimensions of the game grid. |
parentX | number | The X-coordinate of the parent component. |
parentY | number | The Y-coordinate of the parent component. |
canvas | HTMLCanvasElement | The main canvas element. |
renderContext | CanvasRenderingContext2D | The 2D drawing context. |
scaleFactor | number | The factor to scale coordinates and dimensions to match screen resolution. |
timeDifference | number | The time in milliseconds since the last frame was rendered. |
frameStart | number | The timestamp when the current frame started rendering. |
Sources: lib/RenderingContext.ts:3-40
The Render Loop
The rendering process is a continuous loop initiated by the Canvas class constructor. It uses window.requestAnimationFrame for efficient, browser-optimized animation.
This sequence diagram illustrates the flow of a single frame rendering.
Frame Execution Steps
- Request Frame: The
Canvas.render()method callswindow.requestAnimationFrame(this.render.bind(this))to schedule the next frame. - Timing: At the start of a frame, the time elapsed since the last frame (
timeDifference) is calculated. - Clear Canvas: The entire canvas is cleared using
context.clearRect(). - Root Render: The
render()method of therootcomponent is called with a freshRenderingContext. - Component Tick: Inside a component's
rendermethod,onTick()is called first to update its internal state. - Template Traversal: The component iterates over its
templatearray. For eachTemplateItem, it callsRenderUtils.renderTemplateItem(). - Render Utility:
RenderUtils.renderTemplateItem()checks theshowcondition. If true, it applies any specified transformations (e.g., opacity, rotation, clipping) by manipulating the canvas context's state (save,translate,rotate,globalAlpha,clip,restore). - Recursive Render:
RenderUtilscalls therender()method on the child component, passing a newRenderingContextwith updated parent coordinates. - Native Drawing: This process continues recursively until it reaches a "native" component (like
SpriteorArc). These components do not have templates but instead contain direct drawing commands that use theCanvasRenderingContext2Dfrom theRenderingContext(e.g.,drawImage,fillRect). - FPS Display: If enabled, the
FPSDisplayis rendered on top of the scene.
Sources: lib/Canvas.ts:92-114, lib/Component.ts:46-57, lib/utils/Render.ts:7-80
Component Types
Components can be categorized into two main types: logical and native.
Native Components
Native components are the building blocks that perform the actual drawing on the canvas. They directly call methods on the CanvasRenderingContext2D.
Examples include:
Sprite: Renders a static image. It caches theHTMLImageElementto avoid reloading it on every frame.
Sources: lib/components/native/Sprite.tsAnimatedSprite: Renders a sprite sheet animation, cycling through frames based on a giveninterval.
Sources: lib/components/native/AnimatedSprite.tsArc: Draws an arc or a circle.
Sources: lib/components/native/Arc.tsRectangle: Draws a filled rectangle.Text: Renders text on the canvas.Pattern: Renders a repeating image pattern.
Sources: lib/components/native/Pattern.ts
A key optimization in Sprite and AnimatedSprite is disabling image smoothing to maintain a crisp, pixel-art style.
// lib/components/native/Sprite.ts:28
// Prevent the sprite from being scaled and blurred
context.renderContext.imageSmoothingEnabled = false;
Sources: lib/components/native/Sprite.ts:28-29
Logical Components
Logical components do not draw anything themselves. Instead, they manage the rendering of other components based on application logic.
Repeating: This component takes a list of data and renders a specified component for each item in the list. It is used for dynamically rendering collections of objects, like the smoke and score effects insrc/overlays/Effects.ts.
Sources: lib/components/logical/Repeating.ts, src/overlays/Effects.ts:13-41EventListener: An invisible component that listens forclickevents within its bounding box. It is used to create interactive UI elements like buttons.
Sources: src/screens/StartScreen.ts:88-95
Summary
The Farm Siege rendering pipeline is a robust, component-based system that leverages the HTML Canvas API. Its declarative, hierarchical structure simplifies the management of complex scenes. The flow of data through the RenderingContext ensures that components have all the information they need to render correctly, while the main Canvas class orchestrates the render loop and user input. This architecture provides a clear separation between game logic (in onTick methods) and rendering, making the codebase modular and easier to maintain.