GameDev, Indie, Corona SDK, GameJam 48h, DevConf, Go, Laser Flow

Welcome! Immediately apologize for the title — so many things I wanted to tell, but it was too long.

The story will focus on my game (iOS, Android), made with Corona SDK, the corona and the development of her about the contest "write a game in 48 hours" about the recent DevConf about the Go language.

Introduction


I had been developing the game in the Tower Defense genre with the crown, but the game requires a huge amount of time to be really high quality, and the quality of the game is item #1 on the road to success (item #2 is a lot of money on marketing, if someone does not know). So have long wanted to do something, albeit small, but its and in a short time.

"Caution mnogabukav!"

GIGJam 48


And then in early may arranged a competition from a small Studio Glitch Games together with Corona Labs on the development of the game in 48 hours. As Ludum Dare, only for Corona SDK. I wanted to make a puzzle, design ideas, two almost sleepless nights and I made a game of Laser Flow — Experiments with Lasers. Then it was still called just Laser Experiments. Similar games a little different and my adequate control, colour mixing and I hope pleasing to the eye and ear. I myself really like. But the results of the competition, unfortunately, my views do not coincide with the views of the jury. Total was 23 games by about 35 participants.

I made a cute home screen for the game to even slightly impress the jury. Most time was spent on code that is responsible for the reflection of the lasers from the mirrors and the co-flow laser cell by cell so that they overlap each other, and walked side by side. All this could not be avoided and now on some levels it is possible so to position the mirrors that different lasers overlap. I hope this doesn't spoils the game as a whole.

After sending the games began languid expectation of results. Days went by with no results. Then he announced that the jury will take several weeks to assess, and in the IRC room #corona riots broke out...
Those were hard times. Results had to wait in the end more than a month.

By the way here is the screenshots of the game showing how it has changed since submission to the contest.


DevConf 2013


Toward the end of may I was contacted by the organizer DevConf and invited to take part in their competition reports. I suggested two topics: about Corona SDK Pro and Go. Took both reports, which I was very happy. Thanks to the organizers for this, for free travel, accommodation and meals.

I thought how great it would be to finish and release the game to the conference to report immediately to a live example that people could download and play. As the plan was to have a little contest — solve the secret level faster than the others, and get a t-shirt with the game logo (icon). But first, due to a bug with the purchases my app was rejected, and left it only on the second day of the conference, and secondly, a t-shirt are unable to print with the bright colors I need (special CMYK profile). By the way, 1024x1024 pixels is enough to print on a t-shirt pattern 20x20cm.

It turned out that it took me a little over a month to finish the game.

Slides to the presentations I began to do more at home, continued on the plane and finished at the hotel. Used in travel iPad and Asus Eee Pc 701 4G with lubuntu. The one that is touch screen, sinezub and an additional SD slot. The modem had to be removed. Slides were done in LibreOffice.

Thanks to the neighbor's room to Paul for giving the MacBook to recompile the game and sending it to the App Store after unexpectedly rejected. Oddly enough it was enough to get the game approved at this time.
The conference was certainly interesting, had a lot of people and some of the reports gathered so many listeners that there were not enough chairs. Section Mobi really did not enjoy the same popularity.

My reports were the last in the grid and collected, quite frankly, a bit of the audience. Report on the crown, I was prepared with a view to show details, some time-tested best practices and tips that the report was not a dry retelling the main page of the site and documentation, I wanted to make the report truly useful. However, almost none of the audience was not familiar neither with the crown nor with Lua. Had to rebuild the report closer to the trial, to show what the crown and make a kind of introduction to Lua.

To my surprise, as I told him about the crown, the first day of the conference ended. A report on the Go I haven't even begun. A quick poll showed that the audience is waiting for this report and we decided to continue on the ground floor of the hotel. We had a little, I was given a laptop with a large screen (thank you!) instead of a projector, unfortunately not all comfortable sitting and not everyone could normally be seen, but okay. The slides I posted on the site, all who need download. After the presentation we talked a little more about Go, about his prospects, about how it is we all like and was presented a book on the language one participant (also want!)

