Search…

Box2D - Phần 2: Thuật Ngữ Và Khái Niệm

30/09/20208 min read
Giới thiệu về collision và các thao tác xử lý liên quan trên Cocos2d-x.

Collision

Collision là gì?

Là va chạm của các body trong physics world, trong dự án games sử dụng rất nhiều thông tin từ nó để dùng cho logic games của bạn, ví dụ như:

  • Khi bắt đầu một va chạm và kết thúc một va chạm.
  • Những điểm trên body va chạm với body khác.
  • Các lực va chạm.
  • ...

Thông tin một collision

Thông tin về một vụ va chạm được chứa trong một đối tượng b2Contact. Từ đây, có thể kiểm tra rằng khi nào va chạm, và tìm hiểu về vị trí và hướng của phản ứng va chạm. Có hai cách chính, có thể nhận được các đối tượng b2Contact từ Box2D. 

Kiểm tra danh sách b2Contact:

for (b2Contact* contact = world->GetContactList(); contact; contact = contact->GetNext())
      contact->... // làm điều gì đó với va chạm này.

Hoặc với một body:

for (b2ContactEdge* edge = body->GetContactList(); edge; edge = edge->next)
      edge->contact->... //làm gì đó với va chạm này.

Contact listeners

Nếu trong dự án games liên tục xảy ra va chạm. Như bên trên chủ để có một va chạm, cần phải theo dõi từ lúc bắt đầu cho đến kết thúc va chạm đó, còn bây giờ không còn phải làm điều đó nữa. Một contact listeners là một class (b2ContactListener) với bốn chức năng mà cần ghi đè lên khi cần thiết.

 void BeginContact(b2Contact* contact);
 void EndContact(b2Contact* contact);
 void PreSolve(b2Contact* contact, const b2Manifold* oldManifold);
 void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);

Nhưng đôi khi trong dự án games, một vài trường hợp phải dùng b2Contact thay cho b2ContactListener. Nhưng dù bằng cách nào nhận được những contact listener, chúng đều chứa thông tin tương tự. Phần cơ bản nhất của thông tin là những fixtures của va chạm lấy được bằng cách:

b2Fixture* a = contact->GetFixtureA();
b2Fixture* b = contact->GetFixtureB();

Chú ý:

  • Từ các fixtuare mà thu được có thể tìm thấy các body trong vụ va chạm bằng phương thức GetBody() và từ đó sử dụng thông tin này cho logic games.
  • Khi một sự kiện va chạm xảy ra, tức là có 2 fixture va chạm, nhưng không thể biết chính xác thứ tự của các fixture này. Ví dụ, có một va chạm giữa player và enemy, không thể biết chắc rằng b2Fixture A là của body player hay của là body enemi và ngược lại. 

Collision filtering

Ví dụ như sau, trong dự án games có 3 loại đối tượng: người chơi, quái vậtcảnh quan và muốn chúng va chạm theo quy tắc sau:

  • Người chơi không va chạm với người chơi khác, tương tự với con quái vật.
  • Người chơi phải va chạm với con quái vật và ngược lại. Tất nhiên là người chơi và quái vật va chạm với cảnh quan.
  • Các đối tượng canh quan sẽ va chạm với nhau.

Filter catefories và mask

Categoriesmask là điều tốt nhất cho collision filtering, mục đích là để xác định thể loại cho mỗi đối tượng và sử dụng mask để lọc các va chạm giữa các đối tượng. Với ví dụ ở trên xác định có 3 loại đối tượng trong va chạm

short CATEGORY_PLAYER = 0x0001; // 0000000000000001 
short CATEGORY_MONSTER = 0x0002; // 0000000000000010
short CATEGORY_SCENERY = 0x0004; // 0000000000000100

Và:

player1FixtureDef.filter.categoryBits = CATEGORY_PLAYER;
player2FixtureDef.filter.categoryBits = CATEGORY_PLAYER;
 
monster1FixtureDef.filter.categoryBits = CATEGORY_MONSTER;
monster2FixtureDef.filter.categoryBits = CATEGORY_MONSTER;
monster3FixtureDef.filter.categoryBits = CATEGORY_MONSTER;
monster4FixtureDef.filter.categoryBits = CATEGORY_MONSTER;
 
