<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Xer97's dev notes]]></title><description><![CDATA[Xer97's dev notes]]></description><link>https://xer97.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 12:49:15 GMT</lastBuildDate><atom:link href="https://xer97.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Sprite sheet, atlas JSON or loose frames? Picking an export format for a 2D web game]]></title><description><![CDATA[Notes from building a sprite generator, plus loading each format in Phaser and PixiJS]]></description><link>https://xer97.hashnode.dev/sprite-sheet-atlas-json-or-loose-frames-picking-an-export-format-for-a-2d-web-game</link><guid isPermaLink="true">https://xer97.hashnode.dev/sprite-sheet-atlas-json-or-loose-frames-picking-an-export-format-for-a-2d-web-game</guid><category><![CDATA[GameDev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[phaser]]></category><category><![CDATA[Pixel Art]]></category><category><![CDATA[side project]]></category><dc:creator><![CDATA[Xer97]]></dc:creator><pubDate>Thu, 05 Nov 2026 03:37:00 GMT</pubDate><content:encoded><![CDATA[<p>When I started building <a href="https://spritix.ai">Spritix</a>, an AI sprite generator, the export screen took longer to get right than I expected. The animation part was the hard technical problem. But the question users actually hit first was simpler: which file do I download?</p>
<p>Spritix takes one full-body character image and generates Idle, Walk, Run, Jump and Attack animations. At the end you can export a sprite sheet PNG, an animated GIF, an Atlas JSON or a ZIP of individual frames. Those four exist because different engines and workflows want different things, and it's worth knowing why before you pick one. Most of this applies no matter where your sprites come from.</p>
<h2>The plain sprite sheet (PNG)</h2>
<p>One image, frames laid out in a grid, every cell the same size.</p>
<p>Good when your engine can slice by grid, which most can. It's also the easiest thing to hand to someone else or drop into a level editor like Tiled.</p>
<p>The catch is the uniform cell size. If one action has a wide attack pose, every cell grows to fit it, and you get a lot of empty space. For a handful of characters that doesn't matter. For a lot of them, texture memory starts to.</p>
<p>Loading it in Phaser 3:</p>
<pre><code class="language-js">// preload
this.load.spritesheet('hero', 'hero_walk.png', {
  frameWidth: 128,
  frameHeight: 128,
});

// create
this.anims.create({
  key: 'walk',
  frames: this.anims.generateFrameNumbers('hero', { start: 0, end: 7 }),
  frameRate: 10,
  repeat: -1,
});
this.add.sprite(200, 300, 'hero').play('walk');
</code></pre>
<p>You have to know the frame size and frame count yourself. Get the size wrong by a few pixels and the character slowly drifts across its own cell.</p>
<h2>Atlas JSON</h2>
<p>A PNG plus a JSON file describing where each frame lives and what it's called. The engine reads names and rectangles from the JSON instead of guessing from a grid.</p>
<p>This is what I'd pick for anything going beyond a prototype in Phaser or PixiJS. Frame names survive, so code reads <code>walk_03</code> instead of "frame 19", and you stop hard-coding sizes.</p>
<p>Phaser 3:</p>
<pre><code class="language-js">// preload
this.load.atlas('hero', 'hero.png', 'hero.json');

// create
this.anims.create({
  key: 'run',
  frames: this.anims.generateFrameNames('hero', { prefix: 'run_', start: 0, end: 7, zeroPad: 2 }),
  frameRate: 12,
  repeat: -1,
});
</code></pre>
<p>Check the actual frame names in the JSON before writing the prefix. That one line is where most "animation shows nothing" bugs come from.</p>
<p>PixiJS (v7/v8):</p>
<pre><code class="language-js">import { Assets, AnimatedSprite } from 'pixi.js';

