๐พ Easily make 2D browser games with pixi.js
- 100% Type-Safe
- Batteries included (get started using one CLI command)
- Reactive - Re-render view when state changes
- Object pooling
- Timer - Run logic every X ticks
- Animations
- Debug overlay
- Keyboard input
- Sounds
- Sprite sheet generation
- Scenes
- CLI - Create project and components
- CI - Workflows to build, test and deploy to
itch.io - Screen shake
- Uses
vitefor a super fast and modern dev server
Run the init command from within a Git repo
npx alchemy-engine@latest initThis will:
- Copy template files
- Add
alchemy-engineandpixi.jsas dependencies
These are imported directly from alchemy-engine
Convenience functions to create Pixi objects
import { sprite } from 'alchemy-engine'
sprite(container, getTexture('./square-1')])import { animatedSprite } from 'alchemy-engine'
animatedSprite(container, getTextures(['./square-1', './square-2']))import { text } from 'alchemy-engine'
text(container, textStyle, 'Hello world')import { htmlText } from 'alchemy-engine'
htmlText(container, textStyle, 'Hello world')import { bitmapText } from 'alchemy-engine'
bitmapText(container, textStyle, 'Hello world')import { container } from 'alchemy-engine'
container(_container)import { graphics } from 'alchemy-engine'
graphics(container)import { rectangle } from 'alchemy-engine'
rectangle(container, { x: 0, y: 0, width: 10, height: 10 })Convenience functions for mouse input
import { onClick } from 'alchemy-engine'
onClick(container, () => {
console.log('Clicked!')
})import { onHover } from 'alchemy-engine'
onHover(container, {
onOver() {
console.log('Hovered!')
},
onOut() {
console.log('Not hovered!')
},
})Constants for all arrow keys
import { arrowKeys } from 'alchemy-engine'
export const keys = ['a', 'w', 's', 'd', ...arrowKeys] as constTODO
TODO
TODO
Nicely log a Pixi object. Set label property for best result.
import { logObject } from 'alchemy-engine'
const sprite = new Sprite()
sprite.label = 'sprite'
logObject(sprite)Enables easier logging of sprite bounds
import { boundsToString } from 'alchemy-engine'
console.log(boundsToString(sprite))Check if a point is within the bounds of an object
import { contains } from 'alchemy-engine'
if (contains(sprite, { x: 1, y: 1 })) {
// point is within bounds of sprite
}Check if the bounds of two objects are intersecting
import { intersects } from 'alchemy-engine'
if (intersects(sprite1, sprite2)) {
// sprites are intersecting
}import { isAnimatedSprite } from 'alchemy-engine'
if (isAnimatedSprite(sprite)) {
// sprite is of type AnimatedSprite
}This function can be used to for example load a level from image data
import { loadDataFromImage } from 'alchemy-engine'
import map from '~/public/asset/map.png?url'
const { pixels, width, height } = await loadDataFromImage(map)
console.log(pixels)
// ['255-255-255', '0-0-0']The arguments passed to a scene
{
textures,
container,
input,
state,
timer,
sound,
app,
timer,
useScreenShake,
}: SceneGet a texture
function myScene(scene: Scene) {
sprite(scene.container, scene.getTexture('./texture-1'))
}Get multiple textures
function myScene(scene: Scene) {
animatedSprite(
scene.container,
scene.getTextures(['./texture-1', './texture-2']),
)
}Enable the use of screen shake
screenShake takes a number between 0 and 1
const screenShake = useScreenShake(container)
screenShake(0.5)These functions all require an onUpdate and duration argument
Optionally you can pass a startValue (default: 0) and endValue (default: 1)
- sine
- easeOut
- easeIn
- linear
The Pixi Application instance
Record<MusicName, Howl>Example:
scene.music.bgm.loop(true).play()Record<SoundName, Howl>Example:
scene.sound.coin.play()Takes sceneKey as an argument
setScene('mainMenu')timer
A timer that doesn't get cancelled when changing scenes
A scene specific Pixi container. Will be destroyed when scene is changed.
Set state to trigger sync and subscribe functions
Re-exported from valtio
Changes from valtio versions:
- Triggers once up front when called
- Unsubscribes when changing scene
delay
Resolves a promise after X ticks
// Wait 100 updates
await scene.timer.delay(100)repeatUntil
Execute a callback every update until duration is reached
await scene.timer.repeatUntil(3, (time, deltaTime) => {})repeatEvery
Execute a callback indefinitely every interval updates
Returns a cancel function
const cancel = repeatEvery(3, (time, deltaTime) => {})debouncedKey
scene.input.debouncedKey(
'd',
() => {
s.position.x += 1
},
// Delay between key presses
10,
)isKeyDown
Check if a key is currently being pressed
scene.timer.repeatEvery(1, () => {
if (scene.input.isKeyDown(['a', 'ArrowLeft'])) {
s.position.x -= 1
}
if (scene.input.isKeyDown(['d', 'ArrowRight'])) {
s.position.x += 1
}
})There is a built-in seedable random module.
To set the seed, pass it in to createGame
createGame({
randomSeed: 99,
})The module uses the same API as park-miller, with some additions:
chance(percentage) => boolean
Start the dev server. Listens to changes to source code, sprite, src/public/asset/sound and src/public/asset/music folders.
npx alchemy devGenerate sprite sheet
npx alchemy spriteLoad sounds
npx alchemy soundRun ./go.sh to test that things work
- State machine: xstate
- Noise simplex-noise
- 2D Vector vcr-2d