我的游戏引擎实体组件系统中的 GetComponent<>() 函数 returns 编译器错误 C2440

My GetComponent<>() function in my game engine's Entity-Component-System returns Compiler Error C2440

这个问题是关于在 Windows 10 OS 中运行的 C++ 应用程序中来自 Visual Studio 2019 的编译器错误。应该是中级以上的问题吧

总结

我有这个功能:

template<typename T> T* Scene::GetComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID<T>();
    if (HasComponent<T>(entityID)) {
        CompIndex index = componentIndexes[typeID][entityID];
        return components[typeID][index];
    }
    else return nullptr;
}

此函数导致编译器错误 C2440:

'return': cannot convert from '_Ty' to 'T*' with [_Ty=Component*] and [T=Name]

此错误还附有以下两条消息:

message : Cast from base to derived requires dynamic_cast or static_cast

message : see reference to function template instantiation 'T *Scene::GetComponent(EntityID)' being compiled with [T=Name]

这个错误出现了 3 次,每次 T 等于名称、变换和精灵(到目前为止我的游戏引擎中的每个组件)

MSDN 上的编译器错误 C2440:https://docs.microsoft.com/en-us/cpp/error-messages/compiler-errors-1/compiler-error-c2440?view=vs-2019

函数说明

ECS 设计

我的实体组件系统通过两个数组数组存储实体和组件。

第一个是componentIndexes,用于将实体索引到第二个数组中,components将return基于索引,一个组件。本质上,如果您听说过使用“稀疏”数组和 'dense' 数组压缩数组,componentIndexes 是我的稀疏数组,components 是我的密集数组,用于在我的场景中存储组件.

简而言之:

componentIndexes 存储 CompIndex 类型组件的索引,前提是:[ComponentType][entityID] |参见图 1A

components存储Component*类型的组件数据,提供:[ComponentType][index]|参见图 1B

// An array of arrays of indexes into the component arrays, acquired via Entities and Component Types
// Y - std::array of sets with each element position being representative of a Component Type ID
// & each element value being X
// X - std::vector of indexes with each element position being representative of an Entity ID
// & each element value being representative of the position of the component in the component containter(components)
/****EXAMPLE:****
  _E0_E1_E2_E3_   E = Entity ID | C = Component ID | i = Index
C0|i0|i1|~~|i2|     **A ~~ IS REPRESENTATIVE OF A NULL/NAN VALUE (of actual value equal to -1)**
  |--+--+--+--+-    Entity 0 has only Component 0 at index 0
C1|~~|~~|i0|i1|     Entity 1 has Components 0(indx1) and 2(indx0)
  |--+--+--+--+-    Entity 2 has only Component 1(indx0)
C2|~~|i0|~~|i1|     Entity 3 has Components 0(indx2), 1(indx1), and 2(indx1)
****************/
std::array<std::vector<CompIndex>, TOTAL_COMPONENT_TYPES> componentIndexes;

Graphic 1A | componentIndexes

// An array of vectors of Component*s, holding each components' data
// Y - std::array of vectors with each element position being representative of a Component Type
// & each element value being X
// X - std::vector of Component*s with each element position being indexed into via Entity IDs
// & each element value holding the data concerning a certain component
/****EXAMPLE:****
  _i0_i1_i2_i3_   CD = Component Data | C = Component ID | i = Index
C0|CD|CD|CD|~~|     Component 0 has 3 components
  |--+--+--+--+-
C1|CD|CD|~~|~~|     Component 1 has 2 components
  |--+--+--+--+-
C2|CD|CD|~~|~~|     Component 2 has 2 components
****************/
std::array<std::vector<Component*>, TOTAL_COMPONENT_TYPES> components;

Graphic 1B | components

逐行

CompTypeID typeID = TypeToID<T>();

此函数按预期工作,returns 是一个枚举 CompTypeID,用作组件类型的数字表示。这可用于根据我定位的组件类型索引到我的 componentIndexescomponents 数组。

if (HasComponent<T>(entityID)) {
    . . .
}
else return nullptr;

此函数按预期工作,return无论实体是否具有特定组件都是布尔值。如果提供的类型 T 不是组件或者实体没有该组件,它将 return false。 Else return nullptr 是该函数不言自明的失败状态。

