Getting started
This page builds a small program: a window, a background, a player sprite you move with the arrow keys, and a score label. It takes about ten minutes.
1. Make a window project
sh
thicket new mygame window
cd mygameEdit thicket.json5. List the willow system 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, bgColor: "#0E1E3C" },
permissions: { fs: true },
}A project with a window section is a window app. Without one it is a console app.
2. Put a sprite on screen
Put an image at assets/player.png. Replace app.tks with:
ts
import { Sprite } from "willow";
class MyGame {
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);
}
}Run it with thicket run. The image sits in the middle of the window.
new Sprite(path)loads the image and makes a node that draws it.setAnchor(0.5, 0.5)moves the sprite's origin to its center, soxandyplace the middle of the image.engine.root2d.add(player)puts the sprite in the 2D tree. Nodes outside the tree are not drawn.
Don't name your class App
App is the built-in lifecycle class. A class of your own called App hides it in that file, and App.onFrame stops working. Name your entry class after your game.
3. Move it with the keyboard
App.onFrame runs a function once per drawn frame. Keyboard and Key are global, so there is nothing to import.
ts
import { Sprite } from "willow";
class MyGame {
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 = 240;
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;
}
});
}
}dt is the time since the last frame, in seconds. Multiplying by it moves the player the same distance per second at any frame rate.
Keyboard.isDown is true while a key is held. Keyboard.isPressed is true only on the frame it goes down, which is what you want for a jump or a menu choice.
4. Add a background and a label
Nodes draw in tree order: a node added later draws on top. Add the background first, then the player, then the label.
ts
import { Sprite, Quad, Text, Font } from "willow";
class MyGame {
static main(): void {
const ground: Quad = new Quad(800, 120, Color.hex("#2E5E3A"));
ground.y = 480;
engine.root2d.add(ground);
const player: Sprite = new Sprite("assets/player.png");
player.setAnchor(0.5, 1.0);
player.x = 400;
player.y = 480;
engine.root2d.add(player);
const label: Text = new Text("Coins: 0", Font.system());
label.fontSize = 24;
label.color = Color.WHITE;
label.x = 16;
label.y = 16;
engine.root2d.add(label);
let coins: int = 0;
const SPEED: float = 240;
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.isPressed(Key.SPACE)) {
coins += 1;
label.text = "Coins: " + coins;
}
});
}
}Quadis a solid rectangle.Color.hex("#2E5E3A")makes a colour from a hex string.setAnchor(0.5, 1.0)puts the origin at the bottom middle of the sprite, soyis where its feet stand.Font.system()is willow's bundled default font. Assigninglabel.textre-lays out the text on the next frame.
5. Game logic on fixed ticks
App.onFrame runs once per drawn frame, so it runs more often on a faster screen. For logic that should run at a steady rate, use App.onTick. Ticks run 60 times a second by default (App.tps).
ts
import { Sprite } from "willow";
class MyGame {
static main(): void {
const player: Sprite = new Sprite("assets/player.png");
player.setAnchor(0.5, 0.5);
player.x = 400;
player.y = 300;
player.interpolate = true;
engine.root2d.add(player);
const STEP: float = 4;
App.onTick((): void => {
if (Keyboard.isDown(Key.RIGHT)) {
player.x += STEP;
}
if (Keyboard.isDown(Key.LEFT)) {
player.x -= STEP;
}
});
}
}interpolate = true makes willow draw the sprite between its last two tick positions, so it moves smoothly even when the screen refreshes faster than the tick rate. See Lifecycle and time.
Next
- The scene graph explains the tree, the world and screen roots, and layers.
- Sprites and textures covers sharing textures, atlases and animation.
- Input and hit testing makes nodes clickable and draggable.
- Recipes has short answers to common tasks.