Log In  
Follow
PrincessChooChoo
Follow

My submission for the O2A2 VN Jam (Only One of Any Asset), a short visual novel about Lilith meeting the tentacle monster Medusa.
This is prequel to my previous game Medusa a short SHMUP about protecting Medusa as you escape.
Many thanks to dragonsbutalsorabbits for drawing the character sprite at very short notice before the jam deadline.

Cart #pcc_medusa_vn_o2a2-1 | 2021-07-19 | Code ▽ | Embed ▽ | No License
4

P#95045 2021-07-18 23:03 ( Edited 2021-07-19 14:06)

My game for Ludum Dare 47, a little puzzle game with a tline rotation effect.
I made the effect without any idea what I was doing with it, then scrambled to finish the puzzle part for the jam deadline...

Cart #pcc_ld47_chronometry-0 | 2020-10-09 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
7


P#82727 2020-10-09 05:24

Cart #dont_shit_your_pants-4 | 2020-07-07 | Code ▽ | Embed ▽ | No License
8

A port of the classic flash game Don't Shit Your Pants! by Decade Studios, now Cellar Door Games:
https://cellardoorgames.com/our-games/dont-shit-your-pants/

I was reminded of this old game the other day, and after seeing a few demakes recently, I decided to have a go at porting it.

I'm pretty happy with the result, I tried to get everything as accurate as I can to the original, including some weird behaviours. The colours are pretty far off, because even with the extended palette, PICO-8 just doesn't have hideous enough colours to match the original.

Also while I managed to get the intro and success music pretty accurate, I haven't managed to find a nice match for the fail jingle yet, so the current version is my first simpler attempt. If anyone wants to help I'd be grateful for the help.

I'm not marking this one as Creative Commons, since the artwork, music and concept remain the property of Cellar Door Games, which I'm just blatantly copying here, however if anyone finds any part of my code useful feel free to use it.

P#79009 2020-07-07 12:51 ( Edited 2020-07-07 13:26)

I've been writing tweetcarts semi-regularly for the past few months, and I've had a tonne of fun doing it, so I thought I'd write up some guides to share what I've learned, and help other people get started. While it can seem intimidating at first, I've been surprised by how approachable it's been.

It is perhaps appropriate that the cosiest game dev environment has a similarly cosy demo scene. You'd be surprised how far you can get with just a few tricks and some lateral thinking, and even something fairly simple can be impressive when you know it fits in a tweet!

If you're not sure where to get started, I'd suggest you check out some existing tweetcarts, and try to add your own spin to them. That's how I ended up getting into tweetcarts: I had just learned about the concept, and I saw this cart by Luca Harris:

And after thinking a little about how it worked, I was inspired to try making something similar.

This is what I came up with:

So in this first guide, I'm going to dissect this code, and talk a little about the thought process of making it. I'll say now that this is a fairly simple example, and if you're looking for something a bit more advanced, I'll maybe try and get to that in a future guide.

In fact, not only is it simple, there's actually a lot of inefficieny here, as I hadn't learned a lot of tricks yet, so I'll demonstrate at the end how we can shrink this a lot more. But for now, I think it's a good starting point to discuss some simple techniques.

Minification

First of all, perhaps the most obvious trick, the code has been minified by reducing all variables to a single character and removing all the whitespace (almost, I missed some here). There are tools which can do this, but personally I just do it by hand. Aside from me being too lazy to look up tools, this does have one advantage: You can often see ways to shave a few extra characters by changing the ordering of lines as you minify them.

Another related trick is assigning API functions to single character variable names. The assignment itself uses some extra characters, but if you're using the function more than once, this can end up saving characters overall. Whether it helps will depend on how long the function name is, and how often you're using it.

Here's an expanded version of the code that reverses this minification and adds some comments, so we can understand the rest of the code better:

-- initial riverbank values
left=36
right=60
-- constants
max_size=127
color_table={0,0,11,0,0,0,7,0,10,10,11,7}

