반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형
오늘은 벽돌 깨기 게임 만들기 마지막회가 될것 같은데요.
어제까지 일단 화면에 표시할 모든 객체들은 다 표시했습니다.
그리고 화면에서는 공만 여기저기 튀면서 움직이고 있습니다.

이제 남은 일은 밑에 있는 paddle을 유저가 좌우로 움직일 수 있도록 해야 하고,
공이 벽돌에 맞으면 벽돌이 없어지게 해야 하고,
공이 bottom Wall에 맞으면 공이 없어지고 새로 시작하도록 해야 합니다.

그럼 우선 첫번째 paddle을 움직이는 것 부터 해 볼까요?

지난 시간 까지 했던 paddle 생성 함수는 아래와 같습니다.
function createPaddle()
    local paddleWidth = 100
    local paddleHeight = 10
    local paddle = display.newRect( display.contentWidth / 2 - paddleWidth / 2, display.contentHeight - 50, paddleWidth, paddleHeight )
    physics.addBody(paddle, "static", {friction=0, bounce=1})
end

자 touch 이벤트 리스너를 달겠습니다.
Runtime:addEventListener("touch", movePaddle)

paddle에 달지 않고 Runtime에 달았습니다. 유저가 paddle에 닿았을 때만 움직일 수 있도록 하려면 Runtime을 paddle로 바꾸면 됩니다.
하지만 여기선 유저가 움직일 수 있는 객체가 paddle밖에 없고 화면 전체에서 이 이벤트 리스너를 받는게 더 효과적이기 때문에 Runtime에 이벤트 리스너를 달았습니다.

이 이벤트 리스너는 화면 아무곳이나 touch하면 movePaddle 함수가 실행 되도록 하라는 의미 입니다.
이 이벤트 리스너 앞에 movePaddle 함수를 만듭니다.
그리고 event.x를 paddle.x에 대입하면 이 paddle의 위치가 좌우로 움직입니다.

local movePaddle = function(event)
    paddle.x = event.x
end


자 이제 유저가 paddle을 움직일 수 있습니다.

그 다음은 공이 벽돌에 맞으면 그 벽돌이 없어지도록 할께요.
addBody를 할 때 공은 dynamic으로 설정하고 나머지 객체들은 모두 static으로 설정했습니다.
 이 dynamic으로 설정한 공에 충돌(collision)을 체크하는 로직을 적용하도록 하겠습니다.
function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
    physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})
    ball:setLinearVelocity(75, 150)
end

지난 시간까지 한 소스코드 입니다.
이제 ball에 collision 이벤트 리스너를 답니다.
ball:addEventListener("collision", ball)

공이 어딘가에 충돌했을 때 ball.collision 함수가 실행 됩니다.
여기서 어딘가는 physics.addBody 가 적용된 객체들을 말합니다.

그럼 ball.collision함수를 만들어 보겠습니다.
    ball.collision = function(self, event)
        if(event.phase == "ended") then
            if(event.other.type == "destructible") then
                event.other:removeSelf()
            end
        end
    end

벽돌을 만들 때 각각의 벽돌에 type을 destructible 이라고 주었었습니다.
여기서 event.other는 충돌의 상대편을 말합니다. event.target은 충돌 당사자를 말합니다. 그러므로 event.other는 벽돌이고 event.target은 ball 이겠죠.
event.other.type이 destructible이면 즉 벽돌이면 event.other:removeSelf() 합니다. 즉 그 벽돌을 없앱니다.
아주 간단하죠?

이제 벽돌 깨기의 주요 기능이 다 구현 됐습니다.

여기서 보너스로 한가지만 더 한다면 공이 bottom wall에 부딪혔을 때 공이 없어지고 새로 시작하도록 하겠습니다.

이것도 공이 collision 이벤트를 일으켰을 때 일어나는 현상이기 때문에 아까 만들었던 ball.collision 함수 안에 아래 코드를 넣습니다.
        if(event.other.type == "bottomWall") then
            self:removeSelf()
            local onTimerComplete = function(event)
                createBall()
            end
            timer.performWithDelay(500, onTimerComplete , 1)
        end

이걸 넣고 실행해 보시면 공이 바닥에 닿으면 없어지고 가운데에서 다시 생성 되는 것을 보실 수 있을 겁니다.

이 경우 collision 이벤트가 발생했을 때 event.other.type이 bottomWall 이면 self:removeSelf()를 해 줍니다. other가 아니라 자신이 없어지는 겁니다.
그리고 timer로 0.5초의 시간을 두고 onTimerComplete 함수를 한번 호출하도록 만듭니다.
이 함수에서는 createBall()을 호출합니다.
그러면 공이 바닥에 부딪혀서 없어지고 0.5초 후에 새 공이 가운데에서 만들어져서 움직이기 시작할 겁니다.

전체 소스는 아래와 같습니다.
require ("physics")

function main()
    setUpPhysics()
    createWalls()
    createBall()
    createPaddle()
    createBricks()
end

function setUpPhysics()
    physics.start()
    physics.setGravity(0,0)
end

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
    physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})
    ball:setLinearVelocity(75, 150)
   
    ball.collision = function(self, event)
        if(event.phase == "ended") then
            if(event.other.type == "destructible") then
                event.other:removeSelf()
            end
        end
       
        if(event.other.type == "bottomWall") then
            self:removeSelf()
            local onTimerComplete = function(event)
                createBall()
            end
            timer.performWithDelay(500, onTimerComplete , 1)
        end
    end

    ball:addEventListener("collision", ball)
end

function createWalls()
    local wallThickness = 10
    -- Left wall
    local wall = display.newRect( 0, 0, wallThickness, display.contentHeight )
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Top wall
    wall = display.newRect(0,0, display.contentWidth, wallThickness)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Right wall
    wall = display.newRect(display.contentWidth - wallThickness, 0, wallThickness, display.contentHeight)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Bottom wall
    wall = display.newRect(0, display.contentHeight - wallThickness, display.contentWidth, wallThickness)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    wall.type = "bottomWall"
end

function createPaddle()
   
    local paddleWidth = 100
    local paddleHeight = 10
   
    local paddle = display.newRect( display.contentWidth / 2 - paddleWidth / 2, display.contentHeight - 50, paddleWidth, paddleHeight )
    physics.addBody(paddle, "static", {friction=0, bounce=1})

    local  movePaddle = function(event)
            paddle.x = event.x
    end

    Runtime:addEventListener("touch", movePaddle)
   
end
function createBricks()
    local brickWidth = 40
    local brickHeight = 20
    local numOfRows = 4
    local numOfCols = 6
    local topLeft = {x= display.contentWidth / 2 - (brickWidth * numOfCols ) / 2, y= 50}
    local row
    local col
    for row = 0, numOfRows - 1 do
        for col = 0, numOfCols - 1 do
            -- Create a brick
            local brick = display.newRect( topLeft.x + (col * brickWidth), topLeft.y + (row * brickHeight), brickWidth, brickHeight )
            brick:setFillColor(math.random(50, 255), math.random(50, 255), math.random(50, 255), 255)
            brick.type = "destructible"
            physics.addBody(brick, "static", {friction=0, bounce = 1})
        end
    end
end

main()

원본 소스는 ball:setLinearVelocity(75, 150) 를 startGame()에 넣고 사용했는데 저는 하다 보니까 그냥 createBall에 넣었네요. 두개 다 똑 같습니다.
다만 나중에 게임에 여러 기능을 추가하다 보면 startGame()을 따로 Call할 일이 많이 생길 수 있는데 그 때는 원본 소스 같이 따로 함수로 관리하는게 더 낫겠죠.

이 소스는 Corona SDK를 만든 회사인 Ansca Mobile에서 제공한 샘플 소스입니다.
아래 유튜브로 가시면 이 샘플 코드에 대한 강좌를 들으실 수 있습니다.

제가 설명했던 것과는 조금 다르게 설명하겠죠?

이것도 들어보시면 도움이 될 거예요.

