Hành vi / Thuộc tính GameComponent Architecture


7

Tôi là một sinh viên đại học, và phát triển trò chơi là một sở thích nhỏ đối với tôi. Tôi đang viết một trò chơi mới và tôi đang tìm kiếm một cách tiếp cận mới để phát triển công cụ trò chơi để làm cho nó linh hoạt hơn (và cho phép cung cấp cho mình một nền tảng vững chắc cho những nỗ lực trong tương lai). Tôi đã tìm thấy một vài bài viết về cách thay vì dựa vào tính kế thừa sâu sắc để thể hiện các thực thể trò chơi (Điều này có thể dẫn đến các nút gốc và lá nặng trong cây thừa kế của bạn, cũng như kế thừa kim cương trong một số trường hợp), một cách tiếp cận tốt hơn là mô hình hóa logic của bạn trong các thành phần (Tốt hơn nữa, hành vi và thuộc tính).

Dưới đây là một bài trình bày minh họa điều này: http://www.gdcvault.com/free/gdc-canada-09 "Lý thuyết và thực hành của Architec Thành phần đối tượng trò chơi ..."

Tôi đang thực hiện điều này bằng XNA và C #. Tôi có một lớp Thực thể:

class Entity
{
    private Game1 mGame;
    private Dictionary<AttributeType, object> mAttributes;
    private Dictionary<BehaviorType, Behavior> mBehaviors;

    public Entity(Game game)
    {
        mGame = game;
        mAttributes = new Dictionary<AttributeType, object>();
        mBehaviors = new Dictionary<BehaviorType, Behavior>();
    }

    public Game GetGame()
    {
        return mGame;
    }

    //Attributes
    public void SetAttribute<T>(AttributeType attributeType, T value)
    {
        if (mAttributes.ContainsKey(attributeType))
        {
            mAttributes[attributeType] = value;
            OnMessage<T>(MessageType.ATTRIBUTE_UPDATED, attributeType, value);
        }
        else
        {
            mAttributes.Add(attributeType, value);
            OnMessage<T>(MessageType.ATTRIBUTE_CREATED, attributeType, value);
        }
    }

    public T GetAttribute<T>(AttributeType attributeType)
    {
        if (!mAttributes.ContainsKey(attributeType))
        {
            throw new KeyNotFoundException("GetAttribute: Attribute with type: " + attributeType.ToString() + " not found.");
        }
        return (T)mAttributes[attributeType];
    }

    public bool HasAttribute(AttributeType attributeType)
    {
        return mAttributes.ContainsKey(attributeType);
    }

    //Behaviors
    public void SetBehavior(BehaviorType behaviorType, Behavior behavior)
    {
        if (mBehaviors.ContainsKey(behaviorType))
        {
            mBehaviors[behaviorType] = behavior;
        }
        else
        {
            mBehaviors.Add(behaviorType, behavior);
        }
    }

    public Behavior GetBehavior(BehaviorType behaviorType)
    {
        if (!mBehaviors.ContainsKey(behaviorType))
        {
            throw new KeyNotFoundException("GetBehavior: Behavior with type: " + behaviorType.ToString() + " not found.");
        }
        return mBehaviors[behaviorType];
    }

    public bool HasBehavior(BehaviorType behaviorType)
    {
        return mBehaviors.ContainsKey(behaviorType);
    }

    public Behavior RemoveBehavior(BehaviorType behaviorType)
    {
        if (!mBehaviors.ContainsKey(behaviorType))
        {
            throw new KeyNotFoundException("RemoveBehavior: Behavior with type: " + behaviorType.ToString() + " not found.");
        }
        Behavior behavior = mBehaviors[behaviorType];
        mBehaviors.Remove(behaviorType);
        return behavior;
    }

    public void OnUpdate(GameTime gameTime)
    {
        foreach (Behavior behavior in mBehaviors.Values)
        {
            behavior.OnUpdate(gameTime);
        }
    }

    public void OnMessage<T>(MessageType messageType, AttributeType attributeType, T data)
    {
        foreach (Behavior behavior in mBehaviors.Values)
        {
            behavior.OnMessage<T>(messageType, attributeType, data);
        }
    }
}

Trong đó "AttributionType" và "BehaviorType" chỉ là enum:

public enum AttributeType
{
    POSITION_2D,
    VELOCITY_2D,
    TEXTURE_2D,
    RGB_8888
}

Lớp hành vi là một siêu lớp của tất cả các hành vi khác; các hành vi có nghĩa là khép kín và mô-đun (Ví dụ: tôi có thể có SpriteBatchRenderBehavior chỉ biết cách hiển thị một thực thể cho SpriteBatch, có thể sử dụng được trong bất kỳ trò chơi mới nào sử dụng khung này).

