课程作业
https://games104.boomingtech.com/sc/course-work/
作业 3
FSM
动画组件的 FSM 部分我没有找到啊……搞不懂
Move
主要是把运动分量分解成水平分量和竖直分量
运动的水平分量要减去碰撞方向上的分量,才能得到滑移的,沿着碰撞方向的垂直方向的分量
竖直分量就是按照原来的做法,在竖直分量加上之后的位置检查是否重叠,如果重叠则不运动到此位置,也就是不在最终位置加上此竖直分量
Vector3 CharacterController::move(const Vector3& current_position, const Vector3& displacement)
{
std::shared_ptr<PhysicsScene> physics_scene =
g_runtime_global_context.m_world_manager->getCurrentActivePhysicsScene().lock();
ASSERT(physics_scene);
Vector3 horizontal_displacement = Vector3(displacement.x, 0, displacement.z);
Vector3 horizontal_direction = horizontal_displacement.normalisedCopy();
Vector3 vertical_displacement = Vector3(0, displacement.y, 0);
Vector3 horizontal_move_position = current_position + displacement;
Vector3 final_position = current_position;
Transform final_transform = Transform(final_position, Quaternion::IDENTITY, Vector3::UNIT_SCALE);
std::vector<PhysicsHitInfo> hits;
if (physics_scene->sweep(m_rigidbody_shape,
final_transform.getMatrix(),
horizontal_direction,
horizontal_displacement.length(),
hits))
{
final_position += horizontal_displacement - hits[0].hit_normal.dotProduct(horizontal_displacement) /
hits[0].hit_normal.length() *
hits[0].hit_normal.normalisedCopy();
}
else
{
final_position += horizontal_displacement;
}
final_transform.m_position = final_position + vertical_displacement;
if (!physics_scene->isOverlap(m_rigidbody_shape, final_transform.getMatrix()))
{
final_position += vertical_displacement;
}
return final_position;
}
作业 4
我看了别人的一个反射的做法
https://zhuanlan.zhihu.com/p/266297626
别人是
反射宏
CLASS
CLASS(class_name,tags...)
用以反射类型的申明,替换 class 字段,class_name 参数填写类型名称,需要继承基类则直接按继承写法写在该参数内,tags 参数填写传递给反射系统的反射标签,多个标签需以‘,’分隔;
#define CLASS(class_name, ...) class class_name
例:
namespace Piccolo
{
REFLECTION_TYPE(AnimationComponent)
CLASS(AnimationComponent : public Component, WhiteListFields)
{
REFLECTION_BODY(AnimationComponent)
public:
对 Unreal 的对比:
类说明符 UCLASS 和元数据(meta)可以定义引擎和编辑器特定的行为。
类声明的语法如下所示:
UCLASS([specifier, specifier, ...], [meta(key=value, key=value, ...)])
class ClassName : public ParentName
{
GENERATED_BODY()
}
声明包含一个类的标准 C++ 类声明。在标准声明之上,描述符(如类说明符和元数据)将被传递到 UCLASS 宏。它们用于创建被声明类的 UClass,它可被看作引擎对类的专有表达。
此外,GENERATED_BODY() 宏必须被放置在类体的最前方。
看到了 Epic 社区经理对 UE4 的介绍
https://zhuanlan.zhihu.com/insideue4
STRUCT
STRUCT(struct_name,tags...)
用以反射结构体的申明,替换 struct 字段, struct_name 参数填写结构体名称,tags 参数填写传递给反射系统的反射标签,多个标签需以‘,’分隔;
#define STRUCT(struct_name, ...) struct struct_name
例:
REFLECTION_TYPE(GameObjectMaterialDesc)
STRUCT(GameObjectMaterialDesc, Fields)
{
REFLECTION_BODY(GameObjectMaterialDesc)
std::string m_base_color_texture_file;
std::string m_metallic_roughness_texture_file;
std::string m_normal_texture_file;
std::string m_occlusion_texture_file;
std::string m_emissive_texture_file;
bool m_with_texture {false};
};
对 Unreal 的对比:
USTRUCT(BlueprintType)
struct FPerson
{
GENERATED_USTRUCT_BODY()
REFLECTION_TYPE
REFLECTION_TYPE (class_name)
用以提供反射访问器的声明,配合 CLASS 使用,写在类型定义前,class_name
填写类型名称
#define REFLECTION_TYPE(class_name) \
namespace Reflection \
{ \
namespace TypeFieldReflectionOparator \
{ \
class Type##class_name##Operator; \
} \
};
其中的反斜杠是续行符。C 语言的宏要求只能在同一行,是不能跨行的。这里的反斜杠就是告诉编译器,我这里虽然换行了,但其实还是一行,是同一个宏的意思。
define 中的 #
是字符串化的意思,出现在宏定义中的 #
是把跟在后面的参数转成一个字符串;
例:
#define strcpy__(dst, src) strcpy(dst, #src)
strcpy__(buff,abc)
相当于 strcpy__(buff,“abc”)
##
是连接符号,把参数连接在一起
例:
#define FUN(arg) my##arg
FUN(ABC)
等价于 myABC
综上可以看出,这里就是新创建了一个名字为 Type##class_name##Operator
的,在特殊的命名空间中的类
例:
namespace Piccolo
{
REFLECTION_TYPE(AnimationComponent)
CLASS(AnimationComponent : public Component, WhiteListFields)
{
REFLECTION_BODY(AnimationComponent)
public:
REFLECTION_BODY
REFLECTION_BODY(class_name)
对访问器进行友元声明,允许访问器访问其私有属性,写在类型定义内第一行,class_name
填写类型名称,需配合 REFLECTION_TYPE
,CLASS
使用
#define REFLECTION_BODY(class_name) \
friend class Reflection::TypeFieldReflectionOparator::Type##class_name##Operator; \
friend class Serializer;
// public: virtual std::string getTypeName() override {return #class_name;}
这里是,在 REFLECTION_TYPE
定义好了 Type##class_name##Operator
类之后,将它定义为这个 class_name
类的友元函数
例:
namespace Piccolo
{
REFLECTION_TYPE(AnimationComponent)
CLASS(AnimationComponent : public Component, WhiteListFields)
{
REFLECTION_BODY(AnimationComponent)
public:
反射标签
Fields
表明类型或结构体中除特殊标记的属性外(参见 Disable),所有属性均需反射,该标签需在 CLASS 或 STRUCT 中声明。如图 6 所示,MotorComponentRes 类型标记了 Fields 标签,因此其所有属性都支持反射。
REFLECTION_TYPE(MotorComponentRes)
CLASS(MotorComponentRes, Fields)
{
REFLECTION_BODY(MotorComponentRes);
public:
MotorComponentRes() = default;
~MotorComponentRes();
float m_move_speed { 0.f};
float m_jump_height {0.f};
float m_max_move_speed_ratio { 0.f};
float m_max_sprint_speed_ratio { 0.f};
float m_move_acceleration {0.f};
float m_sprint_acceleration { 0.f};
Reflection::ReflectionPtr<ControllerConfig> m_controller_config;
};
WhiteListFields
表明类型或结构体中只有标记过的属性才允许反射(参见 Enable),该标签需在CLASS 或 STRUCT 中声明。如图 7 所示,MeshComponent 类型标记了WhiteListFields 标签,因此只有 m_mesh_res 才允许反射,
REFLECTION_TYPE(MeshComponent)
CLASS(MeshComponent : public Component, WhiteListFields)
{
REFLECTION_BODY(MeshComponent)
public:
MeshComponent() {};
void postLoadResource(std::weak_ptr<GObject> parent_object) override;
const std::vector<GameObjectPartDesc>& getRawMeshes() const { return m_raw_meshes; }
void tick(float delta_time) override;
private:
META(Enable)
MeshComponentRes m_mesh_res;
std::vector<GameObjectPartDesc> m_raw_meshes;
};
Disable
表明该属性无需反射,该标签需要在属性前以 META 参数的形式声明,且只有在CLASS
或 STRUCT
中声明为 Fields
时有效。如图 8 所示,AnimationComponentRes
类型标记了 Fields
签,animation_result
标记了 Disable
,因此它不会被反射。
REFLECTION_TYPE(AnimationComponentRes)
CLASS(AnimationComponentRes, Fields)
{
REFLECTION_BODY(AnimationComponentRes);
public:
std::string skeleton_file_path;
BlendState blend_state;
// animation to skeleton map
float frame_position; // 0-1
META(Disable)
AnimationResult animation_result;
};
Enable
表明该属性需要反射,该标签需要在属性前以 META
参数的形式声明,且只有在CLASS
或 STRUCT
中声明为 WhiteListFields
时有效。如
REFLECTION_TYPE(MeshComponent)
CLASS(MeshComponent : public Component, WhiteListFields)
{
REFLECTION_BODY(MeshComponent)
public:
MeshComponent() {};
void postLoadResource(std::weak_ptr<GObject> parent_object) override;
const std::vector<GameObjectPartDesc>& getRawMeshes() const { return m_raw_meshes; }
void tick(float delta_time) override;
private:
META(Enable)
MeshComponentRes m_mesh_res;
std::vector<GameObjectPartDesc> m_raw_meshes;
};
反射功能类
FieldAccessor
属性访问器类,提供属性名称,属性类型名称以及 set/get 方法。
主要方法:
const char* getFieldName()
:获取属性名称;
const char* getFieldTypeName()
:获取属性的类型名称;
bool getTypeMeta(TypeMeta& filed_type)
:获取属性的类型元信息(参见TypeMeta),filed_type 为返回的类型元信息,方法本身返回一个布尔值,只有当属性类型也支持反射时,才会为 true;
void* get(void* instance)
:instance 为属性所属类型的实例指针,方法返回属性的指针;
void set(void* instance, void* value)
: instance 为属性所属类型的实例指针,value 为要设置的值的指针
例:
void EditorUI::createLeafNodeUI(Reflection::ReflectionInstance& instance)
{
Reflection::FieldAccessor* fields;
int fields_count = instance.m_meta.getFieldsList(fields);
for (size_t index = 0; index < fields_count; index++)
{
auto field = fields[index];
if (field.isArrayType())
{
Reflection::ArrayAccessor array_accessor;
if (Reflection::TypeMeta::newArrayAccessorFromName(field.getFieldTypeName(), array_accessor))
{
void* field_instance = field.get(instance.m_instance);
int array_count = array_accessor.getSize(field_instance);
m_editor_ui_creator["TreeNodePush"](
std::string(field.getFieldName()) + "[" + std::to_string(array_count) + "]", nullptr);
通过这个例子大概知道,FieldAccessor
访问的东西要通过 getFieldsList
拿到
getFieldsList
是 m_meta
的成员,m_meta
是类 ReflectionInstance
的成员
在 ReflectionInstance
中我没看到 m_meta
能通过一个函数赋值,所以我觉得应该是在创建实例的时候就做好了
所以查找构造函数的引用,找到
editor_ui.cpp void EditorUI::createLeafNodeUI(Reflection::ReflectionInstance& instance)
if (ui_creator_iterator == m_editor_ui_creator.end())
{
Reflection::TypeMeta field_meta =
Reflection::TypeMeta::newMetaFromName(field.getFieldTypeName());
if (field.getTypeMeta(field_meta))
{
auto child_instance =
Reflection::ReflectionInstance(field_meta, field.get(instance.m_instance));
这里是用 getFieldTypeName()
获取属性的类型名字,然后用 TypeMeta::newMetaFromName
创建一个 TypeMeta
那样的话,要看的方法就都在 TypeMeta
中了
先看 getFieldsList
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.cpp
int TypeMeta::getFieldsList(FieldAccessor*& out_list)
{
int count = m_fields.size();
out_list = new FieldAccessor[count];
for (int i = 0; i < count; ++i)
{
out_list[i] = m_fields[i];
}
return count;
}
getFieldsList
的返回结果都是从 TypeMeta
类的成员 m_fields
拷贝来的
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.h
std::vector<FieldAccessor, std::allocator<FieldAccessor>> m_fields;
TypeMeta
类型元信息,提供类型的名称、属性访问器列表等。
主要方法:
TypeMeta newMetaFromName(std::string type_name)
:静态方法,可通过类型名称直接创建 TypeMeta;
std::string getTypeName()
:返回类型名称;
int getFieldsList(FieldAccessor*& out_list)
:out_list 为 FieldAccessor 数组头指针,该方法会通过 out_list 返回类型所有反射属性的访问器数组,方法本身返回数组大小;
int getBaseClassReflectionInstanceList(ReflectionInstance*& out_list,void*instance)
:instance 为类型实例指针,out_list 为 ReflectionInstance 数组头指针,该方法会通过 out_list 返回类型所有基类的 ReflectionInstance 数组,方法本身返回数组大小。
ReflectionInstance
反射实例,包含了类型的元信息及实例指针。
属性:
m_meta:类型为 TypeMeta,表示类型元信息;
m_instance:类型为 void*,表示实例的指针;
std::allocator
std::allocator 的用法参考:
https://blog.youkuaiyun.com/fengbingchun/article/details/78943527/
我觉得其实这个 利用 std::allocator 实现自定义的 vector 类 的例子真的很好理解
https://www.cnblogs.com/m4ch0/p/7072023.html
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
template <typename Tp>
class Vector
{
public:
Vector()
: _elems(NULL)
, _first_free(NULL)
, _end(NULL)
{}
~Vector()
{
if(_elems)
{
while(_elems != _first_free)
_alloc.destroy(--_first_free);
_alloc.deallocate(_elems,capacity());
}
}
void push_back(const Tp & value)
{
if(size() == capacity())
{
reallocate();
}
_alloc.construct(_first_free++,value);
}
void pop_back()
{
if(size() > 0)
{
_alloc.destory(--_first_free);
}
}
size_t size() const
{
return _first_free - _elems;
}
size_t capacity() const
{
return _end - _elems;
}
Tp & operator[](size_t idx)
{
return _elems[idx];
}
private:
void reallocate()
{
size_t oldCapacity = capacity();
size_t newCapacity = oldCapacity == 0 ? 1 : oldCapacity * 2;
Tp * newElems = _alloc.allocate(newCapacity);
if(_elems)
{
std::uninitialized_copy(_elems,_first_free,newElems);
while(_elems != _first_free)
_alloc.destroy(--_first_free);
_alloc.deallocate(_elems,oldCapacity);
}
_elems = newElems;
_first_free = _elems + oldCapacity;
_end = _elems + newCapacity;
}
private:
static std::allocator<Tp> _alloc;
Tp * _elems;
Tp * _first_free;
Tp * _end;
};
template <typename Tp>
std::allocator<Tp> Vector<Tp>::_alloc;
void display(Vector<int> & vec)
{
cout << "vec's size = " << vec.size() << endl;
cout << "vec's capacity = " << vec.capacity() << endl;
}
我的感觉就是,对于 std::allocator 类,
allocate
方法分配内存,返回一个指向分配的内存的指针,
construct
在这个分配的内存上创建元素。为了使用 allocator 返回的内存,我们必须用 construct 构造对象
我也不知道为什么就是这样要用指定的函数,应该是有特殊之处?
destory
在这个分配的内存上删除元素
使用 deallocate
退还 allocate
方法分配的内存
然后就是为了避免错误需要遵守一些约定,比如
-
还未构造对象的情况下就使用原始内存是错误的。
就是这个
allocate
方法分配内存,返回一个指向分配的内存的指针,那么如果你对这个指针还没有用construct
在这个指针指向的内存上创建元素,就解了这个指针,那就会出错 -
当我们用完对象后,必须对每个构造的元素调用 destroy 来销毁它们
一旦元素被销毁后,就可以重新使用这部分内存来保存其它 string,也可以将其归还给系统。
-
释放内存通过调用 deallocate 来完成。
我们传递给 deallocate 的指针不能为空,它必须指向由 allocate 分配的内存。而且,传递给 deallocate 的大小参数必须与调用 allocate 分配内存时提供的大小参数具有一样的值。
-
我们只能对真正构造了的元素进行 destroy 操作
这个例子真的很好……展示了实际应用
例如析构函数这里就展示了 destory
在这个分配的内存上删除元素 和 使用 deallocate
退还 allocate
方法分配的内存的区别
~Vector()
{
if(_elems)
{
while(_elems != _first_free)
_alloc.destroy(--_first_free);
_alloc.deallocate(_elems,capacity());
}
}
重新分配内存的函数这里,也展示了 allocate
方法分配内存,返回一个指向分配的内存的指针;也展示了怎么使用这个指针:使用 _elems
指向新分配的内存的指针,并且加上拷贝的旧数据的长度的偏移量,得到指向新内存中的剩余空间的指针,之后插入和删除元素的时候就操控这个 _elems
指针
他还展示了众所周知的,扩容就扩容一倍,并且直接把旧内存中的数据直接拷贝到新内存中,释放旧内存,是怎么做的
void reallocate()
{
size_t oldCapacity = capacity();
size_t newCapacity = oldCapacity == 0 ? 1 : oldCapacity * 2;
Tp * newElems = _alloc.allocate(newCapacity);
if(_elems)
{
std::uninitialized_copy(_elems,_first_free,newElems);
while(_elems != _first_free)
_alloc.destroy(--_first_free);
_alloc.deallocate(_elems,oldCapacity);
}
_elems = newElems;
_first_free = _elems + oldCapacity;
_end = _elems + newCapacity;
}
像是我第一次看的那个文章,其中给出了 std::uninitialized_copy
但是我不知道这个具体怎么用,现在知道了,就是用来拷贝 std::allocator 类的 construct
构造的元素,因为这个构造的元素是 construct
这个函数构造的,应该是有点特殊的(我猜?所以不能直接用 = 而是要用 std::uninitialized_copy
?好吧这也是我猜的,但是总之这个函数就是用在这个 std::allocator 类的元素的拷贝的场合的
然后之后不是说游戏引擎自己要做 vector 类吗,知道了这个自定义 vector 的逻辑,懂得 std::allocator 类的用法,应该就可以自己做了吧
但是我还是不知道为什么要有 std::vector<FieldAccessor, std::allocator<FieldAccessor>> m_fields;
中传入 std::allocator<FieldAccessor>
这样的设计
搜了一下:
题主的问题其实可以分为两个小问题:
STL 的设计中为什么要有 allocator?
allocator 为什么要作为模板参数开放出来?
首先看问题1,为什么要有 allocator?vector 自己分配空间,自己调整策略不就完事了吗,为什么麻烦地弄个 allocator 出来?OK,如果 STL 只有一个容器,的确可以这样做,但 STL 有很多容器,vector 需要分配释放、构造销毁,map 也需要,set 也需要,deque 也需要,所有的容器都需要,作为一个程序员,当这个需求摆在面前的时候,应该怎么做,不需要过多考虑了吧?既然这是个通用的东西,我们当然要设计一个通用的模块。而且稍微细想,内存分配这种东西,和容器关系也不大,不是吗?所以我们就把内存分配释放、构造销毁的操作独立出来,叫它什么呢?就叫 allocator 吧!于是,allocator 出现了。
再看问题2,allocator 出现了,没错,但为什么要作为模板参数开放给使用者?我只用 vector,我根本不关系它的 allocator 是谁啊!从最简单的使用角度来看,这么说没错,STL 自己独立封装 allocator 就可以了,不必让开发者自己传入参数,但如果一个项目有自己的内存管理策略,同时又使用了 STL 库,这个时候如果不能控制 STL 的内存分配,那么 STL 的内存对于项目内存管理器来说就是黑洞,也无法进行相关优化。解决方法?很简单,把分配器作为参数开放出去,给个默认值,外部如果想要接管内存处理,只要按照格式提供定制的 allocator 就可以了,如果不想接管,用默认的就行!
至于题主说的“C++primer中提到是为了避免将内存分配和对象构造组合在一起导致不必要的浪费”,这说的其实是 allocator 的实现为什么要区分 allocate 和 construct,和为什么要有 allocator 没有关系。
作者:桃溪小小生
链接:https://www.zhihu.com/question/274802525/answer/2076217136
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这是讲的为什么要有 allocator
然后又看了一个人的
https://www.cnblogs.com/whyandinside/articles/2221675.html
他的意思是说,想要让多个 vector 都是用同一个 allocator,所以 vector 才会开放 allocator 的模板参数
但是他这里
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.h
private:
std::vector<FieldAccessor, std::allocator<FieldAccessor>> m_fields;
std::vector<MethodAccessor, std::allocator<MethodAccessor>> m_methods;
std::string m_type_name;
我确实也没有看到他们用的是同一个 allocator 啊
就很神奇
反射生成面板过程
实现思路
使用反射系统,将复杂类型拆解为 UI 控件可直接表示的原子类型,生成对应的 UI 控件,形成原子类型控件树,展示复杂类型信息。
实现过程
1.获取物体的所有 component,构造反射实例,对反射实例进行类型拆解
Piccolo-main\engine\source\editor\source\editor_ui.cpp
static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings;
auto&& selected_object_components = selected_object->getComponents();
for (auto component_ptr : selected_object_components)
{
m_editor_ui_creator["TreeNodePush"](("<" + component_ptr.getTypeName() + ">").c_str(), nullptr);
auto object_instance = Reflection::ReflectionInstance(
Piccolo::Reflection::TypeMeta::newMetaFromName(component_ptr.getTypeName().c_str()),
component_ptr.operator->());
createClassUI(object_instance);
m_editor_ui_creator["TreeNodePop"](("<" + component_ptr.getTypeName() + ">").c_str(), nullptr);
}
使用 getComponents()
获取选中物体的所有 component 对象
使用 TypeMeta::newMetaFromName
为每个 component 对象构造反射实例,并依次调用 createClassUI
对反射实例进行类型拆解。
其中, getComponents()
返回的是 Object 对象的 m_components
成员,该成员在使用 GObject::load
加载 Object 时被赋值
Piccolo-main\engine\source\runtime\function\framework\object\object.cpp
bool GObject::load(const ObjectInstanceRes& object_instance_res)
{
// clear old components
m_components.clear();
setName(object_instance_res.m_name);
// load object instanced components
m_components = object_instance_res.m_instanced_components;
for (auto component : m_components)
{
if (component)
{
component->postLoadResource(weak_from_this());
}
}
// load object definition components
m_definition_url = object_instance_res.m_definition;
ObjectDefinitionRes definition_res;
const bool is_loaded_success = g_runtime_global_context.m_asset_manager->loadAsset(m_definition_url, definition_res);
if (!is_loaded_success)
return false;
for (auto loaded_component : definition_res.m_components)
{
const std::string type_name = loaded_component.getTypeName();
// don't create component if it has been instanced
if (hasComponent(type_name))
continue;
loaded_component->postLoadResource(weak_from_this());
m_components.push_back(loaded_component);
}
return true;
}
加载 Object 分为两步,第一步是从 ObjectInstanceRes 读数据,第二步是从 ObjectDefinitionRes 读数据
Piccolo-main\engine\source_generated\serializer\all_serializer.ipp
ObjectInstanceRes 中的 m_instanced_components
是从 json 中读的
template<>
ObjectInstanceRes& Serializer::read(const Json& json_context, ObjectInstanceRes& instance){
assert(json_context.is_object());
if(!json_context["name"].is_null()){
Serializer::read(json_context["name"], instance.m_name);
}
if(!json_context["definition"].is_null()){
Serializer::read(json_context["definition"], instance.m_definition);
}
if(!json_context["instanced_components"].is_null()){
assert(json_context["instanced_components"].is_array());
Json::array array_m_instanced_components = json_context["instanced_components"].array_items();
instance.m_instanced_components.resize(array_m_instanced_components.size());
for (size_t index=0; index < array_m_instanced_components.size();++index){
Serializer::read(array_m_instanced_components[index], instance.m_instanced_components[index]);
}
}
return instance;
}
这个代码就非常直观……
他最后是把 Reflection::ReflectionPtr
类型的 m_instanced_components
数组的长度重新定义了,然后就对这些新出现的元素,把相应位置的 json 的数据读成 string,然后根据这个 string 给元素赋值
Piccolo-main\engine\source\runtime\core\meta\serializer\serializer.h
template<typename T>
static T*& read(const Json& json_context, Reflection::ReflectionPtr<T>& instance)
{
std::string type_name = json_context["$typeName"].string_value();
instance.setTypeName(type_name);
return readPointer(json_context, instance.getPtrReference());
}
2.类型拆解递归拆解基类反射示例,生成基类 UI
createClassUI
先获取该类型的所有基类反射实例,并递归调用该方法生成基类的 UI,然后调用 creatLeafNodeUI
方法生成该类型属性 UI。
Piccolo-main\engine\source\editor\source\editor_ui.cpp
void EditorUI::createClassUI(Reflection::ReflectionInstance& instance)
{
Reflection::ReflectionInstance* reflection_instance;
int count = instance.m_meta.getBaseClassReflectionInstanceList(reflection_instance, instance.m_instance);
for (int index = 0; index < count; index++)
{
createClassUI(reflection_instance[index]);
}
createLeafNodeUI(instance);
if (count > 0)
delete[] reflection_instance;
}
就很妙,递归基类,这样就可以适应类的继承
这里要删除得到的基类列表,就很神奇,因为我没看到 new 居然也要 delete
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.cpp
int TypeMeta::getBaseClassReflectionInstanceList(ReflectionInstance*& out_list, void* instance)
{
auto iter = m_class_map.find(m_type_name);
if (iter != m_class_map.end())
{
return (std::get<0>(*iter->second))(out_list, instance);
}
return 0;
}
std::get
是从 std::tuple
中取元素,
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.cpp
namespace Piccolo
{
namespace Reflection
{
const char* k_unknown_type = "UnknownType";
const char* k_unknown = "Unknown";
static std::map<std::string, ClassFunctionTuple*> m_class_map;
static std::multimap<std::string, FieldFunctionTuple*> m_field_map;
static std::multimap<std::string, MethodFunctionTuple*> m_method_map;
static std::map<std::string, ArrayFunctionTuple*> m_array_map;
m_class_map
是作用域为整个文件中的静态全局变量
怪不得 reflection.cpp 里面有多个类的定义,原来是方便使用这个静态全局变量
*iter->second
得到的是 ClassFunctionTuple*
ClassFunctionTuple 宏定义:
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.h
typedef std::tuple<GetBaseClassReflectionInstanceListFunc, ConstructorWithJson, WriteJsonByName> ClassFunctionTuple;
(std::get<0>(*iter->second))
得到是 GetBaseClassReflectionInstanceListFunc
GetBaseClassReflectionInstanceListFunc 宏定义:
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.h
typedef std::function<int(Reflection::ReflectionInstance*&, void*)> GetBaseClassReflectionInstanceListFunc;
最终就能获得一个 int……很强
那这个函数指针的值最终是谁赋予的呢,只有知道最终的函数指针才能知道传入的 out_list
发生了什么
m_class_map
通过这个函数来修改
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.cpp
void TypeMetaRegisterinterface::registerToClassMap(const char* name, ClassFunctionTuple* value)
{
if (m_class_map.find(name) == m_class_map.end())
{
m_class_map.insert(std::make_pair(name, value));
}
else
{
delete value;
}
}
这个函数在宏中被使用
Piccolo-main\engine\source\runtime\core\meta\reflection\reflection.h
#define REGISTER_BASE_CLASS_TO_MAP(name, value) TypeMetaRegisterinterface::registerToClassMap(name, value);
这个宏最终是在一些被自动生成的代码中被使用的
有些 tuple 中的 getXXXBaseClassReflectionInstanceList
是有值的,所以用了 new
Piccolo-main\engine\source_generated\reflection\motor_component.reflection.gen.h
// base class
static int getMotorComponentBaseClassReflectionInstanceList(ReflectionInstance* &out_list, void* instance){
int count = 1;
out_list = new ReflectionInstance[count];
for (int i=0;i<count;++i){
out_list[i] = TypeMetaDef(Piccolo::Component,static_cast<MotorComponent*>(instance));
}
return count;
}
有些 count 为 0,也是说他自己就是基类,所以
Piccolo-main\engine\source_generated\reflection\mesh_data.reflection.gen.h
static int getMeshDataBaseClassReflectionInstanceList(ReflectionInstance* &out_list, void* instance){
int count = 0;
return count;
}
其中 TypeMetaDef 宏就是根据传入的 class_name
获得它的 string
,创建一个 TypeMeta
实例,然后这个 TypeMeta
实例再跟着这个 class_name*
类型的实例指针一起,传入 Reflection::ReflectionInstance
,创建一个 ReflectionInstance
实例
#define TypeMetaDef(class_name, ptr) \
Piccolo::Reflection::ReflectionInstance(Piccolo::Reflection::TypeMeta::newMetaFromName(#class_name), \
(class_name*)ptr)
所以回到
Piccolo-main\engine\source\editor\source\editor_ui.cpp
void EditorUI::createClassUI(Reflection::ReflectionInstance& instance)
{
Reflection::ReflectionInstance* reflection_instance;
int count = instance.m_meta.getBaseClassReflectionInstanceList(reflection_instance, instance.m_instance);
for (int index = 0; index < count; index++)
{
createClassUI(reflection_instance[index]);
}
createLeafNodeUI(instance);
if (count > 0)
delete[] reflection_instance;
}
reflection_instance
就是父类的 ReflectionInstance
3.获取属性访问器,查找属性对应在 UI 上的构建方法,传入属性的名称和地址,进行最后的 UI 生成
在 createLeafNodeUI
方法中,获取到所有属性访问器。
遍历所有属性访问器,使用属性类型名称查找对应的构建方法,传入属性名称和属性地址,进行最后的 UI 生成。
Piccolo-main\engine\source\editor\source\editor_ui.cpp
void EditorUI::createLeafNodeUI(Reflection::ReflectionInstance& instance)
{
Reflection::FieldAccessor* fields;
int fields_count = instance.m_meta.getFieldsList(fields);
for (size_t index = 0; index < fields_count; index++)
{
auto field = fields[index];
if (field.isArrayType()) { ... }
auto ui_creator_iterator = m_editor_ui_creator.find(field.getFieldTypeName());
if (ui_creator_iterator == m_editor_ui_creator.end()) { ... }
else
{
m_editor_ui_creator[field.getFieldTypeName()](field.getFieldName(),
field.get(instance.m_instance));
}
}
delete[] fields;
}
getFieldsList(fields)
是获取了这个传入的 instance
的所有属性访问器
m_editor_ui_creator[field.getFieldTypeName()]
这一步就是根据属性名称来查找相应的属性在 UI 上的构造方法
根据 if (field.isArrayType())
判断,如果属性是 Array,那么
if (field.isArrayType())
{
Reflection::ArrayAccessor array_accessor;
if (Reflection::TypeMeta::newArrayAccessorFromName(field.getFieldTypeName(), array_accessor))
{
void* field_instance = field.get(instance.m_instance);
int array_count = array_accessor.getSize(field_instance);
m_editor_ui_creator["TreeNodePush"](
std::string(field.getFieldName()) + "[" + std::to_string(array_count) + "]", nullptr);
auto item_type_meta_item =
Reflection::TypeMeta::newMetaFromName(array_accessor.getElementTypeName());
auto item_ui_creator_iterator = m_editor_ui_creator.find(item_type_meta_item.getTypeName());
for (int index = 0; index < array_count; index++)
{
if (item_ui_creator_iterator == m_editor_ui_creator.end())
{
m_editor_ui_creator["TreeNodePush"]("[" + std::to_string(index) + "]", nullptr);
auto object_instance = Reflection::ReflectionInstance(
Piccolo::Reflection::TypeMeta::newMetaFromName(item_type_meta_item.getTypeName().c_str()),
array_accessor.get(index, field_instance));
createClassUI(object_instance);
m_editor_ui_creator["TreeNodePop"]("[" + std::to_string(index) + "]", nullptr);
}
else
{
if (item_ui_creator_iterator == m_editor_ui_creator.end())
{
continue;
}
m_editor_ui_creator[item_type_meta_item.getTypeName()](
"[" + std::to_string(index) + "]", array_accessor.get(index, field_instance));
}
}
m_editor_ui_creator["TreeNodePop"](field.getFieldName(), nullptr);
}
}
他最终也是 array_accessor.get(index, field_instance)
取到每一个元素的属性访问器,进而构造属性的 TypeMeta
实例,进而构造属性的 ReflectionInstance
实例,进而将这个属性的 ReflectionInstance
实例传入 createClassUI
但是其他的条件判断我就有点看不懂了
这里的
auto ui_creator_iterator = m_editor_ui_creator.find(field.getFieldTypeName());
if (ui_creator_iterator == m_editor_ui_creator.end())
就是说明根据 field.getFieldTypeName()
这个名字在 m_editor_ui_creator
中找不到,那就是说这个属性不是原子类型,那么就要继续拆解
auto ui_creator_iterator = m_editor_ui_creator.find(field.getFieldTypeName());
if (ui_creator_iterator == m_editor_ui_creator.end())
{
Reflection::TypeMeta field_meta =
Reflection::TypeMeta::newMetaFromName(field.getFieldTypeName());
if (field.getTypeMeta(field_meta))
{
auto child_instance =
Reflection::ReflectionInstance(field_meta, field.get(instance.m_instance));
m_editor_ui_creator["TreeNodePush"](field_meta.getTypeName(), nullptr);
createClassUI(child_instance);
m_editor_ui_creator["TreeNodePop"](field_meta.getTypeName(), nullptr);
}
else
{
if (ui_creator_iterator == m_editor_ui_creator.end())
{
continue;
}
m_editor_ui_creator[field.getFieldTypeName()](field.getFieldName(),
field.get(instance.m_instance));
}
}
这里根据 field.getTypeMeta(field_meta)
做出的判断……没看懂啊
4.m_editor_ui_creator 是一个原子类型的 UI 构建方法表
UI 生成过程中,需要对 UI 支持的原子类型进行数据和 UI 控件的绑定,因此需要建立原子类型的 UI 构建方法表,这些 UI 构建方法存放在 m_editor_ui_creator
中
Piccolo-main\engine\source\editor\include\editor_ui.h
private:
std::unordered_map<std::string, std::function<void(std::string, void*)>> m_editor_ui_creator;
对于每一种 UI 支持的原子类类型,都需要注册其属性名称及处理方法。这些注册在 EditorUI()
构造函数中完成。
Piccolo-main\engine\source\editor\source\editor_ui.cpp
EditorUI::EditorUI()
{
const auto& asset_folder = g_runtime_global_context.m_config_manager->getAssetFolder();
m_editor_ui_creator["TreeNodePush"] = [this](const std::string& name, void* value_ptr) { ... }
m_editor_ui_creator["TreeNodePop"] = [this](const std::string& name, void* value_ptr) { ... }
m_editor_ui_creator["Transform"] = [this](const std::string& name, void* value_ptr) { ... }
m_editor_ui_creator["bool"] = [this](const std::string& name, void* value_ptr) { ... }
m_editor_ui_creator["int"] = [this](const std::string& name, void* value_ptr) { ... }
...
本次作业的具体内容
1.掌握 Piccolo 中反射宏和反射标签的用法,关于反射宏和反射标签本文档第三部分会做详细说明。
2.同学们选择自己感兴趣的系统,给它管理的 Component 结构增加或修改一个属性,检查它及 Component 结构的反射宏和反射标签,确保它能够被正确的反射。(反射系统支持的原子类型可点此查看文章 B.1 节内容)
3.在 Piccolo 代码中找到 engine/source/editor/source/editor_ui.cpp,找到createClassUI ()函数,详细阅读代码,检查通过反射系统创建 UI 面板的逻辑,确保新加的属性能被正确的显示到右侧 Components Details 面板对应的component 中。(一般来说,基础功能不需要修改相应代码,如果想要实现更复杂的功能,同学们也可以自行对代码进行修改。)
4.基础的参数反射和控件生成完成后,想要继续挑战高难度的同学,可以尝试在对应系统中让新增或修改的属性按照自己预想的方式工作起来!比如接下来的例子中,可以让界面设置的值作为跳跃瞬间初速度控制角色跳跃高度。
例:现在的 Motor 系统中,我们通过 m_jump_height 来控制角色跳跃的高度,其定义位置在 engine/source/runtime/resource/res_type/components/motor.h 文件的 MotorComponentRes 类中。在 engine/source/runtime/function/framework/component/motor/motor_component.cpp 中的MotorCompont::calculatedDesiredVerticalMoveSpeed 方法内,使用该属性计算得到了跳跃瞬间初速度。假如我们想直接从界面设置跳跃瞬间初速度来控制角色的跳跃表现呢?
答案
Piccolo-main\engine\source\runtime\resource\res_type\components\motor.h
REFLECTION_TYPE(MotorComponentRes)
CLASS(MotorComponentRes, Fields)
{
REFLECTION_BODY(MotorComponentRes);
public:
MotorComponentRes() = default;
~MotorComponentRes();
float m_move_speed { 0.f};
float m_jump_height {0.f};
float m_max_move_speed_ratio { 0.f};
float m_max_sprint_speed_ratio { 0.f};
float m_move_acceleration {0.f};
float m_sprint_acceleration { 0.f};
float m_jump_init_speed {0.f}; // homework add
Reflection::ReflectionPtr<ControllerConfig> m_controller_config;
};
相关逻辑:
Piccolo-main\engine\source\runtime\function\framework\component\motor\motor_component.cpp
void MotorComponent::calculatedDesiredVerticalMoveSpeed(unsigned int command, float delta_time)
{
std::shared_ptr<PhysicsScene> physics_scene =
g_runtime_global_context.m_world_manager->getCurrentActivePhysicsScene().lock();
ASSERT(physics_scene);
if (m_motor_res.m_jump_height == 0.f)
return;
const float gravity = physics_scene->getGravity().length();
if (m_jump_state == JumpState::idle)
{
if ((unsigned int)GameCommand::jump & command)
{
m_jump_state = JumpState::rising;
m_vertical_move_speed = Math::sqrt(m_motor_res.m_jump_height * 2 * gravity);
m_jump_horizontal_speed_ratio = m_move_speed_ratio;
}
else
{
m_vertical_move_speed = 0.f;
}
}
else if (m_jump_state == JumpState::rising || m_jump_state == JumpState::falling)
{
m_vertical_move_speed -= gravity * delta_time;
if (m_vertical_move_speed <= 0.f)
{
m_jump_state = JumpState::falling;
}
}
}
竖直下落的时候
下落距离 h = 1 2 g t 2 h = \dfrac{1}{2}gt^2 h=21gt2
在 t 时刻的速度 v = g t v=gt v=gt
竖直上抛分为两个阶段,第一阶段是竖直上升,第二阶段是竖直下落,第一阶段是第二阶段的逆过程,所以直接使用竖直下落的公式,最高点速度为 0,抛出位置相当于竖直下落的 t 时刻的位置
得抛出速度为
v = 2 g h v = \sqrt{2gh} v=2gh
直接改就好了
void MotorComponent::calculatedDesiredVerticalMoveSpeed(unsigned int command, float delta_time)
{
std::shared_ptr<PhysicsScene> physics_scene =
g_runtime_global_context.m_world_manager->getCurrentActivePhysicsScene().lock();
ASSERT(physics_scene);
if (m_motor_res.m_jump_height == 0.f)
return;
const float gravity = physics_scene->getGravity().length();
if (m_jump_state == JumpState::idle)
{
if ((unsigned int)GameCommand::jump & command)
{
m_jump_state = JumpState::rising;
//m_vertical_move_speed = Math::sqrt(m_motor_res.m_jump_height * 2 * gravity);
m_vertical_move_speed = m_motor_res.m_jump_init_speed; // homwork add
m_jump_horizontal_speed_ratio = m_move_speed_ratio;
}
else
{
m_vertical_move_speed = 0.f;
}
}
else if (m_jump_state == JumpState::rising || m_jump_state == JumpState::falling)
{
m_vertical_move_speed -= gravity * delta_time;
if (m_vertical_move_speed <= 0.f)
{
m_jump_state = JumpState::falling;
}
}
}
确实很简单……但是一路上看到的很多反射函数的东西我也大概没看懂……
然后怎么自动生成的反射的代码也没看懂
怎么将 UI 和属性绑定的我也没看懂