Động cơ dựa trên hệ thống thành phần


9

Lưu ý: Tôi đang lập trình phần này trong Javascript, nhưng phần lớn phải là ngôn ngữ bất khả tri.

Tôi đang suy nghĩ về việc chuyển đổi động cơ của mình sang ECS.

Tôi có ý tưởng cơ bản ( lưu ý: điều này sai, xem câu trả lời của tôi ):

Các thực thể là các đối tượng trò chơi.
Các thành phần là các bit của hàm ( reactToInput()) hoặc trạng thái ( position) có thể được "dán" vào các thực thể.
Các hệ thống có một danh sách các thực thể mà họ quản lý và cập nhật.

Nhưng, tôi không chắc chắn rằng tôi có được việc triển khai và một số chi tiết ...

Câu hỏi: một hệ thống có thể hoạt động trên các loại thực thể khác nhau? Tôi thường đưa ra ví dụ về một lớp được gọi Scenetrong công cụ của tôi và nó cũng sẽ phục vụ mục đích này ngay bây giờ. Cảnh là nơi chứa tất cả các đối tượng có thể được hiển thị, cập nhật, ảnh hưởng đến kết xuất (ánh sáng) và có thể, trong tương lai, thậm chí là 2DSoundEmittercác đối tượng. Nó có giao diện cấp cao, do đó người dùng không cần phải lo lắng về loại đối tượng mình đang sử dụng scene.add()và tất cả các loại công cụ đó.

Tôi nhận ra rằng Scenecó thể là một hệ thống. Nó nhận các thực thể, lưu trữ chúng và sau đó nó có thể gọi các phương thức cập nhật của chúng và thậm chí có thể thực hiện một số thay đổi trạng thái. Nhưng, có một vấn đề: như tôi đã mô tả ở trên, Scenecó thể được cho ăn các loại đối tượng khác nhau ! Tôi nên làm gì trong một tình huống trong đó một cảnh có cả các đối tượng có thể kết xuất ("drawables") ánh sáng trong đó? Tôi có nên làm cho nó kiểm tra loại thực thể trước khi tương tác? Hoặc, tôi nên giải quyết nó ở mức thậm chí thấp hơn: tạo ra một LightSourcethành phần có thể được thêm vào bất kỳ đối tượng nào và ánh sáng sẽ chỉ là một thực thể với LightSourcePositioncác thành phần. Điều đó có được chấp nhận không?

Ngoài ra, nó có phải là một thực hành tốt để vẫn sử dụng kế thừa thông thường và các lớp truyền thống? Ví dụ, tôi không thể hiểu được mình sẽ thế nào Renderer! Đây không phải là một hệ thống, vì chức năng duy nhất của nó là chụp camera và cảnh, kết xuất mọi thứ và áp dụng các hiệu ứng (như bóng). Nó cũng quản lý bối cảnh, chiều rộng và chiều cao của trò chơi, thực hiện các bản dịch ... Nhưng nó vẫn không phải là một hệ thống!

Chỉnh sửa: bạn có thể liên kết bất kỳ tài nguyên nào bạn tìm thấy trên ECS không? Tôi đang gặp khó khăn trong việc tìm kiếm những người tốt.


2
Thay vì đăng lại câu trả lời trên trang này, tôi sẽ chỉ cung cấp liên kết này: gamedev.stackexchange.com/questions/23533/ Không nên bắt nguồn từ thực thể, bất kỳ sự khác biệt nào giữa các thực thể nên được thực hiện thông qua các thành phần. Nói chung, bạn sẽ cần một giao diện cho từng hệ thống chính (Kết xuất, Vật lý, Mạng, Đầu vào, Âm thanh, v.v.). Cách tôi thiết lập trình kết xuất của mình là truy vấn cảnh cho các thực thể có thể kết xuất và trình quản lý cảnh hơn là hỏi từng thực thể có thành phần kết xuất trên đó để biết thông tin kết xuất của nó.
Nic Foster

