Game Loop and Screens
Relevant source files
The following files were used as context for generating this wiki page:
The game's architecture is centered around a main game loop that drives updates and rendering, and a screen management system that controls which part of the game is visible to the player. The entry point in main.ts initializes a Canvas object, which in turn manages the Game root component. This Game component contains the primary game loop logic and orchestrates the display of different screens like the start menu, the main game, and the game over screen.
State transitions between screens are managed by the ScreensStore, which holds the currently active screen. Components, particularly the screen components themselves, can dispatch actions to this store to trigger a screen change. The main Game component subscribes to this store and conditionally renders the appropriate screen component based on the current state.
Game Initialization and Loop
The game's lifecycle begins in src/main.ts, where a Canvas class is instantiated. The Canvas class is responsible for setting up the HTML5 canvas, managing the render loop via requestAnimationFrame, and propagating user input events to the root component.
Sources: src/main.ts:7-14, lib/Canvas.ts:25-50
Initialization Flow
The following diagram illustrates the initialization sequence of the game.
The Game Component
The Game class is the root component of the application and orchestrates the entire game.
onInit(): Called once upon initialization. It sets up all the data stores (e.g.,GridStore,ScreensStore,StatsStore) and initializes game-wide features like the soundtrack.onTick(): This method is the heart of the game loop, called on every frame by theCanvasrenderer. It receives thetimeDifferencesince the last frame and updates all relevant game logic, such as character movement, game state checks (like the game-over condition), and updating effects.template: A declarative structure that defines which sub-components to render. It uses conditional logic based on theactiveScreenfromScreensStoreto render only the current screen.
Sources: src/Game.ts:40-227
A key responsibility of onTick is to check for the game-over condition: when the number of friendly plants reaches zero. When this occurs, it triggers the game-over sequence.
// src/Game.ts:98-116
protected onTick(_ctx: PropsContext<{}>, timeDifference: number): void {
// ... (store updates)
if (gridStore.friendlyPlants === 0 && !effectsStore.content.gameOver.active) {
const miscStore = <MiscStore>this.stores.misc;
miscStore.fetchHighscores();
miscStore.updateTip();
gridStore.stop();
const center: Coordinates = new Coordinates(
(gridStore.lastRemovedPlant?.x || 3.5) * 128 + 64 + 288,
(gridStore.lastRemovedPlant?.y || 3.5) * 128 + 64 + 176
);
effectsStore.showGameOverAnimation(center, async () => {
movablesStore.stop();
movablesStore.reset();
gridStore.reset();
screensStore.setScreen(Screens.GameOver);
});
}
// ... (other updates)
}
Sources: src/Game.ts:98-116
Screen Management
Screen management is handled by a combination of the ScreensStore, the main Game component, and individual screen components.
ScreensStore
This store is the single source of truth for the currently active screen. It holds the state and provides methods to change it.
| Property | Type | Description |
|---|---|---|
active | Screens | An enum value representing the current screen. |
onReturn | () => void | A callback function that can be set by a screen to define custom return behavior. |
Sources: src/store/ScreensStore.ts:11-15, src/store/ScreensStore.ts:25-29
The available screens are defined in the Screens enum:
// src/store/ScreensStore.ts:8-16
export enum Screens {
Start,
Game,
GameOver,
Help,
Cosmetics,
Credits
}
Sources: src/store/ScreensStore.ts:8-16
Screen State Machine
The flow between different game screens is determined by user actions and game events. The ScreensStore acts as the state controller for this machine.
This diagram shows the primary navigation paths between the game's screens.
Sources: src/Game.ts:133-174, src/screens/StartScreen.ts:29-98, src/screens/GameOverScreen.ts:29-60, src/screens/HelpScreen.ts:46-61
Conditional Rendering
The Game component's template is responsible for rendering the correct screen component based on the activeScreen state from ScreensStore. Each screen is a component in the template array with a show condition.
// src/Game.ts:133-154
protected template: Template = [
{
component: new Background(),
position: (): Coordinates => new Coordinates(0, 0)
},
{
component: new GameScreen(),
position: (): Coordinates => new Coordinates(0, 0),
show: (): boolean => this.activeScreen === Screens.Game
},
{
component: new StartScreen(),
position: (): Coordinates => new Coordinates(0, 0),
show: (): boolean => this.activeScreen === Screens.Start
},
{
component: new GameOverScreen(),
position: (): Coordinates => new Coordinates(0, 0),
show: (): boolean => this.activeScreen === Screens.GameOver
},
// ... other screens
];
Sources: src/Game.ts:133-154
Screen Transitions
Transitions are initiated by calling screensStore.setScreen(Screens.NewScreen). Often, this is done in response to a user event, like a button click.
The StartScreen provides a clear example. When the "Play" button is clicked, the startGame method is invoked. This method resets relevant game stores and then changes the active screen to either Help (for first-time players) or Game.
The sequence diagram below shows the interactions when a user starts a new game.
The onReturn callback is a mechanism for screens like HelpScreen or CreditsScreen to execute logic when the user navigates away from them, allowing the previous screen (StartScreen in this case) to define the subsequent flow.
Sources: src/screens/StartScreen.ts:50-63, src/store/ScreensStore.ts:25-29
Summary
The game loop and screen management system in Farm Siege is a robust, state-driven architecture. The Canvas class provides the fundamental rendering loop, which calls the onTick method of the central Game component. Game.onTick updates the game's logic and state. The display is managed by a set of screen components, whose visibility is controlled by the ScreensStore. This separation of concerns allows for a clean implementation where game logic is decoupled from UI presentation, and screen transitions are handled predictably through a centralized state store.