Log In  

First, thank you to everyone in the Pico-8 community for being so amazing. With a heavy heart, I have had to regretfully shut down PlayPico after 3 years. I no longer have the time or finances to maintain the site, as I have a very demanding job and a 4th child due 3 weeks from today (PlayPico was a labor of love and cost around $30/mo to run). Other than the finances, I simply don't have the time; the site required a lot of maintenance, and over the past 3 years, I probably invested over 1000 hours into development and maintenance. It started growing in users to the point where more and more new features were becoming absolutely necessary, and it was getting neglected. Several key features were also broken after the BBS updates, and while I fixed many of them, I couldn't find time to fix all of them or continue to keep up should the same issues arise again.

Thanks again for everything, everyone! I hope PlayPico at least brought you all some joy, that's all it was ever meant to do. I'm sorry that it can't continue, but I hope it served you all well while it was there. It had a good run.

Love you all,

  • Scathe
P#65167 2019-06-13 01:56


Hey all, I'm not sure how long account linking has been broken on PlayPico (it was just reported to me), but the feature has been restored. I apologize if you've previously tried to link your accounts and were unsuccessful.

P#63747 2019-04-21 02:18


Hey all,

Hopefully it doesn't seem too spammy to start a new thread instead of updating the old one, but the old thread got pretty huge (including the original post), so instead of adding more to it, I figured it best to start a new thread.

For those who don't know, PlayPico is my personal curated list of only complete games (see the original thread if you're curious as to how games qualify for the list, why some might not be there, etc.)

Finally, after almost 2 years of PlayPico history and 3 years of PICO-8 itself, we've hit 500! Great job to everyone who made these amazing games, and keep 'em coming!

We got a facelift!
This has been a long time coming, as PlayPico needed a facelift for a long time. The old version was pretty much just using Bootstrap 3 in its default state. This is because the site started off as something just for myself that I ran locally on my computer and wasn't public anyway, and also my concern was mostly with developing the server-side aspect, so I tossed together something that would look decent with as little effort as possible. Bootstrap has been completely scrapped in favor of doing my own design writing 100% custom SCSS from scratch.

There was a new design I created very similar to this one in December of 2017 during Christmas vacation from work, but I ultimately got extremely busy at work after the new year, and after I got back to it, it was one of those things where you're like "I've learned so much since then, and this code is trash". There were multiple attempts after that to either rewrite the theme, rewrite the entire back-end, or both (recreate the whole site from scratch). This update was not a culmination of any of those attempts, but rather a sort of "soft merge". I took some of the theme I wrote in December (which was built on Materialize.css, while this one uses no frameworks at all), and just put it on the existing back-end, eliminating Bootstrap from the front-end completely.

For the back-end, well, I've been slowly implementing some of the stuff I came up with for that over the past few months, so a lot of that has silently been going live for a while. The new theme required a bit of new back-end code that was made just for it, and adjusting some of what was already there, which is why I couldn't give an option to "Use the Old Theme" as an alert message like some sites do when they first migrate to a new theme (think Reddit). It's impossible to use the old theme now, unfortunately, as it would be very broken, so I couldn't give the option. It still isn't using the latest version of my Lightning MVC Framework, but it has been migrated over time to a semi-recent version, and it's in the pipeline to get it fully migrated.

Some things I learned that might help developers, no matter what kind of developer, just to "get shit done"
I spent a LOT of time getting nothing done on PlayPico, even though I was actively working on it. Here's what I learned, and how I started actually getting things done on it:

If you start a complete rewrite on an existing project, you might not realize what a big undertaking it's going to be. You might look back on initial development and it might seem like it didn't take as long as it probably did, especially if it was something that evolved over time and just took "a few hours here and there". I learned that PlayPico is a much, much bigger project than it appears to be on the surface, and even I was fooled by its simplicity. There are so many facets to it though that are easy to forget about until you really delve into the features, and it's easy to forget about lots of smaller features that add up to a mountain of molehills.

When you get into a rewrite project, something that SEEMS small might actually be practically insurmountable. It can also be deceptive when you think "well, I can just steal old code whenever I need to do anything complicated, modifying it as necessary, but the logic and basics will be there". That's SOOO deceptive, and can lead to even MORE effort rewriting existing logic to work in a completely new environment. It depends on how much time you can dedicate to it, how likely you are to get pulled away for extended periods of time, and how big the project ACTUALLY is once you get into it vs. how big you THOUGHT it was. The best course of action, in my humble option and based on experience, is to take what you have and rewrite chunks of it at a time. You can still keep your full plan for what you want it to be when rewritten, just start with what you have and do bits at a time instead of the "completely scrap it and reference if needed" ideology -- it's a trap! Make a list of things you want to do and prioritize them in order of importance first, and second, how fast you can launch each thing integrated with the existing system. You'll have the full rewrite done eventually, but if you try to do it all at once, you never will. Trust me on this.

New features!

  • Likes! - Registered users can now like games, and these games will be saved to their profile. You can then view them in your Likes page.
  • Sharing - You can now share games on Facebook and Twitter with one click.
  • Downloads - You can now download the '.p8.png' versions of games from that game's "play" page. This has been a longstanding feature request and has finally been added.
  • Sorting in ALL games lists - Previously, you could only sort games on the "All Games" page; now you can sort them on all pages that show a list of games (except "Home/Featured", because they are meant to be in a random order).
  • View counts in games lists - This feature has returned, but for the reasons detailed in the original thread, I did not bring back the ability to sort by views. Basically, it just breeds a "rich get richer" situation, where everyone was sorting by view count and playing the most viewed games. I feel like all the games on the site are amazing and deserve an equal opportunity to get played, so sorting by view count will likely never return.

