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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

화면 전환 Storyboard API 개요

2011. 12. 10. 00:33 | Posted by 솔웅


반응형
오늘은 이번에 새로 릴리즈된 코로나 버전에서 선보인 Corona SDK Storyboard API에 대해 공부하겠습니다.

이 API가 나오기 전에 코로나에서는 화면전환을 위한 방법으로 우선 모든 display object들을 그룹화하고 그 그룹을 없앤 후 다음 화면의 새 display 그룹을 불러오고 하는 방식으로 사용하도록 제시했었습니다.
그런데 이 방법은 한계가 있었습니다. 예를 들어 한 화면(Scene)의 display 객체들이 별도의 모듈에 있다던지 할 때는 콘트롤 하기가 아주 힘듭니다.

얼마전에 다룬 주제 중에 director.lua 클래스를 이용해서 화면 전환하는 것을 다뤘습니다.
이것은 코로나 SDK를 만든 Ansca 에서 제공한 것이 아니라 Ricardo Rauber 라는 개발자가 개발해서 공유한 3rd party 클래스 입니다. 사실 저도 그렇고 이 리카르도가 배포한 director 클래스를 화면전환할 때 많이 썼습니다.  아마 한 1년 정도는 이 director 클래스가 화면전환에 주로 사용 되어졌을 겁니다.

이제 Ansca에서도 공식적인 화면 전환 API인 Storyboard API 를  Corona build 2011.678 버전에 선보였고 Corona build 2011.703 버전이 무료 사용자에게도 공개 되면서 누구나 사용할 수 있게 됐습니다.

Getting Started

일단 시작해 볼까요?
File-New Project 를 누릅니다.

이런 화면이 나올겁니다.
제가 지금 맥에서 하고 있는데요. 아까 집에서 윈도우즈로 코로나 세버전 다운 받아서 해 보니까 Choose a template에 좀 더 다양한 내용들이 있더라구요.
이번에 코로나가 새로 업그레이드 되면서 많이 변했습니다.
나중에 이 부분은 따로 다룰 기회가 있겠죠?
오늘은 Storyboard API에 대해 공부할 거니까요. 일단  Scene을 체크하고 Next를 누릅니다.
그럼 App Name에 넣은 이름으로 폴더가 만들어지고 그 안에 아래와 같이 4개의 파일이 자동적으로 생길 겁니다.

기본적으로 필요한 파일들을 자동으로 생성해 주네요.
개발하기 훨씬 편해 졌습니다.
여기서 main.lua를 볼까요?
-----------------------------------------------------------------------------------------
-- main.lua
-----------------------------------------------------------------------------------------
local storyboard = require "storyboard"
-- load scenetemplate.lua
storyboard.gotoScene( "scenetemplate" )
-- Add any objects that should appear on all scenes below (e.g. tab bar, hud, etc.):

storyboard를 require하고 storyboard.gotoScene("scenetemplate") 했습니다.

자 이제 이 내용들을 공부해 보겠습니다.

The Basics

우선 Storyboard API를 보세요.
처음에 있는 Scene Template이 아까 만들어진 파일 중에 있는 scenetemplate.lua 입니다.
이것은 조금 후에 공부하겠습니다.
그 다음엔 어떤 함수(메소드)들이 있는지 한번 이름만이라도 보고 갈까요?
getPrevious()는 이전 Scene 정보를 얻는 걸테고 getScene은 현재 정보 그리고 gotoScene()은 다음에 넘어갈 Scene(화면)으로 갈 때 사용하는 함수 일 겁니다.
newScene()은 새로운 화면을 만드는 것 같고 purgeAll(), purgeScene(),removeAll(), removeScene() 함수들은 화면을 없애는 것과 관련이 있을 겁니다.

자세한 내용과 사용법은 나중에 다뤄 보겠습니다. 오늘은 간단히 개요만 볼께요.

Loading Scenes

가장 기본적으로 Scene을 로드 하는 방법은 아래와 같습니다.
local storyboard = require "storyboard"
    storyboard.gotoScene( "scene1" )
여기서 scene1은 scene1.lua 파일의 내용으로 가라는 겁니다.
여기서 알아두어야 할 것은 이 scene1.lua는 scenetemplate.lua에 있는 규칙에 맞게 작성 되어져야 합니다.
gotoScene()함수는 다음 화면으로 가도록 하는 함수입니다.

Scene Events

각 화면(Scene)에는 여러 이벤트가 있어서 이 이벤트 별로 콘트롤이 가능합니다.
즉 scene1.lua에서 이 이벤트 가지고 콘트롤 하게 된다는 얘기 입니다.
storyboard API에는 4가지 이벤트가 있습니다.
이 이벤트를 핸들링 하려면 해당 리스너를 셋업 해야 하겠죠?
이 예제는 아까 생성됐던 scenetemplate.lua 파일에 소개 돼 있습니다.
조금 후에 볼께요.

