Làm thế nào các đối tượng trò chơi nên nhận thức về nhau?


18

Tôi thấy khó tìm cách tổ chức các đối tượng trò chơi sao cho chúng đa hình nhưng đồng thời không đa hình.

Đây là một ví dụ: giả sử rằng chúng ta muốn tất cả các đối tượng của mình update()draw(). Để làm được điều đó, chúng ta cần định nghĩa một lớp cơ sở GameObjectcó hai phương thức thuần ảo đó và để đa hình khởi động:

class World {
private:
    std::vector<GameObject*> objects;
public:
    // ...
    update() {
        for (auto& o : objects) o->update();
        for (auto& o : objects) o->draw(window);
    }
};

Phương thức cập nhật được cho là quan tâm đến bất kỳ trạng thái nào mà đối tượng lớp cụ thể cần cập nhật. Thực tế là mỗi đối tượng cần biết về thế giới xung quanh. Ví dụ:

  • Của tôi cần phải biết nếu ai đó đang va chạm với nó
  • Một người lính nên biết nếu một người lính khác ở gần
  • Một zombie nên biết bộ não gần nhất, trong bán kính, ở đâu

Đối với các tương tác thụ động (như lần đầu tiên) tôi đã nghĩ rằng việc phát hiện va chạm có thể ủy thác những việc cần làm trong các trường hợp va chạm cụ thể với chính đối tượng với a on_collide(GameObject*).

Hầu hết các thông tin khác (như hai ví dụ khác) có thể được truy vấn bởi thế giới trò chơi được truyền cho updatephương thức này. Bây giờ thế giới không phân biệt các đối tượng dựa trên loại của chúng (nó lưu trữ tất cả các đối tượng trong một thùng chứa đa hình duy nhất), vì vậy thực tế nó sẽ trở lại với một lý tưởng world.entities_in(center, radius)là một thùng chứa GameObject*. Nhưng tất nhiên, người lính không muốn tấn công những người lính khác trong đội của mình và một thây ma không nói về những thây ma khác. Vì vậy chúng ta cần phân biệt hành vi. Một giải pháp có thể là như sau:

void TeamASoldier::update(const World& world) {
    auto list = world.entities_in(position, eye_sight);
    for (const auto& e : list)
        if (auto enemy = dynamic_cast<TeamBSoldier*>(e))
            // shoot towards enemy
}

void Zombie::update(const World& world) {
    auto list = world.entities_in(position, eye_sight);
    for (const auto& e : list)
        if (auto enemy = dynamic_cast<Human*>(e))
            // go and eat brain
}

nhưng tất nhiên số lượng dynamic_cast<>mỗi khung hình có thể cao khủng khiếp và tất cả chúng ta đều biết tốc độ dynamic_castcó thể chậm như thế nào . Vấn đề tương tự cũng áp dụng cho on_collide(GameObject*)đại biểu mà chúng ta đã thảo luận trước đó.

Vì vậy, cách lý tưởng để tổ chức mã để các đối tượng có thể nhận biết các đối tượng khác và có thể bỏ qua chúng hoặc thực hiện các hành động dựa trên loại của chúng là gì?


1
Tôi nghĩ rằng bạn đang tìm kiếm một triển khai tùy chỉnh C ++ RTTI linh hoạt. Tuy nhiên, câu hỏi của bạn dường như không chỉ là về các cơ chế RTTI hợp lý. Hầu hết mọi thứ bạn yêu cầu đều được yêu cầu bởi hầu hết mọi phần mềm trung gian mà trò chơi sẽ sử dụng (hệ thống hoạt hình, vật lý để đặt tên cho một số). Tùy thuộc vào danh sách các truy vấn được hỗ trợ, bạn có thể gian lận trong RTTI bằng cách sử dụng ID và chỉ mục trong mảng hoặc cuối cùng bạn sẽ thiết kế một giao thức chính thức để hỗ trợ các lựa chọn thay thế rẻ hơn cho Dynamic_cast và type_info.
teodron

Tôi khuyên bạn không nên sử dụng hệ thống loại cho logic trò chơi. Ví dụ, thay vì phụ thuộc vào kết quả của dynamic_cast<Human*>, hãy thực hiện một cái gì đó như a bool GameObject::IsHuman(), trả về falsetheo mặc định nhưng bị ghi đè để trả về truetrong Humanlớp.
congusbongus