On the second day were very interesting presentations about high load.

From the conference I was left with a baseball cap with the logo of DevConf, notepads, pens, badges and a toothbrush from the hotel. On this DevConf about basically everything. Was great.

The slides for my presentations can be downloaded from my website. The link at the bottom of a topic.

Corona SDK


Crown good. It is the most easy to use framework for game development, recently got the free version and is highly recommended for familiarization. Knowing Python and PHP, it took me only a couple of days learning Lua, another week was devoted to the realization of most of the concepts of the language, the PLO and the main part of the API of the crown.

Lua has cool stuff, for example, logic operations return the last triggered item, you can do a similar design: access to an object only if it exists and you specify a default value otherwise.

the
local txt = a and a.text and a.text:lower() or 'default'

-- Equivalent:
local txt = 'default'
If a then
if a.txt then
txt = a.txt:lower()
end
end

Often make a mistake when using callbacks.
the
local function myFunc()
print('Nya')
end
timer.performWithDelay(1000, myFunc(), 1)

In this case, the timer is not the function itself, and its execution result (nil). You just need to remove the parenthesis after the function name.
Once in IRC asked how to tell the timer that it performed not only a specified time, but right now. Answer — just call it in your code in the same place create the timer.

Even Lua comes with sugar:
the
object.property -> object['property']
local function nya() -> local nya = function()
object:method(params) -> object.method(object, params)
someFunction{name = 'Someone'} -> someFunction({name = 'Someone'})
etc.

OOP in Lua

The PLO can be organized on the basis of pseudo-classes using metamethods (similar to magic methods in PHP). And maybe quite a good idea based on the simple tables. Moreover, both private and public, and even protected, can arrange with no problems.
Show code OOP
the Simplest option with an empty object:
the
local function newObject()
local object = {}
return object
end

local myObject = newObject()

Add public variables:
the
local function newObject()
local object = {}
object.somePublicVar = 100
return object
end

local myObject = newObject()
print(myObject.somePublicVar)

Now let's add some method:
the
local function newObject()
local object = {}
object.somePublicVar = 100
function object:setName(name)
self.name = name
end
return object
end

local myObject = newObject()
myObject:setName('Nyashka')

print(myObject.name)

Note the use of a colon. In this case, inside the function-method, there is a special variable self, which is a reference to the object itself. To call these methods also need a colon, or with one point, but as a first argument to specify the object.
Add a private variable, such as money:
the
local function newObject()
local object = {}
object.somePublicVar = 100
local money = 0
function object:setName(name)
self.name = name
end
function object:addMoney(amount)
money = money + amount
end
return object
end

local myObject = newObject()
myObject:setName('Nyashka')
myObject:addMoney(50)

print(myObject.money) -- nil

If we try to appeal to the money through the point, we get nil, because the variable is not in the object table, and in the scope of the function that created the object. Therefore, this variable is visible in the method addMoney().

Now inheritance:
the
local function newObject()
local object = {}
object.somePublicVar = 100
local money = 0
function object:setName(name)
self.name = name
end
function object:addMoney(amount)
money = money + amount
end
return object
end

local function newChild()
local object = newObject()
return object
end

local myChild = newChild()

In this case, the object is not created an empty table {}, but other base object. So in the function newChild, you can add object properties and methods or even override them.

Protected implemented a bit more interesting. Create a private table to put all the protected variables, and return it together with the created object using return. Assume we want to transfer the money by inheritance.
the
local function newObject()
local object = {}
object.somePublicVar = 100
local protected = {money = 0}
function object:setName(name)
self.name = name
end
function object:addMoney(amount)
protected.money = protected.money + amount
end
function object:getMoney()
return protected.money
end
return object protected
end

local function newChild()
local object protected = newObject()
function object:spendMoney(amount)
protected.money = protected.money - amount
end
return object
end