1
Thiết kế thành phần trên blog T = Machine (vì bạn đã yêu cầu một cái tốt)
John McDonald

Mã và thảo luận về khung thực thể: gamadu.com/artemis
Patrick Hughes

@JohnMcDonald, tôi đã viết một bình luận về bài viết đó, mặc dù nó đang chờ kiểm duyệt. Bạn có thể thấy nó ở đây: t-machine.org/index.php/2007/12/22/ . Tôi là "Yannbane".
jcora

Ngoài ra, @NicFoster, bài viết mà John liên kết trên T = Machine mô tả một cái gì đó khác với câu trả lời của bạn ... Đối với Dave, các thực thể không có danh sách các thành phần, chúng chỉ là một tên. Giống như "flsjn304" - đó là một thực thể. Nó được lưu trữ "ở đâu đó". Và tôi phải đọc lại điều để hiểu nếu anh ta thực sự giữ các thành phần bên trong các hệ thống , điều này có vẻ rất lạ đối với tôi!
jcora

Câu trả lời:


6

Hãy để tôi xem nếu bằng cách cố gắng hiểu như là một nhà phát triển web / UI JS, tôi có thể giúp được gì không. Ngoài ra, đừng đi quá xa trong thuyết bất khả tri ngôn ngữ. Rất nhiều mẫu được thiết lập bằng các ngôn ngữ khác đáng để nghiên cứu nhưng có thể được áp dụng rất khác nhau trong JS do tính linh hoạt của nó hoặc thực sự không cần thiết do tính chất dễ uốn của ngôn ngữ. Bạn có thể thổi bay một số cơ hội nếu bạn viết mã nghĩ về JS giống như có cùng một tập hợp các ranh giới mà một ngôn ngữ hướng OOP cổ điển hơn thực hiện.

Trước hết, về yếu tố "không sử dụng OOP", hãy nhớ rằng các đối tượng JavaScript giống như trò chơi so với các ngôn ngữ khác và bạn thực sự phải tránh khỏi cơn ác mộng để xây dựng một cơn ác mộng kế hoạch xếp tầng vì JS không phải là lớp dựa trên cơ sở và kết hợp đến với nó một cách tự nhiên hơn nhiều. Nếu bạn đang triển khai một số lớp ngớ ngẩn hoặc hệ thống xử lý nguyên mẫu trong JS của bạn, hãy xem xét bỏ nó. Trong JS, chúng tôi sử dụng các bao đóng, các nguyên mẫu và chúng tôi chuyển các hàm xung quanh như kẹo. Thật kinh tởm và bẩn thỉu và sai trái nhưng cũng mạnh mẽ, súc tích và đó là cách chúng ta thích nó.

Các cách tiếp cận nặng kế thừa thực sự được đánh vần là một kiểu chống mẫu trong Mẫu thiết kế và vì lý do chính đáng vì bất kỳ ai đã bỏ qua hơn 15 cấp độ cấu trúc giống như lớp hoặc lớp để thử và tìm ra phiên bản bị phá vỡ của phương thức đã đến từ có thể cho bạn biết.

Tôi không biết tại sao rất nhiều lập trình viên thích làm điều này (đặc biệt là những người java viết JavaScript vì một số lý do), nhưng nó thật tồi tệ, không thể đọc được và hoàn toàn không thể nhận ra khi sử dụng quá mức. Kế thừa là ổn ở đây và ở đó, nhưng không thực sự cần thiết trong JS. Trong các ngôn ngữ mà nó là một lối tắt hấp dẫn hơn, nó thực sự nên được dành riêng cho các mối quan tâm kiến ​​trúc trừu tượng hơn là các sơ đồ mô hình hóa theo nghĩa đen như triển khai thực hiện zombie thông qua chuỗi thừa kế bao gồm BunnyRmus vì nó tình cờ hoạt động. Đó không phải là sử dụng lại mã tốt. Đó là một cơn ác mộng bảo trì.

