Sencha generate app 명령을 사용하여 Sencha Touch 프로젝트를 생성하면, 그 소스 구조는 기본적으로 MVC구조(model, view, controller) 형태로 되어있습니다. 하지만 이런 구조는 개발 작업에 있어서는 편의를 주지만 실제 실행시에 최적화된 형태는 아닙니다.


따라서 실제 하이드리드 앱의 배포, 테스팅을 위해서는 실행에 최적화되도록 별도의 패키징 작업을 거치게 됩니다. 이 작업에는 여러 개로 나뉘어진 .js .css 파일을 하나의 파일로 합치거나 코드 최소화(minified) 등의 과정이 포함됩니다. 이러한 작업이 바로 sencha app build 명령을 통해 이루어집니다.



Sencha app build <옵션>


Sencha app build 명령에는 세가지의 옵션이 있는데, 하이브리드 앱 개발시에는 testing과 package 중에 하나를 선택합니다.


  • testing - 제품 릴리즈보다는 검수 단계의 빌드 생성을 위한 옵션입니다. 모든 .js, .css 파일이 합쳐지나, 디버깅의 용이를 위해 코드 최소화는 하지 않습니다.
  • package - 웹 서버 없이 로컬 파일 시스템에서 실행가능한 제품 릴리즈용 빌드를 생성합니다.
  • production - 제품 릴리즈용 빌드를 생성하나 웹서버에서 호스팅되기 위한 용도입니다.
  • native - package 빌드를 생성한 후에, native 바이너리 실행 파일까지 생성합니다. 본 블로그의 내용에서는 Phone Gap에서 생성한 프로젝트를 사용하므로 고려되지 않았습니다.



본 내용은 아래 버전을 기준으로 작성되었습니다.


- Sencha Touch : 2.3.1
- Sencha CMD : 4.0.1.45

,

1. 커맨드 프롬프트창에서 PhoneGap 커맨드라인 툴을 이용하여 아래와 같이 입력하여, PhoneGap 프로젝트를 생성합니다. (이클립스에서 열 수 있는 안드로이드 프로젝트가 생성됨)


create C:\work\phonegappj com.sample aproject


사용법 : create <프로젝트가 생성될 폴더> <패키지명> <프로젝트명>




2. 반드시 Sencha Touch 설치 폴더 밑에서(sencha-touch.js... 등의 파일이 있는 위치), sencha cmd 툴을 이용하여 Sencha Touch 프로젝트를 생성합니다.

sencha generate app aproject C:\work\senchapj


사용법 : sencha generate app <프로젝트명> <프로젝트가 생성될 폴더>



-> Error occurred during initialization of VM... 에러가 나는 경우의 해결 방법



3. phonegappj\assets\www 밑에 있는 cordova.js 파일을 senchapj 폴더에 복사합니다.


4. phonegappj\assets\www 폴더를 삭제합니다.

5. senchapj\app.json 파일을 텍스트 편집기로 열고, 다음과 같이 cordova.js를 "js" 항목 밑에 추가합니다.


  "js": [


        {
            "path": "cordova.js"
        },


        ... 

    ],




6, 7번은 릴리즈 빌드와 테스트 빌드 중 한가지를 선택하여 진행합니다. 테스트 빌드의 경우, 좀 더 많은 정보를 갖고 있는 코드를 생성하여, 문제 발생시의 추적에 적합한 결과물을 만들어 줍니다.


[릴리즈 빌드의 경우]


6. senchapj 폴더 밑에서 다음의 명령을 실행합니다.

sencha app build package

7. senchapj\build\package 밑에 프로젝트명으로 된 폴더를 phonegappj/assets 밑에 복사하고, 복사된 폴더명을 www로 바꿉니다.



[테스트 빌드의 경우]


6. senchapj 폴더 밑에서 다음의 명령을 실행합니다.

sencha app build testing

7. senchapj\build\testing 밑에 프로젝트명으로 된 폴더를 phonegappj/assets 밑에 복사하고, 복사된 폴더명을 www로 바꿉니다.




-> sencha app build 명령에 대해 좀 더 자세히




8. phonegappj 폴더 밑에 있는 안드로이드 프로젝트를 Eclipse로 import하여 빌드 및 실행하여, 아래와 같이 Sencha Touch 화면이 나타나는 것을 확인합니다.




9. 이후에 Sencha Touch에 대한 수정을 할 경우, senchapj 폴더에서 수정하고 다시 6, 7, 8 과정을 거쳐서 테스트합니다.