-- draw the sky, sun and grass
cls(1)
circfill(50,40,30,9)
rectfill(0,50,max_size,max_size,3)

-- draw the river
for y=50,max_size do
  left=left+rnd(0.2)
  right=right+rnd(1)
  rectfill(left,y,right,y,12)
end

-- draw noise
for i=1,max_size*left do
  x,y=rnd(max_size),rnd(max_size)
  c=pget(x,y)
  if i%c==0 then
    c=color_table[c]
  end
  rectfill(x-rnd(2),y,x+rnd(3),y,c)
end

A lot of this is fairly straightforward once it's expanded, so I'll just focus on some parts that might be a bit unusual.

Drawing the river

First, let's look at the river drawing section. This is fairly easy to understand, I just scan down the screen from the horizon, drawing horizontal blue lines as a river, and I have the left and right ends of the lines drift rightwards by a random amount each row to make it look organic.

for y=50,max_size do
  left=left+rnd(0.2)
  right=right+rnd(1)
  rectfill(left,y,right,y,12)
end

But there are a couple of things worth mentioning: Firstly, you may notice that I'm shifting the left and right positions by non-integer amounts. This works out fine, because PICO-8's graphics APIs will happily accept non-integer values and floors them for us automatically.

And one other peculiarity is that I'm drawing the horizontal lines with rectfill. Obviously this works, but why am I using it instead of line? Well, if you remember above, I had single character variable names for various API functions, and one of those was rectfill.

Originally, I had one call to rectfill and two calls to line. At first, I tried making a single character name for two calls line but that didn't help, so I didn't bother. But then I realised if I used rectfill I could use the same single character name for all three calls, and that helped quite a bit.

Adding noise

Now let's look at the noise section. You can perhaps see the overall structure here. We pick a bunch of random points on the screen, and then draw a short line based on it's colour. But there are perhaps a few things that seem a bit odd.

color_table={0,0,11,0,0,0,7,0,10,10,11,7}
for i=1,max_size*left do
  x,y=rnd(max_size),rnd(max_size)
  c=pget(x,y)
  if i%c==0 then
    c=color_table[c]
  end
  rectfill(x-rnd(2),y,x+rnd(3),y,c)
end

Firstly, why is the size of the loop max_size*left? This is actually quite simple. I needed a number big enough to make the noise look good, and that turned out to be around a 5 digit number, so multiplying two variables ended up being shorter than a constant. Initially I just used m*m but after playing around for a bit I found m*l worked out nicer as it was a bit less intense.

Now let's look at the colour logic. The colour for the line to be drawn will sometimes just be the same as the pixel, which just creates a smearing effect, but sometimes I modify it using the color_table. A table is often a convenient and compact way to implement a simple mapping function.

One intersting note here: An earlier version of this table just assigned each of the previously drawn colours to a new one directly like so: {[1]=0,[3]=11,[9]=10,[12]=7} but this meant if we chose a pixel which already used one of the modified colours, it would end up with nil which draws as black. This didn't look terrible, but I thought it would look better without that. Initially, I thought it would be too expensive, as I'd have to add 4 extra cases to the table, but then I tried just expanding it to an array and it turned out to be only one character longer than the original!

{[1]=0,[3]=11,[9]=10,[12]=7}
{0,0,11,0,0,0,7,0,10,10,11,7}

This could also enable the possibility of using more complex colour mapping for different effects, but I didn't end up trying that.

Finally there's one last trick here that makes the resulting image look good, and I didn't even realise how neat it was when I first wrote it. The condition for using the alternative colour for the noise line is i%c==0. Initially I just used this because it seemed slightly shorter than something like rnd(5)<1 and had a similar effect. But then I realised that this actually varies the probability of a colour change for the different aspects of the scene! This gives the image a really nice sense of texture that I didn't expect, and I think that really contributes to the final effect.

Optimisation

