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
New additions in Beam2.0
Graphics updates
- Altered some sprites and effects
- Beams now continue to glow in lightning land
- Beam colors are similar in day and night
- Added bird reflections
- Animated the door openings
- Added an intro when first launching the overworld
Game updates
- Birds now play a larger role in the narrative
- Added a new area for bird lore
- Reorganized some events to hopefully make the game more interesting and forgiving
- Gated off the first task so it can be treated like a tutorial before you advance
- Added an effect at the end to tie in the birds
- Updated the water sparkles for more control
- The map is significantly different
- added bird idols and functionality
- added a wave sound
Ongoing challenges
There is one area that if conditions are right the CPU overloads.
The music and sound effects sometimes clash and cancel each other out.
Credits
Thank_U={
RealShadowCaster,
jasondelaat,
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
Thank you @RealShadowCaster for the all the PAL and token saving help!
Music in this game is from my forever music project of the same name:
Rupees Music
The overworld music is from the song "Twenty somethings" from The Autumn EP:
Twenty Somethings
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.
Just updated this cart! Thank you again @RealShadowCaster for all the help with this!
What a nice Xmas surprise gift :)
Title screen is absolutely gorgeous, while still in the same art style as the game itself, just great intro that contributes the overall feel.
I may be wrong but I think the night beams are still somewhat color changed compared to daylight color ? As long as it's the way you intended, that's fine. Didn't get to look at it for too long because I softlocked myself : I was in the far end of the map at night with the bird boss (a new door !) and walked down left, hugging the south wall, and got stuck back on the same screen with the player offscreen after two screen transitions.
Will replay as soon as I can.
EDIT : @rupees
just replayed.
Found all 6 beam switches, bird count at 1 with the missing one being near the key that I also can't get to ATM.
Don't know how to open the door in the down right corner of the main map, nor the one near the bird boss.
There's also a red bird I don't know what to do with in the cavern near the orange beam switch.
A small hint would be very welcome.
Every map change so far feels like an improvement, except for the birds in the starting cave for witch I liked the original solve better.
I'm not 100% sure but I think I got an unintended bird when pushing against the south border of a cavern.
The presence of the passing flying birds and their reflections makes the game more lively and makes you feel less like an intruder somehow.
@rupees, gave it another go, found exactly the same things as last run,
then stumbled upon an easter egg and was soft-locked when exiting the room with the named birds still flying...
Guess I'll check map and the code for the doors next time.
Or maybe get a hint for the key for example.
I found the soft-lock problem you speak of in the birdboss area and just updated. There was a sprite that was existing in the nether region of the map and when you got too close it would interact. Eradicated. The weeds were supposed to stop that but i guess not haha. Hopefully that is the same bug that got you twice.
Here is a hint for the key door but I think you got it already:
I think I have a solution to make this puzzle a little more fun but need a couple more tokens. I'm wondering if you ( @RealShadowCaster) know of a way I can use "function string_caller(f,splitter)" with entcave_up()? It seems to have a problem with returning a value.
edit
I should also add that I agree the way the birds were freed in cave1 originally was the best and I brought this back. There was some back and forth when staging the "new door" and this was a potential spot for that but didn't make it. Forgot to change it back but it's back now sans a couple tokens.
string_caller(f,splitter) returns a function that can be used later to call the function f multiple times from a single string. Because there are multiple calls, there are multiple sets of return values, and rather than packing all that in a 2D array, I just discarded it all to save tokens.
We could adapt the function to return the result of the last call of the set, for example :
function string_caller(f,splitter) return function(s) local p=split(s,"\n") local i for i=1,#p-1 do f(unpack(splitter(p[i]))) end return f(unpack(splitter(p[#p]))) end end |
That would work and make sense if you had a lot of places where you needed the return values, but in your case, the modification counterbalances the gain...
Maybe change the entcave_up to accept a CSV string instead ?
function entcave_up(s) local yp,sp=split(s) local y=(p.y+yp)/8 return mget((p.x+2)/8,y)==sp and mget((p.x+6)/8,y)==sp end |
Warning, the new function returns true/false instead of sp/nil , but in a boolean context, it's the same.
For the calls, use string without parenthesis format :
if (mtn and entcave_up"-8,2") show_cave3(1000,264) if (entcave_up"8,69") show_woods1(672,375) |
well, shoot. i tried the "entcave_up to accept a CSV string instead" option in a couple places and got a runtime error:
attempt to perform arithmetic on local "yp" (a table value)
i did this in a new simple file where the entcave_up function was the only mechanic for the player. i wonder if i'm misunderstanding here. here is the sample program i created:
function _init() p={ x=10, y=10, sp=1 } holla=false end function _update() if (btn(⬅️)) p.x-=1 if (btn(➡️)) p.x+=1 if (btn(⬆️)) p.y-=1 if (btn(⬇️)) p.y+=1 if entcave_up"0,2" then holla=true else holla=false end end function _draw() cls() map() spr(p.sp,p.x,p.y) print(holla,10,10,14) end function entcave_up(s) local yp,sp=split(s) local y=(p.y+yp)/8 return mget((p.x+2)/8,y)==sp and mget((p.x+6)/8,y)==sp end |
@rupees
Sorry for the untested code directly written in the post.
split returns an array. That array needs unpacking to fill yp and sp.
yp,sp=unpack(split(s))
Or if you have it in your project,
yp,sp=unpack_split(s)
or maybe
yp,sp=unpack_tfnsplit(s)
depending on what you have available.
Finished the game, didn't notice any sound cut, and only experienced slight lag on the final screen with all the birds. Nothing problematic encountered this run.
My daughter said that the yellow beam turning green during the night is weird and I should tell you or fix it myself.
This is her opinion, I found the game great as is, but will change the beam color for her in a local version (and add her name to the birds also ;) ).
@RealShadowCaster
This fixed it! / Thank you! I had been so traumatized by PAL that the green color problem hadn't been a priority but I agree it's weird. Thank your daughter for me for the intervention haha! The extra tokens brought back some messaging when trying to walk out of bounds and allowed for a better way to turn one of the idols on.
I had originally named the birds after people that played or commented on the cart but I felt bad about doing that without their permission. Would love to add you and your daughter to the list! And that goes for anyone else that wants to be immortalized as a bird in this game! Just let me know.
Told my daughter, she'd like her bird to be named ❤️❤️Lise❤️❤️
If you want, I can help a bit :
here's the menu, take your pick
- do nothing
- token optimization (estimated gain 50)
- token optimization+color tooltip
- token optimization+beam color fix
- token optimization+beam color fix+extra color changes to be defined by you
Tried version 2_6.
Saw the yellow night beam. Got all the birds and the bird boss, yet I couldn't enter his lair.
Tried to de-activate the secret button, but couldn't enter the path any more.
After a few tries, I softlocked myself :
I have X pressed all the time, so maybe I managed to get stuck inside the wall due to a combination of simultaneous key presses and speed. I was playing on keyboard so it's possible to have opposite directions registering on the same frame. Maybe the problem was related to that ?
For the non working path, I have no idea.
Another minor problem : you can enter the doorway that leads to the key from the back once it's unlocked.
Lastly, I took a peek at the code to see if you hat added my daughter's name to the bird's list (thank you), and had a good chuckle at seeing my pseudo : it's RealShadowCaster not RealShadowCastor the underground beaver ;)
Yes i was able to make the yellow beam glow true throughout the night! It would be easy for me to make this change for all the beams but i only know how to do that by replacing the colors in the palette; so i would lose the cool darker versions... and for the most part i like how those look with everything else in night mode except for the much too green version of yellow of course. I will give the other beam colors a try though when things calm down a little.
sorry about the birdname typo! this will be fixed in the next version. i managed to also fix the keycave door entrance from the back. i'm not sure exactly where you are getting soft-locked by the path though. I did a playthrough and couldn't get it to fail.
Hello. You made a fantastic game here, reminds me of Fez a bit, which is great! Can you please tell me if I am missing something to beat the game? I got all the colors activated, the bridge formed, and collected all the birds, plus hit the "know your birds" button. The final door refuses to open, the one with all 6 colors above it. I am not sure if it is bugged, or I am just being a knucklehead. The door just makes a thud, like when you have the wrong colors lit, but all the colors are required. Any help in the right direction would be greatly appreciated. Again, fantastic work here!
mysterious, compelling! I got stuck at the last(?) door (like Rascal) and didn't figure out the:
technical code stuff:
@pancelor
glad you liked it! i'm hoping to add some better clues for that first cave in the next update (2.8). as for the last door, that is a puzzle i also struggle with and need to come up with a better way to let players know what to do.
thanks you for the code suggestions! you are right that the 3rd list item would take some explaining. RealShadowCaster has already helped me free up MANY tokens and navigate PAL and the "unpack_split" stuff is their contribution and i'm still trying to wrap my head around it. :| your suggestions got me down to 8142 so maybe i can come up with something better with a cool 50 tokens. thank you!
@LordZannen
sorry you were also victim to this puzzle! I will offer the same hint i gave pancelor. Hopefully I can find something better soon.
I figured it out! Thank you so much for your hint, and keep up the great work. This is something truly unique! Played through it again today just so I could beat it.
great to hear @LordZannen! i did end up adding a clue to the final task. i also named a bird after you. hope that's ok.
@rupees I think with a clue for that puzzle, it is a perfect difficulty and pace. The running shoes let you cruise once you get the paths figured out, and the game has great speedrun potential if you get the location of everything figured out. I love the art style and music so much, especially how the music gave a nod to Hyper Light Drifter in the cutscene that pans over the ocean to the island spawn at the start.
And thank you for naming a bird after me! I am honored you decided to do that.
[Please log in to post a comment]