,


안드로이드를 기준으로 Sencha Touch(Sencha Touch 2)와 PhoneGap(2.x)을 함께 사용하는 방법에 대해 정리해보겠습니다.

Sencha Touch와 PhoneGap을 함께 사용할 경우, 아래와 같이 역할 분담이 된다고 볼 수 있습니다.


 - Sencha Touch : UI Framework

 - PhoneGap : 프로젝트 뼈대 생성, Native API 제공



환경 설정


1. 기본 프로그램들 (이미 설치되어있는 것으로 가정)

 - JDK
 - Android SDK (여기서 테스트한 버전은 R22.3)
 - Eclipse

2. 다음의 프로그램들을 받은 후, 설치 프로그램을 실행하거나 압축을 풀어서 설치합니다.

 - PhoneGap : 2.9.0
 - Apache Ant : 1.9.2 (PhoneGap 사용을 위해 필요)
 - Sencha Touch : 2.3.1
 - Sencha CMD : 4.0.1.45 (Sencha Touch 사용을 위해 필요)

 - Ruby : 2.0.0-p353 (Sencha Touch 사용을 위해 필요)



3. <PhoneGap 설치 폴더>\lib\android\bin을 PATH 환경 변수에 추가합니다.

4. <Apache Ant 설치 폴더>\bin을 PATH 환경 변수에 추가합니다.

5. <Sencha CMD 설치 폴더>를 PATH 환경 변수에 추가합니다.

6. <Android SDK 설치 폴더>\tools를 PATH 환경 변수에 추가합니다.

7. <Ruby 설치 폴더>\bin을 PATH 환경 변수에 추가합니다.


8. 환경 변수 JAVA_HOME가 JDK의 bin 폴더 경로가 되도록 설정합니다.


9. Command prompt 상에서 android.bat를 한번 실행해주고, Android SDK Manager가 뜨는 것을 확인합니다.





업데이트 : 2013.12


,

import 시에 다음과 같은 에러가 나는 경우가 있는데,


ImportError: DLL load failed: 지정된 모듈을 찾을 수 없습니다.



해당 모듈이 필요로 하는 DLL(Visual C++ Redistributable package 라던가..)이 시스템에 설치되어 있지 않아서 발생합니다.


Dependency walker 라는 프로그램으로 해당 모듈의 .pyd 파일을 열어보면 필요로하는 DLL들을 확인할 수 있습니다.


Dependency walker 사이트 : http://www.dependencywalker.com/


,

[Python] pyBox2D 사용 예제 - 5. pyGame + Box2D 기본 예제 (2)


pyGame에 pyBox2D를 결합한 두번째 예제 코드입니다.


import pygame
from pygame.locals import *
from Box2D import *
from Box2D.b2 import *

SCREEN_WD = 640
SCREEN_HT = 480
TARGET_FPS = 60
PPM = 20.0

screen = pygame.display.set_mode((SCREEN_WD, SCREEN_HT), 0, 32)
pygame.display.set_caption("PyBox2D_Example")
clock = pygame.time.Clock()

world = b2World(gravity = (0, -10), doSleep = True)

ground1BodyDef = b2BodyDef()
ground1BodyDef.position.Set(0, 0)
ground1Body = world.CreateBody(ground1BodyDef)
ground1Shape = b2PolygonShape()
ground1Shape.SetAsBox(50, 1)
ground1Body.CreateFixture(shape = ground1Shape)

box1BodyDef = b2BodyDef()
box1BodyDef.type = b2_dynamicBody
box1BodyDef.position.Set(30, 45)
box1BodyDef.angle = 15
box1Body = world.CreateBody(box1BodyDef)
box1Shape = b2PolygonShape()
box1Shape.SetAsBox(2, 1)
box1FixtureDef = b2FixtureDef()
box1FixtureDef.shape = box1Shape
box1FixtureDef.density = 1
box1FixtureDef.friction = 0.3
box1Body.CreateFixture(box1FixtureDef)

circle1BodyDef = b2BodyDef()
circle1BodyDef.type = b2_dynamicBody
circle1BodyDef.position.Set(20, 45)
circle1Body = world.CreateBody(circle1BodyDef)
circle1Shape = b2CircleShape()
circle1Shape.radius = 0.5
circle1FixtureDef = b2FixtureDef()
circle1FixtureDef.shape = circle1Shape
circle1FixtureDef.density = 1
circle1FixtureDef.friction = 0.3
circle1Body.CreateFixture(circle1FixtureDef)