Now, as I mentioned earlier as this was my first tweetcart, it's actually fairly inefficient. I recently went back to it with all the tricks I've learned since and managed to squash it down quite a bit. So I'll discuss some of those tricks here, to give you a headstart!

So here's the original version again, at 252 chars, as our starting point:

l,r,m,g,f=36,60,127,rnd,rectfill
t={0,0,11,0,0,0,7,0,10,10,11,7}
cls(1)
circfill(50,40,30,9)
f(0,50,m,m,3)
for y=50,m do
l=l+g(0.2)
r=r+g(1)
f(l,y,r,y,12)
end
for i=1,m*l do
x,y=g(m),g(m)
c=pget(x,y)
if i%c==0 then c=t[c] end
f(x-g(2),y,x+g(3),y,c)
end

Firstly, the minification I did missed out on a lot of whitespace that can be removed. It turns out that Lua's syntax actually allows a surprising number of things to run together on the same line! Assignments and function calls can all run directly after each other, as long as you don't end up with letters next to each other that combine into a single word. In my experience, you run into that most often with keywords like do and end.

Applying this, and stripping out unnecessary newlines takes us down to 242 chars very easily:

l,r,m,g,f=36,60,127,rnd,rectfill
t={0,0,11,0,0,0,7,0,10,10,11,7}cls(1)circfill(50,40,30,9)f(0,50,m,m,3)for y=50,m do
l=l+g(0.2)r=r+g(1)f(l,y,r,y,12)end
for i=1,m*l do
x,y=g(m),g(m)c=pget(x,y)if i%c==0 then c=t[c] end
f(x-g(2),y,x+g(3),y,c)end

Secondly, one really important feature of PICO-8's Lua that really helps in tweetcarts is the single line if statement. Rather than the full if/then/end block, we can also write if statements in the form: if(condition)dostuff. This can be applied to reduce the if statement above, although this does require us to move the if statement to its own line (I also noticed a way to shave a char off the condition). This takes us down to 233 chars:

l,r,m,g,f=36,60,127,rnd,rectfill
t={0,0,11,0,0,0,7,0,10,10,11,7}cls(1)circfill(50,40,30,9)f(0,50,m,m,3)for y=50,m do
l=l+g(0.2)r=r+g(1)f(l,y,r,y,12)end
for i=1,m*l do
x,y=g(m),g(m)c=pget(x,y)
if(i%c<1)c=t[c]
f(x-g(2),y,x+g(3),y,c)end

Now let's look at those initialisations at the start. I put them all on the same line using Lua's multi-assignment syntax, as I had seen a few other tweetcarts do that, and assumed it was more efficient, but it's not actually necessary. It's actually the same size as just putting the statements on separate lines (newlines replaced with '/' to show this):

l,r,m,g,f=36,60,127,rnd,rectfill
l=36/r=60/m=127/g=rnd/f=rectfill

But we can do better than this, by running these lines together as well (including the x,y= assignment further down):

l=36r=60m=127g=rnd
f=rectfill
t={0,0,11,0,0,0,7,0,10,10,11,7}cls(1)circfill(50,40,30,9)f(0,50,m,m,3)for y=50,m do
l=l+g(0.2)r=r+g(1)f(l,y,r,y,12)end
for i=1,m*l do
x=g(m)y=g(m)c=pget(x,y)
if(i%c<1)c=t[c]
f(x-g(2),y,x+g(3),y,c)end

This isn't always possible as you can see from the f=rectfill on a separate line, but you can still save a few characters like this taking us down to 229.

We're getting down into really small savings here. One thing I tried at the time using the colour table inline, but it didn't work so I put it in a variable and left it at that. But it turns out if we put the table in parenthesis, it works and saves a whole... one character.

Finally, there's one more thing we can do here. Perhaps you already noticed it? Perhaps it's been bugging you for this entire guide? When I first wrote this tweetcart, I was used to regular Lua, and I didn't know that += and friends existed in PICO-8!