Là một công cụ dựa trên cơ sở / Thành phần / Hệ thống của JS dev tấn công tôi như một hệ thống / mẫu để tách các mối quan tâm thiết kế và sau đó kết hợp các đối tượng để thực hiện ở mức độ chi tiết cao. Nói cách khác, trẻ chơi bằng một ngôn ngữ như JavaScript. Nhưng hãy để tôi xem nếu tôi đang mò mẫm điều này một cách chính xác trước.

  • Thực thể - Điều cụ thể bạn đang thiết kế. Chúng ta đang nói nhiều hơn theo hướng của danh từ thích hợp (nhưng thực tế không phải vậy). Không phải 'Cảnh', mà là 'IntroAreaLevelOne'. IntroAreaLevelOne có thể ngồi trong một hộp cảnhEntity thuộc loại nào đó nhưng chúng tôi đang tập trung vào một cái gì đó cụ thể khác với những thứ liên quan khác. Trong mã, một thực thể thực sự chỉ là một tên (hoặc ID) được gắn với một loạt các thứ mà nó cần phải thực hiện hoặc thiết lập (các thành phần) để có ích.

  • Thành phần - loại điều mà một thực thể cần. Đây là những danh từ chung. Giống như WalkingAnimation. Trong WalkingAnimation, chúng ta có thể biết cụ thể hơn, như "Shamble" (lựa chọn tốt cho zombie và quái vật thực vật) hoặc "ChickenWalker" (tuyệt vời cho các loại robot ed-209ish khớp ngược). Lưu ý: Không chắc chắn làm thế nào điều đó có thể tách rời khỏi kết xuất mô hình 3D như thế - vì vậy có thể là một ví dụ tào lao nhưng tôi là một người chuyên nghiệp hơn là một nhà phát triển trò chơi có kinh nghiệm. Trong JS tôi sẽ đặt cơ chế ánh xạ trong cùng một hộp với các thành phần. Các thành phần theo quyền riêng của chúng có thể sẽ nhẹ về logic và hơn nữa là một lộ trình cho hệ thống của bạn biết phải thực hiện những gì nếu hệ thống thậm chí cần thiết (trong nỗ lực của tôi tại ECS, một số thành phần chỉ là tập hợp các tập thuộc tính). Khi một thành phần được thiết lập, nó '

  • Hệ thống - Thịt chương trình thực sự ở đây. Các hệ thống AI được xây dựng và liên kết, Kết xuất được thực hiện, các chuỗi hoạt hình được thiết lập, v.v ... Tôi đang xử lý và để lại những thứ này chủ yếu cho trí tưởng tượng, nhưng trong ví dụ System.AI lấy một loạt các thuộc tính và tạo ra một chức năng được sử dụng để thêm các trình xử lý sự kiện cho đối tượng cuối cùng được sử dụng trong triển khai. Điều quan trọng về System.AI là nó bao gồm nhiều loại thành phần. Bạn có thể sắp xếp tất cả các công cụ AI với một thành phần nhưng để làm như vậy là hiểu sai quan điểm làm cho mọi thứ trở nên chi tiết.

Lưu ý các Mục tiêu: Chúng tôi muốn giúp dễ dàng cắm một số loại giao diện GUI cho những người không phải là nhà thiết kế để dễ dàng điều chỉnh các loại nội dung khác nhau bằng cách tối đa hóa và khớp các thành phần trong mô hình có ý nghĩa với họ và chúng tôi muốn tránh xa sơ đồ mã tùy ý phổ biến dễ viết hơn rất nhiều so với sửa đổi hoặc duy trì.

Vì vậy, trong JS, có thể một cái gì đó như thế này. Các nhà phát triển trò chơi vui lòng cho tôi biết nếu tôi hiểu sai về nó:

//I'm going with simple objects of flags over arrays of component names
//easier to read and can provide an opt-out default
//Assume a genre-bending stealth assassin game

