레이블이 corona인 게시물을 표시합니다. 모든 게시물 표시
레이블이 corona인 게시물을 표시합니다. 모든 게시물 표시

2016년 3월 27일 일요일

make game as corona sdk (Fly a rocketman)(6) - custom Font


이런 핑게 저런 핑게 대다보니 요즘 통 작업을 못진행한것 같네요.
2016 project인 만큼 빨리 마무리 될 수 있도록 힘을 내보도록 하겠습니다.

사용법은 아래에서 참고 하면됩니다.

https://docs.coronalabs.com/api/library/display/newText.html

두가지 방법으로 사용이 가능하며,
display.newText( [parentGroup,] text, x, y, font [, fontSize] )
display.newText( [parentGroup,] text, x, y [, width, height], font [, fontSize] )
display.newText( options )
font 인자는 아래의 값을 이용 할 수 있습니다.
font (required)
StringUserdata, or Constant. This can be one of the following:

오늘 주제에서는 custom font 이므로 font 를 외부에서 가져왔다고 할때
string형태로 입력을 하게됩니다.

예제입니다.
-- Standard text object
local displayText = display.newText( "Hello World", 150, 80, "CoolCustomFont.ttf", 24 )

저는 용량이 작고 멋있는걸로 "homespun.ttf" 폰트를 구했습니다.
일단 license관련해서 꼭 확인해서 사용하시기 바랍니다.

homespun.ttf
---------------
-The font(s) in this zip file were created by me (Brian Kent).  All
of my Fonts are Freeware, you can use them any way you want to
(Personal use, Commercial use, or whatever).


fota관련해서는 코드를 추가하였습니다.
화면에서 날아오는 글자라고 생각하고 구현하였습니다. 예를 들어 점수 같은게 날아올 수 있습니다.

    self.typeCreateData = {
   -- type 1
   --    name , x  , y ,xsz,ysz,xspeed,categoryBits,maskBits,etc
    {
    {"rect",-100, 30, 40, 80, -300,self.blockCategory,self.blockMask},
    {"rect", 100,-30, 40, 80, -300,self.blockCategory,self.blockMask},
    },
    -- type 2
    {
    {"rect",-100,  0, 40, 80, -300,self.blockCategory,self.blockMask},
    },
    -- type 3
    {
    {"rect",-100,  0, 40, 80, -300,self.blockCategory,self.blockMask},
    },
    -- type 4
    {
    {"font",-100,  0, 40, 80, -200,self.blockCategory,self.blockMask,24,"abcDEF123","homespun"},
    },
    }




function M:runEvent()
local y = self.data[self.currentGroupNum][self.currentPC][2]
local gtype = self.data[self.currentGroupNum][self.currentPC][3]

if(self.data[self.currentGroupNum][self.currentPC][4]=="randomType")then
gtype = random(self.data[self.currentGroupNum][self.currentPC][5],self.data[self.currentGroupNum][self.currentPC][6])
end

local firstTime = self.typeActionData[gtype][1][1]

local cdata = self.typeCreateData[gtype]
local count = #cdata
for i = 1, count, 1 do
local object
if( cdata[i][1] == "rect" ) then
object = display.newRect( self.cwidth + self.cwidth/2 + cdata[i][2], y + cdata[i][3], cdata[i][4], cdata[i][5] )
physics.addBody(object,"dynamic",{ density=1,filter={categoryBits=cdata[i][7],maskBits=cdata[i][8]} })
object:setLinearVelocity(cdata[i][6],0)
elseif( cdata[i][1] == "font" ) then
object = display.newText( cdata[i][10], self.cwidth + self.cwidth/2 , y, cdata[i][11], cdata[i][9] )
physics.addBody(object,"dynamic",{ density=1,filter={categoryBits=cdata[i][7],maskBits=cdata[i][8]} })
object:setLinearVelocity(cdata[i][6],0)
end
if( object~=nil ) then
object.name = "block"
self.objCount = self.objCount + 1
print("objs:",self.objCount)
end

if( object~=nil and firstTime >= 0 ) then
object.actionPC = 1
object.actionObjectType = gtype;
--print("fristTime",firstTime)
if( firstTime == 0 ) then
self:timerActionCB(object)
else
timer.performWithDelay(firstTime, function() self:timerActionCB(object) end)
end
end
end
end




2016년 2월 20일 토요일

make game as corona sdk (Fly a rocketman)(5) - categoryBits,maskBits


앞에서 사용된 이미지를 살펴보면 아래와 같습니다.



위 그림에서 보면 block들이 붙어서 움직일때 블럭간에 충돌이 일어나서 블럭들이 움직임이 어색해 보일수도 있습니다.
이때에 있는것이 특정 블럭간에 충돌 방지가 필요하게 됩니다.
physics.addBody 함수에서 categoryBits,maskBits parameter를 추가해서 충돌 범위를 정할 수 있습니다.

기존
physics.addBody(object,"dynamic",{ density=1 })
변경
physics.addBody(object,"dynamic",{ density=1,filter={categoryBits=2,maskBits=1} })