const sheet = await Assets.load('hero.json');
const hero = new AnimatedSprite(sheet.animations['idle'] ?? Object.values(sheet.textures));
hero.animationSpeed = 0.15;
hero.play();
app.stage.addChild(hero);
</code></pre>
<p>If the JSON has no <code>animations</code> block, you can build the texture array yourself by filtering <code>sheet.textures</code> by name.</p>
<h2>Frames ZIP</h2>
<p>Every frame as its own image. Least efficient at runtime, most flexible before it.</p>
<p>Use it when you plan to edit frames by hand in Aseprite, when you want to repack everything with your own texture packer alongside other assets, or when a tool simply wants a folder of images.</p>
<h2>Animated GIF</h2>
<p>Not for the game. It's for checking that a loop actually loops, and for posting progress somewhere. I use it more than I thought I would, because a GIF shows a bad loop seam faster than scrubbing frames in an editor.</p>
<h2>Things that bite regardless of format</h2>
<ul>
<li><strong>Pixel art blur.</strong> In Phaser set <code>pixelArt: true</code> in the game config. In PixiJS set the texture's scale mode to nearest. Otherwise everything looks smeared when scaled.</li>
<li><strong>Anchor/origin.</strong> Characters with different poses per action should share a consistent foot position. If idle and jump have different origins, the character hops when switching states.</li>
<li><strong>Frame rate per action.</strong> One global frame rate rarely works. Idle usually wants slower, run faster.</li>
</ul>
<p>Spritix also has a loop editor for trimming frames, changing speed (0.5x to 2x) and setting frame count and size before export, which handles some of this up front. But even if you use something else, decide frame size and timing before export, not after. Fixing timing inside engine code across five actions is slower than doing it once at the source.</p>
<h2>What I'd pick</h2>
<ul>
<li>Quick prototype, any engine: PNG sheet.</li>
<li>Phaser or PixiJS project you'll keep working on: Atlas JSON.</li>
<li>Hand editing or your own packing pipeline: frames ZIP.</li>
<li>Sharing or checking a loop: GIF.</li>
</ul>
<p>That's it. If you're using a different packer or engine setup for web games, I'm curious what format you settled on and why.</p>
]]></content:encoded></item><item><title><![CDATA[Turning a sketch or photo into a printable STL without opening Blender]]></title><description><![CDATA[What I built, how it works from the user side, and what to check in the slicer before you print]]></description><link>https://xer97.hashnode.dev/turning-a-sketch-or-photo-into-a-printable-stl-without-opening-blender</link><guid isPermaLink="true">https://xer97.hashnode.dev/turning-a-sketch-or-photo-into-a-printable-stl-without-opening-blender</guid><category><![CDATA[3D printing]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[side project]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Maker]]></category><dc:creator><![CDATA[Xer97]]></dc:creator><pubDate>Thu, 05 Nov 2026 03:28:53 GMT</pubDate><content:encoded><![CDATA[<p>I like 3D printing. I don't like 3D modeling nearly as much.</p>
<p>For a long time that meant printing other people's files. Every time I wanted something of my own, a small figure from a drawing or a desk object from a photo, the path went through Blender, and Blender has a way of turning a "quick" idea into a weekend of tutorials.</p>
<p>So I built <a href="https://imagetostl.io">Image to STL</a>. You give it an image, or a text description, and it generates a full 3D mesh you can download as STL, OBJ or GLB.</p>
<p>One clarification up front, because people ask: this isn't a lithophane or heightmap tool. Those take a photo and turn brightness into depth on a flat plate. This produces a whole object, front and back.</p>
<h2>How it works from your side</h2>
<p>The homepage is the tool. There are two tabs: image to STL and text to STL.</p>
<p>For images, upload a JPG, PNG or WebP between 128 and 5000 px, up to 5 MB. A single image is the default. If you have several angles of the same object, there's a multi-view mode (on Pro and Max) that gives the model more to go on.</p>
<p>Then you pick a quality level:</p>
<p>Standard is a fast draft, around 80K triangles. Pro goes up to about 1.5M triangles. Max is the highest fidelity. My suggestion is to run Standard first to see whether the overall shape is right, and only spend credits on a higher tier once it is. There are also material options, including PBR, which matter more if you're taking the GLB into a game engine or a web viewer than for printing.</p>
<p>Text to STL is for when you don't have a picture at all. Describe the thing, get a mesh.</p>
<p>Downloads: STL for slicers, OBJ if you want to edit in another tool, GLB for engines and the web.</p>
<p>The free tier gives 15 credits a month. Paid plans are $19.9 and $32.9 per month if you need more.</p>
<h2>Picking a good input</h2>
<p>Most of the result is decided before you click generate. What helps:</p>
<p>A clear subject that fills a decent part of the frame. A plain background. Even lighting without hard shadows that could be mistaken for shape. The whole object in view, not cropped.</p>
<p>What hurts: cluttered scenes, overlapping objects, and photos where half the subject is hidden. The model has to invent whatever it can't see, and invented geometry is where things get strange.</p>
<p>For sketches, clean line art with some shading tends to work better than a loose doodle, because shading hints at depth.</p>
<h2>Before you hit print</h2>
<p>A generated mesh has no idea how big it's supposed to be. So the first stop is the slicer.</p>
<p><strong>Scale.</strong> Set the real size you want. Then look again at the thin parts, because shrinking a model is the fastest way to make a sword blade or a finger thinner than your nozzle can print.</p>
<p><strong>Wall thickness.</strong> As a rough rule, anything thinner than about two extrusion widths on FDM is risky. Either print larger, or accept that tiny details will disappear.</p>
<p><strong>Supports.</strong> Figures and organic shapes are full of overhangs. Tree supports are usually easier to remove from this kind of model than grid supports. Try a few orientations; sometimes laying a model back slightly cuts support material a lot.</p>
<p><strong>Bed contact.</strong> If the base isn't flat, add a brim, or flatten the bottom with a cut in the slicer.</p>
<p><strong>Resin.</strong> Hollow it, add drain holes, and think about where the supports touch visible surfaces.</p>
<p>Most slicers will also flag or auto-repair non-manifold edges. Let them, and check the layer preview before committing to a long print.</p>
<h2>Honest limits</h2>
<p>It works best on single, reasonably simple objects with a clear photo. Results depend heavily on the input image. Complex mechanical parts, thin interlocking pieces, or anything that needs exact dimensions are not a great fit; for functional parts you still want CAD.</p>
<p>Some outputs need cleanup before they print well, like a stray blob where the background confused things, or a thin area that needs thickening. Exporting OBJ and doing a quick fix in another editor is sometimes the right move. That's still a lot less work than modeling from nothing, which was the whole point.</p>
<p>If you try it, I'm interested in what kinds of images worked and which didn't. That feedback shapes what I work on next: <a href="https://imagetostl.io">imagetostl.io</a>.</p>
]]></content:encoded></item></channel></rss>