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, 0), 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(16, 20)
box1Body = world.CreateBody(box1BodyDef)
box1Shape = b2PolygonShape()
box1Shape.SetAsBox(1, 1)
box1FixtureDef = b2FixtureDef()
box1FixtureDef.shape = box1Shape
box1FixtureDef.density = 1
box1FixtureDef.friction = 0.3
box1Body.CreateFixture(box1FixtureDef)

box2BodyDef = b2BodyDef()
box2BodyDef.type = b2_dynamicBody
box2BodyDef.position.Set(16, 15)
box2Body = world.CreateBody(box2BodyDef)
box2Shape = b2PolygonShape()
box2Shape.SetAsBox(1, 1)
box2FixtureDef = b2FixtureDef()
box2FixtureDef.shape = box2Shape
box2FixtureDef.density = 1
box2FixtureDef.friction = 0.3
box2Body.CreateFixture(box2FixtureDef)

jointDef = b2PrismaticJointDef()
worldAxis = b2Vec2(0, 1.0)
jointDef.Initialize(box1Body, box2Body, box1Body.worldCenter, worldAxis)
jointDef.lowerTranslation = -5.0
jointDef.upperTranslation = 2.5
jointDef.enableLimit = True
jointDef.maxMotorForce = 1.0
jointDef.motorSpeed = 0
jointDef.enableMotor = True
prismaticJoint = world.CreateJoint(jointDef)

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

prismaticJoint.__SetMotorSpeed(15)

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")


[Python] pyBox2D 사용 예제 - 9. Prismatic Joint

Prismatic Joint는 두개의 body를 가변 가능한 길이로 연결하는데, 마치 유압 실린더와 같은 역활을 합니다.


아래의 코드로 가동 범위를 설정한 후에,

jointDef.lowerTranslation = -5.0
jointDef.upperTranslation = 2.5
jointDef.enableLimit = True


움직일 속도를 세팅합니다.

prismaticJoint.__SetMotorSpeed(15)


,

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, 0), 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(16, 12)
box1Body = world.CreateBody(box1BodyDef)
box1Shape = b2PolygonShape()
box1Shape.SetAsBox(4, 0.25)
box1FixtureDef = b2FixtureDef()
box1FixtureDef.shape = box1Shape
box1FixtureDef.density = 1
box1FixtureDef.friction = 0.3
box1FixtureDef.categoryBits = 0x02
box1FixtureDef.maskBits = 0x04
box1Body.CreateFixture(box1FixtureDef)

circle1BodyDef = b2BodyDef()
circle1BodyDef.type = b2_dynamicBody
circle1BodyDef.position.Set(16, 12)
circle1Body = world.CreateBody(circle1BodyDef)
circle1Shape = b2CircleShape()
circle1Shape.radius = 1
circle1FixtureDef = b2FixtureDef()
circle1FixtureDef.shape = circle1Shape
circle1FixtureDef.density = 1
circle1FixtureDef.friction = 0.3
circle1FixtureDef.categoryBits = 0x04
circle1FixtureDef.maskBits = 0x02
circle1Body.CreateFixture(circle1FixtureDef)

jointDef = b2RevoluteJointDef()
jointDef.Initialize(box1Body, circle1Body, circle1Body.worldCenter)
jointDef.collideConnected = False
jointDef.lowerAngle = 0
jointDef.upperAngle = 0
jointDef.enableLimit = False
jointDef.maxMotorTorque = 10
jointDef.motorToque = 0
jointDef.motorSpeed = 0
jointDef.enableMotor = True
revoluteJoint = world.CreateJoint(jointDef)
print(revoluteJoint)

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

revoluteJoint.__SetMotorSpeed(15)

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")


[Python] pyBox2D 사용 예제 - 8. Revolute Joint



Revolute Joint는 하나의 축을 중심으로 두개의 body 사이에 회전 운동을 가능하게 하는 연결입니다.


위 코드는 사각형과 원을 revolute joint로 연결한 후에, 사각형을 일정한 속도로 회전하도록 하는 예제 코드입니다.


,

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(16, 24)
box1Body = world.CreateBody(box1BodyDef)
box1Shape = b2PolygonShape()
box1Shape.SetAsBox(1, 1)
box1FixtureDef = b2FixtureDef()
box1FixtureDef.shape = box1Shape
box1FixtureDef.density = 1
box1FixtureDef.friction = 0.3
box1Body.CreateFixture(box1FixtureDef)

