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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형
Posted on . Written by



수요일 FAQ 시간이 돌아왔습니다. 오늘은 서브 폴더와 파일들에 접근하는 것에 대한 FAQ 입니다.


1. How do you create a new sub-folder within the Documents or Temporary directory?



루아 파일 시스템(LFS)을 통해서 디렉토리에 서브 폴더를 추가할 수 있습니다. Resource 디렉토리는 modify 될 수 없는 read-only 입니다.

아래에 Documents 디렉토리에 어떻게 Images 폴더를 생성할 수 있는지에 대한 예제가 있습니다.


local lfs = require "lfs"

-- get raw path to app's Documents directory
local docs_path = system.pathForFile( "", system.DocumentsDirectory )

-- change current working directory
local success = lfs.chdir( docs_path ) -- returns true on success
local new_folder_path
local dname = "Images"
if success then
    lfs.mkdir( dname )
    new_folder_path = lfs.currentdir() .. "/" .. dname
end



여기에서 LFS 에 대한 좀 더 자세한 정보를 얻으실 수 있습니다.




2. How do you access (read or write) a file that has been placed in a sub-folder?


여러분은 두가지 방법으로 서브 폴더에 있는 파일에 access 하실 수 있습니다. 그 파일로 무엇을 할 것인지에 따라 접근하는 방법이 다른데요. 이미지를 display 하거나 sound 를 play 하신다면 서브 폴더 이름에 파일이름을 연결해서 거기에 base 디렉토리를 지정해 주시면 됩니다. 예를 들어 Document 디렉토리의 Images 서브폴더에 있는  cat.png 파일을 display 하고 싶다면 아래와 같이 하시면 됩니다.


local catImage = display.newImage( "Images/cat.png", system.DocumentsDirectory, 0, 0 )


Note : baseDirectory 파라미터가 필요한 system.pathForFile 을 API call 에서 사용하지 않은 점을 유의하세요. (e.g., display.newImage, display.newImageRect, audio.loadSound, etc.)


만약 같은 디렉토리의 readme.txt 파일을 열어보고 싶으시면 system.pathForFile 을 사용해서 아래와 같이 하세요.


local path = system.pathForFile( "Images/readme.txt", system.DocumentsDirectory )
local fileHandle = io.open( path )
-- You can now use fileHandle:read or fileHandle:write to read or write the file.


해당 파일이 있다면 fileHandle 은 nil 이 아니겠죠. 그 다음은  아래 질문과 연결 되게 됩니다.



3. How do you test that a file exists in a folder or sub-folder?



해당 폴더나 서브폴더 안에 특정 파일이 존재하는지를 보기 위해 아래와 같이 코딩 하실 수 있습니다. 이 함수를 call 하기 전에 서브폴더 이름을 file 이름에 append 하는 것을 잊지 마세요.


----------------------------------------------------------------------------------
-- doesFileExist
--
-- Checks to see if a file exists in the path.
--
-- Enter:   name = file name
--  path = path to file (directory)
--  defaults to ResourceDirectory if "path" is missing.
--
-- Returns: true = file exists, false = file not found
----------------------------------------------------------------------------------
--
function doesFileExist( fname, path )

    local results = false

    local filePath = system.pathForFile( fname, path )

    -- filePath will be nil if file doesn't exist and the path is ResourceDirectory
    --
    if filePath then
        filePath = io.open( filePath, "r" )
    end

    if  filePath then
        print( "File found -> " .. fname )
        -- Clean up our file handles
        filePath:close()
        results = true
    else
        print( "File does not exist -> " .. fname )
    end

    print()

    return results
end


위의 함수가 어떻게 call 되는지 보겠습니다.


-- Checking for file in Documents directory
local results = doesFileExist( "Images/cat.png", system.DocumentsDirectory )
    
-- or checking in Resource directory
local results = doesFileExist( "Images/cat.png" )


4. How do you copy a file to a sub-folder?

아래 코드는 파일을 A 폴더에서 B 폴더로 copy 하는 예제 입니다. 흔히 사용하는 방법은 파일을 카피하기 위해 Resource 디렉토리에서 Document 디렉토리로 옮기는 겁니다.
이 함수를 사용하기 전에 해당 서브 폴더들이 있어야 합니다.


----------------------------------------------------------------------------------
-- copyFile( src_name, src_path, dst_name, dst_path, overwrite )
--
-- Copies the source name/path to destination name/path
--
-- Enter:   src_name = source file name
--      src_path = source path to file (directory), nil for ResourceDirectory
--      dst_name = destination file name
--      overwrite = true to overwrite file, false to not overwrite
--
-- Returns: false = error creating/copying file
--      nil = source file not found
--      1 = file already exists (not copied)
--      2 = file copied successfully
----------------------------------------------------------------------------------
--
function copyFile( srcName, srcPath, dstName, dstPath, overwrite )

    local results = false

    local srcPath = doesFileExist( srcName, srcPath )

    if srcPath == false then
        -- Source file doesn't exist
        return nil
    end

    -- Check to see if destination file already exists
    if not overwrite then
        if fileLib.doesFileExist( dstName, dstPath ) then
            -- Don't overwrite the file
            return 1
        end
    end

    -- Copy the source file to the destination file
    --
    local rfilePath = system.pathForFile( srcName, srcPath )
    local wfilePath = system.pathForFile( dstName, dstPath )

    local rfh = io.open( rfilePath, "rb" )

    local wfh = io.open( wfilePath, "wb" )

    if  not wfh then
        print( "writeFileName open error!" )
        return false            -- error
    else
        -- Read the file from the Resource directory and write it to the destination directory
        local data = rfh:read( "*a" )
        if not data then
            print( "read error!" )
            return false    -- error
        else
            if not wfh:write( data ) then
                print( "write error!" )
                return false    -- error
            end
        end
    end

    results = 2     -- file copied

    -- Clean up our file handles
    rfh:close()
    wfh:close()

    return results
end


아래에 readme.txt 파일을 Resource 에서 Documents 디렉토리에 복사하는 방법이 있습니다.


copyFile( "readme.txt", nil, "readme.txt", system.DocumentsDirectory, true )
local catImage = display.newImage( "cat.png", system.DocumentsDirectory, 0, 0 )



5. What are the Android restrictions concerning files?


코로나에서의 File access 는 해당 운영체제(Operating System) 에 기반해서 이루어 집니다. 즉 플랫폼에 의존적인 거죠. iOS 디바이스에서는 Resource 디렉토리(main.lua 가 있는 디렉토리) 에 access 하실 수 있습니다. 그외에 Documents 그리고 Temporary 디렉토리들에 access 하실 수 있죠. 안드로이드에서는 Resource 디렉토리에 제한이 있습니다. 왜냐하면 그 디렉토리는 실제 디렉토리가 아니기 때문이죠. 파일들은 zip 파일로 압축돼 있습니다. 코로나는 audio와 image API들을 사용해서 직접 이미지와 오디오를 로딩할 수 있도록 해 줍니다. 하지만 I/O API를 사용해서 Resource 파일들에 접근하는 것은 한계가 있습니다.


