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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형
안녕하세요?

오늘은 두개의 동영상을 소개해 드리겠습니다.
첫번 째 동영상은 Corona SDK의 테스트 담당 팀이 앱을 테스트 하는 장면입니다.

게임 앱인 것 같은데 Score가 올라가고 최고점수 저장하고 이것을 레벨별로 별도로 진행하고 하는 것들을 테스트 하는 겁니다.
Tap기능과 관련해서 iPhone에서는 에러가 나서 Corona SDK의 Tap을 이용한다고 하는데 카메라를 의식해서 약간은 홍보성 멘트인것 같습니다. ^^
보시면 점수 올라가고 또 이것을 최고 점수임을 판별해서 기록하고 하는 것을 별도로 테스트 화면을 만들어서 진행하고 있습니다.
저 화면은 실제 게임에서는 나오지 않겠죠?
프로젝트를 진행하려면 개발팀하고 테스트팀이 별도의 부서로 만들어져서 진행하는게 이상적이라고 생각합니다. (대부분의 한국식 프로젝트 진행에서는 그렇지 못한것이 현실이겠죠?)

그리고 이 화면에서와 같이 별도로 테스트를 위한 부분도 개발을 해서 여러 테스트팀원들이 테스트를 진행할 수 있도록 개발하는 것도 테스트 팀에서 해야 합니다.
테스트 팀에서는 단순 기능테스트부터 User 입장에서 얼마나 interesting 하고 Exciting 한가에 대한 테스트 그리고 내부적으로 불필요한 로직 혹은 메모리를 낭비하는 로직이 없는가 에 대한 소스 테스트 또 데이터 베이스를 사용하는 경우 데이터 베이스 테이블 설계부터 쿼리 커스터마이징 등에 대한 테스트도 진행되야 하구요.

그런데 대부분 이런 부분이 없는 것 뿐만이 아니라 이런 개념조차 없죠?
그래서 설계대로 개발을 하면 뭐 조금만 안되도 개발자에게 따가운 눈초리를 보내고.
개발자는 뭐 잘못한것도 아닌데 고개숙여야 되고....

하여간 프로젝트를 진행하다보면 이런 테스트에 대한 개념있는 매니저가 아쉬울 때가 많습니다.

약간 신세한탄조의 멘트였죠? :)

다음 동영상은 Corona SDK로 아마존의 Kindle Fire용 앱 빌드하는 방법을 설명한 동영상입니다.

앱스토어, 안드로이드 마켓에 이어 아주 잠재력있는 시장인 아마존 앱 시장이 생긴지 좀 됐죠?
Corona SDK에서 이 Kindle Fire용 앱 빌드 기능 제공한게 1~2개월 됐습니다.
한번 개발해서 앱스토어,안드로이드 마켓, 킨들파이어까지 3대 앱 시장용으로 빌드가 가능한 Corona SDK 정말 매력있습니다.

어떤 플랫폼에서도 사용 가능한 HTML5 모바일 앱과 Corona SDK 만 있으면 아주 효율적으로 모바일 시장에 경쟁력 있게 대응할 수 있을 것 같습니다.

참고 삼아 한번 보세요.
자세한 과정은 나중에 강좌 글로 올릴 생각입니다.


반응형


반응형
크리스마스 이브날 Build Up 된 CoronaSDK 2011.715 버전에서 Email, SMS 보내는 기능이 추가 됐습니다.

CoronaSDK의 단점인 Mobile Native 기능 사용에 많은 제한이 있었는데 하나 둘 지원을 해 주고 있습니다.

이 기능은 배포버전이 아니라 최신 Build 버전이기 때문에 유료 가입자만 사용할 수 있습니다.
CoronaSDK 2011.715 버전을 다운 받아서 upgrade 해야 하는데 유료 가입자만 이 것을 다운 받을 수 있거든요.

이메일과 문자메세지 보내는 기능 API를 살펴 보겠습니다.

기본 신택스는 아래와 같습니다.
native.showPopup(name)
native.showPopup(name, options)

샘플을 보겠습니다.

local options =
{
   to = "john.doe@somewhere.com",
   subject = "My High Score",
   body = "I scored over 9000!!! Can you do better?",
   attachment = { baseDir=system.DocumentsDirectory, filename="Screenshot.png", type="image" },
}
native.showPopup("mail", options)

options를 보면 들어가 있는 요소들은 받는이,메일제목,본문, 첨부 등이 있습니다.
만약에 메일 본문에서 HTML을 사용 가능하도록 하려면 아래와 같이 하면 됩니다.

local options =
{
   to = { "john.doe@somewhere.com", "jane.doe@somewhere.com" },
   cc = { "john.smith@somewhere.com", "jane.smith@somewhere.com" },
   subject = "My High Score",
   isBodyHtml = true,
   body = "<html><body>I scored over <b>9000</b>!!! Can you do better?</body></html>",
   attachment =
   {
      { baseDir=system.DocumentsDirectory, filename="Screenshot.png", type="image" },
      { baseDir=system.ResourceDirectory, filename="MyLogo.png", type="image" },
   },
}
native.showPopup("mail", options)

이 샘플에서는 받는이 이외에 참조인까지 넣었습니다. 그리고 isBodyHtml = true 를 해서 본문에 html 태그를 사용할 수 있도록 했습니다.

문자메세지 보내는 샘플은 아래와 같습니다.

local options =
{
   body = "I scored over 9000!!! Can you do better?"
}
native.showPopup("sms", options)

문자메세지 내용은 body에 넣으면 됩니다.
이러면 팝업윈도우에 주소록이 나와서 고를 수 있게 해 준다고 합니다.

그리고 여러명에게 보낼 때는 아래와 같이 하면 됩니다.

local options =
{
   to = { "1234567890", "9876543210" },
   body = "I scored over 9000!!! Can you do better?"
}
native.showPopup("sms", options)

각 파라미터에 대해 자세히 살펴보면 아래와 같습니다.

Parameters:

name
"mail"과 "sms" 둘 중 하나가 올 수 있습니다.

options
옵션들 입니다. 이 옵션들 중 사용하고 싶은 것만 사용하시면 됩니다.


메일과 관련해서는 아래의 프로퍼트들이 지원됩니다.

    options.to : 받는이의 이메일 주소. 한 개 이상 올 수 있습니다.
    options.cc : 참조인 이메일 주소. 한 개 이상 올 수 있습니다.
    options.bcc : 숨은 참조인 이메일 주소. 한 개 이상 올 수 있습니다.
    options.attachment : 첨부. 형식은 { baseDir=, filename= [, type=] }입니다. type 프로퍼티에는 "image" 같은 MIME type을 사용합니다. 여러 첨부 파일을 보내려면 이 테이블의 배열을 만들어야 합니다.
    options.body : 이메일의 내용입니다.
    options.isBodyHtml : true 나 false 값이 올 수 있으며 이메일 내용에 html 태그를 사용할 수 있는지 여부를 정해 줍니다. 디폴트로는 일반 텍스트(html을 사용하지 않는 경우)를 지원합니다.
    options.subject : 이메일 제목 입니다.

sms 와 관련해서는 아래와 같은 property들이 지원 됩니다.

    options.to : 받는이의 전화번호 입니다. 한 개 이상 사용할 수 있습니다.
    options.body : 문자메세지 내용입니다.

Returns:

result
result 값이 false 이면 popup 사용이 불가능하다는 것이거나 해당 기계에서 해당 기능을 사용할 수 없다는 것 입니다.

===== o ===== o ===== o ===== o ====== o ===== o=====
이메일과 sms 를 native.showPopup 을 사용해서 call 을 하면 전화기에서 지원하는 이메일과 문자보내기 화면이 뜰 겁니다. 옵션을 넣으면 그 옵션들이 해당 칸에 들어가게 됩니다.
옵션이 들어가도 유저들이 이를 수정할 수 있습니다. 그 부분은 coronasdk 앱이 아니라 해당 전화기의 앱이니까요.

sms 는 140문자 까지 됩니다. 모바일을 보면 sms 이외에 mms라는 기능도 있던데 이 기능은 아직 따로 지원이 안 되는 것 같습니다.

