基于组件的游戏设计中的共享字段 [英] Sharing Fields in Component-Based Game Design

查看:27
本文介绍了基于组件的游戏设计中的共享字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我认为这是在使用 XNA 在 C# 中完成基于组件的游戏引擎之前的最后一次重大逻辑飞跃.我定义了实体类和抽象组件.我的问题出现在我的 EntityFactory 中.

当我想创建一个新实体时,我将一个 EntityType 枚举传递给工厂中的一个静态方法,它通过一个 switch/case 语句来查找要将哪些组件放在一起.问题是,我正在尝试创建一种方法,使组件可以与同一实体中的其他组件共享字段,而无需访问所有内容.例如,如果两个组件具有表示位置的 Vector2 字段,它们应该都指向同一个 Vector2.

我可以通过初始化实体工厂中的所有字段并要求将它们传递给组件的构造函数(并使用 ref 作为原语)来做到这一点,但这将极难维护,因为任何时候我都会扩展或者更改了一个组件,我必须在工厂中使用该组件的每个地方重写代码.我真的很想避免这个解决方案,但如果我找不到更好的方法,我会忍受.

我目前的解决方案是创建一个名为 Attribute 的包装类.它包含两个字段:

私有的AttributeType类型;私有对象数据;

Attribute Type 是一个枚举,表示属性的用途.所以在Position、Rotation、Texture等的enum中有条目

EntityFactory 创建一个空的属性列表,并将其传递给每个组件构造函数.setField 方法将由组件的构造函数调用,而不是初始化字段.这里是 Attribute 类和 setField 方法.

 公共类属性{私有 AttributeType 类型;私有对象数据;公共属性类型类型{得到 { 返回 this.type;}}公共对象数据{得到 { 返回 this.data;}}公共属性(AttributeType 类型,对象数据){this.type = 类型;this.data = 数据;}public static void setField(ListattributeList, AttributeType type, out T field, T defaultValue){bool attributeFound = false;字段 = 默认值;foreach(attributeList 中的属性属性){if (attribute.Type == type){field = (T)attribute.Data;属性发现 = 真;休息;}}如果(!属性发现){attributeList.Add(new Attribute(type, field));}}}

我的问题是当属性包含原始类型的数据时.我考虑过在

的Attribute类中写一个方法

public void getData(out T field) { field = this.data;}

但是我似乎无法使用 ref 将数据传递给 Attribute 构造函数.我不能使 Attribute 通用,因为它不会进入列表.我只是想知道是否有一种方法可以处理值类型和引用类型数据,或者我是否在整个过程中犯了逻辑错误.

解决方案

Snarky 版本: 恭喜您重新发明了变量.糟糕.或者,充其量是界面上的一个属性.

有用的版本:

我发现您的设计存在一些问题.

