Là lưu trữ tất cả các đối tượng trò chơi trong một danh sách duy nhất một thiết kế có thể chấp nhận?


24

Đối với mọi trò chơi tôi đã thực hiện, cuối cùng tôi chỉ đặt tất cả các đối tượng trò chơi của mình (đạn, xe hơi, người chơi) vào một danh sách mảng duy nhất mà tôi lặp qua để vẽ và cập nhật. Mã cập nhật cho mỗi thực thể được lưu trữ trong lớp của nó.

Tôi đã tự hỏi, đây có phải là cách đúng đắn để làm mọi thứ, hay là một cách hiệu quả nhanh hơn?


4
Tôi nhận thấy bạn đã nói "danh sách mảng", giả sử đây là .Net, thay vào đó bạn nên nâng cấp lên Danh sách <T>. Nó gần như giống hệt nhau, ngoại trừ Danh sách <T> là loại mạnh và do đó, bạn sẽ không phải nhập cast mỗi khi bạn truy cập một phần tử. Đây là tài liệu: msdn.microsoft.com/en-us/l Library / 6sh2ey19.aspx
John McDonald

Câu trả lời:


26

Không có một cách đúng đắn. Những gì bạn đang mô tả công việc, và có lẽ đủ nhanh đến mức nó không thành vấn đề vì bạn dường như đang hỏi vì bạn quan tâm đến thiết kế chứ không phải vì nó gây ra vấn đề về hiệu suất. Vì vậy, nó là một cách đúng đắn, chắc chắn.

Bạn chắc chắn có thể làm điều đó một cách khác nhau, ví dụ như giữ một danh sách đồng nhất cho từng loại đối tượng hoặc bằng cách đảm bảo mọi thứ trong danh sách không đồng nhất của bạn được nhóm lại với nhau để bạn đã cải thiện tính liên kết cho mã trong bộ đệm hoặc cho phép cập nhật song song. Cho dù bạn có thấy bất kỳ cải thiện hiệu suất đáng kể nào bằng cách thực hiện điều này rất khó để nói mà không biết phạm vi và quy mô của trò chơi của bạn.

Bạn cũng có thể tiếp tục xác định trách nhiệm của các thực thể của mình - có vẻ như các thực thể vừa cập nhật và tự hiển thị vào lúc này - để tuân thủ tốt hơn Nguyên tắc Trách nhiệm duy nhất. Điều này có thể tăng khả năng bảo trì và tính linh hoạt của các giao diện riêng lẻ của bạn bằng cách tách chúng ra khỏi nhau. Nhưng một lần nữa, cho dù bạn có thấy lợi ích từ công việc làm thêm đòi hỏi tôi khó có thể nói tôi biết những gì tôi biết về các trò chơi của bạn (về cơ bản là không có gì).

Tôi khuyên bạn không nên nhấn mạnh quá nhiều về nó, và hãy nói rõ rằng đây có thể là một lĩnh vực tiềm năng để cải thiện nếu bạn nhận thấy các vấn đề về hiệu suất hoặc bảo trì.


16

Như những người khác đã nói, nếu nó đủ nhanh, thì nó đủ nhanh. Nếu bạn đang tự hỏi nếu có một cách tốt hơn, câu hỏi để tự hỏi mình là

Tôi có thấy mình lặp đi lặp lại qua tất cả các đối tượng nhưng chỉ hoạt động trên một tập hợp con của chúng không?

Nếu bạn làm như vậy, thì đó là một dấu hiệu cho thấy bạn có thể muốn có nhiều danh sách chỉ giữ các tham chiếu có liên quan. Chẳng hạn, nếu bạn đang lặp qua mọi đối tượng trò chơi để kết xuất chúng nhưng chỉ cần một tập hợp con của các đối tượng, thì có thể giữ một danh sách Kết xuất riêng cho chỉ những đối tượng cần được hiển thị.

Điều này sẽ cắt giảm số lượng đối tượng bạn lặp qua và bỏ qua các kiểm tra không cần thiết vì bạn đã biết tất cả các đối tượng trong danh sách sẽ được xử lý.

Bạn sẽ phải lập hồ sơ để xem nếu bạn đang đạt được nhiều hiệu suất.