//new (function etc... is a lazy way to define a constructor and auto-instantiate
var npcEntities = new (function NpcEntities(){

    //note: {} in JS is an object literal, a simple obj namespace (a dictionary)
    //plain ol' internal var in JS is akin to a private member
    var default={ //most NPCs are humanoids and critters - why repeat things?
        speedAttributes:true,
        maneuverAttributes:true,
        combatAttributes:true,
        walkingAnimation:true,
        runningAnimation:true,
        combatAnimation:true,
        aiOblivious:true,
        aiAggro:true,
        aiWary:true, //"I heard something!"
        aiFearful:true
    };

    //this. exposes as public

    this.zombie={ //zombies are slow, but keep on coming so don't need these
        runningAnimation:false,
        aiFearful:false
    };

    this.laserTurret={ //most defaults are pointless so ignore 'em
        ignoreDefault:true,
        combatAttributes:true,
        maneuverAttrubtes:true, //turning speed only
    };
    //also this.nerd, this.lawyer and on and on...

    //loop runs on instantiation which we're forcing on the spot

    //note: it would be silly to repeat this loop in other entity collections
    //but I'm spelling it out to keep things straight-forward.
    //Probably a good example of a place where one-level inheritance from
    //a more general entity class might make sense with hurting the pattern.
    //In JS, of course, that would be completely unnecessary. I'd just build a
    //constructor factory with a looping function new objects could access via
    //closure.

    for(var x in npcEntities){

        var thisEntity = npcEntities[x];

        if(!thisEntity.ignoreDefaults){

            thisEntity = someObjectXCopyFunction(defaults,thisEntity);
            //copies entity properties over defaults

        }
        else {
            //remove nonComponent property since we loop again later
            delete thisEntity.ignoreDefaults;
        }
    }
})() //end of entity instantiation