내용은 간단합니다.
categoryBits 는 생성하는 physics object의 카테고리 즉 분류 bit가 됩니다.
maskBits 는 충돌하게 되는 카테고리 즉 분류를 설정하는 곳입니다.

예를 들어서 3개의 object는 categoryBits=2 => 2진수로 10 이 됩니다. 그리고 충돌은 1 이 므로 3개의 객체는 충돌이 일어나지 않으므로 붙어다닐 수 있습니다.
physics.addBody(object1,"dynamic",{ density=1,filter={categoryBits=2,maskBits=1} })
physics.addBody(object2,"dynamic",{ density=1,filter={categoryBits=2,maskBits=1} })
physics.addBody(object3,"dynamic",{ density=1,filter={categoryBits=2,maskBits=1} })

player는 categoryBits가 1 이라고 한다면 player는 object1,object2,object3 과 충돌 검사를 할 수 있습니다.
기존 소스에서 해당 부분만 변경하면 충돌시 아래 그림과 같게 됩니다.









2016년 2월 14일 일요일

@2x 이미지가 동작하지(loading 되지) 않을 때 in corona sdk


아래와 같이 구현해놓고 @2x 이미지까지 모두 준비된 상태에서 적절한 이미지가 올라오지않는 경우가 있다.

local buttonArrow = widget.newButton
{
id = "arrow",
defaultFile = "btn_play.png",
overFile = "img/btn_play_pushed.png",
onEvent = buttonHandler,
}

buttonArrow.x = CWidth/2; buttonArrow.y = CHeight/2
sceneGroup:insert(buttonArrow)


@2x 이미지를 사용하는 방법에 대해서는 여기 설명 참고
https://coronalabs.com/blog/2013/09/10/modernizing-the-config-lua/

번역된 예제 
원본 예제

동일 질문
https://forums.coronalabs.com/topic/45519-dinamic-sprite-images-for-widgetnewbutton/
아무도 답변은 없다...
이것 때문에 오랬동안 삽질했다.








해결책

버튼의 크기를 꼭 기록 해야합니다.

local buttonArrow = widget.newButton
{
id = "arrow",
        width = 220, -- this defines the width of the button
        height = 100, -- this defines the height of the button 
defaultFile = "btn_play.png",
overFile = "img/btn_play_pushed.png",
onEvent = buttonHandler,
}

이런 예제라면 이미지 로딩에 크기정보를 꼭 기록 하기 바랍니다.
local bg = display.newImageRect("splash.png",480,320)

2016년 2월 12일 금요일

bit opration(비트연산) in corona sdk, Lua

bit 연산 때문에 여기저기를 찾아봤습니다.

Lua

Lua 의 낮은 버전에서는 원칙적으로 지원하지 않습니다. 5.2부터 지원한다고 합니다.
http://lua-users.org/wiki/BitwiseOperators
http://www.lua.org/manual/5.2/manual.html#6.7 <-여기 참고 하면됩니다.
> print(bit32.bor(3,1))
3
> print(bit32.band(3,1))
1
5.1이하 버전에서 사용하려면 라이브러리를 사용하면 됩니다.

Corona SDK

Corona SDK의 경우 Lua 5.1 기반으로 되어있기 때문에 지원하지 않습니다.
따라서 다른 라이브러리를 사용하면되는데 Corona SDK에서 사용하려면 native 언어로 된것은 일반 사용자가 사용이 안되므로 Lua 언어로된 library를 사용해야합니다.

적절한것은 아래 나온 링크들입니다.

Pure Lua implementations:

그중 [bit.numberlua] 에 사용해 보았습니다.
https://github.com/davidm/lua-bit-numberlua/ 여기에서 받을 수 있습니다.
https://github.com/davidm/lua-bit-numberlua/blob/master/lmod/bit/numberlua.lua 여기에 있는 lua 파일을 다운로드 받아서 자신의 project에 넣고 다음과 같이 사용하면 됩니다.

local bit = require 'numberlua'
print(bit.bor(1,2,3))


make game as corona sdk (Fly a rocketman)(4) - performWithDelay

이번에 작업할 내용은 이벤트가 발생하게 되면 하나의 object를 생성하는게 아니라 그룹형태로 생성 할 목록을 지니도록 할 예정입니다.

지난번에 group 0 이면 특정 객체를 생성하게 구현하였는데 group 번호에 따라 다른 동작이 이루어 지도록 구현 하였습니다.

