2015年4月30日 星期四

[Corona SDK] How to design Segmented Control with our own style?

Segmented Control is very useful in widget tools.
Assume that we have 4 items for user to select.
We can use the following example:
display.setStatusBar( display.HiddenStatusBar )
local widget = require( "widget" )
-- Listen for segmented control events      
local function onSegmentPress( event )
    local target = event.target
    print( "Segment Label is:", target.segmentLabel )
    print( "Segment Number is:", target.segmentNumber )
end

-- Create a default segmented control
local segmentedControl = widget.newSegmentedControl
{
    left = 36,
    top = 150,
    segmentWidth = 24,
    segments = { "1", "2", "3", "4" },
    defaultSegment = 2,
    onPress = onSegmentPress
}
Check the result:

Not bad...
However, you will find that the width of item can be adjusted by segmentWidth.
How about the height value? 
It seems that we cannot modify it.
On the other hand, we may want to have our own style.
How to do?

First, we need to prepare a image file as the source of ImageSheet.
It should include 7 pictures, which are the left/middle/right frames for unselected items, and the left/middle/right frames for selected items. The last one is the divider of segment frame.
The image file:
The codes should be modified as below:
display.setStatusBar( display.HiddenStatusBar )
local widget = require( "widget" )
-- Listen for segmented control events      
local function onSegmentPress( event )
    local target = event.target
    print( "Segment Label is:", target.segmentLabel )
    print( "Segment Number is:", target.segmentNumber )
end

local options = {
     frames = 
     {
         { x=0, y=0, width=24, height=20 },
         { x=26, y=0, width=24, height=20 },
         { x=52, y=0, width=24, height=20 },
         { x=78, y=0, width=24, height=20 },
         { x=104, y=0, width=24, height=20 },
         { x=130, y=0, width=24, height=20 },
         { x=156, y=0, width=1, height=20 }
     },
     sheetContentWidth = 157,
     sheetContentHeight = 20
 }
 local segmentSheet = graphics.newImageSheet( "imageSheet.png", options )
 local segmentedControl = widget.newSegmentedControl
 {
     left = 100,
     top = 100,     

     sheet = segmentSheet,
     leftSegmentFrame = 1,
     middleSegmentFrame = 2,
     rightSegmentFrame = 3,
     leftSegmentSelectedFrame = 4,
     middleSegmentSelectedFrame = 5,
     rightSegmentSelectedFrame = 6,
     segmentFrameWidth = 24,
     segmentFrameHeight = 20,

     dividerFrame = 7,
     dividerFrameWidth = 1,
     dividerFrameHeight = 20,

     labelSize=12,
  labelColor = { default={ 0.5, 0.5, 0.5 }, over={ 1, 1, 1 } },
     segmentWidth = 24,
     segments = { "1", "2", "3", "4", "5"},
     defaultSegment = seg,
     onPress = onSegmentPress
 } 
Check the result:
It does not look right. 
There are two issues. 
First, item 1 and item 2 are highlighted at the same time.
Second, the height of divider is not correct. It is system default setting, but not 20 as expected.

For issue 1, we can modify the width for frame 4 (leftSegmentSelectedFrame).
We just minus it by the width of divider:
local options = {
     frames = 
     {
         { x=0, y=0, width=24, height=20 },
         { x=26, y=0, width=24, height=20 },
         { x=52, y=0, width=24, height=20 },
         { x=78, y=0, width=23, height=20 },
         { x=104, y=0, width=24, height=20 },
         { x=130, y=0, width=24, height=20 },
         { x=156, y=0, width=1, height=20 }
     },
     sheetContentWidth = 157,
     sheetContentHeight = 20
 }
That is, we need to let its value to be less than segmentWidth.

For issue 2, we need to remove segmentedControl and add it again:
local segmentedControl = widget.newSegmentedControl
 {
     left = 100,
     top = 100,     

     sheet = segmentSheet,
     leftSegmentFrame = 1,
     middleSegmentFrame = 2,
     rightSegmentFrame = 3,
     leftSegmentSelectedFrame = 4,
     middleSegmentSelectedFrame = 5,
     rightSegmentSelectedFrame = 6,
     segmentFrameWidth = 24,
     segmentFrameHeight = 20,

     dividerFrame = 7,
     dividerFrameWidth = 1,
     dividerFrameHeight = 20,

     labelSize=12,
  labelColor = { default={ 0.5, 0.5, 0.5 }, over={ 1, 1, 1 } },
     segmentWidth = 24,
     segments = { "1", "2", "3", "4", "5"},
     defaultSegment = seg,
     onPress = onSegmentPress
 } 
 segmentedControl:removeSelf( )
 local segmentedControl = widget.newSegmentedControl
 {
     left = 100,
     top = 100,     

     sheet = segmentSheet,
     leftSegmentFrame = 1,
     middleSegmentFrame = 2,
     rightSegmentFrame = 3,
     leftSegmentSelectedFrame = 4,
     middleSegmentSelectedFrame = 5,
     rightSegmentSelectedFrame = 6,
     segmentFrameWidth = 24,
     segmentFrameHeight = 20,

     dividerFrame = 7,
     dividerFrameWidth = 1,
     dividerFrameHeight = 20,

     labelSize=12,
  labelColor = { default={ 0.5, 0.5, 0.5 }, over={ 1, 1, 1 } },
     segmentWidth = 24,
     segments = { "1", "2", "3", "4", "5"},
     defaultSegment = seg,
     onPress = onSegmentPress
 } 
The final result:
The method shown above is just a workaround....

[Corona SDK] 如何設計自己風格的Segmented Control?

Segmented Controlwidget裡相當好用的一種,
假設你有4個items選項讓使用者設定,
用以下範例即可:
display.setStatusBar( display.HiddenStatusBar )
local widget = require( "widget" )
-- Listen for segmented control events      
local function onSegmentPress( event )
    local target = event.target
    print( "Segment Label is:", target.segmentLabel )
    print( "Segment Number is:", target.segmentNumber )
end

-- Create a default segmented control
local segmentedControl = widget.newSegmentedControl
{
    left = 36,
    top = 150,
    segmentWidth = 24,
    segments = { "1", "2", "3", "4" },
    defaultSegment = 2,
    onPress = onSegmentPress
}
得到的結果:

不過,你會發現,每個item的寛度可以透過segmentWidth來設定,
那高度呢?好像無法改變耶...
或者,我們想要有自己特別的風格呢? 

首先,你必須準備好一張圖,當成ImageSheet的來源,
它裡面要含有7張圖形,分別是item未選取的左框/中框/右框, 以及選到時的左框/中框/右框,最後一張是item中間的分隔線,
圖形內容如下:
程式改寫如下:
display.setStatusBar( display.HiddenStatusBar )
local widget = require( "widget" )
-- Listen for segmented control events      
local function onSegmentPress( event )
    local target = event.target
    print( "Segment Label is:", target.segmentLabel )
    print( "Segment Number is:", target.segmentNumber )
end

local options = {
     frames = 
     {
         { x=0, y=0, width=24, height=20 },
         { x=26, y=0, width=24, height=20 },
         { x=52, y=0, width=24, height=20 },
         { x=78, y=0, width=24, height=20 },
         { x=104, y=0, width=24, height=20 },
         { x=130, y=0, width=24, height=20 },
         { x=156, y=0, width=1, height=20 }
     },
     sheetContentWidth = 157,
     sheetContentHeight = 20
 }
 local segmentSheet = graphics.newImageSheet( "imageSheet.png", options )
 local segmentedControl = widget.newSegmentedControl
 {
     left = 100,
     top = 100,     

     sheet = segmentSheet,
     leftSegmentFrame = 1,
     middleSegmentFrame = 2,
     rightSegmentFrame = 3,
     leftSegmentSelectedFrame = 4,
     middleSegmentSelectedFrame = 5,
     rightSegmentSelectedFrame = 6,
     segmentFrameWidth = 24,
     segmentFrameHeight = 20,

     dividerFrame = 7,
     dividerFrameWidth = 1,
     dividerFrameHeight = 20,

     labelSize=12,
  labelColor = { default={ 0.5, 0.5, 0.5 }, over={ 1, 1, 1 } },
     segmentWidth = 24,
     segments = { "1", "2", "3", "4", "5"},
     defaultSegment = seg,
     onPress = onSegmentPress
 } 