第一个问题很简单,它复杂.最好避免复杂化,除非您有一个令人信服且存在的理由(即:不是可能在未来"的需要).否则
(来源:cowboyprogramming.com)

这让我想到了第三个问题:无论如何,您不应该拥有多个保持位置的组件!(在您的设计中,您似乎方式比您需要的更细化.)

基本上,基于组件的设计是关于重塑,而不是变量.在普通设计中,您可能有这样的渲染"功能:

public void Draw(){spriteBatch.Draw(texture, this.Position, Color.White);}

但是在基于组件的设计中,您将在不同的类中有DrawPosition.顺便说一下,我会实现如下接口:

interface IRenderComponent { void Draw();}interface IPositionComponent { Vector2 Position { get;放;} }

那么Draw如何访问Position?好吧,您需要一种表达this的方法(如果您要重新发明类,this可能是您需要了解的最重要的概念包括).

你会怎么做?这是一个粗略的设计理念:

我会让每个组件类继承自一个带有属性 SelfComponent 类.我会让 Self 返回某种 ComposedObject 并带有一种机制,用于通过接口访问构成组合对象的任何其他组件.因此,您的渲染组件可能看起来像:

class SimpleRenderer : Component, IRenderComponent{公共无效绘制(){sb.Draw(texture, Self.Get().Position, Color.White);}}

(这类似于 GameServiceContainer(即:Game.Services 属性).这里的想法是没有 ComposedObject 应该有更多每个接口的一个实例.如果您的接口数量很少,ComposedObject 甚至不需要使用列表 - 只需直接存储每个接口.不过,您可以拥有实现多个接口的组件.)

现在,如果这对您来说太冗长了,也许您可​​以在 ComposedObject(或使用扩展方法)上为常用数据添加一些便利属性,例如 Position,像这样:

public Vector2 Position { get { return Get().Position;} }

那么你的绘图函数可以是这样的:

spriteBatch.Draw(texture, Self.Position, Color.White);

I am at what I believe to be the last big logical leap before completing my component based game engine in C# using XNA. I have my Entity class and abstract components defined. My issue is arising in my EntityFactory.

When I want to create a new entity I pass in an EntityType enum to a static method in the factory, and it goes through a switch/case statement finding which components to put together. The problem is, I am trying to create a way that the components can share fields with other components in the same Entity without them having access to everything. For instance, if two components have Vector2 fields that represents position, they should both point to the same Vector2.

I can do this by initializing all fields in the entity factory and requiring that they be passed into the constructor of the component (and using ref for the primitives), but this would be extremely hard to maintain, since any time I expanded or changed a component, I would have to rewrite code in the factory in every place that component was used. I really want to avoid this solution, but if I can't find a better way, I will put up with it.

My current solution is to create a wrapper class called Attribute. It contains two fields:

private AttributeType type;
private Object data;

Attribute Type is an enum, representing the purpose of the attribute. So there are entries in the enum of Position, Rotation, Texture, etc.

The EntityFactory creates an empty list of attributes, and passes it to each component constructor. The setField method would be called by the component's constructor rather than initializing the fields. Here is the Attribute class and the setField method.

 public class Attribute
{
    private AttributeType type;
    private Object data;

    public AttributeType Type
    {
        get { return this.type; }
    }
    public Object Data
    {
        get { return this.data; }
    }

    public Attribute(AttributeType type, Object data)
    {
        this.type = type;
        this.data = data;
    }

    public static void setField<T>(List<Attribute> attributeList, AttributeType type, out T field, T defaultValue)
    {
        bool attributeFound = false;
        field = defaultValue;

        foreach (Attribute attribute in attributeList)
        {
            if (attribute.Type == type)
            {
                field = (T)attribute.Data;
                attributeFound = true;
                break;
            }
        }

        if (!attributeFound)
        {
            attributeList.Add(new Attribute(type, field));
        }
    }
}

My problem is when the attribute contains data that is of a primitive type. I considered writing a method in the Attribute class of

public void getData<T>(out T field) { field = this.data; }

however I can't seem to pass in data to the Attribute constructor using ref. I can't make Attribute generic since it wouldn't go into a list. I am just wondering if there is a way to handle value type as well as reference type data, or am I making a logical mistake somewhere in this whole thing.

解决方案

Snarky version: Congratulations you've reinvented the variable. Badly. Or, at best, a property on an interface.

Useful version:

I can see a few issues with your design.

The first issue is simply that it's complicated. Complication is best avoided unless you have a compelling and existent reason for it (ie: not a "maybe in the future" need). Otherwise YAGNI. You should always try to express concepts directly in code before resorting to creating systems to express those concepts in data (like what I said about reinventing the variable; also consider this).

But let's assume that you do have a good reason for a component based design...

Second issue is boxing. Boxing happens anywhere you store a value type (eg: int, float, Vector2, any struct) directly as a reference type (eg: object, IEquatable). Boxed objects are immutable - so every time your position changes, a new boxed object is created. Boxing a variable is (relatively) slow. Boxed objects are stored on the heap - so they are considered during, and can cause, a garbage collection. So the design you propose in your question is going to perform horribly.

I assume your idea for a component based design is similar to the one explained in this article. Here's a helpful diagram:


(source: cowboyprogramming.com)

And that brings me to the third issue: you shouldn't have more than one component that holds a position anyway! (You seem to be going way more granular than you need to, in your design.)

Basically a component-based design is about reinventing the class, rather than the variable. In a normal design, you might have a "Render" function like this:

public void Draw()
{
    spriteBatch.Draw(texture, this.Position, Color.White);
}

But in a component based design you will have Draw and Position in different classes. Which, by the way, I would have implementing interfaces like:

interface IRenderComponent { void Draw(); }
interface IPositionComponent { Vector2 Position { get; set; } }

So how does Draw access Position? Well, you need a way to express this (if you're going to reinvent classes, this is perhaps the most important concept you need to include).

How would you do this? Here's a rough design idea for you:

I would make each component class inherit from a Component class with a property Self. I would make Self return some kind of ComposedObject with a mechanism for accessing any of the other components that make up the composed object by interface. So perhaps your render component might look like:

class SimpleRenderer : Component, IRenderComponent
{
    public void Draw()
    {
        sb.Draw(texture, Self.Get<IPositionComponent>().Position, Color.White);
    }
}

(This works similarly to GameServiceContainer (ie: the Game.Services property). The idea here is that no ComposedObject should have more than one instance of each interface. If your number of interfaces is small, ComposedObject need not even use a list - simply store each directly. You could have components that implement more than one interface, though.)

Now if that is too verbose for you, perhaps you could add a few convenience properties on ComposedObject (or use extension methods) for common pieces of data, like Position, like this:

public Vector2 Position { get { return Get<IPositionComponent>().Position; } }

Then your draw function could simply be this:

spriteBatch.Draw(texture, Self.Position, Color.White);

这篇关于基于组件的游戏设计中的共享字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