그럼 다음시간에 뵐께요.
반응형


반응형
어제 했던 소스는 아래와 같습니다.

require ("physics")

function main()
    setUpPhysics()
    createBall()
end

function setUpPhysics()
    physics.start()
    physics.setGravity(0,0)
end

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
    physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})
    ball:setLinearVelocity(75, 150)
end

main()


정리하자면 공을 만들고 그 공에 physics 엔진을 적용하고 움직이는 것 까지 했습니다.

오늘은 벽을 만들어서 그 벽에 공이 튀게 만들겠습니다.

일단 함수 이름은 createWalls()로 하고 사각형으로 그려보겠습니다.

function createWalls()
    local wallThickness = 10
    -- Left wall
    local wall = display.newRect( 0, 0, wallThickness, display.contentHeight )
    -- Top wall
    wall = display.newRect(0,0, display.contentWidth, wallThickness)
    -- Right wall
    wall = display.newRect(display.contentWidth - wallThickness, 0, wallThickness, display.contentHeight)
    -- Bottom wall
    wall = display.newRect(0, display.contentHeight - wallThickness, display.contentWidth, wallThickness)
end

벽 두께는 10픽셀로 합니다.

그리고 main()함수에서 이 createWall() 함수를 Call 하세요.


이제 사방에 벽이 생겼습니다.
실행하면 공이 벽을 지나쳐서 갑니다.
저 벽에 공이 튀게 만드려면 이 wall들에 addBody를 해 주면 됩니다.

지금까지의 소스는 아래와 같습니다.
require ("physics")

function main()
    setUpPhysics()
    createWalls()
    createBall()
end

function setUpPhysics()
    physics.start()
    physics.setGravity(0,0)
end

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
    physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})
    ball:setLinearVelocity(75, 150)
end

function createWalls()
    local wallThickness = 10
    -- Left wall
    local wall = display.newRect( 0, 0, wallThickness, display.contentHeight )
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Top wall
    wall = display.newRect(0,0, display.contentWidth, wallThickness)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Right wall
    wall = display.newRect(display.contentWidth - wallThickness, 0, wallThickness, display.contentHeight)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Bottom wall
    wall = display.newRect(0, display.contentHeight - wallThickness, display.contentWidth, wallThickness)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    wall.type = "bottomWall"
end

main()

이제 실행하면 공이 계속 벽면에 튀게 됩니다.

여기서 유심히 볼 점은 벽면 모두 변수 이름이 wall입니다. 이 변수를 한번 만들어서 계속 재활용 한 겁니다.
그리고 맨 마지막에 bottom Wall을 만들었고 type을 bottomWall이라고 명명했습니다.
이는 다른 세 벽은 공이 맞고 튀기만 하면 되고 아래 벽엔 맞으면 다른 핸들링을 하기 위해서 이렇게 한 겁니다.

이 부분은 나중에 구현 해 보겠습니다.

이제 아래에 paddle을 한번 만들어 볼까요?
밑에 적당한 위치에 적당한 길이의 사각형을 만들겠습니다.

function createPaddle()
    local paddleWidth = 100
    local paddleHeight = 10
    local paddle = display.newRect( display.contentWidth / 2 - paddleWidth / 2, display.contentHeight - 50, paddleWidth, paddleHeight )
end

보시는 대로 왼쪽 오른쪽 길이(Width)는 100픽셀이고 높이는 10픽셀인 사각형을 그리고 이것을 가운데에 위치시킵니다.
실행 시키면 아직까지 paddle에 physics를 적용하지 않았기 때문에 공은 그냥 통과합니다.
여기에 physics를 아래와 같이 적용하세요.
physics.addBody(paddle, "static", {friction=0, bounce=1})

이러면 공이 지나가다가 이 paddle에 맞으면 튀게 됩니다.

여기에 유저가 paddle을 움직일 수 있도록 하는 기능을 다뤄야 하는데요.
이 기능은 다음 강좌에서 하게 될 겁니다.

이번엔 벽돌들을 만들어 볼까요?
createBricks() 함수를 만드세요.

그리고 사각형을 만들께요. Width는 40 Height는 20으로 하겠습니다.
    local brickWidth = 40
    local brickHeight = 20
local brick = display.newRect( 50, 50, brickWidth, brickHeight )
brick:setFillColor(50, 250, 100, 255)

전체 소스는 아래와 같습니다.
require ("physics")

function main()
    setUpPhysics()
    createWalls()
    createBall()
    createPaddle()
    createBricks()
end

function setUpPhysics()
    physics.start()
    physics.setGravity(0,0)
end

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
    physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})
    ball:setLinearVelocity(75, 150)
end

function createWalls()
    local wallThickness = 10
    -- Left wall
    local wall = display.newRect( 0, 0, wallThickness, display.contentHeight )
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Top wall
    wall = display.newRect(0,0, display.contentWidth, wallThickness)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Right wall
    wall = display.newRect(display.contentWidth - wallThickness, 0, wallThickness, display.contentHeight)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Bottom wall
    wall = display.newRect(0, display.contentHeight - wallThickness, display.contentWidth, wallThickness)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    wall.type = "bottomWall"
end

function createPaddle()
    local paddleWidth = 100
    local paddleHeight = 10
    local paddle = display.newRect( display.contentWidth / 2 - paddleWidth / 2, display.contentHeight - 50, paddleWidth, paddleHeight )
    physics.addBody(paddle, "static", {friction=0, bounce=1})
end

function createBricks()
    local brickWidth = 40
    local brickHeight = 20
    local brick = display.newRect( 50, 50, brickWidth, brickHeight )
    brick:setFillColor(50, 250, 100, 255)
end

main()

이제 벽돌이 하나 생겼습니다.
저 벽돌에 공이 맞았을 때 튀게하려면 지금까지 다른 객체에 했던 식으로 addBody를 하면 됩니다.

그런데 여기서 생각해야 할 점은 벽돌은 1개가 아니라 수십개가 만들어 져야 합니다.
저런식으로 하나하나 각각 위치와 색을 지정해서 벽돌을 만들어도 됩니다.
그런데 그러면 프로그램 답지가 않습니다.

for문을 사용해서 간단하게 벽돌들을 만들겠습니다.

저런 벽돌을 좌우로 6개씩 4줄 총 24개를 만드는 로직을 구현하겠습니다.
아까 벽돌의 두께와 높이는 40,20으로 정했구요.
numOfRows=4, numOfCols = 6 을 추가하겠습니다.

그리고 첫 시작점은 아래와 같이 잡습니다.
local topLeft = {x= display.contentWidth / 2 - (brickWidth * numOfCols ) / 2, y= 50}

이 벽돌은 처음 1줄 6개를 그리고 그 다음 줄 6개 그리는 식으로 4번 작업을 할 겁니다.
그 로직은 아래와 같습니다.
    local row
    local col
    for row = 0, numOfRows - 1 do
        for col = 0, numOfCols - 1 do
            -- Create a brick
            local brick = display.newRect( topLeft.x + (col * brickWidth), topLeft.y + (row * brickHeight), brickWidth, brickHeight )
            brick:setFillColor(math.random(50, 255), math.random(50, 255), math.random(50, 255), 255)
        end
    end


벽돌색은 Random하게 지정했습니다.
여러분 취향대로 라인별로 색을 지정해도 되고 열 별로 지정해도 되고 마음대로 바꾸셔도 됩니다.

여기에 이 벽돌을 맞고도 공이 튀어나오게 하려면 각 벽돌에 addBody를 하면 됩니다.
그리고 type은 destructible로 명명합니다.
나중에 무딪히면 없애야 되거든요.

벽돌에 addBody를 한 오늘의 최종 소스코드는 아래와 같습니다.

require ("physics")

function main()
    setUpPhysics()
    createWalls()
    createBall()
    createPaddle()
    createBricks()
end

function setUpPhysics()
    physics.start()
    physics.setGravity(0,0)
end

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
    physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})
    ball:setLinearVelocity(75, 150)
end