CompIndex index = componentIndexes[typeID][entityID];
return components[typeID][index];

这些函数获取 return 类型为 Component* 的组件。 ECS 设计 中解释了这是如何实现的。 components 中的每个元素都应该是存储的组件——这些是结构并且继承自空结构 Component(例如 struct Name : Component {};)。因为它们是从父 class 组件继承的,所以我应该能够将每个子“组件”存储在 components 中,因为它存储了 Component*.

的类型

我认为问题是什么

GetComponent<T>(EntityID) 的类型 T 将等于 Name、Transform 或 Sprite。 components 的存储类型是 Component*。虽然 Name*s、Transform*s 和 Sprite*s 都可以存储在 Component* 中,但编译器似乎不允许我将类型 Component* 的数据分配给Name*(或其他组件)类型的数据 - 所以虽然理论上我应该能够从我的 Component* 数组中获取名称数据,但我无法这样做,因为我可以'不要将它作为 Name*.

从 GetComponent 函数发送出去

我担心这个问题可能是由于对将子项存储在父项数组中的工作方式的根本误解引起的 - 也许这只存储了子项的父项部分?

我只能想象这是通过两种方式之一解决的。

  1. 在给定父指针位置的情况下,我可能有一种方法可以获取子指针的信息 - 如果它们彼此相邻,也许可以根据父指针和子指针的大小在内存中偏移指针?我不认为 C++ 以这种能力公开内存引用,也许这种方法只能使用 Assembly,因为指针信息隐藏在类型名后面(如 int*Component*)。
  2. 另一种方法可能是使用智能指针或(如错误所附的消息所述)显式 dynamic_cast-ing 或 static_cast-ing 的一些魔法;但我不知道这 3 个东西是如何工作的,它们有什么作用,如何使用它们,或者什么时候应该使用它们。

这些是我最好的猜测 - 它们真的是我在黑暗中拍摄的。如果您有更好的想法,请分享,我会尽力研究。这是一个编译器错误,我在 Google 搜索中几乎找不到文档或谈论它,所以我认为对此做出贡献是合适的。

免责声明

有时我发现我错过了一些基本信息,大多数人由于结构化 class 课程很早就学会了这些信息。我猜这是我将学习智能指针和动态指针的地方。

资源

Scene.h

#ifndef SCENE_H
#define SCENE_H
#pragma once

#include "Components.h"
#include "Systems.h"
#include "Entity.h"
#include <array>
#include <vector>
#include <set>
#include <queue>

class Scene {
private:
    // An array of arrays of indexes into the component arrays, acquired via Entities and Component Types
    // Y - std::array of sets with each element position being representative of a Component Type ID
    // & each element value being X
    // X - std::vector of indexes with each element position being representative of an Entity ID
    // & each element value being representative of the position of the component in the component container(components)
    /****EXAMPLE:****
      _E0_E1_E2_E3_   E = Entity ID | C = Component ID | i = Index
    C0|i0|i1|~~|i2|     **A ~~ IS REPRESENTATIVE OF A NULL/NAN VALUE (of actual value equal to -1)**
      |--+--+--+--+-    Entity 0 has only Component 0 at index 0
    C1|~~|~~|i0|i1|     Entity 1 has Components 0(indx1) and 2(indx0)
      |--+--+--+--+-    Entity 2 has only Component 1(indx0)
    C2|~~|i0|~~|i1|     Entity 3 has Components 0(indx2), 1(indx1), and 2(indx1)
    ****************/
    std::array<std::vector<CompIndex>, TOTAL_COMPONENT_TYPES> componentIndexes;

    // An array of vectors of Component*s, holding each components' data, acquired via Index & Component Type
    // Y - std::array of vectors with each element position being representative of a Component Type
    // & each element value being X
    // X - std::vector of Component*s with each element position being indexed into via Entity IDs
    // & each element value holding the data concerning a certain component
    /****EXAMPLE:****
      _i0_i1_i2_i3_   CD = Component Data | C = Component ID | i = Index
    C0|CD|CD|CD|~~|     Component 0 has 3 components
      |--+--+--+--+-
    C1|CD|CD|~~|~~|     Component 1 has 2 components
      |--+--+--+--+-
    C2|CD|CD|~~|~~|     Component 2 has 2 components
    ****************/
    std::array<std::vector<Component*>, TOTAL_COMPONENT_TYPES> components;

