yuuna-engine 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -135
- package/lib/engine/types.d.ts +78 -27
- package/lib/index.cjs +404 -33
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +404 -33
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,135 +1,194 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="dist/resources/yuuna.png" alt="Yuuna" width="120" height="120" />
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
# Yuuna
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`
|
|
78
|
-
`
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
`
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="dist/resources/yuuna.png" alt="Yuuna" width="120" height="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Yuuna
|
|
6
|
+
|
|
7
|
+
[Live demo & playground](https://lucy-dot-exe.github.io/yuuna/) · [GitHub](https://github.com/lucy-dot-exe/yuuna)
|
|
8
|
+
|
|
9
|
+
A lightweight, state-machine-based TypeScript game engine built for quick
|
|
10
|
+
prototypes — drop it into a page and it's running, no editor or build step
|
|
11
|
+
required. You describe your game as a `state`, a `render(state)` function,
|
|
12
|
+
and a `nextState({ state, event, keyboard })` function — Yuuna owns the
|
|
13
|
+
render loop, input handling, and canvas drawing. It's scratch paper for game
|
|
14
|
+
ideas, not a replacement for Godot or Unity.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install yuuna-engine
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
Add a canvas with `id="yuuna"` to your page:
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<canvas id="yuuna"></canvas>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then describe your game as state + render + nextState:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { runEngine } from "yuuna-engine";
|
|
34
|
+
|
|
35
|
+
type GameState = { cookies: number };
|
|
36
|
+
|
|
37
|
+
runEngine<GameState>({
|
|
38
|
+
initialState: { cookies: 0 },
|
|
39
|
+
|
|
40
|
+
// Optional — size and color the canvas from code instead of HTML/CSS
|
|
41
|
+
canvas: { width: 960, height: 540, backgroundColor: "#0d1831" },
|
|
42
|
+
|
|
43
|
+
render: (state) => ({
|
|
44
|
+
renderables: [
|
|
45
|
+
{
|
|
46
|
+
type: "TEXT",
|
|
47
|
+
text: `${state.cookies} cookies`,
|
|
48
|
+
color: "black",
|
|
49
|
+
position: { x: 100, y: 50 },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "CIRCLE",
|
|
53
|
+
id: "cookie",
|
|
54
|
+
isClickable: true,
|
|
55
|
+
color: "brown",
|
|
56
|
+
position: { x: 50, y: 50 },
|
|
57
|
+
radius: 25,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
}),
|
|
61
|
+
|
|
62
|
+
nextState: ({ state, event }) => {
|
|
63
|
+
if (event.tag === "CLICK" && event.id === "cookie") {
|
|
64
|
+
return { cookies: state.cookies + 1 };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return state;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Concepts
|
|
73
|
+
|
|
74
|
+
- **Renderables** — declarative shapes drawn each frame: `RECTANGLE`,
|
|
75
|
+
`CIRCLE`, `TEXT`, `SPRITE`, `LINE`, `GROUP`, and `ANIMATED_SPRITE`. Give
|
|
76
|
+
one an `id` plus `isClickable` / `isHoverable` / `trackMouseMovement` to
|
|
77
|
+
make it interactive. `TEXT` also takes a `fontSize` (defaults to `30`).
|
|
78
|
+
`GROUP` draws nothing itself — it's just a `position` (plus the usual
|
|
79
|
+
`scale`/`modulate`/`layer` below) for `children` to hang off of, for
|
|
80
|
+
grouping renderables that should move/scale/tint together without
|
|
81
|
+
needing a shape of its own; it's never interactable, since it has no
|
|
82
|
+
shape to hit-test. `ANIMATED_SPRITE` is `SPRITE` with an `animation`
|
|
83
|
+
(name of one defined on that resource — see Sprites below) and an
|
|
84
|
+
optional `timeScale` instead of a `frame` number; the engine picks the
|
|
85
|
+
frame for you based on how long it's been playing. Unlike every other
|
|
86
|
+
renderable, its `id` is required — that's how the engine recognizes
|
|
87
|
+
"this is the same sprite as last frame" across renders (and switches to
|
|
88
|
+
frame 0 if `animation` changes for that `id`), so reusing an id across
|
|
89
|
+
two different entities will make their animations bleed together.
|
|
90
|
+
Every renderable also takes:
|
|
91
|
+
- `layer` — higher values render later, i.e. in front of lower ones.
|
|
92
|
+
Defaults to `0`; renderables on the same layer keep `render()`'s order.
|
|
93
|
+
- `scale` — `{ x, y }` multiplier on the renderable's size, anchored at
|
|
94
|
+
its `position` (or `from`, for `LINE`). Defaults to `{ x: 1, y: 1 }`.
|
|
95
|
+
A `CIRCLE` scaled unevenly draws (and hit-tests) as an ellipse.
|
|
96
|
+
- `modulate` — a CSS color string that multiplies the renderable's color
|
|
97
|
+
channel-by-channel, the same way Godot's `modulate` works — e.g.
|
|
98
|
+
`"#808080"` halves brightness, `"#ff0000"` keeps only the red channel.
|
|
99
|
+
- `children` — nested renderables, positioned relative to this one, like
|
|
100
|
+
Godot's parent/child nodes. A child's `position` is added to its
|
|
101
|
+
parent's (scaled by the parent's own `scale`), and `scale` / `modulate`
|
|
102
|
+
/ `layer` all compose down the tree — a child's effective scale is the
|
|
103
|
+
parent's times its own, `modulate` multiplies the same way, and
|
|
104
|
+
`layer` adds (relative to the parent's, matching Godot's default: a
|
|
105
|
+
deeply-nested child can still end up drawn in front of an unrelated
|
|
106
|
+
top-level renderable if its accumulated layer says so). A child is a
|
|
107
|
+
full `Renderable`, so it can have its own `id` / `isClickable` / even
|
|
108
|
+
its own `children`. `screenSpace: true` makes a renderable (and its
|
|
109
|
+
whole subtree) ignore the camera below and stay fixed to the screen —
|
|
110
|
+
for UI/HUD that shouldn't pan or zoom with the game world.
|
|
111
|
+
- **Camera** — pass `camera: (state) => ({ x, y, zoom })` to `runEngine` to
|
|
112
|
+
pan/zoom every world-space renderable (anything without
|
|
113
|
+
`screenSpace: true`) as a group, the same way a `GROUP` parent works for
|
|
114
|
+
its children — `{ x, y }` is the world position mapped to canvas
|
|
115
|
+
`(0, 0)`, and `zoom` scales everything around that same point. It's a
|
|
116
|
+
function of state, so the camera can follow something or react to a
|
|
117
|
+
zoom level you're tracking yourself.
|
|
118
|
+
- **Events** — your `nextState` function receives one `GameEvent` per call:
|
|
119
|
+
`TIME` (frame tick with `delta`), `CLICK`, `HOVER_IN`, `HOVER_OUT`, or
|
|
120
|
+
`MOUSE_MOVE`. The mouse-carrying ones include both `mouse` (raw canvas
|
|
121
|
+
pixels — use for `screenSpace`/UI logic) and `worldMouse` (that same
|
|
122
|
+
position run through the camera's inverse transform — use to
|
|
123
|
+
place/locate world-space things, e.g. build a turret where the player
|
|
124
|
+
clicked). With no `camera` set, `worldMouse` always equals `mouse`.
|
|
125
|
+
- **Keyboard** — `nextState` also receives a `keyboard` map keyed by
|
|
126
|
+
`KeyCode`-style keys (e.g. `"KeyW"`, `"ArrowLeft"`, `"Space"`), each with
|
|
127
|
+
`isPressed` / `isJustPressed` / `isJustReleased`.
|
|
128
|
+
- **Sprites** — pass a `resources` map of `{ src, size, slices }` to
|
|
129
|
+
`runEngine` to load spritesheets, then reference them by id with a
|
|
130
|
+
`SPRITE` renderable's `resourceId` and `frame`. Set `flipX: true` to
|
|
131
|
+
mirror a sprite horizontally — useful when the art is drawn facing one
|
|
132
|
+
direction but needs to move the other way. Add an `animations` map to a
|
|
133
|
+
resource — `{ frames: number[], frameDuration, loop }` each — to play
|
|
134
|
+
one with `ANIMATED_SPRITE` instead of managing `frame` by hand.
|
|
135
|
+
- **Sound effects** — pass a `sounds` map of `{ src }` to `runEngine`, then
|
|
136
|
+
call the `playSound(id)` function `nextState` receives to play one, e.g.
|
|
137
|
+
`playSound("collect")` when a cookie is clicked. Calling it again while
|
|
138
|
+
a sound is still playing overlaps a new copy instead of cutting the
|
|
139
|
+
first one off.
|
|
140
|
+
- **Music** — pass a `music` map of `{ src }` to `runEngine`, then use the
|
|
141
|
+
`playMusic(id)` / `pauseMusic()` functions `nextState` receives to
|
|
142
|
+
control a looping background track. Unlike `playSound`, only one track
|
|
143
|
+
plays at a time and it keeps running in the background across frames
|
|
144
|
+
instead of firing once; `pauseMusic()` leaves it where it stopped, so
|
|
145
|
+
calling `playMusic(id)` again resumes it instead of starting over.
|
|
146
|
+
- **Canvas** — pass `canvas: { width, height, backgroundColor }` to
|
|
147
|
+
`runEngine` to size and color the canvas from code. All three are
|
|
148
|
+
optional; anything you don't set falls back to the canvas element's
|
|
149
|
+
existing HTML/CSS.
|
|
150
|
+
- **Mechanics** — `nextState` can also be an array of small
|
|
151
|
+
`NextStateFunction`s instead of one big function. Each one is run in
|
|
152
|
+
order for every event, and can return:
|
|
153
|
+
- a new state, to update to
|
|
154
|
+
- `undefined` (or no `return` at all) — no change, but the rest of the
|
|
155
|
+
list still runs, so a guard can just be `if (...) return;`
|
|
156
|
+
- `STOP` (imported from `yuuna-engine`) — no change, and the rest of
|
|
157
|
+
the list is skipped for this event, so a shared rule (like "nothing
|
|
158
|
+
happens once the game is over") only needs to be written once
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { runEngine, STOP, type NextStateFunction } from "yuuna-engine";
|
|
162
|
+
|
|
163
|
+
const freezeOnGameOver: NextStateFunction<GameState> = ({ state }) => {
|
|
164
|
+
if (state.lives <= 0) return STOP;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const moveEnemies: NextStateFunction<GameState> = ({ state, event }) => {
|
|
168
|
+
if (event.tag === "TIME") {
|
|
169
|
+
return { ...state, enemies: move(state.enemies, event.delta) };
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
runEngine<GameState>({
|
|
174
|
+
initialState,
|
|
175
|
+
render,
|
|
176
|
+
nextState: [freezeOnGameOver, moveEnemies /* ... */],
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Development
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
yarn install
|
|
184
|
+
yarn build # builds lib/ (npm package) and dist/bundle.js (landing page)
|
|
185
|
+
yarn watch # rebuild on change
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`dist/index.html` is the landing page — it loads `dist/bundle.js` in the
|
|
189
|
+
browser via a global `Yuuna` object and embeds a live Monaco editor so
|
|
190
|
+
visitors can edit and run a game directly on the page.
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT © [lucy-dot-exe](https://github.com/lucy-dot-exe)
|
package/lib/engine/types.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
type BaseRenderable = {
|
|
2
|
+
layer?: number;
|
|
3
|
+
scale?: {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
};
|
|
7
|
+
modulate?: string;
|
|
8
|
+
children?: Renderable[];
|
|
9
|
+
screenSpace?: boolean;
|
|
10
|
+
id?: string;
|
|
11
|
+
isClickable?: boolean;
|
|
12
|
+
isHoverable?: boolean;
|
|
13
|
+
trackMouseMovement?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type RectangleRenderable = BaseRenderable & {
|
|
2
16
|
type: "RECTANGLE";
|
|
3
17
|
position: {
|
|
4
18
|
x: number;
|
|
@@ -9,12 +23,8 @@ export type RectangleRenderable = {
|
|
|
9
23
|
height: number;
|
|
10
24
|
};
|
|
11
25
|
color: string;
|
|
12
|
-
id?: string;
|
|
13
|
-
isClickable?: boolean;
|
|
14
|
-
isHoverable?: boolean;
|
|
15
|
-
trackMouseMovement?: boolean;
|
|
16
26
|
};
|
|
17
|
-
export type CircleRenderable = {
|
|
27
|
+
export type CircleRenderable = BaseRenderable & {
|
|
18
28
|
type: "CIRCLE";
|
|
19
29
|
position: {
|
|
20
30
|
x: number;
|
|
@@ -22,12 +32,8 @@ export type CircleRenderable = {
|
|
|
22
32
|
};
|
|
23
33
|
radius: number;
|
|
24
34
|
color: string;
|
|
25
|
-
id?: string;
|
|
26
|
-
isClickable?: boolean;
|
|
27
|
-
isHoverable?: boolean;
|
|
28
|
-
trackMouseMovement?: boolean;
|
|
29
35
|
};
|
|
30
|
-
export type TextRenderable = {
|
|
36
|
+
export type TextRenderable = BaseRenderable & {
|
|
31
37
|
type: "TEXT";
|
|
32
38
|
text: string;
|
|
33
39
|
color: string;
|
|
@@ -39,12 +45,9 @@ export type TextRenderable = {
|
|
|
39
45
|
x: "left" | "center" | "right";
|
|
40
46
|
y: "bottom" | "middle" | "top";
|
|
41
47
|
};
|
|
42
|
-
|
|
43
|
-
isClickable?: boolean;
|
|
44
|
-
isHoverable?: boolean;
|
|
45
|
-
trackMouseMovement?: boolean;
|
|
48
|
+
fontSize?: number;
|
|
46
49
|
};
|
|
47
|
-
export type SpriteRenderable = {
|
|
50
|
+
export type SpriteRenderable = BaseRenderable & {
|
|
48
51
|
type: "SPRITE";
|
|
49
52
|
position: {
|
|
50
53
|
x: number;
|
|
@@ -52,15 +55,10 @@ export type SpriteRenderable = {
|
|
|
52
55
|
};
|
|
53
56
|
resourceId: string;
|
|
54
57
|
frame: number;
|
|
55
|
-
scale?: number;
|
|
56
58
|
opacity?: number;
|
|
57
59
|
flipX?: boolean;
|
|
58
|
-
id?: string;
|
|
59
|
-
isClickable?: boolean;
|
|
60
|
-
isHoverable?: boolean;
|
|
61
|
-
trackMouseMovement?: boolean;
|
|
62
60
|
};
|
|
63
|
-
export type LineRenderable = {
|
|
61
|
+
export type LineRenderable = BaseRenderable & {
|
|
64
62
|
type: "LINE";
|
|
65
63
|
from: {
|
|
66
64
|
x: number;
|
|
@@ -72,12 +70,29 @@ export type LineRenderable = {
|
|
|
72
70
|
};
|
|
73
71
|
color: string;
|
|
74
72
|
width?: number;
|
|
75
|
-
id?: string;
|
|
76
|
-
isClickable?: boolean;
|
|
77
|
-
isHoverable?: boolean;
|
|
78
|
-
trackMouseMovement?: boolean;
|
|
79
73
|
};
|
|
80
|
-
export type
|
|
74
|
+
export type AnimatedSpriteRenderable = BaseRenderable & {
|
|
75
|
+
type: "ANIMATED_SPRITE";
|
|
76
|
+
position: {
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
};
|
|
80
|
+
resourceId: string;
|
|
81
|
+
animation: string;
|
|
82
|
+
timeScale?: number;
|
|
83
|
+
paused?: boolean;
|
|
84
|
+
opacity?: number;
|
|
85
|
+
flipX?: boolean;
|
|
86
|
+
id: string;
|
|
87
|
+
};
|
|
88
|
+
export type GroupRenderable = BaseRenderable & {
|
|
89
|
+
type: "GROUP";
|
|
90
|
+
position: {
|
|
91
|
+
x: number;
|
|
92
|
+
y: number;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
export type Renderable = RectangleRenderable | CircleRenderable | SpriteRenderable | TextRenderable | LineRenderable | GroupRenderable | AnimatedSpriteRenderable;
|
|
81
96
|
export type TimeEvent = {
|
|
82
97
|
tag: "TIME";
|
|
83
98
|
delta: number;
|
|
@@ -89,6 +104,10 @@ export type ClickEvent = {
|
|
|
89
104
|
x: number;
|
|
90
105
|
y: number;
|
|
91
106
|
};
|
|
107
|
+
worldMouse: {
|
|
108
|
+
x: number;
|
|
109
|
+
y: number;
|
|
110
|
+
};
|
|
92
111
|
};
|
|
93
112
|
export type HoverInEvent = {
|
|
94
113
|
tag: "HOVER_IN";
|
|
@@ -97,6 +116,10 @@ export type HoverInEvent = {
|
|
|
97
116
|
x: number;
|
|
98
117
|
y: number;
|
|
99
118
|
};
|
|
119
|
+
worldMouse: {
|
|
120
|
+
x: number;
|
|
121
|
+
y: number;
|
|
122
|
+
};
|
|
100
123
|
};
|
|
101
124
|
export type HoverOutEvent = {
|
|
102
125
|
tag: "HOVER_OUT";
|
|
@@ -105,6 +128,10 @@ export type HoverOutEvent = {
|
|
|
105
128
|
x: number;
|
|
106
129
|
y: number;
|
|
107
130
|
};
|
|
131
|
+
worldMouse: {
|
|
132
|
+
x: number;
|
|
133
|
+
y: number;
|
|
134
|
+
};
|
|
108
135
|
};
|
|
109
136
|
export type MouseMoveEvent = {
|
|
110
137
|
tag: "MOUSE_MOVE";
|
|
@@ -113,6 +140,10 @@ export type MouseMoveEvent = {
|
|
|
113
140
|
x: number;
|
|
114
141
|
y: number;
|
|
115
142
|
};
|
|
143
|
+
worldMouse: {
|
|
144
|
+
x: number;
|
|
145
|
+
y: number;
|
|
146
|
+
};
|
|
116
147
|
};
|
|
117
148
|
export type GameEvent = TimeEvent | ClickEvent | HoverInEvent | HoverOutEvent | MouseMoveEvent;
|
|
118
149
|
export type NextStateProps<State> = {
|
|
@@ -123,6 +154,9 @@ export type NextStateProps<State> = {
|
|
|
123
154
|
isJustPressed: boolean;
|
|
124
155
|
isJustReleased: boolean;
|
|
125
156
|
}>;
|
|
157
|
+
playSound: (id: string) => void;
|
|
158
|
+
playMusic: (id: string) => void;
|
|
159
|
+
pauseMusic: () => void;
|
|
126
160
|
};
|
|
127
161
|
export declare const STOP: "Yuuna.STOP";
|
|
128
162
|
export type NextStateFunction<State> = (props: NextStateProps<State>) => State | typeof STOP | undefined;
|
|
@@ -143,12 +177,28 @@ export type RunEngineProps<State> = {
|
|
|
143
177
|
vertical: number;
|
|
144
178
|
horizontal: number;
|
|
145
179
|
};
|
|
180
|
+
animations?: Record<string, {
|
|
181
|
+
frames: number[];
|
|
182
|
+
frameDuration: number;
|
|
183
|
+
loop: boolean;
|
|
184
|
+
}>;
|
|
185
|
+
}>;
|
|
186
|
+
sounds?: Record<string, {
|
|
187
|
+
src: string;
|
|
188
|
+
}>;
|
|
189
|
+
music?: Record<string, {
|
|
190
|
+
src: string;
|
|
146
191
|
}>;
|
|
147
192
|
canvas?: {
|
|
148
193
|
width?: number;
|
|
149
194
|
height?: number;
|
|
150
195
|
backgroundColor?: string;
|
|
151
196
|
};
|
|
197
|
+
camera?: (state: State) => {
|
|
198
|
+
x: number;
|
|
199
|
+
y: number;
|
|
200
|
+
zoom: number;
|
|
201
|
+
};
|
|
152
202
|
};
|
|
153
203
|
export type RunEngineFunction = <State>(props: RunEngineProps<State>) => Promise<void>;
|
|
154
204
|
export declare const keyboardKeys: readonly ["ControlLeft", "ControlRight", "AltLeft", "AltRight", "CapsLock", "End", "Delete", "Tab", "Space", "Enter", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Numpad0", "Numpad1", "Numpad2", "Numpad3", "Numpad4", "Numpad5", "Numpad6", "Numpad7", "Numpad8", "Numpad9", "Digit0", "Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9", "KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ"];
|
|
@@ -158,3 +208,4 @@ export declare var Yuuna: {
|
|
|
158
208
|
runEngine: RunEngineFunction;
|
|
159
209
|
STOP: typeof STOP;
|
|
160
210
|
};
|
|
211
|
+
export {};
|