function createWalls()
    local wallThickness = 10
    -- Left wall
    local wall = display.newRect( 0, 0, wallThickness, display.contentHeight )
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Top wall
    wall = display.newRect(0,0, display.contentWidth, wallThickness)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Right wall
    wall = display.newRect(display.contentWidth - wallThickness, 0, wallThickness, display.contentHeight)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    -- Bottom wall
    wall = display.newRect(0, display.contentHeight - wallThickness, display.contentWidth, wallThickness)
    physics.addBody(wall, "static", {friction=0, bounce = 1})
    wall.type = "bottomWall"
end

function createPaddle()
    local paddleWidth = 100
    local paddleHeight = 10
    local paddle = display.newRect( display.contentWidth / 2 - paddleWidth / 2, display.contentHeight - 50, paddleWidth, paddleHeight )
    physics.addBody(paddle, "static", {friction=0, bounce=1})
end

function createBricks()
    local brickWidth = 40
    local brickHeight = 20
    local numOfRows = 4
    local numOfCols = 6
    local topLeft = {x= display.contentWidth / 2 - (brickWidth * numOfCols ) / 2, y= 50}
    local row
    local col
    for row = 0, numOfRows - 1 do
        for col = 0, numOfCols - 1 do
            -- Create a brick
            local brick = display.newRect( topLeft.x + (col * brickWidth), topLeft.y + (row * brickHeight), brickWidth, brickHeight )
            brick:setFillColor(math.random(50, 255), math.random(50, 255), math.random(50, 255), 255)
            brick.type = "destructible"
            physics.addBody(brick, "static", {friction=0, bounce = 1})
        end
    end
end

main()

이제 공이 벽돌에 부딪히면 벽돌이 없어지고 아래 벽에 부딪히면 게임이 새로 시작하고 paddle을 유저가 좌우로 움직이도록 하는 작업이 남았습니다.

내일은 이 작업에 대해 알아보겠습니다.

반응형


반응형

오늘서 부터 코로나 SDK로 간단한 벽돌깨기 게임을 만들어 보도록 하겠습니다.
몇회 정도 할지는 잘 모르겠습니다. 처음 코로나를 접하시는 분들도 만드실 수 있도록 그리고 프로그래밍 경력이 거의 없으신 분들도 따라올 수 있도록 하나하나 순서대로 코딩하고 화면으로 확인하고 하면서 진행하려고 합니다.
이 강좌는 소스분석 카테고리에 넣을 강좌로 준비했습니다.
벽돌깨기 게임 공개 소스를 분석 하는데, 그냥 공개된 소스만 갖고 분석하는 것 보다 실제 게임을 만드는 순서로 코드를 하나하나 완성해 가면서 해 보려구요.

게임을 완료하게 되면 아래와 같은 화면이 실행 됩니다.


모두 알고 있듯이 저 공은 계속 움직이면서 물체에 부딪히면 튕기게 됩니다.
그리고 공이 벽돌에 맞으면 벽돌은 없어지게 되구요.
바닥에 맞으면 공은 없어지게 됩니다. 다만 아래에 있는 스틱에 맞으면 없어지지 않고 튕겨서 다시 위로 올라가구요.

이 강좌에서는 여기까지만 구현 해 보겠습니다.
이외에 점수 올라가고 공 갯수 제한하고 하는 것은 각자 쉽게 하실 수 있을 겁니다. 이번에 진행되는 강좌를 잘 숙지하시면요...

일단 공이 움직이고 튕기는 것은 코로나 SDK의 physics 엔진을 사용하겠습니다.
그리고 각 객체와 움직임 그리고 이벤트에 대한 핸들링은 함수를 사용해서 구현할 거구요.

일단 여기서 객체들을 그 그룹별로 한번 나눠보죠..
벽, 벽돌들, 공, 공을 튀어내는 stick(paddle)
이렇게 4가지 정도 눈에 보이는 객체 그룹이 있습니다.
그리고 physics엔진을 이용한다고 했구요.
그러면 대충 함수들을 나눠보면
setUpPhysics(),createWalls(),crateBricks(),creatBall(),createPaddle() 이렇게 나눌 수 있습니다.
이 함수들을 만들거구요. 이 함수들은 main()함수에 넣어서 한꺼번에 Call을 할 겁니다. 앱을 많이 만들다 보면 아시겠지만 각 객체별로 함수를 만들고 필요에 따라 이 함수들을 Call하는 함수를 만들어서 구현하도록 디자인 하면 나중에 각 객체들을 콘트롤 하는데 좋습니다.

자 그럼 이렇게 대충 디자인을 하고 개발에 들어갈까요?
제일 먼저 각자 적당한 폴더를 만들고 그 폴더 안에 main.lua파일을 만드세요.
그리고 그 파일 안에 코딩을 시작하겠습니다.

각자 스타일에 따라 다르겠지만 저는 우선 뭔가 눈에 보이는 것 부터 하고 싶네요.
제일 중요한 공부터 만들어 보죠.

일단 createBall() 함수를 만듭니다.
function createBall()

end
이렇게 함수 선언을 하고 닫기 까지 합니다.
그리고 공을 그립니다.
function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
end

이렇게 하고 실행을 하면 화면에 아무것도 안 나옵니다. 저 함수를 call한 곳이 없기 때문이죠.

앞으로 만드는 함수들은 main()함수에 넣어서 관리를 할 테니까 main()함수를 만들어서 이 createBall()함수를 call 하세요.
function main()
    createBall()
end
그리고 main.lua 맨 아래에서 이 main() 함수를 call 하세요.
function main()
    createBall()
end

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
end

main()

자 이러면 화면 한 가운데에 반지름이 10픽셀인 하얀 공이 나올 겁니다.


여기까지 됐구요. 그러면 이제 공을 움직여 보겠습니다.
공을 움직이는 것은 코로나의 physics엔진을 사용하기로 했습니다.
이 물리엔진을 사용하려면 우선 physics를 require해야 합니다.
require ("physics")
그리고 physics를 시작합니다.
physics.start()
Corona DOC 카테고리에 있는 physics강좌를 보시면 아시겠지만 기본적으로 코로나 물리엔진은 디폴트 값으로 (0,9.8)의 중력 값을 가집니다.
벽돌깨기 게임은 중력값이 필요 없으므로 이를 0,0 으로 세팅합니다.
physics.setGravity(0,0)
여기서 physics setUp도 함수로 따로 관리하겠습니다.
require만 빼놓고 나머지 두라인을 setUpPhysics() 함수 안에 넣고 이 함수를 main()함수에서 Call 하겠습니다.
require ("physics")

function main()
    setUpPhysics()
    createBall()
end

function setUpPhysics()
    physics.start()
    physics.setGravity(0,0)
end

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
end

main()
이제 이 공에 addBody를 하고 움직여 보겠습니다.
createBall()안에 공을 addBody 하는 코드를 아래와 같이 집어 넣습니다.
physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})

그리고 실행하면 아무 변화가 없습니다. 여기서 setGraivity값을 0,0에서 0,9.8로 바꿔볼까요?

그러면 공이 아래로 떨어질 겁니다. y 좌표에 9.8의 중력값이 적용 됐기 때문입니다.
이 중력값 적용 대신에 공을 움직일 수 있는 방법은 뭘까요?
setLinearVelocity를 이용하면 됩니다.
다시 중력값을 0,0으로 고치고 createBall() 안에 ball:setLinearVelocity(75, 150) 코드를 추가로 집어 넣습니다.

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
    physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})
    ball:setLinearVelocity(75, 150)
end

그러면 이 공이 오른쪽 아래 방향으로 움직이게 됩니다.


지금은 공이 화면 밖으로 나가버리고 끝이 납니다.
오늘 소스는 아래와 같습니다.
require ("physics")

function main()
    setUpPhysics()
    createBall()
end

function setUpPhysics()
    physics.start()
    physics.setGravity(0,0)
end