看一下執行結果:
好像不太對, 有二個問題,
一個是item 1和item 2,同時highlight了,
另一個是divider的高度,好像是系統預的,不是我們設定的20, 

第一個問題,可以修改frames裡第4個frame (leftSegmentSelectedFrame)的寬度,
我們將它減掉divider的寬度:
local options = {
     frames = 
     {
         { x=0, y=0, width=24, height=20 },
         { x=26, y=0, width=24, height=20 },
         { x=52, y=0, width=24, height=20 },
         { x=78, y=0, width=23, height=20 },
         { x=104, y=0, width=24, height=20 },
         { x=130, y=0, width=24, height=20 },
         { x=156, y=0, width=1, height=20 }
     },
     sheetContentWidth = 157,
     sheetContentHeight = 20
 }
也就說,讓它寛度比segmentWidth小即可, 

第二個問題,可以將segmentedControl移除再加一次即可:
local segmentedControl = widget.newSegmentedControl
 {
     left = 100,
     top = 100,     

     sheet = segmentSheet,
     leftSegmentFrame = 1,
     middleSegmentFrame = 2,
     rightSegmentFrame = 3,
     leftSegmentSelectedFrame = 4,
     middleSegmentSelectedFrame = 5,
     rightSegmentSelectedFrame = 6,
     segmentFrameWidth = 24,
     segmentFrameHeight = 20,

     dividerFrame = 7,
     dividerFrameWidth = 1,
     dividerFrameHeight = 20,

     labelSize=12,
  labelColor = { default={ 0.5, 0.5, 0.5 }, over={ 1, 1, 1 } },
     segmentWidth = 24,
     segments = { "1", "2", "3", "4", "5"},
     defaultSegment = seg,
     onPress = onSegmentPress
 } 
 segmentedControl:removeSelf( )
 local segmentedControl = widget.newSegmentedControl
 {
     left = 100,
     top = 100,     

     sheet = segmentSheet,
     leftSegmentFrame = 1,
     middleSegmentFrame = 2,
     rightSegmentFrame = 3,
     leftSegmentSelectedFrame = 4,
     middleSegmentSelectedFrame = 5,
     rightSegmentSelectedFrame = 6,
     segmentFrameWidth = 24,
     segmentFrameHeight = 20,

     dividerFrame = 7,
     dividerFrameWidth = 1,
     dividerFrameHeight = 20,

     labelSize=12,
  labelColor = { default={ 0.5, 0.5, 0.5 }, over={ 1, 1, 1 } },
     segmentWidth = 24,
     segments = { "1", "2", "3", "4", "5"},
     defaultSegment = seg,
     onPress = onSegmentPress
 } 
最後結果:
上面的方法都只是workaround...

2015年4月29日 星期三

[Corona SDK] How can we decide if the collision will happen or not for each object?

In [Corona SDK] How to detect the collision for physical objects - using Global Collision, we find that all objects will collide with each other.
We can then decide how to deal with the collision in function onGlobalCollision().
What if we wish that fruit1 and fruit2 will not collide with each other?
For it, we need to add the parameter filter, as shown below:
local main = display.newImage( "main.png", 160, 240 )
local mainCollisionFilter = { categoryBits=2, maskBits=1023 } 
physics.addBody( main, { density = 1.0, friction = 0.3, bounce = 0.2 , filter=mainCollisionFilter} )
main.myName = "main"

local fruit1 = display.newImage( "fruit.png", 100, 120 )
local fruit1CollisionFilter = { categoryBits=4, maskBits=2 } 
physics.addBody( fruit1, { density = 1.0, friction = 0.3, bounce = 0.2, filter=fruit1CollisionFilter} )
fruit1.myName = "fruit1"

local fruit2 = display.newImage( "fruit.png", 300, 220 )
local fruit2CollisionFilter = { categoryBits=8, maskBits=6 } 
physics.addBody( fruit2, { density = 1.0, friction = 0.3, bounce = 0.2, filter=fruit2CollisionFilter } )
fruit2.myName = "fruit2"

local fruit3 = display.newImage( "fruit.png", 300, 220 )
physics.addBody( fruit3, { density = 1.0, friction = 0.3, bounce = 0.2 } )
fruit3.myName = "fruit3"
The parameter categoryBits is used to set the category of that object.
Basically, this value will be unique for each object if we want to distinguish them.
categoryBits is set in bit level since it will cooperate with maskBits.
Of course, we can set the same categoryBits for different objects, if we want them to have similar behaviors. 
categoryBits can also be set to 1 for multiple bits, such as 7.
The final behavior will be decided by the result of mask operation with maskBits.
We can show the filter setting of above codes by the chart:
For fruit3, it does not specify the parameter filter. Its default setting will be { categoryBits=1, maskBits=1023 }, which means that it will collide with all other objects.
It there may have some objects without filter setting, we'd better set categoryBits start from 2 for those object with filter.
Besides, in the example, the maskBits for fruit2 is 6. It means that it wish to collide with main and fruit1.
However, the maskBits for fruit1 is 2, which means that it does not want to collide with fruit2.
The mask operation is the result of AND for all values.
fruit1 will not collide with fruit2 in the end.

[Corona SDK] 如何決定各個物體間的碰撞發生與否?

[Corona SDK] 如何偵測物體碰撞 - 利用Global Collision裡,我們會發現所有的物體都會互相碰撞,
我們可以在onGlobalCollision()去決定要如何處理碰撞,
假設我們希望fruit1fruit2不會互相碰撞,要怎麼處理呢?
此時我們就必須增加filter的參數,如以下範例:
local main = display.newImage( "main.png", 160, 240 )
local mainCollisionFilter = { categoryBits=2, maskBits=1023 } 
physics.addBody( main, { density = 1.0, friction = 0.3, bounce = 0.2 , filter=mainCollisionFilter} )
main.myName = "main"

local fruit1 = display.newImage( "fruit.png", 100, 120 )
local fruit1CollisionFilter = { categoryBits=4, maskBits=2 } 
physics.addBody( fruit1, { density = 1.0, friction = 0.3, bounce = 0.2, filter=fruit1CollisionFilter} )
fruit1.myName = "fruit1"

local fruit2 = display.newImage( "fruit.png", 300, 220 )
local fruit2CollisionFilter = { categoryBits=8, maskBits=6 } 
physics.addBody( fruit2, { density = 1.0, friction = 0.3, bounce = 0.2, filter=fruit2CollisionFilter } )
fruit2.myName = "fruit2"

local fruit3 = display.newImage( "fruit.png", 300, 220 )
physics.addBody( fruit3, { density = 1.0, friction = 0.3, bounce = 0.2 } )
fruit3.myName = "fruit3"
其中,categoryBits是用來設定物體的類別,原則上每個物體都會不同,這樣才能區分,
因為要配合後面的maskBits,所以categoryBits設定也是以bit為操作,
當然,不同物體也可以設定成同一個categoryBits值,如果你希望它們的行為是類似的話,
categoryBits也可以是多個bits為1,例如7,
最後的行為都是看和maskBits執行mask的結果,
將上面範例整理成圖表方式:
fruit3並沒有設定filter,它的預設值就會變成{ categoryBits=1, maskBits=1023 }
也就是說,它會和任何物體碰撞,
如果有些物體有設定filter,有些物體沒有設定,
那麼,那些有設定filtercategoryBits,最好是從2開始,才能區分,

另外,範例中,雖然fruit2maskBits是6,也就是說,它希望能和main以及fruit1碰撞,
但是因為fruit1maskBits為2,fruit1不希望和fruit2碰撞,
mask執行是所有設定值執行AND的結果,所以fruit1fruit2並不會發生碰撞

[Corona SDK] How to detect the collision for physical objects - using Global Collision

For detection of collision, we can use Local Collision or Global Collision.
Below is the usage of Global Collision:
local main = display.newImage( "mainRole.png", 160, 240 )
physics.addBody( main, { density = 1.0, friction = 0.3, bounce = 0.2 } )
main.myName = "mainRole"

local fruit1 = display.newImage( "fruit.png", 100, 120 )
physics.addBody( fruit1, { density = 1.0, friction = 0.3, bounce = 0.2 } )
fruit1.myName = "fruit1"

