# Welcome
Odyc.js is a tiny JavaScript library designed to create narrative games by combining pixels, sounds, text, and a bit of logic.
Everything is built **through code**, but without unnecessary complexity: your entire game can fit in a single file.
---
## One Function to Make a Game
Creating a game with Odyc.js is as simple as calling `createGame()`.
You provide your sprites, dialogs, sounds, and interactions โ and the engine takes care of the rest.
```js
createGame({
player: {
sprite: 7,
position: [2, 5]
},
map: `
########
#......#
#..X...#
#......#
########
`,
templates: {
X: {
sprite: 4,
dialog: 'Hello, adventurer!'
}
}
})
```
---
## Where to Start?
The documentation is organized into **four main sections**:
### ๐งฑ World Building
Define the player, the map, sprites, sounds, and dialogs.
### โ๏ธ Interaction & Logic
Make your world interactive using events and game actions.
### ๐จ Configuration
Customize the appearance, filters, controls, colors, and screen.
### ๐ง Helpers
Functions to make your life easier when developing with Odyc.
---
## Questions or Ideas?
Odyc.js is an open and free project.
Youโre welcome to contribute to the documentation, suggest ideas, or ask questions on [GitHub](https://github.com/achtaitaipai/odyc) or via email at [slt@charlescailleteau.com](mailto:slt@charlescailleteau.com).
---
๐ซ **Have fun!**
---
# Getting Started with Odyc.js
Want to create a game quickly? Here are **three ways to start**, depending on your preferences:
---
## Use the Online Editor
The easiest way to get started is to use the [online editor](/playground).
No setup required: just open the link and start coding your game directly in your browser.
---
## Use a CDN
If you prefer working locally **without a complex setup**, you can import Odyc.js from a CDN.
1. Create an `index.html` file
2. Paste the following code:
```html
```
3. Open the file in your browser.
---
## Use a Bundler (Vite, Webpackโฆ)
For more advanced projects, you can install Odyc.js via npm:
```bash
npm install odyc
```
Then in your main JavaScript or TypeScript file:
```js
import { createGame } from 'odyc'
const game = createGame({
title: 'My Awesome Game'
})
```
---
# The Player
The `player` is the character you control in the game. It is defined by two properties: **its appearance** and **its starting position**.
---
## Player Appearance
The playerโs appearance is set using the `sprite` property. It can be:
### A number between `0` and `9`
In this case, the player will be shown as a solid-colored rectangle using the corresponding color:
```js
createGame({
player: {
sprite: 7
}
})
```
### A string
This allows you to define a more complex sprite, line by line. Each digit corresponds to a color, and `.` represents a transparent pixel:
```js
createGame({
player: {
sprite: `
...44...
...88...
...88...
.434434.
4.3443.4
1.3333.1
..3333..
..3..3..
`
}
})
```
---
## Starting Position
You can define the playerโs initial position on the map using the `position` key.
It should be an array in the form `[x, y]`, where `x` is the column and `y` is the row in the grid.
For example, `[2, 5]` places the character in the 2nd column and 5th row (from the top):
```js
createGame({
player: {
sprite: 7,
position: [3, 4]
}
})
```
By default, the player appears at the top-left corner of the map, at position `[0, 0]`.
---
## Player Visibility
You can control whether the player is visible using the `visible` property:
```js
createGame({
player: {
sprite: 7,
visible: false
}
})
```
By default, the player is visible (`visible: true`).
---
# Sprites
Like everything else in Odyc.js, sprites are defined directly in code.
Theyโre described using **strings**, a bit like _ASCII art_.
```js
createGame({
player: {
sprite: `
...55...
...55...
.000000.
0.0000.0
5.0000.5
..3333..
..3..3..
..0..0..
`
}
//...
})
```
---
## A Simple Colored Block
If you want an element to appear as a plain colored rectangle, just assign a single character that corresponds to a palette color:
```js
sprite: '5'
```
---
## Drawing Sprites
Sometimes itโs easier to draw than to explain.
Use the editor below to try out how sprite definitions work.
On one side, you can draw; on the other, youโll see the code string that represents your sprite.
Each line represents a row of pixels, and each character is a pixel:
- **Characters `0โ9`, `aโz`, `AโZ`**: correspond to entries in your palette (up to 62 colors total)
- **Newline**: starts a new row
- **Spaces, tabs, blank lines**: are ignored
- **Other characters**: represent transparent pixels (e.g. `.`)
---
## Resources
Here are useful resources for generating or browsing sprite ideas:
- [Pixeltwist](https://pixeltwist.achtaitaipai.com/) โ an infinite stream of random sprites.
- [Baxel](https://baxel.achtaitaipai.com/) โ a growing, open collection of community-created sprites.
- [odyc-cli](https://github.com/Meldiron/odyc-cli) by Meldiron โ a command-line tool for scaffolding Odyc.js projects, converting images to sprites, and streamlining game development workflow.
---
# Templates and the Map
`templates` define all the objects in your game โ obstacles, items, characters, etc.
Each template is associated with a unique character (e.g. `"X"`, `"$"`, `"e"`, `"#"`).
You can then assign a set of properties to each template and place them in the `map`.
---
## Template properties
Each template accepts the following properties:
| Property | Default value | Description |
| ------------ | ------------- | ---------------------------------------------------------------------------------------------------------- |
| `solid` | `false` | Determines whether the player can pass through the object. |
| `visible` | `true` | Whether the sprite is visible or not. |
| `foreground` | `false` | Defines whether the sprite should be drawn in front of the player. |
| `sprite` | None | Defines the visual appearance of the object ([see sprites](/doc/world-building/sprites)). |
| `sound` | None | Sound played when interacting with the object ([see sounds](/doc/world-building/sounds)). |
| `dialog` | None | Dialog displayed when the player interacts with the object ([see dialogs](/doc/world-building/dialogues)). |
| `end` | None | Ends the game with a custom message ([see title & end screen](/doc/world-building/title-and-end)). |
```js
createGame({
templates: {
r: {
sprite: 6,
sound: ['HIT', 999],
visible: false,
end: 'Game Over'
},
g: {
sprite: 7,
dialog: "I'm grass.",
solid: false
}
}
})
```
---
## The Map
The `map` defines how objects are arranged in the world using an ASCII grid.
Each character in the grid corresponds to a `template`.
๐ก Creating a map is a lot like drawing a sprite!
- Each character defined in `templates` is interpreted.
- Spaces, tabs, and empty lines are ignored.
- Any undefined character is treated as an empty cell.
```js
createGame({
templates: {
x: { sprite: 0 },
g: { sprite: 7 },
r: { sprite: 4 }
},
map: `
xxxxxxxxxxxxxxxx
x..............x
x...........g..x
x..............x
x..............x
x....r.........x
x..............x
xxxxxxxxxxxxxxxx
`
})
```
---
## Dynamic Templates
A `template` doesnโt have to be a fixed object โ
you can also define it as a **function that returns an object**.
This is useful when you want to create elements that are **slightly different each time they appear**.
The function receives the **position of the cell** as an argument with the shape `[number, number]`.
For example, to create a wall where **each instance has a different color**:
```js
createGame({
templates: {
x: (position) => ({
sprite: Math.floor(Math.random() * 9)
})
}
//...
})
```
The function is called **every time an `x` element is placed on the map**.
---
# Sounds
Odyc.js uses **PFXR**, a lightweight JavaScript library made specifically for generating procedural sound effects. It allows you to create, customize, and play very compact audio assets.
---
## Defining a sound in a template
A sound can be associated with a game element using the `sound` key. It will automatically be played when the player interacts with that element.
```js
templates: {
E: {
sprite: 3,
sound: "HIT"
}
}
```
---
## Accepted formats
The `sound` key accepts several formats:
### A preset name
```js
sound: 'FALL'
```
Here's a list of available presets. Click a name to hear an example. Each click generates a random variation.
Be careful with `RANDOM` โ turn your volume down before clicking.
### An array `[preset, seed]`
If you want the sound to always be the same, you can specify a number (seed):
```js
sound: ['LASER', 12345]
```
You can use the Sound tool in the playground to find the perfect sound.
### A PFXR URL
You can create a custom sound using [the Pfxr interface](https://achtaitaipai.github.io/pfxr/) and paste the generated URL:
```js
sound: 'https://achtaitaipai.github.io/pfxr/?fx=1,0.3,...'
```
### A configuration object
For full control, use an object describing the sound parameters:
```js
sound: {
waveForm: 2,
frequency: 440,
sustainTime: 0.2,
decayTime: 0.5
}
```
You can find the complete list of parameters [here](https://github.com/achtaitaipai/pfxr/tree/main/packages/synth#sound).
---
## Global volume
The overall sound volume can be adjusted via the `volume` key in the initial game configuration:
```js
createGame({
volume: 0.8
})
```
Default value: `0.5`
---
# Dialogues
Dialogues let you add conversations, hints, or narrative elements to your game.
You can also enhance them with visual effects and color highlights.
---
## Defining a dialogue
To assign a dialogue to a game element, use the `dialog` property in the `templates` section:
```javascript
createGame({
templates: {
'@': {
dialog: 'Hello world!'
}
}
})
```
Every time the player interacts with the `@` element, a dialogue box will display `Hello world!`.
---
## Splitting a dialogue
To force a line break, use the `|` character.
```js
'Hello!|How are you?|Have a great day!'
```
---
## Adding effects and colors to text
You can make your dialogue more expressive with simple syntax for **visual effects** and **color changes**.
### Effects
| Effect | Syntax | Description |
| ---------------- | ------------- | ------------------------- |
| Vertical wave | `~your text~` | Letters move up and down |
| Horizontal wave | `_your text_` | Letters wave side to side |
| Random shake | `your %text%` | Chaotic shaking |
| Horizontal shake | `your =text=` | Left-right shaking |
| Vertical shake | `your ^text^` | Up-down shaking |
| Blinking | `your ยฐtextยฐ` | Flashing on/off |
### Colors
To apply a color, wrap text in ``, where `c` is a character representing a color from [your palette](/doc/configuration/colors#customizing-the-palette):
```js
'<3>Blue text<3>'
```
You can also combine effects and colors:
```js
'~<2>Gray text with wave effect<2>~'
```
---
## Displaying special characters
To display a reserved character (like `%`, `~`, `_`, `^`, `=`, `ยฐ`), escape it with **two backslashes** `\\`:
```js
'I only have 56\\% battery left'
```
will display:
_I only have 56% battery left_
---
## Dialogue Speed
The display speed of dialogues can be adjusted in the initial game configuration using the `dialogSpeed` key:
```js
createGame({
//...
dialogSpeed: 'FAST'
})
```
Available values:
- `'SLOW'` - Slow typing speed
- `'NORMAL'` - Normal typing speed (default)
- `'FAST'` - Fast typing speed
---
# Title & End Screen
The message box is used to display information like the **game title** when starting up, or an **end message** when the player wins or loses.
---
## Title screen
When the game starts, you can show a **title screen** using the `title` property:
```js
createGame({
title: 'My Awesome Game'
})
```
To add **line breaks**, use a multiline string:
```js
createGame({
title: `** AN AWESOME GAME **
by John Doe`
})
```
---
## Ending the game
To end the game when the player touches a specific element, use the `end` property in a `template`.
The message will be shown, and then the game will restart from the beginning.
```js
createGame({
templates: {
X: {
sprite: 2,
end: 'The End...'
}
}
})
```
---
## Showing multiple messages
You can display several messages in sequence by using an array of strings:
```js
createGame({
title: ['An awesome game', 'by John Doe']
})
```
```js
createGame({
templates: {
X: {
sprite: 2,
end: ['You lost', 'Game Over...']
}
}
})
```
---
## Add effects and colors
Just like with dialogues, you can enhance the text with **colors** and **animations**.
---
# Events
To add interactivity, Odyc.js provides a simple event system.
It lets you trigger actions or modify the game state.
---
## Template Events
### Available event types
There are nine types of events:
- **`onCollideStart`** โ called when the player **collides** with the element, occurs before dialog, sound and end
- **`onCollide`** โ called when the player **collides** with the element
- **`onEnterStart`** โ called when the player **steps onto a tile** containing the element, occurs before dialog, sound and end
- **`onEnter`** โ called when the player **steps onto a tile** containing the element
- **`onLeave`** โ called when the player **leaves a tile** containing the element
- **`onScreenEnter`** โ called when the element **enters the visible screen**
- **`onScreenLeave`** โ called when the element **leaves the screen**
- **`onTurn`** โ called at the end of each turn, after the player attempted to move
- **`onMessage`** - called via the `sendMessageToCells` method
```js
createGame({
templates: {
x: {
onCollideStart() {
alert('collide start')
},
onCollide() {
alert(1)
},
onEnterStart() {
alert('enter start')
},
onEnter() {
alert(2)
},
onLeave() {
alert(3)
},
onTurn() {
alert(4)
},
onScreenEnter() {
alert('hi')
},
onScreenLeave() {
alert('bye')
},
onMessage() {
alert('5 / 5')
}
}
}
})
```
---
### Target of the event
When an event is triggered, the affected object is passed as a parameter.
You can use it to **modify or remove the element dynamically**.
```js
createGame({
templates: {
x: {
onCollide(target) {
target.remove()
}
}
}
})
```
---
### Available properties
| Property / Method | Type | Description | Read-only |
| ----------------- | ---------------------- | --------------------------------------------------- | --------- |
| `solid` | `boolean` | Whether the object is passable | No |
| `visible` | `boolean` | Whether the object is visible | No |
| `sprite` | `number` \| `string` | Changes the objectโs appearance | No |
| `sound` | `string`\| `object` | Changes the sound played on interaction | No |
| `dialog` | `string` \| `string[]` | Modifies the dialog text | No |
| `end` | `string` \| `string[]` | Triggers a custom game ending | No |
| `symbol` | `string` | The character representing the object in the map | Yes |
| `position` | `[number, number]` | `[x, y]` position of the object on the grid | Yes |
| `isOnScreen` | `boolean` | `true` if the object is currently visible on screen | Yes |
| `remove` | `() => void` | Removes the element from the game | โ |
| `moveTo` | `(x, y) => void` | Moves the element to the specified position | โ |
### Example: change a property
Letโs create a character who says "Hello" the first time, then "Hello again" afterwards:
```js
createGame({
templates: {
x: {
dialog: 'Hello',
onCollide(target) {
target.dialog = 'Hello again'
}
}
}
})
```
### Remove an element
To remove an element's properties when touched, use the `remove()` method:
```js
createGame({
templates: {
x: {
onCollide(target) {
target.remove()
}
}
}
})
```
### Move an element
To move an element to a new position, use `moveTo(x, y)`:
```js
createGame({
templates: {
x: {
onCollide(target) {
target.moveTo(3, 2)
}
}
}
})
```
### onMessage
`onMessage` differs slightly from other events in that you trigger it yourself via `game.sendMessageToCells`. The `onMessage` method is called with two arguments: the event target and the message:
```js
const game = createGame({
templates: {
x: {
sprite: 1,
onMessage(target, message) {
if (message === 'turnOff') target.sprite = 0
else if (message === 'turnOn') target.sprite = 1
}
}
}
})
game.sendMessageToCells({ symbols: 'x' }, 'turnOff')
```
---
## Player Events
### `player.onInput`
The `onInput` event is triggered in the following cases:
- when a **direction key is pressed** (or a swipe on a touch screen),
- or when an **action key is used** (`Enter`, `Space`, or a tap on mobile).
```js
createGame({
player: {
onInput(input) {
console.log(input)
}
}
})
```
The function receives an `input` argument, which can be one of: `UP`, `RIGHT`, `DOWN`, `LEFT`, or `ACTION`.
---
### `player.onTurn`
The `onTurn` event is called at the end of each turn, after the player attempted to move.
```js
createGame({
player: {
sprite: '0',
onTurn(player) {
player.sprite = Math.floor(Math.random() * 9)
}
}
})
```
The function receives a `player` argument similar to [`game.player`](/en/doc/interaction-and-logic/game-state#player)
---
# Game Actions
The `game` object exposes several methods to trigger visual or audio effects: show a message, open a dialogue, play a sound, or end the game.
These methods can be called dynamically at any point during gameplay (for example inside `onCollide`, `onEnter`, or from custom logic).
---
## Open a dialog
To trigger a dialog manually, use `game.openDialog()`:
```js
const game = createGame({
// ...
})
game.openDialog('Hello world!')
```
This will display a dialog box with the provided text.
You can use text effects and colors as well (see [Dialogues](/doc/world-building/dialogues)).
---
## Play a sound
To play a sound manually, use `game.playSound()`:
```js
game.playSound('BLIP')
game.playSound('PICKUP', 42)
game.playSound('https://pfxr/...')
game.playSound({ frequency: 300, waveForm: 1 })
```
You can pass a **preset name**, a **preset + seed**, a **PFXR URL**, or a **custom sound object**.
See [the Sounds page](/doc/world-building/sounds) for more details.
---
## Show a message
The method `game.openMessage()` lets you show one or more message boxes:
```js
game.openMessage('Hello')
game.openMessage('Hello', 'Welcome')
game.openMessage('Hello and ~welcome~')
```
---
## Ask the player a question
The `game.prompt()` method lets you present multiple options to the player:
```js
await game.prompt('yes', 'no')
await game.prompt('Rock', 'Paper', 'Scissors')
```
This method returns a **promise** containing the **index** of the selected option: `0` โ first option, `1` โ second optionโฆ
This allows you to react based on the player's choice:
```js
const choice = await game.prompt('Go left', 'Go right')
if (choice === 0) {
game.openMessage('You turned left')
} else {
game.openMessage('You turned right')
}
```
---
## Display a menu
The `game.openMenu()` method lets you **nest multiple `prompt()` calls**.
Itโs a simple way to present a structured menu, with sub-options and associated actions.
```js
await game.openMenu({
Greet: {
Hello: () => game.openDialog('Hello there'),
Yo: () => game.openDialog('Excuse me?')
},
Insult: () => game.openDialog('Same to you!'),
Ignore: null
})
```
- A **function** โ triggers an action
- An **object** โ opens a **sub-menu**
- `null` โ shows a **disabled option**
---
## End the game
To restart the game from the beginning, call `game.end()`.
If you provide one or more strings, they will be shown in the message box before restarting.
```js
game.end()
game.end('You win!')
game.end('Game over', 'But nice try.')
```
---
## Chain actions
The methods `openDialog`, `openMessage`, and `playSound` **return a promise**, which lets you wait for one to finish before continuing.
For example, wait for a message to finish before playing a sound:
```js
await game.openMessage('Watch out...')
game.playSound('EXPLOSION')
```
Or create a sequence of dialogs with a sound in between:
```js
await game.openDialog('Are you ready?')
await game.playSound('BLIP')
await game.openDialog("Let's go.")
```
---
# The Game State
To modify the grid or get information about the game, you can use the `game` object, which provides a set of dedicated methods.
---
## Read/modify a cell at a given position
### `getCellAt`
`getCellAt` allows you to get a cell at a given position, then modify its properties:
```js
const game = createGame()
const cell = game.getCellAt(9, 4)
cell.visible = false
```
### `setCellAt`
`setCellAt` allows you to apply a template to a cell, if the cell already has parameters they will be overwritten.
```js
game.setCellAt(3, 2, '#')
```
### `updateCellAt`
This method allows you to modify multiple properties of an element at a given position.
It takes three parameters: `x`, `y`, and an object containing the properties to modify.
```js
game.updateCellAt(3, 4, {
visible: false,
dialog: 'I am invisible'
})
```
### `clearCellAt`
To remove a cell.
```js
game.clearCellAt(3, 4)
```
---
## Read/modify multiple cells
It is also possible to read or apply modifications to multiple cells at once.
### Query
To do this you will need to use a query that will describe which cells you are addressing.
| name | type | description |
| ------------ | ---------------------- | ----------------------------------- |
| `symbol` | `string` or `string[]` | the template, or a list of template |
| `x` | `number` | The column number |
| `y` | `number` | The row number |
| `isOnScreen` | `boolean` | `true` if the object is on screen |
| `visible` | `boolean` |
| `sprite` | `number` or `string` |
| `dialog` | `string` or `string[]` |
| `end` | `string` or `string[]` |
### `getCells`
To get multiple `cells`, you need to use the `getCells(query)` method
```js
const walls = game.getCells({ solid: true })
```
### `setCells`
`setCells` allows you to apply a `template` to multiple cells.
```js
game.setCells({ isOnScreen: true }, '#')
```
### `updateCells`
The `updateCells` method allows you to modify multiple cells at once. It takes a `query` parameter followed by the parameters to modify.
```js
game.updateCells({ symbol: ['x', '#'], visible: true }, { sprite: 3, solid: true })
```
### `clearCells`
You can remove multiple cells at once with `clearCells`.
```js
game.clearCells({ visible: false, x: 4 })
```
### `sendMessageToCells`
This method allows you to trigger the `onMessage` method on all targeted cells. It takes a `query` parameter followed by an optional message of any type.
```js
game.sendMessageToCells({ symbols: 'x' }, 'turnOff')
```
---
## `player`
The `game.player` object gives you access to the **player**, and lets you change their `position`, `sprite`, and `visible` properties:
```js
game.player.position = [5, 6]
game.player.sprite = `
..1..
.111.
11111
.1.1.
.1.1.
`
game.player.visible = false
```
The `player` object also exposes the `direction` value.
This is a read-only property that reflects the last direction the player attempted to move in.
It updates every time the player presses a movement key, even if the move fails (e.g. because of a wall).
```js
const dir = game.player.direction
// Example: [0, -1] for a move upward
```
---
## `turn`
`game.turn` allows you to know the number of turns elapsed since the beginning of the game. A turn corresponds to a movement attempt.
---
## `width` and `height`
To get the dimensions of the world, use the `game.width` and `game.height` properties.
These are read-only values.
```js
alert(`width: ${game.width}, height: ${game.height}`)
```
---
## `loadMap`
To load a new map, use `game.loadMap()`.
The method takes two parameters:
1. A new `map` as a multiline string,
2. An optional position to relocate the player.
```js
game.loadMap(
`
########
#......#
#......#
#......#
#......#
#......#
#......#
########
`,
[3, 5]
)
```
---
## `updateFilter`
You can update the current filter settings with the `updateFilter` method.
It takes an object containing **the settings to modify** (the others will remain unchanged).
```js
const game = createGame({
filter: {
name: 'fractal',
settings: {
sideCount: 12,
scale: 0.9,
rotation: 0
}
}
})
game.updateFilter({
scale: 0.3
})
```
---
## `clear`
The `clear()` method allows you to stop the game and replace the display with a solid color:
```js
game.clear() // Clear with background color
// or
game.clear('0') // Clear with specific color
```
**Parameter:**
- `color` (string|number, optional): Clear color. If not specified, uses the game's background color.
---
## Rendering Behavior
Odyc automatically redraws the screen **every time the game state changes**.
If you modify a property like `sprite`, `position`, `dialog`, `visible`..., the game is updated immediately:
```js
game.player.sprite = newSprite
game.setCellAt(3, 4, { visible: false })
```
---
# Scene Transitions
To create multiple scenes in your game, simply call `createGame()` multiple times. Each call completely replaces the previous scene.
---
## Basic Principle
```js
function openMenu() {
createGame({
// Menu configuration
templates: [
{
sprite: '1',
onCollide() {
openGame() // Go to game
}
}
]
})
}
function openGame() {
createGame({
// Game configuration
templates: [
{
sprite: '2',
onCollide() {
openMenu() // Back to menu
}
}
]
})
}
openMenu() // Start
```
---
## Preserving Data
Variables declared outside `createGame()` are preserved between scenes:
```js
let score = 0
let level = 1
function nextLevel() {
level++
createGame({
onStart() {
showMessage(`Level ${level} - Score: ${score}`)
}
//...
})
}
```
---
## Clearing the Screen
The `game.clear()` method stops the game and replaces the display with a solid color:
```js
const game = createGame({
templates: [
{
sprite: '1',
async onCollide() {
await game.openMessage('...')
game.clear('0') // Clear with specific color
// Then create new scene
createGame({
//...
})
}
}
]
})
```
**Parameter:**
- `color` (string|number, optional): Color to clear with. If not specified, uses the game's background color.
---
# Customizing Colors
Odyc.js uses a predefined color palette to render sprites, dialogs, and messages.
You can replace or adjust it however you like.
---
## Customizing the Palette
Here is the default color palette. Click a color to copy its hex code.
_These colors are based on the excellent [Open Color](https://yeun.github.io/open-color/) palette._
By **default**, the palette contains **10 colors**, referenced by characters `0` to `9`.
However, you can provide up to **62 colors** in total.
In that case, you can use the full range of characters to represent colors in your sprites:
```
0โ9 โ first 10 colors
aโz โ next 26 colors
AโZ โ final 26 colors
```
Each character corresponds to a position in the `colors` array.
```js
createGame({
colors: [
'red', // 0
'orange', // 1
'lab(50% 40 59.5)', // 2
'hwb(12 50% 0%)', // 3
'#f06595', // 4
'#f09', // 5
'oklch(60% 0.15 50)', // 6
'hsl(150 30% 60%)', // 7
'light-dark(white, black)', // 8
'black', // 9
'hotpink', // a
'#0000ff', // b
'#ffff00' // c
// and so on...
]
})
```
The `colors` array can include any valid [CSS color value](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value): names, hex codes, HSL, LAB, etc.
---
## Background Color
Use the `background` option to set the background color.
```javascript
createGame({
//...
background: '#ff00ff'
})
```
The `background` value can be a [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) or a character pointing to a color in your palette.
---
## Dialog and Message Appearance
You can also customize the appearance of dialog and message boxes with dedicated options.
### Dialog Box
- `dialogColor` โ text color
- `dialogBackground` โ background color
- `dialogBorder` โ border color
These values can be any [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) or a character referring to a color from your palette.
```javascript
createGame({
// ...
dialogBackground: '#228be6',
dialogBorder: '3',
dialogColor: 'white'
})
```
### Message Box
- `messageColor` โ text color
- `messageBackground` โ background color
These also accept any valid [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) or a palette index.
```javascript
createGame({
//...
messageColor: 'red',
messageBackground: '#228be6'
})
```
---
# The Camera
The camera controls which part of the game world is visible.
You can customize the **sprite size**, **screen size**, and **tracking behavior** for either a smooth or snappy follow effect.
---
## Sprite Size
By default, each sprite is **8 ร 8 pixels**, but you can change this using `cellWidth` and `cellHeight`:
```js
createGame({
cellWidth: 16,
cellHeight: 32
})
```
---
## Screen Size
The screen size defines the visible area of the world.
It is measured in **grid cells**, not pixels:
```js
createGame({
screenWidth: 16,
screenHeight: 12
})
```
---
## Camera Tracking
By default, the camera **snaps instantly** when the player moves off screen.
To enable **smooth tracking**, define a central invisible **tracking zone** around the player.
The camera will only move when the player exits this zone.
These dimensions are also in grid cells, not pixels.
```js
createGame({
screenWidth: 12,
screenHeight: 12,
cameraWidth: 4,
cameraHeight: 4
})
```
---
# Filters
**Filters** let you apply visual effects to the entire screen, changing the overall look of your game.
---
## Usage
To use a filter, simply pass a `filter` option when calling `createGame`:
```js
createGame({
filter: {
name: 'neon'
}
})
```
Each filter has a **name**, and can take custom **settings** using the `settings` key.
---
## Available Filters
### `fractal`

Turns each pixel of the image into a **polygon**.
```js
filter: {
name: 'fractal',
settings: {
sideCount: 12, // Number of sides
scale: 0.9, // Global zoom (0 to 1)
rotation: 0 // Rotation (0 to 1)
}
}
```
---
### `crt`

Simulates an old **CRT screen** with scanlines, distortion, and curvature.
```js
filter: {
name: 'crt',
settings: {
warp: 0.7, // Screen curvature (0 to 1)
lineIntensity: 0.2, // Line opacity
lineWidth: 0.6, // Line thickness
lineCount: 85 // Number of scanlines
}
}
```
---
### `glow`

Creates a **luminous and vaporous** effect.
```js
filter: {
name: 'glow',
settings: {
intensity: 0.8 // Glow intensity
}
}
```
---
### `neon`

Creates a glowing **neon effect** with a pixelated mosaic overlay.
```js
filter: {
name: 'neon',
settings: {
scale: 0.75, // Tile size (0 to 1)
intensity: 0.8 // Glow intensity
}
}
```
---
## Custom Shaders
If you want full control, you can define your own **WebGL shaders** using the `filter` property.
A custom filter can include:
- a **fragment shader** (`fragment`)
- a **vertex shader** (`vertex`)
- any **uniforms** via `settings`
Hereโs an example of a filter that inverts all colors:
```js
const myShader = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoords;
void main() {
vec4 color = texture2D(u_texture, v_texCoords);
gl_FragColor = vec4(1.0 - color.rgb, color.a);
}
`
createGame({
filter: {
fragment: myShader
}
})
```
All `settings` values are injected into the shader as uniforms (prefixed with `u_`).
---
# Custom Controls
By default, Odyc.js uses the arrow keys or **WASD** keys for movement, and **Space** or **Enter** to interact.
But you can fully redefine the control scheme using the `controls` option.
---
## Default Setup
Hereโs the default control configuration if none is specified:
```js
controls: {
LEFT: ['ArrowLeft', 'KeyA'],
RIGHT: ['ArrowRight', 'KeyD'],
UP: ['ArrowUp', 'KeyW'],
DOWN: ['ArrowDown', 'KeyS'],
ACTION: ['Enter', 'Space']
}
```
---
## Customizing the Keys
You can override this configuration when calling `createGame()`:
```js
createGame({
controls: {
LEFT: 'KeyA',
RIGHT: 'KeyD',
UP: 'KeyW',
DOWN: 'KeyS',
ACTION: 'ShiftLeft'
}
})
```
Each key can be:
- a **string** representing a keyboard key (`'z'`, `'ArrowLeft'`, `'Shift'`, etc.)
- or an **array of strings** if you want to allow multiple keys for the same action
---
## Recognized Keys
Key names follow the standard **[`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code)** values in JavaScript.
> The `KeyboardEvent.code` represents the physical key on the keyboard,
> not the character it generates.
> For example, the code is `"KeyQ"` for the physical **Q** key,
> which produces different characters depending on the keyboard layout.
Press a key to see its code:
---
## Available Actions
Here are the keys you can customize:
| Key | Action performed |
| -------- | ---------------------- |
| `UP` | Move the player up |
| `DOWN` | Move the player down |
| `LEFT` | Move the player left |
| `RIGHT` | Move the player right |
| `ACTION` | Skip dialog or message |
---
# Default Configuration
When you create a game using `createGame()`, a set of default options is automatically applied.
You can override any of these values to customize your game.
---
## Default Values
Hereโs the configuration used if no custom options are provided:
```js
createGame({
player: {
sprite: 0
},
templates: {},
map: `
........
........
........
........
........
........
........
........
`,
colors: [
'#212529',
'#f8f9fa',
'#ced4da',
'#228be6',
'#fa5252',
'#fcc419',
'#ff922b',
'#40c057',
'#f06595',
'#a52f01'
],
messageBackground: 0,
messageColor: 1,
dialogBackground: 0,
dialogColor: 1,
dialogBorder: 1,
dialogSpeed: 'NORMAL',
screenWidth: 8,
screenHeight: 8,
cellWidth: 8,
cellHeight: 8,
background: 1,
volume: 0.5,
controls: {
LEFT: ['ArrowLeft', 'KeyA'],
RIGHT: ['ArrowRight', 'KeyD'],
UP: ['ArrowUp', 'KeyW'],
DOWN: ['ArrowDown', 'KeyS'],
ACTION: ['Enter', 'Space']
}
})
```
---
# Sprite Helpers
Helper functions for creating and manipulating sprites in Odyc.js.
---
## charToSprite
The `charToSprite()` function converts any character into an 8ร8 sprite for Odyc.js.
### Usage
```js
import { createGame, charToSprite } from 'odyc'
createGame({
player: {
// Character 'A' in color '1'
sprite: charToSprite('A')
},
templates: [
{
// Character '@' in color '3'
sprite: charToSprite('@', '3')
}
]
})
```
### Parameters
- `char` (string) : Any character to convert into a sprite
- `color` (string, optional) : Palette color to use for the character. Default: `0`
**Returns:** An 8ร8 sprite string representation of the character.
---
## mergeSprites
Combines multiple sprites into a single sprite by overlaying them on top of each other. Later sprites in the arguments will be drawn over earlier ones.
### Usage
```js
import { mergeSprites } from 'odyc'
const basePlayerSprite = `
.....
.....
33333
31313
33333
3...3
`
const hatSprite = `
.000.
00000
`
const playerSprite = mergeSprites(basePlayerSprite, hatSprite)
```
### Parameters
- `sprite1` (string) : The first sprite to merge (bottom layer)
- `sprite2` (string) : The second sprite to merge
- `...sprites` (string, optional) : Additional sprites to merge on top
**Returns:** A new sprite string with all input sprites merged together.
---
# vec2
The `vec2` helper provides utilities for manipulating 2D vectors (positions, coordinates).
---
## Creation
```js
import { vec2 } from 'odyc'
// With separate coordinates
const v1 = vec2(3, 4)
// With an [x, y] array
const v2 = vec2([3, 4])
```
---
## Methods
### Addition and subtraction
```js
const v1 = vec2(2, 3)
const v2 = vec2(1, 1)
const addition = v1.add(v2) // or v1.add(1, 1)
const subtraction = v1.sub(v2) // or v1.sub(1, 1)
```
### Multiplication and division
```js
const v = vec2(4, 6)
const multiplied = v.multiply(2) // [8, 12]
const divided = v.divide(2) // [2, 3]
```
### Distance and comparison
```js
const v1 = vec2(0, 0)
const v2 = vec2(3, 4)
const distance = v1.distance(v2) // 5 (Euclidean distance)
const manhattan = v1.manhattanDistance(v2) // 7 (Manhattan distance)
const equal = v1.equals(v2) // false
```
---
## Properties
```js
const v = vec2(3, 4)
console.log(v.length) // 5 (vector magnitude)
console.log(v.direction) // [1, 1] (sign-based direction)
console.log(v.value) // [3, 4] (get coordinates)
// Modify coordinates
v.value = [5, 6]
//or
v.x = 5
v.y = 6
```
---
## Reference Table
| Method/Property | Parameters | Returns | Description |
| --------------------------- | ------------------------------ | ------------------ | ------------------------------------ |
| `add(vector)` | `vec2` or `[x, y]` or `(x, y)` | `vec2` | Adds another vector |
| `sub(vector)` | `vec2` or `[x, y]` or `(x, y)` | `vec2` | Subtracts another vector |
| `multiply(scalar)` | `number` | `vec2` | Multiplies by a scalar |
| `divide(scalar)` | `number` | `vec2` | Divides by a scalar |
| `distance(vector)` | `vec2` or `[x, y]` | `number` | Euclidean distance to another vector |
| `manhattanDistance(vector)` | `vec2` or `[x, y]` | `number` | Manhattan distance to another vector |
| `equals(vector)` | `vec2` or `[x, y]` | `boolean` | Checks if vectors are equal |
| `length` | - | `number` | Vector magnitude (read-only) |
| `direction` | - | `[number, number]` | Sign-based direction (read-only) |
| `value` | - | `[number, number]` | Get/set coordinates |
| `x` | - | `number` | Get/set x coordinate |
| `y` | - | `number` | Get/set y coordinate |
---
# Recording
Helper functions for capturing screenshots and recording gameplay videos from your Odyc.js games.
---
## makeScreenshot
The `makeScreenshot()` function captures the current game screen and downloads it as an image file.
### Usage
```js
import { createGame, makeScreenshot } from 'odyc'
const game = createGame({
// Your game configuration
})
// Take screenshot when pressing Cmd/Ctrl + S
document.addEventListener('keydown', (event) => {
if ((event.metaKey || event.ctrlKey) && event.code === 'KeyS') {
makeScreenshot('game-screenshot')
}
})
// Or take a screenshot programmatically
makeScreenshot('my-game-screenshot')
```
### Parameters
- `filename` (string) : The filename for the downloaded screenshot file
---
## startRecording
The `startRecording()` function begins recording the game screen and returns a function to stop the recording and save it as a video file.
### Usage
```js
import { createGame, startRecording } from 'odyc'
const game = createGame({
// Your game configuration
})
// Start recording with keyboard shortcut (Cmd/Ctrl + R)
document.addEventListener('keydown', (event) => {
if ((event.metaKey || event.ctrlKey) && event.code === 'KeyR') {
const stopAndSave = startRecording()
// Stop recording after 10 seconds
setTimeout(() => {
stopAndSave('gameplay-recording')
}, 10000)
}
})
// Or start recording programmatically
const stopAndSave = startRecording()
// Stop and save the recording
stopAndSave('my-game-recording')
```
### Return Value
The function returns a `stopAndSave` function:
- `stopAndSave(filename: string): void` : Function to stop the recording and save it as a video file with the specified filename
---
# tick
The `tick()` function returns a promise that resolves when the next meaningful state change occurs in the game. This is useful for synchronizing code with the game's internal state updates.
---
## Use Cases
```js
import { createGame, tick } from 'odyc'
const game = createGame({
// Your game configuration
})
async function loadGame() {
document.body.style.transition = 'opacity 0.5s'
document.body.style.opacity = '0'
await new Promise((resolve) => setTimeout(resolve, 500))
const game2 = createGame({ filter: { name: 'crt' } })
await tick()
document.body.style.opacity = '1'
}
```
---
## Return Value
**Returns:** A `Promise` that resolves when the next meaningful game state change occurs.
### When tick resolves:
- After each game render cycle
- When dialogs, messages, or prompts open
- When dialogs, messages, or prompts close
- When the game is cleared