function createBall()
    ball = display.newCircle( display.contentWidth / 2, display.contentHeight / 2, 10 )
    physics.addBody(ball, "dynamic", {friction=0, bounce = 1, radius=ballRadius})
    ball:setLinearVelocity(75, 150)
end

main()

벽을 만들어 놓고 이 벽에 튕기게 하면 공이 화면안에서 계속 튀면서 다니겠죠?

다음 시간엔 벽을 만들고 이 벽들을 addBody해서 공이 튕기도록 하겠습니다.
벽뿐만 아니라 block과 paddle도 만들어서 이 앱에서 필요한 모든 객체들을 화면에 나타나도록 하겠습니다.

그럼 다음 시간에 뵈요.
반응형

드래그 하기 기초

2011. 11. 25. 01:23 | Posted by 솔웅


반응형

오늘은 Thanks Giving day라서 휴일입니다.

사무실에 안 가고 집에서 글을 쓰게 됐네요.
근데 여유있게 글을 쓸 상황은 아니군요.

점심 초대를 받아서 좀 있다 나가야 하거든요.

오늘은 간단하면서도 아주 자주 쓰이는 테크닉을 정리하겠습니다.

앱을 만들 때 특히 게임 같은 앱을 만들 때 드래깅 기술을 많이 사용하게 됩니다.
가장 기본적으로 알아 둬야 할것은 아래와 같습니다.
touch 이벤트 리스너를 사용하기
began에서 현재 위치 저장하기
moved에서 현재 위치 바꿔주기

이 정도만 확실하게 알아 두시면 됩니다.


스크린에 사각형을 그리고 저 사각형을 드래그 하는 앱입니다.
아래 소스코드 참조하세요.
-- create object
local myObject = display.newRect( 0, 0, 100, 100 )
myObject:setFillColor( 255 )

-- touch listener function
function myObject:touch( event )
    if event.phase == "began" then

        self.markX = self.x -- store x location of object
        self.markY = self.y -- store y location of object

    elseif event.phase == "moved" then

        local x = (event.x - event.xStart) + self.markX
        local y = (event.y - event.yStart) + self.markY
       
        self.x, self.y = x, y -- move object based on calculations above
    end
   
    return true
end

-- make 'myObject' listen for touch events
myObject:addEventListener( "touch", myObject )

우선 흰색 사각형을 그리구요.
그 다음 touch 이벤트 리스너에서 호출할 함수를 만듭니다.
맨 아래 부터 부시면 이 myObject라는 객체에 touch 이벤트 리스너를 달았다는 것을 잘 보세요.

이제 myObject라는 함수를 보겠습니다.

began 일 때 해당 객체(myObject)의 위치를 정합니다.
moved일 때 began일 때의 위치 event.xStart 를 현재 위치에서 빼고 그러니까 이동 거리를 구하고 객체의 위치 self.markX를 더해 줍니다.
그리고 그 위치값을  myObject 객체의 위치값으로 대입합니다.
그러면 이동하게 됩니다.

아주 간단합니다. 하지만 반드시 알고 있어야 하는 겁니다.

반응형


반응형

코로나 SDK (Corona SDK)에서는 화면 전환을 위해서 transition.to를 사용합니다.
이것만 사용해서도 아주 다이나믹한 효과를 낼 수 있는데요.

Ricardo Rauber Pereira라는 개발자가 아주 보기 좋은 화면 전환 클래스들을 구현해서 이를 공개했습니다.
director.lua라는 파일인데 구글에서 써핑하셔도 쉽게 찾으실 수 있습니다.

이 클래스에는 총 12개의 화면 전환 효과가 있습니다.
("moveFromRight","overFromRight","moveFromLeft","overFromLeft",
"moveFromTop","overFromTop","moveFromBottom",
"overFromBottom","crossfade","fade","flip","downFlip"
)

파일은 아래에 제가 압축해서 올린 샘플파일에 있습니다. 받아보세요.
제가 가지고 있는 건 버전 1.3 이네요.
 - Version: 1.3
 - Made by Ricardo Rauber Pereira @ 2010
 - Blog: http://rauberlabs.blogspot.com/
 - Mail: ricardorauber@gmail.com
이 파일을 보면 블로그와 이메일도 있으니까 참조하시구요.
저 블로그에 가 보니까 지금은 1.4 버전이 나왔네요.
이 개발자는 브라질 사람인가 봐요.

리카르도는 소스코드를 무료로 공개하는 대신 블로그에서 Donation을 받네요.
자신의 노력의 결과물을 나눠서 다른 사람이 쉽게 사용할 수 있도록 공개하는 개발자들에게는 성의 표시를 해줘도 좋겠죠? 여유가 있으면 Donation을 해도 좋고 블로그에 광고가 있으면 광고를 눌러줘도 되고 아니면 메일이나 트위터로 고맙다고 메세지 날려줘도 좋을 거예요.. :) 그리고 리카르도가 만든 앱을 다운받거나 구매하는 것도 ....


이 director.lua를 이용하는 방법은 아래와 같습니다.

http://www.youtube.com/watch?v=RAktnj7nwos

1. main.lua가 있는 폴더에 director.lua를  복사해 넣는다.
2. require 한다 (local director = require("director"))
3. 그룹을 만든다. local mainGroup = display.newGroup()
4. directorView를 그룹에 넣는다. mainGroup:insert(director.directorView)
5. 화면 전환 할 때 changeScene을 이용한다. director:changeScene("scene2","downFlip")
-> 첫번째 파라미터는 다음 화면을 보여줄 파일 이름입니다. 이 경우 scene2.lua가 되겠죠.
-> 두번째 파라미터는 화면 전환 효과 입니다. 아까 봤던 8가지 중에 아무거나 한가지를 넣으시면 됩니다.

그 다음은 전환될 화면 (scene2.lua)에서는 어떻게 해야 하는지 보겠습니다.
1. 모듈 선언을 한다. module(..., package.seeall) -> 이것은 Corona SDK에서 모듈 사용하는 규칙입니다.
2. localGroup이라는 그룹을 만든다. local localGroup = display.newGroup()
3. 함수 new()를 만든다. function new()    return localGroup end
4. 함수 new()는 localGroup을 return한다.
5. 다른 화면으로 전환하려면 마찬가지로 changeScene을 이용한다.
director:changeScene("nextfile","downFlip")

이것만 지켜주시면 됩니다.


우리의 리카르도가 공개한 샘플 입니다. 파일은 아래에 있습니다.


파일을 열어보니까 제가 못 봤던 book이라는 폴더가 있네요.
실행해 보니까 SlideView 관련된 클래스 인가봐요.
화면 전환 뿐만 아니라 Slideview 관련해서도 편리한 기능을 제공하나봅니다.

이건 제가 공부 좀 해서 다음 기회에 소개해 드릴께요.

오늘은 팁으로 제가 이 director.lua 클래스를 사용할 때 화면 전환 효과를 랜덤하게 처리하고 있는데요.

그 소스를 소개해 드릴께요.

일단 randomScreen.lua 파일을 아래처럼 만들어 놓습니다.
module(..., package.seeall)
function init()
    RandomScreen()
end

function RandomScreen() 
    changes = {"moveFromRight","overFromRight","moveFromLeft","overFromLeft",
                "moveFromTop","overFromTop","moveFromBottom",
                "overFromBottom","crossfade","fade","flip","downFlip"}
    selectScreen = math.random(1,12)
    screenChange = changes[selectScreen]
    return screenChange   
       
end


그리고 main.lua에서 이 파일을 require합니다.

local changeScreen = require("randomScreen")

그리고 화면전환 하기 직전에 이 랜덤한 화면전환 기능 값을 받습니다.

randomScreen = changeScreen.RandomScreen();

그리고 director.lua의 cangeScene을 이용할 때 이 값을 넣습니다.

director:changeScene("파일이름", randomScreen);

이렇게 하면 랜덤하게 화면 전환 효과들을 이용할 수 있습니다.
저는 앱을 만들 때 이렇게 처리하면 편하더라구요.

오늘의 팁이였구요.