timeStep = 1.0 / 60
velIters = 10
posIters = 10

colors = {
    staticBody  : (255,255,255,255),
    dynamicBody : (127,127,127,255),
}

def my_draw_polygon(polygon, body, fixture):
    vertices=[(body.transform*v)*PPM for v in polygon.vertices]
    vertices=[(v[0], SCREEN_HT-v[1]) for v in vertices]
    pygame.draw.polygon(screen, colors[body.type], vertices)
polygonShape.draw=my_draw_polygon

def my_draw_circle(circle, body, fixture):
    position=body.transform*circle.pos*PPM
    position=(position[0], SCREEN_HT-position[1])
    pygame.draw.circle(screen, colors[body.type], [int(x) for x in position], int(circle.radius*PPM))
circleShape.draw=my_draw_circle

running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
            continue
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
            continue

    screen.fill((0, 0, 0, 0))

    for body in world.bodies:
        for fixture in body.fixtures:
            fixture.shape.draw(body, fixture)
                     
    world.Step(timeStep, velIters, posIters)
    pygame.display.flip()
    clock.tick(TARGET_FPS)

pygame.quit()
print("done")


여기서는 두개의 dynamic body를 떨어뜨리는데, 하나는 15도 만큼 회전시킨 박스이며, 또 하나는 원입니다.


박스에 회전 속성을 주는 코드는 다음과 같습니다.

box1BodyDef.angle = 15


박스를 그리는 작업은 my_draw_polygon 함수에서 원을 그리는 작업은 my_draw_circle 함수에서 처리됩니다.


원을 그리는 pygame 함수는 중심점 좌표와 반지름을 파라미터로 받는데 모두 integer 타입이어야 합니다. 중심점 좌표에 대해 polygon에서 처럼 좌표 변환을 한 후, integer로 변환을 하여 넘겨줍니다.

position=body.transform*circle.pos*PPM
position=(position[0], SCREEN_HT-position[1])
pygame.draw.circle(screen, colors[body.type], [int(x) for x in position], int(circle.radius*PPM))




,
[Python] pyBox2D 사용 예제 - 4. pyGame + Box2D 기본 예제 (1)


pyGame에 pyBox2D를 결합한 첫번째 예제 코드입니다.
import pygame
from pygame.locals import *
from Box2D import *
from Box2D.b2 import *

SCREEN_WD = 400
SCREEN_HT = 400
TARGET_FPS = 60
PPM = 20.0

screen = pygame.display.set_mode((SCREEN_WD, SCREEN_HT), 0, 32)
pygame.display.set_caption("PyBox2D_Example")
clock = pygame.time.Clock()

world = b2World(gravity = (0, -10), doSleep = True)

ground1BodyDef = b2BodyDef()
ground1BodyDef.position.Set(0, 1)
ground1Body = world.CreateBody(ground1BodyDef)
ground1Shape = b2PolygonShape()
ground1Shape.SetAsBox(50, 5)
ground1Body.CreateFixture(shape = ground1Shape)

box1BodyDef = b2BodyDef()
box1BodyDef.type = b2_dynamicBody
box1BodyDef.position.Set(10, 15)
box1Body = world.CreateBody(box1BodyDef)
box1Shape = b2PolygonShape()
box1Shape.SetAsBox(2, 1)
box1FixtureDef = b2FixtureDef()
box1FixtureDef.shape = box1Shape
box1FixtureDef.density = 1
box1FixtureDef.friction = 0.3
box1Body.CreateFixture(box1FixtureDef)

timeStep = 1.0 / 60
velIters = 10
posIters = 10

colors = {
    staticBody  : (255,255,255,255),
    dynamicBody : (127,127,127,255),
}

running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
            continue
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
            continue

    screen.fill((0, 0, 0, 0))

    for body in (ground1Body, box1Body):
        for fixture in body.fixtures:
            shape = fixture.shape
            vertices = [(body.transform * v) * PPM for v in shape.vertices]
            vertices = [(v[0], (SCREEN_HT - v[1])) for v in vertices]
            pygame.draw.polygon(screen, colors[body.type], vertices)
                     
    world.Step(timeStep, velIters, posIters)
    pygame.display.flip()
    clock.tick(TARGET_FPS)

pygame.quit()
print("done")
전반적으로는 앞서 두개의 예제가 합쳐진 것이라고 볼 때 크게 다르지 않습니다.