var npcComponents = {
    //all components should have public entityMap properties

    //No systems in use here. Just bundles of related attributes
    speedAttributes: new (function SpeedAttributes(){
        var shamblingBiped = {
            walkingAcceleration:1,
            topWalking:3
        },
        averageMan = {
            walkingAcceleration:3,
            runningAcceleration:4,
            topWalking: 4,
            topRunning: 6
        },
        programmer = {
            walkingAcceleration:1,
            runningAcceleration:100,
            topWalking:2
            topRunning:2000
        }; //end local/private vars

        //left is entity names | right is the component subcategory
        this.entityMap={
            zombie:shamblingBiped,
            lawyer:averageMan,
            nerd:programmer,
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(), //end speedAttributes

    //Now an example of an AI component - maps to function used to set eventHandlers
    //functions which, because JS is awesome we can pass around like candy
    //I'll just use some imaginary systems on this one

    aiFearful: new (function AiFearful(){
        var averageMan = Systems.AI({ //builds and returns eventSetting function
            fearThreshold:70, //%hitpoints remaining
            fleeFrom:'lastAttacker',
            tactic:'avoidIntercept',
            hazardAwareness:'distracted'
        }),
        programmer = Systems.AI({
            fearThreshold:95,
            fleeFrom:'anythingMoving',
            tactic:'beeline',
            hazardAwareness:'pantsCrappingPanic'
        });//end local vars/private members


         this.entityMap={
            lawyer:averageMan,
            nerd:averageMan, //nerds can run like programmers but are less cowardly
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(),//and more components...

    //Systems.AI is general and would get called for all the AI components.
    //It basically spits out functions used to set events on NPC objects that
    //determine their behavior. You could do it all in one shot but
    //the idea is to keep it granular enough for designers to actually tweak stuff
    //easily without tugging on developer pantlegs constantly.
    //e.g. SuperZombies, zombies, but slightly tougher, faster, smarter
}//end npcComponents

function createNPCConstructor(npcType){

    var components = npcEntities[npcType],

    //objConstructor is returned but components is still accessible via closure.

    objConstructor = function(){
        for(var x in components){
            //object iteration <property> in <object>

            var thisComponent = components[x];

            if(typeof thisComponent === 'function'){
                thisComponent.apply(this);
                //fires function as if it were a property of instance
                //would allow the function to add additional properties and set
                //event handlers via the 'this' keyword
            }
            else {
                objConstructor.prototype[x] = thisComponent;
                //public property accessed via reference to constructor prototype
                //good for low memory footprint among other things
            }
        }
    }
    return objConstructor;
}

var npcBuilders= {}; //empty object literal
for (var x in npcEntities){
    npcConstructors[x] = createNPCConstructor(x);
}

Bây giờ bất cứ khi nào bạn cần một NPC, bạn có thể xây dựng với npcBuilders.<npcName>();

GUI có thể cắm vào các đối tượng thành phần và thành phần npcEntities và cho phép các nhà thiết kế điều chỉnh các thực thể cũ hoặc tạo các thực thể mới bằng cách trộn và kết hợp các thành phần (mặc dù không có cơ chế nào trong đó cho các thành phần không mặc định nhưng có thể thêm các thành phần đặc biệt vào trong mã miễn là có một thành phần được xác định cho nó.


Nhìn vào điều này sáu năm sau, tôi không chắc mình hiểu câu trả lời của chính mình. Điều này có thể được cải thiện?
Erik Reppen

1

Tôi đã đọc trên Entity Systems trong các bài viết mà những người tử tế cung cấp trong các bình luận, nhưng tôi vẫn có một số nghi ngờ, vì vậy tôi đã hỏi một câu hỏi khác .

Trước hết, định nghĩa của tôi là sai. Các thực thểThành phần chỉ là những người nắm giữ dữ liệu câm, trong khi các hệ thống cung cấp tất cả các chức năng.

Tôi đã học đủ để bao gồm hầu hết các câu hỏi của tôi ở đây, vì vậy tôi sẽ trả lời nó.

Các Scenelớp tôi đã nói chuyện về việc không phải là một hệ thống. Tuy nhiên, nó nên là một người quản lý trung tâm có thể chứa tất cả các thực thể, tạo điều kiện cho các thông điệp và thậm chí có thể quản lý các hệ thống. Nó cũng có thể hoạt động như một nhà máy sản xuất cho các thực thể và tôi đã quyết định sử dụng nó như thế. Nó có thể lấy bất kỳ loại thực thể nào, nhưng sau đó nó phải cung cấp thực thể đó cho một hệ thống thích hợp (vì lý do hiệu suất, không nên thực hiện bất kỳ kiểm tra loại nào, trừ khi chúng theo bitwise).

Tôi không nên sử dụng bất kỳ OOP nào trong khi triển khai ES, gợi ý Adam, nhưng tôi thấy không có lý do gì để không có các đối tượng với các phương thức cho cả các thực thể và các thành phần, không chỉ các chủ sở hữu dữ liệu câm.

Đơn Renderergiản có thể được thực hiện như một hệ thống. Nó sẽ duy trì một danh sách các đối tượng có thể vẽ và gọi draw()phương thức kết xuất của chúng mỗi 16ms.


1
"Các thực thể và Thành phần chỉ là những người nắm giữ dữ liệu câm, trong khi các hệ thống cung cấp tất cả chức năng" "gọi phương thức draw () của thành phần kết xuất của chúng" bạn vẫn còn bối rối, bên cạnh phương thức "vẽ" đánh bại hoàn toàn hệ thống kết xuất của hệ thống Kết xuất. Ngoài ra tôi không hiểu tại sao đồ thị Cảnh của bạn không phải là một phần của Trình kết xuất, nó chỉ là một công cụ tiện lợi, bạn luôn có thể triển khai thành phần "có thể vẽ" của mình dưới dạng nút. Làm cho biểu đồ cảnh chịu trách nhiệm nhiều hơn cảnh chỉ là không cần thiết và tôi chắc chắn rằng nó sẽ là một mớ hỗn độn để gỡ lỗi.
dreta

@dreta, trình kết xuất hiện tại (triển khai không phải ES của động cơ) thực hiện các phép biến đổi, thay đổi máy ảnh, công cụ alpha và trong tương lai, nó sẽ tạo ra các hiệu ứng khác nhau, GUI và bóng. Nó chỉ có vẻ tự nhiên để nhóm những thứ đó. Không phải cảnh có trách nhiệm tạo ra một thực thể lưu trữ? Hay cái gì khác nên lưu trữ chúng? Phần tạo có lẽ chỉ là một vài dòng tổng hợp các thành phần do người dùng cung cấp với nhau, nó hoàn toàn không thực sự "tạo ra" bất cứ thứ gì, chỉ là tạo ra.
jcora

không phải mọi đối tượng đều có thể kết xuất được, không phải mọi đối tượng đều có thể va chạm hoặc phát ra âm thanh, với đối tượng cảnh bạn đang thực hiện ghép nối cực độ, tại sao? điều này sẽ chỉ là một nỗi đau để viết và gỡ lỗi. Các thực thể được sử dụng để xác định một đối tượng, các thành phần giữ dữ liệu và hệ thống hoạt động trên dữ liệu. Tại sao bạn lại kết hợp tất cả những thứ đó lại với nhau thay vì có các hệ thống phù hợp như RenderingSystem và SoundSystem và chỉ làm phiền các hệ thống đó nếu một thực thể có tất cả các thành phần cần thiết.
dreta

1
đổ bóng thường được gắn vào các nguồn sáng, mặc dù bạn chỉ có thể tạo một thành phần "CastsShadow" và tìm kiếm nó khi kết xuất các đối tượng động. nếu bạn đang làm 2D thì đây chỉ là vấn đề cơ bản của việc đặt hàng, thuật toán của một họa sĩ đơn giản sẽ giải quyết vấn đề này cho bạn. TBH bạn đang lo lắng quá sớm. bạn sẽ nhận ra điều này khi đến lúc phải làm điều đó và bạn chỉ có điều đó trong tâm trí của bạn, ngay bây giờ bạn chỉ đang tự làm mình bối rối. bạn không thể hy vọng có được mọi thứ ngay lần đầu tiên, điều đó sẽ không xảy ra. bạn sẽ đi qua cây cầu đó khi bạn đến đó.
dreta

1
"Các thực thể và Thành phần chỉ là những người nắm giữ dữ liệu câm, trong khi các hệ thống cung cấp tất cả các chức năng." Không cần thiết. Họ đang ở trong một số cách tiếp cận của mọi người. Nhưng không phải người khác. Nhìn vào công cụ Unity - tất cả các hành vi là trong các thành phần.
Kylotan

-2

Giới thiệu về quản lý phụ thuộc 101.

Khóa học này giả định rằng bạn có kiến ​​thức cơ bản về tiêm phụ thuộc và thiết kế kho lưu trữ.

Tiêm phụ thuộc chỉ là một cách ưa thích để các đối tượng nói chuyện với nhau (thông qua tin nhắn / tín hiệu / đại biểu / bất cứ điều gì) mà không được ghép trực tiếp.

Nó đi theo cụm từ: " newlà keo."

Tôi sẽ chứng minh điều này trong C #.

public interface IEntity
{
    int[] Position { get; }
    int[] Size { get; }
    bool Update();
    void Render();
}

public interface IRenderSystem
{
    void Draw(IEntity entity);
}

public interface IMovementSystem
{
    bool CanMoveLeft();
    void MoveLeft();
    bool CanMoveRight();
    void MoveRight();
    bool CanMoveUp();
    void MoveUp();
    bool CanMoveDown();
    void MoveDown();
    bool Moved();
    int[] Position { get; set; }
}

public interface IInputSystem
{
    string Direction { get; set; }
}

public class Player : IEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public bool Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();

        return _movementSystem.Moved();
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

Đây là một hệ thống cơ bản cho Nhập liệu, Di chuyển và Kết xuất. Các Playerlớp thực thể trong trường hợp này và các thành phần là giao diện. Các Playerlớp không biết gì về những phần bên trong như thế nào một cách cụ thể IRenderSystem, IMovementSystemhoặc IInputSystemlàm việc. Tuy nhiên, các giao diện cung cấp một cách Playerđể gửi tín hiệu (ví dụ như gọi Draw trên IRenderSystem) mà không phụ thuộc vào cách kết quả cuối cùng được thực hiện.

Ví dụ: hãy thực hiện IMovementSystem của tôi:

public interface IGameMap
{
    string LeftOf(int[] currentPosition);
    string RightOf(int[] currentPosition);
    string UpOf(int[] currentPosition);
    string DownOf(int[] currentPosition);
}

public class MovementSystem : IMovementSystem
{
    private readonly IGameMap _gameMap;
    private int[] _previousPosition;
    private readonly int[] _currentPosition;
    public MovementSystem(IGameMap gameMap, int[] initialPosition)
    {
        _gameMap = gameMap;
        _currentPosition = initialPosition;
        _previousPosition = initialPosition;
    }

    public bool CanMoveLeft()
    {
        return _gameMap.LeftOf(_currentPosition) == "Unoccupied";
    }

    public void MoveLeft()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]--;
    }

    public bool CanMoveRight()
    {
        return _gameMap.RightOf(_currentPosition) == "Unoccupied";
    }

    public void MoveRight()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]++;
    }

    public bool CanMoveUp()
    {
        return _gameMap.UpOf(_currentPosition) == "Unoccupied";
    }

    public void MoveUp()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]--;
    }

    public bool CanMoveDown()
    {
        return _gameMap.DownOf(_currentPosition) == "Unoccupied";
    }

    public void MoveDown()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]++;
    }

    public bool Moved()
    {
        return _previousPosition == _currentPosition;
    }

    public int[] Position
    {
        get { return _currentPosition; }
    }
}