director.lua에서 파라미터 넘기기나 팝업 기능 사용하기는 직접 소스 보면서 해 보세요.

그렇게 어렵지 않을 겁니다.

그럼...
반응형


반응형
오늘은 Sprite Sheet과 timer와 관련된 팁을 다루겠습니다.

오늘 다룰 소스는 Peach Pellen 이라는 개발자가 자기 친구가 앱을 개발한다는 얘기를듣고 도움이 될까 해서 만든 주사위놀이 샘플 코드입니다. 그리고 그 코드를 공개해서 다른 분들도 도움이 되길 바란다고 하네요.

아래 링크로 가시면 관련 글을 보실 수 있습니다.

http://techority.com/2011/11/22/rolling-the-dice/

저 싸이트로 가시면 샘플 코드를 다운 받으실 수 있구요.
아래 저도 그 코드를 올려 놓을 테니까 여기서 곧바로 다운 받으셔도 됩니다.

실행 화면은 아래와 같습니다.


오른쪽 위에 하얀 사각형을 누르면 주사위가 막 돌다가 멈춥니다.
그러면 터미널에 두 주사위의 합이 출력 됩니다.

그런데 이상한건 제가 집에서 이 소스코드를 실행 했을 때는 두 주사위가 항상 같은 숫자만 나왔거든요.

그래서 다른 숫자가 나오도록 소스를 좀 수정했습니다.
그리고 터미널뿐만 아니라 화면에도 합계가 텍스트로 표시 되도록 했구요.

그런데 회사와서 이 코드를 돌려보니까 두 주사위가 항상 같은 숫자가 나오는게 아니더라구요.

집에 있는 컴퓨터는 윈도우고 회사에 있는건 맥이라서 그런가?

하여간 아래 코드는 제가 조금 수정한 코드입니다. 다운 받은 파일에 있는 소스랑은 아주 조금 다를거예요.


--Hide the status bar => 스테이터스 바 감추기
display.setStatusBar(display.HiddenStatusBar)

--Require sprite => 스프라이트 require하기. 스프라이트는 director.lua나 movieclip.lua 처럼 별도의 파일로 제공되는게 아니라 corona sdk 에 내장된 클래스 입니다. 따로 파일을 구하실 필요가 없습니다.
require "sprite"

=> 스크린 너비와 높이 구해 놓기
_W = display.contentWidth;
_H = display.contentHeight;

--The background image => 배경화면 표시하고 합계를 표시할 텍스트 선언하고 위치 정해 줌
local bg = display.newImage( "bg.png" )
local total = display.newText("", _W/2, _H-(_H/7), native.systemFontBold, 30)

--Total sum of dice after a roll => 주사위 합계 담을 변수
local diceTotal = 0

--States whether or not the dice may be rolled => 주사위 돌리기 가능한 상태인지 아닌지 콘트롤 할 변수
local canRoll = true

--Sprite Setup => 스프라이트를 셋업 함
local diceSheet = sprite.newSpriteSheet( "dice.png", 64, 64)

주사위로 사용할 이미지는 이 이미지 입니다.
일단 이 이미지를 diceSheet이라는 이름으로 만듭니다. 이 프레임의 크기는 64X64 입니다.

local diceSet = sprite.newSpriteSet( diceSheet, 1, 6 )
sprite.add( diceSet, "dice", 1, 6, 195, 0)
sprite.add( diceSet, "dice2", 1, 6, 210, 0)

위 diceSheet를 diceSet이라는 스프라이트 세트에 담습니다. 시작하는 프레임은 1 이고 총 6개 프레임이 있습니다.
이 diceSet를 add합니다. 신택스는 아래와 같습니다.
sprite.add( spriteSet, sequenceName, startFrame, frameCount, time, [loopParam] )
첫번째는 diceSet를 dice라는 sequenceName으로 add를 하는데 시작 프레임 1이고 전체 프레임 count는 6 입니다. time은 195입니다. 이것은 195ms에 이 diceSet을 한번 돌린다는 겁니다.
두번째 줄은 dice2인데 dice와 모두 같고 이 time이 210 입니다.
두 주사위가 6개 프레임의 돌아가는 시간이 다르니까 각각 다른 프레임이 나오겠네요.
윈도우에서 항상 똑 같은 주사위 이미지가 나온게 제가 착각한 건가 본데요?
혹시 여러분 윈도우에서 한번 돌려보시구 어떻게 나오나 잘 살펴 보세요.
저도 집에 돌아간 다음에 다시 돌려 봐야 겠네요.

--First Die
local dice = sprite.newSprite( diceSet )
dice.x = 120
dice.y = 200
dice:prepare("dice")

첫번째 sprite를 dice라는 변수에 담고 화면의 위치를 정해 줍니다.
스프라이트 쉬트 사용하는 순서가 이렇게 되네요.
sprite.newSpriteSheet => sprite.newSpriteSet => sprite.add => sprite.newSprite => spriteInstance:prepare => spriteInstance:play => spriteInstance:pause
따로 정리해서 외워도 좋겠는데요.

--Second Die
local dice2 = sprite.newSprite( diceSet )
dice2.x = 200
dice2.y = 300
dice2:prepare("dice2")

두번째 스프라이트 dice2를 선언합니다.

--Roll button
local rollBtn = display.newImage("die.png")
rollBtn.x = 280
rollBtn.y = 40

=> 버튼 이미지를 표시합니다.

--End Roll Function
local function endRoll()
    dice:pause()
    showTotal();
end

첫번째 주사위 play를 중지시키고 showTotal함수를 실행합니다.

local function endRoll2()
    dice2:pause()
    showTotal();
end

두번째 주사위 play를 중지시키고 showTotal함수를 실행합니다.

function showTotal()
    diceTotal = dice.currentFrame + dice2.currentFrame
    print(diceTotal) --Print the result in the terminal
    total.text="Total : " .. diceTotal;
    canRoll = true --Allow the dice to be rolled again
end

현재 프레임 = 주사위 눈 입니다. dice와 dice2의 현재 프레임을 구해서 더하면 주사위 두 눈의 합이 됩니다.
이것을 터미널에 print하고 스크린에도 표시합니다.
canRoll은 true로 세팅해서 버튼을 누르면 주사위가 돌아갈 수 있도록 합니다.

--Roll function
local function rollDice()
    if canRoll == true then
        canRoll = false --Prevent dice from being rolled again before the current role is over
        dice:play()
        dice2:play()
        randomTime = math.random(1500, 3500)
        timer.performWithDelay(randomTime, endRoll, 1)
        randomTime2 = math.random(1500, 3500)
        timer.performWithDelay(randomTime2, endRoll2, 1)
    end
end
canRoll을 false로 해서 버튼을 눌러도 주사위가 새로 돌지 않도록 막아 놓습니다.
dice와 dice2를 play시킵니다.
랜덤값 (1.5초 ~ 3.5초)를 구해서 timer를 사용해서 그 시간만큼만 돌고 dice를 멈추게 합니다. 다음줄은 dice2를 멈추게 합니다.

rollBtn:addEventListener("tap", rollDice)

버튼에 이벤트 리스너를 달아서 누르면 rollDice가 실행 되도록 합니다.



이렇게 하면 위 그림처럼 화면에 주사위 눈의 합이 출력 됩니다.

원래 소스가 두 주사위의 눈이 각각 다르게 표시되도록 하는 거였다면 제가 수정한 코드는 괜히 수정한 거네요. 이미 원래 소스에 이런 경우를 생각해서 스프라이트에서 프레임 플레이 시간을 각각 다르게 해 준건데......

어쨌든 남의 소스를 보고 나름대로 고쳐보는게 프로그래밍 배우는데 아주 큰 도움이 됩니다.

여러분들도 이 코드를 이용해서 이것저것 고치거나 기능을 덧붙이거나 하면 빨리 실력이 늘고 이 코드도 진짜로 여러분의 코드가 되고 여러분의 자산이 될 거예요.

그럼 다음 시간에 뵐께요......
반응형

스티브 잡스 추모 앱

