<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Xer97</title>
    <description>The latest articles on DEV Community by Xer97 (@xer97).</description>
    <link>https://dev.to/xer97</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4227536%2F6e0a2709-8f18-45a7-868f-0d61a082c7c1.png</url>
      <title>DEV Community: Xer97</title>
      <link>https://dev.to/xer97</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xer97"/>
    <language>en</language>
    <item>
      <title>Getting a usable animated placeholder character into Godot without drawing every frame</title>
      <dc:creator>Xer97</dc:creator>
      <pubDate>Thu, 05 Nov 2026 15:21:02 +0000</pubDate>
      <link>https://dev.to/xer97/getting-a-usable-animated-placeholder-character-into-godot-without-drawing-every-frame-1kd5</link>
      <guid>https://dev.to/xer97/getting-a-usable-animated-placeholder-character-into-godot-without-drawing-every-frame-1kd5</guid>
      <description>&lt;p&gt;Most of my prototypes die in the same place. The movement code works, the jump feels okay, and the player is still a white rectangle because nobody wants to draw a walk cycle at 1am.&lt;/p&gt;

&lt;p&gt;Grey boxes are fine for testing collisions. They are less fine when you want to tell whether a game &lt;em&gt;feels&lt;/em&gt; right, or show it to someone. Animation timing changes how movement reads, and you can't judge that with a rectangle.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://spritix.ai" rel="noopener noreferrer"&gt;Spritix&lt;/a&gt;. You upload one full-body character image, and it generates Idle, Walk, Run, Jump and Attack animations and lays them out as sprite sheets. This post is less about the tool and more about the workflow I use to get from "one drawing" to "a character playing animations in the engine" quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a decent source image
&lt;/h2&gt;

&lt;p&gt;The one input matters a lot. A few things that help with any &lt;a href="https://spritix.ai" rel="noopener noreferrer"&gt;sprite AI&lt;/a&gt; tool or image-to-animation step, not just this one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full body, nothing cropped. If the feet are cut off, the walk cycle has to guess.&lt;/li&gt;
&lt;li&gt;Limbs visible. A character with arms tucked behind a cape gives the model less to work with.&lt;/li&gt;
&lt;li&gt;A plain background.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A front or three-quarter view of the character standing in a neutral pose is the easiest case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick the actions and clean up the loops
&lt;/h2&gt;

&lt;p&gt;Choose which of the five actions you want the &lt;a href="https://spritix.ai" rel="noopener noreferrer"&gt;sprite generator&lt;/a&gt; to produce. For a platformer prototype that's usually all of them; for a top-down thing you might skip Jump.&lt;/p&gt;

&lt;p&gt;After generation there's a loop editor where you can trim frames, change speed (0.5x to 2x) and adjust frame count and frame size. That editing is free, so it's worth spending a minute here instead of fixing timing later in the engine. Version history means you can go back if you overdo it.&lt;/p&gt;

&lt;p&gt;The thing I'd check most carefully is frame size. Keep it uniform across every action you plan to use. Mismatched cell sizes are the most common reason a character "jumps" a few pixels when switching from idle to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export
&lt;/h2&gt;

&lt;p&gt;You get four formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sprite sheet PNG&lt;/li&gt;
&lt;li&gt;animated GIF (handy for checking the loop or posting progress)&lt;/li&gt;
&lt;li&gt;Atlas JSON&lt;/li&gt;
&lt;li&gt;frames ZIP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Godot I usually take the PNG sheet. Phaser and PixiJS are happy with the sheet plus Atlas JSON. If your engine or Aseprite workflow prefers individual images, use the ZIP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Into Godot 4
&lt;/h2&gt;

&lt;p&gt;Create an &lt;code&gt;AnimatedSprite2D&lt;/code&gt; under your player, make a new &lt;code&gt;SpriteFrames&lt;/code&gt; resource, and use "Add frames from sprite sheet" to slice the PNG by grid. Name the animations to match your states: &lt;code&gt;idle&lt;/code&gt;, &lt;code&gt;walk&lt;/code&gt;, &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;jump&lt;/code&gt;, &lt;code&gt;attack&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're doing pixel art, set the texture filter to Nearest on the node or project-wide, or everything goes blurry.&lt;/p&gt;

&lt;p&gt;Then a small state picker in the player script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gdscript"&gt;&lt;code&gt;&lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;CharacterBody2D&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;onready&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AnimatedSprite2D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="n"&gt;AnimatedSprite2D&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;RUN_THRESHOLD&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;180.0&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_physics_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... your movement code that sets velocity ...&lt;/span&gt;
    &lt;span class="n"&gt;move_and_slide&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;_update_animation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_update_animation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_action_just_pressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"attack"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"attack"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"attack"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_playing&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="c1"&gt;# let the attack finish&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_on_floor&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"jump"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RUN_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"walk"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"idle"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sprite&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flip_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;velocity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turn looping off for &lt;code&gt;attack&lt;/code&gt; in the SpriteFrames panel, otherwise &lt;code&gt;is_playing()&lt;/code&gt; never goes false.&lt;/p&gt;

&lt;p&gt;A note on &lt;code&gt;flip_h&lt;/code&gt;: if your character art has an obvious light source on one side, flipping makes it visible. For a placeholder nobody cares. For final art you may want separate left-facing frames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unity, briefly
&lt;/h2&gt;

&lt;p&gt;Import the PNG, set Sprite Mode to Multiple, slice by cell size in the Sprite Editor, then drag the slices for each action into an Animation clip. Set Filter Mode to Point for pixel art. Same idea as Godot, different buttons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it falls short
&lt;/h2&gt;

&lt;p&gt;No &lt;a href="https://spritix.ai" rel="noopener noreferrer"&gt;AI sprite sheet generator&lt;/a&gt;, mine included, is going to replace an animator. Odd poses or partially hidden limbs in the source image produce odd results. Attack animations in particular are generic; they won't know your character is supposed to swing a specific weapon in a specific way. I think of the output as a usable first pass: good enough to tune game feel and show a build, and something a real artist can replace or paint over later.&lt;/p&gt;

&lt;p&gt;There's a free tier of 20 credits a month, which is enough to try it on a character or two.&lt;/p&gt;

&lt;p&gt;If you have a different pipeline for getting temporary animated characters into a prototype, I'd like to hear it. Especially anyone doing this in GameMaker or Phaser.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>pixelart</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