Applying both of these, we get down to 226:

l=36r=60m=127g=rnd
f=rectfill
cls(1)circfill(50,40,30,9)f(0,50,m,m,3)for y=50,m do
l+=g(0.2)r+=g(1)f(l,y,r,y,12)end
for i=1,m*l do
x=g(m)y=g(m)c=pget(x,y)
if(i%c<1)c=({0,0,11,0,0,0,7,0,10,10,11,7})[c]
f(x-g(2),y,x+g(3),y,c)end

That's all I can think of at the moment. It's quite likely I've forgotten something, but in any case with just a few tricks we've managed to save 26 characters! Hopefully knowing these tricks up front will save you from making the same mistakes as me!

Conclusion

And that's it! As you can see, there's not a huge amount going on in this sunset tweetcart. Once de-minified, it's a fairly simple to understand, and there are only really a few tricks here. Nonetheless, I'm quite proud of how nice it looks.

And this is perhaps an important point to end on: How effective a tweetcart demo looks usually ends up being a lot more dependent on working out a nice looking effect and exploring the creative possibilties, than it does on clever code-golfing tricks. As an example, consider this tweetcart I made a little later:

This was surprisingly difficuly to get right, because there's no built-in way to draw arcs with PICO-8, so it requires a careful combination of circles and clipping, so creating this took quite a bit of effort. It was a fun exercise, and I don't regret it, but nonetheless, as an effect I feel like it ended up looking fairly boring.

In contrast consider this tweetcart:

This is a really simple effect, and you can probably take a reasonable guess how it's done without thinking too hard. In fact, it's sufficiently simple that I probably wouldn't have even thought to try doing this if my girlfriend hadn't suggested it. Nonetheless, I've had more positive feedback for this than any of my others. So a good idea from my girlfriend made a much bigger difference than the few hours I spent trying to get those TV curves right!

So keep that in mind, and go and create your own tweetcarts. 🙂

P#76382 2020-05-11 15:25 ( Edited 2020-05-11 15:49)

[sfx]

Finally getting around to learning music on the PICO-8.

This is my initial composition for my Juggler Defender game (rndgame generated name...):

P#75454 2020-04-26 23:49

Cart #pcc_ld46_medusa-1 | 2020-04-19 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
1

First playable version of my LD46 entry, titled "Medusa"

Defend Medusa the tentacle monster from the enemies.

P#74971 2020-04-19 13:58 ( Edited 2020-04-19 13:59)

I've been having a lot of fun making a bunch of tweetcarts this weekend.

Cart #pcc_tweet_sunset-0 | 2020-04-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

Just a little static landscape render.
I was inspired by this scene of a moon over water: https://twitter.com/lucatron_/status/1111025092236959744
It can be animated with a loop, but it's a bit jumpy.

Cart #pcc_tweet_storm-0 | 2020-04-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

A blizzard with lightning.
I was inspired by some on Discord who was making a similar snow effect.
An earlier version did lightning with lines, but I realised a drunkard's walk of pset calls looked way better, and ended up using fewer chars.

Cart #pcc_tweet_warp-0 | 2020-04-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

A warp/hyperspace starfield jump effect as suggested by my gf.
Added a blue tinge when it gets up to speed. Is that how blue shift works? Probably not...

Cart #pcc_tweet_vapourwave-0 | 2020-04-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

A classic vaporwave scene.
Maybe I should add some chill music to this, even if that's not a strict tweetcart.

Cart #pcc_tweet_onebutton-0 | 2020-04-13 | Code ▽ | Embed ▽ | License: CC4-BY-NC-SA
2

A little one-button game to see if I could do something interactive.
Originally I was going for something more like Canabalt, but that was very hard to fit.

P#74698 2020-04-13 14:55

Follow Lexaloffle:        
Generated 2022-08-09 19:26:49 | 0.060s | Q:35