Difference between revisions of "Introduction to Graphics"

From GiderosMobile
(→‎Description: added more content)
(→‎Description: more stuff)
Line 7: Line 7:
 
== Introduction to graphics ==
 
== Introduction to graphics ==
  
In Gideros Studio, all graphical content is constructed by creating graphical instances and adding them to the scene tree. For example, Bitmap class is used to display bitmap graphics, and TextField is used to display text object. Sprite class is used to group and transform these instances. The inheritance diagram of core display is shown in Figure xxx.
+
In Gideros Studio, all graphical content is constructed by creating graphical instances and adding them to the scene tree. For example, the Bitmap class is used to display bitmap graphics, and TextField is used to display text object. Sprite class is used to group and transform these instances.
 +
<br/>
  
Sprite is the base class of all display classes and has no visual representation. It’s the main construction element and used to create display hierarchies. Sprites can have other sprites as their children and these children inherit properties such as position, scaling and transparency from their parent sprite. Hierarchy is an important feature in graphics - when you move your parent sprite, all the child (and grandchild) sprites also move simultaneously.
+
Sprite is the base class of all display classes and has no visual representation. It’s the main construction element and used to create display hierarchies. Sprites can have other sprites as their children and these children inherit properties such as position, scaling, and transparency from their parent sprite. Hierarchy is an important feature in graphics - when you move your parent sprite, all the child (and grandchild) sprites also move simultaneously.
 
Here are some examples:
 
Here are some examples:
  
 +
<source lang="lua">
 
- Create a new Sprite instance:
 
- Create a new Sprite instance:
 
local childSprite = Sprite.new()
 
local childSprite = Sprite.new()
Line 28: Line 30:
 
- Remove the child:
 
- Remove the child:
 
mySprite:removeChild(childSprite)
 
mySprite:removeChild(childSprite)
 +
</source>
 +
<br/>
  
 
Sprites can have only one parent. Therefore if you add a child object that already has a different sprite as a parent, the sprite is removed from the child list of the other sprite and then added to this sprite.
 
Sprites can have only one parent. Therefore if you add a child object that already has a different sprite as a parent, the sprite is removed from the child list of the other sprite and then added to this sprite.
Line 37: Line 41:
 
When Gideros starts, the display list hierarchy contains one item (the global Stage instance) only and we can add more:
 
When Gideros starts, the display list hierarchy contains one item (the global Stage instance) only and we can add more:
  
 +
<source lang="lua">
 
- Create a Sprite object
 
- Create a Sprite object
 
local mySprite = Sprite.new()
 
local mySprite = Sprite.new()
 
- Add this sprite to the stage:
 
- Add this sprite to the stage:
 
stage:addChild(mySprite)
 
stage:addChild(mySprite)
 +
</source>
 +
<br/>
  
 
== Textures and Bitmaps ==
 
== Textures and Bitmaps ==
Line 46: Line 53:
 
Texture class is used to load an image file and holds the data stored in the image file. The Bitmap class inherits from Sprite and wraps a Texture object for on-screen display. By separating image display (Bitmap) from data (Texture), it’s possible to create many Bitmap objects simultaneously display the same Texture object each with its own display characteristics.
 
Texture class is used to load an image file and holds the data stored in the image file. The Bitmap class inherits from Sprite and wraps a Texture object for on-screen display. By separating image display (Bitmap) from data (Texture), it’s possible to create many Bitmap objects simultaneously display the same Texture object each with its own display characteristics.
  
 +
<source lang="lua">
 
- Load a texture:
 
- Load a texture:
 
local texture = Texture.new("image.png")
 
local texture = Texture.new("image.png")
Line 54: Line 62:
 
- Add the Bitmap object to the stage
 
- Add the Bitmap object to the stage
 
stage:addChild(bitmap)
 
stage:addChild(bitmap)
 +
</source>
 +
<br/>
  
 
=== Anchor Point ===
 
=== Anchor Point ===
Line 60: Line 70:
  
 
[[File:Anchorpoint.png]]
 
[[File:Anchorpoint.png]]
setAnchorPoint(0,0)     setAnchorPoint(0.5, 0.5)       setAnchorPoint(1,1)
+
<br/>
 +
setAnchorPoint(0,0)setAnchorPoint(0.5, 0.5)setAnchorPoint(1,1)
 +
<br/>
  
 
In this example, we set the anchor point to (0.5, 0.5):
 
In this example, we set the anchor point to (0.5, 0.5):
  
 +
<source lang="lua">
 
local bitmap = Bitmap.new(Texture.new("image.png"))
 
local bitmap = Bitmap.new(Texture.new("image.png"))
 
bitmap:setAnchorPoint(0.5, 0.5)
 
bitmap:setAnchorPoint(0.5, 0.5)
 +
</source>
 +
<br/>
  
 
=== What is a Texture Atlas? ===
 
=== What is a Texture Atlas? ===
Line 71: Line 86:
 
Texture atlas is a single large image which contains many smaller sub-images. Each sub-image contained in the texture atlas is defined with a rectangular area.
 
Texture atlas is a single large image which contains many smaller sub-images. Each sub-image contained in the texture atlas is defined with a rectangular area.
  
<gallery widths=350px heights=250px perrow=1>
+
<gallery widths=250px heights=350px perrow=1>
 
Ikonomikon screenshot 01.png|
 
Ikonomikon screenshot 01.png|
 
</gallery>
 
</gallery>
  
There are two main reasons of using a texture atlas instead of many independent textures:
+
There are two main reasons for using a texture atlas instead of many independent textures:
 +
<br/>
 
1. Texture atlases usually consume less memory.
 
1. Texture atlases usually consume less memory.
To use an image as a texture, its width and height always have to be a power of two. Gideros automatically increase the texture size to conform this rule. For example, an image with dimensions of 100x200 becomes a texture with dimensions of 128x256 in memory. That’s where the texture atlas becomes handy. Texture atlases usually have power-of-two dimensions and therefore don’t need to be enlarged.
+
To use an image as a texture, its width and height always have to be a power of two. Gideros automatically increase the texture size to conform to this rule. For example, an image with dimensions of 100x200 becomes a texture with dimensions of 128x256 in memory. That’s where the texture atlas becomes handy. Texture atlases usually have power-of-two dimensions and therefore don’t need to be enlarged.
  
 
2. Rendering from a texture atlas is usually faster.
 
2. Rendering from a texture atlas is usually faster.
Each texture is bound to OpenGL just before rendering it and binding is costly operation. Using texture atlases instead of individual textures helps in reducing bind operations
+
Each texture is bound to OpenGL just before rendering it and binding is a costly operation. Using texture atlases instead of individual textures helps in reducing bind operations
 +
<br/>
 +
<br/>
  
 
TextureRegion
 
TextureRegion
 
The TextureRegion class specifies a texture and a rectangular region in it. It is used to define independent texture regions within a texture atlas which is a large image, which contains many smaller sub-images. Displaying the texture regions is same with displaying textures: Create a Bitmap object that wraps the TextureRegion object:
 
The TextureRegion class specifies a texture and a rectangular region in it. It is used to define independent texture regions within a texture atlas which is a large image, which contains many smaller sub-images. Displaying the texture regions is same with displaying textures: Create a Bitmap object that wraps the TextureRegion object:
  
- Load a texture:
+
<source lang="lua">
 +
-- Load a texture
 
local texture = Texture.new("image.png")
 
local texture = Texture.new("image.png")
- Create a texture region as top left point is (30, 40) with dimensions (100, 50):
+
-- Create a texture region as top left point is (30, 40) with dimensions (100, 50)
 
local textureRegion = TextureRegion.new(texture, 30, 40, 100, 50)
 
local textureRegion = TextureRegion.new(texture, 30, 40, 100, 50)
- Create a Bitmap object to display the texture region:
+
-- Create a Bitmap object to display the texture region
 
local bitmap = Bitmap.new(textureRegion)
 
local bitmap = Bitmap.new(textureRegion)
 +
</source>
 +
<br/>
 +
<br/>
  
 
TexturePack
 
TexturePack
TexturePack class is used to create and load texture atlases. You can either create your texture atlas dynamically in your game, or use a pre-packed texture atlas. After creation of your texture pack, you can get a region of it as a TextureRegion object by using getTextureRegion function. These features are described below in more detailed.
+
TexturePack class is used to create and load texture atlases. You can either create your texture atlas dynamically in your game or use a pre-packed texture atlas. After creating your texture pack, you can get a region of it as a TextureRegion object by using the getTextureRegion function. These features are described below in more detailed.
 +
<br/>
  
 
Dynamic Creation of Texture Packs
 
Dynamic Creation of Texture Packs
 
To create a texture pack dynamically (at run-time), create TexturePack object with an array of file names of textures:
 
To create a texture pack dynamically (at run-time), create TexturePack object with an array of file names of textures:
  
 +
<source lang="lua">
 
local texturePack = TexturePack.new({"1.png", "2.png", "3.png", "4.png"})
 
local texturePack = TexturePack.new({"1.png", "2.png", "3.png", "4.png"})
 +
</source>
 +
<br/>
  
 
Gideros loads and packs these images fast and gives you a ready to use texture pack. After creating your texture pack, you can display it easily since it's also a texture in itself:
 