local myChild = newChild()
myChild:addMoney(100)
myChild:spendMoney(60)
print(myChild:getMoney()) -- 40

If you want to override any method, it is declared the same way. The new overwrites the old. If you want to make a wrapper, that first saved the old method in some variable and then call it from the new method:
the
local function newObject()
local object = {}
object.somePublicVar = 100
local protected = {money = 0}
function object:setName(name)
self.name = name
end
function object:addMoney(amount)
protected.money = protected.money + amount
end
function object:getMoney()
return protected.money
end
return object protected
end

local function newChild()
local object protected = newObject()
function object:spendMoney(amount)
protected.money = protected.money - amount
end
local parent_setName = object.setName
function object:setName(name)
name = 'Mr. '.. name
parent_setName(self, name)
end
return object
end

local myChild = newChild()
myChild:setName('Nyashka')
print(myChild.name) -- Mr. Nyashka

In the case of objects displayed on the screen, it is enough to replace an empty table {} when creating an object to call any function from the API Corona SDK that returns a graphic object. It can be display.newImage(), display.newRect() or I often use the group display.newGroup(). If you use a group, these objects are very easy to expand other graphic elements. But keep in mind that an excessive number of groups reduces performance, so if Your code starts to slow down, try to use as less as possible.

The module is also a table and also an object. It is possible to adhere to the same principles as in the PLO. Early modules were written using the keywords/functions module(). She was transferred in the name of the module and it has changed the environment of the script on your own local that it was not necessary to write a bunch of "local", all variables and functions were placed in this local space. And all anything, but inside the module does not have access to global functions like print and others. Then came up, what if you add special function as the second argument of the function module () will search for all global things where it should be — in _G. Responsible this special function package.seeall. But in practice, moreover, all global functions and variables inside all modules, and the modules were created and fit by the function module in the global namespace. Porridge. Lunar porridge. Global lunar porridge.

Insert your example module from the pastebin:
Module
local _M = {} -- module table

_M.someProperty = 1 -- class properties

local function createText()

end

local privateVar -- so do local variables

_GLOBAL_VAR -- without local, it's global

function _M.staticMethod(vars)
-- this is class method like function (dot)
-- there is no "self"
end

function _M:someMethod(vars)
-- this is object method like function (colon)
-- there is "self"
end

function _M:newBaseObject()
-- Here goes the constructor code
local object = display.newImage(...) -- could be a display object or an empty table {}
object.vars = 'some vars'
object.name = 'BaseObject'
object.property = self.someProperty -- from module

function object:sign(song)
print(self.name .. 'is singing' .. song)
end


function object:destroy()
-- optional destructor, you can override removeSelf() as well
self.vars = nil
self:removeSelf()
end

return object
end

Inheritance -- Now
function _M:newChildObject()
local object = _M:newBaseObject()
-- override methods or add any new
object.name = 'ChildObject'
function object:tell(story)
print(self.name .. 'is telling' story..)
end
return object
end

return _M -- return this table as a module to require()

Create table module and handles are placed inside the content what you want. Everything is convenient, everything is fine.
At the end of the file write be sure to return _M. Otherwise the module will not be loaded through require.

Other about Corona SDK

Did you know that the three dots... represents a variable list of arguments of the current function? It is easier to write wrappers or variadic functions.

the
local function my_warning_print(...)
print('Warning:', unpack(arg))
end


Speed Lua is approximately 20 times lower than that of C. LuaJIT is only a couple times slower, but compilation in real time prohibited in iOS. Pichalka. On Android you can compile, but the crown is not implemented. The good news is that Lua is very rarely the bottleneck. Only when intensive calculations.

The speed of development is very important. The crown can forgive a lot just for that.