한번 더 언급하는데요. 이 기능은 CoronaSDK 2011.715 버전 이후에서 사용 가능합니다. 그리고 이 버전은 유료 가입자만 다운 받을 수 있구요.
그래서 아직까지는 유료 가입자만 이 기능을 이용할 수 있습니다.
무료로 SDK를 다운 받아서 연습하시는 분들은 이 버전이 공개될 때까지 기다리셔야 합니다.
아마 조만간 공개 되겠죠.
반응형


반응형
어제 세계 최고의 기획자로 부터 야바위 게임 스토리 보드를 전달 받았습니다.
기획자가 너무 훌륭해서 스토리 보드만 가지고도 충분히 코딩을 할 수가 있겠네요. ^^
기획자로부터 자세히 설명을 듣고 이렇게 스토리보드 까지 받았습니다.

이제 프로그래머는 나름대로 objects 들과 함수들에 대한 설계를 했을 테고 또 Flow Chart 를 그려서 어떻게 앱이 진행될지 그려 보았을 겁니다.

좀 더 완벽하게 준비하는 스타일의 개발자라면 Use Case 도 한번 정리 해 봤겠죠...
물론 이 내용들은 하나의 문서에 정리 돼 있겠구요.

이제 코딩을 시작하겠습니다.

display.setStatusBar(display.HiddenStatusBar)
-- Graphics-- [Background]
local bg = display.newImage('bg.png')

일단 아이폰에서 status bar 를 안보이도록 하구요.
백그라운드 이미지는 앱이 시작하면서 끝날때까지 유지 되니까 함수(function) 내가 아니라 이렇게 앱 시작하면서 display를 해 놓습니다.

그 다음엔 각 objects를 담을 변수들을 만듭니다.

-- [Title View]
local title; local playBtn; local creditsBtn; local titleView

여긴 게임 첫 화면에 나올 objects를 정리했습니다.
게임 제목과 플레이 버튼,크레딧 버튼이구요. 이 첫 화면 객체들을 담을 그룹으로 titleView를 사용할 겁니다.

-- [Credits]
local creditsView -- 첫 화면에서 나올 credit 이미지가 들어갈 변수입니다.

-- [Bank Credits]
local bank; local bankText

게임 화면에서 좌측 상단에 들어갈 숫자(bank)와 이미지(bankText) 변수 입니다.

-- [Shells]
local s1; local s2; local s3; local shells;

3개 shell에 대한 변수 이구요. 이 3개 shell 들을 담을 로컬그룹 변수 shells입니다.

-- [Ball]
local ball -- ball 이미지를 담을 변수입니다.
-- [Button Bar]
local buttonBar -- 아래 진한 색 네모를 표시할 이미지를 담을 변수입니다.
-- [Bet Button]
local betBtn     -- 게임 시작 버튼 이미지를 담을 변수 입니다.
-- [Message Text]
local msg        -- 왼쪽 아래 안내 문구를 담을 변수 입니다.
-- [GameView]
local gameView  -- Game 화면에 있는 모든 변수
-- [Alert]
local alert-- alert 이미지 담을 변수
local moveSpeed = 600  -- shell 움직이는 스피드
local totalMoves = 5      -- shell 움직이는 횟수

자 여기까지 야바위 게임에서 사용할 모든 objects에 대한 변수를 선언했습니다.
세계최고의 기획자가 스토리보드에서 언급한 모든 객체를 생성했구요.
또 프로그래머가 화면 전환할 때 사용하기 위해 각 화면별 objects들의 localGroup으로 사용할 변수들도 선언했습니다.
그리고 스피드,움직이는 횟수같은 개념적인 객체에 대한 변수까지 다 선언했습니다.
변수의 갯수가 총 20개네요.
20개의 objects들이 이 게임을 만들어 나갈겁니다.
출연자들이죠. 주연도 있고 조연도 있고...
이제 스토리보드 대로 (시나리오대로) 움직임을 만들 차례입니다.

그러기 위해 함수(메소드)를 선언합니다.

-- Functions
local Main = {}
local startButtonListeners = {}
local showCredits = {}
local hideCredits = {}
local showGameView = {}
local placeBet = {}
local randomShellMove = {}
local checkMovesLeft = {}
local revealBall = {}
local alert = {}

이 앱에서는 총 10개의 메소드가 사용됩니다.

-- Main Function
function Main()
    title = display.newImage('title.png', display.contentCenterX - 123, 40)
    playBtn = display.newImage('playBtn.png', display.contentCenterX - 25.5, display.contentCenterY - 10)
    creditsBtn = display.newImage('creditsBtn.png', display.contentCenterX - 40.5, display.contentCenterY + 45)
    titleView = display.newGroup(title, playBtn, creditsBtn)
  
    startButtonListeners('add')
end

첫번째 함수는 Main()함수입니다.
이 소스코드 맨 마지막을 보시면 이 메인 함수를 호출합니다.
Main() 부분이죠. 그러니까 이 앱은 이 Main()부터 시작합니다.
그러니까 첫화면에 display될 제목과 플레이/credit 버튼을 화면에 그립니다.
그리고 이 title화면의 객체들을 titleView라는 localGroup으로 그룹화 합니다.
(여기서 bg는 빠졌습니다. 왜냐하면 bg는 모든 화면에 다 등장할 거거든요.)

그리고 startButtonListeners()라는 함수에 add라는 인수를 전달하면서 호출합니다.

function startButtonListeners(action)
    if(action == 'add') then
        playBtn:addEventListener('tap', showGameView)
        creditsBtn:addEventListener('tap', showCredits)
    else
        playBtn:removeEventListener('tap', showGameView)
        creditsBtn:removeEventListener('tap', showCredits)
    end
end

메인에서 호출된 함수인데요.
메인에서 add를 인수로 던졌죠?
안에 내용을 보니까 add가 인수일 경우에는 플레이버튼과 크레딧 버튼에 리스너를 답니다.
플레이버튼을 누르면 showGameView() 함수를 실행하고 크레딧버튼을 누르면 showCredits() 함수를 실행하게 됩니다.
만약 인수가 add가 아니라면 이 두 리스너를 remove합니다.

우선 크레딧 버튼을 눌렀을 때 실행될 showCredits() 함수를 보겠습니다.

function showCredits:tap(e)
    playBtn.isVisible = false
    creditsBtn.isVisible = false
    creditsView = display.newImage('credits.png')
    transition.from(creditsView, {time = 300, x = -creditsView.width, onComplete = function() creditsView:addEventListener('tap', hideCredits) creditsView.x = creditsView.x - 0.5 end})
end

메인함수에서 크레딧 버튼을 누르면 실행되는 함수입니다.
처음에 플레이 버튼과 크레딧 버튼을 안 보이도록 만듭니다.
그리고 creditsView 이미지를 만들구요. 이 이미지를 transition합니다.
0.3초동안 왼쪽에서부터 서서히 나올 겁니다.
이미지가 다 나오면 이 이미지에 리스너를 답니다.
이 creditsView 이미지를 클릭하면 hideCredits() 함수가 실행 됩니다.

function hideCredits:tap(e)
    playBtn.isVisible = true
    creditsBtn.isVisible = true
    transition.to(creditsView, {time = 300, x = -creditsView.width, onComplete = function() creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end})
end

크레딧 이미지를 클릭하면 실행되는 함수 입니다.
아까 안 보이도록 했던 플레이버튼과 크레딧 버튼을 모두 다시 보이도록 합니다.
creditsView를 transition.to를 이용해서 왼쪽으로 사라지도록 합니다.
다 사라지면 아까 만들었던 creditsView에 대한 리스너를 remove합니다.

여기까지가 첫 화면에서 크레딧 버튼을 누르고 나서 다시 원래 화면으로 돌아오기 까지 진행시키는 함수들 입니다.

다음엔 첫 화면에서 플레이 버튼을 누르면 실행되는 showGameView() 함수입니다.
본격적으로 게임이 시작 되는 부분입니다.

