Loại bỏ hiệu quả các đối tượng ngoài màn hình trên bản đồ 2D từ trên xuống


8

Tôi biết hiệu quả là chìa khóa trong lập trình trò chơi và tôi đã có một số kinh nghiệm với việc hiển thị "bản đồ" trước đó nhưng có lẽ không phải là cách tốt nhất.

Đối với trò chơi TopDown 2D: (chỉ cần hiển thị họa tiết / gạch của thế giới, không có gì khác)

Giả sử, bạn có bản đồ 1000x1000 (gạch hoặc bất cứ thứ gì). Nếu ô không nằm trong tầm nhìn của máy ảnh, nó không nên được hiển thị - đơn giản vậy thôi. Không cần phải hiển thị một lát sẽ không được nhìn thấy. Nhưng vì bạn có 1000x1000 đối tượng trong bản đồ của mình, hoặc có lẽ ít hơn bạn có thể không muốn lặp qua tất cả các ô 1000 * 1000 chỉ để xem liệu chúng có giả sử được hiển thị hay không.

Câu hỏi: Cách tốt nhất để thực hiện hiệu quả này là gì? Vì vậy, nó "nhanh / nhanh hơn" có thể xác định gạch nào được giả sử sẽ được hiển thị?

Ngoài ra, tôi không xây dựng trò chơi của mình xung quanh các ô được hiển thị bằng SpriteBatch để không có hình chữ nhật, các hình có thể có kích thước khác nhau và có nhiều điểm, giả sử một vật cong 10 điểm và kết cấu bên trong hình đó;

Câu hỏi: Làm thế nào để bạn xác định xem loại đối tượng này có "bên trong" Chế độ xem của máy ảnh không?

Thật dễ dàng với hình chữ nhật 48x48, chỉ cần xem nó X + Width hay Y + height nằm trong chế độ xem của máy ảnh. Khác nhau với nhiều điểm.

Nói một cách đơn giản, làm thế nào để quản lý mã và dữ liệu một cách hiệu quả để không phải chạy qua / lặp qua hàng triệu đối tượng cùng một lúc.

Câu trả lời:


6

Đối với các đối tượng phức tạp, tôi nghĩ cách tốt nhất là giảm chúng xuống các hình chữ nhật xung quanh và kiểm tra xem hình chữ nhật đó có nằm trong khung nhìn không. Ngay cả khi bạn sẽ kết xuất một kết cấu không thực sự hiển thị (vì hình dạng của nó), nó vẫn có thể sẽ nhanh hơn so với thực hiện thuật toán phát hiện phức tạp hơn.

Để xử lý các bản đồ lớn một cách hiệu quả, bạn nên chia nhỏ bản đồ của mình ở tỷ lệ lớn hơn, giả sử là 10x10. Sau đó, bạn kiểm tra giao lộ khung nhìn của bạn. Trong trường hợp xấu nhất, nó chạm 4 'vùng' này sẽ dẫn đến (100x100) * 4 = 40K đối tượng. Đây là một ví dụ đơn giản. Để sử dụng thực tế, bạn nên xem xét cấu trúc Quadtree đặc biệt hiệu quả đối với các phân khu và phát hiện va chạm như vậy (kiểm tra khả năng hiển thị của chế độ xem về cơ bản là kiểm tra va chạm giữa chế độ xem và sprite).


Sử dụng Quadtree cho các ô bản đồ là một chút quá mức ... vì bạn chỉ có thể tính toán các chỉ mục chính xác của các ô cần được hiển thị. Và bên cạnh đó, các vùng đơn giản hơn và tôi khuyên bạn nên sử dụng chúng cho vòng tối ưu hóa đầu tiên. Nó sẽ giúp hiểu và sử dụng tứ giác sau này :) +1.
Liosan

@Liosan không rõ ràng nếu câu hỏi 'gạch' này có cùng kích thước, nếu không giải pháp sẽ khá tầm thường, tất nhiên.
Petr Abdulin

bạn nói đúng, Deukalion thậm chí đã viết bình luận cho một câu trả lời khác với nội dung 'Và một viên gạch không phải lúc nào cũng có cùng kích thước'.
Liosan

QuadTree thậm chí có hoạt động với bất cứ thứ gì ngoại trừ kích thước vùng chính xác không? Mỗi khu vực không nên có kích thước hình chữ nhật vì đối tượng bên trong hình chữ nhật không có hình chữ nhật. Vì vậy, nó sẽ KHÔNG phải là một lưới pixel 1024x1024 được hiển thị, hình dạng của nó có thể rất chính thống.
Deukalion

Chà, tôi đoán là không (bản thân tôi chưa thực sự sử dụng tứ giác), nhưng điều đó không thực sự quan trọng nếu bạn có thể đặt mọi thứ vào khu vực hình chữ nhật xung quanh. Dù sao, nếu quadtree không phù hợp với nhiệm vụ của bạn vì một số lý do, rất có thể bạn cần sử dụng cách tiếp cận tương tự.
Petr Abdulin

3