Regression :(

  • I temporarily removed the ability to play games on touch devices. The reason is because it would have had to be redone with the new theme, and it frankly never worked very well anyway. In fact, it was pretty much unplayable because it was quickly hacked together. It deserves to be done right, and it will be the next incoming feature.
  • Sorting games is no longer dynamic; you are now directed to new URLs with sorting as URL parameters. Similar to dynamic searching as you would type in the search bar (a kind of filtering that was removed a while ago due to restrictions), this was removed due to performance reasons with the new layout, and allows me to implement inevitable features such as pagination, which wouldn't be possible under the old system. When we only had a few hundred games, this all ran fine as a client-side feature, but as we've approached 500+ games and made a more complex layout, it just doesn't perform well anymore. Pagination is inevitably in the future as well (I'll probably just do lazyloading so it doesn't change the experience much, but CTRL+F searching won't be available anymore.) Pagination would improve the load times of the "All Games" page significantly, although with 500 games it still performs decently, but it definitely has to be done.

TODO & Upcoming features
There's still a lot to do:

  • Support for gaming on mobile (this'll be the very next feature added)
  • User profiles. You'll be able to add a bio, set preferences such as timezone (and all dates/times on the site will be shown in that timezone), see a list of all games you've recently played, optionally opt into weekly notifications about when your favorite games have been updated or favorite authors have new games, etc.
  • Author verification. Once an author registers on the site, they will be able to get verified, and have some degree of control over their games on PlayPico. They will be able to enable/disable comments (yes, this means a commenting feature is coming, ONLY if a verified author enables them), edit their game titles and descriptions on the site, manually trigger their cartridges to be updated from the BBS immediately if they don't want to wait for automatic updates (which runs once every 2 days automatically), remove their game(s) from the site, request that none of their future games get added to the site, and more.
  • Flagging games. Registered users will be able to flag games for various reasons: "Not Working", "Inappropriate", etc.
  • "Code" tab on Play pages will have additional tabs that show the graphics and map data as actual rendered graphics, similar to the way they're shown in the PICO-8 desktop application. SoundFX maybe possible, but don't ask me to make a music tracker :P

Since the new design and features are so new, there may be bugs (and there probably are). I encourage you to PLEASE contact me if you find any issues or have any requests or suggestions for features or changes.

Sorry for the long-winded wall of text, but I'm very excited to share this with all of you and get feedback on it. It's actually been over 9 months in the making (a little on and a lot off, with several versions scrapped in between), and I'm anxious to get feedback and keep it moving forward from here.

Thanks for your support everyone!

P#55397 2018-08-20 00:40 ( Edited 2018-09-20 23:09)


Hey all,

After doing another round of adding games last night, which followed a month-long hiatus, which also followed an almost 2 month long hiatus, which also followed a whopping four month long hiatus, I decided to look at the analytics for the site to see if anyone is actually using it. It turns out, it's getting around 4,000 unique visitors per month, so it seems like a lot of you may have been let down by the gaps in adding new games, so I think it's time to address what's going on and why this has been happening.

As some of you may know, a month after the site was launched in October 2016, my daughter was born in November. This didn't really change anything though about the site's continued updates, and new content was added weekly at minimum for nearly a year following the site's launch. It was when I got a new job in September of 2017 that things started getting fewer and further between.

My new job is very demanding. The company I work for does most of the websites for Sony Music, and we handle most of their individual label websites, many of their major internal web application tools, and pretty much all of the websites for artists signed under any of the Sony labels. We do all of this with 3 developers including myself, and one is designated solely to Sony's internal tools as well as those of Ticketmaster, so there are two of us handling everything else (including the maintenance of all the existing websites we've done). I believe our average throughput is around 300-350 website launches per year, with about 35% of those being custom WordPress builds, and the other 65% being static.

Doing work within the music industry is cool, but because the turnaround time has to be so quick, there's a lot of overtime. I personally average about 3.5 websites built from scratch and launched per week, and that's while about a third of my time is also spent providing updates and support for sites I've already built previously. Also, if anything goes wrong at any time day or night, holiday or weekend, it's an emergency that needs to be dealt with immediately. When I'm not working, I try to spend as much time as I can with my daughter - as she gets older and each milestone comes and goes, I realize that there are so many stages that come and go very quickly, and you can never get them back. You have to enjoy every moment before it's gone forever.

So what am I saying? Is this the end for PlayPico? No! In fact, last month I just renewed the domain for another 2 years! The hosting is on the same VPS I use for my personal website, my email, and the websites of two nonprofit organizations that I am paid to host/maintain, so I'm not going to cut off the hosting either (even if the NPOs cancel for some reason, it's been my personal email server for almost a decade, so it's not going anywhere).

The reason for this post was because I had no idea how many people were using the site. It's been more than half a year since the last time I looked at the analytics, and back then it was getting about a tenth of the traffic it does now. I feel like I owe a lot of people an explanation as to what's been going on. This is also a pledge that I will do what it takes to keep up with the site better and resume adding new features and improvements. I'm going to set a weekly schedule, one night every single week, where new games will be added, and I'm going to work on overhauling the UI/UX completely to give a far better experience.

If you read all this, and you've used PlayPico, please take half a second to give this post a star. I need to know how many of you are out there and how many care, that's a big motivator in all of this.

P#53051 2018-05-27 01:02 ( Edited 2018-05-27 14:13)


Please join me at https://www.twitch.tv/hearthmasta to watch me live stream PlayPico.com development tonight at 10:00 PM EST (2:00 AM UTC, or 20 minutes from the time of this posting)! Great music, great wine, learn web development and give me your feature requests and help me design and develop PlayPico for you!

Looking forward to seeing you there!

P#42862 2017-07-28 21:45 ( Edited 2017-07-29 01:45)


Right, so basically what I'm trying to do is, when a game is running in JS, get some kind of data from them on some kind of event in-game. Example: achievements in-game captured by a website in JavaScript. Anyone found a way to do this? GPIO maybe?

P#41254 2017-06-03 00:20 ( Edited 2017-06-05 02:52)


Cart #36034 | 2017-01-18 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

This is a Pac-Man game with procedurally generated maps, and two ghost AIs (Blinky (red) and Pinky (pink)). I only made two ghosts due to the obviously small map size. Each ghost has their own 'personality', just like in the real game, so each has a slight AI variance from the other.


Press <Shift> to turn the arcade cabinet on and off. Hold <Z> to see your score (pauses the game). The number of lives you have left is displayed in the far right column (vertical line of yellow dots). When you lose your last life, the final score will be displayed, and you can press <Z> or <X> to start a new game. Eat Power Pellets (flashing pellets in each corner) to be able to kill ghosts for a short time. The ghosts will flee from you during this time, so be quick and use the Power Pellets wisely! Eat all of the pellets and power pellets on the map by visiting every walkable tile to advance to the next level.


