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 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 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
- // The shape of your game's data whatever it takes to fully describe
36
- // what's on screen and how it behaves
37
- type GameState = { cookies: number };
38
-
39
- // What that state looks like before anything has happened yet
40
- const initialState: GameState = { cookies: 0 };
41
-
42
- runEngine<GameState>({
43
- initialState,
44
-
45
- // Given the current state, what should be drawn this frame? Called
46
- // every frame — always derive the picture from state, instead of
47
- // reaching for the canvas directly.
48
- render: (state) => ({
49
- renderables: [
50
- {
51
- type: "TEXT",
52
- text: `${state.cookies} cookies`,
53
- color: "black",
54
- position: { x: 100, y: 50 },
55
- },
56
- {
57
- type: "CIRCLE",
58
- id: "cookie",
59
- isClickable: true,
60
- color: "brown",
61
- position: { x: 50, y: 50 },
62
- radius: 25,
63
- },
64
- ],
65
- }),
66
-
67
- // Given the current state and something that just happened, what's the
68
- // next state? Called once per event (a click, a frame tick, ...) — the
69
- // only place game logic lives.
70
- nextState: ({ state, event }) => {
71
- if (event.tag === "CLICK" && event.id === "cookie") {
72
- return { cookies: state.cookies + 1 };
73
- }
74
-
75
- return state;
76
- },
77
- });
78
- ```
79
-
80
- ## Concepts
81
-
82
- - **Renderables** declarative shapes drawn each frame: `RECTANGLE`,
83
- `CIRCLE`, `TEXT`, `SPRITE`, `ANIMATED_SPRITE`, `LINE`, and `GROUP`. Give
84
- one an `id` plus `isClickable`/`isHoverable` to make it interactive.
85
- - **Events** `nextState` receives one `GameEvent` per call: `TIME`,
86
- `CLICK`, `HOVER_IN`, `HOVER_OUT`, `MOUSE_MOVE`, `MOUSE_LEAVE`,
87
- `MUSIC_END`, or a `CUSTOM` event of a type you define yourself, for
88
- reporting things like an async `fetch()` resolving back into your
89
- state machine.
90
- - **Keyboard, camera, sprites & animation, sound effects & music,
91
- canvas config, and mechanics pipelines** all follow the same idea:
92
- small, focused props and functions `runEngine`/`nextState` take, that
93
- compose with everything above instead of replacing it.
94
-
95
- This README stays intentionally thin the full concept-by-concept
96
- reference, with every option and example, lives on the
97
- [wiki](https://github.com/lucy-dot-exe/yuuna/wiki). The
98
- [playground](https://lucy-dot-exe.github.io/yuuna/#playground) also has a
99
- small, focused example for most of these you can run and edit directly.
100
-
101
- ## Templates
102
-
103
- Prefer a working starting point over typing the quick start out by
104
- hand? Grab one from [`templates/`](templates):
105
-
106
- - **[blank](templates/blank)** a single `index.html`, zero install —
107
- open it in a browser and it runs.
108
- - **[npm](templates/npm)** TypeScript + a dev server with hot reload
109
- (via Vite), for a real local project.
110
-
111
- ```sh
112
- npx degit lucy-dot-exe/yuuna/templates/blank my-game
113
- # or: npx degit lucy-dot-exe/yuuna/templates/npm my-game
114
- ```
115
-
116
- [`degit`](https://github.com/Rich-Harris/degit) copies the folder without
117
- its git history — no cloning or forking the whole engine repo needed.
118
- Each template's own README has more on running it once copied.
119
-
120
- ## Development
121
-
122
- ```sh
123
- yarn install
124
- yarn build # builds lib/ (npm package) and dist/bundle.js (landing page)
125
- yarn watch # rebuild on change
126
- ```
127
-
128
- `dist/index.html` is the landing page — it loads `dist/bundle.js` in the
129
- browser via a global `Yuuna` object and embeds a live Monaco editor so
130
- visitors can edit and run a game directly on the page.
131
-
132
- ## Assets
133
-
134
- The examples' art/sound/music lives in `dist/resources/`, gitignored
135
- rather than committed — this repo being open source doesn't make every
136
- asset in it free to redistribute. `runEngine()` falls back to a
137
- generated placeholder for any image that isn't there (and simply plays
138
- nothing for missing audio) instead of failing, so the examples still
139
- run without them — just with placeholder art in place of the real
140
- thing. Drop the real files in locally (or restore them from wherever
141
- you got this repo from) to see them for real.
142
-
143
- Currently used:
144
-
145
- - **[Free Pixel Food!](https://henrysoftware.itch.io/pixel-food)** by
146
- [Henry Software](https://henrysoftware.itch.io/) the food icons in
147
- the Food Clicker and Sprites examples. CC0; credited here by choice,
148
- not requirement.
149
-
150
- ## License
151
-
152
- MIT © [lucy-dot-exe](https://github.com/lucy-dot-exe)
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;
@@ -54,7 +54,7 @@ export type SpriteRenderable = BaseRenderable & {
54
54
  y: number;
55
55
  };
56
56
  resourceId: string;
57
- frame: number;
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 GameEvent = TimeEvent | ClickEvent | HoverInEvent | HoverOutEvent | MouseMoveEvent | MouseLeaveEvent | TabBlurEvent | TabFocusEvent | MusicEndEvent;
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 {};