local fruit2 = display.newImage( "fruit.png", 300, 220 )
physics.addBody( fruit2, { density = 1.0, friction = 0.3, bounce = 0.2 } )
fruit2.myName = "fruit2"

local function onGlobalCollision( event )

    if ( event.phase == "began" ) then        
        if((event.object1.myName=="mainRole" and event.object2.myName=="fruit1") or (event.object1.myName=="fruit1 and event.object2.myName=="mainRole")) then
         print( "began: " .. event.object1.myName .. " and " .. event.object2.myName )         
         if(fruit1 ~= nil) then
          print("Remove fruit1")
          fruit1:removeSelf( )
    fruit1 = nil
         end
        elseif((event.object1.myName=="mainRole" and event.object2.myName=="fruit2") or (event.object1.myName=="fruit2" and event.object2.myName=="mainRole")) then
         print( "began: " .. event.object1.myName .. " and " .. event.object2.myName )         
         if(fruit2 ~= nil) then
          print("Remove fruit2")
          fruit2:removeSelf( )
    fruit2 = nil
         end
        end

    elseif ( event.phase == "ended" ) then
        print( "ended: " .. event.object1.myName .. " and " .. event.object2.myName )
    end
end

Runtime:addEventListener( "collision", onGlobalCollision )
We add the specific name for each object.
When the collision happens, we can know which two objects collide with each other by verify the object names.

However, when we run the codes shown above, we may find that sometimes Runtime error will happen.
In executing fruit1:removeSelf( ), it will tell you that fruit1 is nil.
It is strange since we do check if(fruit1 ~= nil).
For fruit2, the problem is similar.

From the log message, the code print( "began: " .. event.object1.myName .. " and " .. event.object2.myName ) may run few times, and then execute print("Remove fruit1") after that.
It seems that this function is multi-entry.
multi-entry function without critical section mechanism to protect it is liable to corrupt the data.
To solve this problem, we can make the removal of objects to other place, like the following codes:
local removeFruit1 = false
local removeFruit2 = false
local main = display.newImage( "mainRole.png", 160, 240 )
physics.addBody( main, { density = 1.0, friction = 0.3, bounce = 0.2 } )
main.myName = "mainRole"

local fruit1 = display.newImage( "fruit.png", 100, 120 )
physics.addBody( fruit1, { density = 1.0, friction = 0.3, bounce = 0.2 } )
fruit1.myName = "fruit1"

local fruit2 = display.newImage( "fruit.png", 300, 220 )
physics.addBody( fruit2, { density = 1.0, friction = 0.3, bounce = 0.2 } )
fruit2.myName = "fruit2"

local function onGlobalCollision( event )

    if ( event.phase == "began" ) then        
        if((event.object1.myName=="mainRole" and event.object2.myName=="fruit1") or (event.object1.myName=="fruit1 and event.object2.myName=="mainRole")) then
         print( "began: " .. event.object1.myName .. " and " .. event.object2.myName )         
         removeFruit1 = true
        elseif((event.object1.myName=="mainRole" and event.object2.myName=="fruit2") or (event.object1.myName=="fruit2" and event.object2.myName=="mainRole")) then
         print( "began: " .. event.object1.myName .. " and " .. event.object2.myName )         
         removeFruit2 = true
        end

    elseif ( event.phase == "ended" ) then
        print( "ended: " .. event.object1.myName .. " and " .. event.object2.myName )
    end
end

Runtime:addEventListener( "collision", onGlobalCollision )

local function removeAction()
 if(removeFruit1) then
   if(fruit1 ~= nil) then
      print("Remove fruit1")
      fruit1:removeSelf( )
      fruit1 = nil
      removeFruit1 = false
   end
 end
 if(removeFruit2) then
   if(fruit2 ~= nil) then
     print("Remove fruit2")
     fruit2:removeSelf( )
     fruit2 = nil
     removeFruit2 = false
   end
 end 
end
 
timer.performWithDelay( 50, removeAction,-1 )

2015年4月28日 星期二

[Corona SDK] How to detect double-tapped?

If we want to check if user has touched the screen, we can use Tap Detection or Touch Detection.
If we want to know if user has double-tapped the screen, to use Tap Detection is better.
Example shown as below:
local function buttonListener( event )

    if ( event.numTaps == 1 ) then
        print( "single-tapped" )
        runSingleTappedAction()
    elseif ( event.numTaps == 2 ) then
        print( "double-tapped" )
        runDoubleTappedAction()
    else
        return true
    end
end

local myButton = display.newRect( 160, 240, 120, 60 )
myButton:addEventListener( "tap", buttonListener )
For double-tapped, it will happen following single-tapped event.
That is, it will execute runSingleTappedAction() first, and then execute runDoubleTappedAction().
What if we do not want to execute runSingleTappedAction() for double-tapped event?
To achieve that, we should wait for a while before running action, since we need to confirm if it is single-tapped or double-tapped.
Example shown below:
local tapCount=0
local timeId = nil
function runTapAction( event )
 if ( tapCount == 1 ) then
     print( "single-tapped" )   
     runSingleTappedAction() 
 elseif ( tapCount == 2 ) then
     print( "double-tapped" )
     runDoubleTappedAction()
 end
end

local function buttonListener( event )
 tapCount = event.numTaps
 if(timeId ~= nil) then
   timer.cancel( timeId )
 end
 timeId = timer.performWithDelay( 200, runTapAction,1 )
end

local myButton = display.newRect( 160, 240, 120, 60 )
myButton:addEventListener( "tap", buttonListener )
Of course, we will sacrifice the response time for single-tapped event.

[Corona SDK] 如何偵測在螢幕雙擊?

如果要偵測使用者是否有觸控到螢幕,
使用Tap DetectionTouch Detection都可以,
但如果想偵測雙擊的動作,那使用tap detection會簡單些,
範例如下:
local function buttonListener( event )

    if ( event.numTaps == 1 ) then
        print( "single-tapped" )
        runSingleTappedAction()
    elseif ( event.numTaps == 2 ) then
        print( "double-tapped" )
        runDoubleTappedAction()
    else
        return true
    end
end

local myButton = display.newRect( 160, 240, 120, 60 )
myButton:addEventListener( "tap", buttonListener )
雙擊時,一定是先發生單擊事件,然後才是雙擊事件,
也就是說,會執行runSingleTappedAction(),然後才是runDoubleTappedAction(),
如果雙擊時,不想執行runSingleTappedAction(),
那就必需等一段時間確認是單擊或雙擊再執行,
如以下用法:
local tapCount=0
local timeId = nil
function runTapAction( event )
 if ( tapCount == 1 ) then
     print( "single-tapped" )   
     runSingleTappedAction() 
 elseif ( tapCount == 2 ) then
     print( "double-tapped" )
     runDoubleTappedAction()
 end
end

local function buttonListener( event )
 tapCount = event.numTaps
 if(timeId ~= nil) then
   timer.cancel( timeId )
 end
 timeId = timer.performWithDelay( 200, runTapAction,1 )
end

local myButton = display.newRect( 160, 240, 120, 60 )
myButton:addEventListener( "tap", buttonListener )
當然,以上的作法會犠牲單擊的反應速度

2015年4月23日 星期四

[Corona SDK] How to call those functions in other .lua files?

Corona SDK APIs are classified according to their purposes, such as display.*,  os.*, and so on.
If we have functions with similar purposes, we can also put them in the same .lua file.
How can we call them after that?

Assume that we have one file named GameScenario.lua, which is used to configure the setting for each round:
gameScenario = {}

local function configureGameScenario(round)
 print("configureGameScenario():"..round)
end--local function configureGameScenario()

gameScenario.configureGameScenario = configureGameScenario
return gameScenario
configureGameScenario() will decide the setting according to the input parameter round.

To call it from other file:
local gameScenario = require("GameScenario")
gameScenario.configureGameScenario(1)

The above method is to utilize the powerful table of lua.

[Corona SDK] 如何呼叫其它lua檔案的function?

Corona SDK APIs本身就是根據功能的不同分類,
例如display.* , os.* 等等,
我們如果有類似功能的function,也可以將它們擺在同一個lua檔,
那要如何從其它檔案呼叫它們呢?

假設我們有一個檔案GameScenario.lua,用來專門設定遊戲關卡的佈局:
gameScenario = {}

local function configureGameScenario(round)
 print("configureGameScenario():"..round)
end--local function configureGameScenario()

gameScenario.configureGameScenario = configureGameScenario
return gameScenario
configureGameScenario()會根據傳進來的參數round來決定如何設置,
你可以從其它檔案呼叫它:
local gameScenario = require("GameScenario")
gameScenario.configureGameScenario(1)
上面的用法就是利用lua強大的table功能

2015年4月22日 星期三

[Corona SDK] How to load or restart current scene

If we want to change the scene, we can call composer.gotoScene( xxx ).
However, what if we want to reload current scene?
Of course, we can call composer.gotoScene( xxx ), where "xxx" is the name of current scene.

However, you will find that scene:create() is not called.
Only scene:show() is exceuted.
You may move the codes in scene:create() to scene:show() if you want.

Are there any other ways?
We could use storyboard.reloadScene() before.
However, this method is obsolete.
What should we do now?

The simple way is to use a dummy scene.
We can change to the dummy scene and then change back immediately.

local options ={
 effect = "fade",
 time = 0,
 params =
 {
  myData = 1234
 }
}
composer.gotoScene( "RestartDummy", options )
The parameter time can set to be 0 which means that we want to change immediately.
The params are those parameters we want to pass to "RestartDummy".
The codes of "RestartDummy":
local composer = require( "composer" )
local scene = composer.newScene()

function scene:create( event ) 
 print( "((create scene RestartDummy's view))" ) 
 local params = event.params
 print(params.myData)
end --function scene:create( event )

function scene:show( event ) 
 local phase = event.phase 
 if "did" == phase then 
  print( "((show scene RestartDummy's view))" ) 
  composer.removeScene( xxx ) 
  composer.gotoScene( "xxx", "fade", 0 )  
 end 
end

function scene:hide( event ) 
 local phase = event.phase 
 if "will" == phase then 
  print( "((hiding scene RestartDummy's view))" )  
 end 
end

function scene:destroy( event )
 print( "((destroying scene RestartDummy's view))" )
end


-- Listener setup
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )

return scene

[Corona SDK] 如何重新載目前的scene

如果要改變場景,我們可以呼叫composer.gotoScene( xxx )
但,如果是要重新載入目前的場景呢?
我們一樣可以呼叫composer.gotoScene( xxx ),其中xxx只要傳入目前場景的名稱即可,

不過,你會發現重新載入時,
scene:create()並沒有被呼叫到,
而是直接跳到scene:show()
當然,你可以把scene:create()應該做的事移到scene:show()內去,
但這樣會變成scene:create()其實是多餘的,

之前可以透過storyboard.reloadScene()來重新載入,
目前新的Corona SDK已經不支援了,
要如何處理呢?

有一個簡單的方法,就是建一個暫時的轉換場景,
先呼叫它,然後再轉換回來:
local options =
{
 effect = "fade",
 time = 0,
 params =
 {
  myData = 1234
 }
}
composer.gotoScene( "RestartDummy", options )
參數time可以設為0,表示立刻轉換,
params是我們想要傳給"RestartDummy"的參數,
"RestartDummy.lua"部份:
local composer = require( "composer" )
local scene = composer.newScene()

function scene:create( event ) 
 print( "((create scene RestartDummy's view))" )
 local params = event.params
 print(params.myData)
end --function scene:create( event )

function scene:show( event ) 
 local phase = event.phase 
 if "did" == phase then 
  print( "((show scene RestartDummy's view))" ) 
  composer.removeScene( xxx ) 
  composer.gotoScene( "xxx", "fade", 0 )  
 end 
end

function scene:hide( event ) 
 local phase = event.phase 
 if "will" == phase then 
  print( "((hiding scene RestartDummy's view))" )  
 end 
end

function scene:destroy( event )
 print( "((destroying scene RestartDummy's view))" )
end


-- Listener setup
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )

return scene

2015年4月21日 星期二

[Corona SDK] How can we change the size and color of button text?

If we want to add a button, we can use the newButton funtion provided by widget, as the example shown below:
local widget = require( "widget" )
local button1Press = function( event )
 print("button1 pressed")
end

local button1 = widget.newButton
{
 defaultFile = "buttonYellow.png",
 overFile = "buttonYellowOver.png",
 label = "botton 1",     
 onPress = button1Press,
}
defaultFile is to set the default picture,
overFile is to get the picture when use press this button.
The text on button is "botton 1".
However, its size and color are as default.
If we want to change them, we can reference the following codes:
local widget = require( "widget" )
local button1Press = function( event )
  print("button1 pressed")
end

local button1 = widget.newButton
{
  defaultFile = "buttonYellow.png",
  overFile = "buttonYellowOver.png",
  label = "botton 1", 
  font = native.systemFont,
  fontSize = 20,
  emboss = true,
  labelColor = { default = { 0.5, 1, 1 }, over = { 0.5, 0.5, 0.5} },    
  onPress = button1Press,
}

[Corona SDK] 如何設定button的字型大小和顏色?

要增加一個button,只要利用widgetnewButton即可,
如下面範例:
local widget = require( "widget" )
local button1Press = function( event )
 print("button1 pressed")
end

local button1 = widget.newButton
{
 defaultFile = "buttonYellow.png",
 overFile = "buttonYellowOver.png",
 label = "botton 1",     
 onPress = button1Press,
}
defaultFile是用來設定預設的按鈕圖案,
overFile是用來設定使用者按下按鈕時按鈕的顯示圖案,
按鈕上的文字是"botton 1", 可是它的大小和顏色是預設的,
如果想要改變,可以參考以下範例:
local widget = require( "widget" )
local button1Press = function( event )
  print("button1 pressed")
end

local button1 = widget.newButton
{
  defaultFile = "buttonYellow.png",
  overFile = "buttonYellowOver.png",
  label = "botton 1", 
  font = native.systemFont,
  fontSize = 20,
  emboss = true,
  labelColor = { default = { 0.5, 1, 1 }, over = { 0.5, 0.5, 0.5} },    
  onPress = button1Press,
}

2015年4月16日 星期四

[Corona SDK] How to get the files list of some directory?

If we want to get the list of all files in a directory, we can use the LuaFileSystem.
It is convenient for us to show the pictures, especially when we are developing the APP for showing album.
Example:
dir = "/myDir"
for file in lfs.dir(dir) do
    if (file ~= '.' and file ~= '..') then
        print(file)
    end
end
The files and directories will be all listed.
The example below can show the list of all files and directories in current directory, and all files and directories of its sub-directory.
dir = "."
for file in lfs.dir(dir) do
    if (file ~= '.' and file ~= '..') then
     print(file)
        if(lfs.attributes(file,"mode") == "directory") then        
         for subFile in lfs.dir(file) do
          if (subFile ~= '.' and subFile ~= '..') then
          print(file.."/"..subFile)
          end
         end
        end
    end
end

[Corona SDK] 如何取得某一目錄下的所有檔案列表

如果想要取得某一目錄下的所有檔案列表,
只要利用LuaFileSystem即可,
對於要秀圖時,例如相片瀏覧程式,
這樣的功能很方便,
範例如下:
dir = "/myDir"
for file in lfs.dir(dir) do
    if (file ~= '.' and file ~= '..') then
        print(file)
    end
end
目錄或檔案都會列出,
下面的範例是列出目前目錄下的所有檔案和目錄,以及子目錄下的所有檔案列表:
dir = "."
for file in lfs.dir(dir) do
    if (file ~= '.' and file ~= '..') then
     print(file)
        if(lfs.attributes(file,"mode") == "directory") then        
         for subFile in lfs.dir(file) do
          if (subFile ~= '.' and subFile ~= '..') then
          print(file.."/"..subFile)
          end
         end
        end
    end
end

2015年4月13日 星期一

[Corona SDK] How to deal with collision of two objects with irregular shapes using physics.addBody()?

Below is a simple example for physical collision:
local physics = require( "physics" )
physics.start()

local sky = display.newImage( "bkg_clouds.png", 160, 195 )

local ground = display.newImage( "ground.png", 160, 445 )

physics.addBody( ground, "static", { friction=0.5, bounce=0.3 } )

local crate = display.newImage( "crate.png", 180, -50 )
crate.rotation = 5

physics.addBody( crate, { density=3.0, friction=0.5, bounce=0.3 } )
The pictures used are shown below:
bkg_clouds.png

crate.png

ground.png
The result after collision:

However, in real world, most objects are not in regular shape.
Let us modify crate.png and gound.png as below:
crate.png

ground.png
Check the result again:
It seems that the collision is as the result of before changing.
Why?

Actually, we can specify the shape of object in physics.addBody(), such as rectangular body, circular body, polygonal body, and so on.
Here we use the outline of the original object got from graphics.newOutline().
The codes are modified as below:
local physics = require( "physics" )
physics.start()

local sky = display.newImage( "bkg_clouds.png", 160, 195 )

local ground = display.newImage( "ground.png", 160, 445 )
local ground_outline = graphics.newOutline( 2, "ground.png" )
physics.addBody( ground, "static", { friction=0.5, bounce=0.3,outline=ground_outline } )


local crate = display.newImage( "crate.png", 180, -50 )
crate.rotation = 5
local image_outline = graphics.newOutline( 2, "crate.png" )
physics.addBody( crate, { density=0.2, friction=0.5, bounce=0.3,outline=image_outline} )
The first parameter of graphics.newOutline() is to specify the coarseness of the outline.
Let's check the result again:

[Corona SDK] 如何利用physics.addBody() 參數裡outline來達成不規則物件的碰撞

底下是一個簡單的物理碰撞範例:
local physics = require( "physics" )
physics.start()

local sky = display.newImage( "bkg_clouds.png", 160, 195 )

local ground = display.newImage( "ground.png", 160, 445 )

physics.addBody( ground, "static", { friction=0.5, bounce=0.3 } )

local crate = display.newImage( "crate.png", 180, -50 )
crate.rotation = 5

physics.addBody( crate, { density=3.0, friction=0.5, bounce=0.3 } )
其中,用到的圖形如下:
bkg_clouds.png

crate.png

ground.png
碰撞之後結果如下:
不過,現實世界裡,很多物件不是如此方方正正,
假設我們把crate.pnggound.png修改如下:
crate.png

ground.png
再跑一次看看,執行結果如下:
碰撞的過程和結果都我們修改後的圖形無關,
執行仍是按照原來矩形形狀的碰撞結果???

其實在physics.addBody()參數裡,我們可以指定物體的形狀,
參數可以是矩形,圓形或多角形等等,
在這裡,我們可以直接用原來圖形的形狀來當參數,
也就是借用graphics.newOutline()取得的外觀,
修改程式如下:
local physics = require( "physics" )
physics.start()

local sky = display.newImage( "bkg_clouds.png", 160, 195 )

local ground = display.newImage( "ground.png", 160, 445 )
local ground_outline = graphics.newOutline( 2, "ground.png" )
physics.addBody( ground, "static", { friction=0.5, bounce=0.3,outline=ground_outline } )


local crate = display.newImage( "crate.png", 180, -50 )
crate.rotation = 5
local image_outline = graphics.newOutline( 2, "crate.png" )
physics.addBody( crate, { density=0.2, friction=0.5, bounce=0.3,outline=image_outline} )
graphics.newOutline()裡第一個參數是用來設定外觀的精細度,
再看一次執行結果:

2015年4月9日 星期四

[Corona SDK] How to change scene using Composer?

In general, there will have many menus with different purposes in our APP, such as setting menu, game screen, instruction menu, and so on.
We can configure one menu with one "xx.lua", and then use Composer to change the menu (or scene).

The Template of Scene
In each scene file, there will have 4 basic functions:
local composer = require( "composer" )
local scene = composer.newScene()

---------------------------------------------------------------------------------

function scene:create( event )
 local sceneGroup = self.view

 -- Called when the scene's view does not exist.
 -- 
 -- INSERT code here to initialize the scene
 -- e.g. add display objects to 'sceneGroup', add touch listeners, etc.
end

function scene:show( event )
 local sceneGroup = self.view
 local phase = event.phase
 
 if phase == "will" then
  -- Called when the scene is still off screen and is about to move on screen
 elseif phase == "did" then
  -- Called when the scene is now on screen
  -- 
  -- INSERT code here to make the scene come alive
  -- e.g. start timers, begin animation, play audio, etc.
 end 
end

function scene:hide( event )
 local sceneGroup = self.view
 local phase = event.phase
 
 if event.phase == "will" then
  -- Called when the scene is on screen and is about to move off screen
  --
  -- INSERT code here to pause the scene
  -- e.g. stop timers, stop animation, unload sounds, etc.)
 elseif phase == "did" then
  -- Called when the scene is now off screen
 end 
end


function scene:destroy( event )
 local sceneGroup = self.view
 
 -- Called prior to the removal of scene's "view" (sceneGroup)
 -- 
 -- INSERT code here to cleanup the scene
 -- e.g. remove display objects, remove touch listeners, save state, etc.
end

---------------------------------------------------------------------------------

-- Listener setup
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )

