<?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: PDeveloper</title>
    <description>The latest articles on DEV Community by PDeveloper (@pdeveloper).</description>
    <link>https://dev.to/pdeveloper</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F11658%2Fda5ec9b4-089c-4149-9698-d541fc9c35c2.png</url>
      <title>DEV Community: PDeveloper</title>
      <link>https://dev.to/pdeveloper</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pdeveloper"/>
    <language>en</language>
    <item>
      <title>Drag and Drop in Godot 4.x</title>
      <dc:creator>PDeveloper</dc:creator>
      <pubDate>Tue, 28 Nov 2023 16:17:47 +0000</pubDate>
      <link>https://dev.to/pdeveloper/godot-4x-drag-and-drop-5g13</link>
      <guid>https://dev.to/pdeveloper/godot-4x-drag-and-drop-5g13</guid>
      <description>&lt;p&gt;I was working on an inventory system, and as such one must enable players to play inventory Tetris in case they're bored. I have made drag and drop solutions before on different platforms, and know that there are potential edge-cases and problems waiting to suck time and life out of me. Looking for the go-to Godot solution for drag and drop, search engine results were all throwing older, manually implemented approaches mostly.&lt;/p&gt;

&lt;p&gt;But I did finally stumble on the built-in solution which already exists in Godot, and if there's one thing I've tried to get away from, it's from DIY/NIH rabbitholes. Perfect!&lt;br&gt;
Here's a quick run through so you don't have to:&lt;/p&gt;
&lt;h1&gt;
  
  
  Control node interface
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Control&lt;/code&gt; nodes have 3 virtual private functions you can override and will enable drag and drop functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="cp"&gt;# Control that can be dragged from&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_get_drag_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at_position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="kt"&gt;Variant&lt;/span&gt;
&lt;span class="cp"&gt;# Control that can be dragged to&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_can_drop_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at_position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Variant&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;bool&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_drop_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at_position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Variant&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docs: &lt;a href="https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-private-method-get-drag-data"&gt;_get_drag_data&lt;/a&gt;, &lt;a href="https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-private-method-can-drop-data"&gt;_can_drop_data&lt;/a&gt;, &lt;a href="https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-private-method-drop-data"&gt;_drop_data&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The drag and drop mechanism also works in that order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;_get_drag_data&lt;/code&gt; - returns the data that can be dragged from the current &lt;code&gt;Control&lt;/code&gt;. In my case, if a slot has an item, then I'll return the item, otherwise &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_can_drop_data&lt;/code&gt; - will continuously be called on the &lt;code&gt;Control&lt;/code&gt; under the mouse position, passing the relative mouse position and &lt;code&gt;data&lt;/code&gt;, and returns whether or not this &lt;code&gt;data&lt;/code&gt; can be accepted. This is where I check if the current item fits in the inventory grid at the mouse position, and if the current slot is even compatible with the item type (Weapon slot for weapons, Back slot for backpacks, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_drop_data&lt;/code&gt; - the final call, which is the same as &lt;code&gt;_can_drop_data&lt;/code&gt;, except here we are accepting the drop, and should handle removing the item from the previous container, and add it to the current &lt;code&gt;Control&lt;/code&gt; (or whatever else you plan on doing with the &lt;code&gt;data&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To handle the display preview of the drag and drop operation, we use:&lt;br&gt;
&lt;code&gt;func set_drag_preview(control:Control)-&amp;gt;void&lt;/code&gt; (&lt;a href="https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-method-set-drag-preview"&gt;docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;As one can expect, this will use the &lt;code&gt;Control&lt;/code&gt; node you pass as the display icon of the drag and drop, add it to the scene tree, and destroy it once the drag is complete.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is also &lt;code&gt;force_drag(data:Variant, preview:Control)-&amp;gt;void&lt;/code&gt; (&lt;a href="https://docs.godotengine.org/en/latest/classes/class_control.html#class-control-method-force-drag"&gt;docs&lt;/a&gt;) which will initiate a drag and drop programmatically. If you are calling this from the &lt;code&gt;_drop_data&lt;/code&gt; handler, use &lt;code&gt;call_deferred&lt;/code&gt; to call it in the next frame, since the current drag operation is still finishing. I use this when dragging items to a slot that already has an item, so I initiate a drag on the previous item for a faster swapping UX.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;
  
  
  Using it
&lt;/h1&gt;

&lt;p&gt;It's a simple interface, the basic usage is straight forward - Create some data that will be dragged from a &lt;code&gt;Control&lt;/code&gt;, check this data, and potentially accept this data in a &lt;code&gt;Control&lt;/code&gt; that can be dragged to.&lt;/p&gt;

&lt;p&gt;One last problem I had was detecting if the user ends the drag, but on a &lt;code&gt;Control&lt;/code&gt; that will not accept it. Here we can return to &lt;code&gt;Node&lt;/code&gt; basics and use the lifetime of the preview &lt;code&gt;Control&lt;/code&gt;! The full lifecycle then looks as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User clicks and drags, Godot calls: &lt;code&gt;_get_drag_data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We call: &lt;code&gt;set_drag_preview&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User drags over a &lt;code&gt;Control&lt;/code&gt;, Godot calls: &lt;code&gt;_can_drop_data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User releases the drag, and &lt;code&gt;_can_drop_data&lt;/code&gt; was true, Godot calls: &lt;code&gt;_drop_data&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;preview_control.tree_exiting&lt;/code&gt; signal is emitted&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To simplify this lifecycle, I'm using a separate object for managing the drag operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;class_name&lt;/span&gt; &lt;span class="kt"&gt;ItemDrag&lt;/span&gt;

&lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="nf"&gt;drag_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;ItemDrag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Control&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;_source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Control&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;_item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;_preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Control&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_source&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_item&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_preview&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tree_exiting&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_on_tree_exiting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_on_tree_exiting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;drag_completed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this class, an example of the other functions could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;remove_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;void&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_get_drag_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at_position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="kt"&gt;Variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;item&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;at_position&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;item&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;drag_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ItemDrag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;_create_item_preview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;set_drag_preview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drag_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;drag_data&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_can_drop_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at_position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Variant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;ItemDrag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;drag_data&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;ItemDrag&lt;/span&gt;
    &lt;span class="cp"&gt;# Check if the item can fit in the inventory at this position&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intersects_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drag_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;at_position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_drop_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at_position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;Variant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;ItemDrag&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;var&lt;/span&gt; &lt;span class="nv"&gt;drag_data&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;ItemDrag&lt;/span&gt;

    &lt;span class="n"&gt;drag_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;drag_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drag_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drag_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_item_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drag_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;at_position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of the functionality could be enforced by the &lt;code&gt;ItemDrag&lt;/code&gt; class, especially giving things like &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;destination&lt;/code&gt; stronger types that are guaranteed to have an interface for handling/adding/removing items. But having this set of functions defined is a good start to handling the full drag and drop cycle of operations.&lt;/p&gt;

&lt;h1&gt;
  
  
  Global drag and drop
&lt;/h1&gt;

&lt;p&gt;In addition to the above &lt;code&gt;Control&lt;/code&gt;-based (GUI-based) approach, Godot provides some more event hooks into the lifecycle. &lt;code&gt;Viewport&lt;/code&gt; and &lt;code&gt;Control&lt;/code&gt; provide functions on querying the state of any drag and drop operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="cp"&gt;# Viewport&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;gui_is_dragging&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;bool&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;gui_get_drag_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="kt"&gt;Variant&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;gui_is_drag_successful&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;bool&lt;/span&gt;
&lt;span class="cp"&gt;# Control&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;is_drag_successful&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;bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first 2 should be obvious - the first can be queried to see if a drag and drop is happening at any point, the second to get the same &lt;code&gt;data&lt;/code&gt; we returned in the &lt;code&gt;_get_drag_data&lt;/code&gt; callback in our &lt;code&gt;Control&lt;/code&gt; (or whatever &lt;code&gt;data&lt;/code&gt; is contained from &lt;code&gt;force_drag&lt;/code&gt; or other ways a drag has been initiated).&lt;/p&gt;

&lt;p&gt;The last 2 can be used with the &lt;code&gt;func _notification(what:int)-&amp;gt;void&lt;/code&gt; handler to receive global notifications when a drag and drop has started or ended, and if it ended successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;what&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&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;what&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kt"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;NOTIFICATION_DRAG_BEGIN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="cp"&gt;# Drag data is available (populated by our _get_drag_data() function for example)&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_viewport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gui_get_drag_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="cp"&gt;# Use the drag data&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;what&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kt"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;NOTIFICATION_DRAG_END&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="cp"&gt;# Drag data is no longer available and has been disposed already&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Drag ended. Success: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;get_viewport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gui_is_drag_successful&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These may come in handy, but are further from the drag and drop context. Some use-cases could be used by relevant drop receivers to highlight themselves, based on the drag data.&lt;/p&gt;

&lt;p&gt;With these tools, any drag and drop scenario can be handled, hopefully this saves you any attempts at writing a custom drag and drop implementation. I wish you the best of luck and success in using this information!&lt;/p&gt;

&lt;p&gt;P.&lt;/p&gt;

</description>
      <category>godot</category>
      <category>godotengine</category>
      <category>gamedev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Planetarium2D - Noise-based Pseudo-3D planets</title>
      <dc:creator>PDeveloper</dc:creator>
      <pubDate>Wed, 28 Jun 2023 07:50:29 +0000</pubDate>
      <link>https://dev.to/pdeveloper/planetarium2d-noise-based-pseudo-3d-planets-2883</link>
      <guid>https://dev.to/pdeveloper/planetarium2d-noise-based-pseudo-3d-planets-2883</guid>
      <description>&lt;p&gt;A short overview of my planet generation tool. Updates to come. You can view the referenced tool and code here, or just pretty pictures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/PDeveloper/Planet2D"&gt;Planet2D Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pdeveloper.itch.io/planetarium2d"&gt;Planetarium2D itch.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pdeveloper.github.io/planetarium2d/"&gt;Gallery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sVGO1_6q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/oAojoRx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sVGO1_6q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/oAojoRx.png" alt="Cuprum III" title="Cuprum III" width="720" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Boredom to Laziness-driven Development
&lt;/h2&gt;

&lt;p&gt;I was bored, and at work needed to do some drives to some places where I would be waiting in my car for anywhere from a few minutes to maybe even a couple of hours. I was never a fan of having games on my phone, but over time had some small indie RPGs or strategy games, or my golden standard, Sudoku. Back home, every few months I would dust off the digital version of Heroes of Might and Magic III, and accidentally time travel to 5AM before a full day of work. As one does, I imagined having Heroes III with me on my phone and being able to have a huge game going at all times (Very smart to lean into a dangerously time consuming activity...) I could continue whenever I had a few free minutes.&lt;/p&gt;

&lt;p&gt;Obviously, instead of looking for an existing game, I have to make it myself...&lt;/p&gt;

&lt;p&gt;So I started the project with Godot. To minimize the amount of art assets I had to create, I decided I was going to go for a Heroes III-like in space. White pixels are stars in the background, easy money. Unit portraits I was able to find a nice set from Kenney assets, and while browsing, he also had a set of planet layers that could be combined to create nice looking planets. But I'm a smart programmer. What if I want to have 100 different planet types? What if I want a lot of different planet sizes and variations on different properties like atmosphere and maybe special resources that are visible, and how cool would it be to have it rotate? I was approaching pre-mature optimization levels of genius.&lt;/p&gt;

&lt;p&gt;I remembered back in the Flash days and the &lt;code&gt;ConvolutionFilter&lt;/code&gt; people having pseudo-3d spheres in some games, and I liked the effect, I finally had a use-case to apply it. Combined with Godot's built-in &lt;code&gt;FastNoiseLite&lt;/code&gt; which was a one-stop shop for several noise-generation algorithms, and enough parameters to be interesting, it seemed like I had my little planets project all set to take off. Why spend 2 hours making some prototype planet pictures for a personal game nobody else might ever see and continue making an actual game when you can spend 2 weeks making a planet generator and rabbithole into making an editor for them?&lt;/p&gt;

&lt;h2&gt;
  
  
  Spherical Distortion
&lt;/h2&gt;

&lt;p&gt;Step one, create a fragment shader to take a 2D texture, and make it a sleek round ball of sphere. This step is actually fairly simple once you understand the input and output of the fragment shader, and what you want to happen. In shaders, the UV (the coordinate of the texture to draw) is mapped from 0.0 (start of the texture) to 1.0 (end of the texture), meaning that for any texture, a UV of &lt;code&gt;(0.5, 0.5)&lt;/code&gt; will be the center of that texture, basically normalized 2D coordinates. For a spherical distortion, we want to draw the texture normally near the center of the "sphere", and as we move to the edge of the sphere, we can see more and more of the texture squished into the same screenspace, as it wraps around the imaginary sphere we're creating. In pixel terms - near the center, every 1 pixel we're drawing on the screen will be roughly equal to drawing 1 pixel of the texture. Near the edges, every 1 pixel we're drawing will be many pixels of the texture, and eventually infinity right at the edge (since we're looking at the texture from a perpendicular perspective, we can "see" "all" of it). What is a nice equation that gets us there? &lt;code&gt;arcsin&lt;/code&gt; of course!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dUy8vmF8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/vhHgZDE.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dUy8vmF8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/vhHgZDE.png" alt="arcsin" title="arcsin" width="636" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As &lt;code&gt;x&lt;/code&gt; moves to 1, the tangent moves to being vertical, and the &lt;code&gt;y&lt;/code&gt; moves to &lt;code&gt;PI / 2&lt;/code&gt;! Since we'll be &lt;em&gt;scaling&lt;/em&gt; the UV coordinates, we get a ratio by dividing the &lt;code&gt;arcsin&lt;/code&gt; value by the original value, giving us the amount to scale by. We can also divide by &lt;code&gt;PI / 2&lt;/code&gt; to normalize this scaling factor within &lt;code&gt;0 &amp;lt;= x &amp;lt;= 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Godot shader terms, this can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="nf"&gt;spherical_distort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;displacement_scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&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="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;asin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;radius&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;PI2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// PI2 is a PI / 2 constant&lt;/span&gt;
    &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;displacement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;displacement_scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;displacement&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;texture_offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;texture_scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this function, given a UV coordinate which is centered around &lt;code&gt;(0.0, 0.0)&lt;/code&gt;, it will "push" it out as it approaches the edges of the circle. This is the basis for spherical distortion in the shader. You can also see 2 other parameters (in this case they are uniforms), &lt;code&gt;texture_offset&lt;/code&gt; and &lt;code&gt;texture_scale&lt;/code&gt;. The offset is what is controlling the scrolling, or "spinning" of the planet. The scale, as one could assume, is simply a zoom factor for the texture. Since it is a vector, you can stretch the texture horizontally or vertically as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AjOgbXIw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/x5RJ7Mr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AjOgbXIw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/x5RJ7Mr.png" alt="Pixel Planet" title="Pixel Planet" width="512" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I now had nice little spheres I could use. Job done, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  I Don't Have Scope Creep Problems