2011. 11. 21. 23:02 | Posted by 솔웅


반응형

오늘은 누군가가 올린 스티브 잡스 추모 앱을 가지고 공부 해 보겠습니다.

이 앱의 진행은 처음에 애플 인트로 화면이 나오고 조금 있다가 두번째 화면이 나옵니다.
세번째 화면은 로딩 화면이고 그 다음 화면이 스티브 잡스 분향소 이미지 화면입니다.
이 분향소 화면에는 촛불이 있고 유저가 좌측에 있는 버튼을 누르는 동안엔 이 촛불에 불이 켜지게 됩니다.

dsrawat이라는 분이 만들어서 배포한 것인데요...
소스도 같이 공개 했습니다.

이제 부터는 여러 공개된 샘플 코드를 가지고 공부할 생각입니다.
그 첫번째 대상이 스티브 잡스 추모 앱이 됐습니다.

전체 리소스는 아래 파일을 다운 받으시면 됩니다.





main.lua 파일 소스 먼저 보시면....

 --file main.lua

--To Hide the status bar
display.setStatusBar( display.HiddenStatusBar )
--==> 아이폰의 status bar를 없애줍니다.


-- For Importing director.lua class
local director = require("director")
local movieclip = require("movieclip")
uiButton = require("buttonLib")

--==> 화면전환을 위한 클래스 director.lua와 애니메이션 효과를 위한 movieclip.lua 그리고 버튼을 사용하기 위해서 buttonLib.lua 클래스 파일을 require 합니다.

-- For Creating a main group
local mainGroup = display.newGroup()

--==> 객체들을 그룹화 하기 위해 newGroup을 만듭니다. 특히 director 클래스를 사용할 때는 이렇게 그룹화를 해서 display객체들을 관리해야 좋은 화면 전환 효과를 낼 수 있습니다.

-- Start of Main function
local function main()

--==> main() 함수입니다.

     -- Adding the Group from director.lua class
    mainGroup:insert(director.directorView)

--==> 아까 만든 그룹에 director.directorView를 담습니다.
   
local function flash1()
    local myLogo = display.newImage("menubackground/splash.png")
     myLogo.x = 240
    myLogo.y = 160
    myLogo.alpha = 0
    transition.to( myLogo, {time=500, delay=500, alpha=1})
    transition.to( myLogo, {time=500, delay=3500, alpha=0})
end
    timer.performWithDelay(0, flash1)

--==> flash1() 함수입니다. 여기에서는 첫번째 화면 이미지인 splash.png를 표시해 줍니다. 위치를 설정해주고 transition.to로 서서히 이미지가 나타나는 효과를 줍니다.

타이머를 달아서 delay는 0으로 줘서 곧바로 실행하게 하고 이때 flash1함수를 콜해 줍니다.

local function flash2()
    local myLogo1 = display.newImage("menubackground/apple.png")
     myLogo1.x = 240
    myLogo1.y = 160
    myLogo1.alpha=0
    myLogo1.alpha = 0
    transition.to( myLogo1, {time=500, delay=500, alpha=1})
    transition.to( myLogo1, {time=500, delay=3500, alpha=0})
 end
    timer.performWithDelay(3500, flash2)

--==> flash2() 함수입니다. 첫번째 이미지가 서서히 사라지고 난 후 두번째 이미지인 apple.png를 서서히 나타나게 합니다. 그리고 서서히 사라지게 합니다.



    local changeScene = function()
        director:changeScene("loadmainmenu", "fade")
    end

--==> 이 함수는 director클래스의 fade효과를 주면서 loadmainmenu.lua 클래스를 실행 하도록(화면전환) 하는 함수입니다.

    timer.performWithDelay(8000, changeScene)
    --To Change scene withoutany effects
    return true

--==> main()함수의 마지막 부분입니다. 8초 후에 changeScene 함수를 실행 합니다.
end



-- For Running Main Function
main() --==> main함수를 실행합니다.

이 메인 함수가 실행되면 각각의 timer가 실행 될 겁니다. 첫번째는 곧바로 실행되는 첫번째 타이머가 flash1 함수를 실행할 것이고 두번째 타이머는 3.5초 후에 flash2함수를 실행 할 겁니다.

그리고 세번째는 8초후에 실행되는 타이머로 changeScene 함수를 콜해서 다음 화면으로 넘어가도록 할 겁니다.

loadmainmenu.lua 파일 입니다.


module(..., package.seeall)

--==> 처음에 모듈 선언을 함으로서 외부파일에서 불려질 수 있는 모듈임을 알립니다.

-- Main function - MUST return a display.newGroup()
function new() --==> director 클래스에서 사용되는 모듈은 new()함수 내에 위치해야 됩니다. 그리고 display.newGroup()을 return 해야 합니다.
    local localGroup = display.newGroup() --==> 그룹을 만듭니다.
   
    local theTimer
    local loadingImage

--==> theTimer와 loadingImage 변수를 선언만 해 둡니다.
   
    local showLoadingScreen = function()
        loadingImage = display.newImageRect( "menubackground/loading.png", 480, 320 )
        loadingImage.x = 240; loadingImage.y = 160
       
        local goToLevel = function()
            loadingImage:removeSelf()
            director:changeScene( "mainmenupage" )
        end
       
        theTimer = timer.performWithDelay( 1000, goToLevel, 1 )
   
    end

--==> showLoadingScreen 함수입니다. loading.png 이미지 파일을 보여줍니다.

이 함수 안에는 goToLevel이라는 로컬 함수가 있습니다.

이 로컬 함수에서는 loadingImage를 없애고(removeSelf()) director클래스의 changeScene 함수를 이용해서 mainmenupage.lua 클래스에 있는 것을 표현해 줍니다.

theTimer변수에 타이머를 답니다. 1초 후에 goToLevel 함수를 호출하고 이는 1회만 실행 합니다.

 
    showLoadingScreen()

--==> showLoadingScreen()함수를 호출해서 실행 하도록 만듭니다.
   
    unloadMe = function()
        if theTimer then timer.cancel( theTimer );
       
         end
       
        if loadingImage then
            loadingImage:removeSelf()
            loadingImage = nil
        end
    end

--==> unloadMe함수는 theTimer가 있으면 이를 cancel하고 loadingImage가 있으면 이를 없애고 메모리도 풀어 줍니다.
   
    -- MUST return a display.newGroup()
    return localGroup --==> localGroup을 return 합니다. director.lua로 화면전환을 할 때 반드시 해 줘야 합니다.
end

--==> 이렇게 하면 로딩 화면이 1초 정도 떠 있다고 mainmenupage.lua에 있는 내용들이 실행 되겠죠?



 --==> mainmenupage.lua 파일입니다.

module(..., package.seeall) --==> 모듈선언입니다.

local menuGroup=display.newGroup() --==> 그룹을 만듭니다.

 local ui = require("ui") --==> ui.lua 클래스를 이용하기 위해 require합니다.
  local movieclip = require "movieclip" --==> movieclip.lua를 이용하기 위해 require합니다.
  local candle --==> candle변수를 선언합니다.

function new() --==> director.lua를 사용하려면 new()함수를 사용합니다.

local menuGroup=display.newGroup() --==> 위에서 선언했는데 중복되서 선언했네요. 제 생각엔 위에 선언한 것을 지워도 될 거 같습니다.
 -- Background
local background = display.newImage("menubackground/jobs.png")
menuGroup:insert(background)
local localGroup = display.newGroup()
--==> jobs.png 이미지를 display하고 이를 menuGroup에 넣습니다.
 그리고 localGroup을 만듭니다.

candle = movieclip.newAnim{ "candle/candle0.png","candle/candle.png", "candle/candle1.png","candle/candle2.png","candle/candle3.png"}
candle.x=220
candle.y=220
candle.xScale=.4
candle.yScale=.4
candle:stopAtFrame(1)
--==> 촛불 애니메이션 효과를 줍니다. candle 변수에 newAnim을 이용해서 5개의 촛불 이미지를 담습니다. 그리고 위치와 크기를 설정하고 1번 프레임에 stop시켜 놓습니다.
      
    --- ********************************************************* ---
    ---                            BUTTONS                              ---   
    --- ********************************************************* ---
        -->> Set Up "fire" Button with required variables

    --Custom function to execute on began press state
    local function fireBeganFunction()
        print("fire Began State Executed")
        candle:play()
       
    end