---------------------------------------------------------------------------------

return scene
create() is the first function being called when we change to this scene.
We can add display objects such as button or text here, and their related event functions.
show() is the next function being executed where we can add action here, such as playing sound.
hide() will be executed when we are leaving the scene.
destroy() is called if this scene is removed.

main.lua
We can also add display objects in main.lua.
Those objects added here will appear on all scenes.
We can take them as global objects.
-- hide device status bar
display.setStatusBar( display.HiddenStatusBar )

-- require controller module
local composer = require "composer"

local widget = require "widget"

local function runTab1(event )
 composer.gotoScene( "scene1", "fade", 400 )
end

local function runTab2(event )
 composer.gotoScene( "scene2", "fade", 400 )
end

local widget = require( "widget" )

 -- Create buttons table for the tabBar
 local tabButtons = 
 {
  {
   label = "tab1",
   id = "tab1",
   size = 40,
   onPress = runTab1, 
   selected = true
  },
  {
   label = "tab2",
   id = "tab2",
   size = 40,
    onPress = runTab2
  }
 }

 -- Create tabBar at bottom of the screen
 local tabBar = widget.newTabBar
 {
  buttons = tabButtons
 }
 tabBar.y = display.contentHeight - (tabBar.height/2) 
 print(display.contentHeight)
 runTab1()
