我一直在尝试制作一个类似于 Unity 的基于组件的系统,但使用 C++。我想知道如何 GetComponent() Unity 实现的方法有效。这是一个非常强大的功能。具体来说,我想知道它使用什么样的容器来存储其组件。
我在此函数的克隆中需要的两个标准如下。 1. 我还需要返回任何继承的组件。例如,如果 SphereCollider继承对撞机,GetComponent<Collider>()将返回 SphereCollider附在GameObject ,但是 GetComponent<SphereCollider>()不会返回任何 Collider随附的。 2.我需要快速的功能。最好是使用某种散列函数。
对于标准一,我知道我可以使用类似于以下实现的东西
std::vector<Component*> components
template <typename T>
T* GetComponent()
{
for each (Component* c in components)
if (dynamic_cast<T>(*c))
return (T*)c;
return nullptr;
}
但这不符合快速的第二个标准。为此,我知道我可以做这样的事情。std::unordered_map<type_index, Component*> components
template <typename T>
T* GetComponent()
{
return (T*)components[typeid(T)];
}
但同样,这不符合第一个标准。最佳答案
由于我正在编写自己的游戏引擎并采用相同的设计,因此我想我会分享我的结果。
概述
我为我想用作 Components 的类编写了自己的 RTTI。我的 GameObject实例。打字量减少#define两个宏:CLASS_DECLARATION和 CLASS_DEFINITIONCLASS_DECLARATION声明唯一 static const std::size_t将用于识别 class类型 ( Type ) 和 virtual允许对象遍历它们的函数 class通过调用它们的同名父类函数( IsClassType )来实现层次结构。CLASS_DEFINITION定义了这两件事。即Type被初始化为 class 的字符串化版本的哈希值名称(使用 TO_STRING(x) #x ),以便 Type比较只是一个 int 比较而不是一个字符串比较。std::hash<std::string>是使用的散列函数,它保证相等的输入产生相等的输出,并且冲突次数接近于零。
除了散列冲突的低风险之外,这种实现还有一个额外的好处,即允许用户创建自己的 Component使用这些宏的类而不必引用|扩展一些主include enum class 的文件s,或使用 typeid (仅提供运行时类型,不提供父类)。
添加组件
此自定义 RTTI 简化了 Add|Get|RemoveComponent 的调用语法仅指定 template类型,就像 Unity 一样。AddComponent方法完美地将通用引用可变参数包转发到用户的构造函数。因此,例如,用户定义的 Component -派生 class CollisionModel可以有构造函数:
CollisionModel( GameObject * owner, const Vec3 & size, const Vec3 & offset, bool active );
然后稍后用户只需调用:myGameObject.AddComponent<CollisionModel>(this, Vec3( 10, 10, 10 ), Vec3( 0, 0, 0 ), true );
注意 Vec3 的显式构造s 因为如果使用推导的初始化列表语法(如 { 10, 10, 10 }),完美转发可能无法链接不管 Vec3的构造函数声明。 std::unordered_map<std::typeindex,...> 的 3 个问题解决方案:std::tr2::direct_bases 进行层次结构遍历最终结果仍然是 map 中相同指针的重复项。 dynamic_cast需要,直接static_cast . GetComponent只使用 static const std::size_t Type的template type 作为 virtual bool IsClassType 的参数方法并迭代 std::vector< std::unique_ptr< Component > >寻找第一场比赛。GetComponents可以获取请求类型的所有组件的方法,同样包括从父类获取。static成员(member)Type可以在有和没有类的实例的情况下访问。Type是 public , 为每个 Component 声明- 派生类,...并大写以强调其灵活使用,尽管它是 POD 成员。RemoveComponent用途 C++14的 init-capture 传递相同的 static const std::size_t Type的template输入一个 lambda,所以它基本上可以进行相同的 vector 遍历,这次得到一个 iterator到第一个匹配元素。const所有这些的版本也可以很容易地实现。#ifndef TEST_CLASSES_H
#define TEST_CLASSES_H
#include <string>
#include <functional>
#include <vector>
#include <memory>
#include <algorithm>
#define TO_STRING( x ) #x
//****************
// CLASS_DECLARATION
//
// This macro must be included in the declaration of any subclass of Component.
// It declares variables used in type checking.
//****************
#define CLASS_DECLARATION( classname ) \
public: \
static const std::size_t Type; \
virtual bool IsClassType( const std::size_t classType ) const override; \
//****************
// CLASS_DEFINITION
//
// This macro must be included in the class definition to properly initialize
// variables used in type checking. Take special care to ensure that the
// proper parentclass is indicated or the run-time type information will be
// incorrect. Only works on single-inheritance RTTI.
//****************
#define CLASS_DEFINITION( parentclass, childclass ) \
const std::size_t childclass::Type = std::hash< std::string >()( TO_STRING( childclass ) ); \
bool childclass::IsClassType( const std::size_t classType ) const { \
if ( classType == childclass::Type ) \
return true; \
return parentclass::IsClassType( classType ); \
} \
namespace rtti {
//***************
// Component
// base class
//***************
class Component {
public:
static const std::size_t Type;
virtual bool IsClassType( const std::size_t classType ) const {
return classType == Type;
}
public:
virtual ~Component() = default;
Component( std::string && initialValue )
: value( initialValue ) {
}
public:
std::string value = "uninitialized";
};
//***************
// Collider
//***************
class Collider : public Component {
CLASS_DECLARATION( Collider )
public:
Collider( std::string && initialValue )
: Component( std::move( initialValue ) ) {
}
};
//***************
// BoxCollider
//***************
class BoxCollider : public Collider {
CLASS_DECLARATION( BoxCollider )
public:
BoxCollider( std::string && initialValue )
: Collider( std::move( initialValue ) ) {
}
};
//***************
// RenderImage
//***************
class RenderImage : public Component {
CLASS_DECLARATION( RenderImage )
public:
RenderImage( std::string && initialValue )
: Component( std::move( initialValue ) ) {
}
};
//***************
// GameObject
//***************
class GameObject {
public:
std::vector< std::unique_ptr< Component > > components;
public:
template< class ComponentType, typename... Args >
void AddComponent( Args&&... params );
template< class ComponentType >
ComponentType & GetComponent();
template< class ComponentType >
bool RemoveComponent();
template< class ComponentType >
std::vector< ComponentType * > GetComponents();
template< class ComponentType >
int RemoveComponents();
};
//***************
// GameObject::AddComponent
// perfect-forwards all params to the ComponentType constructor with the matching parameter list
// DEBUG: be sure to compare the arguments of this fn to the desired constructor to avoid perfect-forwarding failure cases
// EG: deduced initializer lists, decl-only static const int members, 0|NULL instead of nullptr, overloaded fn names, and bitfields
//***************
template< class ComponentType, typename... Args >
void GameObject::AddComponent( Args&&... params ) {
components.emplace_back( std::make_unique< ComponentType >( std::forward< Args >( params )... ) );
}
//***************
// GameObject::GetComponent
// returns the first component that matches the template type
// or that is derived from the template type
// EG: if the template type is Component, and components[0] type is BoxCollider
// then components[0] will be returned because it derives from Component
//***************
template< class ComponentType >
ComponentType & GameObject::GetComponent() {
for ( auto && component : components ) {
if ( component->IsClassType( ComponentType::Type ) )
return *static_cast< ComponentType * >( component.get() );
}
return *std::unique_ptr< ComponentType >( nullptr );
}
//***************
// GameObject::RemoveComponent
// returns true on successful removal
// returns false if components is empty, or no such component exists
//***************
template< class ComponentType >
bool GameObject::RemoveComponent() {
if ( components.empty() )
return false;
auto & index = std::find_if( components.begin(),
components.end(),
[ classType = ComponentType::Type ]( auto & component ) {
return component->IsClassType( classType );
} );
bool success = index != components.end();
if ( success )
components.erase( index );
return success;
}
//***************
// GameObject::GetComponents
// returns a vector of pointers to the the requested component template type following the same match criteria as GetComponent
// NOTE: the compiler has the option to copy-elide or move-construct componentsOfType into the return value here
// TODO: pass in the number of elements desired (eg: up to 7, or only the first 2) which would allow a std::array return value,
// except there'd need to be a separate fn for getting them *all* if the user doesn't know how many such Components the GameObject has
// TODO: define a GetComponentAt<ComponentType, int>() that can directly grab up to the the n-th component of the requested type
//***************
template< class ComponentType >
std::vector< ComponentType * > GameObject::GetComponents() {
std::vector< ComponentType * > componentsOfType;
for ( auto && component : components ) {
if ( component->IsClassType( ComponentType::Type ) )
componentsOfType.emplace_back( static_cast< ComponentType * >( component.get() ) );
}
return componentsOfType;
}
//***************
// GameObject::RemoveComponents
// returns the number of successful removals, or 0 if none are removed
//***************
template< class ComponentType >
int GameObject::RemoveComponents() {
if ( components.empty() )
return 0;
int numRemoved = 0;
bool success = false;
do {
auto & index = std::find_if( components.begin(),
components.end(),
[ classType = ComponentType::Type ]( auto & component ) {
return component->IsClassType( classType );
} );
success = index != components.end();
if ( success ) {
components.erase( index );
++numRemoved;
}
} while ( success );
return numRemoved;
}
} /* rtti */
#endif /* TEST_CLASSES_H */
#include "Classes.h"
using namespace rtti;
const std::size_t Component::Type = std::hash<std::string>()(TO_STRING(Component));
CLASS_DEFINITION(Component, Collider)
CLASS_DEFINITION(Collider, BoxCollider)
CLASS_DEFINITION(Component, RenderImage)
#include <iostream>
#include "Classes.h"
#define MORE_CODE 0
int main( int argc, const char * argv ) {
using namespace rtti;
GameObject test;
// AddComponent test
test.AddComponent< Component >( "Component" );
test.AddComponent< Collider >( "Collider" );
test.AddComponent< BoxCollider >( "BoxCollider_A" );
test.AddComponent< BoxCollider >( "BoxCollider_B" );
#if MORE_CODE
test.AddComponent< RenderImage >( "RenderImage" );
#endif
std::cout << "Added:\n------\nComponent\t(1)\nCollider\t(1)\nBoxCollider\t(2)\nRenderImage\t(0)\n\n";
// GetComponent test
auto & componentRef = test.GetComponent< Component >();
auto & colliderRef = test.GetComponent< Collider >();
auto & boxColliderRef1 = test.GetComponent< BoxCollider >();
auto & boxColliderRef2 = test.GetComponent< BoxCollider >(); // boxColliderB == boxColliderA here because GetComponent only gets the first match in the class hierarchy
auto & renderImageRef = test.GetComponent< RenderImage >(); // gets &nullptr with MORE_CODE 0
std::cout << "Values:\n-------\ncomponentRef:\t\t" << componentRef.value
<< "\ncolliderRef:\t\t" << colliderRef.value
<< "\nboxColliderRef1:\t" << boxColliderRef1.value
<< "\nboxColliderRef2:\t" << boxColliderRef2.value
<< "\nrenderImageRef:\t\t" << ( &renderImageRef != nullptr ? renderImageRef.value : "nullptr" );
// GetComponents test
auto allColliders = test.GetComponents< Collider >();
std::cout << "\n\nThere are (" << allColliders.size() << ") collider components attached to the test GameObject:\n";
for ( auto && c : allColliders ) {
std::cout << c->value << '\n';
}
// RemoveComponent test
test.RemoveComponent< BoxCollider >(); // removes boxColliderA
auto & boxColliderRef3 = test.GetComponent< BoxCollider >(); // now this is the second BoxCollider "BoxCollider_B"
std::cout << "\n\nFirst BoxCollider instance removed\nboxColliderRef3:\t" << boxColliderRef3.value << '\n';
#if MORE_CODE
// RemoveComponent return test
int removed = 0;
while ( test.RemoveComponent< Component >() ) {
++removed;
}
#else
// RemoveComponents test
int removed = test.RemoveComponents< Component >();
#endif
std::cout << "\nSuccessfully removed (" << removed << ") components from the test GameObject\n";
system( "PAUSE" );
return 0;
}
Added:
------
Component (1)
Collider (1)
BoxCollider (2)
RenderImage (0)
Values:
-------
componentRef: Component
colliderRef: Collider
boxColliderRef1: BoxCollider_A
boxColliderRef2: BoxCollider_A
renderImageRef: nullptr
There are (3) collider components attached to the test GameObject:
Collider
BoxCollider_A
BoxCollider_B
First BoxCollider instance removed
boxColliderRef3: BoxCollider_B
Successfully removed (3) components from the test GameObject
旁注:授予 Unity 使用 Destroy(object)而不是 RemoveComponent ,但我的版本现在适合我的需求。
关于c++ - 在 Unity 中用 C++ 实现组件系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44105058/
我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server
我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里编辑 3.解析依赖到项目中