abstract class Behavior
{
    //Reference to owner to access attributes
    private Entity mOwner;

    public Behavior(Entity owner)
    {
        mOwner = owner;
    }

    public Entity GetOwner()
    {
        return mOwner;
    }

    protected void SetAttribute<T>(AttributeType attributeType, T value)
    {
        mOwner.SetAttribute<T>(attributeType, value);
    }

    protected T GetAttribute<T>(AttributeType attributeType)
    {
        return (T)mOwner.GetAttribute<T>(attributeType);
    }

    public abstract void OnUpdate(GameTime gameTime);

    public abstract void OnMessage<T>(MessageType messageType, AttributeType attributeType, T data);
}

Vì vậy, một thực thể có một túi các thuộc tính được gắn nhãn (POSITION_2D, TEXTURE_2D, v.v.), mỗi hành vi có thể truy cập các thuộc tính của chủ sở hữu của chúng thông qua các phương thức get / set. Tôi đang gặp vấn đề trong đó các thuộc tính từ thực thể của tôi cần được chia sẻ giữa các hành vi (không chắc đây có thực sự là vấn đề không). Về cơ bản, nếu tôi có một hành vi thay đổi vị trí của một thực thể trong thế giới trò chơi, nó sẽ sửa đổi thuộc tính vị trí của thực thể. Sau đó tôi thêm SpriteBatchDrawBehavior vào thực thể, tùy thuộc vào thực thể có vị trí (để nó biết nơi vẽ thực thể). Vấn đề của tôi là, nếu tôi thêm hành vi vẽ trước hành vi di chuyển, không có gì đảm bảo rằng thực thể sẽ có được một thuộc tính vị trí (trong trường hợp đó, một khóa không tìm thấy ngoại lệ sẽ bị ném).

Tôi luôn có thể đảm bảo thực thể có một vị trí bằng cách thêm một hành vi đảm bảo nó S have có một vị trí trước khi thêm một hành vi vẽ (hoặc bất kỳ hành vi nào khác phụ thuộc vào thuộc tính này), tuy nhiên điều này sẽ làm cho các hành vi này được ghép nối theo cách chúng không nên

Tùy chọn khác là kế thừa hành vi vẽ từ hành vi di chuyển kế thừa từ hành vi vị trí (nhưng điều này đưa chúng ta trở lại vấn đề với sự thừa kế kim cương, v.v. và các lớp con không liên quan đến THỰC TẾ với các lớp cha mẹ của chúng, khác hơn là có sự phụ thuộc tương tự).

Thêm một lựa chọn nữa là viết một kiểm tra trong các hành vi của tôi như: "Nếu chủ sở hữu của tôi không có vị trí, thì hãy tạo thuộc tính vị trí cho anh ta", nhưng sau đó, điều này đặt ra câu hỏi: nên chọn giá trị mặc định nào cho thuộc tính mới ? Vân vân.

Bất kỳ đề xuất? Tôi đang nhìn cái gì đó?

Cảm ơn!

c#  xna 

1
FYI: có vẻ như bạn đang cố viết C ++ bằng C #. Chúng tôi không sử dụng các getters như GetGame (). Tra cứu "tài sản" và đơn giản hóa cuộc sống của bạn.
3Dave

Câu trả lời:


5

Rất tiếc - khi họ nói rằng bạn nên mô hình hóa các thực thể của mình thành các thành phần, họ không có nghĩa là biến mọi thực thể thành một túi thuộc tính dựa trên enum!

Điểm quan trọng của thiết kế dựa trên thành phần là, thay vì có một lớp thừa kế cây lớn ( Penguinkế FlightlessBirdthừa từ Birdthừa kế từ Animalthừa kế từ ...) , giống như nhiều trò chơi truyền thống, bạn xây dựng các lớp của mình từ các lớp thành phần xác định các thuộc tính mà lớp có. Ví dụ, Penguinlớp của bạn có thể chứa một thể hiện của một EatsFishthành phần, nhưng sẽ không chứa CanFly.

Xem ở đâyở đây cho một mô tả tốt hơn về thiết kế dựa trên thành phần hơn bao giờ tôi có thể hy vọng sẽ cung cấp cho (Tôi đặc biệt thích này bài viết) .


Lưu ý rằng các slide bạn liên kết với đề xuất sử dụng Mix-in để mô hình hóa các thành phần. C # không hỗ trợ đa kế thừa như C ++, vì vậy bạn không thể thực sự "làm" các hỗn hợp. Bạn sẽ phải soạn các lớp của bạn theo cách thông thường hơn: sử dụng giao diện và / hoặc bố cục .