một bổ sung: bạn gần như không bao giờ gửi một tấn đối tượng cho nhau thực thể có thể quan tâm đến chúng. Đó là một tối ưu hóa rõ ràng bạn sẽ phải thực sự xem xét.
teodron

@congusbongus Sử dụng phần IsAghi đè vtable và tùy chỉnh được chứng minh là chỉ tốt hơn một chút so với đúc động trong thực tế đối với tôi. Điều tốt nhất để làm là cho người dùng có, bất cứ khi nào có thể, sắp xếp danh sách dữ liệu thay vì lặp đi lặp lại một cách mù quáng trên toàn bộ nhóm thực thể.
teodron

4
@Jefffrey: lý tưởng là bạn không viết mã loại cụ thể. Bạn viết mã giao diện cụ thể ("giao diện" theo nghĩa chung). Logic của bạn cho một TeamASoldierTeamBSoldierthực sự giống hệt nhau - bắn vào bất cứ ai trong nhóm khác. Tất cả những gì nó cần của các thực thể khác là một GetTeam()phương thức cụ thể nhất và, theo ví dụ của congusbongus, có thể được trừu tượng hóa hơn nữa thành IsEnemyOf(this)loại giao diện. Mã không cần quan tâm đến phân loại phân loại của binh lính, thây ma, người chơi, v.v. Tập trung vào tương tác, không phải loại.
Sean Middleditch

Câu trả lời:


11

Thay vì thực hiện việc ra quyết định của từng thực thể, bạn có thể thay thế cho mẫu điều khiển. Bạn sẽ có các lớp điều khiển trung tâm nhận thức được tất cả các đối tượng (quan trọng với chúng) và điều khiển hành vi của chúng.

Bộ điều khiển Movement sẽ xử lý chuyển động của tất cả các đối tượng có thể di chuyển (thực hiện tìm đường, cập nhật vị trí dựa trên các vectơ chuyển động hiện tại).

Một MineBehaviorCont kiểm soát sẽ kiểm tra tất cả các mỏ và tất cả các binh sĩ, và ra lệnh cho một quả mìn phát nổ khi một người lính đến quá gần.

Một ZombieBehaviorContoder sẽ kiểm tra tất cả zombie và binh lính trong vùng lân cận của họ, chọn mục tiêu tốt nhất cho mỗi zombie và ra lệnh cho nó di chuyển đến đó và tấn công nó (bản thân việc di chuyển được điều khiển bởi MovementContoder).

Một SoldierBehaviorCont kiểm sẽ phân tích toàn bộ tình huống và sau đó đưa ra các chỉ dẫn chiến thuật cho tất cả các binh sĩ (bạn di chuyển đến đó, bạn bắn nó, bạn chữa lành cho anh chàng đó ...). Việc thực thi thực tế của các lệnh cấp cao hơn này cũng sẽ được xử lý bởi các bộ điều khiển cấp thấp hơn. Khi bạn nỗ lực hết sức, bạn có thể khiến AI có khả năng đưa ra quyết định hợp tác khá thông minh.


1
Có lẽ đây còn được gọi là "hệ thống" quản lý logic cho một số loại thành phần nhất định trong kiến ​​trúc Thực thể-Thành phần.
teodron

Nghe có vẻ giống như một giải pháp kiểu C. Các thành phần được nhóm trong std::maps và các thực thể chỉ là ID và sau đó chúng ta phải tạo một loại hệ thống nào đó (có thể với một thành phần thẻ, vì trình kết xuất sẽ cần biết phải vẽ gì); và nếu chúng ta không muốn làm điều đó, chúng ta sẽ cần phải có một thành phần vẽ: nhưng nó cần thành phần vị trí để biết nơi được vẽ, vì vậy chúng ta tạo ra sự phụ thuộc giữa các thành phần mà chúng ta giải quyết bằng một hệ thống nhắn tin siêu phức tạp. Đây có phải là những gì bạn đang đề nghị?
Giày

1
@Jefffrey "Nghe có vẻ giống như một giải pháp kiểu C" - ngay cả khi điều đó là đúng, tại sao nó nhất thiết phải là một điều xấu? Các mối quan tâm khác có thể hợp lệ, nhưng có giải pháp cho chúng. Thật không may, một bình luận quá ngắn để giải quyết từng vấn đề một cách đúng đắn.
Phi

