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 함수가 호출됩니다.



,