&lt;/h2&gt;

&lt;p&gt;As a genius programmer, it's not enough to have something working, add it to your game and get on with it. What about all sorts of changes, I want to play around with the planets? What about generating planets based on physical parameters like humidity or temperature? &lt;em&gt;Obviously&lt;/em&gt;, I need to create a custom GUI to easily play around with the look and feel of these planets, shouldn't take too long, and &lt;em&gt;it will speed things up in the long run&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The next few days were spent on a little editor, because I wanted to play around with how I could parametrize the planet creation based on a few properties I wanted in the game, things like water-level, humidity, temperature, and types of minerals on the planets. As I worked, I started to want more visual effects, and also easier to use UI controls. Piece by piece, I had a cluster-fridge of ambiguous goals and ideas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pyQkj5Sc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/JzweXAL.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pyQkj5Sc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/JzweXAL.jpg" alt="Early Editor" title="Early Editor" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An early version allowed me to modify the noise generation and color gradients. I'm controlling my scope creep, so these will be enough. I had already added specular lighting, which already felt like reaching too far, but didn't require too many changes, so ... "while I'm at it...".&lt;/p&gt;

&lt;h2&gt;
  
  
  Height and Specular Mapping
&lt;/h2&gt;

&lt;p&gt;I was already happy with what this version was capable of, since my initial goal was to target a pixel-art style. It's simpler to work with in game development terms, at least for my purposes, and the lack of too much detail meant I could be more permissive in the colors and types of planets. It's sci-fi, after all. But after looking at the planets for a bit, it seemed janky to have a pixel-art ground texture, but a smooth high resolution outline. Do I just live with it because it's just a prototype, the important part is progress on the game? Ha.&lt;/p&gt;