1
@Jefffrey Sử dụng cách tiếp cận trong đó bản thân các thành phần không có logic và "hệ thống" chịu trách nhiệm xử lý tất cả logic không tạo ra sự phụ thuộc giữa các thành phần cũng như không yêu cầu hệ thống nhắn tin siêu phức tạp (ít nhất, gần như không phức tạp) . Xem ví dụ: gamadu.com/artemis/tutorial.html

1

Trước hết, hãy cố gắng thực hiện các tính năng để các đối tượng độc lập với nhau, bất cứ khi nào có thể. Đặc biệt bạn muốn làm điều đó cho đa luồng. Trong ví dụ mã đầu tiên của bạn, tập hợp tất cả các đối tượng có thể được chia thành các tập hợp khớp với số lượng lõi CPU và được cập nhật rất hiệu quả.

Nhưng như bạn đã nói, tương tác với các đối tượng khác là cần thiết cho một số tính năng. Điều đó có nghĩa là trạng thái của tất cả các đối tượng phải được đồng bộ hóa tại một số điểm. Nói cách khác, ứng dụng của bạn phải đợi tất cả các tác vụ song song hoàn thành trước, sau đó áp dụng các tính toán liên quan đến tương tác. Rất tốt để giảm số lượng các điểm đồng bộ hóa này, vì chúng luôn ngụ ý rằng một số luồng phải chờ người khác hoàn thành.

Do đó, tôi đề nghị đệm những thông tin đó về các đối tượng cần thiết từ bên trong các đối tượng khác. Với bộ đệm toàn cầu như vậy, bạn có thể cập nhật tất cả các đối tượng của mình độc lập với nhau nhưng chỉ phụ thuộc vào chính chúng và bộ đệm toàn cầu, vừa nhanh hơn và dễ bảo trì hơn. Tại một dấu thời gian cố định, giả sử sau mỗi khung hình, cập nhật bộ đệm với trạng thái hiện tại của các đối tượng.

Vì vậy, những gì bạn làm một lần trên mỗi khung là 1. đệm các đối tượng hiện tại Trạng thái toàn cầu, 2. cập nhật tất cả các đối tượng dựa trên chính chúng và bộ đệm, 3. vẽ các đối tượng của bạn và sau đó bắt đầu lại với việc làm mới bộ đệm.


1

Sử dụng một hệ thống dựa trên thành phần, trong đó bạn có GameObject barebones chứa 1 hoặc nhiều thành phần xác định hành vi của chúng.

Ví dụ: nói rằng một số đối tượng được cho là di chuyển sang trái và phải mọi lúc (một nền tảng), bạn có thể tạo một thành phần như vậy và gắn nó vào GameObject.

Bây giờ nói rằng một đối tượng trò chơi được cho là xoay từ từ mọi lúc, bạn có thể tạo một thành phần riêng biệt để thực hiện điều đó và gắn nó vào GameObject.

Điều gì sẽ xảy ra nếu bạn muốn có một nền tảng di chuyển cũng xoay vòng, trong một chế độ thừa kế truyền thống trở nên khó thực hiện nếu không sao chép mã.

Cái hay của hệ thống này là thay vì có lớp Rotable hoặc MovePl Platform, bạn đính kèm cả hai thành phần đó vào GameObject và giờ đây bạn có một MovePl Platform có khả năng AutoRotates.

Tất cả các thành phần đều có thuộc tính, 'notifyUpdate', trong khi đúng, GameObject sẽ gọi phương thức 'update' trên thành phần đã nói. Ví dụ: giả sử bạn có thành phần Draggable, thành phần này khi thả chuột xuống (nếu nó đã qua GameObject) có thể đặt 'notifyUpdate' thành true, và sau đó, chuột sẽ đặt nó thành false. Cho phép nó đi theo chuột chỉ khi chuột xuống.

Một trong những nhà phát triển Tony Hawk Pro Skater có defacto viết lên nó và nó cũng đáng đọc: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/


1

Thành phần ưu tiên hơn thừa kế.

