Thành phần OOP nặng so với các hệ thống thành phần thực thể thuần túy? [đóng cửa]


13

Tôi thừa nhận, tôi đã phạm tội lạm dụng, và thậm chí lạm dụng quyền thừa kế. Dự án trò chơi (văn bản) đầu tiên mà tôi đã thực hiện khi tham gia khóa học OOP của mình đã đi xa như "Cửa bị khóa" và "Cửa không khóa" từ "Cửa" và "Phòng có một cửa", "Phòng có hai cửa" và cứ thế từ "Phòng".

Với trò chơi (đồ họa) mà tôi đã làm việc gần đây, tôi nghĩ rằng tôi đã học được bài học của mình và đặt giới hạn cho việc sử dụng tính kế thừa. Tuy nhiên tôi nhận thấy những vấn đề sớm bắt đầu xuất hiện. Lớp gốc của tôi bắt đầu nở rộ ngày càng nhiều, và các lớp lá của tôi chứa đầy các mã trùng lặp.

Tôi nghĩ rằng tôi vẫn đang làm sai, và sau khi tìm kiếm nó trực tuyến, tôi phát hiện ra rằng tôi không phải là người duy nhất gặp vấn đề này. Đó là cách tôi kết thúc việc khám phá các hệ thống Thực thể sau một số nghiên cứu kỹ lưỡng (đọc: googlefu)

Khi tôi bắt đầu đọc nó, tôi có thể thấy rõ ràng nó có thể giải quyết các vấn đề tôi gặp phải như thế nào với hệ thống phân cấp OOP truyền thống với các thành phần. Đây là trong các bài đọc đầu tiên tuy nhiên. Khi tôi tình cờ tìm thấy nhiều cách tiếp cận cực đoan khác của ES ES, chẳng hạn như phương pháp tại T-machine .

Tôi bắt đầu không đồng ý với các phương pháp họ đang sử dụng. Một hệ thống thành phần thuần túy dường như quá mức cần thiết, hoặc đúng hơn là không trực quan, có lẽ là thế mạnh của OOP. Tác giả đã đi xa đến mức nói rằng hệ thống ES trái ngược với OOP và trong khi nó có thể sử dụng được dọc theo OOP, thì nó thực sự không nên. Tôi không nói là sai, nhưng tôi không cảm thấy đó là một giải pháp tôi muốn thực hiện.

Vì vậy, với tôi, và để giải quyết các vấn đề tôi gặp phải ở đầu bài, mà không đi ngược lại với trực giác của tôi, vẫn là sử dụng một hệ thống phân cấp, tuy nhiên nó sẽ không phải là một hệ thống phân cấp nguyên khối như những gì tôi đã sử dụng trước đây, mà là một từ đa hình (tôi không thể tìm thấy một từ đối diện với nguyên khối), bao gồm một số cây nhỏ hơn.

Ví dụ sau đây cho thấy điều tôi muốn nói (điều này được lấy cảm hứng từ một ví dụ tôi tìm thấy trong Game Engine Architecture, Chương 14).

Tôi sẽ có một cây nhỏ cho xe cộ. Lớp xe gốc sẽ có thành phần kết xuất, thành phần va chạm, thành phần vị trí, v.v.

Sau đó, một chiếc xe tăng, một lớp con của phương tiện sẽ kế thừa những thành phần đó từ nó và được cung cấp thành phần "pháo" của riêng nó.

Các nhân vật cũng vậy. Một nhân vật sẽ có các thành phần riêng, sau đó Lớp Người chơi sẽ kế thừa nó và được cấp một Bộ điều khiển đầu vào, trong khi các lớp kẻ thù khác sẽ thừa hưởng từ lớp Nhân vật và được cấp một bộ điều khiển AI.