We add a tabbar here.
No matter in which scene, we can change scene using this tabbar.
composer.gotoScene( "scene1", "fade", 400 ) mean that we want to change to "scene1".
There will have one file named "scene1.lua" for it.

scene1.lua
local composer = require( "composer" )
local scene1 = composer.newScene()
local widget = require "widget"
---------------------------------------------------------------------------------
-- BEGINNING OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------

-- Function to handle button events
local function handleButtonEvent( event )

    if ( "ended" == event.phase ) then
        composer.gotoScene( "scene2", "fade", 400 )
    end
end

-- Called when the scene's view does not exist:
function scene1:create( event )
 local sceneGroup = self.view
 text1 = display.newText( "Scene 1", 0, 0, native.systemFontBold, 24 )
 text1:setFillColor( 255 )
 text1.x, text1.y = display.contentWidth * 0.5, 50
 sceneGroup:insert( text1 )
 -- Create the widget
 local button1 = widget.newButton
 {
     left = 100,
     top = 320,
     id = "button1",
     label = "Goto Scene 2",
     onEvent = handleButtonEvent
 } 
 sceneGroup:insert( button1 )
 print( "\n1: create event")
end

function scene1:show( event ) 
 local phase = event.phase 
 if "did" == phase then 
  print( "1: show event, phase did" )   
  composer.removeScene( "scene2" ) 
 end 
end

function scene1:hide( event ) 
 local phase = event.phase 
 if "will" == phase then 
  print( "1: hide event, phase will" ) 
 end 
end

function scene1:destroy( event )
 print( "1: destroying scene 1's view" )
end

---------------------------------------------------------------------------------

-- Listener setup
scene1:addEventListener( "create", scene1 )
scene1:addEventListener( "show", scene1 )
scene1:addEventListener( "hide", scene1 )
scene1:addEventListener( "destroy", scene1 )

---------------------------------------------------------------------------------

return scene1
In scene1, we add a button. We can change to scene2 by this button, except for using tabbar.