Tôi đồng ý với điều này, tôi thường có các tập hợp con trong danh sách của mình cho các đối tượng cần được cập nhật từng khung và tôi đã xem xét thực hiện tương tự cho các đối tượng kết xuất. Tuy nhiên, khi các thuộc tính đó có thể thay đổi nhanh chóng, việc quản lý tất cả các danh sách có thể phức tạp. Khi và đối tượng chuyển từ không thể kết xuất thành không thể kết xuất hoặc có thể cập nhật thành không thể cập nhật và bây giờ bạn đang thêm hoặc xóa chúng khỏi nhiều danh sách cùng một lúc.
Nic Foster

8

Nó phụ thuộc gần như hoàn toàn vào nhu cầu trò chơi cụ thể của bạn . Nó có thể hoạt động hoàn hảo cho một trò chơi đơn giản, nhưng thất bại trên một hệ thống phức tạp hơn. Nếu nó hoạt động cho trò chơi của bạn, đừng lo lắng về nó hoặc cố gắng thiết kế nó cho đến khi có nhu cầu.

Về lý do tại sao cách tiếp cận đơn giản có thể thất bại trong một số tình huống, thật khó để tóm tắt rằng trong một bài viết, vì khả năng là vô tận và phụ thuộc hoàn toàn vào chính các trò chơi. Ví dụ, các câu trả lời khác đã đề cập rằng bạn có thể muốn nhóm các đối tượng chia sẻ trách nhiệm với nhau thành một danh sách riêng.

Đó là một trong những thay đổi phổ biến nhất bạn có thể thực hiện tùy thuộc vào thiết kế trò chơi của bạn. Tôi sẽ có cơ hội mô tả một vài ví dụ khác (phức tạp hơn), nhưng hãy nhớ rằng vẫn còn nhiều lý do và giải pháp khả thi khác.

Để bắt đầu, tôi sẽ chỉ ra rằng việc cập nhật các đối tượng trò chơi của bạn và hiển thị chúng có thể có các yêu cầu khác nhau trong một số trò chơi và do đó cần phải được xử lý riêng. Một số vấn đề có thể xảy ra với việc cập nhật và kết xuất các đối tượng trò chơi:

Cập nhật đối tượng trò chơi

Đây là một đoạn trích từ Game Engine Architecture mà tôi khuyên bạn nên đọc:

Với sự hiện diện của các phụ thuộc giữa các đối tượng, kỹ thuật cập nhật theo giai đoạn được mô tả ở trên phải được điều chỉnh một chút. Điều này là do sự phụ thuộc giữa các đối tượng có thể dẫn đến các quy tắc xung đột chi phối thứ tự cập nhật.

Nói cách khác, trong một số trò chơi, các đối tượng có thể phụ thuộc lẫn nhau và yêu cầu một thứ tự cập nhật cụ thể. Trong kịch bản này, bạn có thể cần phải tạo ra một cấu trúc phức tạp hơn một danh sách để lưu trữ các đối tượng và các phụ thuộc lẫn nhau của chúng.

nhập mô tả hình ảnh ở đây

Kết xuất đối tượng trò chơi

Trong một số trò chơi, cảnh và đối tượng tạo ra một hệ thống các nút cha-con và phải được hiển thị theo đúng thứ tự và với các biến đổi liên quan đến cha mẹ của chúng. Trong những trường hợp đó, bạn có thể cần một cấu trúc phức tạp hơn như biểu đồ cảnh (cấu trúc cây) thay vì một danh sách:

nhập mô tả hình ảnh ở đây

Ví dụ, các lý do khác có thể là sử dụng cấu trúc dữ liệu phân vùng không gian để sắp xếp các đối tượng của bạn theo cách cải thiện hiệu quả của việc thực hiện loại bỏ chế độ xem.


4

Câu trả lời là "có, điều này là tốt." Khi tôi làm việc trên các mô phỏng sắt lớn, nơi hiệu suất không thể thương lượng, tôi đã ngạc nhiên khi thấy mọi thứ trong một mảng toàn cầu lớn (BIG và FAT). Sau đó tôi làm việc trên một hệ thống OO và phải so sánh hai. Mặc dù nó rất xấu, nhưng phiên bản "một mảng để thống trị tất cả" trong C đơn giản cũ được biên dịch trong gỡ lỗi nhanh hơn nhiều lần so với hệ thống OO đa luồng "đẹp" được biên dịch -O2 trong C ++. Tôi nghĩ rằng một chút của nó đã được thực hiện với địa phương bộ đệm tuyệt vời (cả về không gian và thời gian) trong phiên bản mảng lớn.