function showGameView:tap(e)
    transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})
    -- [Bank Credits]
    bank = display.newText('5', 18, 5, native.systemFontBold, 14)
    bank:setTextColor(234, 170, 12)
    bankText = display.newImage('bankText.png', 7.5, 25)

    -- [Ball]
    ball = display.newImage('ball.png', 228, 142)
  
    -- [Shells]
    s1 = display.newImage('shell.png', 50, 114)
    s2 = display.newImage('shell.png', 195, 84)
    s2.name = 's2'
    s3 = display.newImage('shell.png', 340, 114)
    shells = display.newGroup(s1, s2, s3)
  
    -- [Button Bar]
    buttonBar = display.newImage('buttonBar.png', 0, 270)
    msg = display.newText('Click Bet to start', 1, 307, native.systemFont, 9)
    betBtn = display.newImage('betBtn.png', 223, 275)
  
    betBtn:addEventListener('tap', placeBet)
  
    gameView = display.newGroup(bank, bankText, ball, shells, buttonBar, msg, betBtn)
end

실제 게임화면으로 넘어오면 처음에 실행되는게 이전 화면의 객체들을 없애는 작업입니다.
아까 첫 화면 객체들은 titleView라는 localGroup으로 그룹화를 했었습니다.
transition.to를 이용해서 이 titleView를 왼쪽으로 이동시키고 다 이동되면 startButtonListener에 rmv라는 인수를 전달하면서 실행시킵니다.
startButtonListener는 아까 add를 인수로 전달하면서 불렀던 함수인데요.
인수가 add가 아니면 첫 화면의 플레이버튼하고 크레딧 버튼의 리스너를 remove하도록 돼 있었습니다.
그러니까 첫화면의 객체들을 사라지게 하고 이 객체들에 할당된 리스너들도 다 remove 한 겁니다.
그 다음으로는 text를 출력할 bank를 만들고 위치시켜주고 색도 지정합니다.
그리고 bankText 이미지도 지정된 위치에 display합니다.
다음은 ball 이미지를 지정된 위치에 display하구요. shell 3개를 나란히 display합니다.
여기서 ball이 들어가 있게 될 shell은 별도로 name을 s2라고 할당합니다.
그리고 이 shell들을 shells라는 변수에 그룹화 시켜 놓습니다.
다음에 하단에 buttonBar를 표시하고 msg Text를 표시합니다.
그리고 betBtn을 표시하고 이 이미지에 리스너를 답니다.
이 이미지를 누르면 paceBet() 함수가 실행됩니다.
맨 마지막에는 이 게임 화면에 있는 모든 objects를 gameView변수로 그룹화 합니다.
나중에 화면전환 할 때 사용하기 위해서 입니다.


function placeBet:tap(e)  
    -- Place Bet
    bank.text = bank.text - 1;
    -- Remove Button Listener
    betBtn:removeEventListener('tap', placeBet)
    -- Change Msg
    msg.text = ''
    -- Reset Total Moves
    totalMoves = 5
    -- Hide Ball
    transition.to(s2, {time = moveSpeed, y = s2.y + 30, onComplete = randomShellMove})
end

betBtn을 누르면 실행 될 placeBet()함수입니다.
이 함수에서는 bank.text를 -1해서 다시 display합니다.
그리고 betBtn이미지의 리스너를 remove합니다.
다음으로는 msg.text를 아무것도 표시하지 않도록 고치구요.
shell이 몇번 움직일지 그 숫자를 다시 지정합니다.
(지금은 초기 설정값이랑 같은데 만약에 점점 더 움직이는 횟수를 늘리고 싶으면 이 부분에서 코딩해 주면 됩니다.)
다음은 s2를 아래로 내립니다. 그러면 공을 덮어서 안 보이게 됩니다.
완료 되면 randomShellMove() 함수가 호출 됩니다.

function randomShellMove()
    local randm = math.floor(math.random() * 2) + 1
  
    local shell1 = shells[randm]
    local shell2
  
    if(shell1 ~= 3) then
        shell2 = shells[randm + 1]
    elseif(shell1 ~= 1) then
        shell2 = shells[randm - 1]
    end
  
    ball.isVisible = false
  
    totalMoves = totalMoves -1
  
    transition.to(shell1, {time = moveSpeed, x = shell2.x, y = shell2.y})
    transition.to(shell2, {time = moveSpeed, x = shell1.x, y = shell1.y, onComplete = checkMovesLeft})
end

여기는 3개의 shell을 움직이는 로직입니다.
아마 이 앱에서의 핵심 부분일 겁니다.
이 로직은 여러분이 연구해 보세요.
이것보다 더 좋은 로직을 생각해 보셔도 되구요.
이 로직에 대한 해석은 생략하겠습니다.
하여간 로직대로 shell3개를 5회 움직입니다. 이때 ball은 안보이도록 하구요.
shell2가 다 움직이면 checkMovesLeft()를 호출합니다.

function checkMovesLeft()
    if(totalMoves > 0) then
        randomShellMove()
    else
        s1:addEventListener('tap', revealBall)
        s2:addEventListener('tap', revealBall)
        s3:addEventListener('tap', revealBall)
      
        -- Change Msg
        msg.text = 'Click where the ball is'
        msg:setReferencePoint(display.TopLeftReferencePoint)
        msg.x = -20
    end
end

이 함수에서는 totalMoves를 먼저 체크합니다. 0이 아니면 randomShellMove()함수를 다시 호출합니다. 이렇게 해서 총 5번 randomShellMove()함수가 실행 될 겁니다.
totalMoves가 0 이면 각 shell에 모두 리스너를 답니다.
tap 하면 revealBall함수가 실행되도록이요.
그리고 msg.text는 Click where the ball is 로 바꿔 주시고 그 위치를 바로 잡아 줍니다.

function revealBall:tap(e)
    -- Remove Shell Mouse Listeners
    s1:removeEventListener('tap', revealBall)
    s2:removeEventListener('tap', revealBall)
    s3:removeEventListener('tap', revealBall)
  
    -- Move Ball to correct position
    ball.x = s2.x + 75
    ball.y = s2.y + 150
    ball.isVisible = true
  
    -- Give credits if correct guess
  
    if(e.target.name == 's2') then
        bank.text = bank.text + 2
        -- Change Msg
        msg.text = 'Correct! Click Bet to play again'
        msg:setReferencePoint(display.TopLeftReferencePoint)
        msg.x = -20
    else
        msg.text = 'Wrong! Click Bet to play again'
        msg:setReferencePoint(display.TopLeftReferencePoint)
        msg.x = -20
    end

    -- Reveal Ball
    transition.to(s2, {time = moveSpeed, y = s2.y - 30})
  
    -- Add Bet button listener
    betBtn:addEventListener('tap', placeBet)
  
    -- Check for bank credits
    if(bank.text == '0') then
        betBtn:removeEventListener('tap', placeBet)
            alert()
    end
end

이 함수에서는 호출되면 일단 3개 shell에 할당된 리스너를 모두 remove합니다.
그리고 ball의 위치를 두번째 shell 즉 s2 안으로 위치시키고 이 볼이 보일 수 있게 합니다.
터치한 shell이 s2이면 즉 ball이 있는 shell이면 msg.text를 Correct! Click Bet to play again 으로 표시하고 그 위치를 바로 잡습니다. 그리고 s2가  아니면 Wrong! Click Bet to play again 을 표시하고 그 위치를 잡습니다.
그리고 s2를 transition.to를 이용해서 위로 올립니다. 그러면 ball이 나오겠죠?
이제 게임을 다시 시작할 수 있도록 betBtn에 리스너를 다시 달아줍니다.
만약 bank.text가 0 이면 그 리스너를 다시 없애고 alert() 함수를 실행합니다.

function alert()
    alert = display.newImage('alert.png')
    alert:setReferencePoint(display.CenterReferencePoint)
    alert.x = display.contentCenterX
    alert.y = display.contentCenterY
    transition.from(alert, {time = 300, xScale = 0.3, yScale = 0.3})
  
    msg.text = ''
  
    alert:addEventListener('tap', restart)
end

이 함수에서는 alert이미지를 transition.from을 이용해서 display하구요.
msg.text를 아무것도 표시 안 합니다.
그리고 이 alert이미지에 리스너를 달아서 tap하면 restart()함수가 실행되도록 합니다.

function restart()
    display.remove(gameView)
    gameView = nil
    display.remove(alert)
    alert = nil
  
    Main()
end
이 함수에서는 게임화면에 있는 모든 객체들(gameView)을 remove합니다.
그리고 alert화면도 remove합니다.
그리고 Main()을 실행시켜 게임을 다시 시작하도록 합니다.

Main()
이 부분은 앱이 처음 시작할 때 Main()을 호출하는 부분입니다.

