Creating New Game Components
Relevant source files
The following files were used as context for generating this wiki page:This document outlines the architecture and process for creating new game components in the Farm Siege engine. The engine uses a declarative, component-based architecture inspired by Flutter, where the entire game is a tree of components, starting from the root Game component.
Every visual or logical element in the game is a Component. Components are self-contained, reusable pieces of code that define a part of the game's UI, logic, or behavior. They can be composed to build complex screens and interactions. Understanding this component model is essential for extending the game's functionality.
The Component Base Class
All components in the game must extend the abstract Component class. This class provides the core structure, lifecycle methods, and state management access for all its descendants.
Sources: lib/Component.ts
Core Properties
| Property | Type | Description | Source |
|---|---|---|---|
stores | Record<string, Store<any>> | An object containing all registered data stores, allowing the component to access and react to global game state. | [lib/Component.ts:24](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/Component.ts#L24) |
template | Template | A protected array that declaratively defines the child components this component will render. This is the core of the compositional pattern. | [lib/Component.ts:27](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/Component.ts#L27) |
isMounted | boolean | A flag indicating whether the component is currently part of the active component tree. | [lib/Component.ts:22](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/Component.ts#L22) |
isDestroyed | boolean | A flag indicating whether the component has been destroyed and should be cleaned up. | [lib/Component.ts:23](https://github.com/wotschofsky/farmsiege/blob/HEAD/lib/Component.ts#L23) |
Component Lifecycle
Components have a defined lifecycle managed by the game engine. You can hook into this lifecycle by overriding specific methods to initialize state, perform updates, and clean up resources.
The following diagram illustrates the component lifecycle flow.
Sources: lib/Component.ts:33-51
onInit(): Called once, immediately after the component is instantiated. It's the ideal place for one-time setup, such as initializing anInputMapor subscribing to stores.
Sources: lib/Component.ts:33, src/components/CharacterContainer.ts:28-54onMount(): Called when the component is added to the active component tree.
Sources: lib/Component.ts:39onTick(timeDifference: number): Called on every frame (tick) of the game loop while the component is mounted. This is where most of the component's dynamic logic, such as movement, animation timers, or state checks, should reside. ThetimeDifferenceparameter provides the delta time in milliseconds since the last tick.
Sources: lib/Component.ts:36, src/components/CharacterContainer.ts:56onDestroy(): Called when the component is about to be removed from the component tree. Use this to clean up resources, such as disabling input maps or clearing timers.
Sources: lib/Component.ts:42, src/components/CharacterContainer.ts:51-53
Defining a Component's Structure
A component's structure and children are defined declaratively using the template property. This property is an array of objects, where each object represents a child component to be rendered.
Sources: lib/Component.ts:27, lib/Types.ts:1-12
The Template Type
The template is an array of TemplateEntry objects. Each entry configures a child component.
// From lib/Types.ts
export type Template = {
component: Component<any>;
position: (ctx?: PropsContext<any>) => Coordinates;
props?: (ctx?: PropsContext<any>) => any;
show?: (ctx?: PropsContext<any>) => boolean;
transform?: (ctx?: PropsContext<any>) => TransformationConfig;
}[];
Sources: lib/Types.ts:1-12
| Key | Description | Example Source |
|---|---|---|
component | An instance of the child component class. | [src/components/Logo.ts:22](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/components/Logo.ts#L22) |
position | A function that returns a Coordinates object for the child's position. This is re-evaluated on each tick, allowing for dynamic positioning. | [src/screens/StartScreen.ts:103](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/screens/StartScreen.ts#L103) |
props | An optional function that returns an object of properties to be passed to the child component. This is the primary way to pass data down the component tree. | [src/components/grid/GridTile.ts:10-15](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/components/grid/GridTile.ts#L10-L15) |
show | An optional function that returns a boolean. If it returns false, the component (and its entire subtree) will not be rendered or ticked. This is used for conditional rendering. | [src/components/Logo.ts:32-36](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/components/Logo.ts#L32-L36) |
transform | An optional function to apply transformations like opacity or clipping. | [src/overlays/SplashScreen.ts:58-62](https://github.com/wotschofsky/farmsiege/blob/HEAD/src/overlays/SplashScreen.ts#L58-L62) |
Example: The Logo Component
The Logo component is a simple composite component that combines two Sprite components to form the game's logo. Its template demonstrates the basic principles of composition and conditional rendering.
// From src/components/Logo.ts
export default class Logo extends Component<LogoProps> {
// ... helper methods
protected template: Template = [
{
component: new Sprite(),
position: (): Coordinates => new Coordinates(0, 0),
props: (): SpriteProps => ({
source: logoFarmSprite,
width: 600,
height: 152.5
}),
show: (ctx: PropsContext<LogoProps> | undefined): boolean => {
if (!ctx) return false;
if (ctx.props.progress === undefined) return true;
return this.getPartsShown(ctx.props.progress) >= 1;
}
},
{
component: new Sprite(),
position: (): Coordinates => new Coordinates(0, 148),
// ... props
show: (ctx: PropsContext<LogoProps> | undefined): boolean => {
// ... logic
return this.getPartsShown(ctx.props.progress) >= 2;
}
}
];
}
Sources: src/components/Logo.ts:17-48
This diagram shows the component tree for the Logo component.
Component Types
Components can be broadly categorized into visual and logical types.
Visual Components
These are components that render directly to the canvas. The engine provides several "native" visual components.
Sprite: Renders an image.Text: Renders text with specified font, size, and color.Rectangle: Renders a simple colored rectangle.
Custom components like Logo, GridTile, or Weed are also visual components, but they are composed of other components, including these native ones.
Sources: src/components/Logo.ts, src/components/grid/GridTile.ts, src/components/tileContents/Weed.ts
Logical Components
These components do not render anything themselves but provide behavior or control flow.
EventListener: Creates a clickable area. When clicked, it invokes theonClickfunction passed in its props. This is the primary mechanism for handling user input.
Sources: lib/components/logical/EventListener.ts, src/screens/StartScreen.ts:108-112Repeating: Renders a list of components based on a data array. It takes alistand acomponentfactory function, then instantiates and renders the component for each item in the list. This is useful for rendering dynamic collections like bullets or particle effects.
Sources: lib/components/logical/Repeating.ts, src/overlays/Effects.ts:16-27
The following sequence diagram shows how an EventListener handles a click on the "Play" button in the StartScreen.
Conclusion
The component system in Farm Siege provides a powerful and declarative way to build game screens and entities. By extending the base Component class, developers can create reusable and isolated pieces of functionality. The template system allows for complex UIs to be composed from simpler parts, and the lifecycle methods (onInit, onTick) provide clear entry points for implementing game logic. Logical components like EventListener and Repeating further enhance this system by abstracting common patterns like user input and list rendering.