createScene
storyboard.gotoScene()이 call 되고 새 화면이 display 될 때 동작을 합니다.
enterScene
storyboard.gotoScene()이 call되고 transition이 완료된 직후에 동작합니다.
exitScene
gotoScene()이 call되고 transition이 막 일어나기 전에 동작합니다.
이것은 현재의 Scene에 해당 되는 거겠죠? 이전에 두개는 그 다음 Scene에 해당하는 거구요.
destroyScene
display group이 removed 되기 바로 전에 동작합니다. 그리고 storyboard.purgeScene()이나 storyboard.removeScene()을 call 했을 때도 동작합니다.

지 이제 Scene Template를 보겠습니다.
----------------------------------------------------------------------------------
-- scenetemplate.lua
----------------------------------------------------------------------------------
local storyboard = require( "storyboard" )
local scene = storyboard.newScene()
----------------------------------------------------------------------------------
--    NOTE:
--    Code outside of listener functions (below) will only be executed once,
--    unless storyboard.removeScene() is called.
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
-- BEGINNING OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------
-- Called when the scene's view does not exist:
function scene:createScene( event )
    local group = self.view
 -----------------------------------------------------------------------------
    --    CREATE display objects and add them to 'group' here.
    --    Example use-case: Restore 'group' from previously saved state.
----------------------------------------------------------------------------
end
-- Called immediately after scene has moved onscreen:
function scene:enterScene( event )
    local group = self.view
-----------------------------------------------------------------------------
    --    INSERT code here (e.g. start timers, load audio, start listeners, etc.)
----------------------------------------------------------------------------
end
-- Called when scene is about to move offscreen:
function scene:exitScene( event )
    local group = self.view
-----------------------------------------------------------------------------
    --    INSERT code here (e.g. stop timers, remove listeners, unload sounds, etc.)
-----------------------------------------------------------------------------
end
-- Called prior to the removal of scene's "view" (display group)
function scene:destroyScene( event )
    local group = self.view
----------------------------------------------------------------------------
    --    INSERT code here (e.g. remove listeners, widgets, save state, etc.)
-----------------------------------------------------------------------------
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

공간을 절약하기 위해 필요없는 라인은 지웠는데요.
여러분은 scenetemplate.lua 파일을 열어 보시면 됩니다.

주석이 아주 자세히 달려 있어서 보시면 아실 겁니다.
쭉 훑어 보면
storyboard를 require하고 storyboard.newScene()을 합니다.
그 밑에 보면 좀 전에 다뤘던 createScene,enterScene,exitScene,destroyScene 함수가 차례대로 나옵니다.
createScene에 display object들하고 이 객체들을 그룹화 하는 것을 구현해야 합니다.
그리고 enterScene에 이 화면에서 동작할 내용들을 코딩해야 합니다.
주로 타이머 시작이나 오디오 로드, 리스너 등록 등의 작업을 이곳에서 합니다.
exitScene 함수 안에서는 타이머 멈춤, 리스너 제거, unload sounds 등의 작업을 합니다.
destroyScene 함수에서는 리스너 제거나 게임같은 경우에 저장해야 할 데이터(점수,레벨 등등) 저장 등을 이곳에서 합니다.

그리고 그 밑에는 이 함수들에 대한 리스너를 다는 방법이 설명 돼 있습니다.
이건 보시면 아실 겁니다.

각 scene들은 마지막에 반드시 return scene을 해야 합니다.

Scene Purging and Removal

이전 화면에서 다음 화면으로 넘어갔을 때 이전 화면은 스크린에서만 안 보이는 것이지 실제로 없어진 것은 아닙니다. 그 얘기는 이전 신은 여전히 메모리를 차지하고 있다는 얘기 입니다. 이전화면에 빨리 돌아가야 하면 이렇게 메모리에 올라 있는것이 더 좋을 겁니다.
하지만 때때로 절대 불려지지 않을 화면인데 계속 메모리만 차지하고 있으면 메모리 낭비이고 퍼포먼스에도 영향을 줍니다.
이럴때 purge나 remove 함수를 씁니다.

storyboard.purgeScene(sceneName)

이 함수는 해당(seneName) scene을 purge시킵니다. 즉 보이지 않게 없어진다는 겁니다. 하지만 이 scene은 메모리에 남아 있게 됩니다. 그래서 다른 화면에서 이 화면을 storyboard.gotoScene()하게 되면 빠르게 불려지게 됩니다.
purge되기 전에 destroyScene 함수가 실행 될 겁니다.

storyboard.removeScene(sceneName)

이 함수는 우선 scene을 purge하고 나서 메모리에서 이 scene을 unload 합니다.
이후에 이 scene이 gotoScene()으로 call되면 메모리에 reload되게 됩니다.