이번엔 야바위게임을 같이 살펴봤습니다.
게임 설계가 아주 모범적으로 잘 돼 있는것 같습니다.

저도 이 모범적인 설계를 배우고 싶어서 공부 자료로 사용했습니다.
코딩이 아주 깔끔하네요.

아래 원래 소스 파일이 있습니다.

반응형


반응형
오늘은 Corona SDK 로 만든 돈 넣고 돈 먹기 게임을 공부해 보겠습니다.
예전에 장터에 가면 사발이 세개 있고 그 중 한개에 구슬이나 주사위를 넣은 다음 막 돌린 다음 그 주사위가 어디에 있는지 알아 맞추는 놀이가 있었죠?
야바위라고 어른들이 그러셨는데....

Carlos Yanez 라는 개발자가 이 야바위를 모바일 애플리케이션으로 개발 했습니다. Corona SDK로요.
그리고 그 소스를 공유했습니다.

제가 보기엔 아주 프로그램이 알차게 잘 돼 있는 것 같습니다.
이 소스코드를 분석하면 아주 공부가 잘 될 것 같습니다.
(그리고 이 야바위 게임이 전 세계적으로 있었나 보네요. 몰랐는데......)

아래 각 게임 화면들을 캡쳐 했습니다.

이걸 게임 개발 하기 전에 개발자가 받은 Story Board 라고 생각을 해 보죠.
아주 훌륭한 기획자라면 이렇게 디자인까지 다 된 스토리보드를 개발자에게 줄 거예요.
그런데 너무 그런거까지 기대하지 마세요. 디자인 까지는 안 되더라도 모바일 이미지 내 objects 배치해서 주는 기획자도 드물거예요.
그냥 볼펜으로 끄적인 스토리 보드만 줘도 감지덕지죠.
안 에 들어가는 Objects 들하고 있게 될 Event 들 하고 그 이벤트로 인해 진행되는 object 들의 behavior 만 잘 정리 되면 되죠 뭐.

하여간 아래 내용은 세계에서 최고로 개발자에게 편하게 기획하는 기획자가 만든 앱의 스토리보드라고 생각해 봐요 우리.

1. 첫 화면


Objects (객체들)

:  bg 배경이미지, title 게임 제목,  playBtn 플레이 버튼, creditsBtn 크레딧 버튼

Events/Behavior (이벤트)

: playBtn 누르면 현재화면 중 배경만 남고 모두 없어짐 -> 게임 화면으로 옮김

  현재 화면 객체들 왼쪽으로 slide 되면서 없어질 것

: creditsBtn 누르면 Credits를 보여 줌

  플레이와 크레디트 버튼 없어지고 크레디트 이미지가 왼쪽에서 slide 되면서 나옴


2. Credit 화면 (Credit 버튼 누르면 나옴)



Objects

: Credits 이미지

  나머지 bg와 title은 이전화면에서 없어지지 않고 그대로 있음

: Events/Behavior

  Credits 이미지를 클릭하면 이미지가 왼쪽으로 사라짐

  1번 화면이 다시 나옴


3. 게임 화면



objects

: bg 배경화면 그대로

: bank 왼쪽 상단 숫자, 처음엔 5자가 표시 됨

: bankText BANK 이미지

: ball

: s1/s2/s3 shell 3개

: buttonBar 밑에 진한 사각형

: msg 좌측 하단에 메세지, 처음에 Click Bet to start 가 표시 됨

: betBtn 게임 시작 버튼


Events/Behavior

betBtn 버튼에 tab 리스너를 담.

betBtn을 누르면

- Bank 숫자에서 -1을 함

- betBtn 리스너를 Remove 함

- msg 텍스트를 없앰

- 위로 올라간 shell 이 ball 을 덮음

- shell 을 총 5번 움직임. 움직임 속도는 600으로 함. 랜덤하게 움직임


4. Shell 이 다 움직이고 User 의 선택을 기다리는 화면, 선택한 후 화면





objects

: 게임 화면이므로 3번 게임화면과 똑 같음

Event/Behavior

: msg는 Click where the ball is 를 표시함

: 각 shell 에 리스너를 담

: 어떤 Shell 을 클릭해도 공이 있는 Shell이 올라감

: 공이 있는 Shell 을 클릭하면 bank+2를 해 줌, msg는 Correct! Click Bet to play again 을 표시해 줌

: 공이 없는 Shell 을 클릭하면 msg는 Wrong! Click Bet to play again을 해 줌

: betBtn 에 다시 리스너를 담,

: bank 가 0점이면 alert() 화면을 띄움


5. Game Over 화면 (Alert)



Objects
: alert 이미지
Event/Behavior
: msg는 표시 안 함
: alert 이미지에 리스너를 담
  클릭하면 게임을 재 시작 함


자 여기까지가 바로 세계에서 제일 훌륭한 기획자가 만든 스토리 보드 입니다.
기획에서 이정도만 해주면 개발자는 아주 편하겠죠?

이제 이 것을 토대로 Flow Chart 도 만들어 보고 Use Case 도 만들어보고 나름대로 함수(클래스) 설계도 해 보세요.

아마 그러면 실력향상에 아주 도움이 될 겁니다.

다음 시간엔 이 스토리 보드를 가지고 소스 코딩을 해 보겠습니다.

반응형


반응형
제가 예전에 써핑하다가 받은 소스입니다.
누가 만든 로직인지도 모르겠네요.

하옇든간 자기가 힘들게 만든 소스를 여러 사람들과 조건없이 공유하는 개발자분들께 감사드립니다.

오늘 살펴볼 앱은요.
앱을 시작하면 위에서 녹색 공이 떨어집니다.
그리고 화면을 Drag 하면 선이 그려집니다.
녹색 공이 그 선에 떨어지면 튀어 나가게 돼 있습니다.

일단 소스 부터 볼까요?

local physics = require ("physics")
physics.start(true)
physics.setGravity(0, 1)
 
-- Ball rolls on Line
 
local ball = display.newCircle( 0, 0, 25)
ball:setFillColor(0, 255, 0)
ball.x = display.contentWidth/2
ball.y = 0
 
physics.addBody(ball, {bounce=1.1, radius = 25, friction=1})

-- Draw a line

local i = 1
local tempLine
local ractgangle_hit = {}
local prevX , prevY
local function runTouch(event)
  if(event.phase=="began") then
    if(tempLine==nil) then
      tempLine=display.newLine(event.x, event.y, event.x, event.y)
 
-- Random Colors for line
 
      r = math.random (0, 255)
      g = math.random ( 0, 255)
      b = math.random (0, 255 )
      tempLine:setColor(r,g, b)
 
      prevX = event.x
      prevY = event.y
    end
  elseif(event.phase=="moved") then
    tempLine:append(event.x,event.y-2)
    tempLine.width=tempLine.width+0.8
    ractgangle_hit[i] = display.newLine(prevX, prevY, event.x, event.y)
    ractgangle_hit[i]:setColor(r,g, b)
    ractgangle_hit[i].width = 5
 
-- Creates physic joints for line (Turn on draw mode to see the effects)
 
    local Width = ractgangle_hit[i].width * 0.6
    local Height = ractgangle_hit[i].height * 0.2
 
-- Physic body for the line shape
 
    local lineShape = {-Width,-Height,Width,-Height,Width,Height,-Width,Height}
 
    physics.addBody(ractgangle_hit[i], "static", { bounce = -1, density=0.3, friction=0.7, shape = lineShape})
    prevX = event.x
    prevY = event.y
    i = i + 1
  elseif(event.phase=="ended") then
    tempLine.parent.remove(tempLine)
    tempLine=nil
  end
end

Runtime:addEventListener("touch", runTouch)

우선 Physics를 이용하기 위해 require하고 start 시킨 후 중력은 아래로 1을 주었습니다.
기본이 9.8인데 1로 주었으니 풍선처럼 아주 가볍게 그리고 천천히 떨어지겠네요.
그리고 25픽셀이 반지름인 녹색공을 만들고 위치는 상단 가운데로 주었습니다.
이 ball에 addBody를 했구요. 그 body 크기는 반지름이 25픽셀로 녹색 원과 크기가 같고 bounce와 friction 도 조금 주었습니다.

여기까지만 하면 녹색볼이 위에서 아래로 천천히 떨어질 겁니다.
정말 Corona SDK의 물리엔진은 아주 강력합니다.