Nếu bạn muốn hiệu suất, một mảng C lớn toàn cầu sẽ là giới hạn trên (từ kinh nghiệm).


2

Về cơ bản những gì bạn muốn làm là tạo danh sách khi bạn cần chúng. Nếu bạn vẽ lại tất cả các đối tượng địa hình trong một lần, một danh sách chỉ chúng có thể hữu ích. Nhưng để điều này có giá trị trong khi bạn cần hàng chục ngàn trong số chúng, và nhiều thứ 10.000 không phải là địa hình. Mặt khác, lướt qua danh sách mọi thứ của bạn và sử dụng "nếu" để kéo các đối tượng địa hình là đủ nhanh.

Tôi luôn cần một danh sách chính, bất kể tôi có bao nhiêu danh sách khác. Thông thường tôi đặt nó thành Bản đồ, hoặc bảng có khóa hoặc từ điển, để tôi có thể truy cập ngẫu nhiên các mục riêng lẻ. Nhưng một danh sách đơn giản thì đơn giản hơn nhiều; nếu bạn không truy cập ngẫu nhiên các đối tượng trò chơi, bạn không cần Bản đồ. Và việc tìm kiếm tuần tự thỉnh thoảng qua vài nghìn mục để tìm đúng mục sẽ không mất nhiều thời gian. Vì vậy, tôi muốn nói, bất cứ điều gì bạn làm, hãy lên kế hoạch để gắn bó với danh sách chính. Cân nhắc sử dụng Bản đồ nếu nó lớn. (Mặc dù nếu bạn có thể sử dụng chỉ mục thay vì khóa, bạn có thể gắn bó với mảng và có thứ gì đó tốt hơn Bản đồ. Tôi luôn kết thúc với những khoảng trống lớn giữa các số chỉ mục, vì vậy tôi đã phải từ bỏ nó.)

Một từ về mảng: chúng nhanh đến mức không thể tin được. Nhưng khi họ nhận được khoảng 100.000 yếu tố, họ bắt đầu cung cấp cho Bộ sưu tập rác. Nếu bạn có thể phân bổ không gian một lần và không chạm vào nó sau đó, tốt thôi. Nhưng nếu bạn liên tục mở rộng mảng, bạn sẽ liên tục phân bổ và giải phóng các khối bộ nhớ lớn và có khả năng khiến trò chơi của bạn bị treo trong vài giây và thậm chí không thành công với lỗi bộ nhớ.

Vì vậy, nhanh hơn và hiệu quả hơn? Chắc chắn rồi. Một bản đồ để truy cập ngẫu nhiên. Đối với các danh sách thực sự lớn, Danh sách được liên kết sẽ đánh bại một mảng nếu và khi bạn không cần truy cập ngẫu nhiên. Nhiều bản đồ / danh sách để sắp xếp các đối tượng theo nhiều cách khác nhau, khi bạn cần chúng. Bạn có thể đa luồng cập nhật.

Nhưng đó là tất cả rất nhiều công việc. Đó là mảng duy nhất là nhanh chóng và đơn giản. Bạn chỉ có thể thêm các danh sách và những thứ khác khi cần và có thể bạn sẽ không cần chúng. Chỉ cần lưu ý về những gì có thể đi sai nếu trò chơi của bạn trở nên lớn. Bạn có thể muốn có một phiên bản thử nghiệm với số lượng đối tượng gấp 10 lần so với phiên bản thực để cho bạn biết khi nào bạn gặp rắc rối. (Chỉ làm điều này nếu trò chơi của bạn đang trở nên lớn.) (Và đừng thử đa luồng mà không cần suy nghĩ nhiều. Mọi thứ khác đều dễ dàng để bắt đầu và bạn có thể làm nhiều hay ít tùy thích và lùi lại bất cứ lúc nào. Với đa luồng, bạn sẽ phải trả một cái giá lớn để tham gia, các khoản phí để ở lại rất cao và thật khó để thoát ra.)

Nói cách khác, hãy tiếp tục làm những gì bạn đang làm. Giới hạn lớn nhất của bạn có lẽ là thời gian của riêng bạn, và bạn đang tận dụng tốt nhất có thể.