Regular pellet: 10 points
Power pellet: 20 points
Ghost kill: 50 points

Tips & tricks:

  • Blinky is always targeting the player directly, so will usually be following behind you. When the player eats a power pellet, Blinky will run to the bottom-left corner of the map.

  • Pinky is always targeting 3 tiles ahead of the player, based on which direction you're facing, so he is always trying to get in front of you. When you eat a power pellet, he will run to the top-right corner.

  • Enemies can never reverse their direction, even if that means taking the long away around an obstacle.

  • Enemies always respawn in their respective starting corners shortly after being eaten (I didn't have enough screen space to make a ghost house); after eating a ghost, be mindful of the top corners!

The code is pretty messy, but I'm working on a much better coded engine that's based around this type of game, and will share the engine with everyone so you can make your own Pac-Man-inspired games!

Enjoy, and feedback is always welcomed and encouraged! I don't know if the color scheme is too boring or difficult to see, but due to Pac-Man being yellow, with ghosts being red, pink, and then blue/white when the player is powered up, this limited my color options a bit for the walls and path colors. Paths are always a dark color in these types of games, and walls are always a lighter color than paths, so these are the colors I settled on for now. If you can come up with better colors for the walls and paths, let me know!


Version 1.3: Fixed a bug where touching a power pellet and colliding with a ghost at the same time resulted in both consuming the power pellet and dying simultaneously. Now you will correctly become invincible and kill the ghost.

Version 1.2: Buddy of mine thought the cabinet movement was too jarring when you press the directional buttons. I removed that interaction, but you can find the old version below.

Version 1.1: Fixed AI pass-through bug. The problem was that I was checking player->ghost collisions at the end of each update cycle, after both the player and ghosts had moved. I needed to check those collisions after each time a character moved, instead of at the end of the update iteration.

Cart #36019 | 2017-01-18 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

P#36009 2017-01-18 09:55 ( Edited 2017-01-28 04:50)


Hey all, I'm generating random Pacman maps for my Tiny TV Jam game. So far, I have what are called "perfect" mazes being generated, using a common algorithm similar to recursive backtracker. The problem is that a "perfect" maze is one with a start, an end, and only 1 path from start to end.

Pacman mazes aren't designed in such a way, as they are what are called "fully braided" mazes - they have zero dead ends, so all corridors are interconnected. I've done a little research into how a fully braided maze might be generated, and unfortunately there don't seem to be any actual algorithms for doing this. I have only found people who have asked about how to do it, but no real clear answers, only hints about how one might go about doing it.

The best suggestion I found was to generate a normal "perfect" maze, as I am doing now, and then making a "walker" that goes through the maze once it's generated, looks for dead ends (an open tile that's only connected to 1 other open tile), and then removes them either by deleting a wall to connect it to another corridor (if possible), or if not possible to connect it to another corridor, PLACE a wall to fill in the dead end. Then have it repeat walking through again until it's covered all open tiles and verifies all the dead ends are removed.

The problem is that this all sounds a little bit over my head. I have no idea how to go about doing this. I know that the game will not be playable if there are dead ends in the map, because the AI will inevitably get you at the dead end, so it would make the game impossible. Here's my map generator (press Z to generate new maps; orange tiles are walls, purple are paths):

Cart #35720 | 2017-01-15 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

P#35721 2017-01-14 21:23 ( Edited 2017-01-15 09:21)


I know at least 90% of us who trawl the forums already own PICO-8, but just thought I'd share for the few who don't. You can get PICO-8 in the "Beat the Average" bundle for currently less than $7, or pay $10 to add Voxatron. Lots of other good software in there as well.

Here's the sauce: https://www.humblebundle.com/game-developer-software-bundle.

P#34348 2016-12-28 03:06 ( Edited 2016-12-28 08:06)


Cart #30381 | 2016-10-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

This game is based on the 1986 Intellivision classic Thunder Castle. It has dragons, and... dragons! So far only level 1 is (basically) complete, and there are 3 map variations that are chosen at random on startup. Eventually it will have a title screen as well as 2 more levels (I believe you can get to level 2 currently, but it's totally incomplete, doesn't have the right enemies or powerups or the second and third map variations). Level 2 will have 2 wizard enemies instead of the dragon, and level 3 will have 3 demon enemies.

Press action/fire to use items you pick up. Avoid the comb. A couple items don't have their functionality yet. Get the bat to become invincible for a short time in order to slay the dragon!

Credit to @solar for the excellent sounds and music!

Video of the original INTV game: https://youtu.be/WiK1fumT18E?t=1m8s

P#30384 2016-10-09 01:23 ( Edited 2016-10-09 21:39)


I put together a page for my favorite PICO-8 games, and figured you'd all enjoy it! https://www.playpico.com/. At the time of this posting, there are currently 113 games - let's see how it grows!

Big update Jul 27th, 2017