그 다음은 여러 변수들을 선언했습니다.
맨 밑에 Runtime touch리스너가 있고 이 리스너는 runTouch함수를 호출합니다.
그러니까 앱이 시작할 때부터 이 runTouch 함수는 실행 될 겁니다.

이제 그 함수를 볼까요?
일단 touch가 시작되고 tempLine이 nil 일 경우 tempLine에 현재 위치로 라인을 그립니다.
시작점과 끝점 모두 현재 위치가 됩니다.
그리고 r,g,b 변수에 랜덤한 rgb값을 넣고 아까 그린 선에 그 랜덤한 색을 넣습니다.
그리고 현재의 위치를 prevX,prevY에 넣습니다.
그 다음에 touch 가 moved일 경우 즉 드래그가 진행중일 경우에는요.
선을 더 합니다. (append) 그리고 선을 그리는데요. 시작점은 아까 began 일때의 위치가 되고 끝위치는 현재가 됩니다.

다음엔 이 그려진 Line에 physics.addBody를 할 수 있도록 Width,Height공간만큼 구간을 잡구요 이것을 addBody에 shape로 집어 넣습니다.
그리고 다시 prevX,prevY에 현재 위치를 넣습니다.

드래그하던 손을 떼면 ended 부분이 실행 되는데요.
tempLine을 remove하고 nil을 만듭니다.

자 여기 까지가 소스코드를 모두 분석한 내용입니다.

이제 앱을 실행하면 녹색 공이 떨어지고 여러분이 그은 선에 그 공이 맞으면 튀게 될 겁니다.

이 앱은 그 자체로 완성된 게임은 아니지만 그 기본 로직을 활용하면 많은 곳에서 유용하게 사용할 수 있을 것 같습니다.

반응형

앱 화면 Screen Capture하기

2011. 12. 29. 04:26 | Posted by 솔웅


반응형
오랜만에 Corona SDK Tip을 공부합니다.

예전 텍스트, 이미지, 모양 표시하기... 2 에서 이런게 있다는 설명만 하고 넘어갔는데요.
오늘 예제와 함께 자세히 다뤄 볼께요.

display.setStatusBar( display.HiddenStatusBar )
 
-- Fill the screen with a green rectangle
local rect = display.newRect(0, 0, display.contentWidth, display.contentHeight)
rect:setFillColor(0, 255, 0)
 
-- Draw a circle on the screen
local circle = display.newCircle(155, 100, 36)
circle:setFillColor(255, 0, 0)
 
-- Capture the screen
local screenCap = display.captureScreen( true )
 
-- Remove the objects from the screen
rect:removeSelf()
circle:removeSelf()
 
-- Scale the screen capture, now on the screen, to half it's size
screenCap:scale(.5,.5)
 
-- Alert the user to look in the library (device)
-- or on the desktop (simulator) for the screen capture
local alert = native.showAlert( "Success", "Screen Capture Saved to Library", { "OK" } )
이 앱을 실행하면 위 화면이 실행되고 아래 화면이 캡쳐 됩니다.

왜 그런지 소스를 보실까요?
첫째줄은 아이폰에서 status 바를 없앱니다.
그 다음엔 커다란 녹색 사각형을 그리고 그 다음에 빨간 원을 그립니다.
녹색 사각형은 화면에 꽉 차게 그려집니다.
그 다음 display.captureScreen(true)를 해서 스크린을 캡쳐합니다.
(true)를 했으니까 이 파일은 디바이스 안에 이미지 파일로 저장됩니다.
그리고 사각형과 원을 screen에서 없앱니다.
그리고 아까 캡쳐한 것을 반 크기로 줄여서 화면에 표시합니다.
맨 마지막엔 제대로 스크린 캡쳐가 됐다는 Alert 메세지를 뿌립니다.

즉 이 앱은 꽉찬 녹색 사각형과 큰 원을 캡쳐한 후 두 사각형과 원을 지우고 절반크기로 다시 화면에 보여주는 앱입니다.

여기서 사각형과 원을 removeSelf()한 부분을 주석처리한 다음에 실행해 보세요.

그럼 이렇게 나올 겁니다.
보시다시피 캡쳐 전 도형들이 사라지지 않고 나와서 전체가 녹색 사각형이고 빨간 원도 좀 큽니다. 그리고 그 위에 캡쳐한 이미지를 절반크기로 올려 놓은 화면입니다.