Gideros loads and packs these images fast and gives you a ready to use texture pack. After creating your texture pack, you can display it easily since it's also a texture in itself:
  
 +
<source lang="lua">
 
local bitmap = Bitmap.new(texturePack)
 
local bitmap = Bitmap.new(texturePack)
 
stage:addChild(bitmap)
 
stage:addChild(bitmap)
 +
</source>
 +
<br/>
  
Note: Gideros uses an algorithm called MaxRects to pack the textures. This algorithm is considered as one of the best packing algorithms in terms of speed and efficiency. If you are interested, you can find the details of MaxRects algorithm at http://clb.demon.fi/projects/even-more-rectangle-bin-packing
+
'''Note:''' Gideros uses an algorithm called MaxRects to pack the textures. This algorithm is considered as one of the best packing algorithms in terms of speed and efficiency. If you are interested, you can find the details of MaxRects algorithm at http://clb.demon.fi/projects/even-more-rectangle-bin-packing
 +
<br/>
 +
<br/>
  
 
Pre-packed Texture Packs
 
Pre-packed Texture Packs
 
Another option to create texture packs is to use an external texture packer tool. Gideros Texture Packer, which comes with the installation package can be used to create texture packs.
 
Another option to create texture packs is to use an external texture packer tool. Gideros Texture Packer, which comes with the installation package can be used to create texture packs.
 +
<br/>
  
 
Getting Texture Regions from Texture Packs
 
Getting Texture Regions from Texture Packs
 
To get the texture region from the texture pack, you need to call the name of the image:
 
To get the texture region from the texture pack, you need to call the name of the image:
  
 +
<source lang="lua">
 
local texturePack = TexturePack.new({"1.png", "2.png", "3.png", "4.png"})
 
local texturePack = TexturePack.new({"1.png", "2.png", "3.png", "4.png"})
 
local textureRegion = texturePack:getTextureRegion(“1.png”)
 
local textureRegion = texturePack:getTextureRegion(“1.png”)
 +
</source>
 +
<br/>
  
 
== Automatic image resolution ==
 
== Automatic image resolution ==
  
With the introduction of iPad, iPhone 4 and variety of devices running Android, now there are multiple resolutions that game developers should consider. To efficiently use texture memory and processing power of GPUs, applications should use low-resolution images on devices with low-resolution screens and high-resolution images on devices with high-resolution screens. With the help of automatic image resolution, you can bundle both low and high resolution images together with your application and Gideros automatically selects the best resolution according to your scaling.
+
With the introduction of iPad, iPhone 4 and variety of devices running Android, now there are multiple resolutions that game developers should consider. To efficiently use texture memory and processing power of GPUs, applications should use low-resolution images on devices with low-resolution screens and high-resolution images on devices with high-resolution screens. With the help of automatic image resolution, you can bundle both low and high-resolution images together with your application and Gideros automatically selects the best resolution according to your scaling.
  
 
Automatic image resolution is directly related to automatic screen scaling. For example, if your screen is automatically scaled by 2, then using the double-sized image is the best choice in terms of quality and efficiency.
 
Automatic image resolution is directly related to automatic screen scaling. For example, if your screen is automatically scaled by 2, then using the double-sized image is the best choice in terms of quality and efficiency.
  
Open the project “Hardware/Automatic Image Resolution” to see automatic image resolution in action. Run this example multiple times by selecting different resolutions on Gideros Player such as 320x480, 640x960 and 480x800. As you can see, for the different resolutions, the original 200x200 image or high-resolution variants (300x300 and 400x400) are selected and displayed automatically.
+
Open the project “Hardware/Automatic Image Resolution” to see automatic image resolution in action. Run this example multiple times by selecting different resolutions on Gideros Player such as 320x480, 640x960, and 480x800. As you can see, for the different resolutions, the original 200x200 image or high-resolution variants (300x300 and 400x400) are selected and displayed automatically.
  
 
Now right click on the project name and select “Properties…” to understand how we configure automatic image resolution parameters:
 
Now right click on the project name and select “Properties…” to understand how we configure automatic image resolution parameters:
Line 139: Line 174:
 
and let Gideros pick the appropriate one.
 
and let Gideros pick the appropriate one.
  
Providing the alternative images (in our example, these are image@1.5x.png and image@2x.png) is optional but you should always provide the base image (image.png). When Gideros cannot find the alternative image to load, it loads the base image. Also size calculations are done according to the size of the base image.
+
Providing the alternative images (in our example, these are image@1.5x.png and image@2x.png) is optional but you should always provide the base image (image.png). When Gideros cannot find the alternative image to load, it loads the base image. Also, size calculations are done according to the size of the base image.
  
Design for High Resolution Devices
+
Design for High-Resolution Devices
 
In our example, we set the logical dimensions as 320x480 and provide higher-resolution alternative images. On the other hand, it’s possible to set logical dimensions as 480x960 or 768x1024 and provide lower-resolution images. In this case, we can configure the image scales like:
 
In our example, we set the logical dimensions as 320x480 and provide higher-resolution alternative images. On the other hand, it’s possible to set logical dimensions as 480x960 or 768x1024 and provide lower-resolution images. In this case, we can configure the image scales like:
  
Line 159: Line 194:
 
     • Destroy the old scene
 
     • Destroy the old scene
  