주의할 부분은 Box2d의 객체들을 화면으로 그려주는 부분인데,


생성된 지면과 박스의 body 객체 각각에 대해 처리하도록 하면서,
    for body in (ground1Body, box1Body):
body 객체에 연결된 모든 fixture 객체에 대해 처리하도록 합니다.
    for fixture in body.fixtures:
fixture 객체에 연결된 shape 객체를 가져옵니다.
    shape = fixture.shape
Shape 객체 내의 정점 집합에 대해 body 객체 자체의 확대 배율(body.transform)과 미터당 포인트 비율(Point Per Meter, PPM)을 곱하여(Box2D 내부의 좌표 체계는 미터로 되어있음), 결과 또한 집합으로 얻습니다.
    vertices = [(body.transform * v) * PPM for v in shape.vertices]
Box2D 좌표계 상의 Y좌표는 위로 갈수록 커지고, pyGame 좌표계 상의 Y좌표는 아래로 갈수록 커지므로 이를 보정해줍니다. (v[0] : X 좌표, v[1] : Y좌표)
    vertices = [(v[0], (SCREEN_HT - v[1])) for v in vertices]
마지막으로 최종적으로 구해진 좌표 집합으로 polygon을 그리는데, 타입이 static이냐 dynamic이냐에 따라 다른 색으로 그려줍니다.
    pygame.draw.polygon(screen, colors[body.type], vertices)
,

wxPython 사용 중 에러가 발생할 경우, 에러 메세지가 프로그램을 실행한 콘솔창이 아닌 별도의 창이 생성되면서 출력되는데(print문을 사용하여 출력한 메세지도 마찬가지) 프로그램이 종료되면서 이 창도 사라지기 때문에 에러 메세지 확인이 어렵습니다.


Application 객체 생성시에, 다음 방법을 사용하여, 에러 메세지를 파일로 저장 혹은 콘솔창에 출력하도록 할 수 있습니다.



1. 콘솔창에 출력하기


app = wx.App(False)



2. 외부 파일에 저장하기


app = wx.App(True, filename = "파일명.확장자")





※ P.S


이와 같은 현상은 wx.App를 사용하여 application을 생성하였을 경우에만 발생합니다. wx.PySimpleApp을 사용했을 때는 기본적으로 콘솔창으로 출력되게 되어있습니다.


,
다음 예제 코드는 pyGame 라이브러리를 사용하여 기본적인 화면 디스플레이를 수행하는 방법을 보여줍니다.


우선 화면의 크기를 설정하고, 타이틀바의 텍스트를 설정하고 메인 이벤트 루프로 들어갑니다.


루프 초기에서 몇가지 사소한 이벤트에 대해 처리해준 후에, 칙칙한 검은색으로 화면을 채우고, 조그만 상자를 하나를 그려줍니다.


마지막으로 눈깜박할 시간보다 작은 딜레이를 추가해 FPS(초당 프레임수)를 맞출 수 있는 코드를 추가합니다. 헷갈리지 말아야 할 점은 파라미터로 딜레이 시간이 아닌 목표로 하는 FPS 값이 들어간다는 점입니다.(TARGET_FPS)


이벤트는 닫기 버튼과 ESC 키에 대한 이벤트를 처리하고 있습니다. 이 신경 쓰이는 두가지 이벤트가 발생하면 루프를 빠져나가서 프로그램을 종료합니다.

import pygame
from pygame.locals import *

SCREEN_WD = 400
SCREEN_HT = 400
TARGET_FPS = 60

screen = pygame.display.set_mode((SCREEN_WD, SCREEN_HT), 0, 32)
pygame.display.set_caption("Pygame_Example")
clock = pygame.time.Clock()

running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
            continue
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
            continue

    screen.fill((0, 0, 0, 0))
    vertices = [(10, 10), (20, 10), (20, 20), (10, 20)]
    pygame.draw.polygon(screen, (0, 255, 0, 0), vertices)
    pygame.display.flip()
    clock.tick(TARGET_FPS)

pygame.quit()
print("done")


참고 사항

파일명을 pygame.py로 저장하지 않도록 주의합니다. pygame.py로 저장하면 다음과 같은 에러가 뜨면서 실행이 안 될 것입니다.

ModuleNotFoundError: No module named 'pygame.locals'; 'pygame' is not a package


<테스트 환경>
 - OS : Windows 7
 - Python 버전 : 3.6
,