Cũng lưu ý rằng họ hỗ trợ sử dụng một hệ thống nhắn tin, thường được coi là thiết kế xấu (mặc dù được sử dụng rộng rãi).

Cuối cùng, vì bạn đã đề cập đến việc bạn đang sử dụng XNA, tôi cảm thấy bắt buộc phải đề cập rằng XNA có hỗ trợ tích hợp cho các dịch vụ . Điều này sẽ giải quyết các vấn đề khởi tạo bạn đang gặp phải. Và quan trọng hơn, điều này thực sự giúp giữ cho mã của bạn sạch sẽ và tách biệt ... nhưng vì một số lý do vô duyên, nó dường như không bao giờ được đề cập trong bất kỳ hướng dẫn hoặc sách XNA nào.


Tôi muốn giữ khung này là di động như nó có thể có thể; sẽ không tạo trò chơi của tôi bằng XNA GameService sắp xếp trò chơi của tôi xuống XNA chứ? Hoặc khung GameService đủ đơn giản để tự mình thực hiện để sử dụng trên các nền tảng khác (android, iphone, v.v.). Ngoài ra, trong phần trình bày mà tôi đã tham chiếu, họ cho thấy một thành phần sức khỏe lấy tham chiếu đến một thuộc tính có liên quan từ chủ sở hữu của thành phần đó: "GetAttribution (HEALTH_KEY);", đó là lý do tại sao tôi chọn mô hình hóa các thuộc tính của mình theo cách đó.

@Travis: Nếu bạn muốn làm cho nó di động sang Android / iphone, C # không phải là một lựa chọn tốt. Bạn có thể muốn xem xét các thư viện di động đa nền tảng cho Java hoặc C ++. Tuy nhiên, nếu C # là tất cả những gì bạn biết, hãy kiên trì với điều đó và lo lắng về việc thực sự tạo ra một trò chơi ngay bây giờ. Lo lắng về tính di động sau này - tạo ra một trò chơi là đủ khó mà không phải học một ngôn ngữ mới trên đầu trang!
BlueRaja - Daniel Pflughoeft

Ồ, haha ​​Tôi biết rằng C # chắc chắn không phải là tốt nhất cho tính di động, tuy nhiên các khái niệm được sử dụng trong thiết kế công cụ trò chơi này có thể di chuyển sang bất kỳ nền tảng nào (đó là những gì tôi đang tập trung vào). Tôi nắm khá tốt việc viết trò chơi bằng cách sử dụng android ndk, open gl es 2.0 và c ++ directx 10 libs, tôi chỉ viết trò chơi này bằng C # và XNA vì đây là sự kiện của Microsoft Windows Phone trong khuôn viên trường.

Bạn có thể giải thích về cách bạn sẽ thay đổi mã của Travis không? Tôi vẫn đang cố gắng để hiểu thiết kế trò chơi dựa trên thành phần và các bài viết bạn liên kết đến là những bài tôi đã thấy nhiều lần trước đây; chúng quá cao và mơ hồ để thực sự giúp hiểu mô hình (dù sao đối với tôi). Từ hiểu biết của tôi về thiết kế dựa trên thành phần, có vẻ như Travis đang đi đúng hướng. Bạn có thể cụ thể hơn về nơi bạn nghĩ rằng anh ta đã sai và làm thế nào mã của anh ta có thể được cải thiện?
Michael

1

Tôi đã đọc bài thuyết trình từ GDC Canada của Marcin Chady vài giờ trước khi đọc câu hỏi của bạn và tôi nghĩ rằng bạn không hiểu điểm của các thuộc tính. Cá nhân tôi (tôi đã không viết bài thuyết trình, tôi cũng không biết nó thực sự hoạt động như thế nào trong Radical Entertainment) nghĩ rằng bạn phải tạo các thuộc tính cần thiết trước khi thực sự sử dụng chúng.

Cả ChangePocationBehaviourRenderBehaviour đều sử dụng thuộc tính Position , nhưng không phải thuộc tính thứ nhất cũng như thứ hai không khởi tạo thuộc tính. Bạn phải khởi tạo nó trước. Bạn phải nói với thực thể nơi nó nằm.

Hãy để tôi chỉ cho bạn thấy nó trong một ví dụ đơn giản về bản sao Breakout :

Bạn đã có một Levellớp (đó là một cấp trò chơi = room = map), chứa Brickđối tượng (thực thể). Các Levelbiết, rằng vị trí ban đầu của Bricklà tại địa điểm 4.0f, 8.0f(X, Y) và nó tốc độ 0.0f, 1.0f. Nếu nó không biết điều đó, thì đó không thể là một cấp độ, bởi vì bạn phải chỉ định vị trí của các đối tượng trong một định nghĩa cấp độ (tệp dữ liệu cấp độ). Vì vậy, cấp độ làm gì? Nó tạo ra một Brickthực thể và ngay sau đó (trước khi bất kỳ hành vi nào được gọi), nó đặt vị trí của thực Brickthể ban đầu.