Lời khuyên mạnh mẽ nhất của tôi ngoài vấn đề này là: Đừng bị cuốn vào suy nghĩ "Tôi muốn điều này cực kỳ linh hoạt". Tính linh hoạt là tuyệt vời, nhưng hãy nhớ rằng ở một mức độ nào đó, trong bất kỳ hệ thống hữu hạn nào như trò chơi, có những phần nguyên tử được sử dụng để xây dựng toàn bộ. Bằng cách này hay cách khác, quá trình xử lý của bạn phụ thuộc vào các loại nguyên tử được xác định trước. Nói cách khác, phục vụ cho loại dữ liệu "bất kỳ" (nếu có thể) sẽ không giúp bạn về lâu dài, nếu bạn không có mã để xử lý dữ liệu đó. Về cơ bản, tất cả các mã phải phân tích / xử lý dữ liệu dựa trên các thông số kỹ thuật đã biết ... có nghĩa là một tập hợp các loại được xác định trước. Bộ đó lớn cỡ nào? Tùy bạn

Bài viết này cung cấp cái nhìn sâu sắc về nguyên tắc Thành phần so với Kế thừa trong phát triển trò chơi thông qua kiến ​​trúc thành phần thực thể mạnh mẽ và hiệu quả.

Bằng cách xây dựng các thực thể từ các tập hợp con (khác nhau) của một số siêu bộ của các thành phần được xác định trước, bạn đưa ra các cách cụ thể, hiểu biết về thế giới và các tác nhân xung quanh chúng, bằng cách đọc các trạng thái của các thành phần đó.


1

Cá nhân tôi khuyên bạn nên giữ chức năng vẽ ra khỏi lớp Object. Tôi thậm chí khuyên bạn nên giữ vị trí / tọa độ của Đối tượng khỏi chính Đối tượng.

Phương thức draw () đó sẽ xử lý API kết xuất cấp thấp của OpenGL, OpenGL ES, Direct3D, lớp gói của bạn trên các API đó hoặc API công cụ. Có thể là bạn phải trao đổi giữa lúc đó (Nếu bạn muốn hỗ trợ OpenGL + OpenGL ES + Direct3D chẳng hạn.

GameObject đó chỉ nên chứa thông tin cơ bản về hình thức trực quan của nó như Lưới hoặc có thể là một gói lớn hơn bao gồm các đầu vào shader, trạng thái hoạt hình, v.v.

Ngoài ra, bạn sẽ muốn một đường ống đồ họa linh hoạt. Điều gì xảy ra nếu bạn muốn đặt hàng các đối tượng dựa trên khoảng cách của chúng với máy ảnh. Hoặc loại vật liệu của họ. Điều gì xảy ra nếu bạn muốn vẽ một đối tượng 'được chọn' một màu khác. Điều gì sẽ xảy ra nếu thay vì thực sự rending như soo khi bạn gọi một hàm vẽ trên một đối tượng, thay vào đó, nó đưa nó vào một danh sách lệnh hành động để kết xuất (có thể cần thiết cho luồng). Bạn có thể làm điều đó với hệ thống khác nhưng đó là PITA.

Những gì tôi khuyên là thay vì vẽ trực tiếp, bạn liên kết tất cả các đối tượng bạn muốn với cấu trúc dữ liệu khác. Sự ràng buộc đó chỉ thực sự cần phải có một tham chiếu đến vị trí đối tượng và thông tin kết xuất.

Cấp độ / khối / khu vực / bản đồ / trung tâm / wholeworld / bất cứ thứ gì được cung cấp chỉ số không gian, cái này chứa các đối tượng và trả về chúng dựa trên các truy vấn phối hợp và có thể là một danh sách đơn giản hoặc một cái gì đó giống như Octree. Nó cũng có thể là một vỏ bọc cho thứ gì đó được thực hiện bởi động cơ vật lý của bên thứ 3 như một cảnh vật lý. Nó cho phép bạn thực hiện những việc như "Truy vấn tất cả các đối tượng trong chế độ xem của máy ảnh với một số khu vực phụ xung quanh chúng" hoặc cho các trò chơi đơn giản hơn, nơi bạn có thể hiển thị mọi thứ lấy toàn bộ danh sách.

Chỉ mục không gian không phải chứa thông tin định vị thực tế. Chúng hoạt động bằng cách lưu trữ các đối tượng trong các cấu trúc cây liên quan đến vị trí của các đối tượng khác. Chúng có thể mặc dù là một loại bộ nhớ cache bị mất cho phép tra cứu nhanh đối tượng dựa trên vị trí của nó. Không có nhu cầu thực sự để nhân đôi tọa độ X, Y, Z thực tế của bạn. Đã nói rằng bạn có thể nếu bạn muốn giữ

Trong thực tế, các đối tượng trò chơi của bạn thậm chí không cần chứa thông tin vị trí của riêng họ. Ví dụ, một đối tượng chưa được đưa vào một cấp độ không nên có tọa độ x, y, z, điều đó không có ý nghĩa. Bạn có thể chứa nó trong chỉ mục đặc biệt. Nếu bạn cần tra cứu tọa độ của đối tượng dựa trên tham chiếu thực tế của nó thì bạn sẽ muốn có một ràng buộc giữa đối tượng và biểu đồ cảnh (biểu đồ cảnh là để trả về đối tượng dựa trên tọa độ nhưng chậm khi trả lại tọa độ dựa trên đối tượng) .

Khi bạn thêm một đối tượng vào một cấp độ. Nó sẽ làm như sau:

1) Tạo cấu trúc vị trí:

 class Location { 
     float x, y, z; // Or a special Coordinates class, or a vec3 or whatever.
     SpacialIndex& spacialIndex; // Note this could be the area/level/map/whatever here
 };