Tôi thực sự không nghĩ rằng Danh sách được liên kết sẽ vượt trội hơn một mảng ở mọi kích thước, trừ khi bạn thường thêm hoặc xóa nội dung ở giữa.
Zan Lynx

@ZanLynx: Và thậm chí sau đó. Tôi nghĩ rằng tối ưu hóa thời gian chạy mới giúp mảng rất nhiều - không phải là họ cần nó. Nhưng nếu bạn phân bổ và giải phóng các khối lớn bộ nhớ liên tục và nhanh chóng, như có thể xảy ra với các mảng lớn, bạn có thể buộc bộ thu gom rác thành các nút. (Tổng dung lượng bộ nhớ cũng là một yếu tố ở đây. Vấn đề là chức năng kích thước của các khối, tần số miễn phí và phân bổ, và bộ nhớ khả dụng.) Lỗi xảy ra đột ngột, với chương trình bị treo hoặc bị treo. Thông thường có rất nhiều bộ nhớ miễn phí; GC không thể sử dụng nó. Danh sách liên kết tránh vấn đề này.
RalphChapin

Mặt khác, các danh sách được liên kết có khả năng cao hơn đáng kể để bỏ lỡ bộ đệm khi lặp lại do thực tế là các nút không liền kề trong bộ nhớ. Nó gần như không bị cắt và khô. Bạn có thể giảm bớt các vấn đề phân bổ lại bằng cách không làm điều đó (phân bổ trước nếu có thể, vì nó thường xảy ra) và bạn có thể giảm bớt các vấn đề liên kết bộ đệm trong danh sách được liên kết bằng cách phân bổ các nút (mặc dù bạn vẫn có chi phí theo tham chiếu , nó tương đối tầm thường).
Josh

Có, nhưng ngay cả trong một mảng, không phải bạn chỉ lưu trữ con trỏ vào các đối tượng sao? (Trừ khi đó là một mảng tồn tại ngắn cho một hệ thống hạt hoặc một cái gì đó.) Nếu vậy, vẫn sẽ có rất nhiều lỗi bộ nhớ cache.
Todd Lehman

1

Tôi đã sử dụng một phương pháp tương tự cho các trò chơi đơn giản. Lưu trữ mọi thứ trong một mảng là rất hữu ích khi các điều kiện sau là đúng:

  1. Bạn có một số lượng nhỏ hoặc tối đa các đối tượng trò chơi đã biết
  2. Bạn ủng hộ sự đơn giản hơn hiệu suất (nghĩa là: các cấu trúc dữ liệu được tối ưu hóa hơn như cây xanh không đáng để gặp rắc rối)

Trong mọi trường hợp, tôi đã triển khai giải pháp này dưới dạng một mảng có kích thước cố định có chứa một gameObject_ptrcấu trúc. Cấu trúc này chứa một con trỏ tới chính đối tượng trò chơi và giá trị boolean biểu thị cho dù đối tượng đó có "sống" hay không.

Mục đích của boolean là tăng tốc các thao tác tạo và xóa. Thay vì phá hủy các đối tượng trò chơi khi chúng bị xóa khỏi mô phỏng, tôi chỉ cần đặt cờ "còn sống" thành false.

Khi thực hiện tính toán trên các đối tượng, mã sẽ lặp qua mảng, thực hiện các tính toán trên các đối tượng được đánh dấu là "còn sống" và chỉ các đối tượng được đánh dấu "còn sống" được hiển thị.

Một tối ưu hóa đơn giản khác là tổ chức mảng theo loại đối tượng. Tất cả các viên đạn, ví dụ, có thể sống trong n ô cuối cùng của mảng. Sau đó, bằng cách lưu trữ một int với giá trị của chỉ mục hoặc một con trỏ tới phần tử mảng, các loại cụ thể có thể được tham chiếu với chi phí giảm.

Không nên nói rằng giải pháp này không tốt cho các trò chơi có lượng dữ liệu lớn, vì mảng sẽ lớn không thể chấp nhận được. Nó cũng không phù hợp với các trò chơi bị hạn chế bộ nhớ, cấm các đối tượng không sử dụng chiếm bộ nhớ. Điểm mấu chốt là nếu bạn có giới hạn trên đã biết về số lượng đối tượng trò chơi bạn sẽ có tại một thời điểm, không có gì sai khi lưu trữ chúng trong một mảng tĩnh.

Đây là bài viết đầu tiên của tôi! Tôi hy vọng nó trả lời câu hỏi của bạn!


