Beam is a top down puzzle adventure and is meant to be mysterious, relaxing and pretty. If you get stuck read the signs around you! It is inspired by the Zelda series, Myst, The Witness, Tunic, Hyper Light Drifter,etc and is my first attempt at coding/game development. Feedback is very welcome!
Controls
Arrow keys move the player
O (z on the keyboard) various actions
X (x on the keyboard) run if unlocked
Ongoing challenges
The “cobble-stoning” is real!
Palette switching happens and there are some flash artifacts of the incorrect palette when switching rooms.
Out of tokens:(
Overall there is much to improve on here so maybe in part II…
Credits
Thank_U={
jasondelaat,
RealShadowCaster,
kozm0naut,
SmellyFishstiks,
Ummmm_ok
} --for the help in the forums!
Lightning effect taught to me by SAY Sí via this wonderful tutorial:
Custom functions & powers of recursion
Code, graphics and music by Rupees
Music in this game is from my forever music project of the same name:
Rupees Music
Cool game! I like the animation, and the music is pretty good.
This is really nice, the environment looks really cool and encourages you to keep exploring. Nice job with the reflections, and the music!
Overall very nice, despite the huge backtracking and back and forth. The shoes were VERY welcome. The fact that yellow appears green during the night trolled me big time.
The game tab crashed after being left open for a few hours, a progress save would have been welcome.
I couldn't finish the game : got all 6 colors, the crystal, but was apparently missing one bird, visible to the right of the cave where the group of bird in the lower left corner. I'm guessing it was needed to open the last door.
Bugs and mysteries :
I've completed this game, managed to light all colored beams and reach the pond at the peak. Wasn't sure the point of releasing all birds though.
BTW nice puzzle game, almost puzzled with the doors until I noticed the color codes above each door. Reminds me to some of my own games which has similar puzzle.
Hey, I was browsing the map and code of your game, hoping to find how to get to the last bird and open the last two doors (still haven't found yet) , when I stumbled on you gameover_color() function and got a severe brain freeze.
function gameover_color() pal({[0]=0,129,130,131, 128,5,6,7,136, 137,138,139,140, 141,142,143},1) if not flash then pal(141,129,1) pal(131,130,1) pal(139,131,1) pal(138,130,1) pal(5,130,1) pal(0,128,1) pal(6,130,1) pal(7,5,1) pal(137,141,1) pal(140,130,1) pal(143,13,1) pal(130,128,1) pal(136,130,1) end end |
Everything I know about pal() tells me this can't work. I know the 1st parameter is in the 0-15 range and is an index into the palette, while the second parameter is the value to write in it and is in the 0-255 range.
So of course I tested it manually... And it worked.
Went back to my pal test source code where I tried the exact same thing... And it doesn't work, more precisely, another color than the one I'm trying to change gets changed.
Maybe you rewrote the pal function... Checks your code, nope.
Maybe you know a secret poke that changes the pal behavior ? Checks every poke of the code... nope.
What trolly dark magic is this ?
...
After a good half hour of hair pulling, I finally got it :
pal(index,value,1) only cares about the last four bits of the integer value if the index.
When you do
pal(137,141,1) to replace color 137 with color 141, what pico8 does is
pal(137 & 0x0F,141,1) or pal(9,141,1)
and you seem to purposefully have placed color 137 in index 9 of the palette for the magic pal to work.
...
My last question is WHY !
Thank you all for playing this and for the feedback! I've updated some things based on your comments.
v1.4 updates
- fixed some out of bounds areas so you cannot walk off the map
- updated some areas to be more intentional about cave entrances, secret paths, etc
- made some things that were not the easiest to interact with before a little easier to interact with
- fixed a bug that would show an item when it wasn't supposed to be shown
- removed a barrier and made some subtle changes to reduce backtracking (the game should take about 18 mins if you know exactly where to go)
@RealShadowCaster regarding pal()
I wish I had an answer for you! I thought I knew how pal() worked and I spent way more time trying to get the beams to retain their color when lightning wasn't flashing then I'd like to admit. I gave up eventually. In that time I wouldn't put it past myself to have thrown some spaghetti at the wall that opened a pal() wormhole.
I'm looking into this but I had no intentions of trolling or brain freezing!:
"When you do
pal(137,141,1) to replace color 137 with color 141, what pico8 does is
pal(137 & 0x0F,141,1) or pal(9,141,1)
and you seem to purposefully have placed color 137 in index 9 of the palette for the magic pal to work."
I am probably doing many things I don't have to and my code is not what you would call "tight". First timer here and I didn't listen to the warnings about making something simple first. IMO this is a pretty simple LUA graphics study but really advanced to me 12 months in to coding in general.
In summary, once I wrap my head around exactly what I have done here I would probably have the same question for you. :P
Finally finished the game, much faster than last time. The out of bounds bugs (in the cave and near the exit) seem to have been fixed. I also reached the previously mentioned door that leads to the right of the final area. Even after using it, you can't go back, so you still need to go around again at night if you need the button. Not a big deal but it's the only one way door in the entire game so it might be unintentional.
Found a use to the empty area on the left : lightning sightseeing.
Found a new palette related bug : when entering a cave at the same time there is a lightning, the palette is not reset.
Speaking about palette, don't hesitate if you have questions left.
pal(idx,val,1) <-> poke(0x5f10+idx%16,val)
pal(idx,val,0) <-> poke(0x5f00+idx%16,(peek(0x5f00+idx%16)&0xF0)+val%16)
@rupees,
Hello wanted to say that I still love to replay your game, and am now familiar with the code.
It's not too hard to gain tokens by storing arrays literals and long parameter lists in strings, and with the extra space gained, add the colors for the beams in night time for example.
The current way the colors work is that the map and sprites is always drawn the same, and you adjust the display palette according to where you are and if it's day, night or lightning time. When you do, you get duplicates colors in the display palette.
You already made it so the number of onscreen colors leaves room the the 6 beam colors, but you can't display them since the current color handling needs the duplicates.
For the fix, it's the draw palette that has to be changed, and you need to make distinctions between absolute colors like the beams, and affected colors like the map and sprites.
I'm willing to fix the colors, but need your ideal vision of the game :
beams are clearly absolute colors, but the rest is unclear :
are the active beans dots absolute or relative ?
How about the dots over the doors ?
Speaking about them : what are the rules you want about displaying the dots, and about affecting the colors of the dots or not.
About the color changes in cave and in night, are you OK with how it's currently working or would you like changes.
- How about an easy/hard mode selection at the start that would put back the door to the day/night change room ?
Another question : there's an empty space to the left of the map. Most lightnings appear there, and way less over the playable area. Is this how you wanted things or should I investigate and change that? If so how ?
Lastly, if you'd rather adapt things yourself, I could just give you a debug version that would be essentially the same as the current one, but token optimized and with a palette debug tooltip to help you adapt things.
@RealShadowCaster
THANK YOU! for the continued interest and support offerings! I've been working on this since this first version and have found some ways to token optimize. This allows for some things that are bugging me like cave transitions and beam particle glitches but would welcome any suggestions you have about optimizing using arrays! I've combined some functions and used local variables to make space but I'm sure I'm just scratching the surface of what can be done. I've also altered the first tasks of the game to lock the player into a smaller area to start with for tutorial purposes based on some feedback from a couple others. This led to adding one more dot challenge, a new cave entrance and a tree draw function so I don't have to manually overlay bigger trees.
The beams retaining their color at night (outside of the lightning flash) would be such a beautiful effect I think and any help with that would be wonderful. Overall, I like using the alternate palette for night even though the primary colors are a little different. Here are rules as I see them:
"are the active beans dots absolute or relative ?" The beam dots should be absolute because that is the same light as the beam itself. I want this to apply to the beam indicators that follow the camera on the bottom of the screen as well but haven't quite figured that out yet either.
"How about the dots over the doors ?" the door dots can respond to the lightning just like they are now. I like the challenge of having to decode that in a storm.
"About the color changes in cave and in night, are you OK with how it's currently working or would you like changes." the only thing I struggle with in the caves is how pal() doesn't reset at night if you enter when lightning strikes.
- cave1- This has a lights on/off switch tied to beam[3] and I like how dark it gets when you enter from the night when the beam is off.
- cave2- It's not entered in the night time in my new version. I'm happy with how this one works.
- cave3- This cave always has the lights on so should look the same as cave1 when beam[3] is activated relative to day or night.
The empty space to the left was intentional and I originally wanted it to be infinite just to watch the world burn so to speak but am warming up to the idea that its confusing if there is nothing to do there. The lightning is supposed to be present equally over the playable area and the left nothingness so that is something I'd like to fix. I should probably just tie the lightning to the camera.
If you have the capacity I would LOVE a debug version with a palette debug tooltip! I want to learn the ropes so I can lead with better processes for future carts. I will share a new version of the game as soon as I can. Thank you again for all the help!
Here's your cart with examples of token optimizations and a display palette tooltip.
The token optimisations :
All the token helper functions are in tab 9
Mostly stringifications of list of parameters, or even list of calls of the same function.
Some smaller optimizations include :
-
interger division : flr(x/8) becomes x\8
- LUA boolean arithmetic
if keycave then mset(57,9,22) --keycave entrace else mset(57,9,26) --keycave door end |
becomes
mset(57,9,keycave and 22 or 26) |
-
Using intermediate variables
- I don't remember the rest.
The palette debug window/tooltip:
shows (with colors and numbers) the 16 entries of the display palette (palette 1)
text color is window Xposition%16, so there's always at least 1 invisible text, but you can move the window to change witch one.
For the beams and other elements to be displayable during the night, the some palette duplicate entries will have to be replaced by the 6 beam colors.
If the palette is in the way, its position can be changed by keeping X and O pressed, and move around.
Feel free to ask anything about the code.
Cart is currently at the compressed code limit, but with 300+ tokens to spare, and a lot of comments can be removed if needed. Since they are generally useful, work on the .p8 that can exceed max compressed capacity, and only remove comments for the BBS extract, once everything works.
there's also a color_index(c) function in tab 10(A) that returns the index of color c in the display palette.
Once the palettes contain the 6 beam colors,
you'll be able to do things like rectfill(x1,y1,x2,y2,color_index(15)) for example.
@RealShadowCaster
I'm learning a ton about token optimization with this/Thank you for doing this! It's great to compare the code i know with what you have done. So far I am still not quite getting the separation of map/sprites with the beams. Anything I try changes the color for everything. In the example below I do not want the grass flowers to also be the same color green as the beam:
How would I go about defining a distinction between absolute colors for the beams and affected colors for the map/sprites?
@rupees, at night, the grass changes from color 11 to color 131, while the beam should stay at color 11. This means you need both the color 131 and the color 11 in your night palette to be able to display both colors on the same frame. For that, you should replace 6 of the duplicate palette entries with the 6 beam colors.
Let's say you use entry number 5 for the green been (color 11)
You still want color 5 in the map to be displayed as it's night shade, color 130. If color 130 is now only in entry 3 of the display palette, we need
pal(5,3,0) (pal(5,3) for short) : when when we call map(), pixels of color 5 will be displayed on screen as color index 3, and that index in the display palette is color 130.
the entry 5 of the display palette is our beam green
pal(5,11,1)
Remember, palette 0 contains indexes in the 0-15 range. Those are what is written in the video memory that is a big sequence of 4bit color indexes.
Palette 1, the display palette, contains color numbers in the 0-255 range, but there are a lot of values that mean the same color, only 32 distinct colors available.
@RealShadowCaster
Well I've been mulling over this and I'm really struggling. I did some homework on PAL() and am still not seeing a solution but possibly I'm thinking too hard. or not enough.
When the game goes into dark mode I call this:
function gameover_color() pal({[0]=0,129,130,131, 128,5,6,7,136, 137,138,139,140, 141,142,143},1) if not flash then multi_pal [[13,129,1 3,130,1 11,131,1 10,130,1 5,130,1 0,128,1 6,130,1 7,5,1 9,141,1 12,130,1 15,13,1 2,128,1 8,130,1]] end end |
...so the majority of the time the colors on screen have duplicates because we call PAL() to change them to the darker tones. In this case, your super sweet color tool tip shows the colors to be this:
When the lightning strikes the displayed colors are this:
The all pink box is 142 in both images.
I understand that calling 1 in an exclusive case like this: PAL(7,15,1) would change all colors on the screen from 7 to 15 whereas not calling 1 would only change the 7's to 15's after the call and not everything before it. Maybe this is where I'm mixed up and not getting exactly how this works or at least how it works with the code in my game. I'm already putting all the colors for the beams I want to use on screen when the lightning flashes and I feel like the fix to make the beams stay those colors outside of the flash would be to call PAL() just where I'm drawing beams at night and then reset it after so everything else responds to the lightning again. This breaks the colors though.
What am I not getting here?
[8x8] | |
@rupees, when you call pal(index1,index2,0) matters because draws of index index1 will write old_index1 in screen memory before it, and index2 in memory after it.
Changing the draw palette affects what index will be written in memory in future draws.
Unless the game lags so much that the memory screen and display palette isn't fully ready after 1/60s, it does not matter when you call pal(index,color,1)
During the drawing, only the color value at rendering time matters.
Once _draw is finished, pico-8 waits the 1/60s to finish, then the screen memory is read (it's a bunch of 4bit 0-15 index values) and screen pixels are painted based on screen memory indexes and current display palette, for an up to 16 colors image.
if you do
PAL(7,138,1)
map()
PAL(7,15,1)
it's the same as just
map()
PAL(7,15,1)
In both cases, index 7 in the memory will be displayed as color 15 at rendering time.
The display palette is only used when rendering the screen, after the end of _draw.
This means that you should have at least one instance of each color you want onscreen in the display palette. You can't have a color onscreen that is not in the the shown display palette in the default video mode.
Saying that pal channges color1 into color2 is misleading.
pal(index1,index2,0) changes the effect of future draws but doesn't change anything onscreen.
pal(index,color,1) changes the color that index will be rendered as during rendering phase... If not set to something else before reaching that point.
@rupees, maybe an example would be clearer ?
I just took care of the beams, but the beam indicators would be the same.
Before calling map(), you have to adapt the draw palette to the night display palette :
pal({[0]=0,1,0,3,0,3,3,7,8,9,3,11,3,1,14,15},0)
Remark : color index 8 is never used anywhere. We could use it for a permanent white for beams and text for example.
In the screen shot, the player , the pillars and the wall tile look strange. This is because there's a call to pal() somewhere between the call to map and the call to the spr that deal with these 4 sprites.
I didn't bother to fix that, since I just wanted to show you an example of a valid night palette. This is not necessarily the best organization possible, I didn't bother thinking about the day palette, day cave lit, day cave dark, night cave lit and night cave dark palettes and come up with a convenient organization.
I'm just trying to make you fully understand how the draw and display palettes work.
@RealShadowCaster
I'm on my way to having this sorted! Thanks again for the help. I am now just going through all the areas where a PAL() is messing things up. I had woven a PAL() tapestry let me tell you!
Great news, glad you got it and don't need me any more to go forward with pal0+pal1 handling. Maybe next game you'll use more than 16 colors at the same time and try pal2 ?
Now that you're accustomed with token optimization, I recommend studying the source code of
https://www.lexaloffle.com/bbs/?pid=154657
if you want to learn more on the subject.
It's super readable despite being optimized to the max.
For example, you have g_fill()
g_fill(split"m_state,m_t,m_spd,m_fr,m_x,m_y,m_ofx,m_ofy,m_scx,m_scy,m_fx,m_flk",split"1,0,6,0,32,45,0,0,0,0")
That takes advantage of the _ENV global to assign globals in bulk, using strings for the left side of long assignations.
Lastly, during debug, assert and printh are your best friends. A little trick I use : display a frame counter both onscreen and on the printh console.
This way, when things go wrong, you can review the animated gif frame by frame and check the console log to see when things started to go wrong.
[Please log in to post a comment]