STDIO
Tìm kiếm gần đây

    Nội dung

    Làm Sao Để Chia Tách, Cắt Một Đối Tượng Với Box2D?

    23/01/2015
    Làm Sao Để Chia Tách, Cắt Một Đối Tượng Với Box2D?
    Bạn cần một sự chia tách, cắt một đối tượng trong thế giới games có vật lý của bạn, hoặc có thể hơn bạn cần một một vụ nổ thật sự. Trong bài viết này tôi sẽ hướng dẫn làm sao để chia tách, cắt một đối tượng với thư viện vật lý trờ chơi Box2D. Trong bài viết này tôi sử dụng Cocos2d-x để hỗ trợ.

    Giới thiệu

    Sau loạt bài viết của tôi về giới thiệu thư viện vật lý trò chơi Box2D, tôi tiếp tục giới thiệu một số bài viết hiện thực một số các hiện tượng, hiệu ứng nhằm giúp tăng kinh nghiệm làm việc với thư viện vật lý trò chơi này.

    Tiền đề bài viết

    Chia tách, cắt đối tượng là một việc mà bạn có thể thấy nhiều trong games hiện nay, bạn có thể áp dụng nó để có thể thực hiện một vụ nổ.

    Đối tượng hướng đến

    Bài viết tôi hướng đến những lập trình viên đã có kiến thức cơ bản về lập trình games 2D, có kiến thức cơ bản về các thư viện vật lý trò chơi, đặc biệt là Box2D và chưa có kiến thức tương đương.

    Nội dung

    Trong bài viết này, tôi sẽ:

    • Tạo ra một đối tượng.
    • Tạo ra một tia cắt (laser).
    • Cắt đối tượng đó ra làm hai (object1, object2).

    Những kiến thức cần có

    • Physics World.
    • Bodies.
    • Ray Casting.

    ss_1

    Hiện thực

    Tạo ra tia cắt (Laser)

    Để tạo ra tia cắt (laser), tôi sẽ sử dụng Touches Event:

    #define PTM_RATIO 32
    
    void HelloWorld::onTouchEnded(Touch* touch, Event* event) { }
    
    bool HelloWorld::onTouchBegan(Touch* touch, Event* event) {
    	auto location = touch->getLocation();
    	position1.x = location.x / PTM_RATIO;
    	position1.y = location.y / PTM_RATIO;
    	return true;
    }
    
    void HelloWorld::onTouchMoved(Touch* touch, Event* event) {
    	auto location = touch->getLocation();
    	position2.x = location.x / PTM_RATIO;
    	position2.y = location.y / PTM_RATIO;
    }
    Phân tích

    Tia cắt (laser) sẽ có hai điểm đầu và cuối:

    • position1: Điểm đầu, được xác định khi có Event Touch Began.
    • position2: Điểm cuối, được xác định khi có Event Touch Move.

    Tiếp đến tạo ra nó. Tôi sử dụng Ray Casting để tạo ra một tia cắt. Bạn có thể đọc về Ray Casting trong bài viết Box2D - Phần 6: Ray Casting.

    debugDraw->DrawSegment(position1, position2, b2Color(0.8f, 0.8f, 0.8f));
    
    if ((position1.x != position2.x) && (position1.y != position2.y))
    {
    	RayCastClosestCallback callback;
    	m_world->RayCast(&callback, position1, position2);
    
    	RayCastClosestCallback callback2;
    	m_world->RayCast(&callback2, position2, position1);
    }
    Phân tích
    • Dòng 1: Vẽ lên màn hình một đoạn thẳng với hai điểm position1 và position2.
    • Dòng 3-10: Tạo ra 2 Ray Casting.

    Tạo sao lại cần tạo ra 2 Ray Casting:

    1.  Vì mỗi Ray Casting chỉ cắt đối tượng tại một điểm gần position1 của nó nhất. Nhưng khi cắt đối tượng tôi cần tới 2 điểm cắt, vì vậy tôi phải cần tới 2 Ray Casting.
    2. Với Ray Casting đầu tiên là position1 và position2 và Ray Casting thứ hai là position2 và position1.

    Chia tách, cắt đối tượng.

    Tôi có đoạn code sau:

    void HelloWorld::update(float dt)
    {
    #define PTM_RATIO 32 // Dùng để chuyển đổi đơn vị Box sang Pixel và ngược lại
    /* Một hàm tính toán xác định hướng của một điểm với một đường thẳng (đoạn thẳng)
    điều này sẽ nói rõ hơn ở phía dưới. */
    #define Calulate_Determinant(x1,y1,x2,y2,x3,y3) x1*y2 + x2*y3 + x3*y1 - y1*x2 - y2*x3 - y3*x1
    
    //** Đoạn này có lẽ quen thuộc, chỉ là cập nhật cho Physics Wolrd trong games của bạn   
    	int positionIterations = 10;  
    	int velocityIterations = 10; 
    
    	float deltaTime = dt; 
    
    	std::vector<b2Body *>toDestroy;
    	m_world->Step(dt, velocityIterations, positionIterations);
    
    	for (b2Body *body = m_world->GetBodyList(); body != NULL; body = body->GetNext()) {
    		if (body->GetUserData()) {
    			Sprite *sprite = (Sprite *)body->GetUserData();
    			sprite->setPosition(Point(body->GetPosition().x * PTM_RATIO, body->GetPosition().y * PTM_RATIO));
    			sprite->setRotation(-1 * CC_RADIANS_TO_DEGREES(body->GetAngle()));
    		} // end if
    		m_world->ClearForces();
    		m_world->DrawDebugData();
    	} // end for
    ////////////////////////////////////////////////////////////////////////////////////////
    
    	// Vẽ tia cắt (laser) lên màn hình.
    	debugDraw->DrawSegment(position1, position2, b2Color(0.8f, 0.8f, 0.8f));
    
    	if (cuting == true)
    	{
    
    /*Ở phần trên, tôi đã tạo một đối tượng có tên box và là hình hộp. Bây giờ khi tôi chia
     tách, cắt thì điều tôi cần làm đầu tiên là phải xác định hình dạng cụ thể của nó là gì? */
    		b2Fixture *originalFixture = box->GetFixtureList();
    
    /*Các đối tượng mà được chia tách, cắt thì phải là một hình đa giác, tôi sẽ phân tích rõ 
     ở phần dưới */
    		b2PolygonShape *originalPolygon = (b2PolygonShape*)originalFixture->GetShape();
    
    		// Lấy số lượng điểm (đỉnh) tạo thành đa giác, ở đây là box.
    		int vertexCount = originalPolygon->GetVertexCount();
    
    //
    		if ((position1.x != position2.x) && (position1.y != position2.y))
    		{
    			// Tạo ra tia Ray Casting đầu tiên.
    			RayCastClosestCallback callback;
    			m_world->RayCast(&callback, position1, position2);
    
    			// Tạo ra tia Ray Casting thứ hai.
    			RayCastClosestCallback callback2;
    			m_world->RayCast(&callback2, position2, position1);
    
    /* Điều kiện là tia cắt (laser) phải cắt đối tượng.
    Có nghĩa là hai tia Ray Casting được tạo ở trên sẽ phải có cắt đối tượng, lúc này hàm 
    callback1 và callback2 được xác định cùng lúc. */
    			if (callback.m_hit && callback2.m_hit) 
    			{
    
    /*  Tia cắt (laser) căt đối tượng tại 2 điểm, 2 điểm này chính là tạo độ của fration của 
    Ray Casting đầu tiên và Ray Casting thứ hai với đối tượng Box. Ở mỗi điểm tôi vẽ lên 
    đó một hình tròn màu đỏ */
    				debugDraw->DrawCircle(callback.m_point, 0.3f, b2Color(1.5f, 0.0f, 0.0f));
    				debugDraw->DrawCircle(callback2.m_point, 0.3f, b2Color(1.5f, 0.0f, 0.0f));
    
    /* Tôi cần một nơi để lưu trữ các điểm(đỉnh) dùng để tạo thành hình dạng cho bodies khi
    đối tượng box được cắt ra thành object1 và object2 */
    				int vertexCountOfObject1 = 0;
    				int vertexCountOfObject2 = 0;
    
    				b2Vec2 *vertexOfObject1 = (b2Vec2*)calloc(24, sizeof(b2Vec2));
    				b2Vec2 *vertexOfObject2 = (b2Vec2*)calloc(24, sizeof(b2Vec2));
    
    /* 2 điểm đầu tiên của mỗi đối tượng phải là tạo độ của các fraction của Ray Casting với
    đối tượng (box). Ở ví dụ này chính là điểm E và điểm F. Vì khi tạo một đối tượng có hình
    dạng đa giác với một tập điểm ban đầu, bạn cần phải sắp xếp theo thứ tự ngược kim đồng hồ
    vậy nên tôi sắp xếp 2 điểm (đỉnh) ban đầu như ở dưới */
    				b2Vec2 fraction1 = callback.m_point;
    				b2Vec2 fraction2 = callback2.m_point;
    				vertexOfObject1[vertexCountOfObject1++] = fraction1;
    				vertexOfObject1[vertexCountOfObject1++] = fraction2;
    				vertexOfObject2[vertexCountOfObject2++] = fraction2;
    				vertexOfObject2[vertexCountOfObject2++] = fraction1;
    /////////////////////////////////////////////////////////////////////////////////////////
    
    /*Tiếp đến tôi phân loại các điểm (đỉnh) của đối tượng bị cắt (box). Tôi cần phải biết điểm
    (đỉnh) nào cần để tạo hình dạng body cho object1 và object2.
    Tôi sử dụng một phép tính toán giá trị determinant để kiểm tra và phân loại.*/
    				for (int index = 0; index < vertexCount; index++)
    				{
    					b2Vec2 point = originalPolygon->GetVertex(index);
    
    /*Nếu điểm (đỉnh) hiện tại mà bạn đang kiểm tra mà là điểm cắt của tia cắt (laser) với đối tượng
    thì không cần phải kiểm tra nữa*/
    					if (point == fraction1 || point == fraction2)
    					{
    						continue;
    					}
    					//
    					else {
    						float determinant;
    						point.x = box->GetPosition().x + point.x;
    						point.y = box->GetPosition().y + point.y;
    						determinant = calculate_determinant_2x3(position1.x, position1.y, position2.x, position2.y, point.x, point.y);
    
    /*Đây là một cách kiểm tra một điểm (đỉnh) có phía như thế nào so với một đoạn, đường thẳng.
    Nếu giá trị determitan > 0 thì điểm (đỉnh) đó thuộc nửa trên (hay cùng phía với biểu thức
    đường thằng >0), còn nếu giá trị determitan < 0 thì điểm (đỉnh) đó thuộc nửa dưới (hay ngược
    phía với biểu thức đường thằng >0)*/
    						if (determinant > 0)
    						{
    							debugDraw->DrawCircle(point, 0.3f, b2Color(0.8f, 0.8f, 0.8f));
    							vertexOfObject1[vertexCountOfObject1++] = point;
    						}
    						else
    						{
    							debugDraw->DrawCircle(point, 0.3f, b2Color(0.f, 0.9f, 0.4f));
    							vertexOfObject2[vertexCountOfObject2++] = point;
    						} // end if
    					} // end if
    				} // end for
    
    /*Tiếp đến tôi sẽ tạo ra hai object từ các tập điểm đã tìm được. Đối tượng bị cắt là box
    không cần thiết nữa, sau khi tôi tạo xong các object thì tôi sẽ remove nó ra khởi 
    Physics World*/
          
    				// Các tính chất của object mới được tạo giống như đối tượng bị cắt box
    				b2FixtureDef myFixtureDef;
    				myFixtureDef.density = originalFixture->GetDensity();
    				myFixtureDef.friction = originalFixture->GetFriction();
    				myFixtureDef.restitution = originalFixture->GetRestitution();
    
    				// Khởi tạo object1
    				b2Body *object1;
    				b2PolygonShape polygonShape;
    				polygonShape.Set(vertexOfObject1, vertexCountOfObject1);	
    				myFixtureDef.shape = &polygonShape;
    				b2BodyDef myBodyDef;
    				myBodyDef.type = b2_dynamicBody;
    				myBodyDef.position.Set(box->GetPosition().x / PTM_RATIO, box->GetPosition().y / PTM_RATIO);
    				object1 = m_world->CreateBody(&myBodyDef);
    				object1->CreateFixture(&myFixtureDef);
    				///////////////////////////////////////////////////////////////////////////////
    
    				// Khởi tạo object2
    				b2Body *object2;
    				b2PolygonShape polygonShape2;
    				polygonShape2.Set(vertexOfObject2, vertexCountOfObject2);
    				myFixtureDef.shape = &polygonShape2;
    
    				b2BodyDef myBodyDef2;
    				myBodyDef.type = b2_dynamicBody;
    				myBodyDef.position.Set(box->GetPosition().x / PTM_RATIO, box->GetPosition().x / PTM_RATIO);
    				object2 = m_world->CreateBody(&myBodyDef);
    				object2->CreateFixture(&myFixtureDef);
    				///////////////////////////////////////////////////////////////////////////////
    				// 
    				m_world->DestroyBody(box);
    
    				delete[] vertexOfObject1;
    				delete[] vertexOfObject2;
    			} // end if
    		} // end if
    
    		cuting = false;
    	} // end if
    } // end function

    ss_2

    Phân tích
    • A-B-C-D: Là các điểm (đỉnh) của đối tượng được chia tách, cắt.
    • E-F: lần lượt là các điểm mà tia cắt (laser) cắt đối tượng. Tia Ray Casting đầu tiên (E, F), Ray Casting thứ hai (F,E). 
    • E-F-C-B-A: là các điểm (đỉnh) của object1. Được lưu tại vertexOfObject1.
    • E-F-D: là các điểm (đỉnh) của object2. Được lưu tại vertexOfObject2.

    ss_3

    Chú ý

    1. Đoạn code trên cũng như các file ở phần Download tôi hiện thực với mục đích rằng bạn sẽ hiểu vấn đề để chia tách, cắt một đối tượng nhanh nhất. Nhưng đó là một đoạn code không tốt, bạn cần phải phân chia lại cho hợp lý để code đẹp hơn và tối ưu hơn. 
    2. Khi khởi tạo một đa giác với một tập điểm, bạn cần thật sự chú ý rằng một đa giác ít nhất phải có 3 điểm. Và nhiều nhất với thiết lập của Box2D là 8. Nếu bạn muốn tăng số lượng điểm (đỉnh) của đa giác, bạn truy cập vào file b2Settings.h và thay đổi giá trị b2_maxPolygonVertives bằng giá trị mà bạn mong muốn. Ở ví dụ trên tôi sử dụng giá trị 23.
    3. Khi bạn khởi tạo một đa giác với một tập điểm, các tập điểm đó phải có thứ tự ngược chiều kim đồng hồ. Trong ví dụ trên tôi tính toán đơn giản và điều kiện này luôn đúng. Nhưng với những hình dạng phức tạp hơn, tôi không đảm bảo điều đó, vì vậy bạn cần phải có một phương thức sắp xếp các điểm (đỉnh) ngược chiều kim đồng hồ. Công việc rất đơn giản khi bạn sắp xếp nó theo giá trị radian với gốc tọa độ. Có thể có những cách khác. 
    4. Vấn đề nảy sinh ra là khi bạn một cắt một đối tượng hình tròn thì sao? Hiện tại tôi thấy có 2 cách giải quyết. Thứ nhất là sử dụng phần mềm Physics Editor để tạo hình dạng tròn cho đối tượng. Nhưng bạn cần thật sự chú ý tới số lượng điểm đỉnh tối đa. Thứ hai là bạn có thể can thiệp vào lớp b2PolygonShape để tạo ra một cách tạo vật có hình dạng tròn. Tôi thích sử dụng cách đầu tiên.
    5. Vấn để thực khó khăn nhất khi sử dụng Box2D đó chính là đơn vị. Bạn phải cẩn thận với việc chuyển đổi qua lại giữa đơn vị của Box2D và pixel.

    Tổng kết

    Chia tách, cắt một đối tượng trong Box2D là một hiện tượng vật lý nhỏ, có thể xuất hiện trong vài games hiện nay. Mọi thắc mắc bạn có thể bình luận tại bài viết hoặc liên hệ với Trương Đạt :: www.stdio.vn/users/index/11/truong-dat.

    Download

    Cutting_Object_Example

    Thao khảo

    https://www.wikipedia.org

    Thảo luận

    Đăng nhập

    Bài viết liên quan

    HTML5 Sprite Animation – Tạo Đối Tượng Chuyển Động

    HTML5 Sprite Animation – Tạo Đối Tượng Chuyển Động

    Tạo đối tượng chuyển động là cần thiết trong mỗi game, nó làm cho trò chơi trở nên sinh động, bớt nhàm chán và tăng tính hấp dẫn cho người chơi. Vậy làm thế nào để tạo ...

    STDIO Warehouse

    25/08/2015

    Quản Lý Vị Trí Các Đối Tượng Trong Game

    Quản Lý Vị Trí Các Đối Tượng Trong Game

    Thông thường khi khởi tạo một đối tượng trong game, các lập trình viên đều khởi tạo một vị trí nào đó cho chúng. Điều đó có nghĩa là các đối tượng luôn tồn tại trong game ...

    Ryan Lê

    30/03/2015

    Design Pattern: Adapter Pattern

    Design Pattern: Adapter Pattern

    Trong lập trình hướng đối tượng, một khái niệm cực kì quan trọng đó là “giao diện của lớp” . Trong bài viết này sẽ đề cập đến một design pattern được sử dụng rất phổ biến ...

    Software ArchitectureDesign Patterns

    28/11/2014

    UI - Phần 1: Khái Niệm Và Một Số Đối Tượng Để Thiết Kế UI Trong Cocos2d-x 3.x.x

    UI - Phần 1: Khái Niệm Và Một Số Đối Tượng Để Thiết Kế UI Trong Cocos2d-x 3.x.x

    UI không chỉ có phục vụ trong ngành công nghiệp game mà còn phục vụ cho rất nhiều ngành công nghiệp khác. Một game có một UI và một ý tưởng tốt sẽ rất hấp dẫn người chơi. ...

    Trương Xuân Đạt

    23/01/2015

    Tích Hợp Thư Viện Vật Lý Box2D Vào Project C++

    Tích Hợp Thư Viện Vật Lý Box2D Vào Project C++

    Trong quá trình phát triển game, việc viết lại các thư viện mô phỏng vật lý sao cho giống đời thực thực sự là một việc không dễ dàng, vì kiến thức về vật lý cũng như thời ...

    Nguyễn Nghĩa

    10/11/2015

    C++11 - Smart Pointers - Quản Lý Tài Nguyên

    C++11 - Smart Pointers - Quản Lý Tài Nguyên

    Khi chương trình lớn dần lên, ta sẽ cần phải sử dụng các tài nguyên chia sẻ, và khi đó ta mới thật sự đối mặt với vấn đề về quản lý tài nguyên trong C++. Smart pointer sẽ ...

    Hoàng Tiến Đạt

    06/12/2014

    Box2D - Phần 1: Giới Thiệu Và Một Số Thuật Ngữ Và Khái Niệm

    Box2D - Phần 1: Giới Thiệu Và Một Số Thuật Ngữ Và Khái Niệm

    Giới thiệu về engine vật lý sử dụng trong games là Box2D. Giới thiệu Box2D, các khái niệm, cách thành phần liên quan, cách khởi tạo và thao tác với một số thành phần ...

    Trương Xuân Đạt

    23/01/2015

    Tư Tưởng Về Struct

    Tư Tưởng Về Struct

    Khái niệm struct đối với một lập trình viên hẳn không còn là một điều gì mới mẻ hay xa lạ nữa. Tuy nhiên, để "quen" và hiểu rõ được tư tưởng của nó thì không dễ dàng gì ...

    Ryan Lê

    23/01/2015

    template Trong C++

    template Trong C++

    Template là từ khóa trong C++, chúng ta có thể hiểu rằng là nó một kiểu dữ liệu trừu tượng, đặc trưng cho các kiểu dữ liệu cơ bản. Khi học về lập trình hướng đối tượng ...

    Trung Nguyễn

    21/09/2014

    Chuyển Đổi Scene Trong Unity

    Chuyển Đổi Scene Trong Unity

    Đối với đa số game hiện nay, có nhiều hơn một màn chơi hay cấp độ để thách thức người chơi, tăng tính thú vị của game. Vậy làm sao để khi chơi hết một màn ta có thể ...

    Lê Thắm

    03/04/2016

    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    [email protected]

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020