All that is not local is global. Store variables in tables for convenience.
Much time is spent on finding the variable in the service tables of visibility. First, the visibility of the current block or function, then the chain up to the _G. This happens in the case of global variables. Accordingly, they are slow. Cache with the help of all local variables and functions to increase productivity in a particular place. But don't overdo it. Most of the code, the cache will not result in any noticeable speedup. optimize only the places that are called very often. This may be a function in an infinite timer or enterFrame event handler is called every frame. While Lua will finish processing all code for the current frame, Corona does not start the renderer. So if you change the parameter x to allow the images on the screen many times inside of a function, only the last value will be reflected on the screen.

Cache even things like math.random, table.insert, ipars, pairs and other built-in functions.

Do not litter in the area of global visibility, you can do something to break. Or just be a memory leak.

It's possible to override everything in _G, but it is certainly not recommended. To see what's out there just go print it. Remember derived variables and never use them. Some say that in no case do not put anything in _G. But it's overkill. It is quite normal in the global visibility to have a few items for myself like the table for your module (as a namespace, for example "app"), the configuration or just the built-in modules below in each scene, not to write a bunch of require.

When thinking about performance, add a module for monitoring FPS, texture memory and Lua memory. For the first time after you add such a module all of a panic, "where such a memory leak". In fact, a gradual increase of memory consumption is the norm, up to about 10kB per second. The next call of the garbage collector memory will return to its minimum value. The garbage collector can be set up if it hinders you in your algorithms. For example, to run it independently after calculation, that it is not inhibited them.

Lua memory is in practice not exceed a couple of megabytes. The main volume of this texture. It is desirable to optimize. Inside the chip all the textures (meaning any image) are aligned to powers of two. So for best results stick to powers of two for your files.
In the crown of the fps rate is adjusted either 30 or 60, always use 60 as the interface of your application becomes much smoother and more pleasant to look at.

Animate everything. Very simple to achieve this if you follow the rules of good taste when designing your application. To use objects for everything. Even if the functions of the wrapper over the standard function newImage. In this case, you can add animations once in the generator of your object and it will appear throughout the application. Just do not overdo it with them. Congestion or confusion in the sense that you are forcing the user to wait is very bad for the overall impression of the application.

Here is my wrapper for the standard newImage. In one line can define the position on the screen and to add to the group.
Wrapper over the newImage and useful things
local _M = {}
_W = display.contentWidth
_H = display.contentHeight
_T = display.screenOriginY -- Top
_L = display.screenOriginX -- Left
_R = display.viewableContentWidth - _L -- Right
_B = display.viewableContentHeight - _T-- Bottom
_CX = math.floor(_W / 2)
_CY = math.floor(_H / 2)

_COLORS = {}
_COLORS['white'] = {255, 255, 255}
_COLORS['black'] = {0, 0, 0}
_COLORS['red'] = {255, 0, 0}
_COLORS['green'] = {0, 255, 0}
_COLORS['blue'] = {0, 0, 255}
_COLORS['yellow'] = {255, 255, 0}
_COLORS['cyan'] = {0, 255, 255}
_COLORS['magenta'] = {255, 0, 255}

function _M.setRP (object, ref_point)
ref_point = string.lower(ref_point)
if ref_point == 'topleft' then
object:setReferencePoint(display.TopLeftReferencePoint)
elseif ref_point == 'topright' then
object:setReferencePoint(display.TopRightReferencePoint)
elseif ref_point == 'topcenter' then
object:setReferencePoint(display.TopCenterReferencePoint)
elseif ref_point == 'bottomleft' then
object:setReferencePoint(display.BottomLeftReferencePoint)
elseif ref_point == 'bottomright' then
object:setReferencePoint(display.BottomRightReferencePoint)
elseif ref_point == 'bottomcenter' then
object:setReferencePoint(display.BottomCenterReferencePoint)
elseif ref_point == 'centerleft' then
object:setReferencePoint(display.CenterLeftReferencePoint)
elseif ref_point == 'centerright' then
object:setReferencePoint(display.CenterRightReferencePoint)
elseif ref_point == 'center' then
object:setReferencePoint(display.CenterReferencePoint)
end
end