    std::queue<EntityID> oldEntityIDs;
    std::array<std::queue<CompIndex>, TOTAL_COMPONENT_TYPES> oldCompIndexes;
    EntityID GenerateNewEntityID();
    CompIndex GenerateNewCompIndex(CompTypeID compType);
public:
    Scene();

    void Update();

    EntityID NewEntity();
    EntityID GetEntityID(std::string entityName);
    Signature GetEntityComponentSignature(EntityID entityID);
    std::vector<EntityID> GetEntitiesWithComponents(Signature components);
    void DeleteEntity(EntityID entityID);

    // template<typename T> T* AddComponent(EntityID entityID); // Same issue as GetComponent
    template<typename T> T* GetComponent(EntityID entityID);
    template<typename T> bool HasComponent(EntityID entityID);
    bool HasComponents(Signature signComponents, EntityID entityID);
    template<typename T> void DeleteComponent(EntityID entityID);
    void DeleteComponents(Signature signComponents, EntityID entityID);
};

#endif // SCENE_H

Scene.cpp

#include "Scene.h"

Scene::Scene() {

}

void Scene::Update() {
    // RENDER SYSTEM
    for (EntityID entityID : GetEntitiesWithComponents(RenderSystem::required)) {
        RenderSystem::Update(GetComponent<Transform>(entityID), GetComponent<Sprite>(entityID));
    }
}

EntityID Scene::GenerateNewEntityID() {
    EntityID newID;
    if (oldEntityIDs.empty()) {
        newID = (EntityID)componentIndexes[0].size();
    }
    else {
        newID = oldEntityIDs.front();
        oldEntityIDs.pop();
    }
    return newID;
}

CompIndex Scene::GenerateNewCompIndex(CompTypeID typeID) {
    CompIndex newIndex;
    if (oldCompIndexes[typeID].empty()) {
        newIndex = components[typeID].size();
    }
    else {
        newIndex = oldCompIndexes[typeID].front();
        oldCompIndexes[typeID].pop();
    }
    return newIndex;
}

EntityID Scene::NewEntity() {
    // generate entity ID
    EntityID entityID = GenerateNewEntityID();
    // populate entity index
    for (std::vector<CompIndex> componentSparse : componentIndexes) {
        componentSparse[entityID] = NO_COMPONENT;
    }
    // add default entity components
    //AddComponent<Name>(entityID);
    //AddComponent<Transform>(entityID);
    return entityID;
}

EntityID Scene::GetEntityID(std::string entityName) {
    for (EntityID entityID : GetEntitiesWithComponents(IDName)) {
        if (GetComponent<Name>(entityID)->name == entityName) {
            return entityID;
        }
    }
    return 0; // TODO: resolve "NO_ENTITY" case
}

Signature Scene::GetEntityComponentSignature(EntityID entityID) {
    Signature signature;
    // for each component type
    for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
        // if component type is on entity
        if (componentIndexes[i][entityID] != NO_COMPONENT) {
            // mark that bit of the signature to true
            signature.set(i);
        }
    }
    return signature;
}

std::vector<EntityID> Scene::GetEntitiesWithComponents(Signature components) {
    std::vector<EntityID> entities;
    // TODO: write GetEntittiesWithComponents
    return entities;
}

void Scene::DeleteEntity(EntityID entityID) {
    // delete entity Components
    DeleteComponents(GetEntityComponentSignature(entityID), entityID);
    // delete entity indexes
    for (std::vector<CompIndex> componentSparse : componentIndexes) {
        componentSparse.erase(componentSparse.begin() + entityID);
    }
    // add ID to list of avaliable IDs
    oldEntityIDs.push(entityID);
    return;
}

// Same issue as GetComponent()
/*template<typename T> T* Scene::AddComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID<T>();
    if (typeID == IDNotAComponent || componentsSparse[typeID][entityID] != NO_COMPONENT) {
        return nullptr;
    }
    else {
        CompIndex index = GenerateNewCompIndex(typeID);
        componentsSparse[typeID][entityID] = index;
        components[typeID][index] = new T; // TODO Ensure, does this work?? (T if not pointer could have issue?)
        return &components[typeID][index];
    }
}*/