Automatic Scene Purging

개발자가 위 purge나 remove를 하지 않은 상태에서 OS의 메모리가 부족하게 되면 Corona는 자동적으로 이전 Scene을 제거합니다.

Excluding objects from Transitions

scenetemplate.lua에서 보시면 local group=self.view 가 있을 겁니다.
view 는 그 Scene의 display 객체들입니다. 만약 화면전환 효과에 특정 display를 적용시키지 않으려면 두가지 방법이 있습니다.
1. 객체를 main.lua에서 생성한다.
2. scene 모듈에서 객체를 생성했으면 해당 객체를 view 그룹에 insert 하지 않는다.

이 방법을 사용할 때는 잘 생각해서 해야 겠죠? 2번의 방법을 사용할 때 그 객체를 새로 생성되는 로직이 있다면 이전의 객체는 남아있고 새로운 객체는 계속 생기고 하는 현상이 발생합니다.

제가 director.lua 클래스 사용할 때도 group 에 객체를 insert 시키지 않아서 에러가 생기는 바람에 그 에러 잡느라고 시간을 좀 보냈거든요.

오늘은 이렇게 storyboard API에 대해서 개요를 살펴 봤구요.
다음엔 코로나에서 제공한 샘플 코드를 분석해 볼까 합니다.

오늘 글은 아래 코로나 홈페이지에서 제공한 글을 바탕으로 작성했습니다.
http://blog.anscamobile.com/2011/11/introducing-the-storyboard-api/

반응형


반응형

Build 2011.703 버전이 릴리즈 됐고 곧바로 공개됐습니다.

이전 버전들은 일반 공개까지는 안가고 유료 구매자들만 다운 받게 했는데 이번 릴리즈 버전은 테스트를 거쳐서 안정성까지 확인 됐나 봅니다.

많은 기능이 바뀌고 추가 될 거라고 얼마전부터 예고를 했었는데...

어떤 것이 바뀌었는지 주요한 부분을 알아볼께요.

inneractive Ads




지금까지 코로나로 만든 앱에서는 공식적으로 inMobi 광고만 됐습니다. 비공식적으로 web pop up 기능을 이용해서 Admob 광고가 가능한 방법이 개발자들 사이에서 공유 됐었는데요.
이번 버전에서 inneractive 를 이용한 광고가 가능하도록 했답니다.
inneractive는 여러(100여개)의 광고 네트워크를 사용할 수 있도록 연결해 주는 네트워크라고 합니다.
코로나에서는 이 기능을 통해서 앱 개발자가 좀 더 많은 수입을 올릴 수 있도록 했다고 홍보하고 있네요.
inneractive 사용방법은 아래 링크에 있습니다.
http://developer.anscamobile.com/reference/ads-inneractive
사용방법은 기존 inMobi 사용하는것과 비슷해 보입니다.
저도 한번 해 보고 얼마나 수입의 변화가 있는지 한번 봐야겠네요.

Kindle Fire and NOOK