The website has been completely rebuilt from scratch. I'm still working on some more features for it, but it's at a point now where I wanted to push the progress live. It's now built on top of my PHP MVC framework, Lightning, available here: https://github.com/clowerweb/Lightning.

  • UI facelift. It's still fairly similar to the old UI, but has many improvements in both aesthetics and usability.
  • Better performance; it should be snappier than ever now.
  • Featured games! The front page is now a list of 24 featured games, chosen randomly each week. You can still go to the full games list by clicking "All Games" in the navigation, but this is no longer the index page.
  • Every Tuesday, it'll choose 24 random games to feature on the front page. Once a game has been featured, it cannot be featured again for 90 days, to keep the rotation fair.
  • Registration and login! You can now register for the site and login. There aren't any profiles or anything yet, but there are many things coming that registered users will be able to do.
  • There's now a fully interactive volume control, where you can set the game volumes to whatever you like. This preference will be remembered in a cookie and persist through page reloads and from game to game.
  • Searching and sorting now "remember" your search term and sorting preference when clicking the back button to go back to the games list (by appending variables to the URL). This of course isn't a cookie and doesn't persist the same way as volume, it's just so that when you go back to the main list with the browser back button, you don't lose your search/sort.
  • Games now have their titles as the URL (previously, it was the game's ID in the database, for example "Barrier Blast" was at /play/316 -- it's now at /play/barrier-blast. Don't worry about your bookmarks - the old URLs now redirect to the new ones.)

That's about it for now. More to come!

I'm going to preemptively address a few things.

Why isn't my cart there?!

Please don't get offended - it could be one of several innocuous reasons:

  1. Your cart isn't a game. If it's a demo or something like that, while I do love and enjoy them, this page is just for games.
  2. Your game isn't complete or is a WiP that doesn't have enough completeness to provide a lot of gameplay.
  3. Your game is very similar to other games that are already on there. As much as I probably loved your SHMUP, I've already got like 3 or 4 of them on there, and I wanted the page to feature diversity. I didn't pick the SHMUPS that are there because I liked them more than yours (necessarily), I may have just come across yours after the others were already there.
  4. Your game is 2-player only. I don't have anything against games like this, but I personally don't have anyone to play them with to see if they're good.
  5. Your game has a game breaking bug. Fix it and let me know!
  6. Your game is too basic. Get creative, add some flavor and make it juicy, and let me know and I'll take another gander at it!
  7. If your game is new-ish, I don't update the page on daily intervals or anything, so give it time. If it's unique, complete, an actual game, and I see it, then it'll probably make its way on there eventually.
  8. I may not have seen your game - tell me about it! I haven't gone through literally every cart on the BBS, so it's entirely possible that I missed it.

I told you about my game X days ago and it isn't up there yet. Why?

If it fits any of points 1 through 6 above, that could be why, but it's probably because of point #7. I also might not even look at this thread every day.

Why is such-and-such incomplete/WiP/duplicate style/broken game on there when mine isn't?

Because it's my list and I'll do what I want :P I'm not necessarily going to adhere to every single above point 100% of the time. If I thought there was something especially awesome about a game that's not perfect, then I put it up there.

Will you provide a way to submit games to your site, similar to the BBS?

No. I like to keep this as a fairly diverse and quality controlled list. You are welcome to post here and bring my attention to your game, though - there's always the chance that it's not already on there because I haven't seen it yet.

Will you add a comments section to each game, similar to the BBS?

Probably not. The reason is because authors have already published their games to the BBS, and many also post them to itch.io and other sites. I don't want them to feel obligated to check yet another site for comments on their titles. Instead, there's a link from the game pages to the cart thread on the BBS, so players can jump to the BBS thread at any time to comment.

I don't want my game on there. Please remove it.

No problem. Just post something here asking me to remove it. Once I see your post and verify that the game is yours, it will be removed.


August 15th, 2017

  • Added a list of all authors, which takes you to pages listing their games
  • Added mobile controls (finally)
  • Added a few new games

July 25th, 2017

  • Game listings now use smaller icons
  • Games only show an updated date if they've been updated (previously, it would show the updated date the same as the published date if a game hadn't been updated, which looked confusing and messed up sorting by "Updated". The sorting now works properly)
  • Games now have their titles as the URL (previously, it was the game's ID in the database, for example "Barrier Blast" was at /play/316 -- it's now at /play/barrier-blast. Don't worry about your bookmarks - the old URLs now redirect to the new ones)
  • Removed view counts on main list page, and removed sorting by view count. You can still see the view count in each game's respective page. I did this because many users were sorting the games by view count, and those would end up with the most visibility, causing a "the rich get richer" effect. This helps even out the playing field
  • Misc UI changes and improvements
  • Added 13 games since July 18th

June 9th, 2017

  • Added 20 games (sorry about getting a bit behind, guys!)
  • We just broke 300 games!

March 19th - 20th, 2017

  • Moved the domain from games.clowerweb.com to playpico.com
  • Small UI tweaks
  • BIG update coming soon!

January 29th, 2017

  • Fixed a sorting problem that started happening when sorting by title, due to some titles accidentally being inserted into the DB with spaces and/or line breaks in them which confused the sorting script
  • Fixed a sorting problem with authors due to character case that was previously overlooked

January 18th - 23rd, 2017

  • Fixed gamepad support which had broken somehow
  • Changed default sort order to 'Newest' instead of by title, to give more exposure to newer games

January 4th, 2017

  • Added gamepad support (thanks @krajzeg)
  • Added BBS links to games inside game pages
  • Added title attribute to <img> tags with game title and author
  • Added a scraper to the admin section to check the BBS for game updates and show outdated carts
  • Misc. improvements to mobile layout (still no mobile controls yet)

Dec. 28th, 2016

  • Updated web player to 0.1.10c due to 60FPS bug
  • Some improvements to the mobile experience
        - Fixed huge game window too big to fit screen on mobile
        - Fixed game titles on front page wrapping to 3 lines on mobile and overlapping content underneath
  • Fixed linking to non-existent authors (such as with Collab16's "(Various authors)")
  • Game pages now have game title as page title
  • Fixed game page descriptions and code being narrower than the game window
  • Combined and minified JS libraries to hopefully make the site faster

Dec. 27th, 2016

  • Improved page loading speed
  • Updated web player to 0.1.10

Dec. 24th, 2016

  • Added sorting by title, author, publish date, updated date, and view count
  • Added search filtering by title/author

Dec. 22nd, 2016

  • Game titles are displayed more prominently
  • Authors are now listed on the main page
  • Publish date and latest updated date are now displayed
  • Games now track view count for each game
  • The page should be much more mobile-friendly (though I haven't added mobile controls to the games yet)
  • Descriptions are now much more robust and feature HTML
  • The source code of the games is now viewable, along with syntax highlighting. This shows the entire source, not just the code (it's like opening a .p8 file in a text editor)
  • Each game now has its own page, rather than popping up in a modal. This makes it so that users can link directly to games. It's also a lot less clunky for displaying the descriptions
  • Lazy loading of the cart images is now implemented. This should make the page load faster

Dec. 20th, 2016

  • Added a back end interface for adding/editing/removing games and authors (internal use only)

Dec. 16th, 2016

  • A couple of duplicate games were removed (some were the same game under different names)
  • Fixed an issue where some games weren't in the correct alphabetical order (there was a bug in my sorting function)
  • Renamed some games that I had listed incorrectly (mostly typos)
  • Finished adding all game authors with BBS profile links, release dates, and game instructions (if they were included with the game on the BBS)
P#30360 2016-10-08 22:45 ( Edited 2018-11-01 04:54)


Finally hit the milestone in Picoder development where I'm able to decode p8.png files in pure JavaScript. I couldn't have done it without the help of @gamax92 and @dddaaannn, as well as the source code of their respective projects PICOLOVE and PicoTool. It's been a long, bumpy ride, but here it is: http://clowerweb.com/picoder-test/. This will be used to import .png files into Picoder, and the decoding algorithms reversed in order to compile code (edited in Picoder's online IDE) to be playable in the web player for testing.

So huge shoutout and thanks to the above named! Hopefully the IDE itself will be available for public testing soon. Please let me know if you find any issues with the decoder - this is my chance to let you all test it and help me find bugs. Thanks!

Edit: source, if anyone wants it

P#30345 2016-10-08 20:24 ( Edited 2016-10-11 01:04)


For those who missed the Twitter post by Zep, he's published the official 0.1.6 changelog! He said the new version is coming this week.

Pico-8 0.1.6 changelog

P#19653 2016-04-08 19:09 ( Edited 2016-04-17 23:07)


Cart #19329 | 2016-03-20 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Sometimes, we want to be able to play animations in reverse. This is the advanced animation function with a reverse option. In this case, the foliage grows, stays for 5 seconds before shrinking away (playing the "grow" animation in reverse), disappears for 10 seconds, then repeats.

P#19328 2016-03-20 16:33 ( Edited 2016-03-20 20:37)


In the last post, we looked at some basic animation, collision and AI examples. In this one, we're going to take it a few steps further and improve upon these functions.

During the development of my game (tentatively titled "Castle of Thunder", which is a port of INTV's Thunder Castle), I realized that I was going to need some more advanced functionality, because the first enemy is a total of 4 sprites when walking horizontally, and 2 sprites when working vertically. Since we had all of our animation stepping, animation speed, frame numbers, etc stored as properties of our actor in our simple animation demo, it works great for 8x8 actors, but not as well when that actor is comprised of several sprites.

Further, I found that I needed to be able to specify x and y offsets for actors with multiple sprites, so that subsequent sprites can have custom positioning and aren't drawn on top of the first. I also needed a way to reset interrupted animations, so that when they start again, they don't start where they left off when they were interrupted. Finally, I needed a way to stop animations that aren't supposed to loop, as well as set a custom frame for when the animation is stopped, and set flipping for both x and y. That's a lot of added functionality, so I decided to rewrite the animation function from scratch, and instead of passing it a million parameters, we could pass it an object/table containing all of these options.

The object that we pass to the new animation function will be properties of the actor object. So basically, each animation will have its own table filled with all of the options for that animation. Here's an example:

e1={ -- enemy 1
    play={}, -- currently playing
    walklrh={}, -- walk l/r head
    walklrb={}, -- walk l/r body
    walklrt={}, -- walk l/r tail
    walklrf={}, -- walk l/r fire
    walkuh={}, -- walk up head
    walkut={}, -- walk up tail
    walkdh={}, -- walk down head
    walkdt={}, -- walk down tail
    deathh={}, -- death head
    deathb={}, -- death body
    deatht={}  -- death tail


-- sets up each anim with default options
-- we do this so eo don't have to set each one
-- individually above, to save lots of tokens
for k in pairs(e1a) do
  e1a[k].start=0 -- starting frame
  e1a[k].frames=4 -- number of frames in animation
  e1a[k].speed=7 -- animation speed
  e1a[k].flipx=false -- flip x
  e1a[k].flipy=false -- flip y
  e1a[k].loop=true -- loop animation?
  e1a[k].reset=false -- a frame, if you want to stop on a specific frame
  e1a[k].step=0 -- step counter, used to set anim speed
  e1a[k].current=0 -- current frame

-- set custom options where needed

-- walk left/right
e1a.walklrh.start=36 -- head
e1a.walklrb.start=40 -- body
e1a.walklrt.start=44 -- tail
e1a.walklrf.start=48 -- fire

-- walk up
e1a.walkuh.start=60 -- head
e1a.walkut.start=56 -- tail

-- walk down
e1a.walkdh.start=52 -- head
e1a.walkdt.start=56 -- tail

-- death
e1a.deathh.start=64 -- head
e1a.deathb.start=68 -- body
e1a.deatht.start=72 -- tail

Of course, you can also change any of these options dynamically at any time throughout the game if certain conditions are met or whatever. For example, my "walklr" animations are the same for walking left or right, but my sprites are facing right. I just check if the enemy is walking left, and set "flipx" to true on the "walklr" animations, and false again if he's walking right. Similarly, if he's walking up, his tail is flipped on the y axis.

Here's the final animation function:

function anim(a,anim,offx,offy)
  local sta=anim.start
  local cur=anim.current
  local stp=anim.step
  local spd=anim.speed
  local flx=anim.flipx
  local fly=anim.flipy
  local lop=anim.loop

  if(stp%flr(30/spd)==0)    cur+=1
  if(cur==anim.frames) then
    if(lop) then cur=0
    else cur-=1 end


  if(not offx) offx=0
  if(not offy) offy=0
  -- draw the sprite

It's not that much bigger than the simple version, but it's way more powerful. To call it for your huge enemy, you'd do something like this:

local an=e1.anim

anim(e1,an.walklrf,-8) -- fire
anim(e1,an.walklrh) -- head
anim(e1,an.walklrb,8) -- body
anim(e1,an.walklrt,16) -- tail

Here's a demo:

In this demo, the player can walk around, while the enemy just cycles through his walking animations every 5 seconds. Press Z to toggle the player being in a "dead" state.

In the next part, we're going to talk about more advanced movement and collisions (and how they relate to each other), and get back into AI. They are already in the demo (except for the AI) if you'd like to get a head start, but are a topic for another blog entry. Stay tuned for those!

P.S. - You are free to use any of the code provided, but please don't use the graphics, as they are being used in my upcoming game. Thanks!

P#19275 2016-03-17 19:59 ( Edited 2016-06-06 11:29)


Hey all, I have some music and sound effects in mp3 format (they are already 4-track chip tunes) that I need recreated in Pico-8! They don't have to be 100% true to the original, but I'd prefer if they were as close as possible. I've got a game that's deep into development and should be completed within the next week or two, and would love to put your name in the credits! There are about 18 mp3s in total, most of them are just small sound effects, and 6 or 7 of them are short pieces of music.

If you're interested, just reply here and I'll send you a link to the mp3 files. Thanks!

P#19221 2016-03-14 13:30 ( Edited 2016-03-26 03:48)


Allow me to preface this by saying that I'm new to Pico-8 and Lua, but have already fallen in love with it. I've already begun following the community on the BBS, subreddit, and Twitter, and I see a lot of questions about how to perform specific tasks that are necessary for almost every game. Anyone who has ever used a game engine previously may have had a lot of the basic things such as controls, animation, collision detection, or even AI handled for them, so they may not know how to roll their own code for such things.

I'm currently working on my first Pico-8 game, and have decided to document the process, and how I personally overcome various problems. Do know that my way is definitely not the only way - there may be solutions that work better, perform better, have smaller token counts, or are more elegant in general. These are just the solutions that I came up with. There isn't really any 'right' or 'wrong' per se, as long as it gets the job done, with the exception of 'lower token count = better' due to the limitations of the system. So, I'm going to try to solve these problems in the smallest possible token count that I can come up with.


In almost every game, characters need to be able to move around the map. The most common control systems are 2-way (only up and down or left and right), 4-way (up, down, left, right), and 8-way (4-way + diagonals). Any of these are simple enough to do in Pico-8.

Here is a 2-way control system

function _init()
  player={} -- initialize the player object
  player.speed=1 -- set a property 'speed' to 1 in the player object
  player.x=0 -- set the initial x coordinate
  player.y=0 -- set the initial y coordinate

function _update()
  if(btn(0)) player.x-=player.speed -- move left at player.speed
  if(btn(1)) player.x+=player.speed -- move right at player.speed

Simple enough. Now 4-way:

function _init()
  player={} -- initialize the player object
  player.speed=1 -- set a property 'speed' to 1 in the player object
  player.x=0 -- set the initial x coordinate
  player.y=0 -- set the initial y coordinate

function _update()
  if(btn(0)) then player.x-=player.speed -- move left at player.speed
  elseif(btn(1)) then player.x+=player.speed -- move right at player.speed
  elseif(btn(2)) then player.y-=player.speed -- move up at player.speed
  elseif(btn(3)) then player.y+=player.speed -- move down at player.speed

Using the elseif statements ensures that no more than one of these conditions can be met at a time, so it disallows diagonal movement. To allow diagonals for 8-way controls, simply change it to:

function _update()
  if(btn(0)) player.x-=player.speed -- move left at player.speed
  if(btn(1)) player.x+=player.speed -- move right at player.speed
  if(btn(2)) player.y-=player.speed -- move up at player.speed
  if(btn(3)) player.y+=player.speed -- move down at player.speed

Now multiple conditions are allowed to be met, allowing movement diagonally up+left, up+right, etc.


Pico-8 doesn't currently supply any functions for animation, so you have to roll your own code for handling them. So, I wrote an animation function that anyone is free to use for their games, and it should be pretty simple to use and handle most use cases (currently it doesn't support sprites larger than 8x8 or any kind of sprite scaling, but if you need those things then it should be easy enough to modify).

-- object, starting frame, number of frames, animation speed, flip
function anim(o,sf,nf,sp,fl)
  if(not o.a_ct) o.a_ct=0
  if(not o.a_st) o.a_st=0


  if(o.a_ct%(30/sp)==0) then
    if(o.a_st==nf) o.a_st=0


To use it, simply do: anim(player,0,3,10), where the player is what we're animating, 0 is the animation starting frame (basically the offset), 3 is the number of frames in the animation, and 10 is the speed. The 'fl' (or flip) parameter is optional and defaults to false. If set to true, it'll flip the sprite horizontally. You can find more information about it here, and here it is in action:

Cart #19159 | 2016-03-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA


To handle collisions with map tiles, you probably want to set a flag on the tile's sprite itself (those are the little colored buttons in the sprite editor under the scale slider - they start at 0 and go up to 7, so there are 8 possible flags in total). In this example, we're going to check for flag 0 using fget() and we're going to use mget() to see if the player's bounding box overlaps with a solid tile's bounding box.

We're additionally going to check for collisions with the world bounds as well, so that the player can't move outside of the world. Also, I wanted a way to be able to toggle collisions on or off with the player, either with map tiles, world bounds or both. So let's look at the setup:

function _init()
  w=128 -- width of the game map
  h=128 -- height of the game map
  -- collide with map tiles?
  -- collide with world bounds?

Here's we're defining the width and height of the map so that we can check for wold bounds collisions, and then we're setting up 'player.cm' and 'player.cw' to define whether they should collide with map tiles and world bounds. Now let's look at the collision detection code itself:

function cmap(o)
  local ct=false
  local cb=false

  -- if colliding with map tiles
  if(o.cm) then
    local x1=o.x/8
    local y1=o.y/8
    local x2=(o.x+7)/8
    local y2=(o.y+7)/8
    local a=fget(mget(x1,y1),0)
    local b=fget(mget(x1,y2),0)
    local c=fget(mget(x2,y2),0)
    local d=fget(mget(x2,y1),0)
    ct=a or b or c or d
   -- if colliding world bounds
   if(o.cw) then
     cb=(o.x<0 or o.x+8>w or
         o.y<0 or o.y+8>h)

  return ct or cb

Basically all this does is check if we want to collide with map tiles, and then looks for an overlap. It does the same thing with world bounds as well. If there's a collision, it returns true, and if not, then it's false. To use it, we'll add it to our movement code:

function move(o)
  local lx=o.x -- last x
  local ly=o.y -- last y

  -- 8-way movement
  if(btn(0)) o.x-=o.speed
  if(btn(1)) o.x+=o.speed
  if(btn(2)) o.y-=o.speed
  if(btn(3)) o.y+=o.speed

  -- collision, move back to last x and y
  if(cmap(o)) o.x=lx o.y=ly

What we're doing is allowing the player to move into the tile, but since they will get moved back if there's a collision before the frame is drawn, you won't see them appear to 'bounce back out'. Here it is in action:

Cart #19162 | 2016-03-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

You can read more about the collision function here.


AI is a more complex problem, and the solution really depends on your needs. For this case, we're going to be talking about Pacman AI, or how the pathfinding works and how to move the enemy around to chase the player. This is going to be rather complicated for me to try and articulate, even as simple as it is, but I'm going to do my best, so bear with me.

First, we need to start by thinking about what the rules are that the AI needs to follow. Here are the rules for my example AI:

  • Enemy cannot reverse directions (so it can't be moving left and then immediately turn around and start moving right).
  • Our target is the player, so it needs to find the shortest path to the player.
  • Enemy must keep moving, so if it reaches a target it shouldn't stop, but instead keep moving, looping around the shortest path around the target.

We could get into complex pathfinding algorithms such as A* or jump points, but I want to keep it oldschool. It's not going to be perfect like the more complex algorithms, but will find the correct path to the player the vast majority of the time. Look up "Pacman hiding spots", and you'll see that the algorithm we're about to use isn't perfect 100% of the time.

So here's how we're going to do it: whenever the AI has multiple directions it can move to (such as reaching an intersection), it will evaluate which tile that it has the option to move to is the shortest distance from the player. To do this, we use the Pythagorean Theorem, which is the square root of (from x - to x) squared + (from y - to y) squared. In Lua, that's sqrt((fx-tx)^2 + (fy-ty)^2), which returns the distance from a starting point to an ending point in a line. We can make a function out of this since we'll be using it to evaluate all of our optional directions:

function dst(fx,tx,fy,ty)
  return sqrt((fx-tx)^2+(fy-ty)^2)

But how do we know if we're at an intersection? We can use our collision code to check if the tiles around us are open!

local cl=colm(ex-1,ex-1,ey,ey+7)
local cr=colm(ex+8,ex+8,ey,ey+7)
local ct=colm(ex,ex+7,ey-1,ey-1)
local cb=colm(ex,ex+7,ey+8,ey+8)

It's messy, I know, but it works. "cl" means 'collision left', "cr" means 'collision right', etc., so we're adding 1 to our current position in each direction in order to see if we'd collide with that tile, and if not, it's open. Now that we know which tiles are open, we check their distances to the target using our dst() function:

local ld=dst(ex-4,tx+4,ey+4,ty+4)
local rd=dst(ex+11,tx+4,ey+4,ty+4)
local td=dst(ex+4,tx+4,ey-4,ty+4)
local bd=dst(ex+4,tx+4,ey+11,ty+4)

More messy code. The reason for the +/- 4's is that instead of using the top-left of our potential tiles that we can move to, I'm using the center point of them, so it's just an x/y offset. I'm not sure if it helps make it more accurate at all, but in my head it seems like it would help slightly.

The last thing we need to do to evaluate which direction to move in is to make sure that we aren't about to reverse directions. For that, I've set a property of my enemy object that tells me which direction it's moving in. So... the opposite of that direction is invalid, then.

local lo=not cl and e.m!=1 -- "left open" is true if there's no collision left and we're not moving right
local ro=not cr and e.m!=0 -- "right open" is true if there's no collision right and we're not moving left
local to=not ct and e.m!=3
local bo=not cb and e.m!=2

Now we need to know which of our valid directions is the shortest, using what we found for "ld", "rd", etc.:

-- shortest distance, I set it to map width here just to make
--it a number larger than than the possible distance between enemy and player
local sd=w

if(lo)           sd=ld
if(ro and rd<sd) sd=rd
if(to and td<sd) sd=td
if(bo and bd<sd) sd=bd

Now we need to set his moving direction for the next time this code runs, so he won't reverse directions on us. We'll also go ahead and move him:

if(lo and ld==sd) e.m=0
if(ro and rd==sd) e.m=1
if(to and td==sd) e.m=2
if(bo and bd==sd) e.m=3

if(e.m==0) e.x-=e.speed
if(e.m==1) e.x+=e.speed
if(e.m==2) e.y-=e.speed
if(e.m==3) e.y+=e.speed

Here it is in action:

Cart #19156 | 2016-03-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

Whew! I hope that all made sense, but if not, let me know! I'll be happy to answer any questions, and amend the post as necessary to correct or clarify things.

I plan to keep documenting my progress and solutions to other problems as I find them throughout the development process of my game. Since this post is so huge, I will probably make continuations of it in new posts instead of adding further onto this monstrosity.

Here's some stuff I'll be trying to delve into for my game:

  • Procedural map generation
  • Multiple AI-controlled enemies, each having different targets (one might target the player's exact center, one might target the tile behind the player, one might target in front of the player, etc. to vary it up a bit and keep them from grouping together too much).
  • Changing between game states, such as title screen, menu, winning and losing, etc.
  • Implementing a demo/attract mode in which the player is AI-controlled
  • Managing a player inventory and item pickups
  • Multiple AI modes (chase, scared/fleeing)
  • If the AI has multiple path options that are equidistant, choose a random one to make it less predictable
  • Increasing the difficulty of the game as players progress through levels
  • Scoring and keeping a hiscore
  • Adding sound effects and music (although I likely won't get into how to use these tools, as there are already great tutorials out there for that)
  • More as I begin to flesh out my game a little better

Thanks for reading, and I hope this helps someone! Happy coding :)

P#19166 2016-03-10 14:38 ( Edited 2018-03-25 12:38)


Cart #19162 | 2016-03-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

I've seen some questions about how to do collision detection in Pico-8, so figured I'd make another bare-bones demo, this one demonstrating collision detection with map tiles and/or world bounds. The function itself is 24 lines and 125 tokens, and includes flags for turning collisions on or off on an object (such as the player). Here's the full function:

function cmap(o)
  local ct=false
  local cb=false

  -- if colliding with map tiles
  if(o.cm) then
    local x1=o.x/8
    local y1=o.y/8
    local x2=(o.x+7)/8
    local y2=(o.y+7)/8
    local a=fget(mget(x1,y1),0)
    local b=fget(mget(x1,y2),0)
    local c=fget(mget(x2,y2),0)
    local d=fget(mget(x2,y1),0)
    ct=a or b or c or d
   -- if colliding world bounds
   if(o.cw) then
     cb=(o.x<0 or o.x+8>w or
           o.y<0 or o.y+8>h)

  return ct or cb

It will return true if a collision is detected, and the object has the flag(s) set for collisions. To set up the function for use, you need the following global variables/properties set:

w -- the width of your map
h -- the height of your map
[object].cm -- whether the object should collide with map tiles
[object].cw -- whether the object should collide with the world bounds (as defined above in w and h)

To call the function, simply use: cmap([object]).

P#19163 2016-03-10 12:17 ( Edited 2018-02-04 15:08)


Cart #19159 | 2016-03-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

This is a very simple animation function that anyone can use in their games. It's a bare-bones demo to keep everything as simple as possible. The function itself is only 14 lines and 74 tokens, and works like this:

anim(object, start frame, number of frames, speed (in frames per second), [flip])

In the demo, the object is the player, but it can be anything. Start frame is where the animation to be played begins (so in this case, moving left/right starts on frame 6, so we have anim(player,6,[..])). Number of frames is how many frames in that animation to play. We have 3, so anim(player,6,3,[..]). Speed is how fast the animation should play in frames per second (I have it set to 10 here). Lastly, flip is whether or not to flip the sprite horizontally (this parameter is optional, and the default is false).

To use it in your project, you only need to copy the following code, and call it as described above:

function anim(o,sf,nf,sp,fl)
  if(not o.a_ct) o.a_ct=0
  if(not o.a_st) o.a_st=0


  if(o.a_ct%(30/sp)==0) then
    if(o.a_st==nf) o.a_st=0


I hope this helps some of you who have had questions about how to animate in Pico-8. Good luck and have fun! :)

P#19160 2016-03-10 09:50 ( Edited 2018-04-21 04:34)


Cart #19156 | 2016-03-10 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA

What is it?

It's not a game, but rather a framework for a specific genre of games ala Pac-Man and Burgertime. It's a complete skeleton framework with all of the basic functionality to create these types of games.


  • A sample map with collision detection
  • Outside of map bounds = collision; nothing can move out of bounds
  • A player control system with automated assisted controls (it's difficult to line up exactly with a new corridor, so the controls will auto-correct to anticipate and compensate for this when a collision is detected)
  • Enemy AI and movement in "oldschool" style; it does not use A* or other complex pathfinding algorithms, but instead evaluates the closest next direction to move in when it detects an intersection, which is what the oldschool games did.
  • Enemy AI cannot reverse directions (classic Pac-Man Ai rule)
  • You can change the speed of the AI or player (as low as 0.1, and up to a speed of 2) without negatively affecting the collisions or AI.
  • Can easily support multiple AI-controlled units. Just call another eupd() for each of your AI characters.
  • It's currently 691 tokens

Planned features for future revisions:

  • Random map generation would be pretty cool as long as it doesn't make the engine much more token heavy than it already is.
  • I'd like to get it down to 500 tokens or less, if possible, which would definitely allow me to add more features comfortably.
  • Support for chase, scatter and scared (flee) modes?

I don't want to add too much more than that to it. It's intended to be small and then built upon on a case-by-case basis.

Known issues (updated Mar 8, 2016):

  • I don't think the AI will handle hitting a dead-end, so in its current form, I'm pretty sure any dead ends in your maps will break the AI (actually it might just start ignoring collisions and plow its way through the dead end). I just need to code in an exception to the rule that it can never reverse directions (and actually in Pac-Man for example, the AI could reverse direction during certain cases anyway, such as when you eat a power pellet, or on a timer where the AI enters 'scatter' mode).
  • A speed of 2 seems to be about the fastest that the collision system will support before it starts getting a bit wonky. 2 feels like a very fast speed though, so maybe I can let this bug slide for this use case. Let me know your thoughts.
  • A speed of less than 0.5 on the player seems to cause the assisted controls system to become unreliable. I'm not sure why anyone would want to make the player move around that slowly (seems it would be quite boring), but the issue is there nonetheless.
  • I'd like to improve the comments in the code. There isn't much there in the way of comments, and my naming conventions for variables and functions was mostly abbreviations or shortened versions of words, so it's not the most readable code in the world.

Can it easily support 8-direction movement, or is it just for mazes/corridors?

Yes, it can easily support 8-direction movement. Go to line 50, and change:

if(btn(0)) then p.x-=p.s
elseif(btn(1)) then p.x+=p.s
elseif(btn(2)) then p.y-=p.s
elseif(btn(3)) then p.y+=p.s


if(btn(0)) p.x-=p.s
if(btn(1)) p.x+=p.s
if(btn(2)) p.y-=p.s
if(btn(3)) p.y+=p.s

This will allow 8-direction movement for the player, and the AI supports it out of the box already. Be aware that enabling 8-direction movement in a corridor-based maze map such as the example provided, will sometimes cause players to move diagonally at intersections if they are holding down multiple directional buttons. The behavior feels weird in these types of games, so I have it set for 4-directions by default, but 8-direction would work well for roguelikes or Zelda-inspired adventure games.

What do I put for the CC-BY attribution if I use this in my game?

Just credit @clowerweb (my Twitter handle) as a programmer or something similar.

If you like the engine, or find any bugs or can help to reduce the token count or optimize performance, I'd love any and all input. I just started this project today and have no prior experience with Lua, so please forgive any herpyderpies. I hope to see some people make some great games with this!

Update March 9th: Reduced the token count from ~730 to ~690. No other major changes.

P#19115 2016-03-07 23:38 ( Edited 2016-03-15 12:01)

View Older Posts
Follow Lexaloffle:          
Generated 2024-02-27 08:48:27 | 0.147s | Q:48