--==> candle애니메이션을 play() 시키는 함수입니다.

    --Custom function to execute on released press state
    local function fireReleasedFunction()
        print("fire Released State Executed")
        candle:stopAtFrame(1)
        new()
    end
--==> candle 애니메이션을 1번 프레임에 stop시키는 함수입니다.

    fireBTN = movieclip.newAnim({"fire.png", "fire1.png"}) -- The sequence of images : Should contain a default and pressed state image
    fireBTN.x = 40          
    fireBTN.y = 260
         fireBTN.xScale=.5
        fireBTN.yScale=.5       
--==> fireBTN 변수에 두개의 이미지를 담습니다. (newAnim을 이용해서요...)            
     fireBTN.beganFunction = fireBeganFunction       -- The pointer to the custom function to execute on button began state (If any) Optional
    fireBTN.releasedFunction = fireReleasedFunction -- The pointer to the custom function to execute on button began state (If any) Optional
    fireBTN.removeListenerOnTouch = true            -- If true the library will automatically remove the event listener for you when it is done.
   menuGroup:insert(fireBTN)
--==> menuGroup에 fireBTN을 insert합니다.

    fireBTN:addEventListener("touch", uiButton.handleEvent)
fireBTN에 리스너를 달아서 터치를 하면 위에 선언했던 fireBeganFunction과 fireReleasedFunction을 실행하도록 합니다.

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

clean = function()
 fireBTN:removeEventListener("touch", uiButton.handleEvent)
 menuGroup:removeSelf()
end
--==> fireBTN의 리스너를 없애고 메뉴그룹에서도 없애는 함수입니다.

     -- MUST return a display.newGroup()
    return localGroup
end
   
========= o ======= o ======== o ======== o =========

다른 사람이 작성한 코드를 보니까 나 같으면 이렇게 했겠다라는 부분도 있고 내가 모르던 방법을 사용해서 새로 배울 수 있는 부분도 있고 그러네요.

시간이 되면 똑같은 앱을 제 방식대로 한번 만들어 보고 싶네요.
만약 이런 앱을 만드는 의뢰를 받았다면 나는 어떻게 만들지 그리고 이 사람이 만든 코드랑 어떻게 다른지 또 어떤점이 더 낫고 어떤 점은 내가 배울 점인지 좀 더 확실하게 알 수 있도록......

첫번째 샘플 코드 분석은 이렇게 마치겠습니다.

질문이나 의견 있으면 언제든지 기탄없이 댓글로 남겨 주세요...

그럼...

 

반응형

ui 버튼이용 하기 ui.newButton

2011. 11. 18. 23:52 | Posted by 솔웅


반응형

오늘은 ui.lua 클래스 파일을 이용해서 이미지 버튼을 만들겠습니다.

아래 파일을 먼저 받으세요.

이 파일을 열어서 분석해 보시면 newButton과 newLable을 이용할 수 있도록 만든 부분을 보실 수 있으실 거예요.




버튼을 만들면 이렇게 나옵니다.

소스를 볼까요?

display.setStatusBar (display.HiddenStatusBar)

require "ui"

local background = display.newImage ("background.png")

peachbutton = ui.newButton{
default = "peach.png",
over = "peach2.png",
x = 80,
y = 200,
}

local textFieldButtonPress = function( event )
    print("Button Pressed")
end

txtFieldButton = ui.newButton{
        default = "peach.png",
        over = "peach2.png",
        onPress = textFieldButtonPress,
        text = "textField",
        x = 240,
        y = 200,
        size = 16,
        emboss = true
}

local titleLabel = ui.newLabel{
        bounds = { 15, 5, 290, 55 },
        text = "Native Display Objects",
        font = native.systemFontBold,
        textColor = { 240, 240, 90, 255 },
        size = 24,
        align = "center"
}

저 위에 이미지에 newLabel을 하나 추가했어요.

사용법은 간단합니다.
우선 ui.lua를 main.lua 파일이 있는 폴더에 같이 넣구요.
require "ui"를 합니다.

코드를 보시면 우선 백그라운드 이미지를 깔았고
첫번째 이미지 버튼을 만들었습니다.
이 버튼의 평상시 상태는 default로 설정된 peach.png 이미지가 나옵니다.
그리고 이 버튼을 누르면 peach2.png 이미지로 바뀌게 됩니다.

그리고 textFieldButtonPress 라는 함수를 만들었는데 이건 두번째 버튼을 클릭하면 호출되는 함수 입니다.

두번째 이미지 버튼은 첫번째 이미지 버튼에 없는게 몇개 있습니다.
우선 onPress = 는 눌렀을 때 함수를 호출합니다.
그리고 text = 버튼 위에 텍스트를 넣을 수 있구요.
size는 텍스트 사이즈 입니다.

이 두번째 이미지를 누르면 textFieldButtonPress 함수가 실행 됩니다.

그리고 마지막 번엔 newLabel 을 넣었습니다.
처음에 bounds ={} 를  이해하려면 ui.lua의 181번째 줄을 보시면 됩니다.

        local bounds = params.bounds
        local left = bounds[1]
        local top = bounds[2]
        local width = bounds[3]
        local height = bounds[4]

이렇게 돼 있는데요. 4개의 인수들이 있고 각각 왼쪽 위 너비 높이를 나타냅니다.

이렇게 ui.lua를 이용해서 이미지 버튼과 이미지 레이블을 사용하는 법을 알아봤습니다.

전체 소스 파일은 위에서 받으 실 수 있습니다.

코로나에서는 이렇게 ui.lua를 사용하지 않고 Widget API에서 버튼 기능을 제공합니다.
하지만 이것은 Corona build 2011.646 이후 버전에서만 가능합니다.

미리 한번 살펴 보면 신택스는 아래와 같습니다.
widget.newButton( [options] )
샘플예제를 보면요.
local widget = require "widget"
 
    local onButtonEvent = function (event )
        if event.phase == "release" then
            print( "You pressed and released a button!" )
        end
    end
 
    local myButton = widget.newButton{
        id = "btn001",
        left = 100,
        top = 200,
        label = "Widget Button",
        width = 150, height = 28,
        cornerRadius = 8,
        onEvent = onButtonEvent
    }
 
    -- Insert button into a group:
    someGroup:insert( myButton.view )
 
    -- Change the button's label text:
    myButton:setLabel( "My Button" )
 
    -- Remove the button
    display.remove( myButton )

widget을 rerquire 하구요. (이 widget은 ui.lua처럼 별도의 파일이 아니라 corona SDK 가 자체적으로 제공하는 겁니다. 물론 646 이후 버전에서요. 그러니까 별도의 파일을 구해서 폴더에 넣을 필요가 없습니다.)

widget.newButton{} 으로 구현합니다.
안에 내용은 id,left,top,lable,width 뭐 이런것들이 있구요.
아주 다양한 파라미터들이 있습니다.
이건 나중에 이 기능을 제가 사용할 수 있을 때 자세히 살펴 보겠습니다.

onEvent = 에 클릭했을 때 실행될 함수를 넣습니다.

아직 안정성이 검증되지 않은 코로나 SDK 버전에 있는 내용입니다.
지금은 Build 2011.591 버전이 공식적으로 배포되고 있습니다.

나중에 646 버전이 공식 인증된 공개 버전이 되고 제가 그 버전을 사용하게 되면 좀 더 자세히 정리해 볼까 합니다.

그럼 즐거운 주말 되세요.
반응형


반응형

오늘은 timer.performWithDelay를 이용해서 시간을 표시하는 소스를 보겠습니다.
일단 1초 단위로 화면을 갱신해 보일 겁니다.