Brick.SetAttribute<Vector2>(AttributeType.POSITION_2D, new Vector2(4, 8))
Brick.SetAttribute<Vector2>(AttributeType.VELOCITY_2D, new Vector2(0, 1))

Sau đó, bạn có thể chỉ cần tiếp tục. Và khi bạn cần vẽ Brick, bạn đã biết vị trí của nó. Khi bạn cần thay đổi vị trí của nó (đó là một viên gạch có thể di chuyển, di chuyển từ bên trái màn hình sang bên phải và sau đó quay lại), bạn chỉ cần làm:

Vector2 position = GetAttribute<Vector2>(AttributeType.POSITION_2D);
Vector2 speed = GetAttribute<Vector2>(AttributeType.VELOCITY_2D);
position += speed;
SetAttribute<Vector2>(AttributeType.POSITION_2D, position);

Tôi hy vọng bạn hiểu tôi. Thật đơn giản: các hành vi dựa trên các thuộc tính , vì vậy các thuộc tính phải được đặt trước khi bạn tiến hành OnUpdate()OnMessage().

Nhân tiện: (vì tôi không thể nhận xét về danh tiếng người mới của mình trên GameDev SE, tôi đang thêm văn bản btw này vào câu trả lời của tôi) Bạn không cần bất kỳ sự thừa kế nào, vì vậy tôi thực sự không hiểu câu trả lời hàng đầu của đâu BlueRaja - Daniel Pflughoeft sẽ ... Lý thuyết và thực hành Trình bày kiến trúc thành phần đối tượng trò chơi hoàn toàn có thể chuyển đổi thành C # mà không có bất kỳ vấn đề nào, cũng như với bất kỳ ngôn ngữ OOP nào khác. Và tôi không biết có gì sai khi gửi tin nhắn, bởi vì trong ví dụ này, tôi không thể nghĩ ra giải pháp nào khác (bạn không thể tránh sử dụng tin nhắn nếu bạn muốn mã này chung chung, theo ý kiến ​​của tôi) . Có lẽ bạn có thể sử dụng trình xử lý sự kiện tùy chỉnh và thay vì tin nhắn, bạn sẽ vượt qua các đối số sự kiện. Nó có thể làm việc quá.


0

Đề nghị của tôi sẽ có một chức năng khởi tạo cho các hành vi. Khi bạn thêm hành vi vào thực thể, nó có thể thêm các thuộc tính của nó. Khi bạn khởi tạo, bạn tìm kiếm các thuộc tính bạn cần và đưa ra lỗi nếu chúng không có ở đó. Bằng cách đó, bạn có thể thêm tất cả chúng theo bất kỳ thứ tự nào, nhưng thực tế chúng sẽ không tìm kiếm các thuộc tính cho đến khi khởi tạo.

Ngoài ra, bạn có thể sử dụng cơ sở hạ tầng OnMessage của mình để bỏ qua các thuộc tính bị thiếu khi một hành vi được thêm vào thực thể, sau đó lắng nghe tất cả các thuộc tính cần thiết được thêm vào OnMessage cho hành vi. Nếu một thuộc tính bị thiếu khi bạn cố gắng sử dụng hành vi, bạn có thể đưa ra lỗi hoặc đơn giản là không thực hiện hành vi. Bất cứ điều gì là thích hợp hơn cho trò chơi của bạn.

Có ai giúp tôi không?


Tôi thích ý tưởng của bạn về việc các hành vi khởi tạo sau khi xây dựng để kiểm tra xem các thuộc tính bắt buộc có tồn tại không. Tôi nghĩ rằng tôi có thể thử làm điều đó và xem nó hoạt động ra sao. Tôi thực sự có cùng ý tưởng với việc sử dụng các phương thức OnMessage để thông báo cho các hành vi về việc bổ sung các thuộc tính mới; nhưng có vẻ như cả hai giải pháp này vẫn phụ thuộc vào thứ tự các thành phần được thêm / khởi tạo. Với hệ thống thông báo, nếu một thành phần thêm thuộc tính, tin nhắn một lần sẽ được gửi đến tất cả các hành vi, tuy nhiên các hành vi có liên quan có thể chưa được thêm vào thực thể, thiếu thông báo.

@TravisSein Những cái được thêm sau khi tin nhắn được gửi phải kiểm tra thuộc tính khi chúng được thêm vào, theo cách đó nếu nó tồn tại trước khi nó tốt. Nếu nó được tạo sau, nó sẽ nhận được thông báo.
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.