아마존(Amazon)의 Kindle Fire 와 반즈 앤 노블즈(Barnes & Noble's) 사의 NOOK Color 플랫폼을 지원하게 됐습니다. 두 태블릿 모두 안드로이드 기술을 사용하고 있지만 내부적으로 들어가면 좀 다르다고 합니다.
그래서 이 기능을 제대로 이용하려면 유료버전을 사용해야 한답니다. 그것도 PRO 버전으로요.


Storyboard API

이번에 새로 제공되는 화면 전환용 API 입니다.
샘플 앱을 돌려봤는데요. 여러 화면전환 효과를 사용할 수 있더라구요.
기존에는 transition.to 를 사용하거나 director.lua를 사용했는데 이제 코로나 SDK 에서 공식적으로 제공하는 화면전환 API 도 사용할 수 있게 됐네요.
코로나 SDK의 기본정신이 적은 코딩으로 큰 효과를 보는 것이잖아요. 이 기능도 아주 간단하게 사용할 수 있더라구요.

나중에 한번 자세하게 다뤄 볼께요.
미리 공부하고 싶으신 분은 아래 링크를 참조 하세요.
http://developer.anscamobile.com/content/storyboard
물론 새 릴리즈 버전을 인스톨 하면 샘플 코드도 있습니다.

Native UI in Mac OS X Simulator



예전에 안 됐던 시뮬레이터에서 안 돼 디바이스에 인스톨 한 다음에 테스트 해야 됐던 text fields, message boxes, web popups 등이 맥에서 시뮬레이터로 볼 수 있게 됐습니다. (윈도우즈에서는 아직도 안 되나 봅니다.)

New Corona Home Screen


Corona SDK 를 실행하면 나오는 화면이 새로와 졌습니다.




주요한 변화들은 이렇습니다.
이외에 여러 버그들이 수정됐고 다른 마이너한 변화들이 있다네요.

자세한 릴리즈 노트를 보려면 아래 링크를 열어 보세요.
http://developer.anscamobile.com/content/corona-sdk-release-notes-2011703

반응형


반응형
오늘은 외부 모듈을 불러와 클래스를 실행하는 기본 개념에 대해 알아보겠습니다.
얼마전 모듈에 대해 한번 다룬적이 있었는데요.
이 모듈과 클래스 개념은 아주 중요한 거라서 처음 프로그래밍을 접하시는 분들이나 아직 이 부분에 대해 개념이 모호한 분들은 보실만 할 겁니다.

모든일이 그렇듯 반복과 숙달이 필요한 부분인 것 같습니다.

What is a Class?
이 글을 마치면 클래스가 무엇인지 감이 잡히실 겁니다. 여기서 알아야 될 것은 그 클래스들을 이용하는데 어떤 잇점이 있으며 어떻게 이용하는가 입니다. 실제로 편리하게 이용하기 위해서 만들어 진 것이니 무엇이 클래스인가 아는것도 다 사용하려고 하는 것 아니겠습니까?

그러니 일단 시작해 보겠습니다.

객체지향 프로그래밍에서 객체(Object)는 그냥 우리가 일상생활에서 보는 사물이나 동물, 사람 등입니다. (때로는 개념이 될 수도 있겠죠)
그리고 그 객체들은 일정한 행동 양식이 있습니다.
만약에 개라는 객체를 여러분 앱에서 표현하고자 한다면...
개는 뛰고, 짖고, 부비고, 자고, 돌고 그러죠?
만약 고전 오락 갤러그에서 외계인이라는 객체를 보면
옆으로 움직이고, 아래로 내려오고, 미사일을 쏘고, 아군 미사일에 맞으면 폭발하고 그러죠?

이렇게 객체들의 행위를 구현하는 부분을 메소드 또는 함수 라고 합니다.
(자바에서는 메소드라고 하고 루아-코로나-에서는 함수라고 합니다.)

그리고 이 객체와 메소드가 있는 곳이 클래스 입니다.

직접 보시죠.

오늘은 우리랑 친근한 개로 클래스를 만들겠습니다. (개 클래스가 되겠네요.)

-------------------------------------------------
-- dog.lua
-- Example "dog" class for Corona SDK tutorial.
-------------------------------------------------
local dog = {}
local dog_mt = { __index = dog } -- metatable
-------------------------------------------------
-- PRIVATE FUNCTIONS
-------------------------------------------------
local function getDogYears( realYears ) -- local; only visible in this module
return realYears * 7
end
-------------------------------------------------
-- PUBLIC FUNCTIONS
-------------------------------------------------
function dog.new( name, ageInYears ) -- constructor

local newDog = {
name = name or "Unnamed",
age = ageInYears or 2
}

return setmetatable( newDog, dog_mt )
end
-------------------------------------------------
function dog:rollOver()
print( self.name .. " rolled over." )
end
-------------------------------------------------
function dog:sit()
print( self.name .. " sits down in place." )
end
-------------------------------------------------
function dog:bark()
print( self.name .. " says \"woof!\"" )
end
-------------------------------------------------
function dog:printAge()
print( self.name .. " is " .. getDogYears( self.age ) .. " in dog years." )
end
-------------------------------------------------

return dog

처음에 dog이라는 변수가 테이블로 만들어 졌습니다.
테이블로 만든 이유는 개는 한마리가 아니라 여러마리가 있을 수 있잖아요?
도꾸, 메리,  쫑 이렇게...
그렇게 여러마리를 표현하기 위해서 테이블로 만들었습니다.
그리고 메타테이블로 dog_mt 변수를 만들었습니다.

그 다음엔 getDogYears라는 로컬 함수(메소드) 가 있고 realYear 라는 인수를 받습니다.
아까 함수는 어떤 동작을 표현한다고 그랬죠?
이 함수는 인수로 받은 값에 7을 곱하는 동작을 하네요.
개가 직접 하는 동작이 아니라 어떤 로직과 관련된 동작이군요.
이렇게 개념이나 로직과 관련된 행위도 메소드(함수)로 표현됩니다.
로컬로 선언한것은 자바에서 private로 선언한 것과 비슷한데요.
의미는 이 dog.lua라는 클래스 안에서만 이 함수를 call할 수 있다는 겁니다.
외부파일에서는 이 함수를 콜할수 없습니다.

다음은 글로벌 함수인 dog.new 함수가 있습니다. 이 함수는 name과 ageInYears를 인수로 받습니다.
하는 일은 이름과 나이를 세팅하고 이를 메타테이블에 세팅한 값을 반환해 줍니다.
글로벌 함수는 자바에서의 public 과 같은데요. 이건 외부에서도 이 함수를 콜 할 수 있다는 의미입니다.
dog.new함수는 자바에서의 생성자 역할을 하네요. dog란 객체를 생성하는 겁니다.
외부에서 개를 만들 때 (그게 도꾸든 메리든 쫑이든....) 이 함수를 제일 먼저 불러서 객체를 만들어야 한다는 얘기입니다.

다음은 모두 글로벌 함수네요. dog:rollOver,dog:sit,dog:back, dog:printAge 이렇게 네개의 함수가 있습니다.
구르고 앉고 짖는 등 개가 하는 행위를 구현하는 3개의 메소드와 나이를 표시하는 함수 이렇게 4개의 함수(메소드)가 있습니다.

여기서는 터미널에 개가 구른다, 앉는다, 짖는다 같이 문자로 뿌려지는데요.
나중에 게임같은걸 개발 하게 되면 이 부분에 개 이미지를 넣고 애니메이션 기능을 이용해서 구르거나 앉거나 짖는 것을 구현하면 됩니다.

그럼 이 dog 클래스를 어떻게 사용해야 할까요?

main.lua를 보겠습니다.

local dog = require( "dog" )
local dog1 = dog.new( "Yai-Ya", 4 )
local dog2 = dog.new( "Toki", 1 )
    dog1:printAge()
    dog2:printAge()
   
    dog1:rollOver()
    dog2:sit()

보시면 처음에 dog.lua를 require합니다. 이때 .lua는 쓰지않고 파일 이름만 씁니다.

그리고나서 dog1,dog2 이렇게 두마리 강아지를 만듭니다. (두개의 객체를 만들었습니다.)
dog.new로 만들었는데요. 첫번째 개는 이름이 Yai-Ya 고 나이는 4살이네요.
두번째 개는 이름이 Toki이고 나이는 1살이구요.

다음으로 두 개의 나이를 표시해 줍니다.
그리고 첫번째 개 Yai-Ya에게는 구르라고 하고 두번째 개 Toki에게는 앉으라고 합니다.

이것을 실행하면 아래와 같이 됩니다.


보시면
Yai-Ya 28살이고 Toki는 7살이라고 처음에 나오네요.
4살, 1살이라 그랬는데 28살 7살이라고 나온 이유는 dog class에서 printAge()를 보면 나이를 받아서 곱하기 7을 하는 getDogYears( self.age ) 함수를 콜하고 있죠?
그래서 개 나이에 7을 곱해서 나온 겁니다.
아마 개 나이 곱하기 7하면 얼추 사람 나이가 되나 봅니다.

그리고 다음엔 Yai-Ya는 구르고 Toki는 그 자리에 앉는다고 돼 있죠?

모듈과 클래스는 이렇게 이용하시는 겁니다.

처음에 개를 만들고 (이름, 나이) 그리고 그 개에게 짖고,앉고, 구르고 하는 것을 시키는 겁니다.

처음에 메타 테이블을 만들고 생성자 dog.new()에서 이를 return(반환) 했죠? 이것은 새로운 인스턴스를 만드는데 아주 효과적이기 때문입니다. 새로운 인스턴스라 함은 도꾸,메리,쫑 이 각각의 개들의 존재를 구분할 수 있게 해 준다는 겁니다. 이 메타테이블을 이용하지 않으면 개를 한마리 밖에 만들지 못할겁니다. 그것이 도꾸든 메리든 쫑이든....

이제 어느정도 클래스에 대해 감이 잡히셨나요?
처음에 얘기 드렸지만 반복과 숙달이 필요한 부분입니다.

이 글 원본은 아래에 있습니다.
원본을 바탕으로 설명은 그냥 제 방식으로 했습니다.
http://blog.anscamobile.com/2011/09/tutorial-modular-classes-in-corona/

혹시 이 코드가 실행되지 않으시는 분은 댓글 달아 주세요.
정식 등록자가 받을 수 있는 최신 업데이트 된 버전에서만 될 수도 있거든요.
그러니 혹시 실행이 안 되시면 저한테 알려 주세요.



그럼...
반응형


반응형
모바일 애플리케이션으로 E-Book을 구현하거나 하다 보면 책장 넘기는 효과를 주면 훨씬 더 실감나는 E-Book 앱을 만들 수 있습니다.

오늘은 이 책장 넘기는 효과 주는 방법에 대해 공부하겠습니다.
오늘 소개하는 코드는 아주 기본적인 기능만 있습니다.

이 코드에다 여러분이 여러 효과를 주시면 더욱 더 그럴싸한 e-Book을 만들 수 있을 겁니다.

페이지 넘김 효과를 주기 위해서는 두가지 방법으로 이 효과를 구현할 수 있습니다.
첫번째는 frame-based animation을 구현하는 겁니다.. 그러기 위해서 스크린 만한 이미지들이 필요합니다.  즉 아이폰, 아이패드에 맞는 이미지들이 따로따로 있어야 합니다. 그것도 폰을 세웠을 때와 눕혔을 때 모두 다 따로따로 있어야 합니다.

두번째는 optical illusion을 통해 하나의 이미지, bitmap mask 그리고 transitions를 통해 구현하는 방법입니다.

두번째 방법이 훨씬 좋겠죠? 오늘 제공될 소스코드에는 이 두번째 방법을 소개해 드릴겁니다.



Step 1. Preliminary Work

페이지 넘김 효과를 사용하기 위해서는 몇개의 이미지들이 필요합니다.

이 두 이미지는 각각 page1.jpg,page2.jpa입니다.
이것 말고 책장 넘기는 효과를 줄 이미지들도 필요합니다.


이제 이미지가 마련 됐으면 코딩을 해 보겠습니다.

-- hide the status bar
display.setStatusBar( display.HiddenStatusBar )

local page1 = display.newImageRect( "page1.jpg", display.contentWidth, display.contentHeight )
page1:setReferencePoint( display.TopleftReferencePoint )
page1.x, page1.y = display.contentWidth*0.5, display.contentHeight*0.5

local page2 = display.newImageRect( "page2.jpg", display.contentWidth, display.contentHeight )
page2.x, page2.y = display.contentWidth*0.5, display.contentHeight*0.5
page2:toBack() -- make sure 2nd page is underneath the first one

local curlPage = display.newImageRect( "curlPage.png", display.contentWidth, display.contentHeight )
curlPage.x, curlPage.y = display.contentWidth*0.5, display.contentHeight*0.5
curlPage.isVisible = false

-- group to hold the page that will be turned (as well as the "curl" page)
local turnGroup = display.newGroup()

이 4개의 이미지를 불러오는 코드 입니다.
두번째 페이지는 첫번째 페이지 밑으로 가게 했습니다. 그리고 curlPage는 inVisible을 false로 했구요.
이 코드만 실행하면 책 겉장만 보일겁니다.

Step 2. Page Curl Functions

다음은 페이지 넘김 효과를 줄 함수들입니다.

-- The following function will turn the page "back"
local function gotoPrevious( curlPage, time )
local time = time or 500

curlPage.isVisible = true
local hideCurl = function()
curlPage.isVisible = false
turnGroup:setMask( nil )
end
transition.to( turnGroup, {maskX=display.contentWidth*0.5+100, time=time } )
transition.to( curlPage, { rotation=45, x=display.contentWidth+(display.contentWidth*0.10), y=display.contentHeight + (display.contentHeight*0.25), time=time, onComplete=hideCurl })
end

-- The following function will turn the page "forward"
local function gotoNext( currentPage, curlPage, time )
-- add "pages" to page turning group
turnGroup:insert( currentPage )
turnGroup:insert( curlPage )

-- mask should match dimensions of content (e.g. content width/height)
local curlMask = graphics.newMask( "mask_320x480.png" )
turnGroup:setMask( curlMask )

-- set initial mask position
turnGroup.maskX = display.contentWidth * 0.5+100
turnGroup.maskY = display.contentHeight * 0.5

-- prepare the page-to-be-turned and the curl image
currentPage:setReferencePoint( display.BottomLeftReferencePoint )
curlPage:setReferencePoint( display.BottomRightReferencePoint )
curlPage.rotation = 45
curlPage.x = display.contentWidth+(display.contentWidth*0.10)
curlPage.y = display.contentHeight + (display.contentHeight*0.25)
curlPage.isVisible = true

-- show pagecurl animation and transition away (next page should already be in position)
local time = time or 500
transition.to( turnGroup, { maskX=-display.contentWidth*0.75, time=time } )
transition.to( curlPage, { rotation=0, x=0, y=display.contentHeight+20, time=time} )
curlPage.yScale = curlPage.y * 0.2
end

소스코드를 좀 살펴 볼까요?

처음에 나오는 함수는 gotoPrevious 함수 입니다.
인수로 curlPage와 time을 받습니다.
그리고 로컬 변수 time에 값을 넣고 curlPage를 보이도록 바꿉니다.
이 함수의 로컬 함수 hideCurl에서 curlPage를 안 보이도록 하고 setMask를 해제하는 로직을 넣어 둡니다.
transition.to로 turnGroup에 움직이는 효과를 주고 또 curlPage도 움직이는 효과를 주고 이 효과가 끝나면 로컬 함수인 hideCurl을 불러와 curlPage를 안 보이도록 하고 setMask를 해제 하도록 합니다.

그 다음은 gotoNext 함수인데요 currentPage,curlPage,time을 인수로 받습니다.
이 함수 내용은 좀 기네요.
turnGroup에 인수로 받은 currentPage와 curlPage를 insert 합니다.

그리고 curlMask 로컬 변수에 graphcs.newMask로 mask_320X480.png를 세팅해 넣습니다.
graphics.newMask 는 http://developer.anscamobile.com/node/5248 API를 참조하세요.
그리고 이 curlMask를 turnGrooup에 insert 합니다.
maskX와 maskY 값을 할당해 주고요 이미지들의 레퍼런스 포인트를 bottomLeft BottomRight 로 주고 curlPage를 45도 회전시키고 이 curlPage의 위치를 설정해 줍니다.
그리고 이 curlPage.isVisible을 true로 세팅해 줍니다.
그 다음 time값을 세팅하고 transition.to로 움직임 효과들을 줍니다.

이렇게 하면 앞장으로 넘기고 뒷장으로 넘기는 효과를 주는 함수까지 완료 했습니다.

다시 간단히 정리하자면요.
gotoNext()
1. page-to-turn으로 insert하고 curl 이미지를 turnGroup에 넣는다.
2. 비트맵 마스크를 turnGroup에 적용한다.
3. 페이지와 curl  이미지의 레퍼런스 포인트를 bottom right으로 한다.
4. curl 이미지를 회전시키고 시작 위치를 잡아준다.
5. transition을 시작한다.

gotoPrevious()
1. curl 이미지를 visible하게 한다.
2. onComplete 리스너를 달아서 transotion이 끝나면 hiding 페이지를 하도록 하고 turnGroup에서 비트맵 마스크를 없앤다.
3. transition 애니메이션을 시작한다.

Step3. Initiate the Page Curl

timer.performWithDelay( 2000, function() gotoNext( page1, curlPage, 500 ); end )
timer.performWithDelay( 5000, function() gotoPrevious( curlPage, 500 ); end )

이 함수들을 실행하기 위해 2초후에 gotoNext함수를 부르고 5초후에 gotoPrevious 함수를 부릅니다.

그러니까 이 소스코드로는 손가락으로 책장을 넘기는 효과가 아니라 2초후에 책장이 넘어가고 5초후에 책장이 다시 넘어오게 될 겁니다.

제대로 구현하려면 여러분들이 touch나 tap 리스너를 달아서 이 책장 넘김 효과를 컨트롤 하셔야 합니다.

이 소스코드에 대한 원문 강좌는 아래 페이지에 있습니다.
http://blog.anscamobile.com/2011/12/how-to-create-a-page-curl-in-corona/

그리고 소스파일은 아래에 있습니다.

그럼 오늘도 즐코딩.....


반응형


반응형
1. 아마존 (amazon.com)의 Kindle fire 시뮬레이터 추가 - http://yfrog.com/h4yz7gp

2. 올해의 Best Mobile Application 후보에 Nominate - http://crunchies2011.techcrunch.com/nominate/?NDpDb3JvbmEgU0RL

Nominate your favorite companies, products and people for one of our Crunchie award categories below.  Everyone is invited to submit one nomination per category per day through Tuesday night, December 13th, 2011 at 11:59PM PST. Once you nominate a company in a category, you are able to share your nominations through Twitter and Facebook, or you can embed a badge or link to the nomination.  Top nominees in each category will be notified and become 2011 Crunchies Finalists.

Finalist voting will begin in early January 2012.

For past winners, see the page for 2007, 2008, 2009 and for 2010.

좋아하는 회사, 상품 사람들을 Crunchie award category에서 nominate 해 주세요. 누구나 2011년 12월 13일 목요일 밤까지 하루에 한개 카테고리당 1개씩 submit 할 수 있습니다. 카테고리 안에 한 회사를 nominate했으면 당신의 nomination을 트위터나 페이스북을 통해 당신의 친구들과 share할 수 있습니다. 또는 노미네이션 뱃지나 링크를 embed할 수 있습니다. 각 카테고리의 top nominees들은 2011 Crunchies finalists가 되어 발표 됩니다. 최종 결선은 2012년 1월 초에 있을 겁니다.

지난 우승자들을 보려면 링크를 클릭하세요 .2007, 2008, 2009 and for 2010.

3. HP 가 2주 이내에 새로운 Web OS를 내 놓는 답니다.
http://moconews.net/article/419-yes-hp-may-finally-have-some-news-on-webos-in-the-next-two-weeks/

오늘 제가 접한 새로운 소식들은 이렇습니다.

코로나가 아마존 앱마켓에 특별히 자체 개발한 게임 앱을 올려 놓고 홍보하고 그러더니...
시뮬레이터에 아마존의 Kindle Fire 까지 추가했군요.
코로나와 아마존이 계속 긴밀한 관계를 맺고 있는 것 같습니다.

그리고 Corona SDK 가 올해의 베스트 모바일 앱에 추천해 달라고 홍보 하고 있네요.

작년의 베스트 모바일 앱은 구글 맵이 차지 했었네요.

Best Mobile App 2010
Bump
Chomp
Google Mobile Maps for Android (winner)
Hashable
Instagram (runnerup)


저는 Corona SDK를 베스트 모바일 애플리케이션 후보로 추천하고 트위터와 페이스북에도 쉐어 했습니다. ^^




반응형


반응형
자주 사용하는 기능을 모듈화 하면 나중에 재사용할 때 아주 편하고 개발 기간도 많이 save해 줍니다.
예를 들어 스크린 사방 벽에 wall을 만들고 객체들이 이 벽에 맞고 튀어야 할 때 사방 벽 만드는 부분을 모듈화 한다던지.
아니면 레벨별로 배경화면을 다르게 해야 되는 게임을 개발 할 때 배경화면 설정 부분을 모듈화 한다던지.
그 외 여러가지 자기만의 로직을 모듈화 해 두면 그게 자산이 되고 나중에 개발할 때도 많은 도움이 됩니다.

오늘은 이렇게 자기만의 코드를 모듈화 하는 방법에 대해 알아보겠습니다.


낯익은 사진이죠?
자기가 힘들여서 터득한 know how를 열심히 다른 개발자들고 Share하고 있는 peach입니다.
홈페이지는 http://peachpellen.com/ 입니다. 가셔서 고맙다고 코멘트 남기는 것도 좋을 것 같습니다.

이 앱에는 두가지가 모둘화 돼 있습니다.

첫번째는 background image 를 표시하고 이 배경이미지를 클릭하면 없어지는 함수입니다.
그리고 두번째는 x,y좌표 파라미터를 받아서 peach.png 이미지 파일을 해당 x,y좌표에 display 하는 겁니다.

이 모듈화 된 소스를 보겠습니다. ----- setup.lua -----
-- Makes sure the file can be "seen"
module(..., package.seeall)

-- Set up the background
function background (event)
    -- Display the background
    local background = display.newImage ("bg.png")
        -- The function to remove the background
        function killbackground (event)
            background:removeSelf()
        end
    -- Add the event listener to the background
    background:addEventListener("tap", killbackground)
end

-- Insert the traditional Peach pic, this time using params
function peach (params)
    -- My picture
    local peach = display.newImage ("peach.png")
    -- The X and Y of my pic, defined when the function is called
    peach.x = params.x
    peach.y = params.y  
end

첫번째 부분은 모듈화 하기 위해 지정해 줘야 하는 코로나 SDK의 규칙입니다.
두번째 event를 인수로 받는 background라는 함수입니다.
bg.png를 display하고 여기에 tap 이벤트 리스너를 달아서 tab하면 배경이미지를 없애는 함수 입니다.
세번째 함수는 peach라는 함수로 peach.png를 display하고 x,y좌표를 전달받은 x,y좌표로 설정해 이미지를 그 위치에 그려줍니다.

그러면 이 모듈화된 부분을 어떻게 사용하는지 알아보겠습니다.

-- Hides the status bar
display.setStatusBar (display.HiddenStatusBar)

-- Requires setup.lua
require "setup"

-- Performs our "background" event from setup.lua (Create background, create remove function, add listener)
setup.background()

-- Performs our "peach" event; note the x and y - these are our "params"
setup.peach ({ x = 160, y = 240 })

-- Uncomment the block below to see how useful this type of code is when spawning lots of the same image/object
--[[
setup.peach ({ x = 60, y = 100 })
setup.peach ({ x = 60, y = 400 })
setup.peach ({ x = 260, y = 100 })
setup.peach ({ x = 260, y = 400 })
--]]

--[[
*NOTES*
Like many of my tutorials this is as simple as it gets; it shows how to add an image, add a function to that image and
remove that image all in one function from a seperate Lua file.

There is near limitless potential for using multiple files to keep your code clean and organised however this mini
tutorial is only intented to highlight the basics.

By learning how to use the simple examples shown here you are well on your way to much tidier code; something that
will really come in handy as your projects grow larger and more ambitious.
--]]

실제 코드는 단 세 줄 입니다.
우리의 친구 peach가 아주 자세하게 주석을 달아 줬네요.

우선 setup.lua를 require하구요.
background()함수를 불러옵니다.
그리고 peach함수도 해당 파라미터를 넣어서 불러오구요.

그러면 끝입니다.

이렇게 되면 setup.lua에 있는 함수는 다른 앱을 개발 할 때도 그대로 모듈로 사용할 수 있습니다.

원본 소스코드는 아래 압축파일에 있습니다.


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


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

이제 남은 일은 밑에 있는 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 객체의 위치값으로 대입합니다.
그러면 이동하게 됩니다.

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

반응형