groundFixtureDef.filter.categoryBits = CATEGORY_SCENERY;
crate1FixtureDef.filter.categoryBits = CATEGORY_SCENERY;
crate2FixtureDef.filter.categoryBits = CATEGORY_SCENERY;

Chú ý

Ở đây sử dụng các giá trị 0×001, 0×002 và các 0×004. Tại sao không phải 1, 2 và 3? Đó là số lượng phân loại các loại đối tượng. Điều đó có nghĩa rằng các loại có thể là chi hết cho 2 (trong thập phân: 1, 2, 4, 8, 16, 32, 64, 128..., hoặc trong hệ thập lục phân: 0×1, 0×2, 0×4, 0×8, 0×10, 0×20, 0×40, 0×80...). 16 bit có nghĩa là có 16 thư mục có thể, từ 0×0001 để 0×8000.

Bây giờ, hãy xác định các maskBits. Chúng làm việc như sau:

for each object o1 in the world
    for each object o2 in the world
        isCollisionEnabled = (o1.filter.maskBits & o2.filter.collisionBits) ≠ 0

Kết quả của sự va chạm trong ví dụ trên.

short MASK_PLAYER = CATEGORY_MONSTER | CATEGORY_SCENERY; // ~CATEGORY_PLAYER
short MASK_MONSTER = CATEGORY_PLAYER | CATEGORY_SCENERY; // ~CATEGORY_MONSTER
short MASK_SCENERY = -1;
  • Đối tượng loại phong cảnh va chạm với tất cả mọi thứ. Vì vậy sẽ đặt maskBits là -1 cũng tương đương với 0xFFFF trong lĩnh vực bit.
  • maskBits có giá trị 0xFFFF (hoặc -1) sẽ cho phép các va chạm với tất cả các đối tượng khác. Nếu bạn thiết lập giá trị của maskBits là 0×0000 (hoặc 0) thì nó sẽ không va chạm với đối tượng nào cả

Đoạn mã ở dưới khởi tạo các giá trị maskBits cho các đối tượng có trên ví dụ trên:

player1FixtureDef.filter.maskBits = MASK_PLAYER;
player2FixtureDef.filter.maskBits = MASK_PLAYER;
 
monster1FixtureDef.filter.maskBits = MASK_MONSTER;
monster2FixtureDef.filter.maskBits = MASK_MONSTER;
monster3FixtureDef.filter.maskBits = MASK_MONSTER;
monster4FixtureDef.filter.maskBits = MASK_MONSTER;
 
groundFixtureDef.filter.maskBits = MASK_SCENERY;
crate1FixtureDef.filter.maskBits = MASK_SCENERY;
crate2FixtureDef.filter.maskBits = MASK_SCENERY;

Filter group

Có thể làm tất cả mọi thứ về va chạm sao cho phù hợp với logic game của với categoryBitsmaskBits. Nhưng đôi khi không cần đến nó vì sự phức tạp về các giá trị bit. Filter group được xây dựng để nhanh chóng tắt hoặc bật kiểm tra va chạm giữa các đối tượng có liên quan bằng cách nào đó. Nó cho phép chỉ định một chỉ số tích phân nhóm và có thể có tất cả mọi thứ về va chạm với:

  • Chỉ số nhóm giống luôn luôn va chạm (chỉ số tích cực)  
  • Chỉ số nhóm khác nhau không bao giờ va chạm (chỉ số tiêu cực).

Với ví dụ trên sử dụng filter group:

short GROUP_PLAYER = -1;
short GROUP_MONSTER = -2;
short GROUP_SCENERY = 1;

Chỉ định như sau:

player1FixtureDef.filter.groupIndex = GROUP_PLAYER;
player2FixtureDef.filter.groupIndex = GROUP_PLAYER;
 
monster1FixtureDef.filter.groupIndex = GROUP_MONSTER;
monster2FixtureDef.filter.groupIndex = GROUP_MONSTER;
monster3FixtureDef.filter.groupIndex = GROUP_MONSTER;
monster4FixtureDef.filter.groupIndex = GROUP_MONSTER;
 