간단하지만 때에 따라서는 아주 유용하겠죠?
display.captureScreen( false ) 하면 디바이스에는 저장되지 않습니다.
이것을 true로 할 경우
아이폰 같은 경우는 Photo Albums에 자동으로 저장이 되구요.
안드로이드는 build.settings에 아래와 같이 퍼미션을 주어야 합니다.
settings =
{
   androidPermissions =
   {
       "android.permission.WRITE_EXTERNAL_STORAGE"
   },
}
그리고 시뮬레이터는 컴퓨터 바탕화면에 캡쳐된 화면을 저장합니다.
(윈도우에서는 MyPictures\Corona Simulator" 디렉토리에 저장이 될 겁니다.)
저장된 파일은 png형식이고 이름은 Picture#.png이렇게 나갑니다.
10000개까지 가능합니다.


이 화면처럼 버튼을 누르면 화면을 캡쳐하도록 만들어 볼까요?
display.setStatusBar(display.HiddenStatusBar)
local background = display.newImage('background.png')
local captureButton = display.newImage('captureButton.png')

captureButton:setReferencePoint(display.CenterReferencePoint)
captureButton.x = display.contentWidth * 0.5
captureButton.y = display.contentHeight * 0.5

function captureButton:tap(e)
    local screenshot = display.captureScreen(true)
    -- Show Image in Photo Library
    media.show(media.PhotoLibrary)
end
captureButton:addEventListener("tap", captureButton)

이 소스처럼 button이미지에 리스너를 달아서 tap할 경우 display.captureScreen(true)를 하도록 하면 됩니다.
간단하죠?

샘플 파일은 아래에 있습니다.





반응형

코로나 관련 신간 소개

2011. 12. 28. 00:02 | Posted by 솔웅


반응형
요즘 들어 코로나 관련 책들이 하나 둘 나오기 시작하는 것 같습니다.

얼마전에 일본에서 책이 나왔다는 소식도 들었는데요.
지난주에 미국에서도 책이 나왔습니다.

오프라인 서점에 나왔는지 모르겠습니다.
저는 이 책의 첫장 Hello World 만 있는 PDF를 받았거든요.

아래에 공유해 드릴께요.


******** o ******* o ******* o ******* o ******* o ******* o *******

그리고 출판업계 관련 있으신분들 관심 가져 주셨으면 좋겠습니다.
제가 Corona SDK 관련 책을 내려고 준비 중에 있습니다.
CoronaSDK 의 CEO한테도 메일 했고 관심 있다는 답장도 받았습니다.

기존 책을 번역하는 것으로 할지 아니면 별도로 만들지는 지금 고민중인데요.

아직 책을 내 본 경험이 없어서 출판사 쪽에서 관심 있으시면 같이 일 진행 하면 좋겠습니다.

Corona SDK 는 쉽게 모바일 게임을 만들 수 있는 툴로 최근 급 성장하고 있는 개발 Language입니다.

혹시 이 글 보시는 분 들 중에 도움 주실 수 있으신 분들 연락 부탁드립니다.
(댓글로 써 주셔도 되구요. solkit2011@yahoo.com 으로 이메일 주셔도 되요.)

그럼........
반응형


반응형
오랜만에 코로나쪽을 공부해 보겠습니다.
얼마전에 기능이 추가 된 화면전환 효과 Story Board 샘플 소스를 분석해 볼께요.

Story Board API는 예전에 살펴 본 바 있습니다.

이 샘플 예제는 Corona SDK/interface 안에 storyboard 라는 폴더에 있습니다.

우선 main.lua 소스 파일 입니다.

display.setStatusBar( display.HiddenStatusBar )
-- require controller module
local storyboard = require "storyboard"
local widget = require "widget"
-- load first scene
storyboard.gotoScene( "scene1", "fade", 400 )
-- Display objects added below will not respond to storyboard transitions
-- table to setup tabBar buttons
local tabButtons = {
    { label="First", up="icon1.png", down="icon1-down.png", width = 32, height = 32, selected=true },
    { label="Second", up="icon2.png", down="icon2-down.png", width = 32, height = 32 },
}
-- create the actual tabBar widget
local tabBar = widget.newTabBar{
    top = display.contentHeight - 50,    -- 50 is default height for tabBar widget
    buttons = tabButtons
}
main.lua에서는 딱 두 부분으로 나뉘어 져 있습니다.
첫번째는 Story Board 를 이용한 부분이고 두번째는 Tab Button을 이용한 부분입니다.
이 두 기능을 이용하기 위해 storyboard와 widget 을 require 했습니다.
그리고 storyboard 부분은 gotoScene 을 이용해서 화면 전환 한 것 밖에는 없습니다.
scene1.lua 로 가는데 0.4초동안 fade 효과를 이용해서 화면 전환을 합니다.
화면 전환 효과는 fade를 포함해서 모두 20개의 효과가 있습니다.
내용은 여기를 클릭하시면 보실 수 있습니다.
두번째는 탭바를 생성하는 건데요. 일단 tabButtons 테이블 안에 탭버튼 두개의 정보들을 담아 놓습니다. 그리고 widget.newTabBar로 Tabbar를 만드는데요. 중요한건 이 탭 바는 화면전환해도 계속 남아 있는 다는 겁니다.

Story Board API 에서는 scene 의 life cycle 메소드가 4개 있습니다.
createScene,enterScene,exitScene,destroyScene
그리고 이 메소드 안에서 생성한 object 들만 화면전환시 적용이 됩니다.
director.lua 클래스는 localGroup을 생성해서 그 그룹에 insert 한 객체들만 적용이 됐었습니다.
이번에 코로나에서 제공한 화면전환 API는 이보다 더 세밀한 화면 전환 단계를 이용할 수 있게 만들었습니다.
이 4개 메소드가 적용된 예는 Scene1 설명할 때 나올겁니다.

일단 여기서 만드는 tabbar 는 화면전환시 적용되지 않고 처음부터 끝까지 계속 남아있을 겁니다.
(그리고 tab bar 관련된 글을 한번도 안 다뤄봤는데요. 이 기능은 나중에 자세히 다뤄볼께요.)


-- storyboard.gotoScene( "scene1", "fade", 400 )
이렇게 scene1.lua로 넘어가는 부분을 주석 걸어놓고 실행하면 위 화면이 나옵니다.

이게 온전히 main.lua만을 실행했을 때 나오는 화면입니다.
그 다음에 scene1.lua를 보겠습니다.

local storyboard = require( "storyboard" )
local scene = storyboard.newScene()

---------------------------------------------------------------------------------
-- BEGINNING OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------

local image, text1, text2, text3, memTimer

-- Touch event listener for background image
local function onSceneTouch( self, event )
    if event.phase == "began" then
        storyboard.gotoScene( "scene2", "slideLeft", 800  )
        return true
    end
end

-- Called when the scene's view does not exist:
function scene:createScene( event )
    local screenGroup = self.view
   
    image = display.newImage( "bg.jpg" )
    screenGroup:insert( image )
   
    image.touch = onSceneTouch
   
    text1 = display.newText( "Scene 1", 0, 0, native.systemFontBold, 24 )
    text1:setTextColor( 255 )
    text1:setReferencePoint( display.CenterReferencePoint )
    text1.x, text1.y = display.contentWidth * 0.5, 50
    screenGroup:insert( text1 )
   
    text2 = display.newText( "MemUsage: ", 0, 0, native.systemFont, 16 )
    text2:setTextColor( 255 )
    text2:setReferencePoint( display.CenterReferencePoint )
    text2.x, text2.y = display.contentWidth * 0.5, display.contentHeight * 0.5
    screenGroup:insert( text2 )
   
    text3 = display.newText( "Touch to continue.", 0, 0, native.systemFontBold, 18 )
    text3:setTextColor( 255 ); text3.isVisible = false
    text3:setReferencePoint( display.CenterReferencePoint )
    text3.x, text3.y = display.contentWidth * 0.5, display.contentHeight - 100
    screenGroup:insert( text3 )
   
    print( "\n1: createScene event")
end

-- Called immediately after scene has moved onscreen:
function scene:enterScene( event )
   
    print( "1: enterScene event" )
   
    -- remove previous scene's view
    storyboard.purgeScene( "scene4" )
   
    -- Update Lua memory text display
    local showMem = function()
        image:addEventListener( "touch", image )
        text3.isVisible = true
        text2.text = text2.text .. collectgarbage("count")/1000 .. "MB"
        text2.x = display.contentWidth * 0.5
    end
    memTimer = timer.performWithDelay( 1000, showMem, 1 )
end

-- Called when scene is about to move offscreen:
function scene:exitScene( event )
   
    print( "1: exitScene event" )
   
    -- remove touch listener for image
    image:removeEventListener( "touch", image )
   
    -- cancel timer
    timer.cancel( memTimer ); memTimer = nil;
   
    -- reset label text
    text2.text = "MemUsage: "
end


-- Called prior to the removal of scene's "view" (display group)
function scene:destroyScene( event )
   
    print( "((destroying scene 1's view))" )
end

---------------------------------------------------------------------------------
-- END OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------

-- "createScene" event is dispatched if scene's view does not exist
scene:addEventListener( "createScene", scene )

-- "enterScene" event is dispatched whenever scene transition has finished
scene:addEventListener( "enterScene", scene )

-- "exitScene" event is dispatched before next scene's transition begins
scene:addEventListener( "exitScene", scene )

-- "destroyScene" event is dispatched before view is unloaded, which can be
-- automatically unloaded in low memory situations, or explicitly via a call to
-- storyboard.purgeScene() or storyboard.removeScene().
scene:addEventListener( "destroyScene", scene )

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

return scene

이 scene1.lua 는 onSceneTouch 함수와 StoryBoard 에 소속된 4개 함수로 구성된 간단한 구조입니다

이 함수 호출은 Storyboard 4인방은 scene:addEventListener 를 사용해서 호출이 되고 onSceneTouch 는 배경이미지에 리스너를 달아서 화면을 터치하면 scene2로 이동하게끔 하는 겁니다.

4인방 메소드에서는 self.view를 이용해서 객체들을 그룹화 하면서 화면에 보여줄 수 있도록 합니다.

처음 createScene 에서는 배경이미지와 텍스트 등 기본 display 요소들을 정의하고 표시합니다.

enterScene 에서는  0.1초 후에 실행되는 타이머를 달아서 showMem 함수를 실행하도록 했습니다.
이 함수는 배경이미지를 터치하면 scene2.lua로 가도록 하는 onSceneTouch 함수를 Call 하는 리스너를 달았고 현재 메모리를 모여주는 로직이 있습니다.
그 이전에 storyboard.purgeScene( "scene4" ) 이 있는데요. purgeScene 은 해당 scene(여기서는 scene4) 에 있는 객체들을 모두 unload 합니다. purge말고 remove를 사용하면 unload 될 뿐만 아니라 메모리에서도 사라집니다.
이 기능은 메모리를 절약하거나 할 때 사용하는 건데 scene1에서 scene2로 넘어 갈때 터미널에 뭐가 찍히는지 보면 잘 알 수 있습니다.

보시다시피 scene1이 시작되면 scene4 의 destroy 가 호출 되서 그 안의 내용이 실행 됩니다.
scene1에서 scene2로 넘어갈 때 destroyScene 함수가 호출되는게 아니라 해당 scene이 unload 될 때 destroyScene 이 호출 됩니다.
그리고 purgeScene 하게 되면 해당 scene 의 destroyScene 이 호출 됩니다.
removeScene 하게 되면 purgeScene 한 후 메모리에서 없애므로 자연히 destroyScene 이 호출 됩니다.

exitScene 은 다음 화면으로 넘어갈 때 실행 되는데 여기서는 모든 리스너를 remove 하고 timer 도 cancel 합니다.

여기까지 하면 실행되는 화면이 아래와 같습니다.


나머지 scene2,scene3,scene4 도 이 scene1과 구조가 동일 합니다.

그런데 여기서 드는 한가지 의문점은 Tab Bar 는 화면 전환에 상관 없이 계속 있는데요.

특정 화면에서 이 TabBar 를 안 보이게 하거나 없애거나 아니면 어떻게 콘트롤을 할 수 있을까요?

그렇게 하려면 main.lua 에서 tabBar 를 local 이 아니라 glabal 변수로 선언하면 됩니다. 그냥 local 이란 글자만 지우세요.

그리고 scene3.lua 맨 밑에 (return scene 바로 윗줄에) tabBar.isVisible = false; 를 넣어보시고. scene4.lua에는 tabBar.isVisible = true 를 넣어보세요.

그러면 이렇게 scene3에는 탭바가 없어지고 scene4에는 나타나게 됩니다.
이런식으로 tabbar를 제어하시면 됩니다.


반응형


반응형
얼마전에 Corona SDK는 많은 편리한 기능을 추가한 업데이트 된 버전을 추가했습니다.
그리고 다시 조만간 업데이트될 기능을 미리 소개해 줬네요.
- 안드로이드 in-app purchases
- OpenFeint 없는 Game Center
- 앱 내에서 처리 되는 첨부 파일 가능한 이메일 기능 (iOS)
- 안드로이드에 맵뷰 제공
- Push notification(remote) for iOS
- iPhone용 URLSchema
- 안드로이드 인텐트 intents 기능
- WebPopup redux
- 소셜 게이밍 기능
- iOS5를 위한 트위터 기능
- iOS용 문자메세지 보내기 기능
- 그래픽 엔진 개선
- Debugger enhancements

여러분들도 의견 있으시면 여기 눌러서 의견을 올려보세요.
Ansca Mobile 사장인 Carlos Icaza 블로그 입니다.

Carlos가 Corona SDK가 뉴욕타임즈에 실렸다고 자랑하면서 이미지를 하나 올렸네요.



반응형


반응형
Corona SDK로 게임을 만들 때 어떻게 Level별 Lock 기능을 걸고 푸는지에 대해 샘플을 보면서 공부 해 보겠습니다.

오늘 소스는 개발자 peach pellen 이 만들고 공유한 소스코드입니다.
http://peachpellen.com/ 로 가면 많은 정보들도 있구요. 우리의 peach가 열심히 만들어서 공유하고 있는 다른 정보들도 접할 수 있습니다.
가서 댓글로 고마움을 표시하는 것도 좋은 일 인것 같습니다.

원본 파일은 아래에 올리겠습니다.


우선 main.lua를 보겠습니다. (지면을 줄이기 위해서 필요없는 주석은 지울께요.)
display.setStatusBar (display.HiddenStatusBar)
local director = require ("director")
local mainGroup = display.newGroup()
local function main()
    mainGroup:insert(director.directorView)
    director:changeScene("menu")
    return true
end
main()
첫째줄은 늘 그래왔듯이 iPhone의 status bar를 없애는 라인이구요.
두번째 라인은 director.lua 클래스를 require한 라인입니다.
이 director클래스는 일반 개발자가 만든 화면 전환용 클래스죠?
얼마전 코로나에서 화면전환용 api인 storyboard api를 제공하기 시작했으니 그 api를 사용해도 될 겁니다.
이 소스는 그 api가 나오기 이전에 만들어진 것이니 그대로 director 클래스를 사용하겠습니다.
director 클래스에 대해서는 이전에 살펴 본 적이 있으니 따로 설명하지는 않겠습니다.
세번째 줄은 mainGroup이라는 그룹을 만든것이고 그 다음줄에 main() 함수를 만들었습니다.
이 함수 안에서 mainGroup에 directorView를 insert합니다.
그 다음에 changeScene으로 menu.lua 파일로 스크린을 옮깁니다.
화면전환 효과는 아무것도 사용하지 않았네요.
마지막 줄은 이 main함수를 call 해 주는 겁니다.

이젠 그럼 menu.lua를 볼까요?

module(..., package.seeall)
-- BELOW is Director code
function new()
    local localGroup = display.newGroup()
-- ABOVE is Director code

require "saveit"

-------------------------------------------------------------------------
-------------------------------------------------------------------------
-------------------------------------------------------------------------
local function resumeStart()
            local path = system.pathForFile( "ourdata.txt", system.DocumentsDirectory )               
                local file = io.open( path, "r" )

                if file then
                    print("Loading our data...")
                    local contents = file:read( "*a" )
                    -- Loads our data
                   
                    local prevState = explode(", ", contents)

                        _G.onelock = prevState[1]
                        _G.twolock = prevState[2]

                    io.close( file )

                else
                    _G.onelock = 1
                    _G.twolock = 0
                end
            end
            -- Separates the variables into a table
 
local function onSystemEvent( event )
        if( event.type == "applicationExit" ) then             
                local path = system.pathForFile( "ourdata.txt", system.DocumentsDirectory )               
                local file = io.open( path, "w+b" )
                -- Creates the file where we save our data
               
                file:write( _G.onelock ..", ".. _G.twolock)         
                io.close( file )
            end
        end
        -- Saves our data

--BELOW THIS LINE IS ANSCA'S SAMPLE CODE (I don't mess with perfection)
-- explode helper function zomgdata
function explode(div,str)
  if (div=='') then return false end
  local pos,arr = 0,{}
  -- for each divider found
  for st,sp in function() return string.find(str,div,pos,true) end do
    table.insert(arr,string.sub(str,pos,st-1)) -- Attach chars left of current divider
    pos = sp + 1 -- Jump past current divider
  end
  table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
  return arr
