Skip to content

Your first window ​

A project becomes a window app when its thicket.json has a window section. Without one it is a console app.

Make the project ​

sh
thicket new mygame window
cd mygame

Open thicket.json5 and give the window a title and a size. List the willow system, which is the 2D module, and turn on the fs permission so the game can load images from disk:

json5
{
    entry: "app.tks",
    systems: ["willow"],
    window: { title: "My Game", width: 800, height: 600 },
    permissions: { fs: true },
}

window: {} also works and uses every default.

Show a sprite ​

Put an image at assets/player.png, then replace app.tks with this:

ts
import { Sprite } from "willow";

class Game {
    static main(): void {
        const player: Sprite = new Sprite("assets/player.png");
        player.setAnchor(0.5, 0.5);
        player.x = 400;
        player.y = 300;
        engine.root2d.add(player);

        const SPEED: float = 200;

        App.onFrame((dt: float): void => {
            if (Keyboard.isDown(Key.RIGHT)) {
                player.x += SPEED * dt;
            }
            if (Keyboard.isDown(Key.LEFT)) {
                player.x -= SPEED * dt;
            }
            if (Keyboard.isDown(Key.DOWN)) {
                player.y += SPEED * dt;
            }
            if (Keyboard.isDown(Key.UP)) {
                player.y -= SPEED * dt;
            }
            player.rotation += Math.PI * dt;
        });
    }
}

Run it:

sh
thicket run

The window opens with the sprite in the middle, spinning half a turn per second. The arrow keys move it.

What is going on ​

  • new Sprite(path) loads the image and makes a display node that draws it.
  • setAnchor(0.5, 0.5) puts the sprite's origin at its center, so x and y place the middle of the image and rotation turns it around its center.
  • engine.root2d is the root of the 2D display tree. Anything you add to it is drawn every frame.
  • App.onFrame runs your function once per drawn frame. dt is the time since the last frame, in seconds, so SPEED * dt moves the same distance per second at any frame rate.
  • Keyboard and Key are global. You do not import them. Keyboard.isDown is true while a key is held. Keyboard.isPressed is true only on the frame the key goes down.
  • rotation is in radians.

Avoid naming your class App

App is the built-in lifecycle class. A class of your own called App hides it, and App.onFrame stops working in that file. Name your entry class after your game instead.

Game logic on fixed ticks ​

App.onFrame runs once per drawn frame, so it runs more often on a faster screen. For game logic that should run at a steady rate, use App.onTick. Ticks run 60 times a second by default (App.tps). Draw-only work, like smoothing an animation, stays in onFrame.

Learn more about the 2D module in willow.

Next: checking your code.