&lt;p&gt;I started delving into high resolution planets and basing my roadmap on making them look good. They already looked satisfactory, and by using several layers with the same noise parameters, I could differentiate the water and land (at this point specular was a property of the whole layer). But getting side-tracked must not be stopped. I remembered about height mapping from earlier days when I dabbled in 3D rendering, and also having the specular as a property of the texture, with its own gradient, would make creating cool planets easier. I first thought about the amount of work to now have 3 different textures (diffuse/color, normal, specular), generating a normal map from the height texture and how these would increasingly drive the base algorithm further from being close to Godot's built-in functionality (which was a goal). Here, Godot itself was the enabler of my journey to nowhere. Turns out there's a &lt;code&gt;CanvasTexture&lt;/code&gt;. This is a compound texture of, &lt;em&gt;surprise&lt;/em&gt;, a diffuse texture, normal texture, and specular texture. Nice. And to finalize my pragmatic decision making process, Godot's &lt;code&gt;NoiseTexture&lt;/code&gt; can automagically generate a normal map, using the noise as a height map. Looks like I'm in for the long haul.&lt;/p&gt;

&lt;p&gt;"Luckily", because Godot already had these tools built-in, setting things up wasn't too hard, but it required I revisit my gradient editing UI components. Initially these were made to just be usable, but since now I'll be working with 3 different gradients per layer, I can't have the interface being cluttered for the imaginary users of this tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--26qjneZJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i.imgur.com/vZy6gl3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--26qjneZJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://i.imgur.com/vZy6gl3.gif" alt="Curve Editor" title="Curve Editor" width="720" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I got a curve editor working, and (as one does) refactored UI code to be more compartmentalized. As it stands right now, the control points for all gradients are synchronized, so each control point of the color curve has a corresponding control point in the height and specular curves. This isn't inherently necessary, and will probably change in the future to enable better color palette generation (there was a prototype, but we're &lt;em&gt;controlling our scope creep&lt;/em&gt;, so not for now) while keeping height and specular curves simple.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XtFAi6Qh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/3EgQ9Gn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XtFAi6Qh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/3EgQ9Gn.png" alt="Editor with Specular and Height" title="Editor with Specular and Height" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Maybe More Maybe Later
&lt;/h2&gt;

&lt;p&gt;Nice. This should definitely be enough. And it has been, for now. There are future plans to continue development, one important low-hanging fruit would be to have export options for the textures separately (diffuse, normal, specular), at which point I can't help but feel this slowly turning into a generic material editor. &lt;em&gt;RIGHT?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>godot</category>
      <category>procgen</category>
      <category>devjournal</category>
    </item>
  </channel>
</rss>
