Today, I’m going to nerd out just a little on where I’ve started with my Corona-based iOS tinkering. Before I can leap into building out fully-featured apps or games, I have some basic things to resolve, like how to stand up an application architecture. Here’s how I did it, and here are my assumptions around what you already understand!
- Object-oriented Programming – I’m using OO Lua here.
- Lua – If you don’t know Lua, it’s easy enough to follow, but some of the syntax is a little bit weird. I’m omitting some of the basic stuff, like class and subclass declarations, from this post, but it’s all in the ZIP file.
- Using Corona and iOS Deployment – I’m using IntelliJ to edit Lua, XCode to deploy to my iPad for testing, and heavily relying on the Corona Debugger. I won’t talk about these at all; I’m assuming you already know what you need to, or have the patience to Google it.
The Corona SDK comes with a barebones Hello World application. This is enough to get going, but I find that I like a little bit more structure. Therefore, I’m going to set up the following sort of structure:
- The Application class will be instantiated as a singleton object that handles a stack of application states, the top state of which can (if it has an associated view) be displayed. It basically manages the state stack and frame timing.
- The ApplicationState class can be subclassed to hold specific states that handle processing. I didn’t do any of that in this sample.
- The View class handles the logic to render a view of a state. There’s no actual state to hook in to, so this particular view is just doing some frame-based display as a sample.
The Application class essentially manages the state stack, calling runFrame on the top view, and that’s about it. The only function I want to look at here is the runFrame function. Since I haven’t set up states that have actual state to process, there is no code to execute a simulation — this will be fixed in the next version! For now, the important thing is that the runFrame function grabs the current time (which will be passed from here to any/all states that require execution, guaranteeing that the frame time is constant regardless of how long each state takes to process), as well as the associated timeslice length (important for game event timing in particular).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
function Application:runFrame() local now = system.getTimer() if _frameTime ~= 0 then self._lastTimeSlice = now - self._frameTime end self._frameTime = now local state = self:peekState() if state:hasValidView() then local view = state:getView() if view ~= self._lastView then if self._lastView ~= _INVALID_VIEW then self._lastView:clear() end end self._lastView = view view:runFrame(self._frameTime, self._lastTimeSlice) end end
The View class wraps a few simple functions: It manages a tree of Display objects, registers the Application it’s serving to (which feels like a bad design that I’ll clean up later), and has some standard functions: init(), which is called by a subclass to set up the Display object tree, and runFrame(), which is called each frame. Rather than look at the base class, here is the HelloWorldView subclass, which displays a color-cycling, sliding banner:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
require "view" HelloWorldView = View:new() function HelloWorldView:init() local title = display.newText( "Hello, World!", 20, 40, native.systemFont, 36 ) self:addDisplayObject('title', title) end function HelloWorldView:runFrame(frameTime, lastTimeSlice) local title = self:getDisplayObject('title') local r = math.floor(256 * math.cos(frameTime / 10000)) local g = 255 - math.floor(256 * math.cos(frameTime / 9777)) local b = math.floor(math.cos(-frameTime / 8555)) title:translate(4 * math.sin(frameTime / 250), 0) title:setTextColor( r, g, b ) end
I also use an abstract factory helper class to select and instantiate a View. All this concludes in a very straightforward “main.lua” to kick off the application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
require "application" _app = Application:new() _INITIAL_STATE = 'helloworld' function mainLoop() _app:init(_INITIAL_STATE) Runtime:addEventListener( "enterFrame", runFrame ); end function runFrame() _app:runFrame() end mainLoop()
Why go to all this length? For Hello World, it was pretty silly. For a more complex application (coming soon), it means that each state and view can be written in isolation, and I’ll never have to wire up things like frame timeslicing or display-state management again. (In theory!)