MovementSystemcó thể có sự phụ thuộc của riêng mình và Playerthậm chí sẽ không quan tâm. Bằng cách sử dụng các giao diện, một máy trạng thái trò chơi có thể được tạo:

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IEntity>();
        foreach (var entity in _entities)
        {
            if(entity.Update())
                _renderQueue.Add(entity);
        }
        // Linq version for those interested
        //_renderQueue.AddRange(_entities.Where(e => e.Update()));
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

Và đó là sự khởi đầu của một trò chơi đáng yêu (đó cũng là đơn vị thử nghiệm).

Và với một vài bổ sung và một số đa hình:

public interface IEntity
{
}

public interface IRenderableEntity : IEntity
{
    void Render();        
}

public interface IUpdateableEntity : IEntity
{
    void Update();
    bool Updated { get; }
}

public interface IRenderSystem
{
    void Draw(IRenderableEntity entity);
}

// new player class
public class Player : IRenderableEntity, IUpdateableEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public void Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();
    }

    public bool Updated
    {
        get { return _movementSystem.Moved(); }
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IRenderableEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IRenderableEntity>();
        foreach (var entity in _entities)
        {
            if (entity is IUpdateableEntity)
            {
                var updateEntity = entity as IUpdateableEntity;
                updateEntity.Update();
            }

            if (entity is IRenderableEntity)
            {
                var renderEntity = entity as IRenderableEntity;
                _renderQueue.Add(renderEntity);
            }
        }
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

Bây giờ chúng ta có một hệ thống thực thể / thành phần nguyên thủy dựa trên các giao diện tổng hợp và kế thừa lỏng lẻo.


1
Điều này rất chống lại thiết kế thành phần :) Bạn sẽ làm gì nếu bạn muốn một người chơi phát ra âm thanh còn người khác thì không?
Kikaimaru

@Kikaimaru Vượt qua trong ISoundSystem không phát âm thanh. tức là không làm gì cả
Dustin Kingen

3
-1, không phải vì nó là mã xấu, mà bởi vì nó hoàn toàn không liên quan đến kiến ​​trúc dựa trên thành phần - thực tế đó là sự phổ biến của các giao diện như thế này mà các thành phần cố gắng tránh.
Kylotan

@Kylotan Tôi đoán sự hiểu biết của tôi phải sai.
Dustin Kingen
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.