안드로이드의 이러한 제한사항 때문에 다른 디렉토리에 카피할 Resouce 디렉토리 내의 파일을 옮긴다면 resource 디렉토리에 있는 파일 이름을 바꾸어야 합니다. 그래야 file I/O API에 의해 접근 될 수 있습니다. 예를 들어 이미지 파일을 Resource 에서 documents 디렉토리로 옮기고 싶다면 다른 확장자를 가진 파일로 이름을 바꾸셔야 합니다. 그래야 접근할 수 있습니다. cat.png cat.png.txt 로 바꾼 후 카피할 수 있습니다.


아래에 안드로이드에서 어떻게 cat.png 파일을 Document 디렉토리로 복사하는지 알려주는 예제가 있습니다. (assuming it was stored as cat.png.txt)


copyFile( "cat.png.txt", nil, "cat.png", system.DocumentsDirectory, true )
local catImage = display.newImage( "cat.png", system.DocumentsDirectory, 0, 100 )


안드로이드에서 Resource 디렉토리 안에 있는 것 중 읽을 수 없는 확장자들 : html, htm, 3gp, m4v, mp4, png, jpg, and rtf


위 방법은 모든 플랫폼에서 사용 가능합니다. 그러니 안드로이드에서 제대로 돌아가면 어디서든지 돌아갑니다.


오늘은 여기까지 입니다. 유익한 시간이었기를 바랍니다.

반응형


반응형
Posted on . Written by


애플이 iOS 5.0 을 발표했을 때 나왔던 편리한 기능은 native 트위터 지원이었습니다. 이전에는 그 기능은 유저를 인증하고 tweet 을 post 하기 위해 REST API 를 call 하고 하는 것들은 앱이 처리할 일이었었습니다. 코로나의 트위터 샘플 앱이 하듯이 말이죠.


iOS 5.0 에서는 device setting 에 아예 트위터 credential을 입력할 수 있게 됐습니다. 그러면 Email, SMS, Print 등과 함께 트위터가 Sharing screen 에 나타나게 되죠. 이렇게 함으로서 Twitter 를 관리하기 아주 쉽게 되었습니다. 지난해 12월 말에 저희는 이 native 트위터 기능에 접근할 수 있는 기능을 제공했었습니다.





Using “Native” Twitter


코로나는 native.showPopup() API 를 call 함으로서 iOS 의 built-in Twitter 기능에 접근할 수 있도록 합니다. 이 API call 은 native 인터페이스를 통해 이메일과 SMS 메세지를 보낼때 사용하는 것과 같은 겁니다. 또한 native device의 store 와 rating service들에 접근할 때 사용하는 그 API 이기도 하구요.

코딩은 아주 간단합니다.

native.showPopup( "twitter", options )


여기서 options 는 Lua table로 tweet 을 위해 필요한 정보들 입니다. 사용되는 파라미터들은 아래와 같습니다.

  • image — post 하길 원하는 이미지에 대한 form { baseDir=, filename= }
  • message — a string that prepopulates the message.
  • listener — popup 이벤트를 지원하는 리스너
  • url — post 하고 싶은 URL. URL string들이 올 수도 있음.


message option을 사용하는 가장 기본적인 코딩

local options = {
   message = "Hello Twitter world!"
}
native.showPopup( "twitter", options )


트윗들은 140 글자로 글자 수가 제한돼 있죠. 언제 tweet 이 제한 글자수를 다 채우는지 그리고 유저가 그 트윗을 commit 하거나 cancell 했는지를 알고 싶으면 이 유저의 action을 체크하기 위해 call back handler 를 정의하실 수 있습니다.

local function tweetCallback( event )
   if ( event.action == "cancelled" ) then
      print( "User cancelled" )
   else
      print( "Thanks for the tweet!" )
   end
end

local options = {
   message = "Hello Twitter world!",
   listener = tweetCallback
}
native.showPopup( "twitter", options )

업로드하길 원하는 이미지를 포함 시킬 수도 있습니다.


local function tweetCallback( event )
   if ( event.action == "cancelled" ) then
      print( "User cancelled" )
   else
      print( "Thanks for the tweet!" )
   end
end

local options = {
   message = "Hello Twitter world!",
   listener = tweetCallback,
   image = {
      baseDir = system.DocumentsDirectory,
      filename = "mypic.jpg"
      }
}
native.showPopup( "twitter", options )


마지막으로 그 트위터에 넣을 하나 이상의 URL 들을 포함 시킬 수도 있습니다. 이 링크들은 popup screen에 나타나지는 않을 겁니다. 트윗이 post 될 때 나타나게 되죠.


local function tweetCallback( event )
   if ( event.action == "cancelled" ) then
      print( "User cancelled" )
   else
      print( "Thanks for the tweet!" )
   end
end

local options = {
   message = "Hello Twitter world!",
   listener = tweetCallback,
   url = { "http://coronalabs.com", "http://apple.com" }
}
native.showPopup( "twitter", options )


이제 여러분 앱은 전달될 트윗을 popup screen 을 통해 보여줄 겁니다. 여기서 유저는 send 하거나 cancel 할 수 있습니다. iOS 가 이메일이나 SMS 메세지를 처리하는 것과 비슷한 과정입니다.


이 native 기능은 iOS 5.0 이상의 버전에서만 작동하는 것을 잊지 마세요. 코로나에서는 Daily Build 990 부터 가능합니다. 이 기능은 아직 안드로이드에서는 지원되지 않습니다.


반응형


반응형
Posted on . Written by



오늘의 guest tutorial 은 Ingemar Bergmark이 제공합니다. 그는 Corona Enterprise 개발자 입니다. 유닉스 세계에서 Informix와 Oracle을 사용한 DBA/Systems Analyst 경력을 갖고 있고 25년간 프로그래밍을 해 오고 있습니다. 그는 그의 첫번째 컴퓨터인 Sinclair ZX-80 와 더불어 프로그래밍의 세계에 흥미를 갖기 시작했습니다. 10대 초반에 게임 개발에 관심을 갖기 시작했었고 Hercules Graphics Card 가 나오고 부터는 그의 친구들을 위해  graphics SDKs 들을 만드는데 재미를 붙였었습니다.  최근에 그는 게임 개발 세계에 그의 venture 기업을 만듦으로서 발을 본격적으로 발을 들여 놨습니다. 그는 한국 시흥의 독립 스튜디오인 Swipeware의 owner 입니다. 그의 웹 사이트인 www.swipeware.com에 가시면 좀 더 자세한 내용을 보실 수 있습니다.

여러분 앱에서 직접 여러분의 custom extension을 가지고 이메일의 첨부를 열어 보고 싶지 않으세요? 오늘의 튜토리얼은 어떻게 iOS build settings 을 하고 여러분 앱의 documents 디렉토리에 파일을 보내고 그 파일의 path를 구하기 위해 launchArgs URL을 사용하는 방법을 코로나로 어떻게 하는지에 대해 설명 드리겠습니다.





