Mot [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=39676 Formula 1 practice lap <p> <table><tr><td> <a href="/bbs/?pid=87611#p"> <img src="/bbs/thumbs/pico8_mot_formula1-4.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=87611#p"> mot_formula1</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=87611#p"> [Click to Play]</a> </td></tr></table> </p> <p>Strap into one of the world's fastest race cars and blast around the legendary Bronzerock circuit like it's 1988!<br /> ...or something - there's not a lot of physical or historical accuracy in this game :)</p> <p>Z/X=accelerate/brake<br /> Left/right=steering</p> <p>Driving off road slows you down. Driving too fast around corners causes you to slide.</p> <p>You can use the pause menu to view your last/best lap times (my best so far is 1:43.23)</p> <p>This is work-in-progress, so some caveats:</p> <ul> <li>There are no other cars yet.</li> <li>Lots of things need tweaking/adjusting.</li> </ul> https://www.lexaloffle.com/bbs/?tid=41554 https://www.lexaloffle.com/bbs/?tid=41554 Sun, 14 Feb 2021 03:01:03 UTC F1 racing game <p> <table><tr><td> <a href="/bbs/?pid=87276#p"> <img src="/bbs/thumbs/pico8_mot_f1-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=87276#p"> F1 racing game</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=87276#p"> [Click to Play]</a> </td></tr></table> </p> <p>This is very unfinished and I wasn't planning to release this just yet, but somebody asked to play it so here it is :-)</p> <p>Z/X accel/brake<br /> Left/right to steer</p> <p>Eventually there will be consequences for leaving the road and crashing into things.</p> https://www.lexaloffle.com/bbs/?tid=41423 https://www.lexaloffle.com/bbs/?tid=41423 Fri, 05 Feb 2021 21:12:03 UTC Mot's Wolf3D engine <p> <table><tr><td> <a href="/bbs/?pid=86903#p"> <img src="/bbs/thumbs/pico8_mot_wolf3d-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=86903#p"> Mot's Wolf-3D engine</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=86903#p"> [Click to Play]</a> </td></tr></table> </p> <p>This isn't really a game - unless you consider it a short &quot;Walking simulator&quot; - it's more of a tech demo.</p> <p>The engine is a basic Wolfenstein-3D like 3D engine. It has floor and ceiling textures and render reasonably sized and complex rooms at 60 frames-per-second, in a 128x96 viewport.</p> <ul> <li>Arrow keys = move</li> <li>X = toggle map mode</li> </ul> <p>If anyone feels like something out of it, it's fairly easy to get started with (details below).</p> <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;"><br /> Levels are built using the map editor, using the sprites on page 1.</p> <img style="" border=0 src="/media/39676/mot3dsspr p8_000.png" alt="" /> <p>The bottom left sprites are walls, except the left-most one which positions a door.<br /> The next row up is for placing objects.<br /> The numbered circles are for placing triggers that trigger code when the player reaches them.<br /> The gray arrows at the top are for setting the player start position and direction.</p> <p>You can use the top left 124x32 tiles of the map area.</p> <p>Wall textures (and door texture) are sprite tabs 2 and 3.</p> <img style="" border=0 src="/media/39676/mot3dsspr p8_001.png" alt="" /> <p>You can define up to 8 (including the door).</p> <p>Objects are sprite tab 4.</p> <img style="" border=0 src="/media/39676/mot3dsspr p8_003.png" alt="" /> <p>They are always 16x16 pixels. You can define up to 16.</p> <p>Objects must be defined in the &quot;otyp&quot; array (code tab 1):</p> <div> <div style="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>otyp={ -- y h w solid flat { .33,.4, .5, true}, {-.36,.25,.25,false}, { 0, 1, .3, true}, { .5,.45, .7, false,true}, {.375,.5, .7, true}, { -.3,.4, .3, false}, { .3,.35, .4, true}, { .5,.45, .8, false,true}, { .1, .8, .4, true}, { .2, .6, .6, true} }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <ul> <li>y = y position (-0.5 = ceiling, 0.5 = floor)</li> <li>h = height</li> <li>w = width</li> <li>solid = true if object will obstruct player's movement</li> <li>flat = true to flatten object to floor/ceiling</li> </ul> <p>Floor and ceiling textures are defined at the very right of the map.</p> <img style="" border=0 src="/media/39676/mot3dsspr p8_004.png" alt="" /> <p>Floor and ceiling &quot;plane types&quot; must also be defined in code tab 1:</p> <div> <div style="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>-- plane types -- tex scale height xvel yvel pl_tile ={ 0, 0.5 } pl_panel={ 1, 0.5 } pl_dirt ={ 2, 0.125 } pl_stone={ 3, 0.25 } pl_sky ={ 4, 7, 10, .007,.003}</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <ul> <li>tex = Which &quot;texture&quot; to use. 0 = topmost.</li> <li>scale = Texture scale factor.</li> <li>height = Optional. Set the plane height, e.g. for sky textures. Otherwise defaults to floor/ceiling height.</li> <li>xvel,yvel = Optional. Creates moving planes.</li> </ul> <p>You then select the floor and ceiling planes by setting the &quot;floor&quot; and &quot;roof&quot; variables.</p> <div> <div style="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>floor={ typ=pl_dirt, x=0,y=0 } roof ={ typ=pl_sky, x=0,y=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></div></div></div></p> https://www.lexaloffle.com/bbs/?tid=41315 https://www.lexaloffle.com/bbs/?tid=41315 Fri, 29 Jan 2021 07:19:44 UTC Flight simmy <p> <table><tr><td> <a href="/bbs/?pid=84733#p"> <img src="/bbs/thumbs/pico8_mot_flight-3.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=84733#p"> mot_flight</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=84733#p"> [Click to Play]</a> </td></tr></table> </p> <p>A little arcady &quot;flight sim&quot; I started writing.<br /> Buzz past randomly generated islands in a wrap-around ocean.</p> <p>I will probably add stuff to blow up eventually :)</p> <p>There is no throttle! Just steer with arrows.</p> https://www.lexaloffle.com/bbs/?tid=40572 https://www.lexaloffle.com/bbs/?tid=40572 Fri, 27 Nov 2020 07:47:28 UTC Mot's 8-Ball Pool <p> <table><tr><td> <a href="/bbs/?pid=82696#p"> <img src="/bbs/thumbs/pico8_mot_pool-23.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=82696#p"> Pool</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=82696#p"> [Click to Play]</a> </td></tr></table> </p> <p>This is <strong>Mot's 8-Ball Pool</strong>, a little pool simulation inspired by 3D pool on the C64 and Amiga.<br /> Shoot a round of pool against a friend, or one of the 7 different AI characters.</p> <p><img style="" border=0 src="/media/39676/pool3d_37.gif" alt="" /> <img style="" border=0 src="/media/39676/pool3d_35.gif" alt="" /> <img style="" border=0 src="/media/39676/pool3d_38.gif" alt="" /></p> <h3>UI</h3> <p>The UI at the bottom of the screen shows how each player is progressing.<br /> The player's name flashes when it is their turn.<br /> The color they must sink is displayed next to their name, and the balls already sunk are displayed above.<br /> A white ball indicates they have a free ball.</p> <img style="" border=0 src="/media/39676/pool3d_006.png" alt="" /> <p>When player fouls, the reason for the foul is displayed in a scrolling message along the bottom of the screen.</p> <img style="" border=0 src="/media/39676/pool3d_007.png" alt="" /> <h3>Rules</h3> <p>I'll assume you know the basic rules of 8-ball pool, so I'll just describe how the rules have been implemented.<br /> The game uses a simplified set of rules:</p> <ul> <li>The game ends when the black ball is sunk.</li> <li>If you sink it (legally) after sinking all your colored balls, you win. Otherwise you forfeit and your opponent wins.</li> <li>The first ball sunk becomes that player's color.</li> <li>A legal shot involves hitting a ball of your color first, and not sinking the wrong color, white ball or black ball (except when you're supposed to).</li> <li>A free ball is awarded to the other player after a &quot;foul&quot;. A free ball means you get a free second shot even if you don't sink a ball - as long as your shot was legal.</li> </ul> <p>Hope you enjoy it.<br /> If you're curious, you can see the development progress in <a href="https://twitter.com/tommulgrew/status/1303218536010088448">this Twitter thread</a>.</p> https://www.lexaloffle.com/bbs/?tid=39859 https://www.lexaloffle.com/bbs/?tid=39859 Thu, 08 Oct 2020 08:05:51 UTC Still Movin' <p> <iframe src="sfxp.php?id=39676_0" width="769" height="97" style="border:none; overflow:hidden"></iframe><a style="cursor:pointer; font-size:8pt" onclick=' var el = document.getElementById("sfxcode_39676_0"); if (el.style.display == "none") el.style.display = ""; else el.style.display = "none"; microAjax("/bbs/sfxc/39676_0.txt", function (retdata){ var el = document.getElementById("sfxcode_39676_0"); el.innerHTML = retdata; el.focus(); el.select(); } ); '> [sfx] </a> <textarea rows=3 class=lexinput id="sfxcode_39676_0" style="width:480px;background-color:#fed;display:none;overflow:hidden; font-size:4pt;"></textarea> </p> <p>I felt like transcribing a little tune I made in Caustic to Pico8.<br /> Could use a bit more work (instruments, balancing, is pattern 14 off key?) but I'm happy enough with it.</p> https://www.lexaloffle.com/bbs/?tid=39648 https://www.lexaloffle.com/bbs/?tid=39648 Sun, 20 Sep 2020 04:27:06 UTC Pool <p> <table><tr><td> <a href="/bbs/?pid=81393#p"> <img src="/bbs/thumbs/pico8_mot_pool-22.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=81393#p"> Pool</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=81393#p"> [Click to Play]</a> </td></tr></table> </p> <p>A work-in-progress 3D pool table simulation.</p> https://www.lexaloffle.com/bbs/?tid=39447 https://www.lexaloffle.com/bbs/?tid=39447 Mon, 31 Aug 2020 11:55:35 UTC smap - sspr for maps <p>Not sure if anyone has posted a smap() yet, but it's reasonably straightforward to implement with tline, so here's mine.</p> <p>Parameters are:<br /> <strong>cx,cy,cw,ch</strong> Specify a region in the tile map. Measured in tiles.<br /> <strong>sx,sy,sw,sh</strong> Region on the screen to map to.<br /> <strong>flipx,flipy</strong> Whether to flip horizontally and/or vertically.<br /> <strong>layers</strong> Layer flags. Same as for map().</p> <p>You need to supply at least the c and s parameters.</p> <div> <div style="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 smap(cx,cy,cw,ch,sx,sy,sw,sh,flipx,flipy,layers) -- negative screen sizes if(sw&lt;0)flipx=not flipx sx+=sw if(sh&lt;0)flipy=not flipy sy+=sh sw,sh=abs(sw),abs(sh) -- delta local dx,dy=cw/sw,ch/sh -- apply flip if flipx then cx+=cw dx=-dx end if flipy then cy+=ch dy=-dy end -- clip if(sx&lt;0)cx-=sx*dx sx=0 if(sy&lt;0)cy-=sy*dy sy=0 if(sw&gt;128)sw=128 if(sh&gt;128)sh=128 -- render with tlines -- pick direction that results -- in fewest tline calls if sh&lt;sw then -- horizontal lines for y=sy,sy+sh-1 do tline(sx,y,sx+sw-1,y,cx,cy,dx,0,layers) cy+=dy end else -- vertical lines for x=sx,sx+sw-1 do tline(x,sy,x,sy+sh-1,cx,cy,0,dy,layers) cx+=dx 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> <table><tr><td> <a href="/bbs/?pid=79752#p"> <img src="/bbs/thumbs/pico8_mot_smap-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=79752#p"> mot_smap</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=79752#p"> [Click to Play]</a> </td></tr></table> </p> https://www.lexaloffle.com/bbs/?tid=38931 https://www.lexaloffle.com/bbs/?tid=38931 Wed, 22 Jul 2020 05:44:11 UTC Mot's Animation System <h1>Mot's Animation System</h1> <p> <table><tr><td> <a href="/bbs/?pid=79085#p"> <img src="/bbs/thumbs/pico8_mot_animsys-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=79085#p"> Mot's Animation System</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=79085#p"> [Click to Play]</a> </td></tr></table> </p> <p><strong>Mot's Animation System</strong> is a visual editor for creating 2D animations.</p> <p>You start by sequencing sprites and/or tilemaps together to create the basic components. Then combine them using timelines and key-frames to make animations. Or combine them again to make bigger, more complex animations.</p> <p>Animations can trigger sounds and music, and call back into to your main program, e.g. to trigger game play events.</p> <p>You could use it for:</p> <ul> <li>Character animations, like run cycles</li> <li>Larger multi-sprite animated characters</li> <li>Cutscenes</li> <li>Animated birthday cards</li> </ul> <p>Animations can be loaded and played in your own carts (the loading and playback code is 1836 tokens). They can either be stored in Lua strings or loaded from a separate cart.</p> <h3>Disclaimer</h3> <p><strong>This is all very much in Beta at the moment!</strong>. And some corners had to be cut to fit everything into the token limit. In particular the editor doesn't prevent you from shooting yourself in the foot by:</p> <ul> <li>Creating self referencing animations (infinite recursion)</li> <li>Creating animation files larger than 0x4300 bytes (about 17K) that won't save properly.</li> </ul> <p>It lacks any sort of undo/redo logic, so if you use this, please take frequent backups of your saved animations.</p> <p>And also, because the animation code is 1836 tokens and the animation data can be several kilobytes, it's not the best fit for every program.</p> <p>Feel free to @ me if you need any help.</p> <h1>Overview and basic navigation</h1> <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;"><br /> <em>WARNING: While you can edit animations in the nested browser version <strong>you can't save them!</strong> So please don't get too invested if you play with it in-browser. Run it from Pico-8 itself in order to save your changes.</em></p> <h2>Selecting and playing animations</h2> <p>Move the mouse over the window to display the UI.</p> <img style="" border=0 src="/media/39676/mas_002.png" alt="" /> <p>You can pan the animation with the middle mouse button, and zoom in/out with the scroll wheel.<br /> At the bottom right there are 3 controls:</p> <ul> <li>A slider to zoom in/out (behaves exactly the same as the mouse wheel)</li> <li>A play/pause button that does what you'd expect</li> <li>A home (house) button recenters the animation and sets the scale back to 1</li> </ul> <p>The name of the current animation is displayed at the top. Click on it to select another animation.</p> <img style="" border=0 src="/media/39676/mas_003.png" alt="" /> <p>Use the mouse wheel to scroll through the animations. Animations with a person icon <img style="" border=0 src="/media/39676/manicon.PNG" alt="" /> are sprite/tilemap animations. Animations with a dotted line icon <img style="" border=0 src="/media/39676/dots.png" alt="" /> are key frame based animations.</p> <h2>Menus</h2> <p>At the top right are the menu icons. Clicking on them opens a drop down menu.</p> <p>The hamburger menu is for creating/deleting animations.</p> <img style="" border=0 src="/media/39676/animmenu.PNG" alt="" /> <p>You can create new animations of different types, or delete the current animation.</p> <p>The hourglass menu is for saving and loading animation sets.</p> <img style="" border=0 src="/media/39676/filemenu.PNG" alt="" /> <p>You can save/load your animations to from external carts or import sprites and tilemaps from another cart.</p> <p>This is covered in more detail later.</p> <h2>Animation properties</h2> <p>Click &quot;Properties&quot; at the bottom of the screen to expand the properties panel.<br /> This is where you edit the current animation. The UI changes based on the animation type.</p> <img style="" border=0 src="/media/39676/mas_005.png" alt="" /> <p>When expanded, the &quot;Properties&quot; bar also displays two icons. These toggle between different tabs.<br /> The I icon <img style="" border=0 src="/media/39676/infoicon.PNG" alt="" /> displays information and settings for the animation. Once again they differ based on the animation type. The other icon will be the person or dotted line depending on the animation type, and switches back to the main editor.<br /> </div></div></div></p> <h1>Creating animations</h1> <h2>Importing graphics</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> <p>(...and music and sound effects)</p> <p>In order to create animations for your own carts, you'll want to import the sprites and tilemaps that your cart uses.<br /> Click the hourglass icon <img style="" border=0 src="/media/39676/hourglassicon.PNG" alt="" /> at the top right, then select &quot;IMPORT GFX&quot;. This will list the carts in your Pico-8 carts folder.</p> <img style="" border=0 src="/media/39676/mas_006.png" alt="" /> <p>Once again you can scroll with the mouse wheel.<br /> Click on the cart to import its sprites, tile maps, sound effects and music into the editor.</p> <p></div></div></div></p> <h2>Clearing animations</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;"><br /> Select &quot;Clear&quot; from the hourglass menu <img style="" border=0 src="/media/39676/hourglassicon.PNG" alt="" /> to clear out the demo animations.<br /> </div></div></div></p> <h2>Saving and loading</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> <p>The hourglass menu <img style="" border=0 src="/media/39676/hourglassicon.PNG" alt="" /> allows you to save and load your animations. Animations are saved to Pico-8 cartridges (&quot;carts&quot;).<br /> They overwrite the sprites in the cart, and possibly the tile maps, sfx and music, depending on how many animations you have defined. Therefore you should <strong>not</strong> save your animations into your existing game carts!</p> <p>To avoid catastrophe the editor will only save/load animation data into cart files that end with .anim.p8</p> <img style="" border=0 src="/media/39676/filemenu.PNG" alt="" /> <p>&quot;Save&quot;, &quot;Save as&quot; and &quot;Load&quot; work as you would expect.</p> <p>The first time you save (or if you click &quot;Save as&quot;) you will be prompted with a list of animation carts to save into.</p> <img style="" border=0 src="/media/39676/mas_016.png" alt="" /> <p>Or you can click &quot;New Cart&quot; and key in the name of a new cart to create.</p> <img style="" border=0 src="/media/39676/mas_017.png" alt="" /> <p>Enter the name <em>without</em> the .p8 extension, as the editor will add .anim.p8 automatically.</p> <p><strong>WARNING: The editor can only save 0x4300 bytes of animation data to a cart (about 17K). If your animations grow beyond this limit, they will not save correctly and may not reload either.</strong><br /> You can keep tabs on this by running Pico-8 in console mode. The number of bytes written is reported in the console when you save, so you can see when it gets near the 17K limit (for reference, the demo animation is about 3.8K).<br /> Taking frequent backups of your animation file is strongly advised.</p> <p></div></div></div></p> <h2>Sprite animations</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> <p>Sprite (and tile map) animations are the building blocks of all animated sequences.<br /> Create a sprite animation by clicking the hamburger icon <img style="" border=0 src="/media/39676/animmenuicon.PNG" alt="" /> and selecting &quot;NEW SPRITE ANIM&quot;.<br /> <em>To edit the animation, make sure the &quot;Properties&quot; section is expanded.</em></p> <img style="" border=0 src="/media/39676/mas_008.png" alt="" /> <p>This UI allows you to define a sequence of frames, each of which displays a sprite.<br /> The frames of the animation are displayed horizontally.</p> <img style="" border=0 src="/media/39676/spriteframes.PNG" alt="" /> <p>Click on a frame to select it.<br /> If the animation has more than 5 frames, click the rightmost frame to scroll right and the leftmost to scroll left.</p> <p>The &quot;+&quot; and &quot;-&quot; buttons add and delete frames respectively. Adding always creates a new frame at the end of the animation. Alternatively you can insert a new frame immediately after the selected frame by clicking &quot;Ins&quot;.</p> <p>The bottom of the editor shows the available sprites.</p> <img style="" border=0 src="/media/39676/sprites.PNG" alt="" /> <p>As in Pico-8 itself, sprites are grouped into 4 pages, with page number &quot;tabs&quot; at the top right.<br /> To set the sprite for a particular frame, first click on the frame, and then click on the sprite in the sprites area.<br /> The sprite for the current frame is displayed with a white rectangle.</p> <p>You can also flip a frame horizontally or vertically by ticking &quot;Flip X&quot; and &quot;Flip Y&quot; respectively while the frame is selected.</p> <h3>Information and settings</h3> <p>Click the I icon <img style="" border=0 src="/media/39676/infoicon.PNG" alt="" /> on the &quot;Properties&quot; bar to display the sprite animation information and settings.</p> <img style="" border=0 src="/media/39676/mas_011.png" alt="" /> <p>The <strong>Name</strong> allows you to give your animation a name, so you can find it more easily in the animation chooser.<br /> You should try to choose short names in order to save space and so that they display properly in the animation list. (Ideally the editor would enforce this but it doesn't have enough spare tokens.)<br /> The name is also used to fetch animations when rendering them in your own carts, so it is a good idea to give each animation a unique one.</p> <p>The <strong>Type</strong> allows you to choose between a sprite or tile map based animation. (Tile map animations are described in the next section.)</p> <p><strong>FPS</strong> is the frames-per-second speed of the animation.<br /> The value is set using a &quot;number editor&quot; box. Click and drag left/right to decrease/increase the value. Click and drag with the right mouse button to decrease/increase 10x faster.</p> <p><strong>Size</strong> is the horizontal (left box) and vertical (right box) size of each frame in sprites (or tile map tiles, if using a tile map animation).</p> <p>The <strong>Origin</strong> defines the origin point of the animation. If the animation is rendered at coordinates X,Y then the origin is the part of the animation that will align with that position on screen. It is also the part of the animation that stays in the same place if you scale it larger or smaller. It's stored as two values ranging from 0 to 1, representing the fraction of the animation's width and height. So (0,0) sets the top left corner as the origin. (1,1) sets the bottom right corner. (0.5,0.5) sets the center. </p> <img style="" border=0 src="/media/39676/origin.png" alt="" /> <p>Hover the mouse over the animation preview area to see the origin point (displayed as a gray cross)</p> <p></div></div></div></p> <h2>Tile map animations</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> <p>Tile map animations are special sub class of sprite animations, where the animation frames are defined as sections of the tile map. They are useful for creating larger &quot;sprite&quot; animations, or to define backgrounds for animated scenes.</p> <p>To create a tile map animation, first create a sprite animation (as described above), click the I <img style="" border=0 src="/media/39676/infoicon.PNG" alt="" /> to bring up the information/settings and click &quot;Tiles&quot;.<br /> Typically you will want to set &quot;Size&quot; here as well (to the width and height of the animation in tiles).</p> <p>Click the person icon <img style="" border=0 src="/media/39676/manicon.PNG" alt="" /> to edit the tile map frames.</p> <img style="" border=0 src="/media/39676/mas_012.png" alt="" /> <p>The UI is similar to defining sprite frames UI. The main difference is that the tile map is displayed instead of the sprite pages. Use the middle mouse button to drag the tile map around, so that you can select the region that corresponds to your animation frame.</p> <p>(The UI is unfortunately a bit cumbersome and would have benefited from a mouse-wheel zoom. But it's usable.)</p> <p></div></div></div></p> <h2>Timeline animations</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> <p>This is the main feature of the animation system: the ability to create animations defined by key frames on a timeline.<br /> Timeline animations are built from other animations, which are often sprite animations but can be other timeline animations as well. In this section we will refer to the other animations as &quot;child animations&quot;.<br /> The key frames specify which animations to play at which positions, plus other attributes like scale and flip flags. The animation system interpolates between these values based to create the animation.</p> <p>Create a timeline animation by clicking the hamburger icon <img style="" border=0 src="/media/39676/animmenuicon.PNG" alt="" /> and selecting &quot;NEW TIMELINE ANIM&quot;.</p> <img style="" border=0 src="/media/39676/mas_014.png" alt="" /> <p>The timeline animation UI has two main areas.</p> <h3>Timelines and key frames</h3> <p>The left area contains the timelines and key frames.</p> <img style="" border=0 src="/media/39676/timelines.PNG" alt="" /> <p>The blue top line is for triggering events, like sound effects. This is covered later on.</p> <p>The gray lines are timelines. Each one displays a single &quot;child&quot; animation. You can include more child animations by creating more timelines, using the buttons at the bottom.</p> <img style="" border=0 src="/media/39676/tilemap buttons.PNG" alt="" /> <p>The + and - buttons add and delete timelines respectively.<br /> Use the up and down arrows to re-order the timelines. This controls the order that the child animations are drawn on screen. Timelines are drawn from top to bottom, so the topmost timeline's animation will appear behind the others, and the bottom timeline's animation will appear in front.</p> <p>The purple dots on the timeline are key frames.<br /> These can be added and deleted with the key frame buttons:</p> <img style="" border=0 src="/media/39676/keyframe buttons.PNG" alt="" /> <p>To insert a key frame, first click on the timeline at the position corresponding to the desired time. Then click +.<br /> <em>Note: If key frames are close together it may be difficult or impossible to click on the timeline between them without selecting one of the existing ones. The work around is to click + to create the new key frame anyway, then move it to the desired time.</em></p> <p>To delete a key frame, click on it to select it, then click -.</p> <h3>Key frame properties</h3> <p>The right hand side displays the properties of the selected key frame.</p> <img style="" border=0 src="/media/39676/keyframeprops.PNG" alt="" /> <p>Use the number box at the top to set the key frame time on the timeline (drag left/right with the mouse).<br /> (I would have preferred to implement this as dragging the key frame along the timeline, but you know... tokens.)</p> <p>The key frame properties are grouped into two tabs, which are accesed by clicking the icons to the right of the number box.</p> <p>The first tab (<img style="" border=0 src="/media/39676/manicon.PNG" alt="" />) has the following properties:</p> <p><strong>Anm</strong> selects which child animation to play. Click on the animation name to select it from the list. The child animation will play from this key frame onward. If &quot;None&quot; is selected, the key frame will not change the child animation.</p> <p><strong>Loop</strong> indicates whether the child animation will loop. If not it will stop once it has played to the end (or start if playing backwards).</p> <p><strong>T</strong> and <strong>Spd</strong> set the time and speed of the child animation. You can use these to start the child animation half way through, or play it in reverse, or double speed etc.<br /> T and Spd are <em>optional</em> properties. This means you can choose not to specify them on a particular key frame, which is useful if you want to add a key frame to change a <em>different</em> property (say the on-screen position). Optional properties are controlled by a tick box on the right hand side which must be ticked in order to enter a value.<br /> For T and Spd there is only one tick box, as you must either set them both, or none at all.</p> <p><strong>Visible</strong> indicates whether the child animation is visible from this key frame onward.</p> <p>Click the <img style="" border=0 src="/media/39676/props2icon.PNG" alt="" /> icon to display the second tab.</p> <img style="" border=0 src="/media/39676/keyframeprops2.PNG" alt="" /> <p>It has the following properties:</p> <p><strong>Pos</strong> sets the position of the child animation as a X and Y coordinates, relative to the center of the current time line animation. If you render the animation in the center of the screen (like the editor does by default), then 0,0 will position the child animation there.<br /> Pos is an optional property.<br /> It is also an <em>interpolated</em> property. As the animation plays the position will be linearly interpolated between the values supplied on the key frames, so that the child animation moves smoothly from one position to the next.</p> <p><strong>Sca</strong> sets the horizontal and vertical scale factor of the child animation. A value of 1 means no scaling.<br /> Sca is an optional interpolated property.</p> <p><strong>Flp</strong> flips the child animation horizontally and/or vertically from the current key frame onward.</p> <p><em>Note: The <strong>Visible</strong> property is also available on this tab page. It is the same property as on the other page.</em></p> <h3>Information and settings</h3> <p>As with sprite animations you can click the I icon <img style="" border=0 src="/media/39676/infoicon.PNG" alt="" /> to display the information and settings. </p> <img style="" border=0 src="/media/39676/mas_015.png" alt="" /> <p>Refer to the sprite animation section regarding the <strong>Name</strong> property.</p> <p>The <strong>Duration</strong> allows you to specify the animation duration in seconds.</p> <p></div></div></div></p> <h2>Storyboard animations</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> <p>A storyboard animation allows you to play a set of animations one after the other. It is useful for stitching together smaller scenes, like the scenes that make up the demo animation. The editor ensures that each animation starts immediately after the previous one finishes, and adjusts it appropriately in response to duration changes.</p> <p>To create a storyboard animation, click the hamburger icon <img style="" border=0 src="/media/39676/animmenuicon.PNG" alt="" /> and select &quot;NEW STORYBOARD ICON&quot;.</p> <img style="" border=0 src="/media/39676/mas_018.png" alt="" /> <p>The storyboard animation UI is essentially a cut down version of the timeline animation UI.There is no events timeline and only one animation timeline, and key frames are restricted to a subset of properties.</p> <p>Create the storyboard by adding key frames to the single timeline and setting the &quot;Anm&quot; property to the animation that should play.</p> <p>Unlike timeline animations, you cannot set the key frame time directly. Instead there are two new key frame buttons that allow you to control their order.</p> <img style="" border=0 src="/media/39676/storyboardkeyframebuttons.PNG" alt="" /> <p>Click the left button to move the key frame to towards the start of the sequence. The right button moves it towards the end.</p> <p></div></div></div></p> <h2>Animation events</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> <p>Timeline animations can also trigger events. These can be used to play sound effects and music, or display text on the screen. You can also create your own event types and handle them in your own carts.</p> <p>Events are created by inserting key frames into the blue top-most timeline.</p> <img style="" border=0 src="/media/39676/mas_019.png" alt="" /> <p>The <strong>Typ</strong> property specifies the event type.<br /> The remaining properties are data to supply to the event handler. If an event handler doesn't use a particular property, you can choose to omit it by leaving its right-hand tick-box unticked (which saves a byte or two in the animation data).</p> <p>Click on the value to select the event type.</p> <img style="" border=0 src="/media/39676/mas_020.png" alt="" /> <p>The editor displays the built-in event types that it understands. Or you can click &quot;Custom&quot; to create your own event type. Custom event types are ignored by the editor, but you can implement event handlers in your own carts (this is described later on).<br /> You should keep your custom event names short, as the name string is stored in each event.</p> <p>The built-in events are as follows:</p> <p><strong>SFX</strong> plays a sound effect.<br /> n=The sound effect number.</p> <p><strong>MUS</strong> starts/stops music.<br /> n=The starting pattern. Omitting it (unticking the right tick-box) indicates that the music should stop.</p> <p><strong>TXT</strong> displays text on the screen.<br /> txt=The text to display<br /> pos=The screen coordinates<br /> n=The number of frames the text will remain on screen</p> <p><strong>RELTXT</strong> displays text on screen relative to the current animation.<br /> This is exactly the same as <strong>TXT</strong> except that &quot;pos&quot; is relative to the center of the current animation.</p> <p><strong>CLR</strong> clears all text from the screen.</p> <p></div></div></div></p> <h1>Playing animations in your own carts</h1> <h2>Loading and playback code</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;"><br /> In order to load and playback animations from your own carts, you will need the loading and playback code.<br /> All this code is in tab 1 of the &quot;Mot's Animation System&quot; cart, and needs to be copied into your cart. Be aware that it will cost you 1836 tokens.<br /> </div></div></div></p> <h2>Loading from an external cart</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> <p>Loading animations from an external cart means that your cart will not be self-contained, and may prevent it from working when uploaded onto the Lexaloffle BBS. However it's relatively easy to setup, and makes for a streamlined workflow, where you save your changes in the editor and simply rerun your cart to load them.</p> <p>You might want to use this method while developing your cart, and then switch to the embedded Lua string method (described later) once you're ready to release it.</p> <p>To load from an external cart:</p> <div> <div style="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> -- load into gfx ram, then decode reload(0x0,0x0,0x4300,&quot;masintro.anim.p8&quot;) local stream=readmemstream(0x0) anims=loadanims(stream) -- restore gfx ram reload(0,0,0x4300,gfxcart) -- extract animation to play anim=anims[&quot;intro&quot;]</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This copies the animation data into memory, and deserializes it into a Lua table.<br /> The process overwrites the sprites and possible tile maps, sfx and music, so the &quot;reload&quot; is necessary to copy them back in from the cartridge ROM.<br /> Individual animations can then be fetched by name.</p> <p></div></div></div></p> <h2>Rendering an animation</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> <p>To render an animation, call the &quot;draw&quot; method:</p> <div> <div style="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> anim:draw(t)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Where <strong>t</strong> is the animation time in seconds.</p> <p>By default the animation will not loop, but you can use the &quot;duration&quot; method to achieve this if necessary.</p> <div> <div style="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> anim:draw(t%anim:duration())</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 full list of parameters for &quot;draw&quot; is:<br /> t=The animation time<br /> x,y=The position on screen (defaults to 64,64)<br /> xscale,yscale=The horizontal and vertical scale factors (defaults to 1,1)<br /> flipx,flipy=Whether to flip horizontally or vertically (defaults to false,false)<br /> dt=The delta time from the last frame (defaults to 1/30 or 1/60 depending on whether _update or _update60 is defined)</p> <p>The delta time is used to trigger events, and tells the animation system how far the animation has advanced since the previous frame. The animation system will trigger all events on the timeline between then and the specified time.</p> <p></div></div></div></p> <h2>Drawing text</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> <p>If an animation contains &quot;txt&quot; or &quot;reltxt&quot; events, this text is not rendered immediately by the &quot;draw&quot; method, but queued up to be rendered later. This this allows you to render one or more animations then draw the text last so that it appears in front.</p> <p>After drawing your animations, render the text by calling the &quot;framedone&quot; method of the &quot;anim_events&quot; global variable:</p> <div> <div style="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> anim_events:framedone()</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> <h2>Handling animation events</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> <p>When an animation event triggers, the animation system looks for a function on the &quot;anim_events&quot; global variable with the same name as the event to call.<br /> The system sets up a default &quot;anim_events&quot; table with handlers for the built-in events (&quot;sfx&quot;,&quot;mus&quot;,&quot;txt&quot;,&quot;reltxt&quot;,&quot;clr&quot;), so these events will work automatically.</p> <p>You can handle your own events by adding functions to the anim_events table.</p> <p>The parameters are:</p> <p>ev=The event key frame.<br /> This is a table variable with properties:</p> <ul> <li>t</li> <li>x,y</li> <li>n</li> <li>txt</li> </ul> <p>Which correspond to the values configured in the animation editor. Property values (except t) can be nil if no value was entered.</p> <p>x,y=The screen position of the animation that triggered the event.</p> <p>Note: To save tokens the event is called as a function, not a method. I.e. there is no &quot;self&quot; parameter.</p> <h3>After frame rendering</h3> <p>You can also create event types that render content after the animations have been rendered (like the &quot;txt&quot; and &quot;reltxt&quot; events), by calling anim_events.doafterframe and passing it a function to run.<br /> The function will be converted into a coroutine, so you can use yield() for multi-frame drawing.</p> <p>For example, here's an event that causes the screen to flash 3 times. The screen will flash white by default, or you can specify a colour by setting &quot;n&quot;:</p> <div> <div style="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> anim_events.flash=function(ev,x,y) anim_events.doafterframe(function() -- three flashes for i=1,3 do -- one frame of white cls(ev.n or 7) yield() -- two normal frames yield() yield() 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></div></div></div></p> <h2>Embedding animations as Lua strings</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> <p>Instead of loading animation data from an external cart, you can embed it directly into your Lua source as hexidecimal strings, to create a single self-contained cart. </p> <p>For example, here's the demo animation embedded into a cart as a Lua string:</p> <p> <table><tr><td> <a href="/bbs/?pid=79085#p"> <img src="/bbs/thumbs/pico8_mot_animintro-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=79085#p"> Mot's Animation System - Demo animation</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=79085#p"> [Click to Play]</a> </td></tr></table> </p> <p>You will need enough free space to fit the strings in without exceeding the compressed size limit however.</p> <p>To generate the Lua strings you must run Pico-8 in console mode.<br /> Load your animation in the editor, then click the hamburger icon <img style="" border=0 src="/media/39676/animmenuicon.PNG" alt="" /> and select &quot;EXPORT ANIMS&quot;.</p> <p>The editor will write a table of hexidecimal strings to the console window. This looks like (on Windows anyway):</p> <div> <div style="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>INFO: { INFO: &quot;6d617301390108622d636173746c65022810103232019007000107622d68696c6c7302281008320001a00b000107622d7261696c73022810013232012008000105622d736561022810103232018007000107622d7472656573022810103232012000000107632d64616e6365013b010232640a4000004100004000004100014000004100004200004200004200004200000107702d737061726b012802023232010100000106742d616e696d022810043232010003000107742d616e696d73022812043232010003000108742d656469746f72012804013232013000000106742d6d6f7473022810043232010000000108742d73797374656d022810043232018005000108742d76697375616c01280401323201200000010674682d61726d01280102321401460000010774682d626f647901280202416401430000010774682d636c756201280201503201740000010674682d6c656701280102326401450000010774682d6c65676201280102326401470000010774722d626f647902280b05323201940200010774722d7073746e02280201323201980600010774722d726f643202280501323201180700010874722d736b69727402280b01323201140500010874722d736d6f6b6501130101323203600000610000620000010974722d776865656c7302280b023232019405000308632d64616e636532880001020000&quot;, INFO: &quot;0b06000000000000640004440009060000640005000305696e74726f963201090000091b000064000492090b1c000000000000640004860b091d0000640004de0d091e00006400042a12091f00006400046a1809200000640004521c092100006400043a2009220000640004d2280923000064000400020731312d74686f679209010400000f3169001400c800c800000064000ce8030a19001400820000000c6c0702190014000c9e07026900140008034c0403747874070c0008005a1374686f67206265207468696e6b696e672e2e2eaa05037478740702000e00781f77686174206973206d6f74277320616e696d6174696f6e2073797374656d3fd007037478740710003c00781869276d20736f20676c616420796f752061736b65642e2e2e020831322d7469746c65f401040400000b2c00000000000064000c960004640064000caa0004640064000ca00004960096000c0200000b29000000000000640000b9000a0100cdff0000b400040200000b29000000000000640000d2000ad0ff23000000aa00040200000b29000000000000640000f0000a31000e000000dc000403c800037366780202e100037366780203fa00037366780204020731332d646573635802030600000b0df6ffd6ff0000640008410004320032000c550004780078000c690004640064000cc20102f6ffd6ff0cef0102a6ffd6ff0c0600&quot;, INFO: &quot;000b0a1800d6ff0000640008730004320032000c870004780078000c9b0004640064000cc201021800d6ff0cef01025200d6ff0c07000009090000640008eb000600000d00280028000cff0004640064000c130104550064000cc20104550064000cdb0104550005000cef01040a00050008040000037478740714001400ff0469732061af00037478740768001400ff03666f72c0000672656c74787407f4ffe8ffff067069636f2d38c20103636c7200020632312d7365714c04030600000f06d6ff2200c800c8000000000000fa000004770102d6ff220004f4010b1900001e00000096000c52030200001e000c010402000060000c0400000f0611000200c800c8000a00000000130100047701021100020004f4010200001e00000400000f0627002a00c800c80046000000002c01000477010227002a0004f4010200001e00000611000672656c74787407d3ffd8ffff1673657175656e636520737072697465206672616d6573170203636c720058020672656c74787407d8ffd8ffff136c6f6f6b696e6720676f6f6420627269616e21de0303636c7200f401036d757302054704036d757300020732322d636f6d62400601042c010d30c800c800000064000500000000f4010b310a002300000064000d78050250002300080400000672656c74787407c0ffd8ffff20636f6d62696e65207370726974657320666f&quot;, INFO: &quot;72206d6f726520636f6d706c657864000672656c74787407ecffe2ffff0a616e696d6174696f6e73260203636c7200e8030672656c74787407f2ffe8ff3c1274686f67206665656c2077656972642e2e2e020532332d6267e803020500000b24800000000000640008af0002800000000ce10002000000000c840302000000000cb6030280ff00000804000009310000640009e9000ae6ff0800000064000d30020af5ff08008c0000000d840300090300000672656c74787407c4ffceff5a1d7573652074696c65206d61707320666f72206261636b67726f756e6473c2010672656c74787407dcffe7ff3c0d6b6e6f636b206b6e6f636b2e2e9e020672656c74787407f6ffceff46127765277265206e6f7420617420686f6d652e020832342d636f6d6232e8030105c800093500006400047c0108b400000004580208b40064000494020b3914000000000064000ce8030292ff00000c0200000672656c74787407ceffc4ff5a196f7220746f206d616b65206c61726765722073707269746573f4010672656c74787407ceffc4ff641a77686963682063616e20616c736f20626520636f6d62696e6564020833312d7363656e659808010200000b2d000000000000000008c80008000064000c0400000672656c74787407c4ffc4ff781c7468656e20636f6d62696e6520697420616c6c20746f67657468657264000672&quot;, INFO: &quot;656c74787407d8ffceff7813746f20637265617465206375747363656e65732c01036d757302003308036d757300020634312d6f7574c409010300000b2b00000000000064000c080704640064000c6c07046400000008000209622d636173746c6532e803030100000b0400000000000064000c0100000b020000000000006400080100000b0100000000000064000c000208622d747265657332e803020100000b0500000000000064000c0100000b0300001500000064000c000208622d747265657366c800030100000b0400000000000064000c0100000b020000e1ff000064000c0100000b2700000000000064000c000208622d74726565736cc800020200000b2500000000000064000cc80002800000000c0200000b2580ff0000000064000cc80002000000000c000206632d74686f67c800010100000a00000000000064000c000208702d737061726b32c800010400000f07000000000a000a00000064000c1e0004640064000cc800040a000a00086e0004640064000c000208732d636173746c65e803020100000b2400000000000064000c0200000b31e4ff0800320064000d2c010af0ff0800960000000d0177010672656c74787407d8ffe4ff3c0d74686f6720636f6d6520696e3f0205732d6f7574d007040200000b2c00000000000064000c2c01082c0100000c02b4000b3992ff1e00000064000d13&quot;, INFO: &quot;0102b0ff1e000d03e1000b3157001400000096000c8a020a390014003c0000000c00000008028c000b19ecffe4ff000064000c00000008000207732d7469746c65f401030414000f0b0000dcff64006400000064000c000006000070ff900190010c5e01020000dcff0c7c01028000dcff080532000f080000000090019001000064000c00000008460004640064000c720102000000000c90010280ff0000080564000f0c0000900090019001000064000c78000600002400640064000c00000008860102000024000ca401028000240008031900037366780201460003736678020178000373667802010207732d747261696ed007020100000b2600000000000064000c0400000b346e00f6ff000064000c5802020a00f6ff0cb004020a00f6ff0c6c07022efff6ff0c01dd020672656c74787407d8ffd8ff3c1a74686973207363656e65206d616b65206e6f2073656e73652e2e020a74682d61726d636c75626400020100000f10feff0b0087006400000064000c0100000b0e00000000000064000c00020774682d626974739001040700000b1201000000000064000c320002030000000c6400020100ffff0c960002ffff00000cc80002010000000c0e010210000b000c5e0100080700000b0f0000f8ff000064000c3200020000faff0c6400020000f8ff0c9600020000faff0cc800020000f8ff0c0e01020900f4&quot;, INFO: &quot;ff0c540100080700000b11ffffffff000064000c320002fdff00000c670002ffff00000c960002010000000cc80002ffffffff0c0e0102fcff0d000c720100080700000b2e0000efff000064000c3f00020100f1ff0c7100020000efff0ca00002fffff1ff0cc800020000efff0c0e0102edffe7ff0c7c01000c0232000373667802009600037366780200020774682d6d616b65c800010100000b2f0000000090019cff0400020774682d77616c6bc800010100000b2f00000000000064000c00020774722d626974735e01060200000b38e0ffe6ff000064000c640000080400000b330000fbff000064000c6400020000fbff0ca0000aefffeaff000000000c2c0100080400000b1600001300000064000c640002000013000ca000021e0009000cfa0000080400000b1800001500000064000c640002000015000ca0000209001c000c130100080700000f151500130056006400000064000c320002150019000c190002110016000c4b0002180016000c640002150013000ca000022b002d000ce10000080700000b15f0ff1900000064000c320002f0ff13000c190002f3ff16000c4b0002edff16000c640002f0ff19000ca00002ddff28000cc800000800020874722d626f6479321400010200000b1300000000000064000c0a0002000001000c00020774722d6c6f6e67e803060100000b3124000a000000000004&quot;, INFO: &quot;0100000b3900000000000064000c0100000b365b001500000064000c0100000b1945000a00000064000c0100000b1954000a00140064000c0100000b1965000a000a0064000c00020774722d6d616b65f000010100000b320000000054019cff0400020974722d706c7466726de803020100000b160000f9ff000064000c0100000b1800000000000064000c00020974722d736d6f6b65326400010200000b170000000000006400046400020000ebff0400020974722d736d6f6b65336400040100000b3700000000000064000c0100000b37000000001e0064000c0100000b37000000003c0064000c0100000b3700000000500064000c00020874722d747261696e6400010100000b3200000000000064000c00&quot;, INFO: &quot;&quot;, INFO: &quot;&quot;, INFO: &quot;&quot;, INFO: &quot;&quot;, INFO: &quot;&quot;, INFO: &quot;&quot;, INFO: &quot;&quot;, INFO: &quot;&quot;, INFO: &quot;&quot;}</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>You will need to copy it from the console window and remove the &quot;INFO: &quot; line prefixes. You can optionally remove any blank strings at the end of the table.</p> <p>Then add it to your code, assigning it to a variable. For example:</p> <div> <div style="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>animdata={ &quot;6d617301390108622d636173746c65022810103232019007000107622d68696c6c7302281008320001a00b000107622d7261696c73022810013232012008000105622d736561022810103232018007000107622d7472656573022810103232012000000107632d64616e6365013b010232640a4000004100004000004100014000004100004200004200004200004200000107702d737061726b012802023232010100000106742d616e696d022810043232010003000107742d616e696d73022812043232010003000108742d656469746f72012804013232013000000106742d6d6f7473022810043232010000000108742d73797374656d022810043232018005000108742d76697375616c01280401323201200000010674682d61726d01280102321401460000010774682d626f647901280202416401430000010774682d636c756201280201503201740000010674682d6c656701280102326401450000010774682d6c65676201280102326401470000010774722d626f647902280b05323201940200010774722d7073746e02280201323201980600010774722d726f643202280501323201180700010874722d736b69727402280b01323201140500010874722d736d6f6b6501130101323203600000610000620000010974722d776865656c7302280b023232019405000308632d64616e636532880001020000&quot;, &quot;0b06000000000000640004440009060000640005000305696e74726f963201090000091b000064000492090b1c000000000000640004860b091d0000640004de0d091e00006400042a12091f00006400046a1809200000640004521c092100006400043a2009220000640004d2280923000064000400020731312d74686f679209010400000f3169001400c800c800000064000ce8030a19001400820000000c6c0702190014000c9e07026900140008034c0403747874070c0008005a1374686f67206265207468696e6b696e672e2e2eaa05037478740702000e00781f77686174206973206d6f74277320616e696d6174696f6e2073797374656d3fd007037478740710003c00781869276d20736f20676c616420796f752061736b65642e2e2e020831322d7469746c65f401040400000b2c00000000000064000c960004640064000caa0004640064000ca00004960096000c0200000b29000000000000640000b9000a0100cdff0000b400040200000b29000000000000640000d2000ad0ff23000000aa00040200000b29000000000000640000f0000a31000e000000dc000403c800037366780202e100037366780203fa00037366780204020731332d646573635802030600000b0df6ffd6ff0000640008410004320032000c550004780078000c690004640064000cc20102f6ffd6ff0cef0102a6ffd6ff0c0600&quot;, &quot;000b0a1800d6ff0000640008730004320032000c870004780078000c9b0004640064000cc201021800d6ff0cef01025200d6ff0c07000009090000640008eb000600000d00280028000cff0004640064000c130104550064000cc20104550064000cdb0104550005000cef01040a00050008040000037478740714001400ff0469732061af00037478740768001400ff03666f72c0000672656c74787407f4ffe8ffff067069636f2d38c20103636c7200020632312d7365714c04030600000f06d6ff2200c800c8000000000000fa000004770102d6ff220004f4010b1900001e00000096000c52030200001e000c010402000060000c0400000f0611000200c800c8000a00000000130100047701021100020004f4010200001e00000400000f0627002a00c800c80046000000002c01000477010227002a0004f4010200001e00000611000672656c74787407d3ffd8ffff1673657175656e636520737072697465206672616d6573170203636c720058020672656c74787407d8ffd8ffff136c6f6f6b696e6720676f6f6420627269616e21de0303636c7200f401036d757302054704036d757300020732322d636f6d62400601042c010d30c800c800000064000500000000f4010b310a002300000064000d78050250002300080400000672656c74787407c0ffd8ffff20636f6d62696e65207370726974657320666f&quot;, &quot;72206d6f726520636f6d706c657864000672656c74787407ecffe2ffff0a616e696d6174696f6e73260203636c7200e8030672656c74787407f2ffe8ff3c1274686f67206665656c2077656972642e2e2e020532332d6267e803020500000b24800000000000640008af0002800000000ce10002000000000c840302000000000cb6030280ff00000804000009310000640009e9000ae6ff0800000064000d30020af5ff08008c0000000d840300090300000672656c74787407c4ffceff5a1d7573652074696c65206d61707320666f72206261636b67726f756e6473c2010672656c74787407dcffe7ff3c0d6b6e6f636b206b6e6f636b2e2e9e020672656c74787407f6ffceff46127765277265206e6f7420617420686f6d652e020832342d636f6d6232e8030105c800093500006400047c0108b400000004580208b40064000494020b3914000000000064000ce8030292ff00000c0200000672656c74787407ceffc4ff5a196f7220746f206d616b65206c61726765722073707269746573f4010672656c74787407ceffc4ff641a77686963682063616e20616c736f20626520636f6d62696e6564020833312d7363656e659808010200000b2d000000000000000008c80008000064000c0400000672656c74787407c4ffc4ff781c7468656e20636f6d62696e6520697420616c6c20746f67657468657264000672&quot;, &quot;656c74787407d8ffceff7813746f20637265617465206375747363656e65732c01036d757302003308036d757300020634312d6f7574c409010300000b2b00000000000064000c080704640064000c6c07046400000008000209622d636173746c6532e803030100000b0400000000000064000c0100000b020000000000006400080100000b0100000000000064000c000208622d747265657332e803020100000b0500000000000064000c0100000b0300001500000064000c000208622d747265657366c800030100000b0400000000000064000c0100000b020000e1ff000064000c0100000b2700000000000064000c000208622d74726565736cc800020200000b2500000000000064000cc80002800000000c0200000b2580ff0000000064000cc80002000000000c000206632d74686f67c800010100000a00000000000064000c000208702d737061726b32c800010400000f07000000000a000a00000064000c1e0004640064000cc800040a000a00086e0004640064000c000208732d636173746c65e803020100000b2400000000000064000c0200000b31e4ff0800320064000d2c010af0ff0800960000000d0177010672656c74787407d8ffe4ff3c0d74686f6720636f6d6520696e3f0205732d6f7574d007040200000b2c00000000000064000c2c01082c0100000c02b4000b3992ff1e00000064000d13&quot;, &quot;0102b0ff1e000d03e1000b3157001400000096000c8a020a390014003c0000000c00000008028c000b19ecffe4ff000064000c00000008000207732d7469746c65f401030414000f0b0000dcff64006400000064000c000006000070ff900190010c5e01020000dcff0c7c01028000dcff080532000f080000000090019001000064000c00000008460004640064000c720102000000000c90010280ff0000080564000f0c0000900090019001000064000c78000600002400640064000c00000008860102000024000ca401028000240008031900037366780201460003736678020178000373667802010207732d747261696ed007020100000b2600000000000064000c0400000b346e00f6ff000064000c5802020a00f6ff0cb004020a00f6ff0c6c07022efff6ff0c01dd020672656c74787407d8ffd8ff3c1a74686973207363656e65206d616b65206e6f2073656e73652e2e020a74682d61726d636c75626400020100000f10feff0b0087006400000064000c0100000b0e00000000000064000c00020774682d626974739001040700000b1201000000000064000c320002030000000c6400020100ffff0c960002ffff00000cc80002010000000c0e010210000b000c5e0100080700000b0f0000f8ff000064000c3200020000faff0c6400020000f8ff0c9600020000faff0cc800020000f8ff0c0e01020900f4&quot;, &quot;ff0c540100080700000b11ffffffff000064000c320002fdff00000c670002ffff00000c960002010000000cc80002ffffffff0c0e0102fcff0d000c720100080700000b2e0000efff000064000c3f00020100f1ff0c7100020000efff0ca00002fffff1ff0cc800020000efff0c0e0102edffe7ff0c7c01000c0232000373667802009600037366780200020774682d6d616b65c800010100000b2f0000000090019cff0400020774682d77616c6bc800010100000b2f00000000000064000c00020774722d626974735e01060200000b38e0ffe6ff000064000c640000080400000b330000fbff000064000c6400020000fbff0ca0000aefffeaff000000000c2c0100080400000b1600001300000064000c640002000013000ca000021e0009000cfa0000080400000b1800001500000064000c640002000015000ca0000209001c000c130100080700000f151500130056006400000064000c320002150019000c190002110016000c4b0002180016000c640002150013000ca000022b002d000ce10000080700000b15f0ff1900000064000c320002f0ff13000c190002f3ff16000c4b0002edff16000c640002f0ff19000ca00002ddff28000cc800000800020874722d626f6479321400010200000b1300000000000064000c0a0002000001000c00020774722d6c6f6e67e803060100000b3124000a000000000004&quot;, &quot;0100000b3900000000000064000c0100000b365b001500000064000c0100000b1945000a00000064000c0100000b1954000a00140064000c0100000b1965000a000a0064000c00020774722d6d616b65f000010100000b320000000054019cff0400020974722d706c7466726de803020100000b160000f9ff000064000c0100000b1800000000000064000c00020974722d736d6f6b65326400010200000b170000000000006400046400020000ebff0400020974722d736d6f6b65336400040100000b3700000000000064000c0100000b37000000001e0064000c0100000b37000000003c0064000c0100000b3700000000500064000c00020874722d747261696e6400010100000b3200000000000064000c00&quot; }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>You can now use the following code to load the animations:</p> <div> <div style="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> -- load animation from animdata string local stream=readstrstream(animdata) anims=loadanims(stream) -- extract animation to play anim=anims[&quot;intro&quot;]</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=38739 https://www.lexaloffle.com/bbs/?tid=38739 Sun, 12 Jul 2020 00:44:07 UTC Playing Ramps with a racing wheel <p><object width="640" height="400"><param name="movie" value="https://www.youtube.com/v/aoPL7nw_YMM&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/aoPL7nw_YMM&hl=en&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="400"></embed></object></p> <p>OK, this may be a bit bonkers, but I figured out how to play <a href="https://www.lexaloffle.com/bbs/?tid=38221">Ramps</a> with a racing wheel, with proper analogue steering and acceleration/braking.</p> <p>It's a little involved and only works on Windows.</p> <h3>Mouse input</h3> <p>First, you need a version of Ramps (or whatever game you plan to play) edited to accept mouse input. Like this version:<br /> <table><tr><td> <a href="/bbs/?pid=77721#p"> <img src="/bbs/thumbs/pico8_mot_ramps_mouse-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=77721#p"> Ramps - with mouse input</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=77721#p"> [Click to Play]</a> </td></tr></table> <br /> <strong>WARNING: Attempting to drive with an actual mouse may cause high blood pressure and throwing things.</strong><br /> Also I've only tried this in PICO-8 itself - not sure if it works when running in a browser.</p> <h3>Joystick -&gt; mouse</h3> <p>Next, you need a utility to convert game controller input to mouse movement.</p> <p>I couldn't find one that did exactly what I needed, so I ended up <a href="https://basic4gl.net/misc/MapJoyMouse.zip">making my own here</a>.<br /> <em>Tip: If you try it out remember F12 toggles it on/off - as you can't use the mouse when it's enabled.</em></p> <h3>Race</h3> <p>So basically run the utility, run the mouse-input version of Ramps in PICO-8, switch to full-screen, and go :-)</p> https://www.lexaloffle.com/bbs/?tid=38306 https://www.lexaloffle.com/bbs/?tid=38306 Sat, 06 Jun 2020 01:58:54 UTC Suggestion: UDP/IP networking <p>I'm a bit new here, so I don't know if adding network capability has previously been discussed (?)</p> <p>But it feels to me some simple UDP send/receive instructions would add some cool functionality, without adding too much complexity.</p> <p>Perhaps something like:</p> <div> <div style="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>[write message to RAM] poke4([ip-locn],ip) poke2([port-locn],port) send(0x4300,length) </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Where [ip-locn],[port-locn] would be some special reserved locations for IP address and port.<br /> UDP packets are usually small. I've seen &lt;=512 bytes recommended in places, which easily fits into the user RAM address range.<br /> And an IPV4 address would fit in a number variable, and would be adequate for LAN play.</p> <p>Receive could be:</p> <div> <div style="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>length=recv(0x4300,maxlength) [read message from RAM]</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Returning 0 if nothing is waiting.<br /> It could also populate [ip-locn] with the return IP address so that replying would be straightforward.</p> <p>Perhaps [port-locn] could be pre-populated with a default port number (0x1C08 ?).</p> <p>Any thoughts?<br /> Am I trying to make it simpler than is actually possible? (I haven't done socket programming in a while, so I forget what's necessary to get UDP/IP working).</p> https://www.lexaloffle.com/bbs/?tid=38293 https://www.lexaloffle.com/bbs/?tid=38293 Fri, 05 Jun 2020 09:36:53 UTC Ramps <h1>Ramps</h1> <p> <table><tr><td> <a href="/bbs/?pid=77443#p"> <img src="/bbs/thumbs/pico8_mot_ramps-21.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=77443#p"> Ramps</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=77443#p"> [Click to Play]</a> </td></tr></table> </p> <p>This is <strong>Ramps</strong>! A 3D racer inspired by Powerdrift, Stunt Car Racer, Hard Drivin'.</p> <p>Choose from 8 varied race tracks, and overtake the other drivers in 5 laps to win.<br /> As well as racing you'll need to navigate jumps, roller-coaster like ramps and occasionally even drive upside down.</p> <p>Includes 3 difficulty levels, plus a practice mode for getting the feel of the tracks.</p> <img style="" border=0 src="/media/39676/mot_ramps_8.gif" alt="" /> <img style="" border=0 src="/media/39676/mot_ramps_2.gif" alt="" /> <img style="" border=0 src="/media/39676/mot_ramps_3.gif" alt="" /> <img style="" border=0 src="/media/39676/mot_ramps_6.gif" alt="" /> <p>This all started as an experiment to see how well Pico-8 could render a Powerdrift-like scaled sprite racetrack (pretty well, as it turns out). Then it was about trying to implement a driving model that could handle the ramps, jumps and loops. And finally wrapping it up into a somewhat finished game.</p> <p>Like with Loose Gravel, I would have liked to add a championship mode, but there just aren't enough tokens :-)</p> <p>Big thanks to everybody who gave advice and feedback. Particularly <a href="https://www.lexaloffle.com/bbs/?uid=25532"> @freds72</a> who even went as far as digging into the code and making some performance optimisations (~20% CPU improvement!).</p> <h3>Tips</h3> <p>Z = Accelerate<br /> X = Brake<br /> Left/Right to steer</p> <p>You don't have to wait to be automatically respawned after you fall of the track, select &quot;Respawn&quot; in the pause menu to get back into the race faster.</p> <p>The green and red lines on the speedometer are recommended <em>minimum</em> and <em>maximum</em> speeds.<br /> The green line (minimum speed) is particularly important for clearing jumps (and occasionally getting up steep hills), so make sure to keep your speed above it.<br /> The red line (maximum speed) is to prevent you from sliding out on sharp corners or overshooting the jumps. Typically this is just a suggested speed. Depending on your racing line, driving style etc you may be able to go faster.</p> <p>Enjoy,<br /> -Mot</p> <p><strong>Update: Other cars' speeds now vary randomly throughout the race</strong><br /> <strong>Update 2: Can knock other cars sideways a bit &amp; they don't knock you sideways as much. Round road sprite corners a bit.</strong><br /> <strong>Update 3: Fix game crashes in AI code when road is blocked.</strong></p> https://www.lexaloffle.com/bbs/?tid=38221 https://www.lexaloffle.com/bbs/?tid=38221 Sun, 31 May 2020 05:23:50 UTC Instant 3D plus! <h1>Instant 3D plus!</h1> <img style="" border=0 src="/media/39676/d3d2.p8_1.gif" alt="" /> <p><a href="https://www.lexaloffle.com/bbs/?tid=37733">Instant 3D!</a> was a random idea, quickly thrown together to see if it was possible. But after seeing the cool things people can do with it, I wanted to clean it up properly, and also present some of the internal functions more cleanly.</p> <p>Making the 3D functions more accessible means:</p> <ol> <li>You can often get your game working correctly in 3D even if the Instant 3D &quot;magic&quot; doesn't work correctly, by calling the 3D spr/map functions directly with the right parameters.</li> <li>You can do things that the original Instant 3D can't do, like having objects that hover into the air.</li> </ol> <p>I've added a little tutorial of converting a 2D game to 3D to illustrate how this works, at the bottom of this post.</p> <p>Obviously this &quot;snippet&quot; is still very limited, compared to a general purpose 3D library say. You can't use it to create an FPS or a flight simulator. But I think it's a lot easier to use - start with a 2D game, drop it in, and fix up the bits that don't come out right. And you can still do some cool looking 3D stuff with it.</p> <p>Here's the updated snippet:</p> <div> <div style="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>-- instant 3d+! do -- parameters p3d={ vanish={x=64,y=0}, -- vanishing pt d=128, -- screen dist in pixels near=1, -- near plane z camyoff=32, -- added to cam y pos camheight=32 -- camera height } -- save 2d versions map2d,spr2d,sspr2d,pset2d,camera2d=map,spr,sspr,pset,camera -- 3d camera position local cam={x=0,y=0,z=0} -- is 3d mode enabled? is3d=false -- helper functions -- screen to camera space local function s2c(x,y,z) return x-cam.x,y-cam.y,z-cam.z end -- perspective projection local function proj(x,y,z) if -y&gt;=p3d.near then local scale=p3d.d/-y return x*scale+p3d.vanish.x,-z*scale+p3d.vanish.y,scale end end -- screen to projected local function s2p(x,y,z) local x,y,z=s2c(x,y,z) return proj(x,y,z) end -- 3d drawing fns function sspr3d(sx,sy,sw,sh,x,y,z,w,h,fx,fy) w=w or sw h=h or sh local px,py,scale=s2p(x,y,z) if(not scale)return local pw,ph=w*scale,h*scale -- sub pixel stuff local x0,x1=flr(px),flr(px+pw) local y0,y1=flr(py),flr(py+ph) sspr2d(sx,sy,sw,sh,x0,y0,x1-x0,y1-y0,fx,fy) end spr3d=function(n,x,y,z,w,h,fx,fy) if(not z)return -- convert to equivalent sspr() call w=(w or 1)*8 h=(h or 1)*8 local sx,sy=flr(n%16)*8,flr(n/16)*8 sspr3d(sx,sy,w,h,x,y,z,w,h,fx,fy) end function map3d(cx,cy,x,y,z,w,h,lyr) if(not h)return -- near/far corners local fx,fy,fz=s2c(x,y,z) local nx,ny,nz=s2c(x,y+h*8,z) -- clip ny=min(ny,-p3d.near) if(fy&gt;=ny)return -- project local npx,npy,nscale=proj(nx,ny,nz) local fpx,fpy,fscale=proj(fx,fy,fz) if npy&lt;fpy then local tx,ty,ts=npx,npy,nscale npx,npy,nscale=fpx,fpy,fscale fpx,fpy,fscale=tx,ty,ts end -- clamp npy=min(npy,128) fpy=max(fpy,0) -- rasterise local py=flr(npy) while py&gt;=fpy do -- floor plane intercept local g=(py-p3d.vanish.y)/p3d.d local d=-nz/g -- map coords local mx,my=cx,(-fy-d)/8+cy -- project to get left/right local lpx,lpy,lscale=proj(nx,-d,nz) local rpx,rpy,rscale=proj(nx+w*8,-d,nz) -- delta x local dx=w/(rpx-lpx) -- sub-pixel correction local l,r=flr(lpx+0.5)+1,flr(rpx+0.5) mx+=(l-lpx)*dx -- render tline(l,py,r,py,mx,my,dx,0,lyr) py-=1 end end function map3dupright(cx,cy,x,y,z,w,h,lyr) if(not h)return local px,py,scale=s2p(x,y,z) if(not scale)return local pw,ph=w*8*scale,h*8*scale -- texture step local dx,dy=w/pw,h/ph local mx,my=cx+0.0625,cy+0.0625 -- sub pixel stuff local x0,x1=flr(px),flr(px+pw) local y0,y1=flr(py),flr(py+ph) mx+=(x0-px)*dx my+=(y0-py)*dy if(x0&gt;=x1 or y0&gt;=y1)return for y=y0,y1-1 do tline(x0,y,x1,y,mx,my,dx,0,lyr) my+=dy end end function camera3d(x,y,z) cam.x,cam.y,cam.z=x,y,z end -- &quot;instant 3d&quot; wrapper functions local function icamera(x,y) cam.x=(x or 0)+64 cam.y=(y or 0)+128+p3d.camyoff cam.z=p3d.camheight end local function isspr(sx,sy,sw,sh,x,y,w,h,fx,fy) z=h or sh y+=z sspr3d(sx,sy,sw,sh,x,y,z,w,h,fx,fy) end local function ispr(n,x,y,w,h,fx,fy) z=(h or 1)*8 y+=z spr3d(n,x,y,z,w,h,fx,fy) end local function imap(cx,cy,x,y,w,h,lyr) cx=cx or 0 cy=cy or 0 x=x or 0 y=y or 0 w=w or 128 h=h or 64 map3d(cx,cy,x,y,0,w,h,lyr) end function go3d() camera,sspr,spr,map=icamera,isspr,ispr,imap camera2d() is3d=true end function go2d() map,spr,sspr,pset,camera=map2d,spr2d,sspr2d,pset2d,camera2d is3d=false end -- defaults icamera() end -- enable 3d mode go3d() menuitem(3,&quot;3d&quot;,go3d) menuitem(2,&quot;2d&quot;,go2d)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>As before, to use it, just copy it into your 2D program. It should behave exactly the same.<br /> There's one new feature, in that you can toggle between 2D and 3D in the Pico-8 menu.</p> <img style="" border=0 src="/media/39676/pakutto boy 1.00_3.gif" alt="" /> <p>You can also do it in code using</p> <div> <div style="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>go2d()</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>and </p> <div> <div style="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>go3d()</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>which might be useful for 2D title screens etc.</p> <h3>Taking it further</h3> <p>The basics are the same as before, but you can now - with a little bit of work - take your games a bit further by making use of explicit 2D and 3D commands, rather than leaving it up to the snippet to guess your intent.</p> <p>To illustrate this, I made a little 2D cart to convert into 3D.</p> <p> <table><tr><td> <a href="/bbs/?pid=76690#p"> <img src="/bbs/thumbs/pico8_instant3dplus-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=76690#p"> instant3dplus</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=76690#p"> [Click to Play]</a> </td></tr></table> </p> <p>This is a simple little game where you're a bouncing ball that collects coins. It's not finished, but is enough to demonstrate the process. The game is already a little bit 3D in that the ball and coins also have a height, and objects can move in front and behind each other, so it has to sort their positions and draw them from back to front.</p> <img style="" border=0 src="/media/39676/demo3d_1.gif" alt="" /> <p>Dropping the snippet into this program has... mixed results.</p> <img style="" border=0 src="/media/39676/d3d1.p8_0.gif" alt="" /> <p>As you can see, it's kind of 3D, but it has some issues:</p> <ul> <li>The ball doesn't always bounce straight up. In fact if you look closely it's actually staying on the ground and just bounces away and back again.</li> <li>The coins aren't raised up properly either.</li> <li>The metallic struts are flat on the ground, rather than standing up.</li> <li>Likewise the gratings on top are also flat on the ground.</li> <li>The 3 lives are displayed in the wrong place.</li> </ul> <p>Obviously the snippet doesn't know exactly what we're trying to achieve, but fortunately we can help it out.</p> <p>With a little bit of work we can make it look like this:</p> <img style="" border=0 src="/media/39676/d3d2.p8_1.gif" alt="" /> <h3>3D functions</h3> <p>We can fix up the ball using an explicit 3D function. The snippet provides explicit 3D functions spr3d, sspr3d and map3d. They have the same parameters as the standard functions, except there's a Z parameter immediately after the X and Y screen parameters.</p> <p>They use a 3D coordinate system where:</p> <ul> <li>The X axis is to the right</li> <li>The Y axis is out of the screen (towards you)</li> <li>The Z axis is up</li> </ul> <p>This keeps X and Y consistent with the &quot;Instant 3D&quot; logic, where instead of moving up the screen as Y decreases, objects move away from you. The new Z parameter allows us to also specify the height.</p> <p>The ball drawing code looks like this:</p> <div> <div style="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> elseif thing.typ==&quot;player&quot; then shadowcols() sspr(0,32, 8,8, thing.x-4,thing.y-1, 8,4) pal() spr(64+thing.frame%9, thing.x-4, thing.y-thing.height-8) 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 sspr() call draws the shadow, which looks correct already, so it doesn't need to change.<br /> The spr() call draws the ball, based on the thing.x,-.y and -.height variables. The game stores the position of bottom center of the ball, so it has to subtract 4 and 8 to get the top left corner for spr().<br /> We can change it to an explicit 3D call as follows:</p> <div> <div style="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> spr3d(64+thing.frame%9, thing.x-4, thing.y, thing.height+8) </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>We're still specifying the top left corner, but now it's in 3D.</p> <p>The coin code is similar:</p> <div> <div style="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> if thing.typ==&quot;coin&quot; then shadowcols() sspr(0,40, 8,8, thing.x-4,thing.y-1, 8,4) pal() spr(80, thing.x-4,thing.y-thing.height-8) </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Once again we change the spr call to an spr3d:</p> <div> <div style="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> spr3d(80, thing.x-4, thing.y, thing.height+8) </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>With 3D positions supplied, the ball and coins now bounce/float above the ground properly.</p> <img style="" border=0 src="/media/39676/demo3d_2.gif" alt="" /> <h3>Upright maps</h3> <p>Next we can address the red metal structs. Currently they are lying flat on the ground instead of standing up straight.<br /> The &quot;instant 3D&quot; snippet assumes everything drawn with &quot;spr&quot;/&quot;sspr&quot; is upright, and everything drawn with &quot;map&quot; is flat on the ground. However the struts are drawn using map(), so that they can be composed of multiple sprites.<br /> So we need to tell the game to draw them upright.</p> <p>map3d won't help in this case. We can use it to draw them higher up, but they will still be lying flat, not standing upright. So to help with this the snippet provides an alternative &quot;map3dupright&quot; function.</p> <p>The strut drawing code is:</p> <div> <div style="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> elseif thing.typ==&quot;strut&quot; then map(127,0, thing.x,thing.y-40, 1,5)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>We can change the map call to a map3dupright like this:</p> <div> <div style="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> map3dupright(127,0, thing.x, thing.y, 40, 1,5)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Once again it has the same parameters as &quot;map&quot;, except there's a Z parameter immediately after the X and Y.<br /> The struts are 40 pixels high, so we set the Z (i.e. the height) to 40, to specify the top left corner position.</p> <p>With this in place, the struts now stand upright.</p> <img style="" border=0 src="/media/39676/demo3d_3.gif" alt="" /> <p> <table><tr><td> <a href="/bbs/?pid=76690#p"> <img src="/bbs/thumbs/pico8_instant3dplus-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=76690#p"> instant3dplus</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=76690#p"> [Click to Play]</a> </td></tr></table> </p> <h3>3D parameters</h3> <p>Now that objects are above the ground they often disappear above the top of the screen.<br /> This is due to the camera height, and the &quot;vanishing point&quot; of the 3D projection, which is currently set to the top of the screen.</p> <p>We can easily fix this by changing the 3D parameters.<br /> The default parameters are in the snippet:</p> <div> <div style="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> -- parameters p3d={ vanish={x=64,y=0}, -- vanishing pt d=128, -- screen dist in pixels near=1, -- near plane z camyoff=32, -- added to cam y pos camheight=32 -- camera height }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>We can move the vanishing point into the center of the screen by adding a line to the _init function:</p> <div> <div style="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> p3d.vanish.y=64</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Moving the vanishing point is like rotating the camera upwards slightly. Now we can easily see everything.<br /> In fact we can even move the camera down a little and nearer to the ball:</p> <div> <div style="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> p3d.camheight=20 p3d.camyoff=20</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <img style="" border=0 src="/media/39676/demo3d2_0.gif" alt="" /> <h3>3D map coordinates</h3> <p>Now we'll fix the metal grates. These are supposed to sit on top of the struts.<br /> The grates are rendered as a single map, much like the floor. We need to tell the game to draw that map above the ground.</p> <p>This time map3d is the correct function to use.</p> <p>The existing code looks like this.</p> <div> <div style="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> -- roof map map(32,0,0,-40,16,64) </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 3D code is quite similar:</p> <div> <div style="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> -- roof map map3d(32,0,0,0,40,16,64) </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Once again we have a new Z parameter after the X and Y, which we set to 40 to move it up into the air.</p> <p>In the 2D version the grates are drawn last, as because they are above everything. However in the 3D version they actually need to be drawn before the ball, coins and struts to get the correct ordering.<br /> So the line should be moved up immediately after the &quot;map&quot; call that draws the floor.</p> <h3>2D drawing</h3> <p>The last thing to fix is the lives display. Lives are displayed as 3 balls, but they are drawn in the wrong place, because the &quot;instant 3D&quot; logic is trying to position them in 3D.</p> <p>In this case we really just want to draw them in 2D.</p> <p>Fortunately the snippet saves the original 2D functions as &quot;spr2d&quot;, &quot;sspr2d&quot; and &quot;map2d&quot;, so we can call them directly if we need to.</p> <p>The life drawing code looks like this:</p> <div> <div style="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>-- overlay camera() fancyprint(&quot;lives&quot;,4,4,12) for i=1,player.lives do spr(64,25+(i-1)*9,2) 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>Simply change the &quot;spr&quot; to &quot;spr2d&quot; and we're done.</p> <img style="" border=0 src="/media/39676/d3d2.p8_1.gif" alt="" /> <h3>2D/3D code</h3> <p>Adding the various 3D calls makes the game look correct in 3D now. But if you switch it back to 2D (via the pause menu) it now looks broken.</p> <p>One solution is to simply remove the &quot;menuitem&quot; calls from the snippet and disable mode switching. This is perfectly valid if the game is only supposed to be 3D.</p> <p>But if you really do want the 2D option, it is still possible. The snippet includes a variable &quot;is3d&quot; which is set to true in 3D mode. Before calling any 3D function, check it to ensure you are actually in 3D mode. If not, perform the original 2D call instead. For example:</p> <div> <div style="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>if is3d then spr3d(64+thing.frame%9, thing.x-4, thing.y, thing.height+8) else spr(64+thing.frame%9, thing.x-4, thing.y-thing.height-8) 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>Here's the final cart with 2D and 3D mode support.</p> <p> <table><tr><td> <a href="/bbs/?pid=76690#p"> <img src="/bbs/thumbs/pico8_instant3dplus-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=76690#p"> instant3dplus</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=76690#p"> [Click to Play]</a> </td></tr></table> </p> <h3>Other bits</h3> <p>That's the gist of how to use it. There are a couple of functions I've missed, like camera3d (sets the 3D camera position explicitly, rather than inferring it from the 2D position and height/y-offset parameters), and a pset3d/pset2d which should do what you'd expect.</p> <p>Feel free to throw me any questions you have.</p> <p>-Mot</p> <p>** Update 1: Fix spr() when drawing larger than 1x1 sprites</p> https://www.lexaloffle.com/bbs/?tid=37982 https://www.lexaloffle.com/bbs/?tid=37982 Sun, 17 May 2020 00:58:30 UTC Object literals and token counting <p>I often find myself declaring a lot of arrays/object literals that are essentially just data. No computation involved.<br /> For example:</p> <div> <div style="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>-- background types bg_tree={ tex={ {sx=0,sy=64,sw=32,sh=32} }, w=2,h=2, spacing=2 } bg_lamp={ tex={ {sx=0,sy=32,sw=16,sh=8} }, w=.5,h=.25, spacing=2, trans=12 } </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>It's a useful way to create flexible, extendable systems.</p> <p>Unfortunately it quickly chews through the token count.</p> <p>So I was wondering what people's views were of <strong>changing the token counting rules</strong> around such object literals? - say 1 token per object literal.</p> <p>They would have to be restricted in order to qualify for a reduced token count, i.e. data only, no computation, functions etc, similar to how JSON is a restricted subset of JavaScript.<br /> The compiler could either detect this automatically, or possibly a new &quot;data only literal&quot; syntax could be introduced.</p> <p>I feel like this probably aligns with the PICO-8 principals, as it's arguably data rather than code, and you still have the compressed size limit. It seems like a much cleaner solution than the string shredding/JSON parsing workarounds that are currently used.</p> https://www.lexaloffle.com/bbs/?tid=37941 https://www.lexaloffle.com/bbs/?tid=37941 Thu, 14 May 2020 02:15:29 UTC Variable inspector <h1>Variable inspector window</h1> <img style="" border=0 src="/media/39676/planegame_000.png" alt="" /> <p>Debugging carts with &quot;print&quot; and &quot;printh&quot; can be cumbersome, so I made a little snippet to help.<br /> It adds a little window where you can view variables and drill down into them.</p> <p>To use it, add the snippet somewhere into your program:</p> <div> <div style="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>dbg=(function() poke(0x5f2d, 1) -- watched variables local vars,emp={},true -- window state local exp=false -- text cursor local x,y -- scrollbar local sy=0 local sdrag -- mouse state local mx,my,mb,pb,click,mw function clicked(x,y,w,h) return click and mx&gt;=x and mx&lt;x+w and my&gt;=y and my&lt;y+h end function butn(txt,x,y,c) print(txt,x,y,c) return clicked(x,y,4,6) end -- convert value into something easier to traverse and inspect function inspect(v,d) d=d or 0 local t=type(v) if t==&quot;table&quot; then if(d&gt;5)return &quot;[table]&quot; local props={} for key,val in pairs(v) do props[key]=inspect(val,d+1) end return { expand=false, props=props } elseif t==&quot;string&quot; then return chr(34)..v..chr(34) elseif t==&quot;boolean&quot; then return v and &quot;true&quot; or &quot;false&quot; elseif t==&quot;nil&quot; or t==&quot;function&quot; or t==&quot;thread&quot; then return &quot;[&quot;..t..&quot;]&quot; else return &quot;&quot;..v end end function drawvar(var,name) if type(var)==&quot;string&quot; then print(name..&quot;:&quot;,x+4,y,6) print(var,x+#(&quot;&quot;..name)*4+8,y,7) y+=6 else -- expand button if(butn(var.expand and &quot;-&quot; or &quot;+&quot;,x,y,7))var.expand=not var.expand -- name print(name,x+4,y,12) y+=6 -- content if var.expand then x+=4 for key,val in pairs(var.props) do drawvar(val,key) end x-=4 end end end function copyuistate(src,dst) if type(src)==&quot;table&quot; and type(dst)==&quot;table&quot; then dst.expand=src.expand for key,val in pairs(src.props) do copyuistate(val,dst.props[key]) end end end function watch(var,name) name=name or &quot;[var]&quot; local p,i=vars[name],inspect(var) if(p)copyuistate(p,i) vars[name]=i emp=false end function clear() vars,emp={},true end function draw(dx,dy,w,h) dx=dx or 0 dy=dy or 48 w=w or 128-dx h=h or 128-dy -- collapsed mode if not exp then dx+=w-10 w,h=10,5 end -- window clip(dx,dy,w,h) rectfill(0,0,128,128,1) x=dx+2 y=dy+2-sy -- read mouse mx,my,mw=stat(32),stat(33),stat(36) mb=band(stat(34),1)~=0 click=mb and not pb and mx&gt;=dx and mx&lt;dx+w and my&gt;=dy and my&lt;dy+h pb=mb if exp then -- variables for k,v in pairs(vars) do drawvar(v,k) end -- scrollbar local sh=y+sy-dy if sh&gt;h then local sx=dx+w-4 local by=sy/sh*h+dy local bh=h/sh*h rectfill(sx,dy,dx+w,dy+h-1,5) rectfill(sx,by,dx+w,by+bh-1,sdrag and 12 or 6) rect(sx,by,dx+w-1,by+bh-1,13) if click and mx&gt;=sx then if my&lt;by then sy-=h elseif my&gt;by+bh then sy+=h else sdrag=by-my end end if sdrag then sy=(my+sdrag-dy)*sh/h else sy-=mw*8 end if(sy&lt;0)sy=0 if(sy+h&gt;sh)sy=sh-h if(not mb)sdrag=nil else sy=0 end -- clear btn if(butn(&quot;x&quot;,dx+w-15,dy,14))clear() end -- expand/collapse btn if(butn(exp and &quot;-&quot; or &quot;+&quot;,dx+w-10,dy,14))exp=not exp -- draw mouse ptr clip() line(mx,my,mx,my+2,8) color(7) end function show() exp=true while exp do draw() flip() end end function prnt(v,name) watch(v,name) show() end return{ watch=watch, clear=clear, empty=function() return emp end, expand=function(val) if(val~=nil)exp=val return exp end, draw=draw, show=show, print=prnt } 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>There are a few different ways to use it.</p> <h3>Global variables</h3> <p>You can view global variables when your program is paused by typing:</p> <div> <div style="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>dbg.print(myvariable)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <img style="" border=0 src="/media/39676/planegame_001.png" alt="" /> <p>The window will popup and run until you click &quot;-&quot; at the top right to exit it.<br /> <em>Note: If you press &quot;Esc&quot; to exit the inspection window, and try to resume your game with &quot;resume&quot;, it will resume the popup window instead! To avoid this, use the &quot;-&quot; button.</em></p> <h3>Local variables</h3> <p>To view a local variable inside a function you need to add code to that function to capture it.</p> <div> <div style="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>dbg.watch(myvariable,&quot;my variable&quot;)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This will capture it's value and add it to your variable list. The second parameter is the name. You can add multiple variables so long as they all have different names. Calling dbg.watch again with the same name causes it to replace the previous value.</p> <p>To view your captured variables, pause your program by pressing Esc, and type:</p> <div> <div style="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>dbg.show()</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <img style="" border=0 src="/media/39676/planegame_002.png" alt="" /> <p>Be aware that the variable inspector window shows the variable's value <strong>at the time dbg.watch() was executed</strong>. If the variable has been changed since, the changes will <strong>not</strong> show in the inspector. (This is deliberate.)</p> <h3>Displaying variables while the program is running</h3> <p>If you want to run the inspector window directly in your program, add:</p> <div> <div style="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>dbg.draw()</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>to your _draw() function.</p> <p>You can optionally specify the window position (x,y,width,height), e.g.</p> <div> <div style="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>dbg.draw(64,0,64,48)</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 default the window displays collapsed, until you click the &quot;+&quot; button.</p> <img style="" border=0 src="/media/39676/planegame_003.png" alt="" /> <p>Remember you still need to capture variables with &quot;dbg.watch()&quot;, otherwise the window will be empty.</p> <h3>Pausing while the inspector is open</h3> <p>The game will continue running while the inspector window is open.<br /> If you'd prefer it to pause the game, you can add:</p> <div> <div style="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>if(dbg.expand())return</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>to the top of your &quot;_update&quot; function.</p> <div> <div style="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>dbg.expand()</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>returns true when the window is expanded or false when collapsed.<br /> You can also force it open with:</p> <div> <div style="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>dbg.expand(true)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>or collapse it with:</p> <div> <div style="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>dbg.expand(false)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>For example, to invoke the editor from the Pico-8 built in menu:</p> <div> <div style="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>menuitem(1,&quot;debug&quot;,function()dbg.expand(true)end)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h3>Performance considerations</h3> <p>This snippet works by traversing your variables and building a &quot;snapshot&quot; tree structure. If you capture large variables every frame you might get some slow down, due to the traversal and possibly the Lua garbage collector cleaning up snapshots from previous frames. So make sure to remove your dbg.watch() calls when you want your program to run fast again.</p> https://www.lexaloffle.com/bbs/?tid=37822 https://www.lexaloffle.com/bbs/?tid=37822 Sat, 09 May 2020 02:37:20 UTC Instant 3D! <h1>Turn your 2D game into a 3D game!</h1> <img style="" border=0 src="/media/39676/pakutto boy 1.00_0.gif" alt="" /> <img style="" border=0 src="/media/39676/pic-oh mummy!_0.gif" alt="" /> <p>That's <strong>50% extra D!</strong><br /> 100% <em>guaranteed</em> to work, sometimes, if you're lucky.<br /> Simply paste this into the top of your 2D game, and be the envy of all your 2D coding friends!!!1!1</p> <p>(OK, I'll stop now.)</p> <p>This works best for top down games like &quot;Pic-oh mummy!&quot; by <a href="https://www.lexaloffle.com/bbs/?uid=13427"> @Hokutoy</a>, &quot;Pakutto boy&quot; by <a href="https://www.lexaloffle.com/bbs/?uid=15494"> @Konimiru</a> (both pictured)<br /> Admittedly it's more of a gimmick/experiment - the result isn't very playable due to not being able to see the whole play area.</p> <p>I mainly just wanted to see if it would work :)</p> <div> <div style="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>_={ map=map, spr=spr, sspr=sspr, pset=pset, circ=circ, camera=camera, cx=0, cy=0, cyoff=32, ch=32, d=128, ymid=0, s2w=function(x,y) return (x-_.cx)-64,_.ch,128-(y-_.cy) end, proj=function(x,y,z) local scale=_.d/z return x*scale+64,y*scale+_.ymid,scale end } camera=function(x,y) _.cx=x or 0 _.cy=(y or 0)+_.cyoff end camera(0,0) pset=function(x,y,c) local wx,wy,wz=_.s2w(x,y) if(wz&lt;1)return local px,py=_.proj(wx,wy,wz) _.pset(px,py,c) end circ=function(x,y,r,c) local wx,wy,wz=_.s2w(x,y) if(wz&lt;1)return local px,py,scale=_.proj(wx,wy,wz) _.circ(px,py,r*scale,c) end sspr=function(sx,sy,sw,sh,x,y,w,h,fx,fy) w=w or sw h=h or sh local wx,wy,wz=_.s2w(x+w/2,y+h) if(wz&lt;1)return local px,py,scale=_.proj(wx,wy,wz) local pw,ph=w*scale,h*scale -- sub pixel stuff local x0,x1=flr(px-pw/2),flr(px+pw/2) local y0,y1=flr(py-ph),flr(py) _.sspr(sx,sy,w,h,x0,y0,x1-x0,y1-y0,fx,fy) end spr=function(n,x,y,w,h,fx,fy) if(not n or not x or not y)return -- convert to equivalent sspr() call w=(w or 1)*8 h=(h or 1)*8 local sx,sy=flr(n%16)*8,flr(n/16)*8 sspr(sx,sy,w,h,x,y,w,h,fx,fy) end map=function(cx,cy,x,y,w,h,lyr) cx=cx or 0 cy=cy or 0 x=x or 0 y=y or 0 w=w or 128 h=h or 64 -- near/far corners local fx,fy,fz=_.s2w(x,y) local nx,ny,nz=_.s2w(x,y+h*8) -- clip nz=max(nz,0.5) if(fz&lt;=nz)return -- project local npx,npy,nscale=_.proj(nx,ny,nz) local fpx,fpy,fscale=_.proj(fx,fy,fz) -- clamp npy=min(npy,128) -- rasterise local py=flr(npy) while py&gt;=fpy do -- floor plane intercept local g=(py-_.ymid)/_.d local z=_.ch/g -- map coords local mx,my=cx,(fz-z)/8+cy -- project to get left/right local lpx,lpy,lscale=_.proj(nx,ny,z) local rpx,rpy,rscale=_.proj(nx+w*8,ny,z) -- delta x local dx=w/(rpx-lpx) -- sub-pixel correction local l,r=flr(lpx+0.5)+1,flr(rpx+0.5) mx+=(l-lpx)*dx -- render tline(l,py,r,py,mx,my,dx,0,lyr) py-=1 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><strong>EDIT 1:</strong> adjusted near plane to avoid numeric overflow. Defaults for omitted map() parameters.<br /> <strong>EDIT 2:</strong> also 3Dise sspr, pset and circ<br /> <strong>EDIT 3:</strong> sub-pixel logic<br /> <strong>EDIT 4:</strong> add map() layer parameter. fix gaps between adjacent sprites.</p> <h1>New version!</h1> <p>I've made a new version called <a href="https://www.lexaloffle.com/bbs/?tid=37982">Instant 3D plus!</a> :-).<br /> It should behave exactly like the original, but it's cleaned up quite a bit, and you can call the 3D functions directly to fix the bits that it doesn't get right automatically - or place things in exact 3D positions (like up in the air).</p> <img style="" border=0 src="/media/39676/d3d2.p8_1.gif" alt="" /> <p>More details in <a href="https://www.lexaloffle.com/bbs/?tid=37982">the other post</a>.</p> https://www.lexaloffle.com/bbs/?tid=37733 https://www.lexaloffle.com/bbs/?tid=37733 Mon, 04 May 2020 11:57:12 UTC Ramps <p>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<br /> <strong>This is an older work-in-progress version! <a href="https://www.lexaloffle.com/bbs/?tid=38221">Play the completed released version here</a>.</strong><br /> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</p> <p> <table><tr><td> <a href="/bbs/?pid=72871#p"> <img src="/bbs/thumbs/pico8_mot_ramps-14.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=72871#p"> Ramps (working title)</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=72871#p"> [Click to Play]</a> </td></tr></table> </p> <p>This is a little scaled-sprite 3D graphics engine inspired by the old classic game Power Drift.<br /> Still work in progress. Needs some basic game-play rules (laps, win/lose etc) and some difficulty balancing. But it's playable.</p> <p><img style="" border=0 src="/media/39676/rampsgame.p8_0.gif" alt="" /> <img style="" border=0 src="/media/39676/rampsgame.p8_1.gif" alt="" /> <img style="" border=0 src="/media/39676/rampsgame.p8_2.gif" alt="" /> <img style="" border=0 src="/media/39676/rampsgame.p8_3.gif" alt="" /> <img style="" border=0 src="/media/39676/rampsgame_4.gif" alt="" /></p> <p><strong>Update</strong></p> <ul> <li>Fixed bug in saving tracks to external cart.</li> <li>Added a little bit of steering assist to help line up the jumps.</li> <li>Some other tweaks I forget :)</li> </ul> <p><strong>Update 2</strong></p> <ul> <li>2 tracks</li> <li>Track selector</li> <li>Trees generate in the same place</li> </ul> <p><strong>Update 3</strong></p> <ul> <li>New track</li> <li>(Slightly) better physics/collisions</li> <li>Finish line and corner signs</li> </ul> <p><strong>Update 4</strong></p> <ul> <li>New larger test track</li> <li>Performance optimisations. LOD system. More aggressive view volume culling</li> <li>Tweaked the steering</li> </ul> <p><strong>Update 5</strong></p> <ul> <li>More performance tweaks</li> </ul> <p><strong>Update 6</strong></p> <ul> <li>More performance tweaks, thanks to <a href="https://www.lexaloffle.com/bbs/?uid=25532"> @freds72</a></li> <li>Updated physics to support driving upside down!</li> <li>New track with loop-the-loop (to test driving upside down :)</li> </ul> <p><strong>Update 7</strong></p> <ul> <li>AI cars. WIP. No collisions yet. Difficulty will probably be scaled down a bit eventually.</li> <li>Moved editor to separate cart.</li> <li>I tried to draw a palm tree.</li> </ul> <p><strong>Update 8</strong></p> <ul> <li>Different AI car colours.</li> <li>Fixed flip-Y logic when AI cars are upside down.</li> </ul> <p>*<em>Update 9</em></p> <ul> <li>Basic car collisions</li> <li>AI cars now steer around each other (and you)</li> </ul> <p><strong>Update 10</strong></p> <ul> <li>Cockpit graphics with animated front wheels.</li> <li>Main menu</li> <li>Race start sequence</li> </ul> <p><strong>Update 11</strong></p> <ul> <li>Fix wheel animation when frame rate &lt;30</li> <li>Fix NPC cars getting stuck on hills</li> </ul> <p><strong>Update 12</strong></p> <ul> <li>Lap and position displayed on screen</li> <li>Race finishes after 5 laps</li> <li>Note: Difficulty level logic is not hooked up yet!</li> </ul> <p><strong>Update 13</strong></p> <ul> <li>Hook up difficulty levels</li> <li>First attempt at engine sounds</li> <li>&quot;Ramps&quot; logo :)</li> </ul> https://www.lexaloffle.com/bbs/?tid=36777 https://www.lexaloffle.com/bbs/?tid=36777 Fri, 07 Feb 2020 12:34:37 UTC Dodge-balls <p> <table><tr><td> <a href="/bbs/?pid=69892#p"> <img src="/bbs/thumbs/pico8_mot_dodgeballs-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=69892#p"> mot_dodgeballs</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=69892#p"> [Click to Play]</a> </td></tr></table> </p> <p>A little &lt;560 chars game.<br /> Press left/right to dodge between the white balls.</p> <p>Vaguely inspired by &quot;Beam rider&quot;, so there's actually 5 lanes to jump between. I ran out of chars before I could draw the lanes though :)</p> https://www.lexaloffle.com/bbs/?tid=35909 https://www.lexaloffle.com/bbs/?tid=35909 Thu, 14 Nov 2019 10:06:19 UTC Immediate mode GUI <p> <table><tr><td> <a href="/bbs/?pid=69801#p"> <img src="/bbs/thumbs/pico8_mot_imgui-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=69801#p"> mot_imgui</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=69801#p"> [Click to Play]</a> </td></tr></table> </p> <p><a href="https://www.lexaloffle.com/bbs/files/39676/gui.p8_000.png" target=_view_image><img style="" border=0 src="https://www.lexaloffle.com/bbs/files/39676/gui.p8_000.png" width=160 height=160 alt="" /></a><a href="https://www.lexaloffle.com/bbs/files/39676/gui.p8_001.png" target=_view_image><img style="" border=0 src="https://www.lexaloffle.com/bbs/files/39676/gui.p8_001.png" width=160 height=160 alt="" /></a></p> <p>For some reason I felt like writing a little immediate-mode GUI library.</p> <p>It has:</p> <ul> <li>Labels</li> <li>Buttons</li> <li>Check boxes</li> <li>Radio buttons</li> <li>Sliders</li> <li>Text boxes</li> </ul> <p>You write into the text boxes on a little popup keyboard.</p> <p>It works best with the mouse, but it can run in controller-only mode, where you move the pointer around with the direction arrows (it's pretty cumbersome though).</p> <p>&quot;Immediate mode&quot; means you call the functions to draw the widgets in your drawing code, and it returns the user's input. It's generally easier to integrate into projects than a traditional widget UI would be, as you don't have to create and manage objects, you just draw the UI controls you need where and when you need them.</p> <p>The first tab in the source is the library. The second is a little demo, that doubles as the documentation :)</p> <p>UPDATE: Better looking buttons (thanks <a href="https://www.lexaloffle.com/bbs/?uid=25532"> @freds72</a>)</p> https://www.lexaloffle.com/bbs/?tid=35889 https://www.lexaloffle.com/bbs/?tid=35889 Mon, 11 Nov 2019 08:08:41 UTC Creating a pseudo 3D racer - part 3 <p> <table><tr><td> <a href="/bbs/?pid=69744#p"> <img src="/bbs/thumbs/pico8_tudanawati-10.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=69744#p"> Pseudo 3D racer tutorial 17</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=69744#p"> [Click to Play]</a> </td></tr></table> </p> <p>This tutorial is part 3 of a series. View <a href="https://www.lexaloffle.com/bbs/?tid=35767">part 1</a> here.</p> <p>And the end of part 2 we had roadside objects drawn using scaled sprites and sections of the map.</p> <img style="" border=0 src="https://www.lexaloffle.com/bbs/files/39676/tut12_003.png" alt="" /> <p>Everything so far has been flat, so in this tutorial we will add some hills and valleys.<br /> This will require solving some overlap issues which we will solve using a clip rectangle &quot;trick&quot;, which will in turn set us up nicely for implementing tunnels - so we'll do that too.</p> <h1>Defining the pitch</h1> <p>First we'll define the pitch of each corner with a field called &quot;pi&quot; (not to be mistaken for the Greek letter and mathematical constant).</p> <div> <div style="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>road={ {ct=10,tu=0,bgl=bg_tree,bgr=bg_tree}, {ct=6,tu=-.25,bgl=bg_tree,bgr=bg_sign}, {ct=8,tu=0,pi=-.75,bgl=bg_tree,bgr=bg_tree}, {ct=4,tu=.375,bgl=bg_sign,bgr=bg_tree}, {ct=10,tu=0.05,pi=.75,bgl=bg_tree}, {ct=4,tu=0,bgl=bg_tree,bgr=bg_tree}, {ct=5,tu=-.25,bgl=bg_tree,bgr=bg_sign}, {ct=15,tu=0,pi=-.5,bgc=bg_beams}, {ct=12,tu=0,bgl=bg_house,bgr=bg_house}, {ct=8,tu=-.5,bgl=bg_house,bgr=bg_sign}, {ct=8,tu=.5,bgl=bg_sign,bgr=bg_house}, }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This will define the gradient at the start of each corner. So a &quot;pitch&quot; of 1 means the ground rises 1 unit for every unit it advances forward - a 45 degree incline. Likewise -1 would be a 45 degree decline. And 0 is of course level.</p> <p>For brevity we've made the pitch optional - it will default to 0 if not supplied.</p> <p>To make the hills and valleys smooth the pitch will smoothly interpolate between values across each corner. We will calculate the delta to add to the pitch for each segment in the _init() method:</p> <div> <div style="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> -- calculate the change in pitch -- per segment for each corner. for i=1,#road do local corner=road[i] local pi=corner.pi or 0 local nextpi=road[i%#road+1].pi or 0 corner.dpi=(nextpi-pi)/corner.ct corner.pi=pi 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 pitch delta is stored in field &quot;dpi&quot; (not to be confused with &quot;dots per inch&quot; - naming stuff is hard).</p> <h1>Drawing</h1> <p>The pitch affects the vertical direction of the road as it is drawn. We actually already have a &quot;yd&quot; variable which is added to the y coordinate after each segment is drawn. It's just that until now the yd has always been 0, so the ground has always been flat. Now we can use it to create the hills and valleys.</p> <p>First we calculate the initial value at the start of _draw()</p> <div> <div style="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> -- direction local camang=camz*road[camcnr].tu local xd=-camang local yd=road[camcnr].pi+road[camcnr].dpi*(camseg-1) local zd=1</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This is the start value &quot;pi&quot; plus the delta &quot;dpi&quot; added for every segment the camera has traversed along the corner.</p> <p>Then we add &quot;dpi&quot; to the &quot;yd&quot; after each segment is drawn, similar to how we've added the turn &quot;tu&quot; to &quot;xd&quot;:</p> <div> <div style="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> -- turn and pitch xd+=road[cnr].tu yd+=road[cnr].dpi</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This is all we need to give the road a smooth rising and falling effect.<br /> <table><tr><td> <a href="/bbs/?pid=69744#p"> <img src="/bbs/thumbs/pico8_tudanawati-6.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=69744#p"> Pseudo 3D racer tutorial 13</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=69744#p"> [Click to Play]</a> </td></tr></table> </p> <h3>A note on camera movement</h3> <p>It's worth pointing out that the camera automatically follows the shape of the hills and valleys smoothly, without us having to write any specific code.</p> <p>This works, because the camera position is skewed in the direction of the segment it is on. Back in tutorial one we implemented the skew function like this:</p> <div> <div style="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 skew(x,y,z,xd,yd) return x+z*xd,y+z*yd,z 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>In particular the &quot;y+z*yd&quot; ensures the camera moves up and down with the shape of the road.</p> <p>Storing positions &quot;unskewed&quot; and skewing them at draw time has some advantages. Regardless of how the road turns or pitches:</p> <ul> <li>The ground is always at y=0</li> <li>The center of the road is always at x=0</li> <li>The sides of the road are always at x=+/-3</li> </ul> <p>This approach will also make implementing AI cars easier later on.</p> <h1>Clipping</h1> <p>The hills and valleys introduce some obvious overlap issues - sprites visible through hills, far away ground being drawn in front instead of behind.</p> <p>One approach to fixing this would be to change the road drawing order so that it is back-to-front as well.<br /> This would be a perfectly valid approach (although it would require careful management to incorporate the sprites and road/ground at the same time).</p> <p>However we're going to use a different approach. We're going to use clipping rectangles to prevent far away road/ground being drawn over near objects.</p> <p>It will work like this:</p> <ul> <li>We start with a clipping rectangle covering the whole screen. I.e. nothing is clipped.</li> <li>As we draw forward, we move the bottom of the clipping rectangle up so that it does not include the road and ground we have just drawn.</li> </ul> <img style="" border=0 src="https://www.lexaloffle.com/bbs/files/39676/tmp_001.png" alt="" /> <ul> <li>The clipping rectangle prevents anything drawn further down the road from overlapping with what we've already drawn. Anything that would overlap will simply be clipped away.</li> </ul> <p>The advantage of this method is that we can continue drawing the road as we walk forward along it. We don't have to change the algorithm too much.<br /> It also reduces overdraw, which was important back-in-the-day on platforms that were fill-rate limited. Pico-8 drawing is fast enough that it doesn't really matter though.</p> <p>To start with we define the initial clip region before the main drawing loop:</p> <div> <div style="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> -- current clip region local clp={0,0,128,128} clip()</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 &quot;clp&quot; array defines the left, top, right and bottom values in that order.</p> <p>We reduce the clip region after drawing each segment, immediately after adding the background sprites (for reasons we'll see later):</p> <div> <div style="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> -- reduce clip region clp[4]=min(clp[4],ceil(py)) setclip(clp)</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 &quot;min&quot; function ensures that the bottom of the rectangle only ever moves up, which is important for drawing crests of hills correctly.</p> <p>&quot;setclip&quot; is simply a helper function that sets the Pico-8 clip region for drawing:</p> <div> <div style="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 setclip(clp) clip(clp[1],clp[2],clp[3]-clp[1],clp[4]-clp[2]) 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>Putting this together we get:<br /> <table><tr><td> <a href="/bbs/?pid=69744#p"> <img src="/bbs/thumbs/pico8_tudanawati-7.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=69744#p"> Pseudo 3D racer tutorial 14</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=69744#p"> [Click to Play]</a> </td></tr></table> </p> <h2>Clipping sprites</h2> <p>The hills are no longer see through, but now the background sprites are not being drawn correctly. The problem is the sprites are being drawn after the road, when the clipping rectangle has been reduced to cover just the sky. We could reset the clipping rectangle to cover the whole screen before drawing the sprites, which would improve things, but sprites would still be visible through the hills. </p> <p>What we really need to do is draw each sprite using the clipping rectangle that was active when its segment was drawn, which we can do by storing a copy of the clip rectangle in the sprites array.</p> <p>We'll add a &quot;clp&quot; parameter to addbgsprite:</p> <div> <div style="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 addbgsprite(sp,sumct,bg,side,px,py,scale,clp) ... -- add to sprite array add(sp,{ x=px,y=py,w=w,h=h, img=bg.img, mp=bg.mp, flp=flp, clp={clp[1],clp[2],clp[3],clp[4]} }) 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>It's important that we create a new array and copy each of the &quot;clp&quot; fields individually, so that we get a snapshot of the &quot;clp&quot; array at that point. Referencing &quot;clp&quot; directly would not work, because &quot;clp&quot; changes as the road is drawn.</p> <p>Now we update the calls in the main drawing loop to pass in the clipping rectangle:</p> <div> <div style="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> -- add background sprites addbgsprite(sp,sumct,road[cnr].bgl,-1,px,py,scale,clp) addbgsprite(sp,sumct,road[cnr].bgr, 1,px,py,scale,clp) addbgsprite(sp,sumct,road[cnr].bgc, 0,px,py,scale,clp)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Finally we apply the clipping rectangle in drawbgsprite:</p> <div> <div style="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 drawbgsprite(s) setclip(s.clp) ...</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>With this in place we should have hills and valleys drawn correctly, including the background sprites.<br /> <table><tr><td> <a href="/bbs/?pid=69744#p"> <img src="/bbs/thumbs/pico8_tudanawati-8.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=69744#p"> Pseudo 3D racer tutorial 15</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=69744#p"> [Click to Play]</a> </td></tr></table> </p> <h1>Tunnels</h1> <p>For the last part of this tutorial we will implement basic tunnels. Tunnels give racing games a cool change of environment and they're just fun to drive through.</p> <p>And with the clipping rectangle logic is in place they're reasonably straightforward to implement.</p> <p>It's essentially an extension of the clipping logic used for the ground. But now we also reduce the top of the clipping rectangle as we draw the tunnel ceiling, and the left and right sides as we draw the tunnel walls.</p> <p>We'll start by defining the tunnel in our road:</p> <div> <div style="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>road={ {ct=10,tu=0,bgl=bg_tree,bgr=bg_tree}, {ct=6,tu=-.25,bgl=bg_tree,bgr=bg_sign}, {ct=8,tu=0,pi=-.75,bgl=bg_tree,bgr=bg_tree}, {ct=8,tu=0,tnl=true}, {ct=4,tu=0,tnl=true}, {ct=8,tu=0,pi=.75,tnl=true}, {ct=8,tu=-.5,pi=.75,tnl=true}, {ct=4,tu=0,tnl=true}, {ct=8,tu=.5,tnl=true}, {ct=4,tu=0,pi=-.5,tnl=true}, {ct=8,tu=0,pi=-.5,tnl=true}, {ct=4,tu=.375,bgl=bg_sign,bgr=bg_tree}, {ct=10,tu=0.05,pi=.75,bgl=bg_tree}, {ct=4,tu=0,bgl=bg_tree,bgr=bg_tree}, {ct=5,tu=-.25,bgl=bg_tree,bgr=bg_sign}, {ct=15,tu=0,pi=-.5,bgc=bg_beams}, {ct=12,tu=0,bgl=bg_house,bgr=bg_house}, {ct=8,tu=-.5,bgl=bg_house,bgr=bg_sign}, {ct=8,tu=.5,bgl=bg_sign,bgr=bg_house}, }</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Tunnel corners are denoted by &quot;tnl=true&quot;.</p> <p>I've placed the tunnel section towards the start of the road so that it's quicker to get to for testing and debugging. (It can be moved later once everything is working correctly).</p> <h2>Tunnel front face</h2> <p>Next we'll draw the tunnel face.<br /> We need to detect the first corner tunnel (&quot;tnl&quot; is set to true) where the <em>previous</em> corner was not a tunnel, which we can do by tracking the tunnel flag for the current corner, and the previous corner.</p> <p>We set the initial value of the previous tunnel flag before the main drawing loop:</p> <div> <div style="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> -- previous tunnel flag local ptnl=road[cnr].tnl</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Strictly speaking this should be set to the &quot;tnl&quot; property of the previous segment, but it doesn't actually make any noticeable difference for our purposes.</p> <p>Inside the main loop we compare it with the current segment's tunnel flag, and draw the tunnel face at the start of the tunnel:</p> <div> <div style="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> -- draw tunnel face local tnl=road[cnr].tnl if tnl and not ptnl then drawtunnelface(ppx,ppy,pscale) 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>Note that the tunnel face will be drawn at the <em>start</em> of the current segment. This means we must use the <em>previous</em> cursor position (ppx, ppy, pscale), as cursor positions are for the <em>end</em> of their segment.</p> <p>We also need to copy &quot;tnl&quot; to &quot;ptnl&quot; just before the loop ends, so that &quot;ptnl&quot; is set correctly for the next segment:</p> <div> <div style="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> -- track previous projected position ppx,ppy,pscale=px,py,scale ptnl=tnl</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>We will draw the tunnel face by drawing rectangles around the mouth of the tunnel.<br /> We'll start by creating a helper function that takes the projected road position and calculates a rectangle describing the tunnel floor, ceiling and walls.</p> <div> <div style="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 gettunnelrect(px,py,scale) local w,h=6.4*scale,4*scale local x1=ceil(px-w/2) local y1=ceil(py-h) local x2=ceil(px+w/2) local y2=ceil(py) return x1,y1,x2,y2 end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <img style="" border=0 src="https://www.lexaloffle.com/bbs/files/39676/tmp2_000.png" alt="" /> <p><em>The red rectangle is the &quot;tunnel rectangle&quot;. I.e. the mouth of the tunnel.</em></p> <p>We're allowing 6.4 units of width for the road, rather than 6, because the red and white shoulder things stick out another 0.2 units each way.<br /> The tunnel will be 4 units high, which is low enough to make the tunnel feel claustrophobic but high enough to be above the camera.</p> <p>Using this we can implement drawtunnelface:</p> <div> <div style="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 drawtunnelface(px,py,scale) -- tunnel mouth local x1,y1,x2,y2=gettunnelrect(px,py,scale) -- tunnel wall top local wh=4.5*scale local wy=ceil(py-wh) -- draw faces if(y1&gt;0)rectfill(0,wy,128,y1-1,7) if(x1&gt;0)rectfill(0,y1,x1-1,y2-1,7) if(x2&lt;128)rectfill(x2,y1,127,y2-1,7) 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>Essentially we're drawing a rectangle that extends from the top of the facing wall down to the top of the tunnel mouth. Then we draw two more rectangles either side of the mouth down to the ground.</p> <img style="" border=0 src="https://www.lexaloffle.com/bbs/files/39676/tmp2_001.png" alt="" /> <p>The last thing we need to do is restrict the clipping rectangle to the tunnel mouth.<br /> We'll create this function to adjust the clipping rectangle:</p> <div> <div style="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 cliptotunnel(px,py,scale,clp) local x1,y1,x2,y2=gettunnelrect(px,py,scale) clp[1]=max(clp[1],x1) clp[2]=max(clp[2],y1) clp[3]=min(clp[3],x2) clp[4]=min(clp[4],y2) 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>And update the tunnel face drawing code in the main drawing loop:</p> <div> <div style="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> -- draw tunnel face local tnl=road[cnr].tnl if tnl and not ptnl then drawtunnelface(ppx,ppy,pscale) cliptotunnel(ppx,ppy,pscale,clp) setclip(clp) 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 we should have a front facing tunnel wall:<br /> <table><tr><td> <a href="/bbs/?pid=69744#p"> <img src="/bbs/thumbs/pico8_tudanawati-9.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=69744#p"> Pseudo 3D racer tutorial 16</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=69744#p"> [Click to Play]</a> </td></tr></table> </p> <h2>Tunnel interior</h2> <p>Next we need to draw the tunnel interior. This will consist of the ceiling, walls and road.</p> <p>First we need to separate the road drawing code from the ground drawing code, so that we can just draw the road when inside the tunnel. We'll move the ground drawing code out into it's own function:</p> <div> <div style="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 drawground(y1,y2,sumct) if(flr(y2)&lt;ceil(y1))return -- draw ground local gndcol=3 if((sumct%6)&gt;=3)gndcol=11 rectfill(0,ceil(y1),128,flr(y2),gndcol) 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>And delete the &quot;draw ground&quot; lines from drawroad.</p> <p>We'll update the drawing code in the main loop to draw the ground or tunnel interior as appropriate, then draw the road:</p> <div> <div style="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> -- draw ground/tunnel walls local sumct=getsumct(cnr,seg) if tnl then drawtunnelwalls(px,py,scale,ppx,ppy,pscale,sumct) else drawground(py,ppy,sumct) end -- draw road drawroad(px,py,scale,ppx,ppy,pscale,sumct)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>We draw the tunnel walls by comparing the tunnel rectangles at the start and end of the current segment.</p> <img style="" border=0 src="https://www.lexaloffle.com/bbs/files/39676/tmp3_000.png" alt="" /> <p>We draw a ceiling rectangle to connect the top of each tunnel rectangle.<br /> Then we draw wall rectangles on each side to connect the left and right sides.</p> <img style="" border=0 src="https://www.lexaloffle.com/bbs/files/39676/tmp3_001.png" alt="" /> <p>We'll use an alternating colour for effect.</p> <div> <div style="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 drawtunnelwalls(px,py,scale,ppx,ppy,pscale,sumct) -- colour local wallcol=0 if(sumct%4&lt;2)wallcol=1 -- draw walls local x1,y1,x2,y2=gettunnelrect(px,py,scale) local px1,py1,px2,py2=gettunnelrect(ppx,ppy,pscale) if(y1&gt;py1)rectfill(px1,py1,px2-1,y1-1,wallcol) if(x1&gt;px1)rectfill(px1,y1,x1-1,py2-1,wallcol) if(x2&lt;px2)rectfill(x2,y1,px2-1,py2-1,wallcol) 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 final step is to update the reduce-clip-region logic in the main drawing loop to handle tunnels.</p> <div> <div style="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> -- reduce clip region if tnl then cliptotunnel(px,py,scale,clp) else clp[4]=min(clp[4],ceil(py)) end setclip(clp)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>And this completes our tunnel:<br /> <table><tr><td> <a href="/bbs/?pid=69744#p"> <img src="/bbs/thumbs/pico8_tudanawati-10.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=69744#p"> Pseudo 3D racer tutorial 17</a><br><br> by <a href="/bbs/?uid=39676"> Mot</a> <br><br><br> <a href="/bbs/?pid=69744#p"> [Click to Play]</a> </td></tr></table> </p> <h1>Next steps</h1> <p>This is the end of part 3. In part 4 we'll add some cars to overtake.</p> https://www.lexaloffle.com/bbs/?tid=35868 https://www.lexaloffle.com/bbs/?tid=35868 Sat, 09 Nov 2019 23:30:38 UTC