bài viết đầu tiên! vâng
Tili

0

Nó được chấp nhận, mặc dù không bình thường. Các thuật ngữ như "nhanh hơn" và "hiệu quả hơn" là tương đối; thông thường, bạn sẽ sắp xếp các đối tượng trò chơi thành các danh mục riêng biệt (vì vậy một danh sách cho đạn, một danh sách cho kẻ thù, v.v.) để làm cho chương trình hoạt động có tổ chức hơn (và do đó hiệu quả hơn) nhưng không giống như máy tính sẽ lặp qua tất cả các đối tượng nhanh hơn .


2
Thực sự đủ về hầu hết các trò chơi XNA, nhưng việc tổ chức các đối tượng của bạn thực sự có thể khiến việc lặp lại qua chúng nhanh hơn: các đối tượng cùng loại có nhiều khả năng đạt được bộ đệm dữ liệu & lệnh của CPU. Bộ nhớ cache rất tốn kém, đặc biệt là trên bảng điều khiển. Ví dụ, trên Xbox 360, việc bỏ lỡ bộ đệm L2 sẽ khiến bạn mất ~ 600 chu kỳ. Đó là để lại rất nhiều hiệu suất trên bàn. Đây là một nơi mà mã được quản lý có lợi thế hơn mã gốc: các đối tượng được phân bổ gần nhau theo thời gian cũng sẽ đóng trong bộ nhớ và tình hình được cải thiện với bộ sưu tập rác (nén).
Lập trình viên trò chơi mãn tính

0

Bạn nói mảng đơn và đối tượng.

Vì vậy (không có mã của bạn để xem) Tôi đoán tất cả chúng đều liên quan đến 'uberGameObject'.

Vì vậy, có thể hai vấn đề.

1) Bạn có thể có một đối tượng 'thần' - thực hiện hàng tấn công cụ, không thực sự được phân chia theo những gì nó làm hoặc là.

2) Bạn lặp qua danh sách này và truyền để gõ? hoặc unboxing mỗi. Điều này có một hình phạt hiệu suất lớn.

xem xét việc chia các loại khác nhau (đạn, kẻ xấu, v.v.) vào danh sách đánh máy mạnh của riêng họ.

List<BadGuyObj> BadGuys = new List<BadGuyObj>();
List<BulletsObj> Bullets = new List<BulletObj>();

 //Game 
OnUpdate(gameticks gt)
{  foreach (BulletObj thisbullet in Bullets) {thisbullet.Update();}  // much quicker.

Không cần thực hiện truyền kiểu nếu có một số loại cơ sở hoặc giao diện chung có tất cả các phương thức sẽ được gọi trong vòng cập nhật (ví dụ update():).
bummzack

0

Tôi có thể đề xuất một số thỏa hiệp giữa danh sách đơn chung và danh sách riêng cho từng loại đối tượng.

Giữ tham chiếu đến một đối tượng trong nhiều danh sách. Những danh sách này được xác định bởi các hành vi cơ sở. Ví dụ, MarineSoldierđối tượng sẽ được tham chiếu trong PhysicalObjList, GraphicalObjList, UserControlObjList, HumanObjList. Vì vậy, khi bạn xử lý vật lý, bạn lặp đi lặp lại PhysicalObjList, khi quá trình gây sát thương từ chất độc - HumanObjList, nếu Lính chết - loại bỏ nó khỏi HumanObjListnhưng tiếp tục PhysicalObjList. Để làm cho cơ chế này hoạt động, bạn cần tổ chức tự động chèn / xóa đối tượng khi nó được tạo / xóa.

Ưu điểm của phương pháp:

  • đánh máy tổ chức các đối tượng (không AllObjectsListcó đúc trên cập nhật)
  • danh sách dựa trên logic, không phải trên các lớp đối tượng
  • không có vấn đề với các đối tượng có khả năng kết hợp ( MegaAirHumanUnderwaterObjectsẽ được chèn vào danh sách tương ứng thay vì tạo danh sách mới cho loại của nó)

PS: Đối với việc tự cập nhật, bạn sẽ gặp vấn đề khi nhiều đối tượng đang tương tác và mỗi đối tượng phụ thuộc vào người khác. Để giải quyết điều này, chúng ta cần tác động đến đối tượng bên ngoài, không phải bên trong tự cập nhật.

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.