데이터의 예제가 아래와 같습니다.
self.data 안에 있는 마지막에 있는 type 이 1이면 생성할때는 self.typeCreateData 를 이용하여 rect 2개를 생성하고,
-- type 1
{
{"rect",-100, 30},
{"rect", 100,-30},
일정 시간 후에 동작하는 움직임 제어를 위해서는,
self.typeActionData 자료를 참고하여 구현하였습니다.
self.typeActionData = {
-- type 1
{
--time, action, param
{700,"move_y",-100},
{500,"move_y",200},

아래는 전체 data 입니다.


self.data = {
  -- group 1
  {
  -- tick, y, type
   {30,100,1},
   {30,200,2},
   {30,300,1}
  },
  -- group 2
  {
  -- tick, y, type
   {30,100,1},
   {30,200,2},
   {0 ,100,1}, -- todo
   {0 ,150,1}, -- todo
   {0 ,200,1}, -- todo
   {0 ,250,1}, -- todo
   {30,300,1}
  },
    }

    self.typeActionData = {
     -- type 1
     {
      --time, action, param
      {700,"move_y",-100},
      {500,"move_y",200},
     },
     -- type 2
     {
      --tick, action, param
      -- -1 is nothing
      {-1},
     },
    }

    self.typeCreateData = {
     -- type 1
     {
      {"rect",-100, 30},
      {"rect", 100,-30},
     },
     -- type 2
     {
      {"rect",-100,  0},
     },
    }


이전시간에 tick을 이용하여 self.data 를 호출 하도록 구현하였습니다.
self.typeActionData 의 경우 일정 시간 후 동작해야 하므로 timer를 이용하였습니다.
corona sdk에서는 performWithDelay 함수가 준비되어 있습니다.

해당 함수를 사용하면서 주의해야 할것은 listener가 parameter 가 존재하는 경우가 있는 경우 처리를 제대로 해줘야합니다.

https://docs.coronalabs.com/api/library/timer/performWithDelay.html

즉, 비슷하면서도 헷갈리는 동작입니다만, 시험을 해보면 오류가 발생하지는 않으나, 후자의 경우 Delay없이 실행되게 됩니다.
따라서 디버깅이 쉽지 않는 경우가 있습니다.

timer.performWithDelay(firstTime, function() self:timerActionCB(object) end)
timer.performWithDelay(firstTime, self:timerActionCB(object))
위의 예제에서는 빨간색 상단이 맞는 것입니다.

인자가 있는 경우 아래 두가지 용법을 잘 기억해 두어야 합니다.
local tm = timer.performWithDelay( 1000, onTimer )
-- Assign a table of parameters to the "tm" handle
tm.params = { myParam1 = "Parameter1", myParam2 = "Parameter2" }
local myClosure = function() return spawnBall( randomPosition ) end
timer.performWithDelay( 2000, myClosure, 1 )

triggerGroup.lua 입니다. 나머지는 앞쪽 소스 참고하시기 바랍니다.


local M = {}
local random = math.random

function M:init()
 print("init")
 math.randomseed( os.time() )
 self.cwidth = display.contentWidth
 self.frameTick = 0
 self.data = {
  -- group 1
  {
  -- tick, y, type
   {30,100,1},
   {30,200,2},
   {30,300,1}
  },
  -- group 2
  {
  -- tick, y, type
   {30,100,1},
   {30,200,2},
   {0 ,100,1}, -- todo
   {0 ,150,1}, -- todo
   {0 ,200,1}, -- todo
   {0 ,250,1}, -- todo
   {30,300,1}
  },
    }

    self.typeActionData = {
     -- type 1
     {
      --time, action, param
      {700,"move_y",-100},
      {500,"move_y",200},
     },
     -- type 2
     {
      --tick, action, param
      -- -1 is nothing
      {-1},
     },
    }

    self.typeCreateData = {
     -- type 1
     {
      {"rect",-100, 30},
      {"rect", 100,-30},
     },
     -- type 2
     {
      {"rect",-100,  0},
     },
    }

    self.currentGroupNum = nil
    self.currentPC = nil
    self.nextEventTick = 0
end

function M:changeGroup()
 self.currentGroupNum = random(1,#self.data)
 print("group selected:",self.currentGroupNum)
 self.currentPC = 1;
 -- get tick
 self.nextEventTick = self.data[self.currentGroupNum][self.currentPC][1]
end

function M:loadNextInfo()
 self.frameTick = 0
 self.currentPC = self.currentPC + 1
 if( self.currentPC > #self.data[self.currentGroupNum] ) then
  -- all group end
  self:changeGroup()
 else
  self.nextEventTick = self.data[self.currentGroupNum][self.currentPC][1]
 end
end

function M:timerActionCB(selfobj)
 print("aa",selfobj,selfobj.actionPC);
 if( selfobj.actionPC == nil ) then return end

 --object:removeSelf()
 local action = self.typeActionData[selfobj.actionObjectType][selfobj.actionPC]
 if(action[2]=="move_y")then
  local vx, vy = selfobj:getLinearVelocity()
  selfobj:setLinearVelocity(vx,vy+action[3])
  print("action")
 end
 selfobj.actionPC = selfobj.actionPC + 1
 if( #self.typeActionData[selfobj.actionObjectType] >= selfobj.actionPC ) then
  timer.performWithDelay(self.typeActionData[selfobj.actionObjectType][selfobj.actionPC][1], function() self:timerActionCB(selfobj) end)
  --timer.performWithDelay(self.typeActionData[selfobj.actionObjectType][selfobj.actionPC][1], self:timerActionCB(selfobj))
 end
end

function M:runEvent()
 local y = self.data[self.currentGroupNum][self.currentPC][2]
 local gtype = self.data[self.currentGroupNum][self.currentPC][3]
 local firstTime = self.typeActionData[gtype][1][1]
 
 local cdata = self.typeCreateData[gtype]
 local count = #cdata
 for i = 1, count, 1 do
  local object
  if( cdata[i][1] == "rect" ) then
   object = display.newRect( self.cwidth + self.cwidth/2 , y, 40, 80 )
   physics.addBody(object,"dynamic",{ density=1 })
   object:setLinearVelocity(-300,0)
  end
  if( object~=nil and firstTime >= 0 ) then
   object.actionPC = 1
   object.actionObjectType = gtype;
   print("fristTime",firstTime)
   timer.performWithDelay(firstTime, function() self:timerActionCB(object) end)
   --timer.performWithDelay(firstTime, self:timerActionCB(object))
  end
 end
end

function M:tick()
 self.frameTick = self.frameTick + 1

 if( self.currentGroupNum == nil ) then
  -- need to change group
  self:changeGroup()

 end
 if( self.frameTick > self.nextEventTick ) then
  repeat
   self:runEvent()
   self:loadNextInfo()
  until ( self.nextEventTick ~= 0)
 end



end

return M




2016년 2월 5일 금요일

make game as corona sdk (Fly a rocketman)(3)



이번에는 배경이 날라오는 것을 구현하도록 하겠습니다.

랜덤으로 무조건 물체가 날아 오는것은 재미가 없을겁니다.
그래서 몇개의 그룹을 만들고 점차 어려워 지게 할 예정인데요.
A 그룹 B 그룹 C 그룹 D 그룹이 있다면 처음에는 A-B-C 에서 결정이 되다가 시간이 지나면 B-C-D 그룹에서 결정이 되고 점차 어려워질 수 있도록 할예정인데 일단은 몇개의 그룹을 랜덤하게 설정하고 그룹이 끝나면 다음 그룹으로 넘어가도록 구현해볼 예정입니다.

이전과 다른 점은 triggerGroup.lua 라는것을 만들었습니다.. 이것은 일반 언어의 class와 비슷한 개념으로 이해하면 됩니다.
해당 모듈에서는 물체들의 움직을 만들어 냅니다.

기존 소스와 다른점은 모듈을 추가한 소스와
local trigger = require("triggerGroup")
trigger:init()

eventLoop 내에서 trigger:tick()함수를 호출하는 부분만 다릅니다.

local function eventLoop()
    trigger:tick()
    local vx, vy = player:getLinearVelocity()
if( vy < 100 )then
player:setLinearVelocity( vx, vy+7 )
end
end

아래는 main.lua의 전체 소스입니다.


display.setStatusBar( display.HiddenStatusBar )
CWidth = display.contentWidth

-- Physics
local physics = require ("physics")
physics.start()
--physics.setDrawMode("hybrid")
physics.setGravity( 0, 0 )

local trigger = require("triggerGroup")
trigger:init()

local player_outline = graphics.newOutline( 2, "my.png" )
local player = display.newImage("my.png")
player.x = 40
player.y = 150

physics.addBody(player,"dynamic",{ outline=player_outline,density=10 })
player.isFixedRotation = true


function flyUp(event)
 if event.phase == "began" then
     player:applyForce(0, -1500, player.x, player.y)
 end
end

Runtime:addEventListener("touch", flyUp)
 
local function eventLoop()
    trigger:tick()
    local vx, vy = player:getLinearVelocity()
 if( vy < 100 )then
  player:setLinearVelocity( vx, vy+7 )
 end

end

Runtime:addEventListener( "enterFrame", eventLoop ) 



eventLoop가 일정 프레임으로 진입하게되면 아래 tick시점에 이벤트가 일어납니다.
-- group 1
{
-- tick, y, type
{30,100,0},
{30,200,0},
{30,300,0}
},
-- group 2
{
-- tick, y, type
{30,100,0},
{30,200,0},
{30,300,0}
},


아래는 triggerGroup.lua 입니다.

local M = {}
local random = math.random

function M:init()
 print("init")
 math.randomseed( os.time() )
 self.cwidth = display.contentWidth
 self.frameTick = 0
 self.data = {
 -- group 1
 {
 -- tick, y, type
  {30,100,0},
  {30,200,0},
  {30,300,0}
 },
 -- group 2
 {
 -- tick, y, type
  {30,100,0},
  {30,200,0},
  {30,300,0}
 },
    }
    self.currentGroupNum = nil
    self.currentPC = nil
    self.nextEventTick = 0
end

function M:changeGroup()
 self.currentGroupNum = random(1,#self.data)
 print("group selected:",self.currentGroupNum)
 self.currentPC = 1;
 -- get tick
 self.nextEventTick = self.data[self.currentGroupNum][self.currentPC][1]
end

function M:loadNextInfo()
 self.frameTick = 0
 self.currentPC = self.currentPC + 1
 if( self.currentPC > #self.data[self.currentGroupNum] ) then
  -- all group end
  self:changeGroup()
 else
  self.nextEventTick = self.data[self.currentGroupNum][self.currentPC][1]
 end
end

function M:runEvent()
 local y = self.data[self.currentGroupNum][self.currentPC][2]
 local gtype = self.data[self.currentGroupNum][self.currentPC][3]
 if( gtype == 0 ) then
  local object = display.newRect( self.cwidth + self.cwidth/2 , y, 40, 80 )
  physics.addBody(object,"dynamic",{ density=1 })
  object:setLinearVelocity(-300,0)
 end
end


function M:tick()
 self.frameTick = self.frameTick + 1

 if( self.currentGroupNum == nil ) then
  -- need to change group
  self:changeGroup()

 end
 if( self.frameTick > self.nextEventTick ) then
  self:runEvent()
  self:loadNextInfo()
 end

end

return M

실행화면 입니다.

물체와 부딧히면 아래와 같은 장면이 연출 됩니다.






2016년 1월 29일 금요일

make game as corona sdk (Fly a rocketman)(2)

주인공 움직임 만들기

앞에 기획에 이어서 이번에는 주인공 입력에 대한 부분을 구현 해보겠습니다.

중력은 physics.setGravity( 0, 0 ) 0 으로 설정합니다. 이렇게 하는 이유는 스크롤을 위해서 그렇습니다. 스크롤은 앞에서 날라오는 물체가 될텐데 중력이 적용되면 아래로 떨어지면서 날라오게됩니다.
그리고 주인공은 my.png로 대충 그립니다.
물리 관련해서는 addBody를 사용해 주며, isFixedRotation=true 설정을 해줍니다.
그러면 jump하는동안에 회전하지 않게 됩니다.
터치 핸들러에 applyForce로 y축으로 힘을 넣도록 합니다.
아래로 내려오는 중력 가속도가 없기 때문에 이벤트 루프 안에서 아래와 같은 코드를 넣습니다. 즉 프레임 마다 속도를 올려 주다가 일정 속도가 되면 속도 증가가 안되도록 합니다.
 if( vy < 100 )then
  player:setLinearVelocity( vx, vy+7 )
 end

main.lua 소스는 아래와 같습니다.

display.setStatusBar( display.HiddenStatusBar )

-- Physics
local physics = require ("physics")
physics.start()
--physics.setDrawMode("hybrid")
physics.setGravity( 0, 0 )

local player_outline = graphics.newOutline( 2, "my.png" )
local player = display.newImage("my.png")
player.x = 40
player.y = 150

physics.addBody(player,"dynamic",{ outline=player_outline,density=10 })
player.isFixedRotation = true


function flyUp(event)
 if event.phase == "began" then
     player:applyForce(0, -1500, player.x, player.y)
 end
end

Runtime:addEventListener("touch", flyUp)
 
local function eventLoop()
    local vx, vy = player:getLinearVelocity()
 if( vy < 100 )then
  player:setLinearVelocity( vx, vy+7 )
 end
end

Runtime:addEventListener( "enterFrame", eventLoop ) 

my.png





사용된 config.lua 는 아래와 같습니다.  돌아다니다가 구한 만능 config라고 합니다.
기본적으로 가로세로 정보는 아래와 같기 때문에 이미지 크기도 아래에 맞춰서 적당한 이미지를 사용하도록 하겠습니다.

            width = 320,
            height = 570,

if string.sub(system.getInfo("model"),1,4) == "iPad" then
    application = 
    {
        content =
        {
            width = 360,
            height = 480,
            scale = "letterBox",
            xAlign = "center",
            yAlign = "center",
            imageSuffix = 
            {
                ["@2x"] = 1.5,
                ["@4x"] = 3.0,
            },
        },
        notification = 
        {
            iphone = {
                types = {
                    "badge", "sound", "alert"
                }
            }
        }
    }

elseif string.sub(system.getInfo("model"),1,2) == "iP" and display.pixelHeight > 960 then
    application = 
    {
        content =
        {
            width = 320,
            height = 568,
            scale = "letterBox",
            xAlign = "center",
            yAlign = "center",
            imageSuffix = 
            {
                ["@2x"] = 1.5,
                ["@4x"] = 3.0,
            },
        },
        notification = 
        {
            iphone = {
                types = {
                    "badge", "sound", "alert"
                }
            }
        }
    }

elseif string.sub(system.getInfo("model"),1,2) == "iP" then
    application = 
    {
        content =
        {
            width = 320,
            height = 480,
            scale = "letterBox",
            xAlign = "center",
            yAlign = "center",
            imageSuffix = 
            {
                ["@2x"] = 1.5,
                ["@4x"] = 3.0,
            },
        },
        notification = 
        {
            iphone = {
                types = {
                    "badge", "sound", "alert"
                }
            }
        }
    }
elseif display.pixelHeight / display.pixelWidth > 1.72 then
    application = 
    {
        content =
        {
            width = 320,
            height = 570,
            scale = "letterBox",
            xAlign = "center",
            yAlign = "center",
            imageSuffix = 
            {
                ["@2x"] = 1.5,
                ["@4x"] = 3.0,
            },
        },
  LevelDirectorSettings = 
  {
   imagesSubFolder = ".",
   levelsSubFolder = ".",
  }
    }
else
    application = 
    {
        content =
        {
            width = 320,
            height = 512,
            scale = "letterBox",
            xAlign = "center",
            yAlign = "center",
            imageSuffix = 
            {
                ["@2x"] = 1.5,
                ["@4x"] = 3.0,
            },
        },
        notification = 
        {
            iphone = {
                types = {
                    "badge", "sound", "alert"
                }
            }
        },
  LevelDirectorSettings = 
  {
   imagesSubFolder = ".",
   levelsSubFolder = ".",
  }
    }
end

2016년 1월 22일 금요일

make game as corona sdk (Fly a rocketman)(1)

CORONA SDK로 게임 만들기

올해(2016) 주제는 쉽고 중독성 있으면서 재미있는 판타스틱한 게임 도전이 목표입니다.
그래서 곰곰히 거의 15일만에 계획을 세웠습니다.

진행하면서 배우는 공부하게되는 내용은 아래에 정리해 보았습니다.
@랭킹시스템
@물리충돌

기본 동작은 아래와 같습니다.
!!기획 스케치 해보았습니다.

조작법은 1버튼 게임으로 아주 쉽게 되어있습니다. 이런 유형은 Flappy게임 방식으로 헬리콥터가 나오는 버전도 있고 굉장히 다양하게 있습니다.

이 부분이 차이점입니다. 
다가오는 물체들이 회전을 하거나 돌처럼 날라오는데 피해야 합니다. 가로 형태의 슈팅 게임이라고 보면 됩니다. 
물리엔진을 이용해서 좀 더 사실적으로 만들 예정이고 부딧히더라도 뒤로 밀려나가게 되는데 화면밖으로만 안나가면 계속 플레이가 가능합니다.
또한 로켓연료를 안먹으면 연료가 떨어져서 위로 올라갈 수가 없습니다.
여유가 된다면 능력치가 다른 캐릭터를 만들 예정입니다.

게임 기획은 여기까지 입니다.
다음에는 하나씩 test 어플로 구현 예정입니다.

- 물리객체 날라오게 만들기
- 주인공에 날라오는 물리 객체맞으면 뒤로 밀리게 구현 가능한지?
- 물리객체의 생성을 나타내는 맵제작툴 고민하기





2016년 1월 15일 금요일

루아(Lua)에서 colon(:) 과 dot(.) 연산자 이해


사용하면서도 항상 헷갈리는게 colon(:) 과 dot(.) 였습니다. 책을 봐도 이해가 안되는 말뿐이고 이걸 언젠가는 완벽 이해 해야겠다고 결심했습니다.

아래 링크를 보면 외국인들도 헷갈리긴 마찬가지 인가 보네요.

https://coronalabs.com/blog/2015/12/01/tutorial-understanding-the-colon-vs-dot-operator/

CORONA with Lua -원강민 지음- 책 78 page에 보면 차이점이 나오긴 합니다.
둘의 차이는 하나입니다. :으로 된 함수는 내부에 self라는 변수가 자동으로 생성되는 것인데요. 해당 객체 자신을 참조합니다.
예제로 배우는 프로그래밍 루아 -한빛미디어- 책 76 page에는 아래와 같이 나옵니다.
즉, ':'정의를 하면 'self'를 생략할 수 있습니다.

이것을 정리해보도록 하겠습니다.
아래와 같은 예제를 사용했을때 결과를 한번 보시죠
local object = {}

function object.dotMethod( parameter1, parameter2 )
    print( self.someNumber )
end

function object:colonMethod( parameter1, parameter2 )
    print( self.someNumber )
end

object.someNumber = 10
object.dotMethod( "Hello", "World" )    -- Dot method called with dot operator
object:colonMethod( "Hello", "World" )  -- Colon method called with colon operator
Running this code, you can see that the result in the console is:
nil
10
C++ 언어와 비교해 보겠습니다.


#include <iostream>
#include <stdio.h>
using namespace std;

class Object
{
public:
 int someNumber;
 Object()
 {
  this->someNumber = 0;
 }
 void Method();
};

void Object::Method()
{
 printf("%d\n",this->someNumber);
}

int main() {
 Object object1;
 object1.Method();
 object1.someNumber = 10;
 object1.Method();
 
 Object *object2 = new Object();
 object2->Method();
 object2->someNumber = 10;
 object2->Method();
 delete object2;
 return 0;
}

결과
Success time: 0 memory: 3460 signal:0
0
10
0
10


정리하자면 
Lua에서의 self는 타언어 (Java,C++)에서 this와 동일하다.
Lua의 method 내에서 self를 사용할 수 있는 조건은 메소드를 ':'을 사용해서 호출할 때이다.

C++ 언어에서 보면 객체내에서는 this사용이 자유롭다고 보면 됩니다. 하지만 lua는 : 로 호출할때만 내부에서 self 를 이용 가능합니다.
Lua에서 ":" , "." 메소드를 어떻게 만들었냐에 따라 다릅니다.
일반적으로 corona sdk의 기본 library함수는 "."을 사용합니다.

타언어와 비교를 했는데도 어렵다면 객체 지향에 대해서 이해가 필요하고요, 잘모르겠다고 판단되면 . 로 호출되는 대부분의 함수들은 corona sdk의 기본 함수들입니다. 그리고 그외에 소스가 있는것들을 자세히 살펴보면 개발자가 : 사용해서 정의가 되어있는지 . 로 사용하여 정의가 되어있는지에 따라서 호출 방법이 달라지게 됩니다.

string 길이를 구하는데 class 정보가 필요하지 않기 때문에 "."로 되어있나 봅니다.

local length = string.len( "Hello World" )





2016년 1월 8일 금요일

corona SDK android 마켓 올리기

광고 탑제가 궁금하면 다른 게시글을 참고 하시기바랍니다.
여기에서는 corona SDK를 이용해서 만든 어플을 마켓에 올리는 과정입니다.
지금까지는 빌드하는 법은 모두 알 것이라고 생각되는데요.


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

keystore 만들기

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

Apk를 만들기전에 keystore를 만들어야 합니다. keystore를 이용해서 apk signing을 하게 됩니다.
keystore는 app마다 달라도 되고 개발자 또는 회사마다 달라도 되며 모두 같아도 됩니다. 중요한 점은 절대 사용되는 비밀번호를 잃어버리면 절대 안되며, 잃어버리게 되면 해당 어플을 업데이트 할 수가 없게 됩니다.

아래 링크에 친절히 설명되어있습니다만, 간략하게 사용하려면 keytool을 사용하는 방법이 있습니다. keytool은 JDK를 설치해야 합니다. android를 개발하는 사람이라면 JDK를 모두 설치했으리라 판단이 됩니다. (JRE와 혼동 없으시길 바랍니다. jre는 keytool이 포함 되어있지 않습니다.)
참고 : http://www.androidpub.com/4742
http://developer.android.com/tools/publishing/app-signing.html

Generate a private key using keytool. For example:
$ keytool -genkey -v -keystore my-release-key.keystore-alias alias_name -keyalg RSA -keysize 2048 -validity 10000
This example prompts you for passwords for the keystore and key, and to provide the Distinguished Name fields for your key. It then generates the keystore as a file called my-release-key.keystore. The keystore contains a single key, valid for 10000 days. The alias is a name that you will use later when signing your app.


실행을 시켜보면 아래와 같이 실행이 됩니다.

C:\>"C:\Program Files\Java\jdk1.8.0_25\bin\keytool.exe" -genkey -v -keystore my-release-key.keystore -alias xxxxxxxxxx -keyalg RSA -keysize 2048 -validity 20000
키 저장소 비밀번호 입력:
새 비밀번호 다시 입력:
이름과 성을 입력하십시오.
  [Unknown]:
조직 단위 이름을 입력하십시오.
  [Unknown]:
조직 이름을 입력하십시오.
  [Unknown]:
구/군/시 이름을 입력하십시오?
  [Unknown]:
시/도 이름을 입력하십시오.
  [Unknown]:
이 조직의 두 자리 국가 코드를 입력하십시오.
  [Unknown]:
CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown이(가) 맞습니
까?
  [아니오]:  y

다음에 대해 유효 기간이 20,000일인 2,048비트 RSA 키 쌍 및 자체 서명된 인증서(SHA
256withRSA)를 생성하는 중
        : CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
<xxxxxxxxxx>에 대한 키 비밀번호를 입력하십시오.
        (키 저장소 비밀번호와 동일한 경우 Enter 키를 누름):
[my-release-key.keystore을(를) 저장하는 중]


여기에서 xxxxxxxxxx 으로 되어있는 부분은 alias값으로 크게 중요한 값은 아닙니다.  -validity 20000 유효 기간일 수 입니다. 넉넉히 크게 합니다. 나머지는 빈값으로 입력해도 되는데 비밀번호와 생성된 파일은 꼭 잘보관해 두시길 바랍니다.


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

빌드하기

------------------------------------------------------------------------
시뮬레이터에서 File>Build>Android 메뉴를 이용하면 됩니다. 이때 keystore 파일을 선택하면 됩니다.


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

개발자 사이트 등록

------------------------------------------------------------------------
개발자 사이트 접속합니다. 개발자 계정이 없다면 먼저 만들어야 합니다. 이전 게시글에서 언급 되었습니다.
https://play.google.com/apps/publish/






아래 빨간색으로 표시된 부분을 눌러 모든 내용이 입력되도록 해줍니다.
필요한 준비물로는 여러장의 Screen Shot 이미지, 큰화면의 icon, text 문구등등 필요한 사항들이 제법 있습니다.



모든 사항을 입력하고 제출하면 게시 보류 중 상태가 됩니다.
이번 HitNRun 이란 프로젝트는 재미있는 게임성이 높은 App은 아니고, 음악, 효과음, 레벨 셀렉트, 이미지, AdMob광고 세세한 부분까지 1인이 제작한 어플로 App을 등록 과정 및 소스코드를 블로그로 등록하기 위해서 준비한 프로젝트 였습니다.

2015년 project는 이것으로 마치고 2016년에는 새로운 project로 만나도록 하겠습니다.

2016년 1월 2일 토요일

AdMob 가입기 와 Corona SDK에서 admob 사용기


admob은 모바일에서 광고를 하기 위한 플랫폼입니다.
Corona SDK에서 지원한다고 해서 가입해 보았습니다.



  • 아래 링크 접속해서 가입하기 버튼을 누릅니다.

https://www.google.co.kr/admob/




  • 구글 계정 로그인







  • adsense 계정이 있으면 편하게 가입이 되네요. 없으면 adsense계정 가입부터 해야 합니다.





  • 애드워즈 계정을 입력하고 시작하기 버튼을 눌러줍니다.




  • Admob에서 새광고 단위 만들기

광고 단위를 만들어야 어플에서 id를 이용해서 광고를 넣을 수 있습니다. 기본적을 따라하면 어렵지 않게 됩니다. 이미 등록된 어플을 선택할 수도 있고, 등록되지 않은 어플도 추가가 가능합니다. 여기에서는 등록되지 않은 어플 이름을 넣어서 id를 만들었습니다.






  • Corona SDK 에서 적용하기


기본적으로 아래 두개의 세팅을 넣어야 합니다.
https://docs.coronalabs.com/api/library/ads/index.html

Project Settings

To use this plugin, add an entry into the plugins table of build.settings. When added, the build server will integrate the plugin during the build phase.
settings =
{
    plugins =
    {
        ["plugin.google.play.services"] =
        {
            publisherId = "com.coronalabs"
        },
    },      
}

Android

For Android, the following permissions/features are automatically added when using this plugin:
    android =
    {
        usesPermissions =
        {
            "android.permission.INTERNET",
            "android.permission.ACCESS_NETWORK_STATE",
        },
    },


그리고는 아래 url에 참고로 작업해야 하는데요. 간단하면서도 내용이 복잡합니다.
주의할 점은 테스트가 시뮬레이터에서는 되지 않습니다. 따라서 지속적으로 단말에 apk를 올려서 테스트 해봐야 합니다. 가장 중요한점은 테스트시 광고가 Click 되지 않도록 주의합니다. (부정클릭 방지)

https://coronalabs.com/blog/2014/07/15/tutorial-implementing-admob-v2/

아래와같은 admob 에서 광고 단위를 생성하면 id라는게 나오는데요. 아래와 같은 형태가 됩니다. 이것을 Ad Unit ID 혹은 AppID라고 합니다.
ca-app-pub-28374982782348972983749273/4987234823
위 링크에서는 i OS 와 안드로이드를 두개 모두 설명한다고 코드가 복잡한데요.
코드에서 사용할때는 아래와 같이 하면 됩니다. 마지막 ads.show() 함수에 y 인자가 100000 인데 시작시점에 광고를 로딩해서 게임중에 느려짐을 방지하기 위함입니다. 그리고 필요한 곳에서 ads.hide()를 호출한뒤 원하는 위치에 ads.show()를 해주면 됩니다.
여기에서는 banner형태의 광고만 예를 들었습니다. show 함수에 인자를 banner를 넣어주면 됩니다. 그리고 ads.show("banner", {x=0, y=100000}) 이렇게 꼭 안해도 됩니다.
이것은 성능개선을 위한 제안일 뿐입니다.


local ads = require( "ads" )
local bannerAppID = "ca-app-pub-nnnnnnnnnnnnnnnn/nnnnnnnnnn"

local adProvider = "admob"
local function adListener( event )
    local msg = event.response
    -- Quick debug message regarding the response from the library
    print( "Message from the ads library: ", msg )

    if ( event.isError ) then
        print( "Error, no ad received", msg )
    else
        print( "Got one!" )
        adHeight = ads.height()
    end
end

ads.init( adProvider, bannerAppID, adListener )
ads.show("banner", {x=0, y=100000})

이건 위의 안보이는곳에 광고가 나와있으므로 지워주는 역할을 합니다.
ads.hide()
진짜 필요한곳에서 아래와 같은 코드를 수행합니다.
ads.show("banner", { x=0, y=0 })

그리고 광고의 위치는 x,y 값 조절로 변경이 가능하나 크기 정보 변경이 안됩니다.
게시글 리플에 보면 아래와 같이 조절하는것처럼 보이나 테스트해보면 반영되지는 않습니다. 그래서 모양이 어울리지 않기때문에 배치는 제일 위나 제일 아래 영역 통째로 광고 배너를 넣는것이 UI상 보기가 좋습니다.
Position and height-width set :
ads.show(“banner”, { x=100, y=display.contentHeight-30,width=display.contentWidth-200,height=40 })


또한 구글은 배너 광고 시점이 게임의 빠른 처리를 위한 플레이 동안에는 광고를 보여주지 않아야 한다고 강력하게 조언합니다. 무슨 말뜻인지는 알아서 새겨들으면 될 것 같습니다.
Google strongly advises that you do not show banners at times where your CPU time is at a premium, for example, during the main gameplay state of a fast-paced game.