template<typename T> T* Scene::GetComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID<T>();
    if (HasComponent<T>(entityID)) {
        CompIndex index = componentIndexes[typeID][entityID];
        return components[typeID][index];
    }
    else return nullptr;
}

template<typename T> bool Scene::HasComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID<T>();
    if (typeID == IDNotAComponent) {
        return false;
    }
    else if (componentIndexes[typeID][entityID] == NO_COMPONENT) {
        return false;
    }
    else return true;
}

bool Scene::HasComponents(Signature signComponents, EntityID entityID) {
    // for each component type
    for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
        // if component type is in component signature
        if (signComponents.test(i)) {
            // if component type is not on entity
            if (componentIndexes[i][entityID] == NO_COMPONENT) {
                return false;
            }
        }
    }
    return true;
}

template<typename T> void Scene::DeleteComponent(EntityID entityID) {
    CompTypeID typeID = TypeToID(T);
    if (typeID == nullptr || componentIndexes[typeID][entityID] != NO_COMPONENT) {
        return;
    }
    else {
        CompIndex index = componentIndexes[typeID][entityID];
        // delete component
        delete components[typeID][index];
        components[typeID][index] = nullptr;
        // delete component index
        componentIndexes[typeID][entityID] = NO_COMPONENT;
        oldCompIndexes[typeID].push(index);
        return;
    }
}

void Scene::DeleteComponents(Signature signComponents, EntityID entityID) {
    // for each component type
    for (CompTypeID i = (CompTypeID)0; i < TOTAL_COMPONENT_TYPES; i = (CompTypeID)(i + (CompTypeID)1)) {
        // if component type is in component signature
        if (signComponents.test(i)) {
            // if component type is on entity
            if (componentIndexes[i][entityID] != NO_COMPONENT) {
                CompIndex index = componentIndexes[i][entityID];
                // delete component
                delete components[i][index];
                components[i][index] = nullptr;
                // delete component index
                componentIndexes[i][entityID] = NO_COMPONENT;
                oldCompIndexes[i].push(index);
            }
        }
    }
    return;
}

Entity.h

#ifndef ENTITY_H
#define ENTITY_H
#pragma once

#include <limits>
#include <cstdint>

typedef std::uint16_t EntityID;

// Maximum number of entities allowed in a scene
const EntityID MAX_ENTITIES = std::numeric_limits<EntityID>::max(); // 65535

#endif // ENTITY_H

Components.h

#ifndef COMPONENTS_H
#define COMPONENTS_H
#pragma once

#include <bitset>

#include "Name.h"
#include "Transform.h"
#include "Sprite.h"

enum CompTypeID {
    IDName,
    IDTransform,
    IDSprite,
    TOTAL_COMPONENT_TYPES,
    IDNotAComponent
};

template<typename T> CompTypeID TypeToID() {
    if (std::is_same<T, Name>()) {
        return IDName;
    }
    else if (std::is_same<T, Transform>()) {
        return IDTransform;
    }
    else if (std::is_same<T, Sprite>()) {
        return IDSprite;
    }
    else return IDNotAComponent;
}

// Component Signature
typedef std::bitset<TOTAL_COMPONENT_TYPES> Signature;

// Component Index
typedef int CompIndex;

const CompIndex NO_COMPONENT = -1;

#endif // COMPONENTS_H

Name.h

#ifndef NAME_H
#define NAME_H
#pragma once

#include "Component.h"
#include <string>

struct Name : Component {
public:
    std::string name;
};

#endif // NAME_H

Component.h

#ifndef COMPONENT_H
#define COMPONENT_H
#pragma once

struct Component {
};

#endif // COMPONENT_H

我可能遗漏了一些东西,因为你的 post 太长了,我没有读完,但你能不能按照错误信息所说的那样做并替换

return components[typeID][index];

return static_cast<T*>(components[typeID][index]);

据我所知,这只是一个指向基 class 的指针的常见情况,您知道它实际上指向派生的 class。 static_cast 是该问题的解决方案。

对于自学的人来说令人印象深刻的编码顺便说一句。