function _M.newImage(filename, params)
params = params or {}
local w, h = params.w or _W, params.h or _H
local image = display.newImageRect(filename, w, h)
if params.rp then
_M.setRP(image, params.rp)
end
image.x = params.x or _CX
image.y = params.y or _CY
if params.g then
params.g:insert(image)
end
return image
end

function _M.setFillColor (object, color)
if type(color) == 'string' then
color = _COLORS[color]
end
local color = table.copy(color)
if not color[4] then color[4] = 255 end
object:setFillColor(color[1], color[2], color[3], color[4])
end

function _M.setTextColor (object, color)
if type(color) == 'string' then
color = _COLORS[color]
end
local color = table.copy(color)
if not color[4] then color[4] = 255 end
object:setTextColor(color[1], color[2], color[3], color[4])
end

function _M.setStrokeColor (object, color)
if type(color) == 'string' then
color = _COLORS[color]
end
local color = table.copy(color)
if not color[4] then color[4] = 255 end
object:setStrokeColor(color[1], color[2], color[3], color[4])
end

function _M.setColor (object, color)
if type(color) == 'string' then
color = _COLORS[color]
end
local color = table.copy(color)
if not color[4] then color[4] = 255 end
object:setColor(color[1], color[2], color[3], color[4])
end
return _M


A little more about the pastebin.com/GWG3sJLZ

Also includes a very useful function of setRP, which eliminates the need to write a long value reference points. And how to set the color of objects by name.

To support the back button in Android, just add the following code to Your main.lua
the
Runtime:addEventListener('key', function (event)
if event.keyName == 'back' and event.phase == 'down' then
local scene = storyboard.getScene(storyboard.getCurrentSceneName())
and if the scene type(scene.backPressed) == 'function' then
return scene:backPressed()
end
end
end);

And define in each scene where you want the response to this button function:
the
function scene:backPressed()
storyboard.gotoScene('scenes.menu', 'slideRight', 200)
return true
end

Don't forget to add a dialog "are you sure You want to exit" and calling native.requestExit() (only for Android, for iOS os.exit()).

config.lua is a panacea for fragmentation devices

Dynamic scaling is fine. Almost.
The content is adapted to the current screen size, while maintaining the visible virtual part of your application (typically 320x480) and adding seats for any two edges (letterbox). Place your anchors — screen corners, sides or center of the screen. Try not to use elements that would need to be scaled. If on different screens, the controls look a little less or more is not that bad. Most importantly make them big enough. Not lower than 32 pixels for the button. Better 64 or at least 48. There are corners of your content and there are corners of the screen directly, that's a different virtual screen area.
Few people know that in the config.you can write lua code. You can dynamically assign the content area. For what? To achieve pixel perfect graphics on most devices. Otherwise, the picture is a little blurred and flawless look is lost.

Stack your files into subfolders — lib for libraries, scenes to scenes, storyboard, images for pictures, sounds and possibly music for quite a what you guessed. Always use lower case for your files to avoid problems with downloading files on the file system is case-sensitive.

Use bitbucket.com for backup and version control your application. It's free for private projects with teams of a few people.

Music for your game you can search on soundcloud, indiegamemusic, google. Don't forget Garabe band, FrootyLoops, Audacity and bfxr.

In Garage Band with the synth 8 bit sounds you can get a good modern sound oldskula. With the use of effects and EQ.

To call the onRelease of the button widget using the button._view._onReleas().

Carefully browse the library for standard features. Few people know that the crown supports twitter client for ios (native.showPopu('twitter')). Or that you can show the page of the store right from the app.

Note that with multitouch on your button you have to press twice with two fingers. If there is something that you cannot run many times in a row, put the flag inside the button that it was activated and more pressed not.

Use Flurry to understand what's wrong with your game, how to improve it to more users recommend it.