1. Register the Extension in build.settings



첫번째로 여러분의 extension을 build.settings 의 plist section 에 register 시켜야 합니다. 그 안에 두개의 table들이 있게 될 텐데요. 그 테이블 안에는 여러 파라미터들이 들어가게 될 겁니다.

    CFBundleDocumentTypes 은 여러분의  custom extension 을 정의합니다.
    UTExportedTypeDeclarations tells iOS about your custom extension so that other apps (like Mail) will present the option to open the file in your app.
    UTExportedTypeDeclarations는 iOS 에게 여러분의 custom extension에 대해 알려 줍니다. so that other apps (like Mail) will present the option to open the file in your app.

아래 예제에서는 “wxyz“ 라는 custom file extension을 register 합니다. build.settings modifications를 보세요.


settings =
{
orientation =
{
default = "portrait",
supported = { "portrait" }
},
iphone =
{
plist =
{
UIStatusBarHidden = false,
UIPrerenderedIcon = true,
UIApplicationExitsOnSuspend = false,
CFBundleDocumentTypes =
{
{
CFBundleTypeIconFiles =
{
"doctype-hires.png",
"doctype.png"
},
CFBundleTypeName = "wxyz File",
CFBundleTypeRole = "Viewer",
LSHandlerRank = "Owner",
LSItemContentTypes =
{ "com.mycompany.myapp.wxyz" }
}
},
UTExportedTypeDeclarations =
{
{
UTTypeConformsTo =
{
"public.plain-text",
"public.text"
},
UTTypeDescription = "wxyz File",
UTTypeIdentifier = "com.mycompany.myapp.wxyz",
UTTypeTagSpecification =
{
["public.filename-extension"] = "wxyz",
["public.mime-type"] = "myapp/wxyz"
}
}
}
}
}
}



CFBundleDocumentTypes table과 그 안의 파라미터들을 한번 살펴 보죠.

    CFBundleTypeIconFiles — document icon으로 사용 될 이미지 파일의 이름을 담고 있는 string의 배열. 이 이미지들의 사이즈와 specification에 대해 좀 더 자세히 알고 싶으시면 여기에서 Apple의 가이드라인을 확인하세요.
    CFBundleTypeName — document type 에 대한 "abstract" 이름, type을 표현함. key 가 있어야 함. 예를 들어 위 예제에서는 “wxyz File“ 을 사용하고 있음
    CFBundleTypeRole — 그 type에 대한 앱의 role 을 설정함. 이 값은 Editor, Viewer, Shell 이 되거나 아무것도 안 올 수 있음. 이 key 는 필요하고 우리는 이것을 Viewer 로 설정할 것임.
    LSHandlerRank — 이 타입을 사용하는 파일들이 어떤 성격의 것들인지 알리기 위해 사용합니다. 사용할 수 있는 값으로는 Owner, Alternate, None 등이 있습니다.  예제에서는 Owner 를 사용합니다. 이 앱이 이 타입의 파일을 생성하는 앱이라는 것을 나타내기 위해서요.

    LSItemContentTypes — an array of strings; each one must contain a UTI (Uniform Type Identifier) defining a supported file type. For a detailed description of UTI concepts, please refer to Apple’s guidelines here. In this case, we specify the app’s bundle identifier plus our desired file extension of .wxyz.
    LSItemContentTypes - string 으로 구성된 배열. 각각은 지원되는 파일 타입을 정의하는 UTI (Uniform Type Identifier) 를 반드시 가지고 있어야 함. UTI 에 대한 좀 더 자세한 내용은 애플의 가이드라인을 참조하세요. 우리는 앱의 bundle identifier 와 우리가 사용할 팡리 확장자(.wxyz)를 지정했습니다.



이제 UTExportedTypeDeclarations table과 파라미터들을 살펴 보겠습니다.(complete reference can be found here):


    UTTypeConformsTo - string 들로 구성된 배열. 각 string 은 해당 타입을 confirm 하는 UTI 를 정의하고 있습니다. 이 것들은 custom 파일 포맷이 속한 parent 카테고리들을 표현합니다. 예를 들어 JPEG 파일은 public.image 와 public.data 타입임을 confirm 합니다. 좀 더 자세한 내용은 애플의 문서를 참조하세요.
    UTTypeDescription — An “abstract” user-readable description of this type. 유저가 읽을 수 있는 이 타입의 description. (abstract)
    UTTypeIdentifier — 이 타입과 관련된 UTI. 여기서는 앱의 번들 identifier 와 우리가 사용할 파일 확장자를 가리킴
    UTTypeTagSpecification — 한개 이상의 equivalent type idenrifier 를 정의한 dictionary. key-value 한쌍의 형식으로 이뤄졌음. 이 타입의 파일 이름 확장자, MIME 타입, OSType 코드 그리고 pasteboard type 등이 있음.

   


2. Build Handler Functions

다음으로 여러분 앱에 handler 기능을 build 해야 합니다. 아래 코드들을 잘 살펴 보세요.


-----------------------------------------------------------------------------------------
-- main.lua
-----------------------------------------------------------------------------------------
local launchArgs = ...
local launchFile = "";
local fileName;
 
 
local getDocPath = function(fName)
local f = fName;
local docStart, docEnd = f:find("Documents");
f = f:sub(docEnd+1);
return f;
end
 
 
if (launchArgs and launchArgs.url) then
launchFile = launchArgs.url;
if (string.sub(launchFile, 1, 7) == "file://") then -- handle custom extension
launchFile = getDocPath(launchFile);
else
-- handle URL Scheme / do something
end
end
 
 
-- text for testing
display.newText("File passed to system.DocumentsDirectory:", 10, 50, native.systemFont, 12);
fileName = display.newText(launchFile, 10, 65, native.systemFont, 12);
 
 
local onSystemEvent = function(event)
local eventType = event.type;
local eventURL = event.url;
if (eventType == "applicationOpen") then -- app resumes from background
if (eventURL) then
launchFile = getDocPath(eventURL);
fileName.text = launchFile;
end
end
end
Runtime:addEventListener("system", onSystemEvent);


위 예제는 아주 쌈박한 예제는 아닙니다. 단지 Documents 디렉토리안의 imported file 에 대한 path만 display 합니다. 좀 자세히 살펴 볼까요?

    First, we use … which is Lua syntax that returns a table with arguments passed to your app by the system.
    Following that is a utility function that extracts and returns the path to your file in the Documents directory.
    Next, we check for launch arguments. If the .url passed to the app begins with “file://“, we know that we’re trying to handle a custom extension. The .url part can also be used to handle URL schemes, but that’s explained in a different tutorial here.
    Two display objects are then created for the purpose of showing the path to the file that was imported into the app.
    Since our app can suspend to the background, we need a way to detect when the app resumes with an attachment. This is done by adding an event listener for the system event. The event type we look for is applicationOpen.


