route-graphics 0.0.8 → 0.0.9
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 +941 -60
- package/dist/RouteGraphics.js +104 -104
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,118 +1,999 @@
|
|
|
1
|
-
|
|
1
|
+
## Overview
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Route Graphics is a declarative UI system that enables developers to create rich, interactive 2D interfaces through JSON configurations. Instead of manipulating DOM elements directly, you define your interface structure using JSON, and Route Graphics handles the rendering, animations, audio, and interactions automatically.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Typical Usage Patterns
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### How It Works
|
|
8
|
+
|
|
9
|
+
Route Graphics follows a **render cycle** where:
|
|
10
|
+
|
|
11
|
+
1. **Parser Functions** convert your JSON input into Abstract Syntax Trees (AST)
|
|
12
|
+
2. **Element Functions** handle the lifecycle:
|
|
13
|
+
- **Add Functions**: Create new visual elements when they appear in the state
|
|
14
|
+
- **Update Functions**: Modify existing elements when their properties change
|
|
15
|
+
- **Delete Functions**: Remove elements when they're no longer in the state
|
|
16
|
+
3. **Animation Functions** apply smooth transitions between states
|
|
17
|
+
4. **Audio Functions** handle sound effects and background music
|
|
18
|
+
|
|
19
|
+
## Architecture Overview
|
|
20
|
+
|
|
21
|
+
Route Graphics follows a modular plugin architecture with three main plugin categories:
|
|
22
|
+
|
|
23
|
+
1. **Element Plugins** - Render visual elements with add/update/delete functions
|
|
24
|
+
2. **Audio Plugins** - Handle audio playback
|
|
25
|
+
3. **Animation Plugins** - Handle dynamic content and transitions
|
|
26
|
+
|
|
27
|
+
## Getting Started
|
|
28
|
+
|
|
29
|
+
### Installation
|
|
8
30
|
|
|
9
31
|
```bash
|
|
10
|
-
|
|
32
|
+
bun install route-graphics
|
|
11
33
|
```
|
|
12
34
|
|
|
13
|
-
|
|
35
|
+
### Basic Usage
|
|
14
36
|
|
|
15
37
|
```javascript
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
import createRouteGraphics, {
|
|
39
|
+
textPlugin,
|
|
40
|
+
rectPlugin,
|
|
41
|
+
spritePlugin,
|
|
42
|
+
sliderPlugin,
|
|
43
|
+
containerPlugin,
|
|
44
|
+
textRevealingPlugin,
|
|
45
|
+
tweenPlugin,
|
|
46
|
+
soundPlugin,
|
|
25
47
|
createAssetBufferManager,
|
|
26
48
|
} from 'route-graphics';
|
|
27
49
|
|
|
28
|
-
//
|
|
50
|
+
// Define assets with URL and type
|
|
29
51
|
const assets = {
|
|
30
|
-
"
|
|
31
|
-
url: "/public/
|
|
52
|
+
"circle-red": {
|
|
53
|
+
url: "/public/circle-red.png",
|
|
32
54
|
type: "image/png",
|
|
33
55
|
},
|
|
34
|
-
"
|
|
35
|
-
url: "/public/circle-
|
|
56
|
+
"circle-blue": {
|
|
57
|
+
url: "/public/circle-blue.png",
|
|
58
|
+
type: "image/png",
|
|
59
|
+
},
|
|
60
|
+
"circle-green": {
|
|
61
|
+
url: "/public/circle-green.png",
|
|
36
62
|
type: "image/png",
|
|
37
63
|
},
|
|
38
|
-
"
|
|
64
|
+
"slider": {
|
|
65
|
+
url: "/public/slider.png",
|
|
66
|
+
type: "image/png",
|
|
67
|
+
},
|
|
68
|
+
"bgm-1": {
|
|
39
69
|
url: "/public/bgm-1.mp3",
|
|
40
70
|
type: "audio/mpeg",
|
|
41
71
|
},
|
|
72
|
+
"bgm-2": {
|
|
73
|
+
url: "/public/bgm-2.mp3",
|
|
74
|
+
type: "audio/mpeg",
|
|
75
|
+
}
|
|
42
76
|
};
|
|
43
77
|
|
|
78
|
+
// Load assets using asset buffer manager
|
|
44
79
|
const assetBufferManager = createAssetBufferManager();
|
|
45
80
|
await assetBufferManager.load(assets);
|
|
46
81
|
const assetBufferMap = assetBufferManager.getBufferMap();
|
|
47
82
|
|
|
48
|
-
//
|
|
49
|
-
const app =
|
|
83
|
+
// Create and initialize app
|
|
84
|
+
const app = createRouteGraphics();
|
|
50
85
|
await app.init({
|
|
51
|
-
width:
|
|
52
|
-
height:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
width: 1280,
|
|
87
|
+
height: 720,
|
|
88
|
+
plugins: {
|
|
89
|
+
elements: [
|
|
90
|
+
textPlugin,
|
|
91
|
+
rectPlugin,
|
|
92
|
+
spritePlugin,
|
|
93
|
+
sliderPlugin,
|
|
94
|
+
containerPlugin,
|
|
95
|
+
textRevealingPlugin
|
|
96
|
+
],
|
|
97
|
+
animations: [
|
|
98
|
+
tweenPlugin
|
|
99
|
+
],
|
|
100
|
+
audios: [
|
|
101
|
+
soundPlugin
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
eventHandler: (eventName, payload) => {
|
|
105
|
+
console.log('Event:', eventName, payload);
|
|
106
|
+
}
|
|
66
107
|
});
|
|
67
108
|
|
|
68
|
-
// Load assets and
|
|
109
|
+
// Load assets into the app and add to DOM
|
|
69
110
|
await app.loadAssets(assetBufferMap);
|
|
70
111
|
document.body.appendChild(app.canvas);
|
|
71
112
|
|
|
72
|
-
// Render
|
|
113
|
+
// Render your UI
|
|
73
114
|
app.render({
|
|
74
115
|
elements: [
|
|
75
116
|
{
|
|
76
117
|
id: "sprite1",
|
|
77
118
|
type: "sprite",
|
|
78
|
-
|
|
119
|
+
src: "file:circle-red",
|
|
120
|
+
x: 100,
|
|
121
|
+
y: 100,
|
|
122
|
+
width: 64,
|
|
123
|
+
height: 64,
|
|
124
|
+
hover: {
|
|
125
|
+
imageSrc: "file:circle-blue",
|
|
126
|
+
soundSrc: "file:hover-sound"
|
|
127
|
+
},
|
|
128
|
+
click: {
|
|
129
|
+
action: "handleClick",
|
|
130
|
+
soundSrc: "file:click-sound"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
animations: [
|
|
135
|
+
{
|
|
136
|
+
id: "fadeIn",
|
|
137
|
+
targetId: "sprite1",
|
|
138
|
+
type: "tween",
|
|
139
|
+
properties: {
|
|
140
|
+
alpha: {
|
|
141
|
+
initialValue: 0,
|
|
142
|
+
keyframes: [
|
|
143
|
+
{ duration: 1000, value: 1, easing: "linear" }
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
audio: [
|
|
150
|
+
{
|
|
151
|
+
id: "bgMusic",
|
|
152
|
+
type: "sound",
|
|
153
|
+
src: "file:bgm-1",
|
|
154
|
+
volume: 600,
|
|
155
|
+
loop: true
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Architecture Overview
|
|
162
|
+
|
|
163
|
+
Route Graphics follows a modular plugin architecture with three main plugin categories:
|
|
164
|
+
|
|
165
|
+
1. **Parser Plugins** - Convert JSON to Abstract Syntax Trees (AST)
|
|
166
|
+
2. **Element Plugins** - Render visual elements (add/update/delete)
|
|
167
|
+
3. **Audio & Animation Plugins** - Handle dynamic content
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
### The Render Function
|
|
171
|
+
|
|
172
|
+
The main `render()` function is called whenever you want to update your interface. It automatically:
|
|
173
|
+
- Compares the previous state with the new state
|
|
174
|
+
- Determines which elements to add, update, or remove
|
|
175
|
+
- Executes the appropriate element functions
|
|
176
|
+
- Applies animations and audio effects
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
// Initial render
|
|
180
|
+
routeGraphics.render(initialState);
|
|
181
|
+
|
|
182
|
+
// Update with new elements
|
|
183
|
+
routeGraphics.render(updatedState); // Automatically adds/updates/removes elements
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Example Lifecycle:**
|
|
187
|
+
```javascript
|
|
188
|
+
// State 1: Show a button
|
|
189
|
+
const state1 = {
|
|
190
|
+
elements: [{
|
|
191
|
+
id: "button1",
|
|
192
|
+
type: "rect",
|
|
193
|
+
width: 100, height: 40,
|
|
194
|
+
fill: "blue"
|
|
195
|
+
}]
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// State 2: Button changes color and moves
|
|
199
|
+
const state2 = {
|
|
200
|
+
elements: [{
|
|
201
|
+
id: "button1",
|
|
202
|
+
type: "rect",
|
|
203
|
+
width: 100, height: 40,
|
|
204
|
+
fill: "red", // Update: color changes
|
|
205
|
+
x: 50, y: 50 // Update: position moves
|
|
206
|
+
}]
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// State 3: Button is gone
|
|
210
|
+
const state3 = {
|
|
211
|
+
elements: [] // Delete: button1 is removed
|
|
212
|
+
};
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Element Plugins
|
|
216
|
+
|
|
217
|
+
Element plugins handle the creation, updating, and deletion of visual elements. Each plugin follows a consistent interface:
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
createElementPlugin({
|
|
221
|
+
type: "element-type",
|
|
222
|
+
add: addFunction, // Create element
|
|
223
|
+
update: updateFunction, // Update existing element
|
|
224
|
+
delete: deleteFunction // Remove element
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Available Element Plugins
|
|
229
|
+
|
|
230
|
+
#### Sprite Plugin
|
|
231
|
+
Renders image-based sprites with rich interactions.
|
|
232
|
+
|
|
233
|
+
**Required Properties:**
|
|
234
|
+
- `id`, `type`, `x`, `y`, `width`, `height`
|
|
235
|
+
|
|
236
|
+
**Optional Properties:**
|
|
237
|
+
- `src`: Source of the sprite image (asset alias)
|
|
238
|
+
- `anchorX`, `anchorY`: Anchor points (default: 0)
|
|
239
|
+
- `alpha`: Opacity/transparency (0-1, default: 1)
|
|
240
|
+
|
|
241
|
+
**Events:**
|
|
242
|
+
- **click**: Triggered when sprite is clicked
|
|
243
|
+
- `src` (string, optional): Change sprite image
|
|
244
|
+
- `soundSrc` (string, optional): Play sound effect
|
|
245
|
+
- `actionPayload` (object, optional): Custom data sent to event handler
|
|
246
|
+
- **hover**: Triggered when mouse enters/exits sprite
|
|
247
|
+
- `src` (string, optional): Change sprite image
|
|
248
|
+
- `soundSrc` (string, optional): Play sound effect
|
|
249
|
+
- `cursor` (string, optional): CSS cursor style
|
|
250
|
+
- `actionPayload` (object, optional): Custom data sent to event handler
|
|
251
|
+
|
|
252
|
+
**Example:**
|
|
253
|
+
```yaml
|
|
254
|
+
id: character
|
|
255
|
+
type: sprite
|
|
256
|
+
x: 100
|
|
257
|
+
y: 100
|
|
258
|
+
width: 64
|
|
259
|
+
height: 64
|
|
260
|
+
src: hero-idle
|
|
261
|
+
hover:
|
|
262
|
+
src: hero-hover
|
|
263
|
+
cursor: pointer
|
|
264
|
+
soundSrc: hover-sound
|
|
265
|
+
actionPayload:
|
|
266
|
+
action: hoverHero
|
|
267
|
+
click:
|
|
268
|
+
src: hero-active
|
|
269
|
+
soundSrc: click-sound
|
|
270
|
+
actionPayload:
|
|
271
|
+
action: activateHero
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Event Data Structure:**
|
|
275
|
+
- **Click Event:**
|
|
276
|
+
```yaml
|
|
277
|
+
_event:
|
|
278
|
+
id: character
|
|
279
|
+
# All actionPayload properties are spread directly here
|
|
280
|
+
action: activateHero
|
|
281
|
+
```
|
|
282
|
+
- **Hover Event:**
|
|
283
|
+
```yaml
|
|
284
|
+
_event:
|
|
285
|
+
id: character
|
|
286
|
+
# All actionPayload properties are spread directly here
|
|
287
|
+
action: hoverHero
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### Text Plugin
|
|
291
|
+
Renders styled text with comprehensive formatting options.
|
|
292
|
+
|
|
293
|
+
**Required Properties:**
|
|
294
|
+
- `id`, `type`, `x`, `y`
|
|
295
|
+
|
|
296
|
+
**Optional Properties:**
|
|
297
|
+
- `content`: Text content to display (default: "")
|
|
298
|
+
- `width`: Width constraint for text wrapping
|
|
299
|
+
- `anchorX`, `anchorY`: Anchor points (default: 0)
|
|
300
|
+
- `alpha`: Opacity (0-1, default: 1)
|
|
301
|
+
- `textStyle`: Complete text styling object
|
|
302
|
+
|
|
303
|
+
**TextStyle Options:**
|
|
304
|
+
- `fill`: Text color (default: "black")
|
|
305
|
+
- `fontFamily`: Font family
|
|
306
|
+
- `fontSize`: Font size in pixels (default: 16)
|
|
307
|
+
- `align`: Text alignment ["left", "center", "right"] (default: "left")
|
|
308
|
+
- `lineHeight`: Line height (default: 1.2)
|
|
309
|
+
- `wordWrap`: Enable word wrapping (default: true)
|
|
310
|
+
- `breakWords`: Allow breaking words (default: true)
|
|
311
|
+
- `strokeColor`: Text outline color
|
|
312
|
+
- `strokeWidth`: Text outline width
|
|
313
|
+
|
|
314
|
+
**Events:**
|
|
315
|
+
- **click**: Triggered when text is clicked
|
|
316
|
+
- `textStyle` (object, optional): Change text styling
|
|
317
|
+
- `soundSrc` (string, optional): Play sound effect
|
|
318
|
+
- `actionPayload` (object, optional): Custom data sent to event handler
|
|
319
|
+
- **hover**: Triggered when mouse enters/exits text
|
|
320
|
+
- `textStyle` (object, optional): Change text styling
|
|
321
|
+
- `soundSrc` (string, optional): Play sound effect
|
|
322
|
+
- `cursor` (string, optional): CSS cursor style
|
|
323
|
+
- `actionPayload` (object, optional): Custom data sent to event handler
|
|
324
|
+
|
|
325
|
+
**Example:**
|
|
326
|
+
```yaml
|
|
327
|
+
id: title
|
|
328
|
+
type: text
|
|
329
|
+
x: 960
|
|
330
|
+
y: 100
|
|
331
|
+
content: Welcome to Route Graphics
|
|
332
|
+
textStyle:
|
|
333
|
+
fill: "#ffffff"
|
|
334
|
+
fontFamily: Arial
|
|
335
|
+
fontSize: 48
|
|
336
|
+
align: center
|
|
337
|
+
hover:
|
|
338
|
+
textStyle:
|
|
339
|
+
fill: "#ffff00"
|
|
340
|
+
cursor: pointer
|
|
341
|
+
soundSrc: hover-sound
|
|
342
|
+
actionPayload:
|
|
343
|
+
action: hoverTitle
|
|
344
|
+
click:
|
|
345
|
+
textStyle:
|
|
346
|
+
fill: "#00ff00"
|
|
347
|
+
soundSrc: click-sound
|
|
348
|
+
actionPayload:
|
|
349
|
+
action: clickTitle
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Event Data Structure:**
|
|
353
|
+
- **Click Event:**
|
|
354
|
+
```yaml
|
|
355
|
+
_event:
|
|
356
|
+
id: title
|
|
357
|
+
# All actionPayload properties are spread directly here
|
|
358
|
+
action: clickTitle
|
|
359
|
+
```
|
|
360
|
+
- **Hover Event:**
|
|
361
|
+
```yaml
|
|
362
|
+
_event:
|
|
363
|
+
id: title
|
|
364
|
+
# All actionPayload properties are spread directly here
|
|
365
|
+
action: hoverTitle
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### Rectangle Plugin
|
|
369
|
+
Creates filled and bordered rectangles with rotation support.
|
|
370
|
+
|
|
371
|
+
**Required Properties:**
|
|
372
|
+
- `id`, `type`, `width`, `height`
|
|
373
|
+
|
|
374
|
+
**Optional Properties:**
|
|
375
|
+
- `x`, `y`: Position (default: 0)
|
|
376
|
+
- `anchorX`, `anchorY`: Anchor points (default: 0)
|
|
377
|
+
- `alpha`: Opacity (0-1, default: 1)
|
|
378
|
+
- `fill`: Fill color (default: "white")
|
|
379
|
+
- `rotation`: Rotation in degrees (default: 0)
|
|
380
|
+
- `border`: Border styling object
|
|
381
|
+
|
|
382
|
+
**Border Options:**
|
|
383
|
+
- `width`: Border width in pixels (default: 0)
|
|
384
|
+
- `color`: Border color (default: "black")
|
|
385
|
+
- `alpha`: Border opacity (0-1, default: 1)
|
|
386
|
+
|
|
387
|
+
**Events:**
|
|
388
|
+
- **click**: Triggered when rectangle is clicked
|
|
389
|
+
- `soundSrc` (string, optional): Play sound effect
|
|
390
|
+
- `actionPayload` (object, optional): Custom data sent to event handler
|
|
391
|
+
- **hover**: Triggered when mouse enters/exits rectangle
|
|
392
|
+
- `soundSrc` (string, optional): Play sound effect
|
|
393
|
+
- `cursor` (string, optional): CSS cursor style
|
|
394
|
+
- `actionPayload` (object, optional): Custom data sent to event handler
|
|
395
|
+
|
|
396
|
+
**Example:**
|
|
397
|
+
```yaml
|
|
398
|
+
id: panel
|
|
399
|
+
type: rect
|
|
400
|
+
x: 50
|
|
401
|
+
y: 50
|
|
402
|
+
width: 400
|
|
403
|
+
height: 300
|
|
404
|
+
fill: "0x333333"
|
|
405
|
+
border:
|
|
406
|
+
width: 2
|
|
407
|
+
color: "0xffffff"
|
|
408
|
+
alpha: 0.8
|
|
409
|
+
alpha: 0.9
|
|
410
|
+
hover:
|
|
411
|
+
cursor: pointer
|
|
412
|
+
soundSrc: hover-sound
|
|
413
|
+
actionPayload:
|
|
414
|
+
action: hoverPanel
|
|
415
|
+
click:
|
|
416
|
+
soundSrc: click-sound
|
|
417
|
+
actionPayload:
|
|
418
|
+
action: clickPanel
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Event Data Structure:**
|
|
422
|
+
For an element with `actionPayload: { action: "clickPanel", panelType: "settings" }`:
|
|
423
|
+
|
|
424
|
+
- **Click Event:**
|
|
425
|
+
```yaml
|
|
426
|
+
_event:
|
|
427
|
+
id: panel
|
|
428
|
+
action: clickPanel
|
|
429
|
+
panelType: settings
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
- **Hover Event:**
|
|
433
|
+
```yaml
|
|
434
|
+
_event:
|
|
435
|
+
id: panel
|
|
436
|
+
action: hoverPanel
|
|
437
|
+
panelType: settings
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### Container Plugin
|
|
441
|
+
Groups and manages layout of multiple elements.
|
|
442
|
+
|
|
443
|
+
**Required Properties:**
|
|
444
|
+
- `id`, `type`, `x`, `y`
|
|
445
|
+
|
|
446
|
+
**Optional Properties:**
|
|
447
|
+
- `width`, `height`: Container dimensions (calculated from children if not set)
|
|
448
|
+
- `anchorX`, `anchorY`: Anchor points (must be 0, 0.5, or 1 for containers)
|
|
449
|
+
- `alpha`: Opacity (0-1, default: 1)
|
|
450
|
+
- `children`: Array of child elements (default: [])
|
|
451
|
+
- `direction`: Layout layout ["absolute", "horizontal", "vertical"] (default: "absolute")
|
|
452
|
+
- `gap`: Spacing between children in pixels (default: 0)
|
|
453
|
+
- `rotation`: Rotation in degrees (default: 0)
|
|
454
|
+
- `scroll`: Enable scrolling for overflow content (default: false)
|
|
455
|
+
|
|
456
|
+
**Events:** None
|
|
457
|
+
|
|
458
|
+
**Layout Directions:**
|
|
459
|
+
- `horizontal`: Left-to-right arrangement
|
|
460
|
+
- `vertical`: Top-to-bottom arrangement
|
|
461
|
+
|
|
462
|
+
**Example:**
|
|
463
|
+
```yaml
|
|
464
|
+
id: menu
|
|
465
|
+
type: container
|
|
466
|
+
x: 0
|
|
467
|
+
y: 0
|
|
468
|
+
width: 200
|
|
469
|
+
height: 400
|
|
470
|
+
direction: vertical
|
|
471
|
+
gap: 10
|
|
472
|
+
children:
|
|
473
|
+
- id: btn1
|
|
474
|
+
type: sprite
|
|
475
|
+
x: 0
|
|
476
|
+
y: 0
|
|
477
|
+
width: 200
|
|
478
|
+
height: 50
|
|
479
|
+
src: button-normal
|
|
480
|
+
- id: btn2
|
|
481
|
+
type: sprite
|
|
482
|
+
x: 0
|
|
483
|
+
y: 60
|
|
484
|
+
width: 200
|
|
485
|
+
height: 50
|
|
486
|
+
src: button-normal
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
#### Text Revealing Plugin
|
|
490
|
+
Animated text display with typewriter effects.
|
|
491
|
+
|
|
492
|
+
**Required Properties:**
|
|
493
|
+
- `id`, `type`, `x`, `y`
|
|
494
|
+
|
|
495
|
+
**Optional Properties:**
|
|
496
|
+
- `content`: Array of text segments with individual styling
|
|
497
|
+
- `width`: Width constraint for text wrapping
|
|
498
|
+
- `anchorX`, `anchorY`: Anchor points (default: 0)
|
|
499
|
+
- `alpha`: Opacity (0-1, default: 1)
|
|
500
|
+
- `textStyle`: Default text styling for all segments
|
|
501
|
+
- `speed`: Animation speed (default: 50)
|
|
502
|
+
- `revealEffect`: Animation type ["typewriter", "none"] (default: "typewriter")
|
|
503
|
+
- `indicator`: Continuation indicator settings
|
|
504
|
+
|
|
505
|
+
**Content Structure:**
|
|
506
|
+
```yaml
|
|
507
|
+
content:
|
|
508
|
+
- text: "Hello "
|
|
509
|
+
textStyle: # Optional individual styling
|
|
510
|
+
fill: red
|
|
511
|
+
furigana: # Optional Japanese annotations
|
|
512
|
+
text: "こ"
|
|
513
|
+
textStyle:
|
|
514
|
+
fontSize: 12
|
|
515
|
+
- text: "World!"
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Indicator Options:**
|
|
519
|
+
- `revealing`: `src`, `width`, `height` - indicator during animation
|
|
520
|
+
- `complete`: `src`, `width`, `height` - indicator when finished
|
|
521
|
+
- `offset`: Distance between text and indicator (default: 12)
|
|
522
|
+
|
|
523
|
+
**Events:** None (Text Revealing elements do not support events)
|
|
524
|
+
|
|
525
|
+
**Example:**
|
|
526
|
+
```yaml
|
|
527
|
+
id: story
|
|
528
|
+
type: textRevealing
|
|
529
|
+
x: 100
|
|
530
|
+
y: 200
|
|
531
|
+
content:
|
|
532
|
+
- text: "Hello "
|
|
533
|
+
textStyle:
|
|
534
|
+
fill: red
|
|
535
|
+
- text: "World!"
|
|
536
|
+
textStyle:
|
|
537
|
+
fill: blue
|
|
538
|
+
speed: 50
|
|
539
|
+
revealEffect: typewriter
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### Slider Plugin
|
|
543
|
+
Interactive slider controls for value input.
|
|
544
|
+
|
|
545
|
+
**Required Properties:**
|
|
546
|
+
- `id`, `type`, `x`, `y`, `direction`, `thumbSrc`, `barSrc`
|
|
547
|
+
|
|
548
|
+
**Optional Properties:**
|
|
549
|
+
- `anchorX`, `anchorY`: Anchor points (default: 0)
|
|
550
|
+
- `alpha`: Opacity (0-1, default: 1)
|
|
551
|
+
- `width`, `height`: Slider dimensions
|
|
552
|
+
- `min`: Minimum value (default: 0)
|
|
553
|
+
- `max`: Maximum value (default: 100)
|
|
554
|
+
- `step`: Value increment (default: 1, minimum: 0)
|
|
555
|
+
- `initialValue`: Starting value (default: 0)
|
|
556
|
+
|
|
557
|
+
**Events:**
|
|
558
|
+
- **hover**: Triggered when mouse enters/exits slider
|
|
559
|
+
- `thumbSrc` (string, optional): Change thumb sprite image
|
|
560
|
+
- `barSrc` (string, optional): Change bar sprite image
|
|
561
|
+
- `soundSrc` (string, optional): Play sound effect
|
|
562
|
+
- `cursor` (string, optional): CSS cursor style
|
|
563
|
+
- **change**: Triggered during slider drag with current value
|
|
564
|
+
- `actionPayload` (object, optional): Custom data sent to event handler
|
|
565
|
+
|
|
566
|
+
**Example:**
|
|
567
|
+
```yaml
|
|
568
|
+
id: volumeSlider
|
|
569
|
+
type: slider
|
|
570
|
+
x: 100
|
|
571
|
+
y: 500
|
|
572
|
+
direction: horizontal
|
|
573
|
+
thumbSrc: slider-thumb
|
|
574
|
+
barSrc: slider-bar
|
|
575
|
+
min: 0
|
|
576
|
+
max: 100
|
|
577
|
+
step: 1
|
|
578
|
+
initialValue: 50
|
|
579
|
+
hover:
|
|
580
|
+
thumbSrc: slider-thumb-hover
|
|
581
|
+
barSrc: slider-bar-hover
|
|
582
|
+
cursor: pointer
|
|
583
|
+
soundSrc: slider-hover
|
|
584
|
+
change:
|
|
585
|
+
actionPayload:
|
|
586
|
+
action: updateVolume
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Event Data Structure:**
|
|
590
|
+
- **Hover Event:** None (slider hover doesn't trigger event data)
|
|
591
|
+
- **Change Event:** For a slider with `actionPayload: { action: "updateVolume" }` and current value of 75:
|
|
592
|
+
```yaml
|
|
593
|
+
_event:
|
|
594
|
+
id: volumeSlider
|
|
595
|
+
value: 75
|
|
596
|
+
action: updateVolume
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Audio Plugin
|
|
600
|
+
|
|
601
|
+
Integrated audio system for sound effects and background music.
|
|
602
|
+
|
|
603
|
+
### Sound Plugin Properties
|
|
604
|
+
|
|
605
|
+
**Required Properties:**
|
|
606
|
+
- `id`, `type`, `src`
|
|
607
|
+
|
|
608
|
+
**Optional Properties:**
|
|
609
|
+
- `volume`: Volume level (default: 800, minimum: 0)
|
|
610
|
+
- `loop`: Whether to loop the audio (default: false)
|
|
611
|
+
- `delay`: Delay before playing in milliseconds (default: 0)
|
|
612
|
+
|
|
613
|
+
**Volume Details:**
|
|
614
|
+
- 0 = muted
|
|
615
|
+
- 1000 = original full volume
|
|
616
|
+
- Values above 1000 = amplified (may clip)
|
|
617
|
+
|
|
618
|
+
**Example:**
|
|
619
|
+
```yaml
|
|
620
|
+
id: bgMusic
|
|
621
|
+
type: sound
|
|
622
|
+
src: bgm-level1
|
|
623
|
+
volume: 800
|
|
624
|
+
loop: true
|
|
625
|
+
delay: 500
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Audio Integration
|
|
629
|
+
|
|
630
|
+
Audio integrates seamlessly with element interactions:
|
|
631
|
+
|
|
632
|
+
```yaml
|
|
633
|
+
id: button
|
|
634
|
+
type: sprite
|
|
635
|
+
# ... other properties
|
|
636
|
+
hover:
|
|
637
|
+
soundSrc: file:hover-sound # Plays on hover
|
|
638
|
+
click:
|
|
639
|
+
soundSrc: file:click-sound # Plays on click
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
## Animation System
|
|
643
|
+
|
|
644
|
+
Keyframe-based animation system for smooth transitions and effects.
|
|
645
|
+
|
|
646
|
+
### Tween Animation Plugin
|
|
647
|
+
|
|
648
|
+
**Properties:**
|
|
649
|
+
- `id`, `targetId`, `type`, `properties`
|
|
650
|
+
|
|
651
|
+
**Animatable Properties:**
|
|
652
|
+
- `alpha`, `x`, `y`, `scaleX`, `scaleY`, `rotation`
|
|
653
|
+
|
|
654
|
+
**Example:**
|
|
655
|
+
```yaml
|
|
656
|
+
id: fadeSlide
|
|
657
|
+
targetId: myElement
|
|
658
|
+
type: tween
|
|
659
|
+
properties:
|
|
660
|
+
alpha:
|
|
661
|
+
initialValue: 0
|
|
662
|
+
keyframes:
|
|
663
|
+
- duration: 500
|
|
664
|
+
value: 1
|
|
665
|
+
easing: linear
|
|
666
|
+
- duration: 1000
|
|
667
|
+
value: 0.5
|
|
668
|
+
easing: linear
|
|
669
|
+
x:
|
|
670
|
+
initialValue: 100
|
|
671
|
+
keyframes:
|
|
672
|
+
- duration: 1500
|
|
673
|
+
value: 500
|
|
674
|
+
easing: linear
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
## Creating Custom Plugins
|
|
678
|
+
|
|
679
|
+
The plugin system makes it easy to add new element types, animations, or audio handlers.
|
|
680
|
+
|
|
681
|
+
### Element Plugin Creation
|
|
682
|
+
|
|
683
|
+
```javascript
|
|
684
|
+
import { createElementPlugin } from 'route-graphics';
|
|
685
|
+
|
|
686
|
+
// Create custom element plugin
|
|
687
|
+
const customPlugin = createElementPlugin({
|
|
688
|
+
type: "myCustomElement",
|
|
689
|
+
add: ({ element, app }) => {
|
|
690
|
+
// Creation logic - return a PIXI display object
|
|
691
|
+
const graphics = new Graphics();
|
|
692
|
+
graphics.beginFill(element.fill || 0xffffff);
|
|
693
|
+
graphics.drawRect(element.x, element.y, element.width, element.height);
|
|
694
|
+
graphics.endFill();
|
|
695
|
+
return graphics;
|
|
696
|
+
},
|
|
697
|
+
update: ({ element, app, displayObject }) => {
|
|
698
|
+
// Update logic - modify the display object
|
|
699
|
+
if (displayObject) {
|
|
700
|
+
displayObject.x = element.x;
|
|
701
|
+
displayObject.y = element.y;
|
|
702
|
+
displayObject.alpha = element.alpha ?? 1;
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
delete: ({ element, app, displayObject }) => {
|
|
706
|
+
// Cleanup logic
|
|
707
|
+
if (displayObject) {
|
|
708
|
+
displayObject.destroy();
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
// Optional: Parse function for JSON to AST conversion
|
|
712
|
+
parse: (element) => {
|
|
713
|
+
// Parse JSON element to AST format
|
|
714
|
+
return {
|
|
715
|
+
...element,
|
|
716
|
+
parsed: true
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
// Register plugin in your app
|
|
722
|
+
await app.init({
|
|
723
|
+
// ... other config
|
|
724
|
+
plugins: {
|
|
725
|
+
elements: [
|
|
726
|
+
// ... existing plugins
|
|
727
|
+
customPlugin
|
|
728
|
+
],
|
|
729
|
+
animations: [/* ... */],
|
|
730
|
+
audios: [/* ... */]
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Animation Plugin Creation
|
|
736
|
+
|
|
737
|
+
```javascript
|
|
738
|
+
import { createAnimationPlugin } from 'route-graphics';
|
|
739
|
+
|
|
740
|
+
const customAnimationPlugin = createAnimationPlugin({
|
|
741
|
+
type: "myCustomAnimation",
|
|
742
|
+
add: ({ animation, app }) => {
|
|
743
|
+
// Animation creation logic
|
|
744
|
+
return {
|
|
745
|
+
animation,
|
|
746
|
+
startTime: Date.now(),
|
|
747
|
+
active: true
|
|
748
|
+
};
|
|
749
|
+
},
|
|
750
|
+
update: ({ animation, app, animationData }) => {
|
|
751
|
+
// Animation update logic
|
|
752
|
+
if (animationData.active) {
|
|
753
|
+
const elapsed = Date.now() - animationData.startTime;
|
|
754
|
+
if (elapsed >= animation.duration) {
|
|
755
|
+
animationData.active = false;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
delete: ({ animation, app, animationData }) => {
|
|
760
|
+
// Animation cleanup logic
|
|
761
|
+
// Stop any ongoing animations
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### Audio Plugin Creation
|
|
767
|
+
|
|
768
|
+
```javascript
|
|
769
|
+
import { createAudioPlugin } from 'route-graphics';
|
|
770
|
+
|
|
771
|
+
const customAudioPlugin = createAudioPlugin({
|
|
772
|
+
type: "myCustomAudio",
|
|
773
|
+
add: ({ audio, app }) => {
|
|
774
|
+
// Audio creation logic
|
|
775
|
+
return {
|
|
776
|
+
audio,
|
|
777
|
+
startTime: Date.now(),
|
|
778
|
+
playing: true
|
|
779
|
+
};
|
|
780
|
+
},
|
|
781
|
+
update: ({ audio, app, audioData }) => {
|
|
782
|
+
// Audio update logic
|
|
783
|
+
if (audioData.playing && Date.now() - audioData.startTime >= audio.delay) {
|
|
784
|
+
// Start playing after delay
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
delete: ({ audio, app, audioData }) => {
|
|
788
|
+
// Audio cleanup logic
|
|
789
|
+
// Stop audio playback
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
## Parser System
|
|
795
|
+
|
|
796
|
+
The parser system transforms your JSON input into Abstract Syntax Trees (AST) that the rendering engine can process. Each element plugin includes its own parser function that converts JSON definitions into the internal AST format.
|
|
797
|
+
|
|
798
|
+
### parseElements Function
|
|
799
|
+
|
|
800
|
+
```javascript
|
|
801
|
+
// Main parsing function that processes all elements
|
|
802
|
+
const parsedElements = parseElements({
|
|
803
|
+
JSONObject: [
|
|
804
|
+
{
|
|
805
|
+
id: "myButton",
|
|
806
|
+
type: "sprite",
|
|
79
807
|
x: 100,
|
|
80
808
|
y: 100,
|
|
809
|
+
width: 64,
|
|
810
|
+
height: 64
|
|
81
811
|
}
|
|
82
812
|
],
|
|
83
|
-
|
|
813
|
+
parserPlugins: [spritePlugin, textPlugin, ...]
|
|
84
814
|
});
|
|
85
815
|
```
|
|
86
816
|
|
|
87
|
-
|
|
817
|
+
### Parser Function Properties
|
|
818
|
+
|
|
819
|
+
**Parameters:**
|
|
820
|
+
- `state`: The raw JSON element definition from the input
|
|
821
|
+
- `parserPlugins`: Array of available parser plugins (useful for nested elements like containers)
|
|
822
|
+
|
|
823
|
+
**Returns:**
|
|
824
|
+
- AST (Abstract Syntax Tree) node with processed properties ready for rendering
|
|
825
|
+
|
|
826
|
+
### Parser Function Example
|
|
827
|
+
|
|
828
|
+
```javascript
|
|
829
|
+
const parseMyElement = ({ state, parserPlugins }) => {
|
|
830
|
+
// Validate required properties
|
|
831
|
+
if (!state.id || !state.type) {
|
|
832
|
+
throw new Error('Missing required properties');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Apply defaults and type conversion
|
|
836
|
+
return {
|
|
837
|
+
id: state.id,
|
|
838
|
+
type: state.type,
|
|
839
|
+
x: Math.round(state.x ?? 0),
|
|
840
|
+
y: Math.round(state.y ?? 0),
|
|
841
|
+
width: Math.round(state.width ?? 100),
|
|
842
|
+
height: Math.round(state.height ?? 50),
|
|
843
|
+
// Custom property with default
|
|
844
|
+
customProperty: state.customProperty ?? "default",
|
|
845
|
+
// Example: Hex color to number conversion
|
|
846
|
+
color: state.color ? parseInt(state.color.replace('#', ''), 16) : 0x000000,
|
|
847
|
+
};
|
|
848
|
+
};
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
## Asset Management
|
|
852
|
+
|
|
853
|
+
Route Graphics uses a sophisticated asset management system with aliasing.
|
|
854
|
+
|
|
855
|
+
### Asset Management
|
|
856
|
+
|
|
857
|
+
#### Loading Assets
|
|
858
|
+
|
|
859
|
+
Load assets using the `createAssetBufferManager`:
|
|
860
|
+
|
|
861
|
+
```javascript
|
|
862
|
+
import { createAssetBufferManager } from 'route-graphics';
|
|
863
|
+
|
|
864
|
+
// Define assets with URL and type
|
|
865
|
+
const assets = {
|
|
866
|
+
"hero-sprite": {
|
|
867
|
+
url: "./assets/hero.png",
|
|
868
|
+
type: "image/png",
|
|
869
|
+
},
|
|
870
|
+
"background-image": {
|
|
871
|
+
url: "./assets/background.jpg",
|
|
872
|
+
type: "image/jpeg"
|
|
873
|
+
},
|
|
874
|
+
"bg-music": {
|
|
875
|
+
url: "./audio/background.mp3",
|
|
876
|
+
type: "audio/mpeg",
|
|
877
|
+
},
|
|
878
|
+
"click-sound": {
|
|
879
|
+
url: "./audio/click.wav",
|
|
880
|
+
type: "audio/wav"
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
// Load assets using asset buffer manager
|
|
885
|
+
const assetBufferManager = createAssetBufferManager();
|
|
886
|
+
await assetBufferManager.load(assets);
|
|
887
|
+
const assetBufferMap = assetBufferManager.getBufferMap();
|
|
888
|
+
|
|
889
|
+
// Load assets into the app
|
|
890
|
+
await app.loadAssets(assetBufferMap);
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
#### Asset Aliases
|
|
894
|
+
|
|
895
|
+
Once loaded, assets are referenced by their alias keys:
|
|
896
|
+
|
|
897
|
+
```yaml
|
|
898
|
+
id: hero
|
|
899
|
+
type: sprite
|
|
900
|
+
src: hero-sprite
|
|
901
|
+
```
|
|
88
902
|
|
|
89
|
-
|
|
90
|
-
- Sprite management
|
|
91
|
-
- Text rendering with reveal effects
|
|
92
|
-
- Container layouts
|
|
93
|
-
- Transitions and animations
|
|
94
|
-
- Interactive elements
|
|
95
|
-
- Audio playback
|
|
903
|
+
#### Supported Asset Types
|
|
96
904
|
|
|
97
|
-
|
|
905
|
+
- **Images**: PNG, JPG, JPEG, WebP, GIF
|
|
906
|
+
- **Audio**: MP3, WAV, OGG
|
|
907
|
+
- **Fonts**: TTF, OTF, WOFF
|
|
908
|
+
|
|
909
|
+
## Development & Testing
|
|
910
|
+
|
|
911
|
+
### Running Tests
|
|
98
912
|
|
|
99
913
|
```bash
|
|
100
|
-
#
|
|
101
|
-
bun
|
|
914
|
+
# Run all tests
|
|
915
|
+
bun run test
|
|
916
|
+
```
|
|
917
|
+
### Code Quality
|
|
102
918
|
|
|
103
|
-
|
|
919
|
+
```bash
|
|
920
|
+
# Build visual tests
|
|
104
921
|
bun run vt:generate
|
|
105
922
|
|
|
106
|
-
# Lint
|
|
107
|
-
bun run lint
|
|
108
923
|
|
|
109
|
-
#
|
|
110
|
-
bun run
|
|
924
|
+
# Fix linting issues
|
|
925
|
+
bun run lint:fix
|
|
111
926
|
|
|
112
|
-
|
|
113
|
-
bun run test:coverage
|
|
927
|
+
```
|
|
114
928
|
|
|
929
|
+
## Complete YAML Example
|
|
115
930
|
|
|
931
|
+
```yaml
|
|
932
|
+
elements:
|
|
933
|
+
- id: background
|
|
934
|
+
type: sprite
|
|
935
|
+
x: 0
|
|
936
|
+
y: 0
|
|
937
|
+
width: 1920
|
|
938
|
+
height: 1080
|
|
939
|
+
src: file:bg-image
|
|
940
|
+
- id: title
|
|
941
|
+
type: text
|
|
942
|
+
x: 960
|
|
943
|
+
y: 100
|
|
944
|
+
content: Game Title
|
|
945
|
+
textStyle:
|
|
946
|
+
fill: "#ffffff"
|
|
947
|
+
fontFamily: Arial
|
|
948
|
+
fontSize: 64
|
|
949
|
+
align: center
|
|
950
|
+
- id: buttonContainer
|
|
951
|
+
type: container
|
|
952
|
+
x: 960
|
|
953
|
+
y: 400
|
|
954
|
+
width: 200
|
|
955
|
+
height: 300
|
|
956
|
+
direction: vertical
|
|
957
|
+
gap: 20
|
|
958
|
+
children:
|
|
959
|
+
- id: startBtn
|
|
960
|
+
type: sprite
|
|
961
|
+
x: 0
|
|
962
|
+
y: 0
|
|
963
|
+
width: 200
|
|
964
|
+
height: 50
|
|
965
|
+
src: file:btn-normal
|
|
966
|
+
hover:
|
|
967
|
+
imageSrc: file:btn-hover
|
|
968
|
+
soundSrc: file:hover-sound
|
|
969
|
+
click:
|
|
970
|
+
action: startGame
|
|
971
|
+
soundSrc: file:click-sound
|
|
972
|
+
animations:
|
|
973
|
+
- id: titleFadeIn
|
|
974
|
+
targetId: title
|
|
975
|
+
type: tween
|
|
976
|
+
properties:
|
|
977
|
+
alpha:
|
|
978
|
+
initialValue: 0
|
|
979
|
+
keyframes:
|
|
980
|
+
- duration: 2000
|
|
981
|
+
value: 1
|
|
982
|
+
easing: linear
|
|
983
|
+
audio:
|
|
984
|
+
- id: bgMusic
|
|
985
|
+
type: sound
|
|
986
|
+
src: file:background-music
|
|
987
|
+
volume: 600
|
|
988
|
+
loop: true
|
|
116
989
|
```
|
|
117
990
|
|
|
118
991
|
|
|
992
|
+
## Schemas
|
|
993
|
+
|
|
994
|
+
All element types are defined with YAML schemas in the `src/schemas/` directory, providing:
|
|
995
|
+
|
|
996
|
+
- Type definitions and validation
|
|
997
|
+
- Property documentation
|
|
998
|
+
- Default values
|
|
999
|
+
- Required field specifications
|