High level scene management functions aren’t provided directly in Gideros but the Gideros team has released a “Scene Manager” library (see: https://github.com/gideros/Scene-Manager) that can be used as is or that can be modified and extended to by the user (it’s licensed under the liberal MIT license). The library includes a demonstration Gideros project that shows many of the available built-in transitions (a demonstration video can be found on YouTube: http://www.youtube.com/watch?feature=player_embedded&v=KQMSHOMInQU).
+
High-level scene management functions aren’t provided directly in Gideros but the Gideros team has released a “Scene Manager” library (see: https://github.com/gideros/Scene-Manager) that can be used as is or that can be modified and extended to by the user (it’s licensed under the liberal MIT license). The library includes a demonstration Gideros project that shows many of the available built-in transitions (a demonstration video can be found on YouTube: http://www.youtube.com/watch?feature=player_embedded&v=KQMSHOMInQU).
  
 
=== Scene Manager Usage Overview ===
 
=== Scene Manager Usage Overview ===
Line 165: Line 200:
 
Create a class for each of your scenes (and preferably put them into separate .lua files):
 
Create a class for each of your scenes (and preferably put them into separate .lua files):
  
 +
<source lang="lua">
 
Menu = gideros.class(Sprite)
 
Menu = gideros.class(Sprite)
 
Game = gideros.class(Sprite)
 
Game = gideros.class(Sprite)
 
EndScene = gideros.class(Sprite)
 
EndScene = gideros.class(Sprite)
 +
</source>
 +
<br/>
  
 
Create a SceneManager instance passing a table with scene names as keys and your classes as values:
 
Create a SceneManager instance passing a table with scene names as keys and your classes as values:
  
 +
<source lang="lua">
 
local sceneManager = SceneManager.new{
 
local sceneManager = SceneManager.new{
 
   ["menu"] = Menu,
 
   ["menu"] = Menu,
Line 177: Line 216:
 
}
 
}
 
stage:addChild(sceneManager)
 
stage:addChild(sceneManager)
 +
</source>
 +
<br/>
  
 
Change between scenes with function SceneManager:changeScene(sceneName, duration, transition, ease) like so:
 
Change between scenes with function SceneManager:changeScene(sceneName, duration, transition, ease) like so:
  
 +
<source lang="lua">
 
sceneManager:changeScene("game", 1, SceneManager.moveFromTop, easing.linear)
 
sceneManager:changeScene("game", 1, SceneManager.moveFromTop, easing.linear)
 +
</source>
 +
<br/>
  
 
SceneManager dispatches events as it makes the transition from one scene to another (enterBegin, enterEnd, exitBegin, exitEnd). You can register to listen for these events to begin/end your animations, clear your timers, save the scores, etc.
 
SceneManager dispatches events as it makes the transition from one scene to another (enterBegin, enterEnd, exitBegin, exitEnd). You can register to listen for these events to begin/end your animations, clear your timers, save the scores, etc.
Line 220: Line 264:
 
     • enterEnd: Dispatched after the transition of new scene has ended. Mostly, you add listeners and start your logic here.
 
     • enterEnd: Dispatched after the transition of new scene has ended. Mostly, you add listeners and start your logic here.
 
     • exitBegin: Dispatched when your current scene is about to leave the stage. You can remove listeners and stop timers here.
 
     • exitBegin: Dispatched when your current scene is about to leave the stage. You can remove listeners and stop timers here.
     • exitEnd: Dispatched after your current scene has leaved the stage.
+
     • exitEnd: Dispatched after your current scene has left the stage.
  
 
The following code shows an example scene (“MyScene”) and how you might use the “enterEnd” and “exitBegin” events:
 
The following code shows an example scene (“MyScene”) and how you might use the “enterEnd” and “exitBegin” events:
  
 +
<source lang="lua">
 
MyScene = gideros.class(Sprite)
 
MyScene = gideros.class(Sprite)
 
function MyScene:init()
 
function MyScene:init()
Line 241: Line 286:
 
-- and so on...
 
-- and so on...
 
end
 
end
 +
</source>
 +
<br/>
  
 
== Fonts ==
 
== Fonts ==
Line 248: Line 295:
 
=== Bitmap Fonts ===
 
=== Bitmap Fonts ===
  
A bitmap font consists of an image that contains all the glyphs, and a descriptor file that defines the properties and bounds for each glyph. Also font descriptor file can contain kerning information between each glyph.
+
A bitmap font consists of an image that contains all the glyphs, and a descriptor file that defines the properties and bounds for each glyph. Also, font descriptor file can contain kerning information between each glyph.
  
 
You need to use an external tool to create bitmap fonts. Gideros Font Creator is one of these and you can find it inside the installation directory. Other noticeable bitmap font creator tools are:
 
You need to use an external tool to create bitmap fonts. Gideros Font Creator is one of these and you can find it inside the installation directory. Other noticeable bitmap font creator tools are:
Line 255: Line 302:
 
     • Hiero (java, multiplatform) http://slick.cokeandcode.com/demos/hiero.jnlp
 
     • Hiero (java, multiplatform) http://slick.cokeandcode.com/demos/hiero.jnlp
  
Usually these tools have the ability to enhance the bitmap fonts with effects like shadows, outlines, color gradients, and so on. And this is the main advantage of using bitmap fonts.
+
Usually, these tools have the ability to enhance the bitmap fonts with effects like shadows, outlines, color gradients, and so on. And this is the main advantage of using bitmap fonts.
  
 
After creating the image (e.g. font.png) and the descriptor file (e.g. font.txt or font.fnt), you can load the bitmap font as:
 
After creating the image (e.g. font.png) and the descriptor file (e.g. font.txt or font.fnt), you can load the bitmap font as:
  
 +
<source lang="lua">
 
local font = Font.new("font.txt", "font.png")
 
local font = Font.new("font.txt", "font.png")
 +
</source>
 +
<br/>
  
 
And after loading the font, you can create a TextField object to display a text as:
 
And after loading the font, you can create a TextField object to display a text as:
  
 +
<source lang="lua">
 
local textField = TextField.new(font, "Hello World")
 
local textField = TextField.new(font, "Hello World")
 +
</source>
 +
<br/>
  
 
Finally you can add the resulting TextField object to the stage to display it:
 
Finally you can add the resulting TextField object to the stage to display it:
  
 +
<source lang="lua">
 
stage:addChild(textField)
 
stage:addChild(textField)
 +
</source>
 +
<br/>
  
 
TextField inherits from Sprite class and therefore has all the properties and capabilities of sprites.
 
TextField inherits from Sprite class and therefore has all the properties and capabilities of sprites.
  
Bitmap fonts are fast to render and fast to update. But they should be created with a tool beforehand and therefore the size of any bitmap font is fixed. Also you need to create separate bitmap fonts for each automatic image resolution suffix (e.g. @2x, @4x, etc.) and Gideros automatically selects the best bitmap font according to your scaling.
+
Bitmap fonts are fast to render and fast to update. But they should be created with a tool beforehand and therefore the size of any bitmap font is fixed. Also, you need to create separate bitmap fonts for each automatic image resolution suffix (e.g. @2x, @4x, etc.) and Gideros automatically selects the best bitmap font according to your scaling.
  
 
=== TrueType Fonts ===
 
=== TrueType Fonts ===
Line 277: Line 333:
 
Gideros also can load and use .ttf (TrueType font) files directly:
 
Gideros also can load and use .ttf (TrueType font) files directly:
  
 +
<source lang="lua">
 
local font = TTFont.new("arial.ttf", 20)
 
local font = TTFont.new("arial.ttf", 20)
 
local text = TextField.new(font, "Hello world")
 
local text = TextField.new(font, "Hello world")
 
text:setPosition(10, 20)
 
text:setPosition(10, 20)
 
stage:addChild(text)
 
stage:addChild(text)
 +
</source>
 +
<br/>
  
Here the first parameter is the font file and second parameter is the size.
+
Here the first parameter is the font file and the second parameter is the size.
  
 
While using TrueType fonts, whenever a text is created or updated, the given TrueType font is used to render the text on a texture and unfortunately it's a slow operation. This is the main disadvantage of using TrueType fonts but can be avoided with caching.
 
While using TrueType fonts, whenever a text is created or updated, the given TrueType font is used to render the text on a texture and unfortunately it's a slow operation. This is the main disadvantage of using TrueType fonts but can be avoided with caching.
Line 290: Line 349:
 
It's possible to cache TrueType font glyphs into an internal texture so that updating the font text won't be a slow operation anymore. For example, to create a TrueType font with all uppercase characters:
 
It's possible to cache TrueType font glyphs into an internal texture so that updating the font text won't be a slow operation anymore. For example, to create a TrueType font with all uppercase characters:
  
 +
<source lang="lua">
 
local font = TTFont.new("arial.ttf", 20, " ABCDEFGHIJKLMNOPQRSTUVWXYZ")
 
local font = TTFont.new("arial.ttf", 20, " ABCDEFGHIJKLMNOPQRSTUVWXYZ")
 
local text = TextField.new(font, "HELLO WORLD")
 
local text = TextField.new(font, "HELLO WORLD")
 
text:setPosition(10, 20)
 
text:setPosition(10, 20)
 
stage:addChild(text)
 
stage:addChild(text)
 +
</source>
 +
<br/>
  
Note: If a character is not found in the internal cache, it simply won't be displayed.
+
'''Note:''' If a character is not found in the internal cache, it simply won't be displayed.
  
 
=== Font Metrics ===
 
=== Font Metrics ===
  
In Gideros, TextField object are positioned according to their baselines:
+
In Gideros, TextField objects are positioned according to their baselines:
  
 
[[File:Typography Line Terms.png]]
 
[[File:Typography Line Terms.png]]
Line 305: Line 367:
 
The ascender of a font is the distance from the baseline to the highest position characters extend to. So that...
 
The ascender of a font is the distance from the baseline to the highest position characters extend to. So that...
  
 +
<source lang="lua">
 
local font = TTFont.new("arial.ttf", 20)
 
local font = TTFont.new("arial.ttf", 20)
 
local text = TextField.new(font, "Hello world")
 
local text = TextField.new(font, "Hello world")
 
text:setY(font:getAscender())
 
text:setY(font:getAscender())
 
stage:addChild(text)
 
stage:addChild(text)
 
+
</source>
Talk about kerning, advance and widths.. TO BE WRITTEN MORE.…
+
<br/>
  
 
== Drawing sprites ==
 
== Drawing sprites ==
Line 342: Line 405:
 
Shape:clear()
 
Shape:clear()
 
Clears all paths from the shape and resets the line and fill style to default values
 
Clears all paths from the shape and resets the line and fill style to default values
 +
<br/>
  
 
One question that you might ask is: Why draw using this primitive API? Wouldn’t it be better to use some other authoring environment (e.g., Photoshop, Inkscape, Gimp) then import the image as a Texture? In most cases, an authoring tool is the right answer. However, there are cases where you don’t know which graphics you need in advance:
 
One question that you might ask is: Why draw using this primitive API? Wouldn’t it be better to use some other authoring environment (e.g., Photoshop, Inkscape, Gimp) then import the image as a Texture? In most cases, an authoring tool is the right answer. However, there are cases where you don’t know which graphics you need in advance:
Line 351: Line 415:
 
=== Gideros coordinate system ===
 
=== Gideros coordinate system ===
  
The standard cartesian coordinate system that everyone learns in school consists of an origin point (x=0, y=0) which is often drawn in center of the page. The x values increase to the right horizontally and the y values increase upwards vertically (i.e., x values are negative to the left of the origin and positive to the right, y values are negative below the origin and positive above).   
+
The standard cartesian coordinate system that everyone learns in school consists of an origin point (x=0, y=0) which is often drawn in the center of a page. The x values increase to the right horizontally and the y values increase upwards vertically (i.e., x values are negative to the left of the origin and positive to the right, y values are negative below the origin and positive above).   
  
The origin point in the Gideros coordinate system is in the upper left hand corner of the screen with an inverted y axis (i.e., y values are negative above the origin and positive below the origin).  The x axis is the same as the standard cartesian coordinate system. {ISSUE:  Current origin point in gideros desktop player seems to be 1,0 -- posted issue on gideros forum}
+
The origin point in the Gideros coordinate system is in the upper left-hand corner of the screen with an inverted y-axis (i.e., y values are negative above the origin and positive below the origin).  The x-axis is the same as the standard cartesian coordinate system. {ISSUE:  Current origin point in gideros desktop player seems to be 1,0 -- posted issue on gideros forum}
  
 
[[File:Coordinate system.png]]
 
[[File:Coordinate system.png]]
Line 361: Line 425:
 
Shapes consist of zero or more paths.  Each path consists of zero or more lines with the same line and fill style. A different line or fill style can be used for each path. Since a path can only have one line style and one fill style, multiple paths are required to use different line / fill styles within a shape.
 
Shapes consist of zero or more paths.  Each path consists of zero or more lines with the same line and fill style. A different line or fill style can be used for each path. Since a path can only have one line style and one fill style, multiple paths are required to use different line / fill styles within a shape.
  
A pen & paper analogy is often used to describe the drawing approach used by the Shape class.  The “moveTo” function can be thought of as lifting a pen off the paper and moving it to a different location without drawing anything. The “lineTo” function draws a straight line from current pen location to the destination location.
+
A pen & paper analogy is often used to describe the drawing approach used by the Shape class.  The “moveTo” function can be thought of as lifting a pen off the paper and moving it to a different location without drawing anything. The “lineTo” function draws a straight line from the current pen location to the destination location.
  
 
The overall flow for drawing with the Shape class is:
 
The overall flow for drawing with the Shape class is:
Line 381: Line 445:
 
Here’s the code to draw the line:
 
Here’s the code to draw the line:
  
 +
<source lang="lua">
 
local shape = Shape.new() -- create the shape
 
local shape = Shape.new() -- create the shape
 
shape:beginPath()        -- begin a path
 
shape:beginPath()        -- begin a path
Line 388: Line 453:
 
shape:endPath()          -- end the path  
 
shape:endPath()          -- end the path  
 
stage:addChild(shape)    -- add the shape to the stage
 
stage:addChild(shape)    -- add the shape to the stage
 +
</source>
 +
<br/>
  
 
Some things to note about this code:
 
Some things to note about this code:
    • Without the call to setLineStyle, the line wouldn’t have been visible --  in this example, we used setLineStyle to set the line width to 1 (the default line color is black)
+
* Without the call to setLineStyle, the line wouldn’t have been visible --  in this example, we used setLineStyle to set the line width to 1 (the default line color is black)
    • We added the shape directly to the stage.  For this example that’s good enough.  For more complicated examples you’d probably use some other parent.  Most of the the following Shape examples will use this same approach.
+
* We added the shape directly to the stage.  For this example that’s good enough.  For more complicated examples you’d probably use some other parent.  Most of the following Shape examples will use this same approach.
  
 
=== Drawing a Rectangle ===
 
=== Drawing a Rectangle ===
  
Lines aren’t very interesting so lets expand our line into a filled rectangle.   We’ll draw the rectangle with the upper left corner at 100,100 and the lower right corner at 200,200. We’ll also fill the rectangle with a boring gray color.
+
Lines aren’t very interesting so let's expand our line into a filled rectangle. We’ll draw the rectangle with the upper left corner at 100,100 and the lower right corner at 200,200. We’ll also fill the rectangle with a boring gray color.
  
 +
<source lang="lua">
 
local shape = Shape.new() -- create the shape
 
local shape = Shape.new() -- create the shape
 
shape:beginPath()        -- begin a path
 
shape:beginPath()        -- begin a path
Line 408: Line 476:
 
shape:endPath()          -- end the path
 
shape:endPath()          -- end the path
 
stage:addChild(shape)    -- add the shape to the stage
 
stage:addChild(shape)    -- add the shape to the stage
 +
</source>
 +
<br/>
  
 
This example is similar to our line drawing program plus 1 additional line to fill the rectangle and 3 additional new lines to draw the right, bottom, and left sides of the rectangle.
 
This example is similar to our line drawing program plus 1 additional line to fill the rectangle and 3 additional new lines to draw the right, bottom, and left sides of the rectangle.
Line 416: Line 486:
  
 
There are a couple of simplifications that we can make to the rectangle code:
 
There are a couple of simplifications that we can make to the rectangle code:
    • The call to moveTo can be replaced with lineTo.  The first call to lineTo after a path is started behaves just like a moveTo.
+
* The call to moveTo can be replaced with lineTo.  The first call to lineTo after a path is started behaves just like a moveTo.
    • Although a rectangle has 4 points, we have to use a total of 5 moveTo/lineTo calls -- the first point has to be used twice (once for the initial moveTo command and once to close the rectangle with the final lineTo call).    The last lineTo command can be replaced with a call to closePath.   The closePath function is equivalent to a lineTo command to the first point in a path.
+
* Although a rectangle has 4 points, we have to use a total of 5 moveTo/lineTo calls -- the first point has to be used twice (once for the initial moveTo command and once to close the rectangle with the final lineTo call).    The last lineTo command can be replaced with a call to closePath. The closePath function is equivalent to a lineTo command to the first point in a path.
  
 
The following code uses these two simplifications to draw a polygon from an arbitrary list of data points:
 
The following code uses these two simplifications to draw a polygon from an arbitrary list of data points:
  
 +
<source lang="lua">
 
local path = { {100,100}, {200,100}, {200,200}, {100,200} }
 
local path = { {100,100}, {200,100}, {200,200}, {100,200} }
 
local shape = Shape.new()
 
local shape = Shape.new()
Line 431: Line 502:
 
shape:closePath()  -- connect last point to first point
 
shape:closePath()  -- connect last point to first point
 
shape:endPath()
 
shape:endPath()
 +
</source>
 +
<br/>
  
 
We can change path to have as many points as we’d like and the above code will draw the closed polygon.
 
We can change path to have as many points as we’d like and the above code will draw the closed polygon.
Line 438: Line 511:
 
=== Shape Anchor Point ===
 
=== Shape Anchor Point ===
  
Shapes are “anchored” at the graph origin (0,0).   The anchor point affects how the shape behaves when manipulated (e.g., rotated, scaled).   If we were to rotate the rectangle we drew earlier by 90%, the rectangle would have rotated off the screen since it would have rotated about the (0,0) origin point.   The anchor point also affects scaling -- scaling the rectangle would have moved it further away from (0,0) if it was enlarged and closer to (0,0) if we’d scaled it down.
+
Shapes are “anchored” at the graph origin (0,0). The anchor point affects how the shape behaves when manipulated (e.g., rotated, scaled). If we were to rotate the rectangle we drew earlier by 90°, the rectangle would have rotated off the screen since it would have rotated about the (0,0) origin point. The anchor point also affects scaling -- scaling the rectangle would have moved it further away from (0,0) if it was enlarged and closer to (0,0) if we’d scaled it down.
 +
<br/>
  
Let’s change our previous polygon example so that the rectangle will stay centered at it’s center point (150,150) if rotated or scaled.   The code will need the following two changes:
+
Let’s change our previous polygon example so that the rectangle will stay centered at its center point (150,150) if rotated or scaled. The code will need the following two changes:
 
     • Draw the rectangle centered at (0,0)
 
     • Draw the rectangle centered at (0,0)
 
     • Use the inherited Sprite:setPosition function to position the shape
 
     • Use the inherited Sprite:setPosition function to position the shape
Line 446: Line 520:
 
We can make these changes by modifying the first few lines of our previous example like so:
 
We can make these changes by modifying the first few lines of our previous example like so:
  
 +
<source lang="lua">
 
local path = { {-50,-50}, {50,-50}, {50,50}, {-50,50} }
 
local path = { {-50,-50}, {50,-50}, {50,50}, {-50,50} }
 
-- CHANGED: points are now centered around (0,0)
 
-- CHANGED: points are now centered around (0,0)
Line 451: Line 526:
 
shape:setPosition(150,150) -- NEW
 
shape:setPosition(150,150) -- NEW
 
-- ... the rest of the original example would go here …
 
-- ... the rest of the original example would go here …
 +
</source>
 +
<br/>
  
 
=== Line and Fill Styles ===
 
=== Line and Fill Styles ===
Line 457: Line 534:
  
 
The setLineStyle function has one required parameter (width) and two optional parameters (color, alpha):
 
The setLineStyle function has one required parameter (width) and two optional parameters (color, alpha):
width: width of the line (integer number > 0)
+
'''width''': width of the line (integer number > 0)
color: color value of the line (optional hexadecimal number, default = 0x000000)
+
'''color''': color value of the line (optional hexadecimal number, default = 0x000000)
 
(red is 0xFF0000, blue=0x0000FF, green=0x00FF00, white=0xFFFFFF, etc.)
 
(red is 0xFF0000, blue=0x0000FF, green=0x00FF00, white=0xFFFFFF, etc.)
alpha: alpha value for the line (optional floating point value between 0 & 1, default=1) (0 = invisible, 0.5 = 50% transparency, 1 = no transparency)
+
'''alpha''': alpha value for the line (optional floating point value between 0 & 1, default=1) (0 = invisible, 0.5 = 50% transparency, 1 = no transparency)
 +
<br/>
 +
 
 
The following figure shows some example line styles.
 
The following figure shows some example line styles.
  
Line 466: Line 545:
  
 
Shapes can be unfilled, filled with a color, or filled with a texture. The first argument to setFillStyle determines the fill type:
 
Shapes can be unfilled, filled with a color, or filled with a texture. The first argument to setFillStyle determines the fill type:
    • Shape.NONE: Clears the fill style (i.e., the shape will not be filled)
+
* Shape.NONE: Clears the fill style (i.e., the shape will not be filled)
    • Shape.SOLID: Sets the fill style as a solid color. In this mode,  an additional color parameter is required (hexadecimal number).  An optional alpha value (floating point number between 0 and 1) can also be specified.
+
* Shape.SOLID: Sets the fill style as a solid color. In this mode,  an additional color parameter is required (hexadecimal number).  An optional alpha value (floating point number between 0 and 1) can also be specified.
    • Shape.TEXTURE: Fills the shape with a tiled texture. In this mode, and additional texture argument is required.  An optional transformation matrix can also be specified.
+
* Shape.TEXTURE: Fills the shape with a tiled texture. In this mode, and additional texture argument is required.  An optional transformation matrix can also be specified.
  
 
The following illustrates how to use the three different types of fill styles:
 
The following illustrates how to use the three different types of fill styles:
  
 +
<source lang="lua">
 
setFillStyle(Shape.NONE) -- unfilled
 
setFillStyle(Shape.NONE) -- unfilled
 
setFillStyle(Shape.SOLID, 0xff0000) -- fill solid red color
 
setFillStyle(Shape.SOLID, 0xff0000) -- fill solid red color
setFillStyle(Shape.SOLID, 0xff0000, 0.5) -- fill with red,
+
setFillStyle(Shape.SOLID, 0xff0000, 0.5) -- fill with red, 50% transparency
-- 50% transparency
 
 
local texture = Texture.new("image.png")
 
local texture = Texture.new("image.png")
 
setFillStyle(Shape.TEXTURE, texture) -- fill with texture
 
setFillStyle(Shape.TEXTURE, texture) -- fill with texture
 
local matrix = Matrix.new(0.5, 0, 0, 0.5, 0, 0)
 
local matrix = Matrix.new(0.5, 0, 0, 0.5, 0, 0)
setFillStyle(Shape.TEXTURE,texture,matrix -- fill with texture
+
setFillStyle(Shape.TEXTURE,texture,matrix) -- fill with texture scale x/y by 0.5
-- scale x/y by 0.5
+
</source>
 +
<br/>
  
 
There are two important things to note when using texture fills:
 
There are two important things to note when using texture fills:
    • If the shape of the texture is smaller than the area to be filled, Gideros will tile the textures in both the x and y dimensions.   The tiles will be aligned so that the upper left hand corner of the texture will be at x=0, y=0 (unless this is modified by a transformation matrix).
+
* If the shape of the texture is smaller than the area to be filled, Gideros will tile the textures in both the x and y dimensions. The tiles will be aligned so that the upper left-hand corner of the texture will be at x=0, y=0 (unless this is modified by a transformation matrix).
    • If the width and/or height of the texture is not a power of 2, Gideros will increase the width/height to be a power of 2 with the expanded area left unfilled or transparent.   For example, a 33 (width) x 123 (height) texture will be re-sized to 64 x 128 then tiled as necessary.
+
* If the width and/or height of the texture is not a power of 2, Gideros will increase the width/height to be a power of 2 with the expanded area left unfilled or transparent. For example, a 33 (width) x 123 (height) texture will be re-sized to 64 x 128 then tiled as necessary.
 +
<br/>
  
 
As stated previously, all lines and drawn shapes within a path share the same line and fill style. Gideros applies the most recently specified line and fill style when each path is ended (i.e., with endPath). The following code draws a shape with 2 paths. Everything drawn between a beginPath/endPath pair will have the same line and fill style. In other words, even though setLineStyle is called 5 different times, only 2 of them have an effect because there are only 2 paths.
 
As stated previously, all lines and drawn shapes within a path share the same line and fill style. Gideros applies the most recently specified line and fill style when each path is ended (i.e., with endPath). The following code draws a shape with 2 paths. Everything drawn between a beginPath/endPath pair will have the same line and fill style. In other words, even though setLineStyle is called 5 different times, only 2 of them have an effect because there are only 2 paths.
  
 +
<source lang="lua">
 
local s = Shape.new()
 
local s = Shape.new()
 
s:setLineStyle(1)
 
s:setLineStyle(1)
 
s:beginPath()
 
s:beginPath()
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 1
+
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 1
 
s:endPath()
 
s:endPath()
 
-- All lines in the following path will have a width of 3.
 
-- All lines in the following path will have a width of 3.
Line 497: Line 579:
 
s:beginPath()
 
s:beginPath()
 
s:setLineStyle(1)
 
s:setLineStyle(1)
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 3
+
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 3
 
s:setLineStyle(2)
 
s:setLineStyle(2)
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 3
+
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 3
 
s:setLineStyle(3)
 
s:setLineStyle(3)
 
s:endPath()
 
s:endPath()
 
s:setLineStyle(4)
 
s:setLineStyle(4)
 +
</source>
 +
<br/>
  
 
=== Winding Rules ===
 
=== Winding Rules ===
  
 
Simple paths such as circles, triangles, rectangles have a well-defined and obvious “fill area.”. For complex paths (e.g., concentric circles,  shapes with intersecting lines), multiple choices can be made as to whether fill an area. The Shape:beginPath function takes an optional winding argument that determines how the path will be filled (the default value for the argument is Shape.EVEN_ODD). To determine whether an area should be filled, the following two rules are available within Gideros:
 
Simple paths such as circles, triangles, rectangles have a well-defined and obvious “fill area.”. For complex paths (e.g., concentric circles,  shapes with intersecting lines), multiple choices can be made as to whether fill an area. The Shape:beginPath function takes an optional winding argument that determines how the path will be filled (the default value for the argument is Shape.EVEN_ODD). To determine whether an area should be filled, the following two rules are available within Gideros:
    • Even odd rule (Shape.EVEN_ODD)
+
* Even-odd rule (Shape.EVEN_ODD)
    1. Draw a line with a start point in the area to be filled extending to infinity in any direction
+
# Draw a line with a start point in the area to be filled extending to infinity in any direction
    2. If the line crosses an even number of lines, the area should NOT be filled
+
# If the line crosses an even number of lines, the area should NOT be filled
    3. If the line crosses an odd number of lines, the area should be filled
+
# If the line crosses an odd number of lines, the area should be filled
    • Non-zero rule (Shape.NON_ZERO) - this rule depends on the drawing direction (or winding) of the edges of the path. When paths of opposite drawing direction intersect, the area will be unfilled.  
+
* Non-zero rule (Shape.NON_ZERO) - this rule depends on the drawing direction (or winding) of the edges of the path. When paths of opposite drawing direction intersect, the area will be unfilled.
    1. Draw a line with a start point in the area to be filled extending to infinity in any direction
+
# Draw a line with a start point in the area to be filled extending to infinity in any direction
    2. Start with a count of 0
+
# Start with a count of 0
    3. Every time the line crosses a line of the polygon drawn in one direction add one to the count.   Subtract one from the count for every edge drawn in the opposite dimension.
+
# Every time the line crosses a line of the polygon drawn in one direction add one to the count. Subtract one from the count for every edge drawn in the opposite dimension.
    4. If the ending count is zero, the area should NOT be filled.   If the ending count is zero, the area should be filled.
+
# If the ending count is zero, the area should NOT be filled. If the ending count is zero, the area should be filled.
 +
<br/>
  
For example, consider a simple path such as a rectangle: a line started from “outside” the rectangle will either cross 0 edges or 2 edges. Since the line crosses an even number of lines, the area should not be filled using the even odd rule. The area wouldn’t be filled with the non-zero rule either: the line would either cross 0 edges (count would be 0) or two edges -- one edge drawn in one direction, the other edge drawn in the opposite direction (the count would be 0). If a line is started inside the rectangle, the line will intersect one edge (an odd count for the even odd rule, and a count of either -1 or 1 for the non zero rule).
+
For example, consider a simple path such as a rectangle: a line started from “outside” the rectangle will either cross 0 edges or 2 edges. Since the line crosses an even number of lines, the area should not be filled using the even-odd rule. The area wouldn’t be filled with the non-zero rule either: the line would either cross 0 edges (count would be 0) or two edges -- one edge drawn in one direction, the other edge drawn in the opposite direction (the count would be 0). If a line is started inside the rectangle, the line will intersect one edge (an odd count for the even-odd rule, and a count of either -1 or 1 for the non zero rule).
 +
<br/>
  
In can be confusing to decide whether an area should be filled but thankfully examples of different winding rules are easy to find since these two winding rules are used in flash, svg, postscript, and many other drawing technologies.
+
In can be confusing to decide whether an area should be filled but thankfully examples of different winding rules are easy to find since these two winding rules are used in flash, SVG, postscript, and many other drawing technologies.
  
 
== Using tilemaps ==
 
== Using tilemaps ==
Line 527: Line 613:
 
== Timelined animations with Movieclip ==
 
== Timelined animations with Movieclip ==
  
Note: Currently there’s no way to set the animation speed, however you can prepare different animations for different speeds like:
+
'''Note:''' Currently there’s no way to set the animation speed, however you can prepare different animations for different speeds like:
  
 +
<source lang="lua">
 
local eliAnim = MovieClip.new{
 
local eliAnim = MovieClip.new{
 
{1, 7, self.anim[1]},
 
{1, 7, self.anim[1]},
Line 539: Line 626:
 
eliAnim:gotoAndPlay(1) --> play slow animation
 
eliAnim:gotoAndPlay(1) --> play slow animation
 
eliAnim:gotoAndPlay(16) --> play fast animation
 
eliAnim:gotoAndPlay(16) --> play fast animation
 +
</source>
 +
<br/>
  
 
This way, you can position your MovieClip wherever you want (e.g. running player, walking player, flying helicopter, etc).
 
This way, you can position your MovieClip wherever you want (e.g. running player, walking player, flying helicopter, etc).

Revision as of 03:31, 15 June 2019

The Ultimate Guide to Gideros Studio

Graphics and animation

Introduction to graphics

In Gideros Studio, all graphical content is constructed by creating graphical instances and adding them to the scene tree. For example, the Bitmap class is used to display bitmap graphics, and TextField is used to display text object. Sprite class is used to group and transform these instances.

Sprite is the base class of all display classes and has no visual representation. It’s the main construction element and used to create display hierarchies. Sprites can have other sprites as their children and these children inherit properties such as position, scaling, and transparency from their parent sprite. Hierarchy is an important feature in graphics - when you move your parent sprite, all the child (and grandchild) sprites also move simultaneously. Here are some examples:

- Create a new Sprite instance:
local childSprite = Sprite.new()
- Add the new Sprite as a child:
mySprite:addChild(childSprite)
- Set the position:
mySprite:setPosition(10, 20)
- Set the scaling:
mySprite:setScale(0.5, 1)
- Set the rotation angle:
mySprite:setRotation(90)
- Set the alpha transparency:
mySprite:setAlpha(0.7)
- Get the first child:
mySprite:getChildAt(1)
- Remove the child:
mySprite:removeChild(childSprite)


Sprites can have only one parent. Therefore if you add a child object that already has a different sprite as a parent, the sprite is removed from the child list of the other sprite and then added to this sprite.

Stage

The scene tree is the hierarchy of all graphical objects currently displayed and this hierarchy needs a root. The root of the scene tree is an instance of the Stage class which is automatically created and set as a global variable when Gideros starts. You can access this global variable with the name stage.

When Gideros starts, the display list hierarchy contains one item (the global Stage instance) only and we can add more:

- Create a Sprite object
local mySprite = Sprite.new()
- Add this sprite to the stage:
stage:addChild(mySprite)


Textures and Bitmaps

Texture class is used to load an image file and holds the data stored in the image file. The Bitmap class inherits from Sprite and wraps a Texture object for on-screen display. By separating image display (Bitmap) from data (Texture), it’s possible to create many Bitmap objects simultaneously display the same Texture object each with its own display characteristics.

- Load a texture:
local texture = Texture.new("image.png")
- Load a texture with filtering:
local texture = Texture.new("image.png", true)
- Create a Bitmap object to display the texture
local bitmap = Bitmap.new(texture)
- Add the Bitmap object to the stage
stage:addChild(bitmap)


Anchor Point

Each Bitmap object has an anchor point that affects the positioning of the texture displayed. By modifying the anchor point, you change the origin of the texture. For example, setting the anchor point to (0.5, 0.5) moves the center of the texture to the origin. If you set the anchor point to (1, 1) instead, the bottom-right corner of the texture will be the origin. The default value of anchor point is (0, 0) which means top-left of the texture is the origin by default.

Anchorpoint.png
setAnchorPoint(0,0), setAnchorPoint(0.5, 0.5), setAnchorPoint(1,1)

In this example, we set the anchor point to (0.5, 0.5):

local bitmap = Bitmap.new(Texture.new("image.png"))
bitmap:setAnchorPoint(0.5, 0.5)


What is a Texture Atlas?

Texture atlas is a single large image which contains many smaller sub-images. Each sub-image contained in the texture atlas is defined with a rectangular area.

There are two main reasons for using a texture atlas instead of many independent textures:
1. Texture atlases usually consume less memory. To use an image as a texture, its width and height always have to be a power of two. Gideros automatically increase the texture size to conform to this rule. For example, an image with dimensions of 100x200 becomes a texture with dimensions of 128x256 in memory. That’s where the texture atlas becomes handy. Texture atlases usually have power-of-two dimensions and therefore don’t need to be enlarged.

2. Rendering from a texture atlas is usually faster. Each texture is bound to OpenGL just before rendering it and binding is a costly operation. Using texture atlases instead of individual textures helps in reducing bind operations

TextureRegion The TextureRegion class specifies a texture and a rectangular region in it. It is used to define independent texture regions within a texture atlas which is a large image, which contains many smaller sub-images. Displaying the texture regions is same with displaying textures: Create a Bitmap object that wraps the TextureRegion object:

-- Load a texture
local texture = Texture.new("image.png")
-- Create a texture region as top left point is (30, 40) with dimensions (100, 50)
local textureRegion = TextureRegion.new(texture, 30, 40, 100, 50)
-- Create a Bitmap object to display the texture region
local bitmap = Bitmap.new(textureRegion)



TexturePack TexturePack class is used to create and load texture atlases. You can either create your texture atlas dynamically in your game or use a pre-packed texture atlas. After creating your texture pack, you can get a region of it as a TextureRegion object by using the getTextureRegion function. These features are described below in more detailed.

Dynamic Creation of Texture Packs To create a texture pack dynamically (at run-time), create TexturePack object with an array of file names of textures:

local texturePack = TexturePack.new({"1.png", "2.png", "3.png", "4.png"})


Gideros loads and packs these images fast and gives you a ready to use texture pack. After creating your texture pack, you can display it easily since it's also a texture in itself:

local bitmap = Bitmap.new(texturePack)
stage:addChild(bitmap)


Note: Gideros uses an algorithm called MaxRects to pack the textures. This algorithm is considered as one of the best packing algorithms in terms of speed and efficiency. If you are interested, you can find the details of MaxRects algorithm at http://clb.demon.fi/projects/even-more-rectangle-bin-packing

Pre-packed Texture Packs Another option to create texture packs is to use an external texture packer tool. Gideros Texture Packer, which comes with the installation package can be used to create texture packs.

Getting Texture Regions from Texture Packs To get the texture region from the texture pack, you need to call the name of the image:

local texturePack = TexturePack.new({"1.png", "2.png", "3.png", "4.png"})
local textureRegion = texturePack:getTextureRegion(1.png)


Automatic image resolution

With the introduction of iPad, iPhone 4 and variety of devices running Android, now there are multiple resolutions that game developers should consider. To efficiently use texture memory and processing power of GPUs, applications should use low-resolution images on devices with low-resolution screens and high-resolution images on devices with high-resolution screens. With the help of automatic image resolution, you can bundle both low and high-resolution images together with your application and Gideros automatically selects the best resolution according to your scaling.

Automatic image resolution is directly related to automatic screen scaling. For example, if your screen is automatically scaled by 2, then using the double-sized image is the best choice in terms of quality and efficiency.

Open the project “Hardware/Automatic Image Resolution” to see automatic image resolution in action. Run this example multiple times by selecting different resolutions on Gideros Player such as 320x480, 640x960, and 480x800. As you can see, for the different resolutions, the original 200x200 image or high-resolution variants (300x300 and 400x400) are selected and displayed automatically.

Now right click on the project name and select “Properties…” to understand how we configure automatic image resolution parameters:

Project properties.png

In this example, our logical dimensions are 320x480 and scale mode is Letterbox. So the scaling factor for a device with screen resolution 320x480 (older iPhones) is 1, scaling factor for 480x960 (iPhone 4) is 2 and scaling factor for 480x800 (Samsung Galaxy S) is around 1.5. As you can see, we’ve configured image scales as:

Image scales.png

So if you have a base image with resolution 200x200 with name “image.png”, you provide these 3 images:

   • image.png (200x200)
   • image@1.5x.png (300x300)
   • image@2x.png (400x400)

and let Gideros pick the appropriate one.

Providing the alternative images (in our example, these are image@1.5x.png and image@2x.png) is optional but you should always provide the base image (image.png). When Gideros cannot find the alternative image to load, it loads the base image. Also, size calculations are done according to the size of the base image.

Design for High-Resolution Devices In our example, we set the logical dimensions as 320x480 and provide higher-resolution alternative images. On the other hand, it’s possible to set logical dimensions as 480x960 or 768x1024 and provide lower-resolution images. In this case, we can configure the image scales like:

Image scales 2.png

and provide alternative images with suffix “@half” as half-resolution of the original ones.

Automatic screen scaling

Creating game flow and logic

Scene management

Most applications and games have different scenes (e.g., menu, scoreboard, game levels, inventory, end-of-game, etc). A transition from one scene to another is usually started by user input or some other event. These transitions typically consist of the following steps:

   • Construct the new scene (e.g., load bitmaps, sprites, sounds)
   • Hide the original scene and show the new scene, often with some kind of special effect (e.g., crossfading)
   • Destroy the old scene

High-level scene management functions aren’t provided directly in Gideros but the Gideros team has released a “Scene Manager” library (see: https://github.com/gideros/Scene-Manager) that can be used as is or that can be modified and extended to by the user (it’s licensed under the liberal MIT license). The library includes a demonstration Gideros project that shows many of the available built-in transitions (a demonstration video can be found on YouTube: http://www.youtube.com/watch?feature=player_embedded&v=KQMSHOMInQU).

Scene Manager Usage Overview

Create a class for each of your scenes (and preferably put them into separate .lua files):

Menu = gideros.class(Sprite)
Game = gideros.class(Sprite)
EndScene = gideros.class(Sprite)


Create a SceneManager instance passing a table with scene names as keys and your classes as values:

local sceneManager = SceneManager.new{
   ["menu"] = Menu,
   ["game"] = Game,
   ["endScene"] = EndScene,
}
stage:addChild(sceneManager)


Change between scenes with function SceneManager:changeScene(sceneName, duration, transition, ease) like so:

sceneManager:changeScene("game", 1, SceneManager.moveFromTop, easing.linear)


SceneManager dispatches events as it makes the transition from one scene to another (enterBegin, enterEnd, exitBegin, exitEnd). You can register to listen for these events to begin/end your animations, clear your timers, save the scores, etc.

Built-in Transition Effects

The scene manager library defines the following transition effects: Move functions

   • SceneManager.moveFromLeft
   • SceneManager.moveFromRight
   • SceneManager.moveFromBottom
   • SceneManager.moveFromTop
   • SceneManager.moveFromLeftWithFade
   • SceneManager.moveFromRightWithFade
   • SceneManager.moveFromBottomWithFade
   • SceneManager.moveFromTopWithFade

Overlay functions

   • SceneManager.overFromLeft
   • SceneManager.overFromRight
   • SceneManager.overFromBottom
   • SceneManager.overFromTop
   • SceneManager.overFromLeftWithFade
   • SceneManager.overFromRightWithFade
   • SceneManager.overFromBottomWithFade
   • SceneManager.overFromTopWithFade

Fade & flip functions

   • SceneManager.fade
   • SceneManager.crossFade
   • SceneManager.flip
   • SceneManager.flipWithFade
   • SceneManager.flipWithShade

Transition Events

The scene manager dispatches the following four events:

   • enterBegin: Dispatched when your new scene is about to enter the stage. You can initialize your variables here.
   • enterEnd: Dispatched after the transition of new scene has ended. Mostly, you add listeners and start your logic here.
   • exitBegin: Dispatched when your current scene is about to leave the stage. You can remove listeners and stop timers here.
   • exitEnd: Dispatched after your current scene has left the stage.

The following code shows an example scene (“MyScene”) and how you might use the “enterEnd” and “exitBegin” events:

MyScene = gideros.class(Sprite)
function MyScene:init()
	self:addEventListener("enterEnd", self.onEnterEnd, self)
	self:addEventListener("exitBegin", self.onExitBegin, self)
end
function MyScene:onEnterEnd()
	self:addEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
	-- create your timers
	-- also you can add your player sprite
	-- and so on...
end
function MyScene:onExitBegin()
	self:removeEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
	-- stop your timers
	-- save score
	-- and so on...
end


Fonts

Gideros supports for both bitmap fonts (also known as BMFonts) and TrueType Fonts. Font and TTFont classes are used to load fonts and TextField class is used to create display objects for text display.

Bitmap Fonts

A bitmap font consists of an image that contains all the glyphs, and a descriptor file that defines the properties and bounds for each glyph. Also, font descriptor file can contain kerning information between each glyph.

You need to use an external tool to create bitmap fonts. Gideros Font Creator is one of these and you can find it inside the installation directory. Other noticeable bitmap font creator tools are:

   • BMFont from AngelCode (windows only) http://www.angelcode.com/products/bmfont/
   • Glyph Designer (mac only) http://www.71squared.com/glyphdesigner
   • Hiero (java, multiplatform) http://slick.cokeandcode.com/demos/hiero.jnlp

Usually, these tools have the ability to enhance the bitmap fonts with effects like shadows, outlines, color gradients, and so on. And this is the main advantage of using bitmap fonts.

After creating the image (e.g. font.png) and the descriptor file (e.g. font.txt or font.fnt), you can load the bitmap font as:

local font = Font.new("font.txt", "font.png")


And after loading the font, you can create a TextField object to display a text as:

local textField = TextField.new(font, "Hello World")


Finally you can add the resulting TextField object to the stage to display it:

stage:addChild(textField)


TextField inherits from Sprite class and therefore has all the properties and capabilities of sprites.

Bitmap fonts are fast to render and fast to update. But they should be created with a tool beforehand and therefore the size of any bitmap font is fixed. Also, you need to create separate bitmap fonts for each automatic image resolution suffix (e.g. @2x, @4x, etc.) and Gideros automatically selects the best bitmap font according to your scaling.

TrueType Fonts

Gideros also can load and use .ttf (TrueType font) files directly:

local font = TTFont.new("arial.ttf", 20)
local text = TextField.new(font, "Hello world")
text:setPosition(10, 20)
stage:addChild(text)


Here the first parameter is the font file and the second parameter is the size.

While using TrueType fonts, whenever a text is created or updated, the given TrueType font is used to render the text on a texture and unfortunately it's a slow operation. This is the main disadvantage of using TrueType fonts but can be avoided with caching.

TrueType Fonts with Caching

It's possible to cache TrueType font glyphs into an internal texture so that updating the font text won't be a slow operation anymore. For example, to create a TrueType font with all uppercase characters:

local font = TTFont.new("arial.ttf", 20, " ABCDEFGHIJKLMNOPQRSTUVWXYZ")
local text = TextField.new(font, "HELLO WORLD")
text:setPosition(10, 20)
stage:addChild(text)


Note: If a character is not found in the internal cache, it simply won't be displayed.

Font Metrics

In Gideros, TextField objects are positioned according to their baselines:

Typography Line Terms.png

The ascender of a font is the distance from the baseline to the highest position characters extend to. So that...

local font = TTFont.new("arial.ttf", 20)
local text = TextField.new(font, "Hello world")
text:setY(font:getAscender())
stage:addChild(text)


Drawing sprites

Using textures

Shapes

Gideros provides a Shape class for drawing primitive shapes. It is similar to the drawing API found in Flash although at the moment it only has a subset of Flash’s capabilities. Since the Shape class inherits from Sprite (which itself inherits from EventListener), instances of the shape class can be rotated, scaled, positioned on the screen, listen for and dispatch events - basically anything that Sprites can do. See the chapter on Sprites for a full list of inherited capabilities.

The unique functions added by the Shape class are:

Function Name Description Shape.new() Creates a new Shape object Shape:setFillStyle(type, …) Sets the fill style for a shape Shape:setLineStyle(width, color, alpha) Sets the line style for a shape Shape:beginPath(winding) Begins a path Shape:endPath() Ends a path Shape:moveTo(x,y) Moves the pen to a new location Shape:lineTo(x,y) Draws a line from the current location to the specified point Shape:closePath() Draws a line from the current location to the first point in the path Shape:clear() Clears all paths from the shape and resets the line and fill style to default values

One question that you might ask is: Why draw using this primitive API? Wouldn’t it be better to use some other authoring environment (e.g., Photoshop, Inkscape, Gimp) then import the image as a Texture? In most cases, an authoring tool is the right answer. However, there are cases where you don’t know which graphics you need in advance:

   • Drawing tool (e.g., drawing lines based on user input)
   • Graphs (e.g., stock prices, bar-charts)
   • Graphics equalizer in a music player
   • Skinning physics engine soft bodies 

Gideros coordinate system

The standard cartesian coordinate system that everyone learns in school consists of an origin point (x=0, y=0) which is often drawn in the center of a page. The x values increase to the right horizontally and the y values increase upwards vertically (i.e., x values are negative to the left of the origin and positive to the right, y values are negative below the origin and positive above).

The origin point in the Gideros coordinate system is in the upper left-hand corner of the screen with an inverted y-axis (i.e., y values are negative above the origin and positive below the origin). The x-axis is the same as the standard cartesian coordinate system. {ISSUE: Current origin point in gideros desktop player seems to be 1,0 -- posted issue on gideros forum}

Coordinate system.png

Usage overview

Shapes consist of zero or more paths. Each path consists of zero or more lines with the same line and fill style. A different line or fill style can be used for each path. Since a path can only have one line style and one fill style, multiple paths are required to use different line / fill styles within a shape.

A pen & paper analogy is often used to describe the drawing approach used by the Shape class. The “moveTo” function can be thought of as lifting a pen off the paper and moving it to a different location without drawing anything. The “lineTo” function draws a straight line from the current pen location to the destination location.

The overall flow for drawing with the Shape class is:

   • Create a new shape
   • For each path that you want to draw;
   • Begin the path
   • Set the fill style (fill or line must be set for the shape to be visible)
   • Set the line style (fill or line must be set for the shape to be visible)
   • Move pen and draw lines
   • Close the path (optional)
   • End the path

Drawing your first line

Our first drawing will be a simple horizontal line from x=100,y=100 to x=200,y=100 as shown in the following figure. We’ll use the moveTo function to move to the first point, then the lineTo function to actually draw the line.

First line.png

Here’s the code to draw the line:

local shape = Shape.new() -- create the shape
shape:beginPath()         -- begin a path
shape:setLineStyle(1)     -- set the line width = 1
shape:moveTo(100,100)     -- move pen to start of line
shape:lineTo(200,100)     -- draw line
shape:endPath()           -- end the path 
stage:addChild(shape)     -- add the shape to the stage


Some things to note about this code:

  • Without the call to setLineStyle, the line wouldn’t have been visible -- in this example, we used setLineStyle to set the line width to 1 (the default line color is black)
  • We added the shape directly to the stage. For this example that’s good enough. For more complicated examples you’d probably use some other parent. Most of the following Shape examples will use this same approach.

Drawing a Rectangle

Lines aren’t very interesting so let's expand our line into a filled rectangle. We’ll draw the rectangle with the upper left corner at 100,100 and the lower right corner at 200,200. We’ll also fill the rectangle with a boring gray color.

local shape = Shape.new() -- create the shape
shape:beginPath()         -- begin a path
shape:setLineStyle(1)     -- set the line width = 1
shape:setFillStyle(Shape.SOLID, 0xcccccc) -- NEW: boring gray
shape:moveTo(100,100)     -- move pen to start of line
shape:lineTo(200,100)     -- draw top of rectangle
shape:lineTo(200,200)     -- NEW: draw right side of rectangle
shape:lineTo(100,200)     -- NEW: draw bottom of rectangle
shape:lineTo(100,100)     -- NEW: draw left side of triangle
shape:endPath()           -- end the path
stage:addChild(shape)     -- add the shape to the stage


This example is similar to our line drawing program plus 1 additional line to fill the rectangle and 3 additional new lines to draw the right, bottom, and left sides of the rectangle.

First rectangle.png

Drawing Arbitrary Polygons

There are a couple of simplifications that we can make to the rectangle code:

  • The call to moveTo can be replaced with lineTo. The first call to lineTo after a path is started behaves just like a moveTo.
  • Although a rectangle has 4 points, we have to use a total of 5 moveTo/lineTo calls -- the first point has to be used twice (once for the initial moveTo command and once to close the rectangle with the final lineTo call). The last lineTo command can be replaced with a call to closePath. The closePath function is equivalent to a lineTo command to the first point in a path.

The following code uses these two simplifications to draw a polygon from an arbitrary list of data points:

local path = { {100,100}, {200,100}, {200,200}, {100,200} }
local shape = Shape.new()
shape:setLineStyle(1)
shape:setFillStyle(Shape.SOLID, 0xcccccc)
shape:beginPath()
for i,p in ipairs(path) do
	shape:lineTo(p[1], p[2])  -- lineTo used for all points
end
shape:closePath()  -- connect last point to first point
shape:endPath()


We can change path to have as many points as we’d like and the above code will draw the closed polygon.

path = { {x1,y1}, {x2,y2}, … add as many points as you want … }

Shape Anchor Point

Shapes are “anchored” at the graph origin (0,0). The anchor point affects how the shape behaves when manipulated (e.g., rotated, scaled). If we were to rotate the rectangle we drew earlier by 90°, the rectangle would have rotated off the screen since it would have rotated about the (0,0) origin point. The anchor point also affects scaling -- scaling the rectangle would have moved it further away from (0,0) if it was enlarged and closer to (0,0) if we’d scaled it down.

Let’s change our previous polygon example so that the rectangle will stay centered at its center point (150,150) if rotated or scaled. The code will need the following two changes:

   • Draw the rectangle centered at (0,0)
   • Use the inherited Sprite:setPosition function to position the shape

We can make these changes by modifying the first few lines of our previous example like so:

local path = { {-50,-50}, {50,-50}, {50,50}, {-50,50} }
-- CHANGED: points are now centered around (0,0)
local shape = Shape.new()
shape:setPosition(150,150) -- NEW
-- ... the rest of the original example would go here …


Line and Fill Styles

We’ve used setLineStyle and setFillStyle in our examples, but the lines and rectangles that we’ve drawn so far are pretty boring. This section provides more details on these two functions.

The setLineStyle function has one required parameter (width) and two optional parameters (color, alpha): width: width of the line (integer number > 0) color: color value of the line (optional hexadecimal number, default = 0x000000) (red is 0xFF0000, blue=0x0000FF, green=0x00FF00, white=0xFFFFFF, etc.) alpha: alpha value for the line (optional floating point value between 0 & 1, default=1) (0 = invisible, 0.5 = 50% transparency, 1 = no transparency)

The following figure shows some example line styles.

Linestyles.png

Shapes can be unfilled, filled with a color, or filled with a texture. The first argument to setFillStyle determines the fill type:

  • Shape.NONE: Clears the fill style (i.e., the shape will not be filled)
  • Shape.SOLID: Sets the fill style as a solid color. In this mode, an additional color parameter is required (hexadecimal number). An optional alpha value (floating point number between 0 and 1) can also be specified.
  • Shape.TEXTURE: Fills the shape with a tiled texture. In this mode, and additional texture argument is required. An optional transformation matrix can also be specified.

The following illustrates how to use the three different types of fill styles:

setFillStyle(Shape.NONE) -- unfilled
setFillStyle(Shape.SOLID, 0xff0000) -- fill solid red color
setFillStyle(Shape.SOLID, 0xff0000, 0.5) -- fill with red, 50% transparency
local texture = Texture.new("image.png")
setFillStyle(Shape.TEXTURE, texture) -- fill with texture
local matrix = Matrix.new(0.5, 0, 0, 0.5, 0, 0)
setFillStyle(Shape.TEXTURE,texture,matrix) -- fill with texture scale x/y by 0.5


There are two important things to note when using texture fills:

  • If the shape of the texture is smaller than the area to be filled, Gideros will tile the textures in both the x and y dimensions. The tiles will be aligned so that the upper left-hand corner of the texture will be at x=0, y=0 (unless this is modified by a transformation matrix).
  • If the width and/or height of the texture is not a power of 2, Gideros will increase the width/height to be a power of 2 with the expanded area left unfilled or transparent. For example, a 33 (width) x 123 (height) texture will be re-sized to 64 x 128 then tiled as necessary.


As stated previously, all lines and drawn shapes within a path share the same line and fill style. Gideros applies the most recently specified line and fill style when each path is ended (i.e., with endPath). The following code draws a shape with 2 paths. Everything drawn between a beginPath/endPath pair will have the same line and fill style. In other words, even though setLineStyle is called 5 different times, only 2 of them have an effect because there are only 2 paths.

local s = Shape.new()
s:setLineStyle(1)
s:beginPath()
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 1
s:endPath()
-- All lines in the following path will have a width of 3.
-- Only the setLineStyle call right before endPath has an effect
s:beginPath()
s:setLineStyle(1)
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 3
s:setLineStyle(2)
s:moveTo(10,10); s:lineTo(20,10) -- line width will be 3
s:setLineStyle(3)
s:endPath()
s:setLineStyle(4)


Winding Rules

Simple paths such as circles, triangles, rectangles have a well-defined and obvious “fill area.”. For complex paths (e.g., concentric circles, shapes with intersecting lines), multiple choices can be made as to whether fill an area. The Shape:beginPath function takes an optional winding argument that determines how the path will be filled (the default value for the argument is Shape.EVEN_ODD). To determine whether an area should be filled, the following two rules are available within Gideros:

  • Even-odd rule (Shape.EVEN_ODD)
  1. Draw a line with a start point in the area to be filled extending to infinity in any direction
  2. If the line crosses an even number of lines, the area should NOT be filled
  3. If the line crosses an odd number of lines, the area should be filled
  • Non-zero rule (Shape.NON_ZERO) - this rule depends on the drawing direction (or winding) of the edges of the path. When paths of opposite drawing direction intersect, the area will be unfilled.
  1. Draw a line with a start point in the area to be filled extending to infinity in any direction
  2. Start with a count of 0
  3. Every time the line crosses a line of the polygon drawn in one direction add one to the count. Subtract one from the count for every edge drawn in the opposite dimension.
  4. If the ending count is zero, the area should NOT be filled. If the ending count is zero, the area should be filled.


For example, consider a simple path such as a rectangle: a line started from “outside” the rectangle will either cross 0 edges or 2 edges. Since the line crosses an even number of lines, the area should not be filled using the even-odd rule. The area wouldn’t be filled with the non-zero rule either: the line would either cross 0 edges (count would be 0) or two edges -- one edge drawn in one direction, the other edge drawn in the opposite direction (the count would be 0). If a line is started inside the rectangle, the line will intersect one edge (an odd count for the even-odd rule, and a count of either -1 or 1 for the non zero rule).

In can be confusing to decide whether an area should be filled but thankfully examples of different winding rules are easy to find since these two winding rules are used in flash, SVG, postscript, and many other drawing technologies.

Using tilemaps

Optimization and memory management

Timelined animations with Movieclip

Note: Currently there’s no way to set the animation speed, however you can prepare different animations for different speeds like:

local eliAnim = MovieClip.new{
{1, 7, self.anim[1]},
{8, 15, self.anim[2]},
{16, 18, self.anim[1]},
{19, 21, self.anim[2]},
}
eliAnim:setGotoAction(15, 1) --> frames 1-15: slow animation
eliAnim:setGotoAction(21, 16) --> frames 16-21: fast animation
eliAnim:gotoAndPlay(1) --> play slow animation
eliAnim:gotoAndPlay(16) --> play fast animation


This way, you can position your MovieClip wherever you want (e.g. running player, walking player, flying helicopter, etc).