Tôi không thực sự thấy bất kỳ vấn đề với thiết kế này. Mặc dù không sử dụng Hệ thống điều khiển thực thể thuần túy, nhưng vấn đề với hiệu ứng sủi bọt và lớp gốc lớn được giải quyết bằng cách sử dụng hệ thống phân cấp nhiều cây, và vấn đề về các lá trùng lặp mã nặng nề đã biến mất vì các lá không có bất kỳ mã nào để bắt đầu, chỉ cần các thành phần. Nếu một thay đổi cần được thực hiện ở cấp độ lá, thì đơn giản như thay đổi một thành phần duy nhất, thay vì sao chép dán mã ở mọi nơi.

Tất nhiên, là người thiếu kinh nghiệm như tôi, tôi không thấy có vấn đề gì khi lần đầu tiên tôi bắt đầu sử dụng mô hình nặng phân cấp, kế thừa duy nhất, vì vậy nếu có vấn đề với mô hình mà tôi hiện đang nghĩ đến việc thực hiện, tôi sẽ không có thể nhìn thấy nó

Ý kiến ​​của bạn?

PS: Tôi đang sử dụng Java, vì vậy sử dụng nhiều kế thừa để thực hiện điều này thay vì sử dụng các thành phần thông thường là không thể.

PPS: Truyền thông liên thành phần sẽ được thực hiện bằng cách liên kết các thành phần phụ thuộc với nhau. Điều này sẽ dẫn đến khớp nối, nhưng tôi nghĩ đó là một sự đánh đổi ok.


2
Tất cả điều này có vẻ tốt với tôi. Có một ví dụ cụ thể trong hệ thống phân cấp hoặc thực thể của bạn "có mùi" với bạn không? Cuối cùng, tất cả là về việc làm cho trò chơi được thực hiện nhiều hơn một chút cảm giác thuần khiết.
drhayes

Tôi không, nhưng như tôi đã nói, tôi hầu như không có kinh nghiệm và tôi không phải là thẩm phán tốt nhất cho việc này.
Midori Ryuu

3
Tôi đã bỏ phiếu để đóng câu hỏi này vì nó chủ quan và mở cho cuộc thảo luận rộng. Tôi đánh giá cao nó đã được hỏi từ một vị trí chân thành - nhưng chọn cách tiến hành ở đây hoàn toàn là vấn đề quan điểm cá nhân. Nhược điểm thực sự duy nhất của việc sửa các thành phần của bạn thành một lớp con là việc sắp xếp các thành phần không thể thay đổi trong thời gian chạy - nhưng điều đó chắc chắn phải rõ ràng với bạn và đó là lựa chọn bạn đưa ra.
Kylotan

4
Tôi đã bỏ phiếu để đóng quá. Đó là một câu hỏi hay và được viết tốt, nhưng không phù hợp với GDSE. Bạn có thể thử đăng lại bài này tại GameDev.net.
Sean Middleditch

Như đã viết, với câu hỏi là 'Ý kiến ​​của bạn?', Nó thực sự là kết thúc mở và không thể trả lời được. Tuy nhiên, thể trả lời được nếu bạn trả lời nó như thể câu hỏi là 'Có bẫy nào mà tôi có thể rơi vào mà không nhận ra không?' (Ít nhất, nếu bạn nghĩ rằng trên thực tế có bất kỳ sự khác biệt thực nghiệm nào giữa các kiến ​​trúc khác nhau.)
John Calsbeek

Câu trả lời:


8

Xem xét ví dụ này:

Bạn đang thực hiện một RTS. Để phù hợp với logic hoàn chỉnh, bạn quyết định tạo một lớp cơ sở GameObjectvà sau đó là hai lớp con BuildingUnit. Điều này hoạt động tốt, tất nhiên, và bạn kết thúc với một cái gì đó trông như thế này:

  • GameObject
    • ModelComponent
    • CollisionComponent
    • Giáo dục
  • Building
    • ProductionComponent
    • Giáo dục
  • Unit
    • AimingAIComponent
    • WeaponComponent
    • ParticleEmitterComponent
    • AnimationComponent
    • Giáo dục