As IDE for Corona SDK, I chose Zero Brane Studio — open source, English-speaking developer, written in Lua + wxWidgets cross-platform. There are other IDE, but this as a whole like more.

Go


Go classy. Anyone to use.

I wrote the server part of another game (the order) and I enjoyed it very much. The speed and ease of development than C/C++ execution speed or a little slower, or the same. It is now a vast community of language and quite a lot appeared different libraries.

Laser Flow


The game was released in the App Store June 15. On this day, there were 3,000 downloads and 10 purchases. Perhaps the game was too complicated, but maybe just for fans of the game.

I want to tell you about the level generator and level editor. Some days I took to write the algorithm to find solutions. Used different variations of brute force, with different entry conditions, with all the code optimizations and the use LuaJIT, the result was disastrous — the levels are solved very slowly. Need to develop an algorithm based on the search algorithms of the shortest path, but it's not easy. Left from the generator only the generator field, and called it the Abyss of Random.

Editor interesting. Chose to do either within the game itself, but there is the inconvenience of control — need to figure out where to place a bunch of buttons, if you do a removable panel, it is very awkward turns. Another option is to make a separate desktop application. I have the power keyboard but the development almost from scratch.

In the end, I decided to combine. Googled Keylogger for Mac OS in python and the script determine the current active window. Therefore, if the currently active simulator of the crown, then send it via UDP pressed key plain text. The crown rises a UDP server and listens to commands when they are in the scene with the playing field.

In the end, the simple taps of the keyboard, I add any items on the field can regenerate it, can save, can change the color of the lasers and set the target cell. Everything was very convenient. 140 levels I have made for the day.

Three more bonus levels hidden in the game to find them easily.

Code Keylogger
from AppKit import NSWorkspace
from Cocoa import *
from Foundation import *

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

class AppDelegate(NSObject):
def applicationDidFinishLaunching_(self, aNotification):
NSEvent.addGlobalMonitorForEventsMatchingmask_handler_(NSKeyDownMask, handler)

def handler(event):
if NSWorkspace.sharedWorkspace().activeApplication()['NSApplicationName'] == 'Corona Simulator':
s = unicode(event).split(' ')
if s[8]:
s = s[8][-2:-1]
if (s >= 'a' and s <= 'z') or s == "' or s == '[' or s == ']':
sock.sendto(s, ('127.0.0.1', 5000))

def main():
app = NSApplication.sharedApplication()
delegate = AppDelegate.alloc().init()
NSApp().setDelegate_(delegate)
AppHelper.runEventLoop()

if __name__ == '__main__':
main()


the server Code in Lua
local udp = socket.(udp)
udp:setsockname('127.0.0.1', 5000)
udp:settimeout(0.001)
local char, ip, port
timer.performWithDelay(200, function ()
char, ip, port = udp:receivefrom()
if char then
local scene = storyboard.getScene(storyboard.getCurrentSceneName())
if type(scene.keyboardPressed) == 'function' then
scene:keyboardPressed(char)
end
end
end, 0)

On the same principle you can make a button to save screenshots directly from the game to download in various app stores. You need to use the function display.save().

Before publishing on habré added support of Russian language, made available from the levels easy, added the dimension that made the offer to buy all the levels and put in Google Play.

Chat on IRC like Corona SDK and Go. On freenode.net: #corona and #go-nuts.
Players in Th offended if you go to their channel (#go) and start shipping issues in programming.

Links


Laser Flow
iOS itunes.apple.com/us/app/laser-flow/id647540345?ls=1&mt=8
Android play.google.com/store/apps/details?id=com.spiralcodestudio.laserflow

Slides from the conference: Corona SDK, Go Language.

Corona SDK coronalabs.com
Go Language golang.org

Thank you for your attention! If you have any questions, I will be glad to answer in comments.

Typing errors and mistakes please send a PM.
Article based on information from habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Car navigation in detail

PostgreSQL: Analytics for DBA

Google has launched an online training course advanced search