Component Model
Relevant source files
The following files were used as context for generating this wiki page: - [lib/Component.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/Component.ts) - [lib/Types.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/Types.ts) - [lib/Canvas.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/Canvas.ts) - [lib/components/logical/Repeating.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/components/logical/Repeating.ts) - [src/components/CharacterContainer.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/components/CharacterContainer.ts) - [src/components/ScoreCounter.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/components/ScoreCounter.ts) - [src/overlays/Bullets.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/overlays/Bullets.ts) - [lib/components/native/Text.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/components/native/Text.ts) - [src/components/instructions/Instructions9.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/components/instructions/Instructions9.ts) - [src/components/tileContents/Tomato.ts](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/components/tileContents/Tomato.ts)The FarmSiege Component Model is a declarative, composition-based architecture for rendering and managing all visual elements of the game on an HTML5 Canvas. It allows developers to create complex game objects and UI elements by composing smaller, reusable components. Every visual element, from the player character to UI text and particle effects, is a Component.
This model is centered around the abstract Component class, which provides a common interface for rendering, state management, and lifecycle events. Components define their structure and appearance through a template property, which is an array of child components and their associated properties. This creates a hierarchical tree of components that is rendered on every frame.
Core Concepts
The Component Base Class
All components in the application must extend the abstract Component<P> class, where P is a generic type for the component's props. This class provides the fundamental structure and lifecycle methods.
Sources: lib/Component.ts:10-67
The class hierarchy for components is illustrated below.
Key methods and properties of the Component class are:
| Method/Property | Description |
|---|---|
template | A protected property of type Template that defines the child components to be rendered. |
render() | The main rendering method. It processes the onTick logic and then iterates through the template to render child components. |
onInit() | A protected lifecycle method called only once, on the first render of the component. Used for one-time setup. |
onTick() | A protected lifecycle method called on every render frame. Used for game logic, animation, and state updates. |
propagateEvent() | Propagates browser events (like click, keydown) down the component tree. |
registerStore() | Registers a global state store with the component and its children, making it accessible via this.stores. |
stores | A protected getter that provides access to all registered state stores. |
Sources: lib/Component.ts:11-67
Component Composition via Templates
Components are built by composing other components within a template array. Each item in the template is a TemplateItem object that defines a child component and its rendering properties.
Sources: lib/Types.ts:18-25, lib/Component.ts:11
A TemplateItem has the following structure:
| Property | Type | Description |
|---|---|---|
component | Component<any> | An instance of the child component to render. |
position | (ctx) => Coordinates | A function that returns the Coordinates (x, y) of the child component, relative to its parent. |
props | (ctx) => object | An optional function that returns the props object to be passed to the child component. |
show | (ctx) => boolean | An optional function that determines whether the child component should be rendered. |
transform | (ctx) => TransformationConfig | An optional function for applying transformations like rotation or opacity. |
Sources: lib/Types.ts:18-25
Here is an example of a simple component, Weed, which uses a Sprite component in its template.
// src/components/tileContents/Weed.ts:12-22
export default class Weed extends Component<WeedProps> {
protected template: Template = [
{
component: new Sprite(),
position: (): Coordinates => new Coordinates(0, 0),
props: (): SpriteProps => ({
width: 128,
height: 128,
source: weed
})
}
];
}
Sources: src/components/tileContents/Weed.ts:12-22
Rendering Pipeline
The rendering process is initiated and managed by the Canvas class. It runs a continuous loop using requestAnimationFrame to redraw the entire scene on each frame.
Sources: lib/Canvas.ts:79-108
The rendering flow for a single frame is as follows:
- The main
Canvas.render()method is called. - It clears the canvas and calls the
render()method of therootcomponent. - The
rootcomponent'srender()method first calls its ownonTick()for logic updates. - It then iterates through its
template, callingRenderUtils.renderTemplateItemfor each child. renderTemplateItemchecks theshowcondition, calculates position and props, and recursively calls the child component'srender()method.- This process continues down the tree until it reaches a "native" component (e.g.,
Text,Sprite) which performs the actual drawing operation on the canvas context.
This diagram illustrates the top-down rendering flow.
Sources: lib/Canvas.ts:79-108, lib/Component.ts:53-67
Component Types
Components can be broadly categorized into two types: Native and Logical.
Native Components
Native components are the fundamental building blocks that perform direct drawing operations on the canvas. They do not have a template themselves but implement a render method that uses the CanvasRenderingContext2D API.
Examples include:
Text: Renders text on the canvas. Sources: lib/components/native/Text.tsSprite: Renders an image. Sources: src/components/tileContents/Weed.ts:14AnimatedSprite: Renders a sprite sheet as an animation. Sources: src/components/Smoke.ts:14Rectangle: Renders a filled rectangle. Sources: src/components/ScoreCounter.ts:15Arc: Renders an arc or a circle. Sources: lib/components/native/Arc.ts
Logical Components
Logical components do not render anything directly. Instead, they manage the rendering of other components, often based on application state or logic. They are used for control flow, such as loops and conditionals.
The Repeating Component
The most prominent logical component is Repeating. It takes a list (an array of data) and renders a specified component for each item in the list. This is highly useful for rendering dynamic collections of objects like bullets, enemies, or high scores.
Sources: lib/components/logical/Repeating.ts
The Repeating component's props are:
| Prop | Description |
|---|---|
list | The array of data to iterate over. |
component | A function that returns a new component instance for an item. |
position | A function that calculates the position for each item's component. |
props | A function that calculates the props for each item's component. |
Sources: lib/components/logical/Repeating.ts:9-14
The Bullets overlay uses Repeating to render a Rectangle for each active bullet stored in the CharacterStore.
// src/overlays/Bullets.ts:14-29
export default class Bullets extends Component<BulletsProps> {
protected template: Template = [
{
component: new Repeating(),
position: (): Coordinates => new Coordinates(0, 0),
props: (): RepeatingProps => {
const characterStore = <CharacterStore>this.stores.character;
return {
list: characterStore.content.bullets,
component: (): Rectangle => new Rectangle(),
position: (data: BulletData): Coordinates => new Coordinates(data.x, data.y),
props: (): RectangleProps => ({
color: '#000',
width: 12,
height: 12
})
};
}
}
];
}
Sources: src/overlays/Bullets.ts:14-29
State Management and Data Flow
Components are designed to be stateless where possible, receiving data through props. Global application state is managed in Store objects (e.g., CharacterStore, StatsStore).
Components gain access to these stores via the registerStore method, which is typically called at the application's root. Once registered, a component can access any store via this.stores. This allows components to read from the global state to determine what to render.
For example, ScoreCounter accesses this.stores.score to get the current score and display it using a Text component.
// src/components/ScoreCounter.ts:25-35
{
component: new Text(),
position: (): Coordinates => new Coordinates(2, 30),
props: (): TextProps => {
const statsStore = <StatsStore>this.stores.score;
return {
text: `${statsStore.content.score} Points`,
// ... other props
};
}
}
Sources: src/components/ScoreCounter.ts:25-35, lib/Component.ts:26-34
Component Lifecycle and Logic
The component model provides hooks for running logic at different points in a component's life.
onInit(): Called once before the first render. Ideal for setting up event listeners or initial state specific to the component, as seen inCharacterContainerwhere it initializes theInputMap. Sources: src/components/CharacterContainer.ts:41-65, lib/Component.ts:22-24onTick(ctx, timeDifference): Called on every frame. This is the primary place for implementing component-specific logic like updating timers for animations, handling movement, or checking for state changes.Instructions9usesonTickto manage a timer that controls the visibility of different animated elements. Sources: src/components/instructions/Instructions9.ts:14-16, lib/Component.ts:16-21