scene2.lua
local composer = require( "composer" )
local scene2 = composer.newScene()
local widget = require "widget"

-- Function to handle button events
local function handleButtonEvent( event )

    if ( "ended" == event.phase ) then        
        composer.gotoScene( "scene1", "fade", 400 )
    end
end

function scene2:create( event )
 local sceneGroup = self.view
 text1 = display.newText( "Scene 2", 0, 0, native.systemFontBold, 24 )
 text1:setFillColor( 255 )
 text1.x, text1.y = display.contentWidth * 0.5, 50
 sceneGroup:insert( text1 )
 local button1 = widget.newButton
 {
     left = 100,
     top = 320,
     id = "button1",
     label = "Goto Scene 1",
     onEvent = handleButtonEvent
 } 
 sceneGroup:insert( button1 ) 
 print( "\n2: create event" )
end

function scene2:show( event ) 
 local phase = event.phase 
 if "did" == phase then 
  print( "2: show event, phase did" )
  composer.removeScene( "scene1" ) 
 end
 
end

function scene2:hide( event ) 
 local phase = event.phase
 if "will" == phase then 
  print( "2: hide event, phase will" )
 end
end

function scene2:destroy( event ) 
 print( "2: destroying scene 2's view" )
end

---------------------------------------------------------------------------------

-- Listener setup
scene2:addEventListener( "create", scene2 )
scene2:addEventListener( "show", scene2 )
scene2:addEventListener( "hide", scene2 )
scene2:addEventListener( "destroy", scene2 )

---------------------------------------------------------------------------------

return scene2
scene2 is similar with scene1.
When we call composer.gotoScene( "scene2", "fade", 400 ) in scene1:show() function,
the functions being called will be:
scene1:hide() => scene2:create() => scene2:show()
In scene2:show(), we run composer.removeScene( "scene1" ), the function scene1:destroy() will be called then.

[Corona SDK] 如何利用Composer來切換場景

我們的程式通常會有幾個不同功能的畫面,
例如,設定相關畫面..遊戲畫面..說明畫面等等,
我們可以把同一個畫面相關程式放在同一個"xx.lua"檔裡,
然後利用Composer來切換不同的畫面(場景Scene),

The Template of Scene
每個Scene檔裡,都會有4個基本的function
local composer = require( "composer" )
local scene = composer.newScene()

---------------------------------------------------------------------------------

function scene:create( event )
 local sceneGroup = self.view

 -- Called when the scene's view does not exist.
 -- 
 -- INSERT code here to initialize the scene
 -- e.g. add display objects to 'sceneGroup', add touch listeners, etc.
end

function scene:show( event )
 local sceneGroup = self.view
 local phase = event.phase
 
 if phase == "will" then
  -- Called when the scene is still off screen and is about to move on screen
 elseif phase == "did" then
  -- Called when the scene is now on screen
  -- 
  -- INSERT code here to make the scene come alive
  -- e.g. start timers, begin animation, play audio, etc.
 end 
end

function scene:hide( event )
 local sceneGroup = self.view
 local phase = event.phase
 
 if event.phase == "will" then
  -- Called when the scene is on screen and is about to move off screen
  --
  -- INSERT code here to pause the scene
  -- e.g. stop timers, stop animation, unload sounds, etc.)
 elseif phase == "did" then
  -- Called when the scene is now off screen
 end 
end


function scene:destroy( event )
 local sceneGroup = self.view
 
 -- Called prior to the removal of scene's "view" (sceneGroup)
 -- 
 -- INSERT code here to cleanup the scene
 -- e.g. remove display objects, remove touch listeners, save state, etc.
end

---------------------------------------------------------------------------------

-- Listener setup
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )

---------------------------------------------------------------------------------

return scene
create()是切換到此場景時先被呼叫的地方,我們可以這裡加入button等場景需要的物件以及相對應的function,
show()是場景要開始真正運作的進入點,我們在這裡加入需要的執行動作,例如播放音樂等等,
hide()是場景要被切換時會呼叫,也就是要離開場景時,
destroy()是場景被remove時會呼叫

main.lua
我們也可以在main.lua裡加入display相關物件,
在這裡加入的物件,不管如何切換場景,它們都會出現在畫面上,
可以視它們為global objects
-- hide device status bar
display.setStatusBar( display.HiddenStatusBar )

-- require controller module
local composer = require "composer"

local widget = require "widget"

local function runTab1(event )
 composer.gotoScene( "scene1", "fade", 400 )
end

local function runTab2(event )
 composer.gotoScene( "scene2", "fade", 400 )
end

local widget = require( "widget" )

 -- Create buttons table for the tabBar
 local tabButtons = 
 {
  {
   label = "tab1",
   id = "tab1",
   size = 40,
   onPress = runTab1, 
   selected = true
  },
  {
   label = "tab2",
   id = "tab2",
   size = 40,
    onPress = runTab2
  }
 }

 -- Create tabBar at bottom of the screen
 local tabBar = widget.newTabBar
 {
  buttons = tabButtons
 }
 tabBar.y = display.contentHeight - (tabBar.height/2) 
 print(display.contentHeight)
 runTab1()
在這裡我們加入一個tabbar,
不管在那個場景,我們都可以這個tabbar切換場景,
composer.gotoScene( "scene1", "fade", 400 )表示要切到"scene1", 它需要有一個對應的"scene1.lua"

scene1.lua
local composer = require( "composer" )
local scene = composer.newScene()
local widget = require "widget"
---------------------------------------------------------------------------------
-- BEGINNING OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------

local image, text1, text2, text3, memTimer

-- Function to handle button events
local function handleButtonEvent( event )

    if ( "ended" == event.phase ) then
        composer.gotoScene( "scene2", "fade", 400 )
    end
end

-- Called when the scene's view does not exist:
function scene:create( event )
 local sceneGroup = self.view
 text1 = display.newText( "Scene 1", 0, 0, native.systemFontBold, 24 )
 text1:setFillColor( 255 )
 text1.x, text1.y = display.contentWidth * 0.5, 50
 sceneGroup:insert( text1 )
 -- Create the widget
 local button1 = widget.newButton
 {
     left = 100,
     top = 320,
     id = "button1",
     label = "Goto Scene 2",
     onEvent = handleButtonEvent
 } 
 sceneGroup:insert( button1 )
 print( "\n1: create event")
end

function scene:show( event ) 
 local phase = event.phase 
 if "did" == phase then 
  print( "1: show event, phase did" )   
  composer.removeScene( "scene2" ) 
 end 
end

function scene:hide( event ) 
 local phase = event.phase 
 if "will" == phase then 
  print( "1: hide event, phase will" ) 
 end 
end

function scene:destroy( event )
 print( "((destroying scene 1's view))" )
end

---------------------------------------------------------------------------------

-- Listener setup
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )

---------------------------------------------------------------------------------

return scene
在scene1裡我們加入了一個button,除了用原本的tabbar, 我們也可以利這個button來切換到scene2

scene2.lua
local composer = require( "composer" )
local scene = composer.newScene()
local widget = require "widget"
local image, text1, text2, text3, memTimer
-- Function to handle button events
local function handleButtonEvent( event )

    if ( "ended" == event.phase ) then        
        composer.gotoScene( "scene1", "fade", 400 )
    end
end

function scene:create( event )
 local sceneGroup = self.view
 text1 = display.newText( "Scene 2", 0, 0, native.systemFontBold, 24 )
 text1:setFillColor( 255 )
 text1.x, text1.y = display.contentWidth * 0.5, 50
 sceneGroup:insert( text1 )
 local button1 = widget.newButton
 {
     left = 100,
     top = 320,
     id = "button1",
     label = "Goto Scene 1",
     onEvent = handleButtonEvent
 } 
 sceneGroup:insert( button1 ) 
 print( "\n2: create event" )
end

function scene:show( event ) 
 local phase = event.phase 
 if "did" == phase then 
  print( "2: show event, phase did" )
  composer.removeScene( "scene1" ) 
 end
 
end

function scene:hide( event ) 
 local phase = event.phase
 if "will" == phase then 
  print( "2: hide event, phase will" )
 end
end

function scene:destroy( event ) 
 print( "2: destroying scene 2's view" )
end

---------------------------------------------------------------------------------