box2BodyDef = b2BodyDef()
box2BodyDef.type = b2_dynamicBody
box2BodyDef.position.Set(16, 18)
box2Body = world.CreateBody(box2BodyDef)
box2Shape = b2PolygonShape()
box2Shape.SetAsBox(1, 1)
box2FixtureDef = b2FixtureDef()
box2FixtureDef.shape = box2Shape
box2FixtureDef.density = 1
box2FixtureDef.friction = 0.3
box2Body.CreateFixture(box2FixtureDef)

jointDef = b2DistanceJointDef()
jointDef.Initialize(box1Body, box2Body, box1Body.worldCenter, box2Body.worldCenter)
jointDef.frequencyHz = 4.0
jointDef.dampingRatio = 0.5
world.CreateJoint(jointDef)

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")


[Python] pyBox2D 사용 예제 - 7. Distance Joint

Distance Joint는 두개의 body를 고정된 길이로 연결하여, 마치 스프링 연결과 같은 역활을 합니다. 이 때 스프링의 특성은 frequencyHz와 dampingRatio 속성을 사용하여 정의되는데


  • frequencyHz : 진동수를 나타내며, 일반적으로 time step의 빈도수의 절반보다 작은 값을 가져야 합니다. 즉, time step이 60Hz 라면 frequencyHz는 30Hz 미만의 값을 가져야 합니다.
  • dampingRatio : 감쇄율을 나타내며, 일반적으로 0과 1사이의 값을 가집니다.



