create-gamenative-app 0.1.9 → 0.1.10

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
@@ -104,13 +104,73 @@ Requires [pkg](https://github.com/vercel/pkg); target is `node18-win-x64`. For o
104
104
  | `npm start` | Run `dist/main.js` (needs `game.config.json`) |
105
105
  | `npm run build:exe` | Build then package to `release/GameNative.exe` |
106
106
 
107
- ## API overview (for game code)
107
+ ## API reference (everything you need to make a game)
108
108
 
109
- - **`run(game, config?)`** Framework entry; config: `title`, `width`, `height`, `vsync`, `resizable`, `icon`.
110
- - **`IGame`**: optional `init(ctx, config)`, required `update(dt)`, `draw()`, optional `dispose()`.
111
- - **`GameContext`**: `gl`, `input`, `width`, `height`.
112
- - **Input**: `ctx.input.mouseX`, `mouseY`, `mouseLeft`, `mouseRight`, `mouseMiddle`, `isKeyDown(scancode)`.
113
- - **`SCANCODE`**: e.g. `SCANCODE.W`, `SCANCODE.Escape`.
114
- - **`createDraw2D(gl)`**: `clear(r,g,b,a?)`, `fillRect(x,y,w,h,r,g,b,a?)`, `dispose()`.
109
+ Import from `../../scripts/index.js` (or your framework path). All APIs are available in your game file.
115
110
 
116
- For custom rendering, use `ctx.gl` (WebGL 1) directly.
111
+ ### Game lifecycle
112
+
113
+ - **`run(game, config?)`** — Starts the framework. Call once from your entry point. `config`: `title`, `width`, `height`, `vsync`, `resizable`, `icon`.
114
+ - **`IGame`** — Your game implements: optional **`init(ctx, config)`**, required **`update(dt)`**, required **`draw()`**, optional **`dispose()`**.
115
+ - **`GameContext`** — Passed to `init` and available each frame: **`gl`** (WebGL context), **`input`**, **`width`**, **`height`** (updates on resize).
116
+
117
+ ### Input
118
+
119
+ - **`ctx.input.mouseX`**, **`ctx.input.mouseY`** — Cursor position in pixels.
120
+ - **`ctx.input.mouseLeft`**, **`mouseRight`**, **`mouseMiddle`** — Boolean button state.
121
+ - **`ctx.input.isKeyDown(scancode)`** — Returns true if key is held. Use **`SCANCODE`** for key codes: `SCANCODE.W`, `SCANCODE.A`, `SCANCODE.S`, `SCANCODE.D`, `SCANCODE.Space`, `SCANCODE.Escape`, `SCANCODE.Up`, `SCANCODE.Down`, `SCANCODE.Left`, `SCANCODE.Right`, `SCANCODE.Enter`, `SCANCODE.Shift`, `SCANCODE.Ctrl`, `SCANCODE.Tab`, and `SCANCODE.A`–`SCANCODE.Z`.
122
+
123
+ ### 2D drawing (`createDraw2D`)
124
+
125
+ - **`createDraw2D(ctx.gl)`** — Returns a Draw2D helper. Create once in `init`, reuse in `draw`.
126
+ - **`draw2d.clear(r, g, b, a?)`** — Clear the screen (default a = 1).
127
+ - **`draw2d.fillRect(x, y, w, h, r, g, b, a?)`** — Filled rectangle in pixels. Origin top-left, y down.
128
+ - **`draw2d.fillTriangles(verts, r, g, b, a?)`** — Draw triangles. `verts` is a `Float32Array` of pixel coords: `[x,y, x,y, x,y, ...]` (3 vertices per triangle).
129
+ - **`draw2d.strokeRect(x, y, w, h, r, g, b, a?)`** — 1px outline rectangle (panels, buttons).
130
+ - **`draw2d.dispose()`** — Call in `dispose()` to free resources.
131
+
132
+ ### Text (fonts, sizes, colors)
133
+
134
+ - **`loadFont(path)`** — Async. Load a TTF/OTF from path (relative to cwd or absolute). Returns a **`Font`**.
135
+ - **`drawText(draw2d, font, text, x, y, size, r, g, b, a?)`** — Draw a string. (x, y) = top-left; y-axis down; `size` = font size in pixels.
136
+ - **`getTextTriangles(font, text, x, y, size)`** — Returns `Float32Array` of triangle vertices. Use with `draw2d.fillTriangles(triangles, r, g, b, a)` for custom color or caching.
137
+ - **`measureText(font, text, size)`** — Returns `{ width, height }` in pixels (for layout).
138
+
139
+ ### UI / hit-testing
140
+
141
+ - **`isPointInRect(px, py, x, y, w, h)`** — Returns true if point (px, py) is inside the rectangle. Use with `ctx.input.mouseX`, `mouseY`, `mouseLeft` in `update()` to implement buttons.
142
+
143
+ ### Textures (images / sprites)
144
+
145
+ - **`loadTexture(gl, path)`** — Async. Load a PNG from path, upload to a WebGL texture, return **`{ texture, width, height }`**. You bind and draw with your own shader; the helper only does load + upload.
146
+
147
+ ### Raw WebGL — do anything
148
+
149
+ - **`ctx.gl`** — Full WebGL 1 context. No wrapper. Use it for custom shaders, 3D, framebuffers, MSAA, texture arrays, etc. You have full control.
150
+
151
+ ### Full SDL — do anything
152
+
153
+ - **`sdl`** — The full [@kmamal/sdl](https://github.com/kmamal/node-sdl) module is exported. Use it directly for everything SDL exposes:
154
+ - **`sdl.video`** — displays, windows (we create one; you can query others).
155
+ - **`sdl.keyboard`**, **`sdl.mouse`**, **`sdl.touch`** — input (we use keyboard + mouse for `ctx.input`; you can use more).
156
+ - **`sdl.audio`** — **`sdl.audio.getDevices()`**, **`sdl.audio.openDevice(device, options)`** for playback/recording. Open a device, enqueue buffers (WAV/samples), no high-level helper.
157
+ - **`sdl.joystick`**, **`sdl.controller`** — gamepads; **`sdl.sensor`** — accelerometer/gyro.
158
+ - **`sdl.clipboard`**, **`sdl.power`** — clipboard, battery.
159
+
160
+ ### Node / OS — do anything
161
+
162
+ - Your game runs in **Node.js**. You can **`import 'fs'`**, **`import 'path'`**, use **`child_process`**, native addons, or any npm package. File I/O, save data, scripting, or calling out to other tools — nothing is locked down.
163
+
164
+ ### Exports summary
165
+
166
+ | Export | Purpose |
167
+ |--------|--------|
168
+ | `run`, `sdl`, `createInput`, `SCANCODE` | Entry, full SDL ref, input factory, key codes |
169
+ | `IGame`, `GameConfig`, `GameContext` | Types for your game and config |
170
+ | `InputState` | Type for `ctx.input` |
171
+ | `createDraw2D`, `Draw2D` | 2D clear/rect/triangles/stroke |
172
+ | `loadFont`, `drawText`, `getTextTriangles`, `measureText`, `Font` | Text rendering |
173
+ | `loadTexture`, `LoadedTexture` | Load PNG → WebGL texture |
174
+ | `isPointInRect` | Button/UI hit-test |
175
+
176
+ **Summary:** You get raw **`ctx.gl`** (WebGL), full **`sdl`** (SDL2), and **Node** (fs, path, etc.). Helpers (draw2D, text, texture load, isPointInRect) are thin conveniences; they don’t hide the native layers. See **FRAMEWORK.md** in a created project for more detail and a minimal SDL audio example.
package/bin/create.js CHANGED
@@ -47,6 +47,7 @@ async function scaffold(projectName, parentDir) {
47
47
  earcutDts,
48
48
  textTs,
49
49
  uiTs,
50
+ textureTs,
50
51
  copyConfigJs,
51
52
  setExeIconJs,
52
53
  ] = await Promise.all([
@@ -63,6 +64,7 @@ async function scaffold(projectName, parentDir) {
63
64
  readFile(join(FRAMEWORK_ROOT, 'scripts/earcut.d.ts'), 'utf-8'),
64
65
  copyFrameworkFile('scripts/Text.ts'),
65
66
  copyFrameworkFile('scripts/UI.ts'),
67
+ copyFrameworkFile('scripts/Texture.ts'),
66
68
  readFile(join(FRAMEWORK_ROOT, 'scripts/copy-config.js'), 'utf-8'),
67
69
  readFile(join(FRAMEWORK_ROOT, 'scripts/set-exe-icon.js'), 'utf-8'),
68
70
  ])
@@ -296,6 +298,7 @@ export default game
296
298
  writeFile(join(scripts, 'earcut.d.ts'), earcutDts),
297
299
  writeFile(join(scripts, 'Text.ts'), textTs),
298
300
  writeFile(join(scripts, 'UI.ts'), uiTs),
301
+ writeFile(join(scripts, 'Texture.ts'), textureTs),
299
302
  writeFile(join(games, 'Game.ts'), gameTemplate),
300
303
  writeFile(join(scripts, 'copy-config.js'), copyConfigJs),
301
304
  writeFile(join(scripts, 'set-exe-icon.js'), setExeIconJs),
@@ -320,21 +323,150 @@ Game built with [GameNative](https://github.com/your-org/GameNative).
320
323
 
321
324
  See FRAMEWORK.md for 2D, 3D, lighting, sound, UI, and camera.
322
325
  `
323
- const frameworkMd = `# GameNative capabilities
326
+ const frameworkMd = `# GameNative API — everything you need to make a game
324
327
 
325
- Your game has access to:
328
+ Import from \`../../scripts/index.js\` in your \`src/games/*.ts\` files.
326
329
 
327
- - **2D**: \`createDraw2D(ctx.gl)\` — \`clear()\`, \`fillRect()\`, \`fillTriangles()\`, \`strokeRect()\`. Use \`ctx.gl\` (WebGL 1) for textures, sprites, batching.
328
- - **Text**: \`loadFont(\'path/to/font.ttf\')\` (TTF/OTF), \`getTextTriangles(font, text, x, y, size)\` → triangles, \`drawText(draw2d, font, text, x, y, size, r, g, b, a)\`, \`measureText(font, text, size)\`. Any font, any size; draw with \`fillTriangles\` for full control.
329
- - **3D**: Use \`ctx.gl\` directly: buffers, shaders, matrices. Implement camera (view/projection), meshes, and lighting in shaders.
330
- - **Lighting**: In 3D shaders use uniforms for light position/color; in 2D use tint or custom fragment shaders.
331
- - **Assets**: Load images (decode PNG with pngjs or similar), upload to \`ctx.gl\` textures; load audio (see sound).
332
- - **Sound**: \`ctx.sdl.audio\` (SDL audio) — open device, enqueue buffers. Or add a small wrapper in your game.
333
- - **Visuals**: Full WebGL — post-process by rendering to framebuffer, then to screen; particles, blur, etc. in shaders.
334
- - **UI / Buttons**: \`fillRect\` + \`strokeRect\` for panels/buttons, \`drawText\` for labels. \`isPointInRect(px, py, x, y, w, h)\` for hit-test. Track \`ctx.input.mouseX\`, \`mouseY\`, \`mouseLeft\` in \`update\` and draw any style in code.
335
- - **Camera**: 2D: store \`offsetX, offsetY, scale\` and pass to your draw calls or a uniform. 3D: \`view\` and \`projection\` matrices from position/target/up and perspective.
330
+ ---
336
331
 
337
- All of this is done in your \`Game.ts\` using \`ctx.gl\`, \`ctx.input\`, and the exported helpers (createDraw2D, loadFont, drawText, isPointInRect, etc.).
332
+ ## Game lifecycle
333
+
334
+ Your game exports a default object implementing **IGame**:
335
+
336
+ - **init?(ctx, config)** — Called once. Use to create \`createDraw2D(ctx.gl)\`, load fonts, etc.
337
+ - **update(dt)** — Called every frame. \`dt\` = seconds since last frame. Handle input, physics, UI state.
338
+ - **draw()** — Called every frame after \`update\`. Draw with your draw2d, text, or \`ctx.gl\`.
339
+ - **dispose?()** — Called when the window closes. Free resources (draw2d.dispose(), etc.).
340
+
341
+ **GameContext** \`ctx\`: \`ctx.gl\` (WebGL 1), \`ctx.input\`, \`ctx.width\`, \`ctx.height\` (updates on resize).
342
+
343
+ ---
344
+
345
+ ## Input
346
+
347
+ \`\`\`ts
348
+ ctx.input.mouseX // number (pixels)
349
+ ctx.input.mouseY
350
+ ctx.input.mouseLeft // boolean
351
+ ctx.input.mouseRight
352
+ ctx.input.mouseMiddle
353
+ ctx.input.isKeyDown(SCANCODE.W) // boolean — use SCANCODE.* for keys
354
+ \`\`\`
355
+
356
+ **SCANCODE**: \`A\`–\`Z\`, \`Space\`, \`Escape\`, \`Up\`, \`Down\`, \`Left\`, \`Right\`, \`Enter\`, \`Shift\`, \`Ctrl\`, \`Tab\`.
357
+
358
+ ---
359
+
360
+ ## 2D drawing
361
+
362
+ \`\`\`ts
363
+ const draw2d = createDraw2D(ctx.gl) // in init
364
+
365
+ draw2d.clear(0.1, 0.1, 0.15, 1)
366
+ draw2d.fillRect(100, 100, 80, 40, 0.2, 0.6, 0.9, 1) // x, y, w, h, r, g, b, a
367
+ draw2d.strokeRect(98, 98, 84, 44, 1, 1, 1, 1) // outline (e.g. button border)
368
+ draw2d.fillTriangles(verts, 1, 0, 0, 1) // verts: Float32Array [x,y, x,y, x,y, ...]
369
+ \`\`\`
370
+
371
+ Coordinates: origin top-left; y increases down. All in pixels.
372
+
373
+ ---
374
+
375
+ ## Text (any font, size, color)
376
+
377
+ \`\`\`ts
378
+ const font = await loadFont('assets/MyFont.ttf') // TTF or OTF, in init
379
+
380
+ drawText(draw2d, font, 'Score: 100', 20, 20, 24, 1, 1, 1, 1) // x, y, size, r, g, b, a
381
+
382
+ // Or get triangles and draw yourself (e.g. cache or custom color):
383
+ const tri = getTextTriangles(font, 'Hello', 0, 0, 32)
384
+ draw2d.fillTriangles(tri, 0, 1, 0, 1)
385
+
386
+ const { width, height } = measureText(font, 'Hello', 32) // for layout
387
+ \`\`\`
388
+
389
+ ---
390
+
391
+ ## UI / buttons
392
+
393
+ \`\`\`ts
394
+ // In update():
395
+ if (ctx.input.mouseLeft && isPointInRect(ctx.input.mouseX, ctx.input.mouseY, 100, 100, 80, 40)) {
396
+ // button clicked
397
+ }
398
+
399
+ // In draw(): draw the button with fillRect + strokeRect + drawText (any style you want)
400
+ \`\`\`
401
+
402
+ \`isPointInRect(px, py, x, y, w, h)\` — returns true if (px, py) is inside the rect. Build menus, HUDs, and custom-styled buttons in code.
403
+
404
+ ---
405
+
406
+ ## Textures (images / sprites)
407
+
408
+ \`\`\`ts
409
+ const { texture, width, height } = await loadTexture(ctx.gl, 'assets/sprite.png') // in init
410
+ // Bind and draw with your own shader; helper only does load + upload.
411
+ gl.bindTexture(gl.TEXTURE_2D, texture)
412
+ // ... your quad + sampler2D shader
413
+ \`\`\`
414
+
415
+ \`loadTexture(gl, path)\` returns \`{ texture, width, height }\`. You own the texture; use \`ctx.gl\` for the rest.
416
+
417
+ ---
418
+
419
+ ## Raw WebGL — do anything
420
+
421
+ \`ctx.gl\` is a **full WebGL 1** context. No wrapper. Use it for custom shaders, 3D, framebuffers, MSAA, texture arrays, etc. You have full control.
422
+
423
+ ---
424
+
425
+ ## Full SDL — do anything
426
+
427
+ \`sdl\` is the full [@kmamal/sdl](https://github.com/kmamal/node-sdl) module. Use it directly:
428
+
429
+ - **sdl.video** — displays, windows
430
+ - **sdl.keyboard**, **sdl.mouse**, **sdl.touch** — input
431
+ - **sdl.audio** — playback/recording (see below)
432
+ - **sdl.joystick**, **sdl.controller** — gamepads
433
+ - **sdl.sensor** — accelerometer/gyro
434
+ - **sdl.clipboard**, **sdl.power** — clipboard, battery
435
+
436
+ ### Minimal SDL audio (playback)
437
+
438
+ \`\`\`ts
439
+ import { sdl } from '../../scripts/index.js'
440
+
441
+ // In init: get playback devices, open one, enqueue buffers (e.g. decoded WAV).
442
+ const devices = sdl.audio.getDevices().filter(d => d.type === 'playback')
443
+ const device = devices[0]
444
+ if (device) {
445
+ const playback = sdl.audio.openDevice(device, {
446
+ format: 's16',
447
+ frequency: 44100,
448
+ channels: 2,
449
+ samples: 1024,
450
+ callback: (buffer) => { /* fill buffer with samples */ }
451
+ })
452
+ // Keep playback in scope; call playback.destroy() in dispose()
453
+ }
454
+ \`\`\`
455
+
456
+ SDL expects raw PCM in the callback (e.g. 16-bit stereo). Decode WAV/MP3 yourself or use a library; then fill the buffer. No high-level \`playSound(file)\`; you get full control.
457
+
458
+ ---
459
+
460
+ ## Node / OS — do anything
461
+
462
+ Your game runs in **Node.js**. Use \`import 'fs'\`, \`import 'path'\`, \`child_process\`, or any npm package. File I/O, save data, scripting — nothing is locked down.
463
+
464
+ ---
465
+
466
+ ## Beyond this
467
+
468
+ - **Camera**: 2D — store \`offsetX\`, \`offsetY\`, \`scale\` and pass to draw calls or a uniform. 3D — \`view\` and \`projection\` matrices.
469
+ - **Assets**: Fonts → \`loadFont('assets/...')\`. Images → \`loadTexture(ctx.gl, 'assets/...')\`. Audio → \`sdl.audio\` + decode + buffers.
338
470
  `
339
471
  await writeFile(join(projectPath, 'README.md'), readme)
340
472
  await writeFile(join(projectPath, 'FRAMEWORK.md'), frameworkMd)
@@ -0,0 +1,12 @@
1
+ import type { WebGLRenderingContext as GLContext } from '@kmamal/gl';
2
+ export interface LoadedTexture {
3
+ texture: WebGLTexture;
4
+ width: number;
5
+ height: number;
6
+ }
7
+ /**
8
+ * Load PNG from path (relative to cwd or absolute), upload to GL, return texture and size.
9
+ * You own the texture; bind and draw with your own shader or use raw gl.
10
+ */
11
+ export declare function loadTexture(gl: GLContext, path: string): Promise<LoadedTexture>;
12
+ //# sourceMappingURL=Texture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Texture.d.ts","sourceRoot":"","sources":["../../scripts/Texture.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,qBAAqB,IAAI,SAAS,EAAE,MAAM,YAAY,CAAA;AAEpE,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,YAAY,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAoBrF"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Load a PNG from path and upload to a WebGL texture. Minimal helper; use ctx.gl for everything else.
3
+ */
4
+ import { readFile } from 'fs/promises';
5
+ import { join } from 'path';
6
+ import pngjs from 'pngjs';
7
+ /**
8
+ * Load PNG from path (relative to cwd or absolute), upload to GL, return texture and size.
9
+ * You own the texture; bind and draw with your own shader or use raw gl.
10
+ */
11
+ export async function loadTexture(gl, path) {
12
+ const abs = path.startsWith('/') || /^[A-Za-z]:/.test(path) ? path : join(process.cwd(), path);
13
+ const buf = await readFile(abs);
14
+ const pngjsMod = pngjs;
15
+ const PNG = pngjsMod.default?.PNG ?? pngjsMod.PNG;
16
+ const png = PNG.sync.read(buf);
17
+ const width = png.width;
18
+ const height = png.height;
19
+ const data = Buffer.isBuffer(png.data)
20
+ ? png.data
21
+ : (png.data instanceof Uint8Array ? png.data : new Uint8Array(png.data));
22
+ const texture = gl.createTexture();
23
+ gl.bindTexture(gl.TEXTURE_2D, texture);
24
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
25
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
26
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
27
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
28
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
29
+ gl.bindTexture(gl.TEXTURE_2D, null);
30
+ return { texture, width, height };
31
+ }
32
+ //# sourceMappingURL=Texture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Texture.js","sourceRoot":"","sources":["../../scripts/Texture.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAAa,EAAE,IAAY;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAA;IAC9F,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC/B,MAAM,QAAQ,GAAG,KAA6N,CAAA;IAC9O,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,IAAI,QAAQ,CAAC,GAAG,CAAA;IACjD,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAA;IACvB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAA;IACzB,MAAM,IAAI,GAAwB,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QACzD,CAAC,CAAC,GAAG,CAAC,IAAI;QACV,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,YAAY,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAyB,CAAC,CAAC,CAAA;IAC/F,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,EAAG,CAAA;IACnC,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;IACtC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;IAC3F,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,aAAa,CAAC,CAAA;IACpE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,aAAa,CAAC,CAAA;IACpE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,MAAM,CAAC,CAAA;IACjE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,kBAAkB,EAAE,EAAE,CAAC,MAAM,CAAC,CAAA;IACjE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,IAA+B,CAAC,CAAA;IAC9D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AACnC,CAAC"}
@@ -7,4 +7,6 @@ export type { Draw2D } from './Graphics.js';
7
7
  export { loadFont, getTextTriangles, drawText, measureText } from './Text.js';
8
8
  export type { Font } from './Text.js';
9
9
  export { isPointInRect } from './UI.js';
10
+ export { loadTexture } from './Texture.js';
11
+ export type { LoadedTexture } from './Texture.js';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../scripts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC3D,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC7E,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../scripts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAC3D,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAC7E,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA"}
@@ -2,4 +2,5 @@ export { run, sdl, createInput, SCANCODE } from './Game.js';
2
2
  export { createDraw2D } from './Graphics.js';
3
3
  export { loadFont, getTextTriangles, drawText, measureText } from './Text.js';
4
4
  export { isPointInRect } from './UI.js';
5
+ export { loadTexture } from './Texture.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../scripts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAI3D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE7E,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../scripts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAI3D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE7E,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-gamenative-app",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Native TypeScript framework for desktop games — no game engine, just SDL + WebGL.",
5
5
  "type": "module",
6
6
  "main": "dist/scripts/main.js",
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Load a PNG from path and upload to a WebGL texture. Minimal helper; use ctx.gl for everything else.
3
+ */
4
+ import { readFile } from 'fs/promises'
5
+ import { join } from 'path'
6
+ import pngjs from 'pngjs'
7
+ import type { WebGLRenderingContext as GLContext } from '@kmamal/gl'
8
+
9
+ export interface LoadedTexture {
10
+ texture: WebGLTexture
11
+ width: number
12
+ height: number
13
+ }
14
+
15
+ /**
16
+ * Load PNG from path (relative to cwd or absolute), upload to GL, return texture and size.
17
+ * You own the texture; bind and draw with your own shader or use raw gl.
18
+ */
19
+ export async function loadTexture(gl: GLContext, path: string): Promise<LoadedTexture> {
20
+ const abs = path.startsWith('/') || /^[A-Za-z]:/.test(path) ? path : join(process.cwd(), path)
21
+ const buf = await readFile(abs)
22
+ const pngjsMod = pngjs as { default?: { PNG: { sync: { read(b: Buffer): { width: number; height: number; data: Buffer | Uint8Array } } } }; PNG: { sync: { read(b: Buffer): { width: number; height: number; data: Buffer | Uint8Array } } } }
23
+ const PNG = pngjsMod.default?.PNG ?? pngjsMod.PNG
24
+ const png = PNG.sync.read(buf)
25
+ const width = png.width
26
+ const height = png.height
27
+ const data: Buffer | Uint8Array = Buffer.isBuffer(png.data)
28
+ ? png.data
29
+ : (png.data instanceof Uint8Array ? png.data : new Uint8Array(png.data as ArrayLike<number>))
30
+ const texture = gl.createTexture()!
31
+ gl.bindTexture(gl.TEXTURE_2D, texture)
32
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data)
33
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
34
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
35
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
36
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
37
+ gl.bindTexture(gl.TEXTURE_2D, null as unknown as WebGLTexture)
38
+ return { texture, width, height }
39
+ }
package/scripts/index.ts CHANGED
@@ -7,3 +7,5 @@ export type { Draw2D } from './Graphics.js'
7
7
  export { loadFont, getTextTriangles, drawText, measureText } from './Text.js'
8
8
  export type { Font } from './Text.js'
9
9
  export { isPointInRect } from './UI.js'
10
+ export { loadTexture } from './Texture.js'
11
+ export type { LoadedTexture } from './Texture.js'