gengi (pronounced: gaen-ghee)
a norns game engine
This came to be as part of the development process for thumper. I’ve solidified some decent patterns for developing the basics of a game engine, and I wanted to share a clean little example of that while @Snail and I continue to hack away at something fun for y’all to play.
This repo will be updated as things happening in demos (for progress on these, see the thumper thread) prove useful and worth keeping around. Please feel free to hack on it as a way of contributing to the norns game dev ecosystem! The thumper thread is a bit of a mad-woman’s diary at the moment… Once we have a solid renderer, audio backends are sure to follow
. Then the game/instruments will start to appear!
Requirements
Documentation
- see the script notes for usage of the example script
-
how would I add a sprite or something else on screen?
modify redraw() and add your drawing functions between clear() and update(). Check out the following code:
function redraw()
...
screen.clear()
...
my_character:draw()
screen.update()
...
end
-
how do I switch between screens in control functions?
this involves my dirty trick to keep the code so light:
- rendering/key/encoder functions look up behavior based on global state stored in the
game
table
-
screens
are an easy way to add text/graphic overlays tangled with key/encoder functions. A third screen could easily be added called “world”.
- when you want to go to another screen, manipulate the
game
table. Consider this example:
game.screen[1].event = function(self,bool)
if bool then
print("starting game!")
-- 3rd screen contains the world
game.screen.selected = 3
-- assign a drawfunc so that the character gets drawn
game.my_character.draw = game.my_character.draw.drawfunc
end
end
Download
21 Likes
Just thought of a screen for a level editor:
editor = {
tiles = { my, tile, objects, selected=1 }
tools = {
selected=1,
{
name="tile picker",draw=function(self)
set_screen_bright(); set_font_small()
screen.move(0,10); screen.text_left(tile.name)
end
},{
name="place",draw=function(self, xy)
set_screen_dim(); set_font_small()
screen.move(0,10); screen.text_left(tile.name)
draw_cursor(xy[1],xy[2])
end
},{
name="erase",draw=function(self,xy)
set_screen_dim(); set_font_small()
screen.move(0,10); screen.text_left(tile.name)
draw_cursor(xy[1],xy[2])
end
},
xy = {1,1},
draw = function(self)
local tool = self.tools[self.tools.selected]
local tile = self.tiles[self.tiles.selected]
set_font_small(); set_screen_dim()
screen.move(0,10); screen.text_center(tool.name)
tool:draw(self.xy)
end,
event = function(self,bool)
local tool = self.tools[self.tools.selected]
local tile = self.tiles[self.tiles.selected]
local x,y = self.xy[1],self.xy[2]
if tool = "place" then
-- game.screen[3] is the world
if bool then game.screen[3].tiles[x][y] = tile end
elseif tool = "erase" then
if bool then game.screen[3].tiles[x][y] = nil end
end
end
}
Use your imagination for the key and encoder funcs but they basically just involve firing off events and scrolling whatever happens to be in focus 
This reveals a pattern for chaining draw function lookups combined with state. This is probably not great for performance in the long run, but eventually these calls can be optimized by composing these functions in clever ways.
Not sure if any of this is a good idea, but it’s a fun framework for really flexible UIs.
4 Likes
Not that I really understand much about it, but to me this seems like a great idea! 
2 Likes
I’ve not dug into this code yet, but a quick question - you mention sprites in the description. How should someone create a sprite? Is this a graphic file like png? some bitmap that’s read into a table?
I did some stuff like this last year but have completely forgotten all that. Hoping I can re-learn a bit from your code. 
I’m using the term loosely – Ive been just typing in tables of pixel values in the source code.
Theyre basically just drawing functions that draw something at a given offset.
1 Like