,

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(20, 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
box1FixtureDef.filter.categoryBits = 0x0002
box1FixtureDef.filter.maskBits = 0x0002 | 0x0001
box1Body.CreateFixture(box1FixtureDef)

circle1BodyDef = b2BodyDef()
circle1BodyDef.type = b2_dynamicBody
circle1BodyDef.position.Set(20, 0.5)
circle1Body = world.CreateBody(circle1BodyDef)
circle1Shape = b2CircleShape()
circle1Shape.radius = 0.5
circle1FixtureDef = b2FixtureDef()
circle1FixtureDef.shape = circle1Shape
circle1FixtureDef.density = 1
circle1FixtureDef.friction = 0.3
circle1FixtureDef.filter.categoryBits = 0x0004
circle1FixtureDef.filter.maskBits = 0x0004 | 0x0001
circle1Body.CreateFixture(circle1FixtureDef)

class MyContactListener(b2ContactListener):
    def BeginContact(self, contact):
        print("BeginContact")
    def EndContact(self, contact):
        print("EndContact")

contactListener = MyContactListener()
world.contactListener = contactListener

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")


[Python] pyBox2D 사용 예제 - 6. 충돌 필터링


충돌 필터링이란 Fixture들을 카테고리로 분류하여 카테고리간에 충돌이 일어나게 하거나 일어나지 않도록 제어하는 것을 말합니다.


위의 예제 코드에서, static ground body인 지면 위로, dynamic body인 사각형과 원모양이 떨어지는데, 사각형의 충돌 필터는 다음과 같이 정의되어 있습니다.


box1FixtureDef.filter.categoryBits = 0x0002
box1FixtureDef.filter.maskBits = 0x0002 | 0x0001


사각형의 카테고리(categoryBits)를 0x0002로 하며, 상대방의 카테고리가 0x0002나 0x0001 인 경우에만 충돌을 일으킨다는 의미입니다.


원의 충돌 필터는 다음과 같이 정의되어 있습니다.


circle1FixtureDef.filter.categoryBits = 0x0004
circle1FixtureDef.filter.maskBits = 0x0004 | 0x0001


원의 카테고리를 0x0004로 하며, 상대방의 카테고리가 0x0004나 0x0001 인 경우에만 충돌을 일으킨다는 의미입니다.


따라서 이 상태에서 사각형과 원은 서로 충돌하지 않습니다. 다만 categoryBit의 기본초기값은 0x0001이며, static ground body의 categoryBit는 정의되지 않아서 기본초기값을 가지므로 사각형과 원은 지면과는 충돌하여 멈추게 됩니다.


만약 maskBits에서 | 0x0001을 빼버리면 지면과도 충돌하지 않고 통과되어 지나가는 것을 볼 수 있습니다.


사각형과 원을 충돌하게 하려면, 사각형의 maskBits를 0x0004 | 0x0001로 하고, 원의 maskBits를 0x0002 | 0x0001로 하면 됩니다. 한쪽이 아닌 양쪽 모두에 설정되어 있어야 충돌이 일어납니다.


box1FixtureDef.filter.maskBits = 0x0004 | 0x0001

...

circle1FixtureDef.filter.maskBits = 0x0002 | 0x0001


그리고 maskBits를 설정하지 않았을 때, 기본값은 0xFFFF인데, 이는 모든 카테고리와 충돌을 일으킨다는 의미입니다.



다음은 contact listener를 설정하는 코드입니다.


class MyContactListener(b2ContactListener):
    def BeginContact(self, contact):
        print("BeginContact")
    def EndContact(self, contact):
        print("EndContact")

contactListener = MyContactListener()
world.contactListener = contactListener


Contact listener를 설정하게 되면, 어떤 충돌이 시작되고 끝날 때에 해당 call-back 함수가 호출되어 적당한 동작을 실행할 수 있게 해줍니다. 충돌이 시작될 때, BeginContact 함수가, 충돌이 끝났을 때, EndContact 함수가 호출됩니다.



,

[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)
,
다음 예제 코드는 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
,

[Python] pyBox2D 사용 예제 - 2. Box2D 기본 예제


pyBox2D 첫번째 기본 예제입니다.


실행을 해보면 (0, 5) 좌표에 위치한 박스가 중력에 의해 지면을 향해 떨어져 지면에 부딧혀 멈출 때까지의 박스의 좌표가 출력됩니다. (무려 텍스트로!)

Box2D 자체에는 그래픽 표현 기능이 없으므로 좌표 계산만 해줍니다. 이것을 화면 상으로 표현하는 것은 이후의 예제를 통하여 알아보겠습니다.


from Box2D import *

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(0, 5)
box1Body = world.CreateBody(box1BodyDef)
box1Shape = b2PolygonShape()
box1Shape.SetAsBox(1, 1)
box1FixtureDef = b2FixtureDef()
box1FixtureDef.shape = box1Shape
box1FixtureDef.density = 1
box1FixtureDef.friction = 0.3
box1Body.CreateFixture(box1FixtureDef)

timeStep = 1.0 / 60
velIters = 6
posIters = 2

for i in range(60):
    world.Step(timeStep, velIters, posIters)
    world.ClearForces()
    print(box1Body.position)


Box2D에서 어떤 물체는 body, fixture, shape 객체로 표현됩니다.


Body 객체

Body 객체는 어떤 물체를 표현하는 기본 객체로 위치, damping(감쇄율) 등의 정보가 저장됩니다. 다음과 같이 세가지 타입으로 나뉩니다.

  • Static body
  • Kinetic body
  • Dynamic body

Static body는 지면과 같이, 절대 움직이지 않는 물체를 나타내고, Kinetic body는 항상 일정한 속도로 움직이는 물체를(움직이는 배경), Dynamic body는 완전히 물리 법칙에 의해 그 움직임이 좌우되는 물체를 나타냅니다. Static, kinetic body 사이에서는 충돌이 일어나지 않으며, dynamic body는 static 혹은 kinetic body들과 충돌을 일으킬 수 있습니다.


Fixture 객체

Fixture 객체에는 density(밀도), friction(마찰력), resistitution(탄성력) 등의 정보를 담고, 물체의 모양을 나타내는 Shape 객체도 fixture 객체를 통해 연결됩니다. 하나의 body는 여러개의 fixture 객체를 가질 수도 있는데, 그 이유는 하나의 물체가 여러개의 모양, 매질이 결합된 형태일 수도 있기 때문이 아닐까하고 우선은 추정해봅니다.


Shape 객체

물체의 모양을 나타냅니다. Fixture 객체의 속성의 하나로서 body 객체에 연결됩니다.


아래와 같은 생성 관계를 잘 기억해두도록 합시다.


  • DLL 로딩 문제


첫줄의 from Box2D import * 실행시에 다음과 같은 에러가 나는 경우가 있는데,


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


Visual C++ 2010 Redistributable package가 설치가 안되서 발생하는 것이었습니다.

아래의 페이지에서 받은 후 설치해줍니다.


http://www.microsoft.com/en-us/download/details.aspx?id=5555



Dependency walker 라는 프로그램으로 <Python 설치 폴더>/Lib/site-packages/Box2D에 있는 _Box2D.pyd 파일을 열어보면 Box2D 라이브러리가 필요로하는 DLL들을 확인할 수 있습니다.


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


,