yuuna-engine 0.5.0 → 0.7.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 +160 -152
- package/lib/engine/renderables.d.ts +8 -0
- package/lib/engine/types.d.ts +24 -4
- package/lib/index.cjs +286 -25
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +280 -26
- package/lib/index.js.map +1 -1
- package/package.json +4 -2
- package/lib/utils/constants.d.ts +0 -6
package/README.md
CHANGED
|
@@ -1,152 +1,160 @@
|
|
|
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
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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 — drop it into a
|
|
10
|
+
page and it's running, no editor or build step required. You describe your
|
|
11
|
+
game as a `state`, a `render(state)` function, and a
|
|
12
|
+
`nextState({ state, event, keyboard })` function — Yuuna owns the render
|
|
13
|
+
loop, input handling, and canvas drawing.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install yuuna-engine
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
Add a canvas with `id="yuuna"` to your page:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<canvas id="yuuna"></canvas>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then describe your game as state + render + nextState:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { runEngine } from "yuuna-engine";
|
|
33
|
+
|
|
34
|
+
// The shape of your game's data — whatever it takes to fully describe
|
|
35
|
+
// what's on screen and how it behaves
|
|
36
|
+
type GameState = { cookies: number };
|
|
37
|
+
|
|
38
|
+
// What that state looks like before anything has happened yet
|
|
39
|
+
const initialState: GameState = { cookies: 0 };
|
|
40
|
+
|
|
41
|
+
runEngine<GameState>({
|
|
42
|
+
initialState,
|
|
43
|
+
|
|
44
|
+
// Given the current state, what should be drawn this frame? Called
|
|
45
|
+
// every frame — always derive the picture from state, instead of
|
|
46
|
+
// reaching for the canvas directly.
|
|
47
|
+
render: (state) => ({
|
|
48
|
+
renderables: [
|
|
49
|
+
{
|
|
50
|
+
type: "TEXT",
|
|
51
|
+
text: `${state.cookies} cookies`,
|
|
52
|
+
color: "black",
|
|
53
|
+
position: { x: 100, y: 50 },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: "CIRCLE",
|
|
57
|
+
id: "cookie",
|
|
58
|
+
isClickable: true,
|
|
59
|
+
color: "brown",
|
|
60
|
+
position: { x: 50, y: 50 },
|
|
61
|
+
radius: 25,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
// Given the current state and something that just happened, what's the
|
|
67
|
+
// next state? Called once per event (a click, a frame tick, ...) — the
|
|
68
|
+
// only place game logic lives.
|
|
69
|
+
nextState: ({ state, event }) => {
|
|
70
|
+
if (event.tag === "CLICK" && event.id === "cookie") {
|
|
71
|
+
return { cookies: state.cookies + 1 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return state;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Concepts
|
|
80
|
+
|
|
81
|
+
- **Renderables** — declarative shapes drawn each frame: `RECTANGLE`,
|
|
82
|
+
`CIRCLE`, `TEXT`, `SPRITE`, `ANIMATED_SPRITE`, `LINE`, and `GROUP`. Give
|
|
83
|
+
one an `id` plus `isClickable`/`isHoverable` to make it interactive.
|
|
84
|
+
- **Events** — `nextState` receives one `GameEvent` per call: `TIME`,
|
|
85
|
+
`CLICK`, `HOVER_IN`, `HOVER_OUT`, `MOUSE_MOVE`, `MOUSE_LEAVE`,
|
|
86
|
+
`MUSIC_END`, or a `CUSTOM` event of a type you define yourself, for
|
|
87
|
+
reporting things like an async `fetch()` resolving back into your
|
|
88
|
+
state machine.
|
|
89
|
+
- **Keyboard, camera, sprites & animation, sound effects & music,
|
|
90
|
+
canvas config, and mechanics pipelines** all follow the same idea:
|
|
91
|
+
small, focused props and functions `runEngine`/`nextState` take, that
|
|
92
|
+
compose with everything above instead of replacing it.
|
|
93
|
+
|
|
94
|
+
This README stays intentionally thin — the full concept-by-concept
|
|
95
|
+
reference, with every option and example, lives on the
|
|
96
|
+
[wiki](https://github.com/lucy-dot-exe/yuuna/wiki). The
|
|
97
|
+
[playground](https://lucy-dot-exe.github.io/yuuna/#playground) also has a
|
|
98
|
+
small, focused example for most of these you can run and edit directly.
|
|
99
|
+
|
|
100
|
+
## Templates
|
|
101
|
+
|
|
102
|
+
Prefer a working starting point over typing the quick start out by
|
|
103
|
+
hand? Grab one from [`templates/`](templates):
|
|
104
|
+
|
|
105
|
+
- **[blank](templates/blank)** — a single `index.html`, zero install —
|
|
106
|
+
open it in a browser and it runs.
|
|
107
|
+
- **[npm](templates/npm)** — TypeScript + a dev server with hot reload
|
|
108
|
+
(via Vite), for a real local project.
|
|
109
|
+
- **[neutralino-desktop](templates/neutralino-desktop)** — the `npm`
|
|
110
|
+
template wrapped in [Neutralino](https://neutralino.js.org) to run as a
|
|
111
|
+
native desktop window.
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
npx degit lucy-dot-exe/yuuna/templates/blank my-game
|
|
115
|
+
# or: npx degit lucy-dot-exe/yuuna/templates/npm my-game
|
|
116
|
+
# or: npx degit lucy-dot-exe/yuuna/templates/neutralino-desktop my-game
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
[`degit`](https://github.com/Rich-Harris/degit) copies the folder without
|
|
120
|
+
its git history — no cloning or forking the whole engine repo needed.
|
|
121
|
+
Each template's own README has more on running it once copied.
|
|
122
|
+
|
|
123
|
+
## Made with Yuuna
|
|
124
|
+
|
|
125
|
+
- **[Yuuna's Heroes](https://lucinaexe.itch.io/yuunas-td)** — a game made
|
|
126
|
+
using Yuuna.
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
yarn install
|
|
132
|
+
yarn build # builds lib/ (npm package) and dist/bundle.js (landing page)
|
|
133
|
+
yarn watch # rebuild on change
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`dist/index.html` is the landing page — it loads `dist/bundle.js` in the
|
|
137
|
+
browser via a global `Yuuna` object and embeds a live Monaco editor so
|
|
138
|
+
visitors can edit and run a game directly on the page.
|
|
139
|
+
|
|
140
|
+
## Assets
|
|
141
|
+
|
|
142
|
+
The examples' art/sound/music lives in `dist/resources/`, gitignored
|
|
143
|
+
rather than committed — this repo being open source doesn't make every
|
|
144
|
+
asset in it free to redistribute. `runEngine()` falls back to a
|
|
145
|
+
generated placeholder for any image that isn't there (and simply plays
|
|
146
|
+
nothing for missing audio) instead of failing, so the examples still
|
|
147
|
+
run without them — just with placeholder art in place of the real
|
|
148
|
+
thing. Drop the real files in locally (or restore them from wherever
|
|
149
|
+
you got this repo from) to see them for real.
|
|
150
|
+
|
|
151
|
+
Currently used:
|
|
152
|
+
|
|
153
|
+
- **[Free Pixel Food!](https://henrysoftware.itch.io/pixel-food)** by
|
|
154
|
+
[Henry Software](https://henrysoftware.itch.io/) — the food icons in
|
|
155
|
+
the Food Clicker and Sprites examples. CC0; credited here by choice,
|
|
156
|
+
not requirement.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT © [lucy-dot-exe](https://github.com/lucy-dot-exe)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AnimatedSpriteRenderable, CircleRenderable, GroupRenderable, LineRenderable, RectangleRenderable, SpriteRenderable, TextRenderable } from "./types";
|
|
2
|
+
export declare const rectangle: (props: Omit<RectangleRenderable, "type">) => RectangleRenderable;
|
|
3
|
+
export declare const circle: (props: Omit<CircleRenderable, "type">) => CircleRenderable;
|
|
4
|
+
export declare const text: (props: Omit<TextRenderable, "type">) => TextRenderable;
|
|
5
|
+
export declare const sprite: (props: Omit<SpriteRenderable, "type">) => SpriteRenderable;
|
|
6
|
+
export declare const animatedSprite: (props: Omit<AnimatedSpriteRenderable, "type">) => AnimatedSpriteRenderable;
|
|
7
|
+
export declare const line: (props: Omit<LineRenderable, "type">) => LineRenderable;
|
|
8
|
+
export declare const group: (props: Omit<GroupRenderable, "type">) => GroupRenderable;
|
package/lib/engine/types.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export type SpriteRenderable = BaseRenderable & {
|
|
|
54
54
|
y: number;
|
|
55
55
|
};
|
|
56
56
|
resourceId: string;
|
|
57
|
-
frame
|
|
57
|
+
frame?: number;
|
|
58
58
|
opacity?: number;
|
|
59
59
|
flipX?: boolean;
|
|
60
60
|
swapColors?: {
|
|
@@ -174,7 +174,11 @@ export type MusicEndEvent = {
|
|
|
174
174
|
tag: "MUSIC_END";
|
|
175
175
|
id: string;
|
|
176
176
|
};
|
|
177
|
-
export type
|
|
177
|
+
export type FullscreenChangeEvent = {
|
|
178
|
+
tag: "FULLSCREEN_CHANGE";
|
|
179
|
+
isFullscreen: boolean;
|
|
180
|
+
};
|
|
181
|
+
export type GameEvent = TimeEvent | ClickEvent | HoverInEvent | HoverOutEvent | MouseMoveEvent | MouseLeaveEvent | TabBlurEvent | TabFocusEvent | MusicEndEvent | FullscreenChangeEvent;
|
|
178
182
|
export type CustomGameEvent<Custom> = {
|
|
179
183
|
tag: "CUSTOM";
|
|
180
184
|
event: Custom;
|
|
@@ -187,6 +191,11 @@ export type NextStateProps<State, Custom = never> = {
|
|
|
187
191
|
isJustPressed: boolean;
|
|
188
192
|
isJustReleased: boolean;
|
|
189
193
|
}>;
|
|
194
|
+
mouseButton: {
|
|
195
|
+
isPressed: boolean;
|
|
196
|
+
isJustPressed: boolean;
|
|
197
|
+
isJustReleased: boolean;
|
|
198
|
+
};
|
|
190
199
|
playSound: (id: string) => void;
|
|
191
200
|
playMusic: (id: string) => void;
|
|
192
201
|
pauseMusic: () => void;
|
|
@@ -204,11 +213,11 @@ export type RunEngineProps<State, Custom = never> = {
|
|
|
204
213
|
nextState: NextStateFunction<State, Custom> | NextStateFunction<State, Custom>[];
|
|
205
214
|
resources?: Record<string, {
|
|
206
215
|
src: string;
|
|
207
|
-
size
|
|
216
|
+
size?: {
|
|
208
217
|
width: number;
|
|
209
218
|
height: number;
|
|
210
219
|
};
|
|
211
|
-
slices
|
|
220
|
+
slices?: {
|
|
212
221
|
vertical: number;
|
|
213
222
|
horizontal: number;
|
|
214
223
|
};
|
|
@@ -229,6 +238,8 @@ export type RunEngineProps<State, Custom = never> = {
|
|
|
229
238
|
width?: number;
|
|
230
239
|
height?: number;
|
|
231
240
|
backgroundColor?: string;
|
|
241
|
+
resize?: "none" | "fit" | "stretch";
|
|
242
|
+
pixelRatio?: number | true;
|
|
232
243
|
};
|
|
233
244
|
camera?: (state: State) => {
|
|
234
245
|
x: number;
|
|
@@ -238,6 +249,8 @@ export type RunEngineProps<State, Custom = never> = {
|
|
|
238
249
|
};
|
|
239
250
|
export type RunEngineFunction = <State, Custom = never>(props: RunEngineProps<State, Custom>) => Promise<{
|
|
240
251
|
sendEvent: (event: Custom) => void;
|
|
252
|
+
requestFullscreen: () => Promise<void>;
|
|
253
|
+
exitFullscreen: () => Promise<void>;
|
|
241
254
|
}>;
|
|
242
255
|
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"];
|
|
243
256
|
export type KeyboardKeys = (typeof keyboardKeys)[number];
|
|
@@ -245,5 +258,12 @@ export type KeyboardState = Record<KeyboardKeys, boolean>;
|
|
|
245
258
|
export declare var Yuuna: {
|
|
246
259
|
runEngine: RunEngineFunction;
|
|
247
260
|
STOP: typeof STOP;
|
|
261
|
+
rectangle: (props: Omit<RectangleRenderable, "type">) => RectangleRenderable;
|
|
262
|
+
circle: (props: Omit<CircleRenderable, "type">) => CircleRenderable;
|
|
263
|
+
text: (props: Omit<TextRenderable, "type">) => TextRenderable;
|
|
264
|
+
sprite: (props: Omit<SpriteRenderable, "type">) => SpriteRenderable;
|
|
265
|
+
animatedSprite: (props: Omit<AnimatedSpriteRenderable, "type">) => AnimatedSpriteRenderable;
|
|
266
|
+
line: (props: Omit<LineRenderable, "type">) => LineRenderable;
|
|
267
|
+
group: (props: Omit<GroupRenderable, "type">) => GroupRenderable;
|
|
248
268
|
};
|
|
249
269
|
export {};
|