Bây giờ mỗi lớp con của Unitbạn thực hiện đã có tất cả các thành phần bạn cần. Và như một phần thưởng, bạn có một nơi duy nhất để đặt tất cả mã thiết lập tẻ nhạt đó newlên WeaponComponent, kết nối nó với một AimingAIComponentvà mộtParticleEmitterComponent Người sáng suốt!

Và tất nhiên, bởi vì nó vẫn đưa các yếu tố logic vào các thành phần, khi cuối cùng bạn quyết định rằng bạn muốn thêm một Tower lớp đó là một tòa nhà nhưng có vũ khí, bạn có thể quản lý nó. Bạn có thể thêm một AimingAIComponent, a WeaponComponentvà a ParticleEmitterComponentvào lớp con của bạn Building. Tất nhiên, sau đó bạn phải đi qua và đào mã khởi tạo từ Unitlớp và đặt nó ở một nơi khác, nhưng đó không phải là mất mát lớn.

Và bây giờ, tùy thuộc vào mức độ tầm nhìn xa của bạn, bạn có thể hoặc không thể kết thúc với các lỗi tinh vi. Hóa ra có thể có một số mã ở nơi khác trong trò chơi giống như thế này:

if (myGameObject instanceof Unit)
    doSomething(((Unit)myGameObject).getWeaponComponent());

Điều đó âm thầm không làm việc cho bạn Tower tòa nhà , mặc dù bạn thực sự muốn nó hoạt động cho bất cứ thứ gì có vũ khí. Bây giờ nó phải trông giống như thế này:

WeaponComponent weapon = myGameObject.getComponent(WeaponComponent.class);
if (weapon)
    doSomething(weapon);

Tốt hơn nhiều! Nó xảy ra với bạn sau khi thay đổi điều này rằng bất kỳ phương thức truy cập nào bạn đã viết có thể kết thúc trong tình huống này. Vì vậy, điều an toàn nhất để làm là loại bỏ tất cả chúng, và truy cập mọi thành phần thông qua một getComponentphương thức này, có thể hoạt động với bất kỳ lớp con nào. (Cách khác, bạn có thể thêm từng loại get*Component()cho siêu lớp GameObjectvà ghi đè chúng trong các lớp con để trả về thành phần. Tuy nhiên, tôi sẽ giả sử loại đầu tiên.)

Bây giờ của bạn BuildingUnitcác lớp có khá nhiều chỉ là các túi linh kiện, thậm chí không có người truy cập vào chúng. Và cũng không có khởi tạo ưa thích nào, bởi vì hầu hết những thứ đó cần phải được kéo ra để tránh trùng lặp mã với một đối tượng khác trong trường hợp đặc biệt của bạn ở đâu đó.

Nếu bạn làm theo điều này đến mức cực đoan, bạn sẽ luôn khiến các lớp con của bạn hoàn toàn trơ các túi linh kiện, tại thời điểm đó thậm chí không có điểm nào có các lớp con xung quanh. Bạn có thể đạt được gần như tất cả lợi ích của việc có một hệ thống phân cấp lớp với hệ thống phân cấp phương thức tạo ra các thành phần thích hợp. Thay vì có một Unitlớp, bạn có một addUnitComponentsphương thức thực hiện AnimationComponentvà cũng gọi addWeaponComponents, tạo ra một AimingAIComponent, a WeaponComponentvà a ParticleEmitterComponent. Kết quả là bạn có tất cả các lợi ích của một hệ thống thực thể, tất cả việc sử dụng lại mã của một hệ thống phân cấp và không có sự cám dỗ nào để kiểm tra instanceofhoặc chuyển sang loại lớp của bạn.


Nói hay lắm. Thêm vào điều này tôi nghĩ giải pháp lý tưởng là khởi tạo mỗi thực thể bằng một tập lệnh hoặc với các tệp mô tả. Tôi sử dụng tập tin văn bản để khởi tạo thực thể. Nó nhanh và cho phép các lập trình viên không kiểm tra các thông số khác nhau.
Chó sói

