pancelor [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=27691 photo_carousel <p>This is a silent tutorial video; skip to 2:10 for the juicy bit:<br /> <object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/bkB_kN-3L8U&hl=en&fs=1&rel=0"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="https://www.youtube.com/v/bkB_kN-3L8U&hl=en&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="400"></embed></object></p> <p>This is an animated wallpaper that shows your custom PNG files -- just place them in a particular folder! It also works as a screensaver. It's cpu-friendly, only drawing during transitions.</p> <h2>Installing</h2> <ul> <li><code>load #photo_carousel</code></li> <li><code>mkdir /appdata/system/wallpapers</code></li> <li><code>save /appdata/system/wallpapers/photo_carousel.p64</code></li> <li>run the cart once, to generate the appdata folder</li> <li><code>cd /appdata/photo_carousel</code></li> <li><code>folder</code></li> <li>using your host OS, copy any PNG files into this folder (don't put them in subfolders)</li> <li>set your wallpaper to <code>photo_carousel</code> in System Settings</li> </ul> <h2>Settings</h2> <p>Run <code>podtree /appdata/photo_carousel/settings.pod</code> to edit the settings. Be sure to not press enter while editing (this crashes Picotron 0.1.0e) and you must save with the mouse, not ctrl-s (another Picotron bug(?) -- ctrl-s saves the current cart, instead of the settings file)</p> <p>Set the transition time to 0 to disable transitions</p> <h2>Details</h2> <h3>Converting PNGs</h3> <p>This cart uses <a href="https://www.lexaloffle.com/bbs/?pid=importpng#p">importpng</a> to convert PNG files into Picotron graphics. It does this once, in the background, and caches the results.</p> <p>If you edit one of your PNGs, photo_carousel won't notice (afaik it's impossible to get last-edited time of PNGs in Picotron) Rename your file to generate a new cache entry, or delete <code>/appdata/photo_carousel/cache.pod</code> to regenerate the entire cache.</p> <p><a href="https://www.lexaloffle.com/bbs/?pid=importpng#p">importpng</a> is fast, but it's sometimes lacking in its ability to convert images into Picotron's palette. If you want better colors, you should use an external tool (like Aseprite) to convert your PNG into the base Picotron palette (you'll want the base palette, because the global palette changes to the base palette whenever you focus on some other window)</p> <p>In the future I may use <a href="https://www.lexaloffle.com/bbs/?pid=pngframe#p">PNG Frame</a> to get better colors by default, but I haven't done that work yet.</p> <h3>Transitions</h3> <p>You can add your own transitions -- you can skim through src/subdraw.lua without needing to understand anything else in the cart, and add new entries to the list of &quot;subdraws&quot; (transitions). Its very shadery/demosceney code</p> <p>Many of the subdraws use some helper functions in src/tools.lua</p> <p>Post your transition code here!</p> <h3>CPU usage</h3> <p>The cart uses about 0.002 cpu (0.2%) most of the time. During transitions, it can spike up to 25% cpu or so (depending on the transition). If you care, you can disable the transitions as a whole (by editing the settings) or individually (by editing the multi-line comments in src/subdraw.lua)</p> <h3>Cart</h3> <p>Here's the cart file. (It won't work here on the BBS, you need to download it)</p> <p> <table><tr><td> <a href="/bbs/?pid=145966#p"> <img src="/bbs/thumbs/pico64_photo_carousel-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=145966#p"> photo_carousel</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=145966#p"> [Click to Play]</a> </td></tr></table> </p> https://www.lexaloffle.com/bbs/?tid=141520 https://www.lexaloffle.com/bbs/?tid=141520 Sun, 07 Apr 2024 11:59:48 UTC coroutine bug <p>hey <a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>, I've found a nasty coroutine(?)/multival bug. I'm on Linux + picotron 0.1.0d.</p> <p>tl;dr: sometimes <code>select(&quot;#&quot;,tostr(i))</code> is 2, possibly triggered by calling coresume() with extra args.</p> <hr /> <p>I ran into this initially because <code>add({},3,nil)</code> is a runtime error now (it used to work in PICO-8, but now it throws <code>bad argument #2 to 'add' (position out of bounds)</code>). I had some code: <code>add(list,quote(arg))</code> that was crashing as if quote() was returning a second value for some reason, even though the code for quote() definitely returned just one value. (surrounding it in parens (to only keep the first return value) fixed my bug: <code>add(list,(quote(arg)))</code>)</p> <p>Version A of the code is very short, and trips the assert inside <code>spin</code> maybe 50% of the time? sometimes many runs in a row don't trigger the assert, but sometimes many runs in a row <em>all</em> trigger the assert. (maybe that's just statistics tho). Version B is a bit more complex but always trips the assert instantly for me.</p> <hr /> <p>Version A: (short, inconsistent) <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>printh&quot;---&quot; function _init() local bad,good = cocreate_spin() fn = bad end function _draw() cls() local _,prog = fn(0.70) ?stat(1) ?prog or &quot;done&quot; end function cocreate_spin() local coro = cocreate(spin) return function( cpu_limit) if costatus(coro)==&quot;suspended&quot; then --this one breaks sometimes return assert(coresume(coro,cpu_limit or 0.90)) end end, function() if costatus(coro)==&quot;suspended&quot; then --this one always works return assert(coresume(coro)) end end end local total = 1000000 function spin() for i=1,total do if i%10000==0 --[[and stat(1)&gt;cpu_limit]] then yield(i/total) end local n = select(&quot;#&quot;,quote(i)) if n!=1 then assert(false,tostr(n)..&quot; retvals?! &quot;..i) end end end function quote(t) return tostr(t) end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <p>Version B: (longer, very consistent) <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>printh&quot;---&quot; function _init() poke(0x5f36,0x80) -- wrap text window{ title=&quot;import png&quot;, width=160, height=64, } job = job_importpng() end function _update() job:work(0.70) end function _draw() cls() print(stat(1)) print(costatus(job.coro)) end function job_importpng() local coro = cocreate(pq_many) assert(coresume(coro)) local job = { coro = coro, progress = 0.00, } -- returns true iff job has more work to do function job:work( cpu_limit) if costatus(self.coro)==&quot;suspended&quot; then local _,dat = assert(coresume(self.coro,cpu_limit or 0.90)) if dat then self.progress = dat return true -- more work to do end end end return job end function pq_many() for i=1,1000000 do if i&amp;2047==0 then yield() end pq(i) end end -- quotes all args and prints to host console -- usage: -- pq(&quot;handles nils&quot;, many_vars, {tables=1, work=11, too=111}) function pq(arg) local s= {} local n = select(&quot;#&quot;,quote(arg)) if n!=1 then local second = select(2,quote(arg)) assert(false,tostr(n)..&quot; retvals?: &quot;..quote(arg)..&quot; &quot;..tostr(second)) end add(s,(quote(arg))) -- add(s,quote(arg)) printh(table.concat(s,&quot; &quot;)) end -- quote a single thing -- like tostr() but for tables -- don't call this directly; call pq or qq instead function quote(t) if type(t)~=&quot;table&quot; then return tostr(t) end local s={} for k,v in pairs(t) do add(s,tostr(k)..&quot;=&quot;..quote(v)) end return &quot;{&quot;..table.concat(s,&quot;,&quot;)..&quot;}&quot; end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <hr /> <p>As noted in Version A, the two coresume-wrapping-functions inside cocreate_spin() act differently -- I've never been able to trip the assert with the &quot;good&quot; version. I've tried versions of this code with no coroutines and haven't been able to trip the assert.</p> <p>idk what else to say, this bug seems baffling -- sometimes <code>select(&quot;#&quot;,quote(i))</code> is 2, despite quote() being a wrapper for tostr()</p> https://www.lexaloffle.com/bbs/?tid=141267 https://www.lexaloffle.com/bbs/?tid=141267 Sat, 30 Mar 2024 08:35:58 UTC import png <p> <table><tr><td> <a href="/bbs/?pid=144661#p"> <img src="/bbs/thumbs/pico64_importpng-9.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=144661#p"> importpng</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=144661#p"> [Click to Play]</a> </td></tr></table> </p> <h2>Guide</h2> <p>Currently (in Picotron 0.1.0e) it's hard to use a PNG image as a sprite. You can <code>fetch(&quot;myimage.png&quot;)</code>, but the result isn't in the right image format. So, here's a small tool to convert PNG files into picotron sprites.</p> <p>Drag any .png or .qoi image into the tool to convert it into a sprite pod on your clipboard. You can paste this into the graphics editor, or into code.</p> <p>Drag in a .hex file (e.g. from lospec.com) or a .pal file (e.g. from <a href="https://www.lexaloffle.com/bbs/?pid=okpal#p">OkPal</a>) before importing your png to change the import palette.</p> <h2>Details</h2> <h3>Import speed</h3> <p>This tool prioritizes import speed. Images with only a few unique colors (e.g. an image already in the picotron palette) will import much faster than images with many colors.</p> <h3>Color quantization</h3> <p>You can use any colors, and the tool will try it's best to fit them to the palette. It isn't very smart about converting colors (it uses nearest euclidean distance in RGB space, and doesn't do any dithering). If you want better color conversion, use some external tool and make sure the PNG is in the correct palette before converting it with this tool.</p> <p>See also PNG Frame, in &quot;Related tools&quot; below.</p> <h3>Wallpapers</h3> <p>If you're making a desktop wallpaper, I think you should stick to the default palette. If you use a custom palette, then your colors will change whenever you focus some window besides the desktop, I'm pretty sure.</p> <p>To make a wallpaper:</p> <ul> <li>make sure your wallpaper folder exists (<code>mkdir /appdata/system/wallpapers</code>)</li> <li>save a new cart into that folder with this code: <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function _draw() spr(1) _draw = function() end end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div></li> <li>convert your image using this tool, and copy it into your clipboard</li> <li>paste your image into sprite 1 of the default sprite file</li> </ul> <h3>Library usage</h3> <p>This code is organized to make it easy to use as a library in your code. The library is self-contained in <code>lib/importpng.lua</code>, and you interact with it by calling <code>myjob = job_importpng(myimg)</code>. The returned &quot;job&quot; is essentially a coroutine, but one that will let you limit its processing time. Call <code>myjob:work(limit)</code> during _update() until the job is done, or call <code>myjob:join()</code> a single time to finish it all at once (likely lagging the game). See the code for more details.</p> <p>License: <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC4-BY-NC-SA</a></p> <h2>Related tools</h2> <ul> <li>Cutievirus' <a href="https://www.lexaloffle.com/bbs/?pid=pngframe#p">PNG Frame</a> -- a similar tool with much better color conversion. If your png is already in the right palette, importpng is faster than PNG Frame. But if you want better color quantization, use PNG Frame.</li> <li>My <a href="https://gist.github.com/pancelor/43512028087bf34e5d9773a61a03f93a">Aseprite PICO-8/Picotron export extension</a> lets you export Picotron pods directly to your clipboard from Aseprite.</li> <li>My <a href="https://www.lexaloffle.com/bbs/?tid=140800">sprite importer</a> lets you import PICO-8 spritesheets into Picotron.</li> <li>My <a href="https://www.lexaloffle.com/bbs/?pid=photo_carousel#p">photo carousel</a> uses this importer to show your custom PNGs as an animated wallpaper.</li> <li>drakmaniso's <a href="https://www.lexaloffle.com/bbs/?pid=okpal#p">OkPal</a> palette editor.</li> </ul> <h2>changelog</h2> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <h3>v0.4 (#importpng-9)</h3> <ul> <li>add <a href="https://qoiformat.org/">QOI</a> image support - thanks to vebrun for prompting me to add it!</li> </ul> <h3>v0.3 (#importpng-7)</h3> <ul> <li>support for custom palettes</li> <li>make it easy to use importpng as a library</li> </ul> <h3>v0.2 (#importpng-6)</h3> <ul> <li>show image as it imports</li> <li>draw black in preview (not transparent anymore)</li> <li>export to compressed pod format</li> </ul> <h3>v0.1 (#importpng-4)</h3> <ul> <li>initial release<br /> </div></div></div></li> </ul> https://www.lexaloffle.com/bbs/?tid=141149 https://www.lexaloffle.com/bbs/?tid=141149 Wed, 27 Mar 2024 11:24:42 UTC p8x8: convert PICO-8 carts to Picotron <p> <table><tr><td> <a href="/bbs/?pid=144532#p"> <img src="/bbs/thumbs/pico64_p8x8-5.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=144532#p"> p8x8</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=144532#p"> [Click to Play]</a> </td></tr></table> </p> <h3>p8x8: convert PICO-8 carts into Picotron carts (some assembly required)</h3> <p>I'm declaring p8x8 good enough for public release! It's a tool to convert pico8 carts to picotron -- it's not perfect and it requires some manual intervention in most cases, but it's magical being able to play a bunch of games on the new system without much effort.</p> <p>Lots more info (instructions, compatibility notes, license, etc) here: <a href="https://github.com/pancelor/p8x8/">https://github.com/pancelor/p8x8/</a></p> <p>Teaser video here: <a href="https://mastodon.social/@pancelor/112162470395945383">https://mastodon.social/@pancelor/112162470395945383</a></p> <h2>changelog</h2> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <h3>v1.5 (#p8x8-5)</h3> <ul> <li>custom fonts work now <ul> <li>docs + snippet for making custom fonts work even if you set them up with p8scii</li> </ul></li> <li>fix player 2 btn/btnp</li> <li>some progress on porting sfx -- still looking for help!</li> <li>minor bugfix: prevent crash when input cart has no <strong>gfx</strong> section</li> </ul> <h3>v1.4 (#p8x8-4)</h3> <ul> <li>bugfixes for add(), rnd()</li> <li>organize github docs</li> <li>fix sfx()/music() (used to be noop; now passthrough to p64)</li> </ul> <h3>v1.3 (#p8x8-3)</h3> <ul> <li>better default fullscreen border</li> <li>better warnings</li> </ul> <h3>v1.2 (#p8x8-2)</h3> <ul> <li>support fullscreen border images</li> <li>easier main.lua tweaking (fullscreen, pause_when_unfocused)</li> <li>more docs / warnings / ux</li> </ul> <h3>v1.1 (#p8x8-1)</h3> <ul> <li>nicer error/warning handling (e.g. when there's no code section)</li> <li>handle windows CRLF carts properly</li> <li>generate warnings much faster (smarter sorting algorithm)</li> </ul> <h3>v1.0 (#p8x8-0)</h3> <ul> <li>public release<br /> </div></div></div></li> </ul> https://www.lexaloffle.com/bbs/?tid=141107 https://www.lexaloffle.com/bbs/?tid=141107 Tue, 26 Mar 2024 14:35:54 UTC sedish: edit system files on startup <p>Here's my <code>/appdata/system/startup.lua</code> file (picotron automatically runs it on startup)</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- take str, delete all chars between -- indices i0 and i1 (inclusive), and -- insert newstr into that space local function splice(str,i0,i1, newstr) return sub(str,1,max(1,i0)-1)..(newstr or &quot;&quot;)..sub(str,i1+1) end -- use str:find to do sed-like file editing. -- no lua patterns, just literal string matching -- return str, but replace the first instance of cut with paste local function _replace(str,cut,paste) local i0,i1 = str:find(cut,1,true) -- no pattern-matching if not i0 then return str,false end return splice(str,i0,i1,paste),true end local function sedish(fname,mods) local src = fetch(fname) if not src then printh(&quot;sedish: couldn't find file &quot;..fname) return end for i,mod in ipairs(mods) do local cut,paste = unpack(mod) -- printh(cut..&quot; &quot;..paste) if not cut or not paste then printh(&quot;sedish: bad cut/paste data in &quot;..fname) return end local changed src,changed = _replace(src,cut,paste) if not changed then printh(&quot;sedish: mod #&quot;..i..&quot; did nothing to &quot;..fname) end if not src or #src==0 then printh(&quot;sedish: bad result in &quot;..fname) return end end -- printh(&quot;storing &quot;..fname..&quot;: &quot;..sub(src,1,100):gsub(&quot;[\n\r]&quot;,&quot;&quot;)) store(fname,src) end sedish(&quot;/system/lib/gui_ed.lua&quot;,{ { -- fix alt-tab annoyance (pressing alt-tab inserts tab character into code) [[if (keyp(&quot;tab&quot;)) then]], [[if keyp&quot;tab&quot; and not key&quot;alt&quot; then]], }, }) sedish(&quot;/system/apps/podtree.p64/main.lua&quot;,{ { -- fix podtree bug (pressing enter crashes the program) [[if(key(&quot;shift&quot;)) return true]], [[if(_ENV.key(&quot;shift&quot;)) return true]], }, }) -- (you need to open a new gfx tab for these gfx.p64 edits to work) sedish(&quot;/system/apps/gfx.p64/pal.lua&quot;,{ { -- show pal numbers [[pcols[x + y*epr])]], [[pcols[x + y*epr])if pal_swatch==1 and x%2==0 then print(pcols[x + y*epr],x*ww+5,y*ww+1,0) end]], }, }) -- sedish(&quot;/system/apps/gfx.p64/nav.lua&quot;,{ -- { -- display sprite numbers in hex -- [[string.format(&quot;%03d&quot;,current_item)]], -- [[string.format(&quot;x%02x&quot;,current_item)]], -- }, -- }) sedish(&quot;/system/apps/gfx.p64/update.lua&quot;,{ { -- don't flip when you press ctrl-v (noticeable when undoing) [[if (keyp(&quot;v&quot;))]], [[if (keyp&quot;v&quot; and not key&quot;ctrl&quot;)]], }, }) sedish(&quot;/system/lib/gui_ed.lua&quot;,{ { -- fix custom key callbacks in gui [[content.key_callback(k)]], [[content.key_callback[k](k)]], }, }) -- doesn't work for startup terminal -- need to wait for it to regenerate, e.g. after running a fullscreen app sedish(&quot;/system/apps/terminal.lua&quot;,{ { -- add custom terminal_cd event (to let my z.lua util &quot;run&quot; cd for you) [[-- scroll down only if needed]], [[on_event(&quot;terminal_cd&quot;,function(msg) cd(msg.path) end)]], }, { -- show slightly different terminal prompt when in upgraded terminal [[return result -- custom prompt goes here]], [[return &quot;\f6&quot;..pwd()..&quot;\f7 &gt; &quot;]], }, }) printh &quot;sedish done&quot;</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h2>what is this</h2> <p>I made this because picotron inserts a tab into my code whenever I alt-tab. I figured out how to edit the code editor itself to not do that, and then I made this script to automatically make the required edits. (you can't just edit the files on disk, because they get regenerated every time picotron boots)</p> <p>this won't be necessary for too long hopefully, but it works great for now!</p> <p>(the name &quot;sedish&quot; comes from the (loose) inspiration for this: <a href="https://ss64.com/bash/sed.html">sed</a>)</p> <h2>post your tweaks</h2> <p>If anyone has other system tweaks like this one (fixing alt-tab), post them here!</p> https://www.lexaloffle.com/bbs/?tid=140847 https://www.lexaloffle.com/bbs/?tid=140847 Mon, 18 Mar 2024 04:05:44 UTC pico8 sprite+map importer <p>update: there are basically 3 tools that are relevant here:</p> <ul> <li>this tool (sprimp) -- maybe still useful, but superceded by p8x8:</li> <li><a href="https://www.lexaloffle.com/bbs/?pid=p8x8#p">p8x8</a>: a tool to fully convert PICO-8 carts to Picotron carts (some assembly required) -- this grew out of sprimp</li> <li>my <a href="https://gist.github.com/pancelor/43512028087bf34e5d9773a61a03f93a">aseprite exporter plugin</a> -- still useful</li> </ul> <p>You probably want to check out p8x8 instead, but I'm leaving this thread as it is since it may still be useful or interesting to some people.</p> <hr /> <p> <table><tr><td> <a href="/bbs/?pid=143506#p"> <img src="/bbs/thumbs/pico64_sprimp-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=143506#p"> sprimp</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=143506#p"> [Click to Play]</a> </td></tr></table> </p> <p>tada! now you can import pico8 spritesheets. and map too!</p> <h2>importing a .p8 file</h2> <ul> <li>put your game.p8 file somewhere inside picotron's file system <ul> <li>the filename should not have any dashes (&quot;-&quot;), they generally cause problems currently (picotron 0.1.0b2)</li> </ul></li> <li>run this importer (sprimp)</li> <li>import the p8 cart <ul> <li>you can drag-and-drop the cart onto the sprimp window</li> <li>or you can use &quot;Import .p8 file&quot; in the menu (the dialog that pops up says &quot;save as&quot; -- please ignore this. this is the best way I know how to launch a system filepicker)</li> </ul></li> </ul> <h2>exporting sprites</h2> <ul> <li>press &quot;Save gfx..&quot;. choose a filename; the default is <code>/ram/cart/gfx/0.gfx</code> (which is the default spritesheet for your current cart)</li> <li><strong>IMPORTANT</strong>: if the spritesheet was already open in picotron's gfx editor, you'll need to close the file and reopen it (right click on the tab, close, then press the plus)</li> <li>if you saved to <code>/ram/cart/...</code> save your cart afterwards (ctrl-s)</li> </ul> <h2>exporting map</h2> <ul> <li>(same as exporting sprites, just click &quot;Save map..&quot; instead)</li> <li>for an easy transition from pico8 maps to picotron maps, check out <a href="https://www.lexaloffle.com/bbs/?pid=143245#p">this snippet</a>, which includes <code>mget</code> and <code>mset</code> replacements</li> </ul> <h2>other ways to import (copypaste, aseprite)</h2> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <h3>importing sprite by copy-paste</h3> <ul> <li>open the pico8 sprite editor</li> <li>copy the entire spritesheet (press tab, ctrl-a, ctrl-c)</li> <li>your clipboard now has a <code>[gfx]...</code> code in it</li> <li>open this tool (sprimp) in picotron</li> <li>past the <code>[gfx]...</code> code into the tool</li> </ul> <h3>importing a single sprite</h3> <ul> <li>you can copy from pico8 and paste into picotron directly, zep made that work already</li> <li>you can even paste your entire spritesheet into a single picotron sprite -- it's sorta wild. but you probably wanted to spread the spritesheet out into individual sprites, right? that's where this tool can help</li> </ul> <h3>importing from aseprite</h3> <p>you can use <a href="https://gist.github.com/pancelor/43512028087bf34e5d9773a61a03f93a">my exporter script</a> for aseprite to export aseprite images into the <code>[gfx]...</code> format, letting you paste them into picotron or sprimp (or even pico8!)<br /> </div></div></div></p> <h2>future plans</h2> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <ul> <li>Yes: Make importing sprites/maps easy. (mostly done! still some UI/UX work tho)</li> <li>Done but not yet published: importing p8 code tabs into .lua files. with a big disclaimer that you'll need to do a bunch of manual work to fix the code afterward</li> <li>Maybe: sfx/music. I likely won't do this myself. If you have a working sfx/music importer and want to join forces, let me know!</li> <li>No: make it possible to drag-and-drop a pico8 cart into picotron and have everything &quot;just work&quot;. this is <em>not</em> meant to be a perfect emulator, just a stepping stone</li> </ul> <p>maybe zep will add improvements to picotron itself, and this whole project will no longer be relevant. that would be great! but whether or not that happens, this was a fun small project for me to learn some basics of picotron tool-making<br /> </div></div></div></p> <h2>changelog</h2> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <h3>v0.2</h3> <ul> <li>add .p8 import -- thank you <a href="https://www.lexaloffle.com/bbs/?uid=16423"> @Krystman</a> for the idea!</li> <li>add drag-and-drop support</li> <li>add map import/export</li> </ul> <h3>v0.1</h3> <ul> <li>sprite import/export<br /> </div></div></div></li> </ul> https://www.lexaloffle.com/bbs/?tid=140800 https://www.lexaloffle.com/bbs/?tid=140800 Sun, 17 Mar 2024 13:58:25 UTC adrift <p> <table><tr><td> <a href="/bbs/?pid=143111#p"> <img src="/bbs/thumbs/pico64_susizker-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=143111#p"> susizker</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=143111#p"> [Click to Play]</a> </td></tr></table> </p> <p>just a port of a tweetcart of mine to the new system :)</p> <p>move this into <code>/system/screensavers/</code> and it'll show up as an option in your settings</p> <p>If you run it on its own as a cart, you'll want to run <code>reset() vid(0)</code> in the console afterwards to get things back to normal</p> <p>edit: if you want it to be permanently available, you need to put it in <code>/appdata/system/screensavers</code> (otherwise, you need to re-add it every time you start picotron). create the folder by copying the system folder: <code>cp /system/screensavers /appdata/system/screensavers</code></p> https://www.lexaloffle.com/bbs/?tid=140680 https://www.lexaloffle.com/bbs/?tid=140680 Fri, 15 Mar 2024 13:37:14 UTC ghost patrol <p> <table><tr><td> <a href="/bbs/?pid=135418#p"> <img src="/bbs/thumbs/pico8_ghostpatrol-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=135418#p"> ghostpatrol</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=135418#p"> [Click to Play]</a> </td></tr></table> </p> <p><em>Oh no, ghosts are approaching the town!</em></p> <p>Mayor Wombledon has begged <strong>ALFREDO THE GHOUL BANISHER</strong> to team up with <strong>THE WILY WIZ</strong> to protect the town. Can they put aside their differences and work together, or will evil spirits devour the populace?</p> <h2>Controls</h2> <ul> <li>Arrow keys: move</li> <li>Z: swap heroes</li> <li>Enter/P: pause (level select, volume controls)</li> <li>X: next level</li> </ul> <h2>Outcomes</h2> <ul> <li>1 night protected: You have the villagers' sincere gratitude 🙏</li> <li>5 nights protected: The villagers are beginning to hope again 😭</li> <li>10 nights protected: Valiant Heroes 🏆</li> <li>40 nights protected: Local Deity 🤯</li> </ul> <h2>Tips</h2> <ul> <li>Alfredo can dig up graves with his fearsome claws.</li> <li>The Wiz can swap places with nearly anything.</li> <li>You can change levels freely in the pause menu.</li> <li>Never give up; always fight with your full strength.</li> </ul> <img style="margin-bottom:16px" border=0 src="/media/27691/scr1.gif" alt="" /> <img style="margin-bottom:16px" border=0 src="/media/27691/90_banner.png" alt="" /> <img style="margin-bottom:16px" border=0 src="/media/27691/scr2-small.png" alt="" /> <p><a href="https://pancelor.itch.io/ghost-patrol">1024 bytes</a>, made by pancelor for <a href="https://itch.io/jam/pico-1k-2023">PICO-1K 2023</a>. Thanks to timp + shrinko8 for compression help!</p> <p>The layouts were randomly generated, but they don't change. My best times for the first 5 levels are 7 / 11 / 15 / 39 / 41.</p> <p>If you enjoyed this, you might also enjoy my game <a href="https://pancelor.itch.io/hungry-eggbug">Hungry Eggbug</a>, or the board game that loosely inspired this game: <a href="https://boardgamegeek.com/boardgame/51/ricochet-robots">Ricochet Robots</a>.</p> https://www.lexaloffle.com/bbs/?tid=54459 https://www.lexaloffle.com/bbs/?tid=54459 Wed, 04 Oct 2023 21:47:32 UTC long comment parse bug &quot;for --[[a]]e=0,1 do&quot; <p>Hi <a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>, found a parser bug for ya:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>for --[[a]]e=0,1 do print(e) end print(fore)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>expected: 0 1 [nil] (this is what lua 5.4 outputs)<br /> observed: [nil] 0</p> <p>pico-8's highlighting works correctly, but the runtime seems to see this somehow:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>fore=0,1 do print(e) end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>system: linux / pico8 0.2.5g</p> <hr /> <p>I ran into this while using shrinko8's annotations (<code>for --[[preserve]]e=0,1 do</code>)</p> <p>Workaround: add an extra space (<code>for --[[preserve]] e=0,1 do</code>)</p> <hr /> <p>edit: ah! this thread has more cases / info: <a href="https://www.lexaloffle.com/bbs/?tid=51618">https://www.lexaloffle.com/bbs/?tid=51618</a></p> https://www.lexaloffle.com/bbs/?tid=54395 https://www.lexaloffle.com/bbs/?tid=54395 Sun, 01 Oct 2023 10:22:34 UTC extra 0xff inserted when no newline at __meta__ EOF <p><a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a> if you open this cart in pico8 and save it, pico8 inserts an extra byte at the end (0xff) (this is wrong, but additionally it is very confusing because my text editor thinks the text encoding has changed and starts displaying weird unicode everywhere)</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>pico-8 cartridge // http://www.pico-8.com version 41 __lua__ ?'hi' __meta:title__ cooltitle</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>After some minimal testing, the conditions necessary seem to be:</p> <ol> <li>the file ends with a <code>__meta__</code> section (<code>__gfx__</code> doesn't trigger the bug)</li> <li><strong>the file does not have a trailing newline</strong> (the last byte of this particular file is 'e', not '\n')</li> </ol> <img style="margin-bottom:16px" border=0 src="/media/27691/bug.png" alt="" /> <p>platform: linux / pico8: 0.2.5g</p> https://www.lexaloffle.com/bbs/?tid=54389 https://www.lexaloffle.com/bbs/?tid=54389 Sun, 01 Oct 2023 00:14:37 UTC Bitplanes! An overview <p><a href="https://en.wikipedia.org/wiki/Bit_plane">Bitplanes</a> are powerful, but they can be difficult to understand. How do you use them in PICO-8?</p> <p>The short version: bitplanes let you draw colors to the screen without completely overwriting the existing colors, making it possible to do effects like shadows, transparency, etc. But be warned: they come with a lot of unintuitive restrictions, like monopolizing half your screen palette or requiring it to be set up in a particular way.</p> <p>Longer version: PICO-8 colors are 4-bit numbers (0-15). The screen is a 128x128 4-bit image, but you can imagine it as 4 separate 12x128 1-bit images overlaid on top of each other. By poking a particular location in memory, we can tell PICO-8 to draw to these &quot;bit planes&quot; separately. Normally, drawing overwrites any existing colors, but if we selectively disable some of the bitplanes, some bits of the old color will remain onscreen.</p> <p>Technical version: see &quot;Technical details&quot; below.</p> <p>This post lists some specific examples and tricks that you can do with bitplanes. I won't attempt to fully explain how bitplanes work, but I'll leave some resources at the end.</p> <h2>Examples</h2> <h3>Shadows</h3> <ol> <li>set up your screen palette:<br /> i. 0-7: shadow palette; 0 is the shadow color for 8, 1 is the shadow color for 9, etc<br /> ii. 8-15: any colors</li> <li>draw anything</li> <li>enable bitplane mode 0x08 (clear the 0/8 bitplane)</li> <li>draw anything -- any pixels &quot;drawn&quot; will instead overwrite colors 8-15 with their shadow colors</li> <li>disable bitplane mode</li> </ol> <p> <table><tr><td> <a href="/bbs/?pid=134693#p"> <img src="/bbs/thumbs/pico8_bitplane_shadows-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=134693#p"> bitplane_shadows</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=134693#p"> [Click to Play]</a> </td></tr></table> </p> <p>I used this method in my <a href="https://www.lexaloffle.com/bbs/?pid=freecell1k#p">freecell clone</a>.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/80_spr6.png" alt="" /> <h3>Spotlight</h3> <ol> <li>set up your screen palette:<br /> i. 0-7: all identical; e.g. all 0 (black)<br /> ii. 8-15: any colors</li> <li>draw your scene using colors 0-7. they will all appear black</li> <li>enable bitplane mode 0xf8 (only draw the 0/8 bit)</li> <li>draw a <code>circfill</code> in color 8 -- instead of drawing red, it will add 8 to each color it hits, revealing the image &quot;underneath&quot; the darkness</li> <li>disable bitplane mode</li> </ol> <p><a href="https://www.lexaloffle.com/bbs/?pid=120402#p">This cart</a> uses a very similar technique to create an &quot;xray&quot; effect. (They set up their palette differently and use a different bitplane mode, swapping adjacent colors instead of shifting the palette by 8)</p> <img style="margin-bottom:16px" border=0 src="/media/27691/81_spr1.png" alt="" /> <h3>1-bit sprite packing</h3> <p>If you have 1-bit sprites, you can store them merged in the 4 bitplanes in the spritesheet, and extract them at runtime using bitplanes. It's essentially another way to do <a href="https://8bit-caaz.tumblr.com/post/171458093376/layering-sprite-data">this sprite-layering technique</a>. Here's a <a href="https://www.lexaloffle.com/bbs/?tid=53207">tool</a> that does something similar. Neither of these actually use pico8's bitplane feature, but they could be implemented that way to save some tokens and a tiny bit of cpu.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/spr3.png" alt="" /> <h3>Chromatic aberration</h3> <p>You can create some cool effects (like <code>https://mastodon.social/@zep/109315783011955478</code>) by drawing slightly different images on different bitplanes.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/spr2.png" alt="" /> <h3>Trippy motion blur</h3> <p>Flashing lights warning, this one can be pretty rough on the eyes. It's a neat <a href="https://twitter.com/lexaloffle/status/1318792339351990272">trippy effect</a> where each bitplane gets updated every 4 frames, leaving a slightly out-of-date impression onscreen for those other 3 frames, which creates a motion blur of sorts. I find it hard to look at (especially the jelpi example in the replies!) but it looks fascinating and it's very neat how bitplanes make it easy to create this effect in any game. I wonder how this could look with palette specifically designed for it... (the palette from the following &quot;Anti-aliasing&quot; example works decently well!)</p> <img style="margin-bottom:16px" border=0 src="/media/27691/84_spr8.png" alt="" /> <p>(thanks freds72 for finding the link to this! I couldn't remember where I'd seen it)</p> <h3>Anti-aliasing</h3> <p>You can add anti-aliasing by drawing the same thing at slight subpixel offsets, like in <a href="https://www.lexaloffle.com/bbs/?pid=104555#p">this example</a>. (Note that this restricts your palette to 5 colors)</p> <img style="margin-bottom:16px" border=0 src="/media/27691/spr5.png" alt="" /> <h2>Technical details</h2> <p><a href="https://en.wikipedia.org/wiki/Bit_plane">Wikipedia</a> has some general info, and the <a href="http://pico8wiki.com/index.php?title=Memory">PICO-8 wiki</a> (search &quot;bitplane&quot;) has specifics about how they work in PICO-8:</p> <p>&gt; 0x5f5e / 24414<br /> &gt; Allows PICO-8 to mask out certain bits of the input source color of drawing operations, and to write to specific bitplanes in the screen (there's 4 of them since PICO-8 uses a 4BPP display). Bits 0..3 indicate which bitplanes should be set to the new color value, while bits 4..7 indicate which input color bits to keep.<br /> &gt; For example, <code>poke(0x5f5e, 0b00110111)</code> will cause drawing operations to write to bitplanes 0, 1, and 2 only, with 0 and 1 receiving the color value bits, 2 being cleared, and 3 being unaltered.<br /> &gt; This formula is applied for every pixel written:<br /> &gt; <code>dst_color = (dst_color &amp; ~write_mask) | (src_color &amp; write_mask &amp; read_mask)</code></p> <p>If you're not sure what to try, setting the write_mask and read_mask to the same value is often useful.</p> <h2>Other resources</h2> <p>-<a href="https://www.lexaloffle.com/bbs/?tid=38338">Bitwise math tutorial</a><br /> -<a href="https://www.lexaloffle.com/bbs/?tid=39726">A simple bitplane example</a> -- three circles rotating in 3D<br /> -<a href="https://www.lexaloffle.com/bbs/?tid=54214">My bitplane explorer</a> -- it helps visualize how colors will interact</p> <img style="margin-bottom:16px" border=0 src="/media/27691/spr4.png" alt="" /> <p>-<a href="https://www.lexaloffle.com/bbs/?pid=76044#p">The original discovery</a><br /> -<a href="https://twitter.com/lexaloffle/status/1258464454485803009">Bitplanes confirmed by zep</a><br /> -<a href="https://www.lexaloffle.com/bbs/?tid=46286">Circular Clipping Masks</a> -- discusses other non-bitplane ways to get shadow and spotlight effects</p> https://www.lexaloffle.com/bbs/?tid=54215 https://www.lexaloffle.com/bbs/?tid=54215 Thu, 21 Sep 2023 11:11:20 UTC bitplane explorer <p>PICO-8 supports bitplane drawing; the <a href="http://pico8wiki.com/index.php?title=Memory">wiki</a> (search &quot;bitplane&quot;) has a description of how they work:</p> <p>&gt; 0x5f5e / 24414<br /> &gt; Allows PICO-8 to mask out certain bits of the input source color of drawing operations, and to write to specific bitplanes in the screen (there's 4 of them since PICO-8 uses a 4BPP display). Bits 0..3 indicate which bitplanes should be set to the new color value, while bits 4..7 indicate which input color bits to keep.<br /> &gt; For example, <code>poke(0x5f5e, 0b00110111)</code> will cause drawing operations to write to bitplanes 0, 1, and 2 only, with 0 and 1 receiving the color value bits, 2 being cleared, and 3 being unaltered.<br /> &gt; This formula is applied for every pixel written:<br /> &gt; <code>dst_color = (dst_color &amp; ~write_mask) | (src_color &amp; write_mask &amp; read_mask)</code></p> <p>This is precise and correct, but I find it a bit hard to understand. So I made this cart to give myself an interactive sandbox where I can play around with bitplanes, to see how they affect drawing.</p> <p> <table><tr><td> <a href="/bbs/?pid=134692#p"> <img src="/bbs/thumbs/pico8_bitplane_explorer-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=134692#p"> bitplane_explorer</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=134692#p"> [Click to Play]</a> </td></tr></table> </p> <p>The code is straightforward:</p> <ol> <li>draw the full 16-color palette</li> <li>enable bitplanes, using the <code>poke</code> shown onscreen</li> <li>draw a circle, using the <code>circfill</code> shown onscreen</li> </ol> <p>You can change the bitplane settings and the circle's &quot;color&quot; -- the controls are shown onscreen. This interactively shows how drawing any color will interact with any other color under the current bitplane settings.</p> <p>You'll still need to study the description from the wiki to understand how to use bitplanes, but this cart is a helpful supplement for me when working with bitplanes. I hope you find it helpful too!</p> https://www.lexaloffle.com/bbs/?tid=54214 https://www.lexaloffle.com/bbs/?tid=54214 Thu, 21 Sep 2023 02:13:53 UTC Responsive menuitem() UX <p><em>update 2024: the 0.2.6 update improves things! search the <a href="https://www.lexaloffle.com/bbs/?tid=140421">update post</a> for <code>menuitem(0x301</code> for details. (My original menuitem post remains below, unchanged)</em></p> <hr /> <p>PICO-8 has <a href="https://www.lexaloffle.com/bbs/?tid=41544">fancy menuitems</a> but there are some <a href="https://www.lexaloffle.com/bbs/?tid=52196">gotchas</a> and <a href="https://www.lexaloffle.com/bbs/?tid=42392">bugs</a> to be aware of.</p> <p>Here's an example of what I do by default; the rest of this post will explain how the code works and why I do things this way:</p> <p> <table><tr><td> <a href="/bbs/?pid=133332#p"> <img src="/bbs/thumbs/pico8_menuitems-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=133332#p"> menuitems</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=133332#p"> [Click to Play]</a> </td></tr></table> </p> <h2>L/R pitfall</h2> <p>Imagine you want to add a &quot;mute&quot; button to your game's menu. Can you spot the issue with this code?</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function _init() menuitem(1,&quot;mute&quot;,function() ismuted=not ismuted -- ... play/pause the music end) -- ... other game init end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>The issue: left/right can be used to change the menuitem, and left/right always leaves the menu open afterward (this may be a bug; it's unclear) so there's no indication to the player that they accidentally muted the game! It's a minor issue... but it's easy to accidentally hit left/right when navigating the menu on a gamepad, which makes this worse.</p> <p>An easy fix: don't respond to L/R (<code>menuitem(1,&quot;mute&quot;,function(bb) if bb&gt;=16 then ... end end)</code>). This is what I'd recommend for games that are short on tokens but willing to spare a few to fix this.</p> <h2>Responsive menu</h2> <p>If you'd like a more responsive menu, you can do this:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function _init() menuitem(1,ismuted and &quot;music: off&quot; or &quot;music: on&quot;,function() ismuted=not ismuted -- ... play/pause the music -- update the label: menuitem(nil,ismuted and &quot;music: off&quot; or &quot;music: on&quot;) -- feels like we could save some tokens... return true end) -- ... other game init end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Now the option shows its status, and updates its label immediately when you change it!</p> <p>Note the <code>return true</code> -- this keeps the menu open. Without this, if the player presses left (toggling the setting) and then presses enter (trying to close the menu but instead toggling the setting again), they'll wonder why their change isn't sticking. By leaving the menu open, we show them that they actually re-toggled the setting, and they need to choose &quot;continue&quot; instead to resume the game.</p> <h2>Code organization idiom</h2> <p>The repeated <code>ismuted and &quot;music: off&quot; or &quot;music: on&quot;</code> is suspicious -- can we save some tokens? Yes, by <strong>setting up each menuitem() again at the end of the callback</strong>, just to refresh the labels:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function _init() domenuitems() -- setup all menuitem()s -- ... other game init end function domenuitems() menuitem(1,ismuted and &quot;music: off&quot; or &quot;music: on&quot;,function() ismuted=not ismuted -- ... play/pause the music domenuitems() -- refresh the label return true end) -- menuitem(2,... -- menuitem(3,... end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>I do this for code-organizing reasons -- it's nice to have all the menuitem code in one place -- but it can also save a few tokens, depending on your setup.</p> <h2>Context-dependent menuitems</h2> <p>Some menuitems should only be show in certain situations -- e.g. don't show &quot;back to title&quot; if we're already on the title screen. You could break each menuitem into its own little setup function (like zep's original <code>LOAD #CUSTOM_MENU</code> cart does -- check it out, it's nice!) or do what I do: <code>menuitem(1,condition and &quot;label&quot;,...)</code>. This will only show the menuitem when <code>condition</code> is truthy. I call <code>domenuitems()</code> anytime the menu needs changing (e.g. at the end of <code>init_title_screen()</code>) and it all works out.</p> <h2>Recommendations / Summary</h2> <p>I put all my <code>menuitem()</code> calls inside a custom <code>domenuitems()</code> function. Every callback runs <code>domenuitems()</code> at the end, to refresh the labels.</p> <p>There are three types of menu options I commonly use: <strong>commands, toggles, and selectors</strong>.</p> <ul> <li>For <strong>commands</strong> (run some code, e.g. return to the title screen), I wrap the callback code in a <code>if bb&gt;=16 then ... end</code> block, to prevent L/R from triggering the command</li> <li>For <strong>toggles</strong> (e.g. mute/unmute the game), my callback always has <code>return true</code>, leaving the menu open afterward and avoiding the UX issue described above</li> <li>For <strong>selectors</strong> (choose a number from a range, e.g. change levels), my callback has <code>return bb&lt;16</code>, leaving the menu open only if L/R were used to change the selection. (this is technically unnecessary due to a longstanding <a href="https://www.lexaloffle.com/bbs/?tid=42392">bug(?)</a> in PICO-8's menu handling)</li> </ul> <p>Read the code of the example cart at the top of this post for more info!</p> https://www.lexaloffle.com/bbs/?tid=53822 https://www.lexaloffle.com/bbs/?tid=53822 Mon, 21 Aug 2023 01:02:04 UTC Feature request: commandline exit code on failure <p>I often use shell scripts to export and then upload my games to itch.io, and there's a small inconvenience that trips me up sometimes: If my game is too large, the export fails, but I have no way of detecting that from my shell script.</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>&gt; pico8 game.p8 -export &quot;-f froggypaint.html&quot; EXPORT: -f froggypaint.html failed: code block too large &gt; echo $? 0</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>I would expect the status code (<code>echo $?</code>) to be 1 or some other failure code</p> <p>(Hm, now that I've written this up I suppose I could work around this by reading <del>stderr</del> stdout and checking for the string &quot;failed&quot;...)</p> https://www.lexaloffle.com/bbs/?tid=52848 https://www.lexaloffle.com/bbs/?tid=52848 Wed, 24 May 2023 08:14:05 UTC low-token sort <p>A slow but token-efficient sort:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- 35-token bubblesort, by pancelor function sort(arr) for i=1,#arr do for j=i,#arr do if arr[j] &lt; arr[i] then add(arr,deli(arr,j),i) --slow swap end end end end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>I did a brief speed analysis and this function seems reasonable to use for <strong>arrays up to around 50 or 100 elements</strong>, depending on how much CPU you have available to burn. </p> <p>speed analysis:<br /> <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"><br /> I did some minimal testing, using this code:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>cls() function _draw() arr={ --20,5,8,3,7,4,1,9,2,-30, --20,5,8,3,7,4,1,9,2,-30, --20,5,8,3,7,4,1,9,2,-30, --20,5,8,3,7,4,1,9,2,-30, 20,5,8,3,7,4,1,9,2,-30, } sort(arr) end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>By commenting out lines of the array, I changed it's length. Here's how much CPU the function spent, as a percentage of a single 30fps frame (measured with the ctrl-p monitor, on pico8 0.2.5g)</p> <p>The &quot;best case cpu&quot; was calculated by defining <code>arr</code> outside of <code>_draw</code>, so that the array was already sorted after the first frame</p> <table> <thead> <tr> <th>Array length</th> <th>Typical cpu (30fps)</th> <th>Best case cpu (30fps)</th> </tr> </thead> <tbody> <tr> <td>10</td> <td>0%</td> <td>0%</td> </tr> <tr> <td>20</td> <td>1%</td> <td>1%</td> </tr> <tr> <td>50</td> <td>5%</td> <td>4%</td> </tr> <tr> <td>100</td> <td>21%</td> <td>15%</td> </tr> <tr> <td>200</td> <td>81%</td> <td>58%</td> </tr> <tr> <td>300</td> <td>181%</td> <td>130%</td> </tr> <tr> <td>400</td> <td>321%</td> <td>231%</td> </tr> </tbody> </table> <p>I believe this algorithm is O(n^3): O(n^2) for the two loops, and an additional O(n) b/c the swap step uses <code>add</code> and <code>deli</code>, which shift around the array elements. But the chart seems to indicate that doubling the length will quadruple the cpu cost (instead of octupling it) so maybe it's only O(n^2) somehow? It doesn't much matter, since you don't want to use this for large arrays anyway, but maybe add/deli are cheaper than I would expect.<br /> </div></div></div></p> https://www.lexaloffle.com/bbs/?tid=52478 https://www.lexaloffle.com/bbs/?tid=52478 Fri, 21 Apr 2023 23:04:42 UTC orthogonal line drawing perf <p>mildly interesting: when drawing a perfectly vertical line (<code>line(1,1,1,h)</code>) or perfectly horizontal line (<code>line(1,1,w,1)</code>), it used to be cheaper (in cpu cycles) to use <code>rect</code> or <code>rectfill</code> instead. but not anymore! I'm testing this on 0.2.5g; I'm not sure when this changed.</p> <p><strong>tl;dr: line and rectfill cost the same for orthogonal lines</strong> (rect is more expensive for vertical lines only)</p> <p>simple test:<br /> <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"><br /> <code>load #prof</code>, then add these lines:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- horizontal prof(function() line(0,0,90,0,2) end,function() rect(0,0,90,0,2) end,function() rectfill(0,0,90,0,2) end) -- 9+10=19 (lua+sys) -- 9+10=19 (lua+sys) -- 9+10=19 (lua+sys) -- vertical prof(function() line(0,0,0,90,2) end,function() rect(0,0,0,90,2) end,function() rectfill(0,0,0,90,2) end) -- 9+10=19 (lua+sys) -- 9+22=31 (lua+sys) -- 9+10=19 (lua+sys)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <p>full test:<br /> <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function one(fn,opts) local fmt=function(n) -- leftpad a number to 2 columns return n&lt;9.9 and &quot; &quot;..n or n end local dat=prof_one(fn,opts) printh(fmt(dat.lua) ..&quot;+&quot; ..fmt(dat.sys) ..&quot;=&quot; ..fmt(dat.total) ..&quot; (lua+sys)&quot;) end function hline(x) line(0,0,x,0,2) end function hrect(x) rect(0,0,x,0,2) end function hrfil(x) rectfill(0,0,x,0,2) end function vline(x) line(0,0,0,x,2) end function vrect(x) rect(0,0,0,x,2) end function vrfil(x) rectfill(0,0,0,x,2) end printh&quot;--&quot; for i=0,127 do printh(i..&quot;: &quot;) one(hline,{locals={i}}) one(hrect,{locals={i}}) one(hrfil,{locals={i}}) one(vline,{locals={i}}) one(vrect,{locals={i}}) one(vrfil,{locals={i}}) end --[[ ... 14: 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 15: 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 16: 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 4=13 (lua+sys) 9+ 2=11 (lua+sys) 17: 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 2=11 (lua+sys) 9+ 4=13 (lua+sys) 9+ 2=11 (lua+sys) ... ]]</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>summary:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>rect: when h is 1, sys cost is: max(1,w\16)*2 (agrees with CPU wiki page) when w is 1, sys cost is: max(1,(h-1)\8)*2 (disagrees? unsure) line and rectfill a,b = max(w,h),min(w,h) when b is 1, sys cost is: max(1,a\16)*2 (agrees with CPU page for rectfill, but not line(?))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> https://www.lexaloffle.com/bbs/?tid=52166 https://www.lexaloffle.com/bbs/?tid=52166 Sat, 25 Mar 2023 03:47:18 UTC the tower <p> <table><tr><td> <a href="/bbs/?pid=125012#p"> <img src="/bbs/thumbs/pico8_thetower-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=125012#p"> thetower</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=125012#p"> [Click to Play]</a> </td></tr></table> </p> <p><em>welcome to the tower</em><br /> <em>welcome to the tower</em><br /> <em>welcome &zwj; to the tower</em><br /> <em>welcome to the tower</em></p> <h2>controls</h2> <ul> <li>arrow keys: walk, interact</li> <li>enter: pause menu (volume, toggle effects, etc)</li> </ul> <p>the game will autosave your progress each floor</p> <h2>credits</h2> <p>made by <a href="https://tallywinkle.itch.io/">tally</a> (art, room design, dialog) and <a href="http://pancelor.com/website2022-12/">pancelor</a> (code, game design, music, dialog)</p> <p>pixel art created with tiles from <a href="https://teaceratops.itch.io/1-bit-tileset">teaceratops</a> and <a href="https://1bityelta.itch.io/pokelike-tileset">yelta</a></p> <p>built with pico-8, <a href="https://www.aseprite.org/api/">aseprite</a>, <a href="https://www.lexaloffle.com/bbs/?pid=px9#p">PX9</a>, and <a href="https://github.com/thisismypassport/shrinko8">shrinko8</a></p> <h2>changelog</h2> <p><div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <ul> <li>thetower-1: added title screen</li> <li>thetower-0: initial release<br /> </div></div></div></li> </ul> https://www.lexaloffle.com/bbs/?tid=51413 https://www.lexaloffle.com/bbs/?tid=51413 Sat, 18 Feb 2023 13:09:04 UTC custom font character height newline bug <p>switching between custom and default fonts (<code>?&quot;\015&quot;</code>) behaves a bit strangely; the first line of text is spaced differently from the rest, depending on whether:</p> <ol> <li>custom fonts are enabled by default (<code>poke(0x5f58,0x81)</code>), and</li> <li>the line height (<code>peek(0x5602)</code>) is more or less than 6 (the size of the default font)</li> </ol> <p>run this cart to see what I mean:</p> <p> <table><tr><td> <a href="/bbs/?pid=125013#p"> <img src="/bbs/thumbs/pico8_kahedapibi-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=125013#p"> kahedapibi</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=125013#p"> [Click to Play]</a> </td></tr></table> </p> <p>specifically:</p> <ol> <li>when the custom font is enabled by default and the custom font's height is more than 6, the default-font text in this cart has a large gap between the first and second lines:</li> </ol> <img style="margin-bottom:16px" border=0 src="/media/27691/temp_3.png" alt="" /> <ol start="2"> <li>when the custom font is not enabled by default and the custom font's height is less than 6, the custom-font text in this cart has a large gap between the first and second lines:</li> </ol> <img style="margin-bottom:16px" border=0 src="/media/27691/temp_2.png" alt="" /> <p>I didn't test how <code>?&quot;\^y&quot;</code> affects things.</p> <p>my system info: 64-bit linux, pico8 0.2.5e</p> https://www.lexaloffle.com/bbs/?tid=51414 https://www.lexaloffle.com/bbs/?tid=51414 Mon, 30 Jan 2023 07:21:14 UTC minor weirdness with .. token cost <p>how many tokens should this cart cost?</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>s=&quot;x&quot;..&quot;=&quot; a=1+2</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>in 0.2.1b, it costs 10 tokens (5 for each line). this seems correct to me. however, in 0.2.5e, the first line only costs 4 tokens for some reason.</p> <p>edit: even weirder: <code>s=&quot;x&quot;..&quot;=&quot;</code> costs 4 tokens but <code>s=&quot;x&quot;..&quot;y&quot;</code> costs 5 tokens. it seems like concatenating any string that starts with the equals symbol is 1 token cheaper than it should be; how odd! maybe this is somehow due to the recent parser updates for <code>+=</code> etc?</p> https://www.lexaloffle.com/bbs/?tid=50966 https://www.lexaloffle.com/bbs/?tid=50966 Tue, 03 Jan 2023 07:52:05 UTC twiddler: knobs for livecoding <p>When I'm making games or tweetcarts, I often adjust numbers just a tiny bit, then rerun the whole game. e.g. I change the player speed by 0.1, then change it back, then try 0.05...</p> <p>This is a bit slow, so here's a library I made to help me do it faster. I also find it useful for analyzing other people's tweetcarts -- if I can easily adjust the values they use, I can quickly figure out what they mean</p> <h2>setup</h2> <ul> <li><code>load #twiddler</code></li> <li>copy the <code>knobs.lua</code> + <code>helpers</code> tabs into your game</li> <li>use <code>kn[1]</code>, <code>kn[2]</code>, ... <code>kn[8]</code> in place of any number</li> <li>add <code>twiddler()</code> to the end of your <code>_draw</code> function</li> </ul> <p>Now, run your code: </p> <ul> <li>press tab and adjust the values (see &quot;controls&quot; below)</li> <li>press tab again -- the values will be copied to your clipboard</li> <li>paste the values into the start of your code to save them</li> </ul> <h2>example 1</h2> <p>Start with a tweetcart you want to study. For example, this one by 2DArray: <a href="https://twitter.com/2DArray/status/1492566780451205120">https://twitter.com/2DArray/status/1492566780451205120</a></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>?&quot;\^!5f10◆█🐱░4웃9&hearts;&quot; ::_::?&quot;⁶1⁶c&quot; w=t()/8 c=cos(w) s=sin(w) n=1800 for i=0,n do a=i*.618 j=1-i/n*i/n y=1+j*j-2*j*j*j r=1-(1-j)^3+sin(a*10)/9 p=(6+sin(a*10)*(.5+r/2))*(.7+y*y/3) circfill(64+cos(a+w)*r*20,80-y*30+sin(a+w)*r*12,2,p) end goto _</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Add the knobs, and replace some interesting-looking numbers with knobs:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>kn={.5,.7,.618,2,0,0,0,0} --changed ?&quot;\^!5f10◆█🐱░4웃9&hearts;&quot; ::_::?&quot;⁶1⁶c&quot; w=t()/8 c=cos(w) s=sin(w) n=1200 --changed -- twiddler needs a little bit of cpu to run for i=0,n do a=i*kn[3] --changed j=1-i/n*i/n y=1+j*j-2*j*j*j r=1-(1-j)^3+sin(a*10)/9 p=(6+sin(a*10)*(kn[1]+r/2))*(kn[2]+y*y/3) --changed circfill(64+cos(a+w)*r*20,80-y*30+sin(a+w)*r*12,kn[4],p) --changed end twiddler() --changed goto _</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Now run the code and play around! looks like knobs 1 and 2 control the color gradient, knob 3 controls the spiralness of the circles. cool! Add new knobs, remove old knobs, keep going until satisfied.</p> <img style="margin-bottom:16px" border=0 src="/media/27691/temp_0.gif" alt="" /> <h2>example 2</h2> <p>This process works on your own code that you're in the middle of writing, too. Write <code>kn[1]</code> etc instead of a number, open the knobs panel, and edit the value until it looks right:</p> <img style="margin-bottom:16px" border=0 src="/media/27691/tweet-dev.gif" alt="" /> <p>This is from the dev process of a recent tweetcart of mine (<a href="https://twitter.com/pancelor/status/1583765792004640769?s=20&amp;amp;t=Yh6RYSIz43DsQoc3UB3__g">twitter</a>, <a href="https://cohost.org/pancelor/post/146796-adrift">cohost</a>)</p> <h3>controls:</h3> <p>Press Tab or W to open/close the knob panel</p> <ul> <li>LMB - change slider value</li> <li>MMB - pan slider range</li> <li>RMB - cancel current interaction</li> <li>wheel - zoom slider range</li> </ul> <p>When you close the panel (Tab/W), knob values will be copied to your clipboard -- paste them into your code to save them.</p> <p> <table><tr><td> <a href="/bbs/?pid=119729#p"> <img src="/bbs/thumbs/pico8_twiddler-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=119729#p"> twiddler</a><br><br> by <a href="/bbs/?uid=27691"> pancelor</a> <br><br><br> <a href="/bbs/?pid=119729#p"> [Click to Play]</a> </td></tr></table> </p> <p>When you're done with the knobs, you can replace <code>kn[1]</code> etc with the literal value of the knobs, and delete the twiddler code. This is a tool meant for development, not meant to be used during a finished game. (although, a finished game that used something like this would be pretty nifty!)</p> https://www.lexaloffle.com/bbs/?tid=49949 https://www.lexaloffle.com/bbs/?tid=49949 Fri, 28 Oct 2022 22:11:27 UTC