-- Listener setup
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )

---------------------------------------------------------------------------------

return scene
scene2和scene1類似,
當我們呼叫composer.gotoScene( "scene2", "fade", 400 )時,
scene1的hide()會先被呼叫,
然後是scene2的create(),再來是scene2的show()
在scene2的show()裡面我們執行composer.removeScene( "scene1" )
此時scene1的destroy()會被呼叫

2015年4月8日 星期三

[Corona SDK] How to get local IP and Public Internet IP?

If we access the Internet through some AP, we should get a local IP provided by the AP.
To get the local IP, we can use the method shown below:
local getIP = function()
    local s = socket.connect("www.google.com", 80)--need to have internet connection
    local ip, _ = s:getsockname()
    print( "myIP:", ip)
    text1 = display.newText( "Local IP:"..ip, display.contentCenterX, 40, native.systemFontBold, 24 )
    text1:setFillColor( 1,1,0 )
    return ip
end

getIP()
The method mentioned above need to performed with Internet connection available.
It just connects to some open website, such as "www.google.com" in our example.
You can connect to other website if you want.

For local IP, we may want to get such information without the Internet connection.
To achieve this, we can use the following way:
local getIP = function()
    local s = socket.udp()  --creates a UDP object
    s:setpeername( "1.1.1.1", 80 )--No need to have internet connection
    local ip, _ = s:getsockname()
    print( "myIP:", ip)
    text1 = display.newText( "Local IP:"..ip, display.contentCenterX, 0, native.systemFontBold, 24 )
    text1:setFillColor( 1,1,0 )
    return ip
end

getIP()
We establish a UDP connection and then we can get our own local IP.
The target "1.1.1.1" can be any IP.
It doesn't matter since we don't really transfer any data.

What if we want to get the public Internet IP of our device?
We can get it through some website which can provide such information.
function findPublicIPAddress()
    
    local getipScriptURL = "http://myip.dnsomatic.com/"
    local DeviceIP
    
    function ipListener(event)
        if not event.isError and event.response ~= "" then
            DeviceIP = event.response 
            print("Public DeviceIP:"..DeviceIP)
            text1 = display.newText( "Public IP:"..DeviceIP, display.contentCenterX, 80, native.systemFontBold, 24 )
            text1:setFillColor( 0,1,1 )
        end
    end
    
    network.request(getipScriptURL,"GET",ipListener) 
    
end
 
findPublicIPAddress()
Of course, we should have Internet connection for running above example.

[Corona SDK] 如何取得local IP和Public Internet IP?

如果我們是透過某個AP連上網路,
AP會給你的裝置一個local IP,
要取得這個IP,可利用下面方式:
local getIP = function()
    local s = socket.connect("www.google.com", 80)--need to have internet connection
    local ip, _ = s:getsockname()
    print( "myIP:", ip)
    text1 = display.newText( "Local IP:"..ip, display.contentCenterX, 40, native.systemFontBold, 24 )
    text1:setFillColor( 1,1,0 )
    return ip
end

getIP()
上面的方式需要在有連網的情形下執行,
它是連到某個開放網站,例如"www.google.com"
當然,你也可以連到其它網站,

因為是取得local IP,有時會希望在沒有連上網路時也能取得這樣的資訊,
此時可使用下面的方式:
local getIP = function()
    local s = socket.udp()  --creates a UDP object
    s:setpeername( "1.1.1.1", 80 )--No need to have internet connection
    local ip, _ = s:getsockname()
    print( "myIP:", ip)
    text1 = display.newText( "Local IP:"..ip, display.contentCenterX, 0, native.systemFontBold, 24 )
    text1:setFillColor( 1,1,0 )
    return ip
end

getIP()
我們建立一個UDP連線,透過這樣的方式來取得自己的IP,
例子中,"1.1.1.1"可以是任何IP,
因為我們並沒有真的要傳送資料,

那...如果要取得真正對外的網路IP呢?
此時我們可利用一些提供這種服務的網站,
例如以下的方式:
function findPublicIPAddress()
    
    local getipScriptURL = "http://myip.dnsomatic.com/"
    local DeviceIP
    
    function ipListener(event)
        if not event.isError and event.response ~= "" then
            DeviceIP = event.response 
            print("Public DeviceIP:"..DeviceIP)
            text1 = display.newText( "Public IP:"..DeviceIP, display.contentCenterX, 80, native.systemFontBold, 24 )
            text1:setFillColor( 0,1,1 )
        end
    end
    
    network.request(getipScriptURL,"GET",ipListener) 
    
end
 
findPublicIPAddress()
當然,上面的例子必須在有連網的情形下執行

2015年4月7日 星期二

[Corona SDK] How to use widget.newTabBar to establish tab bar?

Tab is quite often used in Windows style UI.
Due to the low resolution, it is seldom used in phone APPs, especially for game APPs.
However, for tools like APPs or menu, it is still useful to use Tab style.
It can let the UI more tidy and easy to manipulate.

To establish a tab is quite simple.
We just use widget.newTabBar as demonstrated below:
display.setStatusBar( display.HiddenStatusBar )
local widget = require( "widget" )

local function runTab1(event )
 display.remove( text2 )
 text1 = display.newText( "Tab 1", 100, 100, native.systemFont, 40 )
end

local function runTab2(event )
 display.remove( text1 )
 text2 = display.newText( "Tab 2", 100, 100, native.systemFont, 40 )
end


 -- Create buttons table for the tabBar
 local tabButtons = 
 {
  {
   label = "tab1",
   id = "tab1",
   size = 40,
   onPress = runTab1, 
   selected = true
  },
  {
   label = "tab2",
   id = "tab2",
   size = 40,
   onPress = runTab2
  }
 }

 -- Create tabBar at bottom of the screen
 local tabBar = widget.newTabBar
 {
  buttons = tabButtons
 }

 tabBar.y = display.contentHeight - (tabBar.height/2) 
We need to declare require( "widget" ) if we want to use widget.
In widget.newTabBar, we just need to add the parameter buttons = tabButton.
The tab bar is built already.
In tabButtons, we can add function in onPress event to define our action when the tab bar is selected.
Below is the result:
If we want our tab to be more interesting, we can put the pictures on tab bars:
local tabButtons = 
 {
  {
   label = "tab1",
   id = "tab1",
   size = 40,
   onPress = runTab1, 
   selected = true
  },
  {
   label = "tab2",
   id = "tab2",
   size = 40,
   width = 232,
   height = 40,
         defaultFile = "1.png",
         overFile = "2.png",
   onPress = runTab2
  }
 }
In the example above, the default picture for tab 2 is 1.png
When it is selected, the picture will be 2.png.
Please note that the width and height parameters must be specified when we use pictures.
The result:

[Corona SDK] 如何使用widget.newTabBar 建立tab bar?

Tab在視窗操作中算是很實用的方式,
在手機遊戲程式裡,Tab倒是少人用,
不過,如果是有點工具類性質的操作畫面,
個人覺得用上Tab可以使畫面更加簡潔,
操作起來也方便快速...

要建立一個tab很簡單,只要使用widget.newTabBar即可,
如下面只要幾行即可:
display.setStatusBar( display.HiddenStatusBar )
local widget = require( "widget" )

local function runTab1(event )
 display.remove( text2 )
 text1 = display.newText( "Tab 1", 100, 100, native.systemFont, 40 )
end

local function runTab2(event )
 display.remove( text1 )
 text2 = display.newText( "Tab 2", 100, 100, native.systemFont, 40 )
end


 -- Create buttons table for the tabBar
 local tabButtons = 
 {
  {
   label = "tab1",
   id = "tab1",
   size = 40,
   onPress = runTab1, 
   selected = true
  },
  {
   label = "tab2",
   id = "tab2",
   size = 40,
   onPress = runTab2
  }
 }

 -- Create tabBar at bottom of the screen
 local tabBar = widget.newTabBar
 {
  buttons = tabButtons
 }

 tabBar.y = display.contentHeight - (tabBar.height/2) 