Khi bạn có nhiều đối tượng di động, bạn nên lưu trữ chúng theo tọa độ của chúng trong cấu trúc cây đa chiều. Bằng cách đó bạn có thể có được một danh sách tất cả các đối tượng nằm trong một hình chữ nhật nhất định. Bạn thậm chí có thể sắp xếp chúng theo tọa độ x hoặc y của chúng, điều này rất quan trọng đối với thứ tự vẽ khi đối tượng mọc chồng lên nhau.

Điều này cũng sẽ rất hữu ích để phát hiện va chạm.

Xem bài viết trên wikipedia về cây kd để biết chi tiết.

Khi cây 2d quá phức tạp đối với bạn, đó cũng là một cách thay thế dễ dàng hơn nhưng không kém hiệu quả: Lưu trữ các đối tượng như con của gạch. Khi bạn di chuyển một đối tượng, bạn xóa nó khỏi danh sách đối tượng của ô cũ và đặt nó vào danh sách đối tượng của đối tượng mới. Khi bạn vẽ các đối tượng, bạn một lần nữa lặp lại các ô trong khung nhìn và truy xuất các đối tượng của chúng. Sau đó, bạn sắp xếp tất cả chúng theo tọa độ y và vẽ chúng.


1

Không biết đó có phải là cách tốt nhất không, nhưng đây là cách tôi học để làm điều đó:

bạn có một mảng "gạch" hai chiều

public Tile tilemap[][];

và bạn quyết định vị trí của "máy ảnh" với Vector2, bạn sẽ chỉ hiển thị những gì bên trong cảnh, hình chữ nhật lớn là những gì bạn có thể nhìn thấy trên màn hình, vô dụng để vẽ phần còn lại của cảnh.

Bây giờ bạn cần lấy phần bù, giả sử bạn muốn máy ảnh của mình ở trung tâm của cảnh:

offsetX = (graphics().width() / 2 - Math.round(cam.Position().X));
offsetX = Math.min(offsetX, 0);
offsetX = Math.max(offsetX, graphics().width() / 2 - mapWidth);
offsetY = (graphics().height()) / 2 - Math.round(cam.getPosition().Y);
offsetY = Math.min(offsetY, 0);
offsetY = Math.max((graphics().height() / 2 - mapHeight), offsetY);

Bây giờ, trong phần nào của mảng, các ô hiển thị bắt đầu và kết thúc?

firstTileX = pixelsToTiles(-offsetX);

lastTileX = firstTileX + pixelsToTiles(graphics().width());

firstTileY = pixelsToTiles(-offsetY);

lastTileY = firstTileY + pixelsToTiles(graphics().height());

int pixelsToTiles(int pixels) {
    return (int) Math.floor((float) pixels / Tile.getHeight());
}

và trong phương thức vẽ của bạn, bạn chỉ cần lặp qua phần hiển thị của mảng:

   for (int x = firstTileX; x < lastTileX; x++) {
        for (int y = firstTileY; y < lastTileY; y++) {
              Vector2 position = new Vector2(tilesToPixelsX(x) + offsetX,
                        tilesToPixelsY(y) + offsetY);
              tilemap[x][y].Draw(surf, position);
        }
    }

1
Vâng, nhưng gạch là một ví dụ để đơn giản hóa mọi thứ. Tôi đã viết rằng tôi hiểu quá trình xác định xem một đối tượng đã ở trong "chế độ xem" với hình chữ nhật / tiêu đề, không phải với hình dạng nâng cao hơn có nhiều điểm. Ngoài ra, tôi đã tìm kiếm thứ gì đó khiến tôi "không" trải qua tất cả các ô trong mỗi phương thức Update () trong XNA. Nếu tôi có bản đồ "LỚN", với khoảng 10000 đối tượng (hình dạng từ 3 điểm trở lên) thì đây không phải là cách để làm điều đó. Mỗi lần cập nhật tôi phải chạy một vòng 10000 cập nhật và tính toán nhiều như vậy. Tôi không sử dụng gạch và điều này không hiệu quả; Tôi đã từng ở đó.
Deukalion

Và một lát không phải luôn luôn có cùng kích thước, vì vậy tôi cũng không thể làm điều đó với nó. Tôi không sử dụng hình chữ nhật.
Deukalion

Nói một cách đơn giản: Tôi chỉ muốn lặp qua các đối tượng NÊN được hiển thị, không lặp qua các đối tượng không nên.
Deukalion

@Deukalion Mã của Riktothepast chỉ lặp qua các ô xuất hiện trong hộp giới hạn của màn hình (mặc dù điều này không rõ ràng lắm). Kỹ thuật cơ bản tương tự có thể được sử dụng để lặp qua bất kỳ hình chữ nhật nào của gạch trong một tập hợp tọa độ đã cho.
DampeS8N

0

Bạn có thể có một Bitmap là toàn bộ cảnh nhưng không được hiển thị. Và sau đó, một Bitmap lớp máy ảnh có kích thước màn hình được hiển thị, chỉ rút ra từ toàn cảnh nhưng chỉ là phần cần được hiển thị.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.