end
 
local function init()
        -- start and resume from previous state, if any
        resumeStart()  
        
        Runtime:addEventListener( "system", onSystemEvent )    
end
 
 
--start the program
init()
--ABOVE THIS LINE IS ANSCA'S SAMPLE CODE (You don't need to change it, so don't!)

----------------------------------------------------------------------
--                                SCENE SETUP                            --
----------------------------------------------------------------------
local background = display.newImage ("background.png")
localGroup:insert(background)
-- Sets the background

local leveloneicon = display.newImage ("level1icon.png")
leveloneicon.x = 50
leveloneicon.y = 40
localGroup:insert(leveloneicon)
-- Sets the icon for level one

local function gotoone (event)
director:changeScene("level1")
end
leveloneicon:addEventListener("tap", gotoone)
-- Go to level one when icon is tapped

local function gototwo (event)
director:changeScene("level2")
end
-- Function to go to level two

local function seticontwo (event)
if _G.twolock-0 == 0 then
local leveltwoicon = display.newImage ("level2locked.png")
leveltwoicon.x = 130
leveltwoicon.y = 40
localGroup:insert(leveltwoicon)
elseif _G.twolock-0 == 1 then
local leveltwoicon = display.newImage ("level2icon.png")
leveltwoicon.x = 130
leveltwoicon.y = 40
localGroup:insert(leveltwoicon)
leveltwoicon:addEventListener("tap", gototwo)
end
end
seticontwo()

----------------------------------------------------------------------
----------------------------------------------------------------------
-- BELOW is Director code
    -- MUST return a display.newGroup()
    return localGroup
end
-- ABOVE is Director code

모듈로 사용되기 위해 필요한 부분이 맨 처음 줄이구요.
그 다음은 Director클래스를 사용하기 위해 new()함수 안에 모든 코드를넣고 localGroup이라는 그룹을 만든 겁니다.
다음엔 saveit.lua파일을 require했습니다.
짧으니까 잠깐 saveit.lua 파일을 볼께요.
module(..., package.seeall)
function save( event )          
                local path = system.pathForFile( "ourdata.txt", system.DocumentsDirectory )               
                local file = io.open( path, "w+b" )
                -- Creates the file where we save our data if needed
                file:write( _G.onelock ..", ".. _G.twolock)         
                io.close( file )
                -- Saves our data
end
첫줄은 마찬가지로 모듈로 사용되기 위해 필요한 라인이구요. 그 다음에 save(event) 함수를 구현했습니다.
event가 인수로 있는 것으로 봐서 어떤 리스너에서 불러오지 않나 생각되네요.
함수 내용은 ourdata.txt를 쓰기 가능으로 열고 그 파일 안에 _Gonelock,_Gtwolock을 씁니다. 그리고 저장하면서 닫습니다.

이 기능이 menu.lua 어느곳에선가 불려져서 사용 될 겁니다.
다시 menu.lua로 넘어올께요.