Đây cũng có thể là một tham chiếu đến một đối tượng trong động cơ vật lý của bên thứ 3. Hoặc nó có thể là tọa độ bù có tham chiếu đến vị trí khác (đối với camera theo dõi hoặc đối tượng hoặc ví dụ đính kèm). Với tính đa hình, nó có thể tùy thuộc vào việc nó là đối tượng tĩnh hay động. Bằng cách giữ một tham chiếu đến chỉ mục không gian ở đây khi tọa độ được cập nhật, chỉ số không gian cũng có thể.

Nếu bạn lo lắng về việc cấp phát bộ nhớ động, hãy sử dụng nhóm bộ nhớ.

2) Một liên kết / liên kết giữa đối tượng của bạn, vị trí của nó và biểu đồ cảnh.

typedef std::pair<Object, Location> SpacialBinding.

3) Binding được thêm vào chỉ số không gian bên trong cấp độ tại điểm thích hợp.

Khi bạn đang chuẩn bị kết xuất.

1) Lấy máy ảnh (Nó sẽ chỉ là một đối tượng khác, ngoại trừ vị trí của nó sẽ theo dõi nhân vật người chơi và trình kết xuất của bạn sẽ có một tham chiếu đặc biệt đến nó, thực tế đó là tất cả những gì nó thực sự cần).

2) Nhận SpacialBinding của máy ảnh.

3) Lấy chỉ số không gian từ ràng buộc.

4) Truy vấn các đối tượng (có thể) hiển thị trước máy ảnh.

5A) Bạn cần xử lý thông tin hình ảnh. Hoạ tiết được tải lên GPU, v.v. Điều này sẽ được thực hiện tốt nhất trước (chẳng hạn như khi tải mức) nhưng có lẽ có thể được thực hiện trong thời gian chạy (đối với một thế giới mở, bạn có thể tải công cụ khi bạn đang ở gần một đoạn nhưng vẫn nên được thực hiện trước).

5B) Tùy chọn xây dựng cây kết xuất được lưu trong bộ nhớ cache, nếu bạn muốn phân loại độ sâu / vật liệu hoặc theo dõi các đối tượng gần đó, có thể hiển thị sau đó. Nếu không, bạn chỉ có thể truy vấn chỉ mục không gian mỗi lần nó sẽ phụ thuộc vào yêu cầu trò chơi / hiệu suất của bạn.

Trình kết xuất của bạn có thể sẽ cần một đối tượng RenderBinding sẽ liên kết giữa Đối tượng, tọa độ

class RenderBinding {
    Object& object;
    RenderInformation& renderInfo;
    Location& location // This could just be a coordinates class.
}

Sau đó, khi bạn kết xuất, chỉ cần chạy qua danh sách.

Tôi đã sử dụng các tài liệu tham khảo ở trên nhưng chúng có thể là con trỏ thông minh, con trỏ thô, xử lý đối tượng và như vậy.

BIÊN TẬP:

class Game {
    weak_ptr<Camera> camera;
    Level level1;

    void init() {
        Camera camera(75.0_deg, 1.025_ratio, 1000_meters);
        auto template_player = loadObject("Player.json")
        auto player = level1.addObject(move(player), Position(1.0, 2.0, 3.0));
        level1.addObject(move(camera), getRelativePosition(player));

        auto template_bad_guy = loadObject("BadGuy.json")
        level1.addObject(template_bad_guy, {10, 10, 20});
        level1.addObject(template_bad_guy, {10, 30, 20});
        level1.addObject(move(template_bad_guy), {50, 30, 20});
    }