要使用widget,需要宣告require( "widget" )
widget.newTabBar裡帶入buttons = tabButton即可建立tab bar,
上面的範例裡,我們建立二個tab 按鈕,
tabButtons裡的onPress 可帶入按下tab按鈕後想執行的動作,
下面是執行結果:
另外,如果希望tab按鈕可以多一個變化,
我們也可以把圖形放在上面,例如:
local tabButtons = 
 {
  {
   label = "tab1",
   id = "tab1",
   size = 40,
   onPress = runTab1, 
   selected = true
  },
  {
   label = "tab2",
   id = "tab2",
   size = 40,
   width = 232,
   height = 40,
         defaultFile = "1.png",
         overFile = "2.png",
   onPress = runTab2
  }
 }
上面的例子裡,tab2預設的圖形是1.png,選到時的圖形是2.png
需注意此時widthheight的參數必須指定,
執行如果如下:

2015年4月6日 星期一

[Android] Why the sound is not playing smoothly when using AudioTrack()?

When we use AudioTrack(), if the tone is too high or too low, the problem may be caused by wrong format or wrong sample length.

What if the sound is not playing smoothly?
Especilly when we encounter the error message similar with the following:
W/AudioTrack(..): releaseBuffer() track 0x6eaf04d0 name=0x1 disabled, restarting
How should we do?

In general, it means that we feed the data too slowly.
For local file, it should not have such problem.
But for streaming source, the input data rate is not constant.
We may encounter the problem of buffer underflow sometimes.

We can either add the buffer mechanism in the network part when receiving streaming data, or can we add buffer when feeding data into AudioTrack.

Let's see the demo for second method - adding buffer for AudioTrack.

private static void queueAudioData(byte[] buf, int size) {  
 if((audioPcmBufferDataCount + size)> audioBufferSize)
 {
  return;  
 }
 if((audioPcmBufferFront + size) > audioBufferSize)
 {
  //rewind
  System.arraycopy(buf, 0, audioPCMData, audioPcmBufferFront, audioBufferSize - audioPcmBufferFront);
  audioPcmBufferDataCount += (audioBufferSize - audioPcmBufferFront);
  size -= (audioBufferSize - audioPcmBufferFront);
  audioPcmBufferFront = 0;      
 }
 System.arraycopy(buf, 0, audioPCMData, audioPcmBufferFront, size);
 audioPcmBufferFront += size;
 audioPcmBufferDataCount += size;
} 

The PCM data from audio decoder is queued to the queue buffer through queueAudioData().
After that, we write the data to AudioTrack in a stable rate.
private class playAudio extends Thread {
 @Override
 public void run() {  
  super.run();
  int len = 512;
  while(!isInterrupted()) {
   try {
    if(audioPcmBufferDataCount < len)
    {
     Thread.sleep(10);
     continue;
    }

    if((audioPcmBufferEnd + len) > audioBufferSize)
     writeSize = audioBufferSize - audioPcmBufferEnd;
    else
     writeSize = len;
    playAudioTrack.write(audioPCMData, audioPcmBufferEnd, writeSize);
    Thread.sleep(1);
    audioPcmBufferDataCount -= writeSize;
    audioPcmBufferEnd += writeSize;
    if(audioPcmBufferEnd >= audioBufferSize)
     audioPcmBufferEnd=0;
    
    if(playAudioTrack.getPlayState()!=AudioTrack.PLAYSTATE_PLAYING) {     
          playAudioTrack.play();
       }
   } catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
   }       
  }
 }
}

[Android] How to use MediaExtractor and MediaCodec ? - Audio part

In [Android] How to use MediaExtractor and MediaCodec? - Video part, we learned how to deal with the video data.
Let's check the audio part here.

Basically, we can use the same MediaExtractor to deal with video and audio.
We just need to switch the track dynamically using selectTrack().
However, it seems that we often missed the data somehow.
We will use separate MediaExtractor for video and audio individually.

The process for audio data is similar with that for video.
Audio data do not need to render to SurfaceView and we need an extra AudioTrack() for decoded PCM data.
private MediaExtractor extractorAudio;
private MediaCodec decoderAudio;

extractorAudio = new MediaExtractor();
extractorAudio.setDataSource("myTest.mp4");

for (int i = 0; i < extractorAudio.getTrackCount(); i++) {
 MediaFormat format = extractorAudio.getTrackFormat(i);
 String mime = format.getString(MediaFormat.KEY_MIME); 
 if (mime.startsWith("audio/")) {  
  audioTrack = i;  
  extractorAudio.selectTrack(audioTrack);
  formatAudio = format;        
  decoderAudio = MediaCodec.createDecoderByType(mime);
  sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
  decoderAudio.configure(format, null, null, 0);
  break;
 }
}

if (audioTrack >=0) {
 if(decoderAudio == null)
 {
  Log.e(TAG, "Can't find audio info!");
  return;
 }
 else
 {
   // create our AudioTrack instance
   int minBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT);
      int bufferSize = 4 * minBufferSize;
  playAudioTrack = new AudioTrack(
    AudioManager.STREAM_MUSIC,
    formatAudio.getInteger(MediaFormat.KEY_SAMPLE_RATE),
    AudioFormat.CHANNEL_OUT_STEREO,
    AudioFormat.ENCODING_PCM_16BIT,
    bufferSize,
    AudioTrack.MODE_STREAM
   );
  playAudioTrack.play();
  decoderAudio.start();
 }
}
Similarly, extractorAudio will find out the audio track according to MIME information.
We pass in the audio format using decoderAudio.configure(). No other parameters are necessary.

The data after decoderAudio are in PCM format.
We need AudioTrack() to play out the sound actually.

Below is the decode part:
ByteBuffer[] inputBuffersAudio=null;
ByteBuffer[] outputBuffersAudio=null;
BufferInfo infoAudio=null;


if (audioTrack >=0)
{
 inputBuffersAudio = decoderAudio.getInputBuffers();
 outputBuffersAudio = decoderAudio.getOutputBuffers();
 infoAudio = new BufferInfo();
}
boolean isEOS = false;
long startMs = System.currentTimeMillis();
long lasAudioStartMs = System.currentTimeMillis();
while (!Thread.interrupted()) { 
 if (audioTrack >=0)
 { 
  if (!isEOS) {
   int inIndex=-1;
   try {
    inIndex = decoderAudio.dequeueInputBuffer(10000);
   } catch (Exception e) {
    e.printStackTrace();    
   }
   if (inIndex >= 0) {    
    ByteBuffer buffer = inputBuffersAudio[inIndex];
    int sampleSize = extractorAudio.readSampleData(buffer, 0);
    if (sampleSize < 0) {
     
     decoderAudio.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
     buffer.clear();
     isEOS = true;
    } else {     
     decoderAudio.queueInputBuffer(inIndex, 0, sampleSize, extractorAudio.getSampleTime(), 0);
     buffer.clear();
     extractorAudio.advance();
    }
    
   }
  }

  int outIndex=-1;
  try {
   outIndex = decoderAudio.dequeueOutputBuffer(infoAudio,10000);
  } catch (Exception e) {
   e.printStackTrace();   
  }

  switch (outIndex) {
  case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
   Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
   outputBuffersAudio = decoderAudio.getOutputBuffers();
   break;
  case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
   Log.d(TAG, "New format " + decoderAudio.getOutputFormat());
   playAudioTrack.setPlaybackRate(formatAudio.getInteger(MediaFormat.KEY_SAMPLE_RATE));
   break;
  case MediaCodec.INFO_TRY_AGAIN_LATER:
   Log.d(TAG, "dequeueOutputBuffer timed out!");
   break;
  default:
   if(outIndex>=0)
   {
    ByteBuffer buffer = outputBuffersAudio[outIndex];
    byte[] chunk = new byte[infoAudio.size];
    buffer.get(chunk);
    buffer.clear();
                if(chunk.length>0){         
                 playAudioTrack.write(chunk,0,chunk.length);
                }                
    decoderAudio.releaseOutputBuffer(outIndex, false);
   }
   break;
  }

  // All decoded frames have been rendered, we can stop playing now
  if ((infoAudio.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
   Log.d(TAG, "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
   break;
  }
 }
}

if (audioTrack >=0)
{
 decoderAudio.stop();
 decoderAudio.release();
 playAudioTrack.stop();
}

extractorAudio.release();

The process are similar with video part. No more explanation here.
Need to notice is that for decoderAudio.releaseOutputBuffer(outIndex, false), the second parameter need to set to false. That is, no need to render.