11/2/16: Updated demo to a new version, now with ~9~ playable rooms.
A lot of stuff updated under the hood (the biggest being an overhaul to a more OOP-oriented entity system, among others) but also the following gameplay changes:
- Added a 1-frame delay to turning.
- Some sounds and graphics updated.
- Corrected reversed X/O button descriptions (oops)
- Since the turn delay makes position adjustment easier, X was turned into an alternate jump button.
- Some simple effects that illustrate block creation/destruction and also illustration of what is preventing you when you fail to do so.
Hope you enjoy checking it out :) Of course, let me know if you run into obvious bugs or problems as well.
(initial post follows)
This is my first major project in PICO-8, and I love the system to bits.
This is an action puzzle game I'm still working on with heavy inspiration from a NES/Famicom game I played a great deal as a kid that I loved, Solomon's Key.
In each room, you must collect the key to open the exit, and then get to it. The controls are explained on the placeholder title screen, but I will go over them here as well in more detail.
|> Use left/right keys to move. There is a very short delay when you turn.
> Press O to use your creation/destruction magic where you are facing. You require MP points (shown at the top) in order to create. You can increase your MP by collecting red magic stones in each room. You can destroy your own blocks and brittle brown blocks, but not much else.
> Press down to duck. Ducking does not help avoiding getting hit (no reduction in collision area), but it allows you to aim lower than you normally do.
> Press X or up to jump. Jumping underneath a breakable block will also let you break it, but it requires two hits.
I find it much easier to play with a controller vs. a keyboard because of the coordination sometimes required, so I suggest trying that.
Very nice. The levels are a bit easy as puzzles go.
Yeah getting the right block can be a little finicky, but that's part of the difficulty of being a little demon who creates and destroys blocks. For movement finickiness, maybe if you make turning around cost a frame or two it would not require the 'change facing withouth moving' button.
Thanks for the reply! Yes, the 5 levels are on the easy side on purpose, mostly just as a tutorial or demo :)
That's a great idea to add a delay when turning, for some reason I didn't even think of that. I'll experiment with that idea and see if it improves things when I get the chance.
Really quite nice, Terumoc. :) It reminds me a lot of Solomon's Key.
I really liked this. I never played Solomon's Key, but the pace of the game actually reminded me of Donkey Kong 1994 on Gameboy. I agree that the puzzles are not brain-busters, but you need to execute on a plan while keeping in mind the timing of surrounding obstacles.
The controls still feel a little 'stiff' to me, particularly in regards to making your blocks. In the last level, there is a 1 space gap between some un-blockable tile and the wall - it is surprisingly difficult to put a block there. I feel like the block-creation should be a tad more forgiving - if there is truly no open space to create a block around you, then play the mis-fire animation, but if I am a few pixels too far or too close to an obstacle, I think the system should create the block anyway where I intended. I might also shrink the collision boxes on the hazards a bit, but that would definitely make things easier.
I was peaking at your code, and I have a question for you. I have not yet plunged into OOP in Pico-8 Lua, and I have seen two styles of doing so - one using a typical class-declaration, and one where you kind-of make a list and also stick functions in it.You appear to do the latter. Is there an advantage to doing things one way or another? For my next game I would like to use a more proper OOP style, since my first game is a hideous tangle of lists and functions.
I really enjoyed this. I hope you finish it up. It's pretty solid as-is - a few more levels would really wrap it up.
Hi Paloblancogames, thank you very much for the feedback and praise. I'm also very glad to hear that you enjoy it so far!
I agree that it really needs more rooms and that's what I'm in the process of doing when I work on this, in addition to making it so you don't need to start from the very beginning each time if you've reached certain milestones.
1) I understand how you feel about the block placement, it may only be intuitive if you have in fact played Solomon's Key. Right now it is very simple: there is a pointer that follows your character about 1 block ahead of where he's facing (plus 1 block lower if you are ducking) and it simply checks that one point and the map cell it is in. If it fails due to there being a block that cannot be affected or a magic-proof entity overlapping, it fails and that's it. With your note of having difficulty, I might experiment with having it check a few places around the pointer if the initial attempt fails to try and make it more friendly.
2) OOP in Lua is unusual (and confusing to newcomers) in that there are no "classes" or official way to implement it, but it is actually very easy to do however you like thanks to how extremely flexible tables are. In this game I use the extremely straightforward metatable-based method for a slim 58-token implementation that allows single inheritance and not much else (since that's all I really need) -- the entirety of my OOP system is at the very beginning in the function "class".
There is no way to really do a "class declaration" in Lua, no matter how it's implemented it will be via creating a table and/or calling functions. Thanks to metamethods and Lua's helpful (but admittedly sometimes opaque) syntax it might look like something else, but everything in Lua is just tables and functions.
For example, when you see
in this game's code, that is actually syntax for calling
(the table is used as a single parameter for the function, and contains the default values for instance variables if they are not defined for a particular instance) and then the return value of that call is put in
. (In the case of my class implementation, it returns a table that represents everything about the class, including a "new" function that creates a new instance and returns it)
Because I use metatables for my implementation of OOP, declaring functions in the class can be done initially or afterwards. I could have put them in the initial table when I called the "class" function, but I chose to declare them afterwards because I think it's cleaner to use the colon syntax both to automatically define "self" (save a token or 2) and make it clear at a glance that these functions are intended to be used as instance methods for that class.
That was a lot of explanation, and it might not have even helped because it can be difficult to understand. But if you have more questions, ask away, I don't mind explaining things. (or at least trying to)
Terumoc, thanks for the explanation! I am coming from Python mostly, and while just about everything in python is a class, there is a clear distinction between a 'list' and a class.
Man, Lua tables are strange! You can put anything in there! It's a list, a dictionary, a class, an array - whatever you want it to be!
I spent some time with your code, but to be honest I spent a little more time studying the "object-oriented programming example" by Jihem. My take-away is:
- You make a table with the variables and functions you want
- you make one of those functions a "new" function, which essentially returns a copy of the table itself, AND
- (here's the part i'm still shaky on) you use a metatable assignment which makes sure that when you refer to the __index operator in the returned table, you make changes to THAT table, and not the original table.
I guess you need that 'setmetatable' assignment since tables don't necessarily get 'copied' when you set a new variable equal to them - that new variable would actually refer to the original table, so changes made to it would be made to the "class" and not the "instance". It looks like "self" is a way to force this locally.
One thing I am still fuzzy on is the argument "..." that you use a few times. I can't find good documentation on this. Can you fill me in a bit?
How is Tome coming?
Hey again, no problem, I'm happy that you asked questions about this stuff since I like to see more people making interesting games rather than less.
And yes, Lua is built on a really simple of idea of everything being based on tables which can act as nearly any type of container you want. Adding on the concept of metatables only makes them even more powerful and flexible, if at the penalty of sometimes being confusing or opaque. I've been writing things in Lua for years thanks to Love2D, Garry's Mod, and many other places where it's used and I can say that experience helps more than anything. When I heard about PICO-8 and that it was based on Lua I was sold immediately.
Anyhow, let me try to answer your questions.
Your list of points is mostly correct, except perhaps the third part where you say you're shaky. The most difficult part to grasp about metatables is that they are a seperate companion to another table, and influence that other table's behavior. By calling setmetatable(x,y) you tell Lua to use table "y" as the metatable for table "x".
When implementing OOP-style behavior, by far the most important metatable key is index, which basically makes the table you associate (table X above) search the table in Y.index for keys if you try to access a key that does not exist in X. (ex: calling X.a() when there is no "a" key pointing to a function in X will try Y.index.a() before failing) So, the table Y.index is a "prototype" for tables that have Y as its metatable.
The most important thing to grasp is that Table Y is a metatable, not the prototype. The prototype is Y.index. Thus the new() function for a "class" associates the new "object" (just a table, like everything else in Lua) to a metatable with index=class_prototype.
Nothing is copied at all with this system, it's all just pointers to tables already defined in memory. When class() is called in my code, it returns the class prototype table, and if that returned value is modified by say, adding new keys that point to functions (like I do) then objects created will have access to those functions thanks to metatable magic. Similarly, defining keys with values in the prototype will make those "default" values for any object made with that prototype.
Oh, and the ellipses "..." parameter is great and super convenient. It's a token that simply represents "all remaining parameters" so you can create variadic functions (in simpler terms, a function that accepts any number of arguments) extremely simply. I mostly use it to create "wrappers" around functions; for example the "new" function for every created class in my code calls the constructor function _init for each new created object and passes the parameters from the new() call to the constructor directly via the ellipses token.
function example(a,b,...) print("a + b is ".. a+b) other_func(...) end
in this above stupid example, if you called
it would end up printing "a + b is 35" then call other_func(3,4)
And the game is coming along, I've just been lazy this week after a few weeks on working all the time on it. I've got about 16 rooms done and just want to get more done before I release it... preferably with the whole game playable finally.
[Please log in to post a comment]