groundFixtureDef.filter.groupIndex = GROUP_SCENERY;
crate1FixtureDef.filter.groupIndex = GROUP_SCENERY;
crate2FixtureDef.filter.groupIndex = GROUP_SCENERY;

Có thể thấy đoạn mã ngắn hơn rất nhiều. Tuy nhiên, không có cách nào để vô hiệu hóa các va chạm giữa các nhóm, tức là một khi muốn người chơi không va chạm với người chơi khác, nhưng đôi khi có một số trường hợp muốn người chơi khác va chạm với nhau, không thể làm được. Đó là lý do tại sao việc sử dụng categoryBitsmaskBits lại mạnh hơn so với filter group.

Chú ý

Quay trở lại với chú ý của mục Collision, tôi đã nói rằng đôi khi việc bạn sử dụng b2Contact thay vì cho b2ContactListeners để bắt một sự kiện va chạm lại tốt hơn. Vì b2ContactListeners không thể bắt được sự kiện va chạm giữa các đối tượng không va chạm với nhau. Có nghĩa là khi một người chơi va chạm với một người khác, chỉ có b2Contact bắt được sự kiện này, còn b2ContactListeners thì không. 

Di chuyển một body

Forces và impulses

Forces hành động theo thời gian để thay đổi vận tốc của một body trong khi impulses có thể thay đổi vận tốc của body ngay lập tức. Tùy vào mục đích bạn mô phỏng một sự việc nào đó để chọn cách tác động lực lên body cho nó di chuyển.

myBodyDef.type = b2_dynamicBody;
    
b2PolygonShape polygonShape;
polygonShape.SetAsBox(1, 1); 
  
b2FixtureDef myFixtureDef;
myFixtureDef.shape = &polygonShape;
myFixtureDef.density = 1;

myBodyDef.position.Set(20, 20);
bodies = m_world->CreateBody(&myBodyDef);
bodies->CreateFixture(&myFixtureDef);

Với forces:

bodies->ApplyForce(b2Vec2(0,50), bodies->GetWorldCenter(), true);

Với impulses:

bodies->ApplyLinearImpulse(b2Vec2(0,50), bodies->GetWorldCenter(), true);

Với transform:

bodies->SetTransform(b2Vec2(10,20), 0);
  • Với forces và impulses có 2 tham số: lực tác động và điểm tác động trên body. Tùy vào điểm tác động trên body mà sẽ quyết định body đó quay theo chiều cùng với kim đồng hồ hay ngược chiều kim đồng hồ. 
  • Với transform có 2 tham số là: lực tác động và góc quay.

Linear

Tác động một lực lên body, nhưng body sẽ không đồng thời quay tròn.​

bodies->SetLinearVelocity(b2Vec2(-10.0f, 0.0f));

Với việc sử dụng fores và impulses với điểm tác động là trung tâm body và transform với góc quay là 0 cũng sẽ làm cho body di chuyển mà không đồng thời quay tròn.

Chú ý: Khi tác động một lực lên một body nó luôn là một vector, có phương và độ lớn tương ứng.

User data

Chức năng này có tác dụng thêm một data vào trong các đối tượng. Các đối tượng gồm

  • b2Body
  • b2Fixture
  • b2Joint

Box2D cần biết thông tin này là gì và nó không làm bất cứ điều gì với data đó. Nó chỉ giữ data và sẽ trả lại khi được yêu cầu. Đối tượng trên có các phương thức sau:

void SetUserData(void* data);
void* GetUserData();

Thường thì trong games bạn hay sử dụng data là một sprite. Việc bạn thiết lập data cho các body, fixtuare là sẽ vô cùng hữu ích. 

int myInt = 123;
body->SetUserData((void*)myInt);

int udInt = (int)body->GetUserData();
  
//*********************************************************
struct bodyUserData {
      int entityType;
      float health;
      float stunnedTimeout;
};

bodyUserData* myStruct = new bodyUserData;
myStruct->health = 4;//set structure contents as necessary
body->SetUserData(myStruct);

bodyUserData* udStruct = (bodyUserData*)body->GetUserData();
IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024