Wow đọc bài viết của bạn đã cho tôi một cơn ác mộng! Tất nhiên, tôi có nghĩa là theo một cách tốt, nhìn thấy khi bạn mở mắt ra cơn ác mộng mà tôi có thể kết thúc! Tôi ước tôi có thể trả lời nhiều hơn cho câu trả lời chi tiết như vậy, nhưng thực sự không có gì nhiều để thêm vào!
Midori Ryuu

Trên một cách đơn giản hơn. Bạn kết thúc sử dụng Kế thừa như mô hình Nhà máy của người nghèo.
Zardoz89

2

Thành phần trên Kế thừa là thuật ngữ OOP (ít nhất là cách tôi đã học / được dạy nó). Để chọn giữa thành phần và thừa kế, bạn nói to "Xe là một loại bánh xe" (thừa kế) và "Xe có bánh xe" (thành phần). Một âm thanh phải chính xác hơn âm thanh khác (sáng tác trong trường hợp này) và nếu bạn không thể quyết định, mặc định thành phần cho đến khi bạn quyết định khác.


1

Khi nói đến việc tích hợp các hệ thống dựa trên các thành phần cho các trò chơi hiện có dựa trên các đối tượng trò chơi, có một bài viết về lập trình cao bồi http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ có thể cung cấp cho bạn một số gợi ý về cách chuyển đổi có thể được thực hiện.

Đối với các vấn đề, mô hình của bạn sẽ vẫn cần xử lý người chơi khác với kẻ thù khác. Bạn sẽ không thể chuyển trạng thái (tức là người chơi bị kiểm soát AI) khi người chơi ngắt kết nối hoặc trong trường hợp đặc biệt mà không xử lý anh ta khác với các nhân vật khác.

Ngoài việc tái sử dụng nhiều các thành phần trên các thực thể khác nhau, ý tưởng về các trò chơi dựa trên thành phần là tính đồng nhất của toàn bộ hệ thống. Mỗi thực thể có các thành phần có thể được gắn và tách ra theo ý muốn. Tất cả các thành phần cùng loại được xử lý chính xác theo cùng một cách với một tập hợp các cuộc gọi được thiết lập bởi giao diện (thành phần cơ sở) của từng loại (vị trí, kết xuất, tập lệnh ...)

Kết hợp kế thừa đối tượng trò chơi với cách tiếp cận dựa trên thành phần mang lại cho bạn một số lợi thế của cách tiếp cận dựa trên thành phần với nhược điểm từ cả hai cách tiếp cận.

Nó có thể làm việc cho bạn. Nhưng tôi không trộn lẫn cả bên ngoài giai đoạn chuyển tiếp từ kiến ​​trúc này sang kiến ​​trúc khác.

PS được viết trên điện thoại để tôi có một thời gian khó liên kết với các tài nguyên bên ngoài.


Cảm ơn bạn đã trả lời mặc dù bạn đã nghe điện thoại! Tôi hiểu ý của bạn Các thành phần dựa trên di chuyển là, tôi càng linh hoạt để thay đổi mọi thứ. Đó là lý do tại sao tôi sẽ tiếp tục và thử một cách tiếp cận sáng tác với sự kế thừa tối thiểu. (Thậm chí nhiều hơn những gì tôi đề xuất.)
Midori Ryuu

@ REoriRyuu tránh tất cả sự kế thừa trong các đối tượng thực thể. Bạn đủ may mắn để sử dụng một ngôn ngữ có tích hợp phản chiếu (và hướng nội). Bạn có thể sử dụng các tệp (txt hoặc XML) để khởi tạo các đối tượng của mình với các tham số phù hợp. Nó không quá nhiều công việc và nó sẽ cho phép cải tiến trò chơi tinh chỉnh mà không cần biên dịch lại. Nhưng hãy giữ lớp Entity, ít nhất là cho giao tiếp giữa các thành phần và các tác vụ tiện ích khác. PS vẫn còn trên điện thoại :(
Coyote
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.