    void render() {
        camera->getFrustrum();
        auto level = camera->getLocation()->getLevel();
        auto object = level.getVisible(camera);
        for(object : objects) {
            render(objects);
        }
    }

    void render(Object& object) {
        auto ri = object.getRenderInfo();
        renderVBO(ri.getVBO());
    }

    Object loadObject(string file) {
        Object object;
        // Load file from disk and set the properties
        // Upload mesh data, textures to GPU. Load shaders whatever.
        object.setHitPoints(// values from file);
        object.setRenderInfo(// data from 3D api);
    }
}

class Level {
    Octree octree;
    vector<ObjectPtr> objects;
    // NOTE: If your level is mesh based there might also be a BSP here. Or a hightmap for an openworld
    // There could also be a physics scene here.
    ObjectPtr addObject(Object&& object, Position& pos) {
        Location location(pos, level, object);
        objects.emplace_back(object);
        object->setLocation(location)
        return octree.addObject(location);
    }
    vector<Object> getVisible(Camera& camera) {
        auto f = camera.getFtrustrum();
        return octree.getObjectsInFrustrum(f);
    }
    void updatePosition(LocationPtr l) {
        octree->updatePosition(l);
    }
}

class Octree {
    OctreeNode root_node;
    ObjectPtr add(Location&& object) {
        return root_node.add(location);
    }
    vector<ObjectPtr> getObjectsInRadius(const vec3& position, const float& radius) { // pass to root_node };
    vector<ObjectPtr> getObjectsinFrustrum(const FrustrumShape frustrum;) {//...}
    void updatePosition(LocationPtr* l) {
        // Walk up from l.octree_node until you reach the new place
        // Check if objects are colliding
        // l.object.CollidedWith(other)
    }
}

class Object {
    Location location;
    RenderInfo render_info;
    Properties object_props;
    Position getPosition() { return getLocation().position; }
    Location getLocation() { return location; }
    void collidedWith(ObjectPtr other) {
        // if other.isPickup() && object.needs(other.pickupType()) pick it up, play sound whatever
    }
}

class Location {
    Position position;
    LevelPtr level;
    ObjectPtr object;
    OctreeNote octree_node;
    setPosition(Position position) {
        position = position;
        level.updatePosition(this);
    }
}

class Position {
    vec3 coordinates;
    vec3 rotation;
}

class RenderInfo {
    AnimationState anim;
}
class RenderInfo_OpenGL : public RenderInfo {
    GLuint vbo_object;
    GLuint texture_object;
    GLuint shader_object;
}

class Camera: public Object {
    Degrees fov;
    Ratio aspect;
    Meters draw_distance;
    Frustrum getFrustrum() {
        // Use above to make a skewed frustum box
    }
}

Đối với việc làm cho mọi thứ 'nhận thức' về nhau. Đó là phát hiện va chạm. Nó sẽ được thực hiện trong Octree có lẽ. Bạn sẽ cần cung cấp một số cuộc gọi lại trong đối tượng chính của bạn. Công cụ này được xử lý tốt nhất bởi một công cụ vật lý thích hợp như Bullet. Trong trường hợp đó, chỉ cần thay thế Octree bằng ChemistryScene và Position bằng một liên kết đến một cái gì đó như CollisionMesh.getPocation ().


Wow, điều này có vẻ rất tốt. Tôi nghĩ rằng tôi đã nắm bắt được ý tưởng cơ bản, nhưng không có thêm ví dụ, tôi hoàn toàn không thể có được cái nhìn bên ngoài về điều này. Bạn có bất kỳ tài liệu tham khảo hoặc ví dụ trực tiếp về điều này? (Tôi sẽ tiếp tục đọc câu trả lời này trong một thời gian).
Giày

Không thực sự có bất kỳ ví dụ nào, đó chỉ là những gì tôi dự định làm khi có thời gian. Tôi sẽ thêm một vài lớp tổng thể và xem nếu nó giúp, Có cái nàycái này . đó là về các lớp đối tượng hơn là cách chúng liên quan hoặc kết xuất. Khi tôi không tự mình thực hiện nó, có thể có những cạm bẫy, các bit cần xử lý hoặc hiệu năng nhưng tôi nghĩ cấu trúc tổng thể vẫn ổn.
David C. Giám mục
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.