위의 코드들을 implement 한 후 모든 setting들을 꼼꼼히 검토해 보세요. 그리고 나서 wxyz 확장자를 갖는 첨부파일을 넣고 여러분에게 이메일을 보내 보세요.


이제 여러분의 device 에서 Mail app 을 여시고 방금 보낸 그 메일을 선택하세요. 그리고 첨부파일을 “tap-and-hold” 하세요. 이제 “Open in” dialog pop-up에서 여러분의 앱 이름을 보실 수 있을 겁니다. 그 리스트에서 여러분 앱을 선택하시면 여러분 앱이 Document directory 안의 파일과 함께 다음 프로세스를 진행하기 위해 열릴겁니다.



What’s Next?


그 다음 작업으로 저는 다른 사람들로부터 받은 데이터(첨부)를 import 할 수 있도록 하고 그 데이터를 parse 한 다음에 SQLite 데이터 베이스에 저장하는 기능을 구현하고 있습니다. 그 기능이 완성되면 사람들이 첨부로 보낸 데이터를 쉽게 여러분 앱에 import 할 수 있도록 하는 기능을 제공하게 될 겁니다. 그 기능이 완료 될 때까지 이 튜토리얼이 코로나에서 이메일 첨부파일에 접근하고 그것을 share 하는 개념을 이해하는데 도움이 되기를 바랍니다.


반응형


반응형

지난번 튜토리얼이 길어서 11번에 나눠서 다 올렸습니다.

그거 올리느라고 다른 글들이 많이 밀렸네요.

공부할 것이 많아서 좋습니다. :)

오늘 다룰 글은 1월 29일에 Coronasdk 홈페이지에 올라온 튜토리얼입니다.

모양 맞추기 게임을 아주 쉽게 잘 가르쳐 주고 있네요.


Posted on . Written by



오늘의 guest tutorial 은 Greg Pugh 의 무료 강좌 입니다. 그는 MC Strategies 에서 플래시 및 앱 디벨로퍼로 일하고 있고 펜실베니아 Nanticoke 에 있는 independent studio인 GP Animations의 owner 이기도 합니다.  Greg이 개발한 앱들은 그의 블로그에서보실 수 있습니다. 또한 그는 RayWenderlich.comKwiksher.com에 Corona tutorial을 연재하고 있습니다. 또한 그는 최근 Corona Ambassador 로서 Corona SDK 의 세계로 새로 진입하는 개발자들을 위한 iBook 을 집필하고 있습니다.


Preface


