If you decide to master the development of games using the
Corona SDK , this article will give you the necessary fundamentals of the engine itself and the Lua language on which you will have to develop. In its own way, this language is beautiful and in many respects unusual. I tried to collect in one article all the most necessary information, but it turned out to be more than can be placed in one publication and I had to divide the article into 3 parts, this first part and in it we will consider the following questions:
- Source Commenting Procedure
- Variables, constants, scope
- Modules and project organization
- Conditional statements
Source Commenting Procedure
There is a notion of comments in almost any programming language, Lua is no exception. What the comment is generally is the explanations to the source code of the program, which are directly inside the commented code. The syntax of comments is determined by the programming language. From the point of view of the compiler or interpreter, comments are part of the program text that does not affect its semantics. So comments either do not affect the order of the code execution and you write them for yourself and those who after some time can begin to study your source code. Comments can be used as a debugging tool, i.e. You can place in a comment a piece of code temporarily excluding it from execution and thereby simplifying the moment of searching for an error in another section. There are 2 kinds of comments in Lua.
')
Single line comment
Everything written from the sequence of characters "-" to the end of the line is considered a comment.
Multiline comment
A multi-line comment begins with the sequence "- [[" and ends with any number of lines with the sequence "]]". Further examples of the application of comments:
function summ(a,b) return a + b end print(summ(5,7))
I will not pay undue attention to the issue of the quality of commenting code, it decides everyone for himself will cite just a few simple tips:
- A good code comments on itself - i.e. if you write obviously and call the variables deliberately in most cases no comments are required
- Do not write comments - for comment. Those. you should not write comments where everything is clear.
- Brevity is the soul of wit. Make comments simple and straightforward.
- Comment on the purpose of the file and functions.
Variables, constants, scope
Most programming languages have variables. This concept in most cases is understood as the named, or otherwise addressed memory area, the address or name of which can be used to access data and change the value during program execution. In Lua, variables can be created right where they need to be applied, i.e. There is no specific block (
as in languages like Pascal ) where it is necessary to declare variables before using them. I will give an example of declaring variables:
a = 1 b = 3 local c = 'example' d,pi = 2, 3.14 a = 'stroka'
Experts in other languages I think noticed several features:
- the type of variables does not need to be specified, the value determines its type
- the type of variables can be changed, i.e. if the variable was numerical (in the example “a”), and then it was assigned a lowercase value, it became lowercase
- Parallel assignment through a single "=" sign ( d, pi variables ) is available. This property can significantly shorten the time of the initial initialization, also allows you to exchange variable values without an intermediate variable, for example, a, b = b, a
- keyword local. This word is used to limit the scope of variables, if a variable is declared local inside an operation block ( function, cycle, conditional statement ), it will be visible only inside it. If the de-local variable is declared in the global context of the file, it will be visible from this line to the end of the file ( you will learn more about the scope in a bit below )
Let us dwell on constants. Under the constant is understood - the variable value of which does not change during use. Frequently, it is convenient to store several values in a project (
for example, an application version ) repeatedly in constants, in some languages there is a tool for creating constants in the direct sense of the word, variables that were declared once and in the future their value cannot be changed; in Lua there is no such tool You can blame everything, (
even the implementation of a function ) and for this reason, a convenient option for protecting against changing constants can be considered - the order of their naming. It is best to call constants in some special way so that they are more noticeable in the code, for example, BIG_BUKVAMI_SO_WORDS_ THROUGH_SPECKING, but this is not a rule but rather a tip.
I will briefly describe the format of acceptable names for variables, constants and functions, some things are declarative and cannot be broken, others are just tips:
- variables can contain Latin uppercase and lowercase letters, numbers, and the underscore "_"
- variables cannot begin with a digit
- variables cannot have the names of reserved words: and, break, do, else, else, end, false, for, function, if, in, local, nil, not, or, repeat, return, then, true, until, while
- case dependence As with most languages in Lua, the register is a determining factor in the uniqueness of a variable, i.e. lua, lua, lua and lua are all different variables.
- You should not abuse the name of variables beginning with an underscore, unless of course you are sure that these names are not exactly used by the language for naming internal mechanisms.
- use deliberate variable names that have a wide scope. It should be understood this way - if a variable is used in a large part of a project, it should not have a name or a meaningless and short
- share the naming format of variables, constants and functions. For example, it is possible to use TAKO_FORMAT for constants, for variable approximately_accordingly, and for the function to use SuchFormat - this will make the code more convenient.
Under the scope is understood - the code space within which you can directly access data. An incorrect understanding of the principles of organizing a scope can lead to a lot of unpleasant errors that are usually difficult to eliminate:
- the variable is not visible - it seems to you that everything is fine, and the variable is nil ( not defined ). This error is usually easy to identify but not always easy to fix.
- the variable can be seen where it is not used - this seemingly minor problem can lead to extremely complex bugs, for example, you do not expect that a variable from another part of the code will be visible in this area and do a test for its non-existence ( to initialize it ) and check It does not end with success and your initialization code is not executed, which leads to the use of a value that was defined elsewhere. To avoid this problem always and all the variables try to make the minimum visible using the word local. The use of global variables should always be kept to a minimum.
Further, we will not once again encounter examples of incorrect organization of the scope, I will focus on these places for this reason, this section can be completed.
Modules and project organization
A software module is a functionally complete program fragment, designed as a separate source code file. As you already know, the execution of any project in the
Corona SDK starts with the main.lua file. If you write the entire application in one file, you may have a lot of difficulties, give an example:
- Large amount of code. The source code of almost any full-fledged game can include hundreds, thousands, tens of thousands, and in some cases more than a hundred thousand lines of code, if you place all the code in one file at some stage, it will be incredibly difficult to accompany this project
- Many scenes. in most cases, real applications contain more than one scene ( functional screen ), for example, various menus, loading screens, perhaps several battle scenes, open world scenes, etc. Again, if you place everything in one file, you will have to load up the code with dozens of procedures for creating and deleting a scene when you switch between scenes the code will again become very complicated
- Universal portable code. In the process of creating your applications you will often encounter tasks of the same type that you transfer to libraries, and then you can easily transfer these libraries to your next projects, but if you write the portability of ready-made libraries in one file, it will be difficult As a useful code, it is necessary to extract fragments from a huge file.
There may be other problems if you incorrectly organize the project and write everything into one main.lua file, but in our lessons we will not do that, and then I will show you the method that I chose for my projects and which has proved itself well more than once to accept it or coming up with your own is up to you, but I will draw up further examples of projects in this way. Thus, we came to the conclusion that somehow we need to separate the source of the project into several files, moreover, these files will have different purposes and based on this, each file type will have its own naming order determined by the prefix in the name. This method is personally mine and I do not claim that it is the best, but since the further examples of games that you will see in my articles will have such a design mechanism for the project, I decided to take it as a basis. Briefly run through the types of files that may be useful.
Scene
Scene - sc prefix. The file name format is scName.lua. Where the name begins with a capital letter and determines the purpose of the file. For other types, this rule will also act therefore I will only specify their prefixes. The scene file has a fairly strict format and it is determined by the requirements of the
Corona SDK library - composer. A separate lesson will be devoted to the consideration of this library. For now, I am attaching a source file template:
local composer = require( "composer" ) local scene = composer.newScene()
It is worth noting that scene files do not need to be connected in any way to the project, since they will be connected as part of using the composer library.
Universal Library
Universal library - lib prefix. These files should be placed ready-made universal functions that can later be used in different parts of the application, for example, the trigonometric functions of your projects can be placed in the file libTriginom.lua and then these functions will be replenished and in the following projects they can be easily used simply by transferring the file and by connecting. Try to avoid binding in the universal libraries to the graphic design elements of the application, and pass the initial values to the call parameters. I will give an example of a simple library:
This library can be connected by placing the following code in main.lua:
Trigonom = require("libTrigonom")
I will give an example of using functions in any part of the project:
local a,b = 3,4
Widget / service
Widget / service - prefix svc. Widget is an analogue of the library, but the main functionality is based on the creation of graphic components. Using widgets it is convenient to create a composite interface, i.e. there are basic static components of the scene that are created as part of the main code in sc files and on top of static components can create dynamic widgets in the form of pop-up windows, relocatable forms and other amenities. Most conveniently, all project widgets should be designed with a single interface (
standard set of functions ), for example:
- show () - creates a window with the functionality of the widget and places it in the specified location on the screen
- refresh () - updates the state of the widget components making it relevant to the current moment. For example, if this widget is a clock, you need to update it once a second or minute (if there is no second hand).
- hide () - hides or removes the entire interface of the widget
It is worth noting that there is no risk in that you have the same method (
function ) names in different widgets - show / refresh / hide to access a function, you must add the name of the widget (
as is the case with libraries ) and for this reason globally namespace will not be clogged.
I will give an example of a widget (
clock ):
The order of connection and application of the service is as follows:
_W = display.contentWidth _H = display.contentHeight Clock = require "svcClock" Clock.show()
Services like libraries should be made fairly versatile, since they are the same candidates for reuse, most of the parameters (
ideally all ) should be made optional; have default values.
Database
Databases - prefix db. These files do not contain functions, but they store information about the static settings of the system objects, for example, the characteristics of units. I will give an example of a database with unit settings:
return { peasant = { title = {ru = '', en = 'Peasant'}, force = 1, defence = 2, speed = 1, }, spearman = { title = {ru = '', en = 'Spearman'}, force = 3, defence = 2, speed = 2, }, archer = { title = {ru = '', en = 'Archer'}, force = 5, defence = 1, speed = 3, }, knight = { title = {ru = '', en = 'Knight'}, force = 7, defence = 3, speed = 4, }, }
It should be noted that this type of implementation for larger databases is easier to replace with such an implementation
local M = { name = {'peasant','spearman','archer','knight'}, title = { {ru = '', en = 'Peasant'}, {ru = '', en = 'Spearman'}, {ru = '', en = 'Archer'}, {ru = '', en = 'Knight'}, }, stats = { {force = 1, defence = 2, speed = 1,}, {force = 3, defence = 2, speed = 2,}, {force = 5, defence = 1, speed = 3,}, {force = 7, defence = 3, speed = 4,}, }, M.res = {}, } for i = 1,#M.name do M.res[M.name[i]] = { title = M.title[i] force = M.stats[i].force, defence = M.stats[i].defence, speed = M.stats[i].speed, } end return M.res
In fact, this is the same thing in the first case, everything is initialized immediately, and in the second case, when the library is connected, the loop fills exactly the same table, the second method is more versatile because it allows you to edit this data more conveniently in the future, and secondly if a lot of data can significantly reduce the record Connect to use such databases as follows:
units = require('dbUnits') print(units.archer.title.ru)
Insert code
Insert code - the prefix ins. No matter how we try to reduce the size of the files, it is not always possible, in this case some sections united by a common goal can be put into a separate file. I will give an example, there is a code:
now we move the functions sum and razn to a separate file InsExample.lua, we have 2 files:
It should be noted that when I moved to a separate file, I made the functions global (
removed local ), otherwise they will go out of scope from main.lua. You also need to understand that inside the file insExample.lua the local variable P will not be visible, but the global variable k will be visible, and if you declare the variable k after require, then it will not be visible.
IMPORTANT :
In any use case of require for any file manipulations, this call is executed only once when you first run the thread through this section of the code, you should always take this into account if you do not want to suffer for a long time with complex bugs. Also, if you, like me, in modules of db / lib / svc type use a certain M variable to assemble data before return ( or any other ), do not forget to make this variable local, otherwise every next require will rewrite the result of the previous and this will lead to an even more complex error.
Conditional statements
As in most other languages, Lua has the ability to create conditional code branching operations, for this purpose, several types of if statement are used and a fairly convenient ternary branching method is used.
Comparison operators.
For data comparison, comparison operations are used. Each such operation consists of two variables, between which there is a comparison operator, there are 6 of them in Lua:
- == equals
- ~ = not equal
- <less
- > more
- <= less or equal
- > = greater than or equal to
In essence, the result of any comparison operation is false or true (
false or true ), these values are characteristic of the type, which, along with other types, will be considered below. Now we will consider using what constructions of the language the comparison operations are applied.
IF operator
At once for example - if a is greater than b, we assign c true
if a > b then c = true end
. — if .. , .. c end, c If if:
local c = false fa > b then c = true end
local c = a > b
:) if. and(
) or(
). , .
AND
and , , . , - , . obj1.obj2.obj3 , :
if obj1.obj2.obj3 then c = true end
, , obj1 obj2 .. :
if obj1 and obj1.obj2 and obj1.obj2.obj3 then = true end
.
local c = obj1 and obj1.obj2 and obj1.obj2.obj3 and true
OR
or . Those. and, , and — , or — . or.
if a>b or c>b or k<0 then c = true end
:
local c = a>b or c>b or k<0
NOT
. not , .. a > b a b, not a > b a b.
IF ELSE
: , . if else :
if a>b then c= true else c =false end
. AND OR:
local c = a>b and true or false
IF ELSEIF ELSE
: 1 — 1, 2 — 2… N — N, X. switch, Lua :
if a>b or d<b then k = 1 elseif b>c and a<b then k = 2 elseif c>d then k = 3 else k = 0 end
— . IF ELSEIF ELSE: - else — , - , , end. , , :
k = 1 if k > 0 then k = 2 print(k) elseif k > 1 then k = 3 print(k) end
print -> 2. IF ELSEIF ELSE:
local k = (a>b or d<b) and 1 or (b>c and a<b) and 2 or c>d and 3 or 0
or and , or , .
:
, : - — , - .
( 2/3)