첫번째로 resumeStart()함수를 보겠습니다.
이 함수에서는 ourdata.txt라는 파일을 엽니다.
만약 이 파일이 있으면 "Loading our data..." 라는 문장을 터미널에 print합니다.
그리고 파일 안의 내용을 읽어오구요.  explode 함수를 사용해서 쉼표 , 를 기준으로 파일 안의 내용을 나눕니다.
_G.onelock 에 첫번째, _G.twolock 에 두번째 내용을 저장합니다.
그리고 파일을 닫습니다. (이 때 저장됩니다.)
만약 파일이 없다면 (이 앱이 처음으로 실행 되는 경우가 됩니다.)
_G.onelock 에 1을 _G.twolock에 0을 넣습니다.

두번째로 onSystemEvent(event)함수를 알아볼까요?
이 함수는 event를 인수로 받기 때문에 어떤 리스너에서 Call하는 걸 겁니다.
밑에 보니까 앱이 최초로 실행 될 때인 Runtime 리스너에서 call하네요.
 Runtime:addEventListener( "system", onSystemEvent )
이 함수 내용을 보면 event.type 이 applicationExit일 경우 그러니까 앱이 종료 될 때 if 문 안에 있는 내용들이 실행 됩니다.
그 내용은 ourdata.txt를 쓰기 가능으로 열고 그 안에 _G.onelock,_G.twolock을 저장하고 파일을 닫습니다.
이 내용은 saveit.lua의 내용과 똑 같네요. 이렇게 중복된 코딩은 그렇제 좋은 방법은 아니죠. 저 같으면 이 내용도 saveit.lua 파일 안에 있는 함수를 call 해서 사용할 것 같습니다.
어쨌든 이 소스코드 분석을 하자면 그렇고.. 압축파일을 열어보시면 그 아래 peach가 직접 주석을 달아서 설명한 부분이 있으니까 한번 읽어 보세요.

그 다음엔 explode(div,str) 함수가 있습니다.
그 내용은 div 가 아무 내용이 없으면 false를 return 하구요.
아니면 pos와 arr을 0과 {} (테이블)로 선언합니다.
그리고 for문을 돌려 arr 테이블에 나누는 기준에 따라 string을 나눠서 저장합니다.
마지막엔 이 arr 테이블을 return합니다.

다음 함수는 init()함수인데요. resumeStart()함수와 런타임 리스너로 onSystemEvent를 call합니다.

그 밑에선 init()함수를 call 하고 있네요.

여기까지만 보면 최초로 init()이 불려지고 그 다음에 resumeStart() 그리고 onSystemEvent 함수가 불려지겠네요.

그 다음은 화면 셋업 부분입니다.
백그라운드 이미지를 표시하고 level1icon 이미지를 표시합니다. 그리고 두 이미지 모두 localGroup에 insert합니다.

그 다음은 gotoone(event)함수인데요. 이건 바로 아래 leveloneicon의 tap리스너에서 call 됩니다. 그 내용은 level1.lua화면으로 넘어 가는겁니다.

다음은 gototwo(event)함수로 내용은 level2.lua 화면으로 넘어가는 건데요. 이 함수는 seticontwo(event) 함수 내에서 call 됩니다.

즉 gotoone 함수는 무조건 실행되고 gototwo함수는 seticontwo 함수에서 일정 조건에 부합되면 실행이 되겠네요.

그러면 seticontwo(event)함수를 살펴보겠습니다.
만약에 _G.twolock-0이 0이면 leveltwoicon이 level2locked.png로 설정이 되구요.
이 변수를 그룹에 insert합니다.
여기서 참고로 왜 -0을 했냐 하면요. 혹시 그 이전에 _G.twolock 변수가 string으로 처리 됐을 경우 == 0 같은 숫자 비교하는 부분이 에러가 날 수 있습니다.
그래서-0 을 해 주면 자동으로 int형으로 바꿔주기 때문에 따로 -0을 하지 않았나 생각되네요.
저도 그런 경험이 있거든요.
그리고 만약에 _G.twolock-0이 1이면 leveltwoicon은 level2icon.png가 되고 이 이미지를 tap할 경우 gototwo 함수가 실행 됩니다.

다음줄은 이 seticontwo()함수를 호출 한 겁니다.

그 다음줄은 director 클래스를 사용하려면 반드시 있어야 하는 부분인데요. localGroup을 return하는 겁니다.

이제 주요 내용은 다 본 겁니다.

level1.lua를 한번 볼까요?
module(..., package.seeall)

function new()
    local localGroup = display.newGroup()

----------------------------------------------------------------------
--                                SCENE SETUP                            --
----------------------------------------------------------------------
local background = display.newImage ("background.png")
localGroup:insert(background)
-- Sets the background

local unlockbutton = display.newImage ("unlock2.png")
unlockbutton.x = 160
unlockbutton.y = 140
localGroup:insert(unlockbutton)
-- Image of unlock button

local lockbutton = display.newImage ("lock2.png")
lockbutton.x = 160
lockbutton.y = 240
localGroup:insert(lockbutton)
-- Image of lock button

----------------------------------------------------------------------
--                            FUNCTIONALITY                            --
----------------------------------------------------------------------

local function onewin (event)
print "Level 2 is now unlocked! :)"
_G.twolock = 1
saveit.save()
director:changeScene("menu")
end
unlockbutton:addEventListener("tap", onewin)

local function onefail (event)
print "Level 2 is now locked! :("
_G.twolock = 0
saveit.save()
director:changeScene("menu")
end
lockbutton:addEventListener("tap", onefail)

----------------------------------------------------------------------
----------------------------------------------------------------------
-- Below is the standard stuff, commented in MENU.LUA
    -- MUST return a display.newGroup()
    return localGroup
end

마찬가지로 첫줄은 모듈로 쓰이기 위해서 필요한 라인이구요. 그 다음 두 줄은 dorector.lua클래스를 사용할 때 반드시 있어야 되는 부분이구요.

다음은 화면 셋업하는 부분입니다.
백그라운드, unlockbutton, lockbutton 세개 이미지를 셋업합니다.

그 다음은 함수들인데요.
onewin(event)함수는 unlockbutton을 tap했을 때 불려지는 함수입니다.
그 내용은 터미널에 "Level 2 is now unlocked! :)" 를 프린트 해 주고 saveit.lua파일에 있는 save()함수를 실행시킵니다. 그리고 menu.lua로 돌아가구요.

두번째 함수는 onefail(event)인데요. 이건 lockbutton 이미지를 tap하면 call되는 함수입니다.
내용은 "Level 2 is now locked! :(" 를 터미널에 프린트 해 주고 _G.twolock을 0으로 해 줍니다. 그 다음 save.save() 함수를 call하고 menu.lua 내용을 화면에 뿌려 줍니다.

다음은 마찬가지로 director.lua 클래스를 사용할 때 있어야 하는 규칙으로 localGroup을 return하구요.

마지막으로 level2.lua를 보시죠.
module(..., package.seeall)
-- Main function - MUST return a display.newGroup()
function new()
    local localGroup = display.newGroup()
----------------------------------------------------------------------
--                                SCENE SETUP                            --
----------------------------------------------------------------------
local background = display.newImage ("background2.png")
localGroup:insert(background)
    return localGroup
end

여기는 특별한 부분 없이 그냥 background 이미지를 출력하는 부분만 있습니다.

이제 게임 중 Level별로 Lock하고 Lock을 해제하고 하는 부분들을 이해하시겠죠?


Lock이 풀리면 맨 마지막에 우리의 Peach 사진을 볼 수가 있네요.
전 얘가 남자인지 여자인지 확실히 모르겠어요. 20대 초반에 시드니에 살고 있다고 하던데..
http://peachpellen.com/about/
하여간 얘가 운영하는 홈페이지가 아래에 있습니다.
http://techority.com/
저하고는 관계가 없지만 얘가 share한 소스코드로 제가 공부를 많이 했고 또 여러분에게도 소개해 드리고 해서 이렇게 소개 드립니다.

이 세상을 경쟁으로 생각하기 보다는 이렇게 서로서로 돕자는 마음으로 하면 더 좋을 것 같아서.. 홈페이지 들어가주고 내용 봐주고 필요하면 이용해 주고 힘 주는 댓글 남겨주고 하는게 우리들이 도움 받은거 은혜 갚는거 같애서... ^^

하여간 여러분들도 즐팅 하세요....
반응형