애들이 있다 보니까 저의 iPad는 아이들이 주로 독서하거나 게임할 때 그리고 앱들을 가지고 노는데 사용합니다. 바로 지금 제 딸이 두 책에 끌리는가 봅니다. 제가 쓴 책 (http://www.ColinTurtle.com)과  약간 괴상한 "Game For Cats" 라는 책에요. 조만간 제 딸은 자기 외모와 색깔들과 알파벳 뭐 이런것들을 배울 나이가 되겠죠. 그리고 다른 앱들을 가지고 놀게 될 겁니다. 오늘 저는 아이들이 모양을 드래그 and 드롭해서 같은 모양의 틀에 맞추는 아주 기초적인 아이들용 앱을 만드는 방법으로 보여드리려고 합니다.

우선 실제 이 앱이 동작하는 것을 이 비디오를 통해서 먼저 보셨으면 합니다.

보시다시피 두개의 모양을 동시에 drag 할 수 있게 만들 겁니다. 왜냐하면 아이들은 smart device들을 가지고 놀 때 두손을 모두 사용하려고 하는 경향이 있거든요. 이 기능을 구현하기 위해 David McCuskey 가 만든 dmc_multitouch 를 사용할 겁니다. (관련 모듈이 들어 있는 파일들은 프로젝트에 포함돼 있습니다. 그러니 따로 다운 받으실 필요는 없구요. 그냥 이 프로젝트만 다운 받으시면 됩니다.) 이 글을 쓰는 지금 시점에는 코로나 시뮬레이터에서 멀티터치를 지원하지 않고 있습니다. 테스트를 하시려면 publish 를 해서 디바이스에 인스톨 하신 다음에 멀티터치를 테스트 하셔야 할 겁니다.



프로젝트 파일은 여기에서 다운 받으실 수 있습니다. 일단 SnapShapes 폴더의 압축을 풀면 그 안에 두개의 폴더가 있을 겁니다. SnapShapes_FINAL 에는 완성된 main.lua file 가 있습니다. 그리고 SnapShapes_START 폴더에는 단순한 artwork 와 supporting files 들만 있습니다.


Project Code


시작하시려면 우선 여러분이 사용하시는 텍스트 에디터를 여시고 create a new file 을 선택하신 다음에 그 파일 이름을 main.lua 로 하고 SnapShapes_START 폴더 안에 저장하세요. 첫번째로 할 작업은 dmc_multitouch.lua 파일을 import 하는 겁니다. 아래 코드를 카피해서 main.lua 에 붙여넣기 하세요.


-- Require dmc_multitouch
MultiTouch = require("dmc_multitouch");


다음으로는 status bar 를 감출겁니다. 아이들은 상태바에 나오는 시간이나 여러분 phone 에 어떤 서비스들이 있는지 등은 관심이 없거든요.


-- Hide status bar
display.setStatusBar(display.HiddenStatusBar);


이제 제공된 이미지들을 여러분의 images 폴더에 넣으세요. 그리고 아래 코드를 복사해서 main.lua  파일에 붙여 넣고 저장하세요.


-- Background image
local background = display.newImageRect("images/background.png", 640, 960);
background.x = display.contentCenterX;
background.y = display.contentCenterY;
-- Square outline
local sqLine = display.newImageRect("images/sqLine.png", 228, 228);
sqLine.x = 473;
sqLine.y = 181;
-- Square
local square = display.newImageRect("images/square.png", 188, 188);
square.x = 144;
square.y = 778;
-- Circle outline
local circLine = display.newImageRect("images/circLine.png", 245, 245);
circLine.x = 181;
circLine.y = 180;
-- circle positioning
local circle = display.newImageRect("images/circle.png", 200, 200);
circle.x = 473;
circle.y = 778;
-- myText positioning
local myText = display.newImageRect("images/myText.png", 508, 78);
myText.x = 311;
myText.y = 475;


Corona SDK Simulator 를 여신 후 File > Open 을 선택하세요. 그리고 SnapShapes_START  폴더에 있는 main.lua 를 여세요. phone 종류는 iPhone으로 선택하시기 바랍니다. 이제 여러분 시뮬레이터는 아래 이미지처럼 보일 겁니다.




이제 여러분은 화면에 display 해야할 모든 것을 다 display 했습니다. 이제 기능들을 추가할 차례입니다.
우선 파란 원에 적용될 코드를 만들겁니다. 그 다음에는 단지 변수 이름 등만 수정해서 빨란 사각형에 적용하면 됩니다. 이 때 이 원에 대해 멀티터치가 가능하도록 하고 initial positioning variables 들을 0으로 세팅하세요. 아래 코드를 복사해서 여러분이 만든 main.lua 파일에 붙여 넣으세요.


-- Circle
MultiTouch.activate(circle, "move", "single");
-- Set initial variables to 0
local circlePosX = 0;
local circlePosY = 0;


원을 드래그 하면 이 원은 touch event의 target 이 될 겁니다. moved 와 ended 같은 phases 들을 이용해서 기능을 구현할 수 있게 되는 거죠. 이 원이 위에 있는 동그란 점선 중앙에 50픽셀 이내로 접근했다면 그 원은 그 동그란 점선 안으로 snap 될 겁니다. 그 외의 경우에는 원은 그냥 드래그한 장소에 있게 됩니다.
아래 코드를 추가해 주세요.


-- User drag interaction on blue circle
local function circleDrag (event)
local t = event.target
-- If user touches & drags circle, follow the user's touch
if event.phase == "moved" then
   circlePosX = circle.x - circLine.x; 
   circlePosY = circle.y - circLine.y;
   if (circlePosX < 0) then circlePosX = circlePosX * -1; end
   if (circlePosY < 0) then circlePosY = circlePosY * -1; end
   -- If user drags circle within 50 pixels of center of outline, snap into middle
   if (circlePosX <= 50) and (circlePosY <= 50) then
      circle.x = circLine.x;
      circle.y = circLine.y;
   end


이 원이 동그란 점선 안으로 snap 됐고 유저가 드래그 하는 것을 멈췄다면 그 원은 동그란 점선 안에 제대로 안착돼 있을 갑니다. 


아래 코드를 추가하세요.


-- When the stops dragging circle within 50 pixels of center of outline, snap into middle, and...
elseif event.phase == "ended" then
   if (circlePosX <= 50) and (circlePosY <= 50) then
      circle.x = circLine.x;
      circle.y = circLine.y;
     -- ...lock circle into place where it cannot be moved.
     MultiTouch.deactivate(circle);
   end
end
return true;
end


마지막으로 circleDrag 함수에 이벤트 리스너를 추가하세요. 그리고 저장하시구요.


circle:addEventListener(MultiTouch.MULTITOUCH_EVENT, circleDrag);


시뮬레이터를 refresh 하면 화면에서 파란 원을 드래그 하실 수 있으실 겁니다. 그리고 동그란 점선 중앙에서 50 픽셀 이내로 접근하면 동그란 점선 중앙에 원이 곧바로 snap 될 겁니다. 더이상 드래그를 하지 않으면 그 원은 동그란 점선 안에 제대로 위치해 있을 겁니다.

이제 같은 코드를 빨간 사각형에 적용해 보죠.
그냥 변수 이름들만 바꾸시면 됩니다.


-- Same actions for the square as the circle
MultiTouch.activate(square, "move", "single");
local squarePosX = 0;
local squarePosY = 0;
local function squareDrag (event)
local t = event.target
if event.phase == "moved" then
   squarePosX = square.x - sqLine.x;
   squarePosY = square.y - sqLine.y;
   if (squarePosX < 0) then squarePosX = squarePosX * -1; end
   if (squarePosY < 0) then squarePosY = squarePosY * -1; end
   if (squarePosX <= 50) and (squarePosY <= 50) then
      square.x = sqLine.x;
      square.y = sqLine.y;
   end
elseif event.phase == "ended" then
   if (squarePosX <= 50) and (squarePosY <= 50) then
      square.x = sqLine.x;
      square.y = sqLine.y;
      -- If you'd like to be able to move the square again, comment out the line below
      MultiTouch.deactivate(square);
   end
end
return true;
end
square:addEventListener(MultiTouch.MULTITOUCH_EVENT, squareDrag);


사각형이 점선안에 제대로 자리 잡은 후에도 계속 드래그할 수 있도록 하시려면 코드의 deactivate 라인을 주석처리해 주세요.
이제 유저가 두 모양을 모두 제자리에 놓았다면 유저에게 잘 했다고 축하 해 주시면 되겠네요.

여기까지 입니다. 아이들이 가지고 놀 수 있는 앱을 아주 쉽고 빠르고 만들었습니다.




The Next Step?


위에 구현된 기본적은 기능을 확장해서 좀 더 큰 아이나 어른들을 위한 앱으로 발전 시킬 수도 있겠죠.



    이 코드를 활용해서 아이들이 인형에게 옷을 입히는 앱을 만들 수도 있겠죠?
    여러 조각의 퍼즐을 맞추는 게임을 만들수도 있을 겁니다. 위에 있는 함수들을 여러 조각들에 적용해서 구현하면 될 겁니다.
    adventure game 에 puzzle challenge 를 만들 수도 있을 겁니다.



모바일 혁명이 계속된다면 교육용 앱도 지금 보다 많이 대중성을 가질 겁니다. multitouch 모양 맞추기 기능의 기본을 이해하고 있으면 집에서만이 아니라 학교에서도 사용할 수 있는 아주 좋은 앱을 확장해서 개발할 수도 있겠죠?

반응형

Pinch Zoom Rotate 구현하기 - 11/11 -

2013. 2. 14. 23:41 | Posted by 솔웅


반응형
Posted on . Written by



And Finally…


지금까지의 코드는 single display object 에 대한 내용이었습니다. 실제로는 여러 object 에 이것이 적용 될 수도 있을 겁니다. object 들의 그룹에 pinch-zoome 이 일어날 수도 있겠죠. 더 중요한 것은 이 기능을 re-use 할 수 있도록 하려면 어떻게 해야 할까요?

:touch() 함수를 re-use 하려면 이 기능이 어떤 display object(이미지이든 그룹이든)에도 쉽게 적용 될 수 있도록 해야겠죠. 사용하고 있는 reference 들을 간단하게 change 할 수도 있어야 되겠구요. 이 기느응ㄹ 구현하기 위해 여러 object 들을 포함한 display group 을 하나 만듭시다. 그리고 touch listener 를 붙이고 그 그룹에 function 도 적용해 보죠.


sample11.lua
local group = display.newGroup() -- create display group to listen for new touches
-- populate display group with objects
local rect1 = display.newRect( group, 200, 200, 200, 100 )
rect1:setFillColor( 0, 0, 255 )
 
local rect2 = display.newRect( group, 300, 300, 200, 100 )
rect2:setFillColor( 0, 255 ,0 )
 
local rect3 = display.newRect( group, 100, 400, 200, 100 )
rect3:setFillColor( 255, 0, 0 )
 
group.dots = {} -- keep a list of the tracking dots
 
-- advanced multi-touch event listener
function touch(self, e)
local target = e.target -- get the object which received the touch event
local rect = self -- get reference to self object
if (e.phase == "began") then -- handle began phase of the touch event life cycle...
-- ...
 
end
 
group.touch = touch -- attach pinch zoom touch listener
group:addEventListener("touch") -- listen for touches starting on the touch object
 



In Summary


이제 모두 완료 됐습니다. 이제 touch listener module 은 어떤 display object 나 그룹에도 적용될 수 있습니다. 즉 쉽게 multitouch pinch-zoom-rotation 을 구현할 수 있게 된거죠.


아직 전체 project 를 다운받지 못하신 분들은 여기에서 다운받아서 실행해 보세요.


질문이나 의견이 있으시면 언제든지 댓글에 달아 주세요.

반응형

Pinch Zoom Rotate 구현하기 - 10/11 -

2013. 2. 14. 23:23 | Posted by 솔웅


반응형
Posted on . Written by



Pinch Centre Translation


이전 글에서 만들었던 sample9.lua 코드를 실행해 보세요. tracking dot 들이 rotate,scale, move 할 때 display object 가 약간씩 이동하는 걸 보실 수 있을 겁니다. 이렇게 하면 정확하게 그 midpoint 를 얻을 수가 없는 상황이거든요.


이 문제를 해결하려면 translation, scaling, rotation 을 적용하는데 단순히 basic 만 사용하면 안됩니다. 여기에 display object 의 center point location 도 같이 적용해야 합니다.

    Scaling 은 midpoint 와 “rect” centre 사이의 거리도 참고해서 적용되어야 합니다.
    Rotation 은 “rect” centre 에 적용 되야 합니다. tracking dot midpoint 주위를 회전해야 합니다.
    translation 은 이미 적용됐기 때문에 따로 고려를 하지 않아도 됩니다.


이제 어떤 standard library 수학 함수를 우리가 사용해야 할까요? point 를 회전시켜야 하는데 그 회전은 다른 포인트 주변을 돌아야 합니다. 그러니까 우리는 math helper 들을 사용할 필요가 있습니다. 또한 moved phase 에도 작업을 해 줘야 합니다.



sample10.lua


-- rotates a point around the (0,0) point by degrees
-- returns new point object
function rotatePoint( point, degrees )
local x, y = point.x, point.y
local theta = math.rad( degrees )
local pt = {
x = x * math.cos(theta) - y * math.sin(theta),
y = x * math.sin(theta) + y * math.cos(theta)
}
return pt
end
 
-- rotates point around the centre by degrees
-- rounds the returned coordinates using math.round() if round == true
-- returns new coordinates object
function rotateAboutPoint( point, centre, degrees, round )
 
local pt = { x=point.x - centre.x, y=point.y - centre.y }
pt = rotatePoint( pt, degrees )
pt.x, pt.y = pt.x + centre.x, pt.y + centre.y
if (round) then
pt.x = math.round(pt.x)
pt.y = math.round(pt.y)
end
return pt
end
 
 
-- changes to 'moved' phase
 
-- ...
-- apply rotation and scaling to rect
rect.rotation = rect.rotation + rotate
rect.xScale, rect.yScale = rect.xScale * scale, rect.yScale * scale
end
local pt = {} -- declare working point for the rect location
-- translation relative to centre point move
pt.x = rect.x + (centre.x - rect.prevCentre.x)
pt.y = rect.y + (centre.y - rect.prevCentre.y)
-- scale around the average centre of the pinch (centre of tracking dots, not rect centre)
pt.x = centre.x + ((pt.x - centre.x) * scale)
pt.y = centre.y + ((pt.y - centre.y) * scale)
-- rotate the rect centre around the pinch centre (same rotation as the rect is rotated!)
pt = rotateAboutPoint( pt, centre, rotate, false )
-- apply pinch translation, scaling and rotation to the rect centre
rect.x, rect.y = pt.x, pt.y
-- store the centre of all touch points
rect.prevCentre = centre
 
else -- "ended" and "cancelled" phases

main.lua

sample10.lua


moved phase 에 여러 기능들이 추가 됐습니다.


    pt 는 display 객체의 포지션 에 대한 working space 를 사용하기 위해 정의됩니다.
    midpoint translation은 working object 에 적용됩니다.
    midpoint 와 display object 중심사이의 거리가 scale 됩니다.
    display object 의 중심은 midpoint 주위를 회전합니다.


이제 코드를 실행해 보세요. 여러분 손가락이 어디에 있던 touch (tracking dot) 은 display object 에서 시작되고 touch point 에 따라 pinch-zoom 이 일어날 겁니다.


이 효과는 손가락 두개를 사용할 때 제대로 나타날 겁니다. 이전에는 tracking point들이 display object 에서의 그들의 시작지점의 영향을 받았었는데 이제는 약간 달라졌거든요. 결과는 거의 같긴 하지만 터치 포인트들간의 평균이 조금 더 정확해 졌습니다.



반응형

Pinch Zoom Rotate 구현하기 - 9/11 -

2013. 2. 14. 21:35 | Posted by 솔웅


반응형
Posted on . Written by


Rotation


이제 display object를 회전시키는 기능을 적용하겠습니다. 기본적인 로직은 midpoint 주위로 각각의 tracking dot 이 얼마나 ratate 되었느냐를 판단하는것부터 시작해야 할 겁니다. 그 평균을 구해서 object 의 이전 .rotation 값에 그 차이를 더해 주어야겠죠. 이것을 하려면 이것을 계산하는 함수들을 몇개 더 추가 해야 겠네요.

왜냐하면 angle 을 계산하려면 특정 수학 공식을 대입해야 하거든요. 그리고 원 둘레의 두 포인트 사이의 가장 작은 angle 을 구하는 작업도 필요합니다. 이것은 tracking dot 이 회전한 것에 대한 angle 을 사용할 때 아주 중요하게 사용됩니다. 잘못하면 작은 각도 말고 큰 각도를 사용할 수 있거든요. 90도와 260도가 있다면 우리가 필요한 것은 90도 입니다.


sample9.lua

-- returns the degrees between (0,0) and pt (note: 0 degrees is 'east')
function angleOfPoint( pt )
local x, y = pt.x, pt.y
local radian = math.atan2(y,x)
local angle = radian*180/math.pi
if angle < 0 then angle = 360 + angle end
return angle
end
 
-- returns the degrees between two points (note: 0 degrees is 'east')
function angleBetweenPoints( a, b )
local x, y = b.x - a.x, b.y - a.y
return angleOfPoint( { x=x, y=y } )
end
 
-- returns the smallest angle between the two angles
-- ie: the difference between the two angles via the shortest distance
function smallestAngleDiff( target, source )
local a = target - source
if (a > 180) then
a = a - 360
elseif (a < -180) then
a = a + 360
end
return a
end

calcAvgScaling 함수에서 했던 것처럼 모든 tracking dot들이 midpoint 주위를 회전한 것에 대한 평균 값을 구하기 위해 calcAvgRotation 함수를 사용하고 그 안에서 위의 함수들을 사용할 겁니다. 그리고 tracking dot 각도들 사이의 차이들도 update 해야 합니다. 그리고 직전 각도들도 마찬가지구요. 다행히 midpoint 로부터의 거리와 관련해서는 이미 구현해 놨었습니다. 약간의 코드만 추가하면 될 것 같습니다.

-- calculates rotation amount based on the average change in tracking point rotation
local function calcAverageRotation( points )
local total = 0
for i=1, #points do
local point = points[i]
total = total + smallestAngleDiff( point.angle, point.prevAngle )
end
return total / #points
end
 
-- calculate each tracking dot's distance and angle from the midpoint
local function updateTracking( centre, points )
for i=1, #points do
local point = points[i]
point.prevAngle = point.angle
point.prevDistance = point.distance
point.angle = angleBetweenPoints( centre, point )
point.distance = lengthOf( centre, point )
end
end

이제 이 약간의 코드를 추가함으로서 rect:touch() 함수는 이미 began 과 ended  phases 에 해당 값들을 업데이트 하게 되었습니다. 이제 우리가 해야 할 일은 moved phase 안의 “rect” display object에 rotation 을 적용하는 것 뿐입니다. 이 기능은 tracking dot 이 하나 이상일 때에 적용되는 기능입니다. 이제 tracking dot들의 midpoint 주위의 회전값 평균을 계산하고 이 object 에 적용하기 위해 위의 함수들을 call 하기만 하면 됩니다.

-- if there is more than one tracking dot, calculate the rotation and scaling
if (#rect.dots > 1) then
-- calculate the average rotation of the tracking dots
rotate = calcAverageRotation( rect.dots )
-- calculate the average scaling of the tracking dots
scale = calcAverageScaling( rect.dots )
-- apply rotation to rect
rect.rotation = rect.rotation + rotate
-- apply scaling to rect
rect.xScale, rect.yScale = rect.xScale * scale, rect.yScale * scale
end

main.lua

sample9.lua


반응형

Pinch Zoom Rotate 구현하기 - 8/11 -

2013. 2. 14. 08:14 | Posted by 솔웅


반응형
Posted on . Written by


Scaling

display object를 멀티 터치로 컨트롤 하면서 우리가 원하는 transformation을 적용하려면 모든 tracking dot들의 평균 값들이 필요합니다. 그리고 그 값을 display object의 midpoint (그 평균 위치) 에 이미지를 위치시켜야 합니다.


scaling 과 관련해서는 수학적인 계산이 필요합니다.

    midpoint 와 tracking dot들 간의 거리의 합 구하기
    거리의 합계를 dot 들의 갯수로 나눈 평균 거리 구하기
    tracking dot 들의 이전 위치에 대한 같은 평균 거리 구하기
    이전과 현재의 평균 거리 사이의 차이 구하기
    display object 의 .xScale 과 .yScale에  multiplication 으로 그 차이를 적용하기


이건 여러 tracking dot들이 움직일 때 display object 의 평균 transition 을 어떻게 적용할 지를 정해 주는 겁니다. 이 scaling 값을 얻도록 하는 기본 라이브러리 함수들을 만들 겁니다. 아래 함수는 스크린의 두개의 지점사이의 거리를 계산하는 함수입니다. 아주 전형적인 삼각함수죠.

sample8.lua

-- returns the distance between points a and b
function lengthOf( a, b )
local width, height = b.x-a.x, b.y-a.y
return (width*width + height*height)^0.5
end


tracking dot들의 midpoint 를 얻기 위해 sample 5 에서 다뤘던  calcAvgCentre() 함수를 사용할 겁니다.  midpoint 와 tracking dot 들 사이의 평균 거리를 구해서 저장하기 위해 다음과 같은 함수들을 사용할 겁니다. 첫번째는 각 dot 들의 현재 distance 를 get 해서 그 값을 tracking dot 에 저장할 겁니다. 그리고 이전 distance 도 물론 저장하구요. 두번째 함수에서는 이전과 현재 거리 set 들 사이의 차이를 계산하는 함수를 사용할 겁니다.


-- calculate each tracking dot's distance from the midpoint
local function updateTracking( centre, points )
for i=1, #points do
local point = points[i]
point.prevDistance = point.distance
point.distance = lengthOf( centre, point )
end
end
 
-- calculates scaling amount based on the average change in tracking point distances
local function calcAverageScaling( points )
local total = 0
for i=1, #points do
local point = points[i]
total = total + point.distance / point.prevDistance
end
return total / #points
end

이 함수를 사용하는 방법은 간단합니다. rect:touch()의 began 과 ended phase 에서 call 하시면 됩니다. 그러면 이 함수들이 적당한 값으로 해당 tracking dot 들의 값을 update 할 겁니다. 아래는 began 과 ended phase 에서 call 하는 부분입니다.


-- "began"
-- pre-store the tracking dot scale and rotation values
updateTracking( rect.prevCentre, rect.dots )
return true -- we handled the began phase
 
-- "ended"
-- store the new centre of all touch points
rect.prevCentre = calcAvgCentre( rect.dots )
-- refresh tracking dot scale and rotation values
updateTracking( rect.prevCentre, rect.dots )

moved phase 는 조금 더 복잡합니다. 왜냐하면 실제 이 부분에서 대부분의 일이 진행 될 거거든요. 다행히 여기서 우리가 해야 될 일은 tracking dot 을 update 하는 똑 같은 일에다가 tracking dot 이 한개 이상일 경우 scaling 을 적용하는 일 뿐입니다.


if (e.phase == "moved") then
print( e.phase, e.x, e.y )
local centre, scale, rotate = {}, 1, 0 -- declare working variables
centre = calcAvgCentre( rect.dots ) -- calculate the average centre position of all touch points
updateTracking( rect.prevCentre, rect.dots ) -- refresh tracking dot scale and rotation values
-- if there is more than one tracking dot, calculate the rotation and scaling
if (#rect.dots > 1) then
scale = calcAverageScaling( rect.dots ) -- calculate the average scaling of the tracking dots
rect.xScale, rect.yScale = rect.xScale * scale, rect.yScale * scale -- apply scaling to rect
end
rect.x = rect.x + (centre.x - rect.prevCentre.x) -- update the X position of rect
rect.y = rect.y + (centre.y - rect.prevCentre.y) -- update the Y position of rect
rect.prevCentre = centre -- store the centre of all touch points
else -- "ended" and "cancelled" phases

main.lua

sample8.lua


위에서 moved phase 에서 우리가 바꾼 부분은 아래와 같습니다.

    앞으로 일어날 transformation 값들과 같이 사용될 변수들 정의
    tracking dot 들의 저장된 distance 값을 refresh 하기 위해 updateTracking 을 call 하기
    tracking scaling 에서 변한 값의 평균을 구하기 위해 distance 값들 사용하기
    display object "rect" 에 해당 scaling 적용하기

The display object now translates (moves) and scales (zooms) along with our tracking dots (touch points).
이제 이 display object 는 tracking dot 들 (touch point 들) 의 움직임에 따라 translates (moves) 과  scales (zooms) 를 하게 됐습니다.

반응형

Pinch Zoom Rotate 구현하기 - 7/11 -

2013. 2. 12. 10:32 | Posted by 솔웅


반응형
Posted on . Written by



이전 글 까지만 해도 이 코드는 유용합니다. 하지만 좀 더 다듬어야 하죠. 작은 파란 사각형을 하나 이상의 손가락으로 움직일 수 있습니다. multitouch input device의 장점은 virtual 상황에서 real world 의 느낌을 주는데 있습니다. 이 코드에 rotation 과 scaling 효과를 주면 훨씬 더 실감나게 움직이곘죠.



Relative Motion


이 작업을 하기 전에 이전 코드(6번)를 실행 시킨 후 한 손가락을 이용했을 때 사각형이 어떻게 움직이는지 한번 보세요. touch point 로 사각형의 중심이 이동한 다음에 움직이죠. 이렇게 사각형의 중심이 이동하지 않고 그냥 touch point 와 그냥 relative 하게 움직이도록 할 겁니다. 이 작업을 하기 위해 moved와 ended phases 에 코드를 추가 할 겁니다.



sample7.lua

function rect:touch(e)
local target = e.target -- get the object which received the touch event
-- handle began phase of the touch event life cycle...
if (e.phase == "began") then
print( e.phase, e.x, e.y )
local dot = newTrackDot(e) -- create a tracking dot
rect.dots[ #rect.dots+1 ] = dot -- add the new dot to the list
-- pre-store the centre position of all touch points
rect.prevCentre = calcAvgCentre( rect.dots )
return true -- we handled the began phase
elseif (e.parent == rect) then
if (e.phase == "moved") then
print( e.phase, e.x, e.y )
-- calculate the centre position of all touch points
local centre = calcAvgCentre( rect.dots )
rect.x = rect.x + (centre.x - rect.prevCentre.x) -- update the X position of rect
rect.y = rect.y + (centre.y - rect.prevCentre.y) -- update the Y position of rect
rect.prevCentre = centre -- store the centre of all touch points
else -- ‘ended’ and ‘cancelled’ phases
print( e.phase, e.x, e.y )
if (isDevice or e.numTaps == 2) then -- remove the tracking dot from the list
local index = table.indexOf( rect.dots, e.target ) -- get index of dot to be removed
table.remove( rect.dots, index ) -- remove dot from list
e.target:removeSelf() -- remove tracking dot from the screen
-- store the new centre of all touch points
rect.prevCentre = calcAvgCentre( rect.dots )
end
end
return true
end
return false -- if the target is not responsible for this touch event return false
end

main.lua

sample7.lua


여기서 수정한 부분은 아래와 같습니다.

    모든 touch 들의 center 를 계산하고 began phase 안에서 참고하기 위해 그 값을 저장한다.
    moved phase 에서 사각형의 x,y 값에 이전 touch 와 현재의 touch center 사이의 차이점을 추가적용한다.
    ended phase 에 터치들의 저장된 터치들의 중심값을 업데이트한다. 그래서 손가락을 떼더라도 다음 moved phase 에서 사각형이 필요없이 이동하지 않도록 한다.

이제 사용자는 사각형 위에 여러 손가락을 얹어도 이상하게 움직이지 않을 겁니다. 그 손가락들을 바꾸고 움직이고 해도 어색하지 않게 움직입니다. 이제 사각형이 손가락 움직임에 따라 회전하면 더 자연스럽겠네요.




반응형

Pinch Zoom Rotate 구현하기 - 6/11 -

2013. 2. 12. 09:50 | Posted by 솔웅


반응형
Posted on . Written by


multitouch display object 들을 위한 시뮬레이터 디버거를 사용하실 수 있습니다. tracking dot 중의 하나에서 touch 를 release 했을 때 이 dot 이 사라지지 않는걸 보셨을 겁니다. 이게 디버깅하기 아주 좋은 조건이거든요. 왜냐하면 touch 를 뗐어도 여러 touch point 가 있는 것처럼 데이터가 저장돼 있을 거거든요. 디버깅에는 좋지만 실제 앱을 이렇게 만드는 건 별로 유용하지는 않을 거에요. 계속 하얀 원이 쌓일 테니까요.


이 문제를 해결하시려면 rect:touch() function 의 ended phase 에서 해당 tracking dot들을 없애주면 됩니다. 일단 device 에서 running 하는지 여부를 코드의 시작 부분에서 변수에 저장해 두는 것에서부터 시작해야 되겠네요.


-- which environment are we running on?
local isDevice = (system.getInfo("environment") == "device")


The isDevice variable will be true if the code is running on a real, physical device and it can be used to automatically remove the tracking dot when the user lifts their finger.
isDevice 변수는 이 앱이 실제 디바이스에서 구동될 경우 true 가 될 겁니다. 그러면 이 값이 true 일 경우에만 손가락이 떼어질 때 해당 tracking dot 을 없애버리면 되겠죠.





sample6.lua


if (e.phase == "moved") then
print( e.phase, e.x, e.y )
 
else -- ‘ended’ and ‘cancelled’ phases
 
print( e.phase, e.x, e.y )
if ( isDevice or e.numTaps == 2 ) then -- remove the tracking dot from the list
local index = table.indexOf( rect.dots, e.target ) -- get index of dot to be removed
table.remove( rect.dots, index ) -- remove dot from list
e.target:removeSelf() -- remove tracking dot from the screen
end
end
return true
 

isDevice or e.numTaps == 2 가 있죠? 이렇게 함으로서 이미 rect:touch() function를 call 한 tap lisener 를 가지는 tracking dot을 가능하게 합니다. 그러면 시뮬레이터에서는 double tap 으로 tracking dot 을 remove 할 수 있도록 구현할 수 있습니다.

그 tap listener 는 이 코드가 시뮬레이터에서 돌아갈 때에만 동작하도록 해야합니다. 그래서 이 때 isDevice 변수를 다시 사용할 겁니다. 이 tap listener 는 tracking dot들을 생성하는 newTrackDot() 함수 안에 추가 됐습니다.


-- listen for a tap when running in the Simulator
function circle:tap(e)
if (e.numTaps == 2) then
e.parent = rect -- set the parent
rect:touch(e) -- call touch to remove the tracking dot
end
return true
end
-- only attach tap listener in the simulator
if (not isDevice) then
circle:addEventListener("tap")
end

main.lua

sample6.lua


Note that we also:
    우리는 두개의 tap 을 체크해서 double tap 일 경우에만 tracking dot을 remove 할 겁니다.
    그리고 touch 함수안에 했던 대로 .parent property를 세팅합니다.
    이 코드가 시뮬레이터에서 동작할 경우에만 해당 tap listener를 attach 합니다.



반응형