이 소스를 조금 더 손 보시면 스탑와치도 가능 할 거예요.


소스를 볼까요?

display.setStatusBar(display.HiddenStatusBar)

_W = display.contentWidth;
_H = display.contentHeight;

local bg = display.newRect( 0, 0, 320, 480 )

local secsText = 00
local minsText = 0

local timeText = display.newText(minsText.. ":0" ..secsText, _W/2-60, _H/2-60, "Helvetica", 60)
timeText:setTextColor(43,100,154)

local function updateTime (event)
secsText = secsText + 1

if secsText < 10 then
secsText = "0" ..secsText
elseif secsText > 59 then
secsText = 00
minsText = minsText+1
end
timeText.text = minsText .. ":" ..secsText
end
timer.performWithDelay(1000, updateTime, 0)

처음 세 줄은 설명을 건너 뛰겠습니다.

bg 변수에 스크린 크기만한 사각형을 만듭니다.
(칼라가 지정 안 됐으니 디폴트 색인 흰색이 그려질 겁니다.)
그리고 초를 담을 변수와 분을 담을 변수를 만듭니다.

그리고 시간을 표시할 text를 만들고 칼라를 지정합니다.

그 다음에 updateTime 함수가 있습니다
파라미터로 event를 받는 걸로 봐서 어떤 이벤트 리스너에서 호출 할 겁니다.

일단 함수 내용을 볼까요?

위에 초를 담을 변수 secsText에 1을 더합니다.
그러니까 이 함수를 호출할 때마다 1씩 더해 지겠네요.

다음에 secsText가 10 이하이면 sectText 앞에 0을 붙입니다.
즉 secsText가 1 이면 여기서 01로 변환이 됩니다.
그리고 secsText가 59보다 크면 값을 00으로 바꾸고
minsText에 1을 더합니다.

그리고 timeText.text에 minsText:secsText 값을 할당합니다.

여기까지가 함수 updateTime의 내용입니다.

마지막 줄은 이 함수를 1초 단위로 부르는 timer 입니다.

이 소스는 9분 59초까지 1초 단위로 계속 올라가겠네요.
10분이 되면 다시 0:00이 되겠구요.

이 코드를 조금 변환하면 밀리세컨드부터 시간단위까지 나오게 할 수 있겠죠?
버튼을 만들어서 이 버튼을 누르면 updateTime을 불러오는 timer를 멈추게 하고

뭐 이런 기능들을 넣으면 stopWatch 앱이 될 겁니다.

직접 해 보시면 많이 도움이 되실 거예요.

그럼...
반응형


반응형

오늘은 버튼을 누르면 다음 화면이 나오는 기능을 알아보겠습니다.

기본적으로 이 기능은 API 내에 있는 Animation - transitions 를 사용합니다.
(저는 실제 앱 개발에서는 director.lua 클래스를 사용하고 있지만 이 클래스도 기본적으로 transitions 기능으로 구성 돼 있습니다.  director.lua 클래스 사용법도 조만간 정리할 예정입니다.)

transitions에는 아래와 같은 네가지 메소드가 있습니다.
transition.to() : handle = transition.to( target, params )
예) transition.to( square, { time=1500, alpha=0, x=(w-50), y=(h-50), onComplete=listener1 } )

transition.from() : handle = transition.from( target, params )
예) transition.from( square, { time=1500, x=(w-50), y=(h-50), onComplete=listener } )

transition.dissolve() : transition.dissolve( src, dst, duration, delayDuration )

transition.cancel() : transition.cancel(tween)
예) local trans1 = transition.to(currentTarget, { time=400, y=upperLimit, transition=easing.outQuad}) 
transition.cancel(trans1)




위 그림은 화면을 클릭하면 다음 화면으로 바뀌는 앱입니다.
압축파일은 아래에 있습니다.


소스코드를 분석해 보겠습니다.

-- Views Handler App
-- Developed by Carlos Yanez
-- Hide Status Bar
display.setStatusBar(display.HiddenStatusBar)
-- Add Default View
defaultView = display.newImage('defaultView.png')
-- Swap View Function
lastView = {}
local function swapViews(current, new, from)
    lastView = current
    if(from == 'down') then
        transition.from(new, {y = new.height * 2, onComplete = removeLastView})
    elseif(from == 'up') then
        transition.from(new, {y = -new.height, onComplete = removeLastView})
    elseif(from == 'left') then
        transition.from(new, {x = -new.width * 2, onComplete = removeLastView})
    elseif(from == 'right') then
        transition.from(new, {x = new.width * 2, onComplete = removeLastView})
    end
end

-- Remove Last View
removeLastView = {}
function removeLastView()
    lastView:removeSelf()
end

-- Change View Event Function
local modes = {'up', 'down', 'left', 'right'}
local changeView = {}
local changed = false
function changeView:tap(e)
    if(changed == false) then
        secondView = display.newGroup()
        bg = display.newImage('secondViewBg.png')
        button = display.newImage('button.png', 40, 223)

        secondView.insert(secondView, bg)
        secondView.insert(secondView, button)
        secondView:addEventListener('tap', changeView)
       
        swapViews(defaultView, secondView, modes[math.random(1, 4)])
        changed = true
    else
        defaultView = display.newImage('defaultView.png')
        defaultView:addEventListener('tap', changeView)
       
        swapViews(secondView, defaultView, modes[math.random(1, 4)])
        changed = false
    end
end

-- Listener
defaultView:addEventListener('tap', changeView)
 
처음에 statusBar 없애고 defaultView.png를 display합니다.
그리고 lastView 테이블을 선언합니다.

다음엔 swapViews함수가 있습니다. 이 함수는 current,new,from 이렇게 세 인자값을 받습니다.
함수 안에서 하는 일은 아래와 같습니다.
1. 아까 선언했던 lastView 테이블에 current를 넣습니다.
2. from이 down,up,left,right인지 체크가호 그에 따라 new의 좌표를 바꿔줍니다.
    transition이 끝나면 removeLastView 함수를 실행합니다.

다음은 removeLastView 테이블을 선언합니다.
removeLastView함수가 있고 이 함수는 lastView:removeSelf() 를 수행합니다.

다음 단계를 보면요
modes 테이블에 up,down,left,right를 넣습니다.
changeView 테이블을 선언하고 changed를 false로 선언합니다.

다음엔 changeView함수가 있습니다. tap 이벤트 리스너에서 호출될 함수입니다.
이 함수에서 하는 일은 다음과 같습니다.
1. changed가 false이면 secondView 라는 새 그룹을 만들고 secondViewBG.png를 display합니다.
2. button.png를 적당한 위치에 display합니다.
3. secondView그룹에 bg와 button을 insert합니다.
4. secondView그룹에 tab이벤트 리스너를 달고 탭이 이뤄지면 changeView를 호출합니다.
5. swapViews를 호출합니다. 인자로는 defaultView,secondView,modes 중 랜덤하게 선택된 값을 넘겨 줍니다.
6. changed=true로 바꿉니다.
7. changed가 false가 아니면 defaultView에 defaultView.png를 넣습니다.
8. tap 이벤트 리스너를 달고 탭이 일어나면 changeView를 호출합니다.
9. swapViews 함수를 호출하고 4개 인자를 전달합니다.
10. changed를 false로 선언합니다.

그리고 맨 마지막 줄엔 defaultView에 탭 이벤트 리스너를 달고 탭 이벤트가 일어나면 changeView 함수를 호출합니다.

이렇게 하면 버튼을 누를 때마다 화면이 전환되는 효과를 줄 수 있습니다.
화면전환도 위,아래,좌,우 측으로 화면이 이동하면서 바뀝니다.

보시면 아시겠지만 실제로는 다른 화면이 두개 있는 것이 아니라 객체들의 위치를 바꾸면서 화면 전환 효과를 주는 겁니다.

안드로이드의 Activity개념과는 다릅니다.

이 transition기능을 이용해서 다양한 화면 이동 효과를 내 보세요.
반응형