当前位置: 首页 > news >正文

电子商务网站的建设流程是怎样的运营策划方案模板

电子商务网站的建设流程是怎样的,运营策划方案模板,托管管理系统app,个性logo设计北京vi设计公司------- scene viewport ---------- 》》》》做了两件事#xff1a;设置视口和设置相机比例 》》》》为什么要设置 m_ViewportSize 为 glm::vec2 而不是 ImVec2 ? 因为后面需要进行 ! 运算#xff0c;而 ImVec2 没有这个运算符的定义#xff0c;只有 glm::vec2 有这个运算…------- scene viewport ---------- 》》》》做了两件事设置视口和设置相机比例 》》》》为什么要设置 m_ViewportSize 为 glm::vec2 而不是 ImVec2 ? 因为后面需要进行 ! 运算而 ImVec2 没有这个运算符的定义只有 glm::vec2 有这个运算符的定义。 所以需要用 ImVec2 接收 GetContentRegionAvail 返回的 ImVec2类型的 panelSize然后将两者进行比较。 》》》》发现一个问题 其中无论对 m_Framebuffer 是否调用 Resize其渲染结果和响应好像都是一样的并没有什么影响实际上这应该对图像的分辨率有一定影响但为何我没有发现什么明确特征。 而且不调用Framebuffer-Resize的话调整窗口大小的时候图像并不会出现闪烁的现象。所以说闪烁正是因为帧缓冲对纹理附件的刷新而导致的 》》》》另一个问题 》》》》值得一提的是相机的纵横比更新函数参数需要为 float 类型的而不是 uint 类型否则会导致窗口尺寸过小时无渲染结果。 -----------ImGui Layer Events--------- 》》》》发现一个问题 Hazel中有一次维护是删除 inline 关键字的我大致看了眼觉得没有必要就没有提交到 Nut只是添加到待办里面了这导致一个问题。 操作 在简化了Input.h之后只剩下了5个函数的声明而且这些函数在简化前都是内联函数在.h文件中就已经定义过了。 建议 所以在删除掉了定义之后还应该删除inline关键字我们要确保使用 inline 关键字的时候就对函数在头文件中定义否则不添加inline关键字避免出现错误。 如果仅仅删除了定义但是没有删除inline关键字就会出现 LNK2019 的报错比如 - public: static bool __cdecl Nut::Input::IsKeyPressed(int) (?IsKeyPressedInputNutSA_NHZ) 函数 public: void __cdecl Nut::OrthoGraphicCameraController::OnUpdate(class Nut::Timestep) (?OnUpdateOrthoGraphicCameraControllerNutQEAAXVTimestep2Z) 中引用了该符号。 问题 OrthoCameraController本应使用函数可是为什么会查找不到或者说对这个函数链接失败呢 原因 这正是因为我在头文件中只声明了函数为 inline然后没在头文件中定义这个函数而是在 CPP 文件中定义它。 此时编译器会在编译时找不到这个函数的定义因为头文件已经告诉编译器这是一个 inline 函数并期望在头文件中找到它的实现。 这样会导致链接错误或重复定义错误这全都由于 CPP 文件中的定义与头文件中的 inline 声明不匹配。 》》》》提醒 记得不要写成 ImGui::IsWindowFocused;   :) ----Where to go Code review前瞻与代码审核------ 》》》》Cherno 所做的 Cherno 在这一集前8分钟修复了一个小Bug然后就算是开始审核代码了基本上讲了自己对游戏引擎的理解与期望还有接下来的进程。 》》》》我将提交一些维护代码因为这一集也没什么要做的。 》》》》调整ImGui窗口大小时闪烁的原因是我们在绘制ImGui窗口时同步更新了FrameBuffer和Camera   我们应该先更新后绘制。 问题出现原理以及解决方法 在 OnImGuiRender 函数中处理窗口大小变化时你会在每一帧的渲染过程中检查窗口尺寸并同时处理窗口尺寸。因为在窗口调整时你重新创建了帧缓冲Framebuffer那么在调整过程中的某些渲染操作可能会使用未完全准备好的新帧缓冲这就会导致显示的内容不稳定从而产生闪烁。 将窗口大小的调整逻辑提前放在 Onupdate 函数中可以确保在每一帧的渲染之前已经完成了所有的帧缓冲调整。这意味着当 ImGuiRender 执行时帧缓冲已经是正确的状态减少了因帧缓冲调整导致的闪烁现象。 未准备好的帧缓冲概念 帧缓冲Framebuffer重建过程: 当窗口大小变化时通常需要重新创建或调整帧缓冲的尺寸以适应新的窗口尺寸。这个过程包括删除旧的帧缓冲对象并创建新的对象同时可能需要重新分配或调整与之关联的纹理和深度缓冲区。 未准备好的帧缓冲: 在帧缓冲重新创建或调整的过程中新的帧缓冲可能尚未完全配置和初始化。例如新的纹理可能尚未正确分配或绑定或者深度缓冲区的设置尚未完成。在这个过渡期间帧缓冲可能处于一个不稳定的状态无法正确显示内容。 》》》另外还要注意一个问题 第一种逻辑更新方式是不可取的因为 ImGui::GetContentRefionAvail() 获取的是当前ImGui窗口的面板大小需要在 ImGui 窗口绘制范围内进行使用否则获取的Window值为nullptr 即没有找到可获取的ImGui窗口。 第二种方式可取因为每一次m_Viewport在ImGui窗口事件触发时更新后每当下一次绘制开始执行OnUpdate函数m_ViewportSize已经是新窗口尺寸而specification中存储的是旧窗口尺寸。 这时触发Resize函数随后帧缓冲m_Framebuffer更新相应的帧缓冲m_Framebuffer中存储的specification也会更新为新窗口尺寸。 下一次窗口大小改变时也是类似的操作。 逻辑 需要注意的是这里的m_Viewport值是在Onupdate函数执行后更新的也就是说图像的更新逻辑为当前绘制时先判断逻辑然后执行绘制。检测窗口尺寸变化的代码确实在绘制函数OnImGuiRender中不过没有直接绘制被更新的帧而是将新窗口尺寸保留在全局变量m_ViewportSize中在下一次绘制开启前先在Onupdate更新窗体逻辑然后在绘制函数中更新实际窗口尺寸。简而言之就是当前帧检测到变化但不更新在下一帧开始时发送变化值并执行更新 ------------ECS(实体系统--------- 》》》》76,77主要讲了EnTT的设计理念使用方法以及使用案例不是很难我尽可能的做一些笔记以理解同时上传77集的示例。 》》》》ECS 的概念 实体组件系统ECS是一种设计模式常用于游戏开发和其他需要高性能和灵活性的应用程序中。它将对象分解为“实体”、“组件”和“系统”三个主要部分。 实体是唯一的标识符组件是数据容器而系统是处理组件数据的逻辑模块。 》》实体组件系统之间的关系 1.实体 (Entity) 定义实体是系统中唯一的标识符通常是一个简单的ID比如unsigned int。它本身不包含任何数据或逻辑。 作用实体作为其他数据即组件和逻辑即系统的载体用来标识和操作这些数据或逻辑。 2. 组件 (Component) 定义组件是包含数据的结构体或类但不包含逻辑。每种组件代表一种特定的数据类型例如位置、速度、健康值等。 作用组件用于存储实体的状态信息。每个实体可以拥有一个或多个组件从而描述它的各种属性。 3. 系统 (System) 定义系统包含处理特定类型组件数据的逻辑。系统会遍历所有具有所需组件的实体执行相应的操作。 作用系统负责更新和处理组件数据实现游戏逻辑或其他功能。每个系统通常专注于一个特定的任务如物理模拟、渲染或AI决策等。 》》关系和使用方式 关系 实体与组件 实体是组件的容器通过附加不同类型的组件来描述实体的属性和行为。实体本身没有实际的数据只是一个标识符。 组件与系统 系统会查询所有拥有特定组件集合的实体并对这些实体的组件数据进行处理。例如物理系统可能会查找所有拥有位置和速度组件的实体并更新它们的位置数据。 使用方式 创建实体实例化一个新的实体并为其分配唯一标识符。 添加组件为实体附加一个或多个组件。每个组件存储实体的特定数据。 定义系统实现系统逻辑这些系统会在每一帧或特定的事件触发时运行。 更新在每一帧或游戏循环中系统会遍历实体并更新组件数据实现游戏逻辑。 示例 假设你在开发一个简单的游戏其中有角色实体Player和敌人实体Enemy。 实体 playerEntity 和 enemyEntity。 组件 PositionComponent存储实体的位置数据x, y。 VelocityComponent存储实体的速度数据vx, vy。 HealthComponent存储实体的健康值。 系统 MovementSystem遍历所有具有 PositionComponent 和 VelocityComponent 的实体并根据速度更新位置。 HealthSystem遍历所有具有 HealthComponent 的实体处理伤害、恢复等健康相关的逻辑。 》》ECS可以看做是一种数据结构吗有什么优点 与其看做是数据结构不如称之为是一种设计模式。 优点 性能ECS通过将数据组件分开存储提高了缓存效率、减少内存碎片从而提高性能。 灵活性ECS使得添加、删除和修改组件变得容易而且系统将数据和行为分离的方式也使得数据和逻辑的耦合度降低这有助于维护和扩展系统。使系统更加灵活和可扩展。 》》》》下载头文件 https://github.com/skypjack/entt/tree/master/single_include/entt 这个库只需要加载头文件因为entt是一个模板库不需要链接。 》》》》entt::registry 的原理与机制 概念 entt 的 entt::registry 是一个高度优化的数据结构用于高效地管理实体及其组件。 结构 存放实体的结构 实体ID每个实体都有一个唯一的ID这个ID用于标识实体可以在内部数据结构中索引和检索。 实体状态管理entt::registry 维护一个实体池entity pool来跟踪实体的创建和销毁。通常是通过位图或其他类似的数据结构来管理实体。 管理组件的结构 组件存储entt::registry 为每种组件类型维护一个独立的存储结构通常是一个类似于数组或向量的容器。每个组件的存储容器按所属于的 实体ID进行索引从而实现快速访问。 组件映射为了支持快速的组件查询和操作entt::registry 使用组件映射component map来跟踪哪些实体拥有特定的组件。 这种映射通常基于组件的签名signature来实现签名是实体拥有的组件的集合。 组件更新entt::registry 允许高效的组件添加、删除和更新。组件的存储和管理通常采取增量更新的方式以提高性能。 存储结构图示 假设我们有两个实体ID1 和 ID2以及两个组件位置和速度。 实体管理entt::registry 维护一个实体池跟踪所有有效的实体ID比如1 和 2。 ------------------- |    实体池         | -------------------|  位图               | |  [1] [2] [ ] [ ]    |      // 实体ID 1 和 2 是有效的 ------------------- 位置组件用一个数组或向量存储位置数据比如 { {100, 200}, {300, 400} }其中 {100, 200} 是玩家的位置{300, 400} 是敌人的位置。 速度组件用另一个数组或向量存储速度数据比如 { {10, 0}, {5, -2} }其中 {10, 0} 是玩家的速度{5, -2} 是敌人的速度。 ------------------- | 位置组件存储| -------------------|  {100, 200}       |     // 实体ID1 的位置 |  {300, 400}       |     // 实体ID2 的位置 ------------------- ------------------- |速度组件存储 | -------------------|  {10, 0}            |     // 实体ID1 的速度 |  {5, -2}             |     // 实体ID2 的速度 ------------------- 位置映射entt::registry 使用一个哈希表将实体ID映射到位置数据。例如ID1 映射到 {100, 200}ID2 映射到 {300, 400}。 速度映射类似地ID1 映射到 {10, 0}ID2 映射到 {5, -2}。 ----------------------------- | 组件映射哈希表   | -----------------------------|  位置                        | |  ID1 - {100, 200}     | |  ID2 - {300, 400}     | |                                     | |  速度                        | |  ID1 - {10, 0}           | |  ID2 - {5, -2}            | ----------------------------- 》》entt::registry的查询与更新 #include entt/entt.hpp#include iostream// 定义组件struct Position {float x, y;};struct Velocity {float vx, vy;};int main() {// 创建一个注册表entt::registry registry;// 使用 registry 创建实体并返回句柄auto entity registry.create();// 为实体添加组件registry.emplacePosition(entity, 10.0f, 20.0f);registry.emplaceVelocity(entity, 1.0f, 2.0f);// 查询组件if (auto* pos registry.try_getPosition(entity)) {std::cout Position: ( pos-x , pos-y )\n;} else {std::cout Entity has no Position component.\n;}if (auto* vel registry.try_getVelocity(entity)) {std::cout Velocity: ( vel-vx , vel-vy )\n;} else {std::cout Entity has no Velocity component.\n;}// 更新组件if (auto* pos registry.try_getPosition(entity)) {pos-x 5.0f;  // 增加 x 坐标pos-y - 3.0f;  // 减少 y 坐标}if (auto* vel registry.try_getVelocity(entity)) {vel-vx * 2.0f; // 增加 x 速度vel-vy * 0.5f; // 减少 y 速度}return 0;} 》》》》新版 Entt 的 entt::registry 好像没有 has 这个成员函数. 》》》》结构化绑定: auto group m_Registry.groupTransformComponent, MeshComponent();for (auto entity : group){auto [transform, mesh] group.getTransformComponent, MeshComponent(entity);} 这就是就是所谓的结构化绑定, 可以查看 Cherno C 系列的视频。 BiliBili  【75】【Cherno C】【中字】C的结构化绑定_哔哩哔哩_bilibili YoutubeSTRUCTURED BINDINGS in C Structured binding: 结构化绑定 语法 auto [var1, var2, ...] expression; auto 表示变量的类型将由编译器自动推导。 [var1, var2, ...] 是结构化绑定的变量列表用于接收解构后的值。 expression 是一个可以解构的对象通常是一个 std::tuple、std::pair 或自定义类型。 》》》》On_Construct 函数的意义 on_construct 是用于注册回调函数当某个组件类型的组件被添加到实体时会触发这些回调。它允许你在组件创建时执行特定的操作。 connectOnTransformConstruct(): 将 OnTransformConstruct 函数连接到这个信号。每当 TransformComponent 被添加到实体时OnTransformConstruct 就会被调用。 ----Entities And Components----------- Cherno 》》》》感觉没啥要记的 auto group m_Registry.groupTransformComponent(entt::getSpriteComponent); m_Registry.groupTransformComponent定义了一个组其中的实体必须具有TransformComponent组件。 entt::getSpriteComponent这是一个额外的条件表示要包括SpriteComponent组件。 这样group中的实体不仅必须有TransformComponent还必须有SpriteComponent。 ---------------Entity class------------- 》》》》强指针与弱指针的区别 强指针 定义: 强指针是指在程序中直接引用对象的指针或引用。当一个对象有一个或多个强指针指向它时该对象的生命周期是由这些强指针管理的直到所有强指针都被释放或指向其他对象。 特性: 只要存在一个强指针指向对象该对象就不会被回收或释放。强指针会延长对象的生命周期防止对象在被引用期间被销毁。 弱指针 定义: 弱指针是一种不会阻止对象被回收的指针。它通常用于引用那些可能会被销毁的对象避免强引用循环所导致的内存泄漏。 特性: 弱指针不会改变对象的引用计数这意味着即使存在弱指针指向对象只要没有强指针指向对象该对象依然会被回收。弱指针通常与强指针一起使用以避免内存泄漏问题。 内存管理: 强指针 会增加对象的引用计数使对象在所有强指针都被释放之前不会被回收。 弱指针 不会影响对象的引用计数它只是一个辅助指针用于访问对象但不阻止对象的回收。 例如 在观察者模式的设计中弱指针可以在观察对象时不干预对象生命周期。也就是说 std::weak_ptr 可以通过避免持有对象的强引用来实现有效的内存管理。 》》》》registry.has( ) 函数被重命名了 现在叫 all_of You can find it with : https://github.com/skypjack/entt/issues/690 》》》》关于 operator bool() const 这是一个类型转换运算符type conversion operator, 其作用是允许类 Entity 对象在需要布尔值的上下文中如条件语句被隐式地转换为 bool 类型。 例如 operator bool() const { return m_EntityHandle ! 0; } 在这里m_EntityHandle 是一个表示实体的句柄handle。 如果 m_EntityHandle 为 0 (null 或 空)则m_EntityHandle ! 0 返回 false 可以认为 Entity 对象无效可能表示它已经被销毁或未初始化。 否则若 m_EntityHandle 不为空 则m_EntityHandle ! 0 结果为 true, 可以认为它是有效的。 ----------------Camera system------------ 》》》》Cherno的意思好像是在游戏编辑时使用一种摄像机。但在游戏运行时使用另外一种摄像机。当然这两种摄像机有别。 我的理解是游戏在编辑的时候需要编辑者全面/自由的观察游戏设计样貌所以摄像机相对复杂一点因为它要有能力在局部空间/世界空间中位移。 而游戏运行时大部分时间需要以一个固定的视角游玩所以摄像机不用太复杂。 》》》》primary 的作用 Primary 用于确定当前摄像机是否为主摄像机。也就是说当你使用此摄像机的时候这个摄像机会被标记为主摄像机当下正在使用的摄像机。 根据这个标识我们可以正确的对当下所观察的事物进行处理。 》》》》Group 和 View 的区别 参考 http://t.csdnimg.cn/TZiz5 不同之处 1. 基本概念 view 概念用于访问具有特定组件的实体。你可以创建一个视图来遍历所有包含某种或多种组件的实体。 特点视图是相对轻量级的并且对组件的访问是直接的。 视图通常是以某种组件的组合为基础进行过滤的。 group 概念是一个更复杂的数据结构允许你对实体进行更多的分类和管理。 特点group 不仅可以过滤组件还可以根据实体的组件组合创建逻辑组。 group 可以指定需要的组件和额外的条件以便管理和操作那些具有特定组件组合的实体。 2. 用法和功能 view 用法用于遍历符合组件条件的实体。 特点访问速度较快因为它不涉及复杂的分组逻辑只是简单地提供对符合条件的实体的访问。 例子 auto view m_Registry.viewTransformComponent(); for (auto entity : view) {     auto transform view.getTransformComponent(entity);     // 对实体进行操作 } group 用法用于将实体分组到一个更复杂的集合中考虑了多个组件及其组合。 特点group提供了更高层次的管理功能可以在创建时定义更复杂的过滤条件和数据访问逻辑。 例子 auto group m_Registry.groupTransformComponent(entt::getSpriteComponent); for (auto entity : group) {auto transform group.getTransformComponent(entity);auto sprite group.getSpriteComponent(entity);// 对实体进行操作 } 3. 性能和优化 view view主要依赖于组件的数据结构和缓存通常较为高效适合用于简单的组件过滤和遍历操作。 group 由于其包含了更多的逻辑和条件它可能在创建时需要额外的计算和管理开销。 但在需要处理更复杂的组件组合和分类时group可以提供更好的性能优化。 总结 使用view时你是在基于组件的简单过滤进行操作适合轻量级的遍历和访问。 使用group时你可以进行更复杂的组件组合和条件过滤更适合需要进行复杂数据管理和分类的场景。 这取决于你的具体需求和性能要求。 》》》》为什么这一集之后键盘上的 wasd 对摄像机控制失效了。 因为之前绘制的时候通过 BeginScene( OrthographicCamera camera ) 传入的是一个 OrthographicCamera而且我们在更新的时候 CameraController 操控的也是 OrthographicCamera 每一次响应wasd都在通过 UpdateMatrix( ) 更新 OrthographicCamera。 但是在此次绘制的实体中通过 BeginScene( Camera camera ) 我们传入的都是新类型 Camera并且在 CameraController 的更新函数中没有对 Camera 类型摄像机进行更新的函数这就导致在仅绘制 Camera 类型摄像机时WASD 不起作用。 》》》》为什么Cherno在代码中总是获取位移矩阵的第四列Transform[3]以此对物体进行操纵呢 因为OpenGL中的位移矩阵是列主序的所以位移向量存储在矩阵的第4列代码中表示为 0,1,2,3 这 4 个标号中的第 4 个也就是 3 参考 变换 - LearnOpenGL CN (learnopengl-cn.github.io) 》》》》一个提示 Camera 中的投影矩阵被用为 BeginScene 的一个参数所以要确保运行之前已经为 CameraComponent 的成员 Camera 添加了数据。 》》》》一个理解 这一集需要我们重载 Renderer2D::BeginScene() Cherno 是这样做的 我是这样做的 其实差不多我在填入参数时就将 transform 位移矩阵转换成了 view 观察矩阵. 》》接下来看看重载 BeginScene() 这个函数目的 在先前的 Renderer2D::BeginScene() 中我们传入的是 OrthographicCamera: 这么做的用意是可以直接通过 OrthographicCamera 对象的成员函数获取视图投影矩阵 ViewProjectionMatrix然后将其上传为统一变量以便计算与呈现渲染结果。 但是在游戏运行时我们只想进行简单的计算并使用简单的 Camera类型仅仅包含投影矩阵 ProjectionMatrix这时候想要绘制一个图形在使用 Renderer2D::BeginScene() 时就需要进行重载将 Camera 类型对象作为参数的一员。 所以我们还需要传入一个位移矩阵 transform 在 Renderer2D::BeginScene() 临时的计算一下视图投影矩阵 ViewProjectionMatrix然后将其上传为统一变量。 值得一提的是这个视图矩阵 viewMatrix 就是由位移矩阵 transform 进行转置运算得来的这一点在之前的 OrthographicCamera.cpp 中也可见端倪 》》》》纠正两个Cherno的错误 1.视图的命名是不是使用错了 2.BeginScene() 的参数是不是填错了 》》》》又发现一个问题绘制的图像在位移之后其初始位置的图像却一直在绘制没有清除。 后来我发现原因出在这里注释掉之后运行情况正常 由于我为这两个摄像机实体都添加了 sprite 组件所以在绘制图形的时候额外绘制了当前摄像机比例下的两个图形。但不可否认的是新添加的两个图像由于 CPU 运行顺序导致两个新图在绘制时都是覆盖在旧图之上的。 如果解除1的注释但不解除2的注释效果是这样的。初始位置多绘制一个图像 如果解除2的注释但不解除1的注释效果是这样的。总有一个紫色的新图像覆盖在旧的蓝图像之上 为何会是这两句代码导致错误呢其中的逻辑是什么 想不出来我现在想睡觉。 ----Scene Camera (fixed aspect ratio)------ 》》》》在C中整数除法和浮点数除法有什么区别吗 整数除法操作数都是整数int, long, short等结果也是整数。 浮点数除法操作数至少有一个是浮点数float, double, long double结果是浮点数。 》》》》什么类 可以访问 其他类中 protected 类型的成员变量 派生类可以访问基类父类中的 protected 成员。 派生类子类不能直接访问基类父类的 private 成员 友元类和友元函数可以访问类的 protected 成员。类内部的成员函数可以访问该类的 protected 成员。 》》》》关于子类与父类构造函数的关系。 前提子类在没有显示地调用父类的构造函数时编译器依旧会尝试隐式调用父类的默认构造函数。如果父类没有定义默认构造函数那么编译器就会产生错误。 原因 子类与父类构造函数的结构 子类的构造函数会首先调用父类的构造函数以确保父类部分被正确初始化。子类构造函数可以通过初始化列表显式指定调用哪个父类构造函数。 初始化顺序父类的构造函数先于子类的构造函数执行 即使子类的构造函数中没有显式调用父类构造函数父类的构造函数也会被自动调用。 所以如果父类没有默认构造函数子类构造函数就必须提供一个有效的父类构造函数调用。 》》》》Button  Bottombutton是按钮bottom是底部。 》》》》为什么在正交矩阵的计算中对于 bottom 和 top 这两个参数不用乘以纵横比 AspectRatio? 因为 m_OrthographicSize 已经算是定义了视口的高度。所以在确定的高度之下只需要对宽度从 Left 到 Right进行比例换算即可得到正确的视觉效果。 如果只对高度从 Bottom 到 Top 乘以纵横比 AspectRatio但不对宽度进行计算结果应该是相似的只不过会变扁一点。 》》》》一个提醒 错误缘由 这个错误是由于你尝试用不兼容的方式初始化 Nut::CameraComponent 对象。 在 Entt 库的代码中它期望某种特定的初始化方式初始化对象但你的代码使用了不匹配的方式。 排除错误 这里就需要检查 Nut::CameraComponent 的构造函数或 Entt 库的文档。 纠正错误 由于更换了 CameraComponent 中的成员 Camera ( Nut::Camera - SceneCamera )我们需要删除 AddComponent 的参数  glm::ortho(…) 因为子类 SceneCamera 的所有构造都不需要填入参数 而且父类的默认构造函数也不需要填入参数 所以在此处为 CameraCompoonent 的成员 SceneCamera Camera 初始化的时候不需要填入数据。 而且在默认情况下我们还为父类的成员投影矩阵 ProjectionMatrix 添加了一个默认值 glm::mat4(1.0f,以防没有填入参数对其带来未定义的错误。 随后我们在 Camera 的子类 SceneCamera 中更新了投影矩阵投影矩阵主要在 SceneCamera 中定义。 投影矩阵由 glm::ortho( ) 中填入的参数决定并定义。 。 》》》》另一个提示 由于我们去除了为摄像机组件 CameraComponent 填入的正交矩阵 glm::ortho(…), 所以现在两个摄像机实体 m_CameraEntity 和 m_SecondCamera 在空间中看起来是一样的大小、比例…。 “GameEngine6”页上的一个图片【 来自》》》》一个提醒 】 这是因为我们的投影矩阵在之前由摄像机组件 CameraComponent 填入的正交矩阵 glm::ortho(…)决定本来填入的 glm::ortho(…) 直接为组件中的成员 Nut::Camera Camera 进行初始化现在组件中的成员改为了 Nut::SceneCamera Camera后者不需要填入矩阵参数而现在由父类 SceneCamera 中的函数 UpdateProjection( ) 决定。 “GameEngine6”页上的图片 【 来自》》》》一个提醒 】   一开始组件成员为 Nut::Camera Camera 时填入的正交矩阵 glm::ortho 直接被父类 Camera 的投影矩阵 m_ProjectionMatrix 所用现在组件成员改为 Nut::SceneCamera Camera 之后不用填入正交矩阵 glm::ortho但是这样也散失了灵活性因为现在子类 SceneCamera 中从父类 Camera 继承的 m_ProjectionMatrix 被固定的数据计算出来而且上传为统一变量。 在 UpdateProjection( ) 函数中所有的数据由私有成员计算得来这些成员在类对象初始化时就已经定义了而且每一个对象的默认值都一样 父类 Camera 或子类 SceneCamera 所计算并更新的投影矩阵 m_ProjectionMatrix 在这里被获取并上传至统一变量。 ↓ ----Native Scripting 本机脚本----- 》》》》十分抱歉因为前两天出去玩后面又有事情耽搁一直没更新。现在我试着恢复到工作状态中来。 》》》》什么是脚本 概念 在编程中脚本Script通常指的是一种用于自动化任务的程序代码。 脚本语言通常是解释性语言意味着它们不需要编译成机器代码可以直接由解释器逐行执行。 常用于 自动化任务系统管理网页开发数据分析测试等目的。 常见脚本语言 常见的脚本语言包括Python、JavaScript、Bash、Perl和Ruby。 》》》》本机脚本和普通的脚本有什么不同 普通脚本Managed Scripts以 Unity  为例 1.语言通常使用 C# 编写。 2.运行环境运行在 Unity 的 Mono 或 .NET 运行时环境中。这些脚本是托管代码由 Unity 的托管环境处理。 3.编译在 Unity 编辑器中C# 脚本被编译成 .NET 程序集DLLs。 4.接口通过 Unity 的 MonoBehaviour 类和 Unity 的 API 访问和操作游戏对象和组件。 5.调试可以通过 Unity 编辑器的调试工具或 Visual Studio 等 IDE 进行调试。 6.特性因为采用的语言 C# 使其易于编写和维护因为它们利用了 .NET 的垃圾回收和其他高级语言功能。 本机脚本Native Scripts 1.语言通常使用 C 或 C 编写。这些脚本通过 Unity 的本机插件接口Native Plugin Interface集成。 2.运行环境直接运行在操作系统的本机环境中而不是 Unity 的托管环境中。 3.编译需要编译成平台特定的动态链接库DLLs、so 文件、dylib 文件等。 4.接口通过 Unity 提供的本机插件接口进行交互。Unity 允许通过 DllImport 等机制调用本机插件中的函数。 5.调试调试可能会更加复杂因为它涉及到不同的工具和环境通常需要使用本机开发工具如 Visual Studio 的 C 部分。 6.特性C 提供了对更底层硬件和系统资源的访问可以优化性能但编写和维护更为复杂。还需要处理内存管理和其他低级问题。 使用场景 普通脚本 适合大多数游戏逻辑、用户界面、输入处理等应用场景。 本机脚本 适合需要高性能计算、平台特定功能、或与现有本机代码库集成的场景。 例如进行复杂的数学计算或直接操作硬件等。 总结 普通脚本提供了更高层次的抽象和易用性而本机脚本则提供了更低层次的控制和优化能力。 》》》》函数指针 Check it with: BiliBili  【58】【Cherno C】【中字】C的函数指针_哔哩哔哩_bilibili YouTube Function Pointers in C 》》》》std::function 概念 std::function 是 C 标准库中的一个可调用对象封装器定义在 functional 头文件中。 它可以存储和调用任意类型的可调用对象包括函数指针、Lambda 表达式、函数对象即重载了 operator() 的类甚至是绑定了参数的函数。 使用 定义声明一个 std::function 对象并指定其接受的参数和返回类型。 std::functionvoid(int) func; 赋值将函数指针、Lambda 表达式或函数对象等等赋值给 std::function 对象。 func [](int x) { std::cout x std::endl; }; func(10); // 输出 10 使用直接调用 std::function 对象就像调用普通函数一样。 void print(int x) {     std::cout x std::endl; } func print; func(20); // 输出 20 》》》》Lambda 表达式 Lambda 表达式在 C 中是定义匿名函数的简洁方式。 基本语法 [capture](parameters) - return_type { body } Lambda 表达式的组成部分 捕获列表 [capture] 指定 Lambda 表达式可以访问的外部变量。决定了 Lambda 表达式如何访问外部变量 参数列表 (parameters) 定义 Lambda 表达式的参数决定 Lambda 表达式接受哪些参数 返回类型 - return_type 指定 Lambda 表达式的返回类型如果编译器无法自动推断的话。这部分是可选的。 函数体 { body } Lambda 表达式的实现部分。 捕获列表 [capture] 决定了 Lambda 表达式如何捕获外部变量。你可以通过不同的方式来捕获这些变量 [] 以引用的方式捕获所有外部变量。这意味着 Lambda 表达式使用并修改外部变量。 [] 以值的方式捕获所有外部变量。这意味着 Lambda 表达式会创建外部变量的副本并且对这些副本的修改不会影响外部变量。 [var] 以引用的方式捕获指定的变量 var其他外部变量不被捕获。 [, var] 以值的方式捕获所有外部变量但以引用的方式捕获指定的变量 var。 You Can Check it with: BiliBili  【59】【Cherno C】【中字】C的lambda_哔哩哔哩_bilibili YouTube Lambdas in C 》》》》关于代码的一些理解 》》Instance 的作用 因为我们计划通过脚本组件中的OnCreateFunction、OnDestroyFunction、OnUpdateFunction 来获取脚本类中自定义的 3 个函数。所以为了OnCreateFunction、OnDestroyFunction、OnUpdateFunction 能够获取到指定脚本类中的函数我们需要在调用函数时获取到合适的脚本类对象然后在OnCreateFunction、OnDestroyFunction、OnUpdateFunction 中调用该对象的成员函数。 》》为什么不能将脚本类中的函数 Create、Update、destroy 直接作为脚本组件的成员呢而非要使用三个函数调用脚本类中的这三个函数 个人理解 1. 首先如果是在脚本组件中定义了三个成员函数分别用于传入脚本类成员函数这样在初始化的时候调用起来就很麻烦想象一下一个组件就要多调用好几次用于传入的函数。 2.既然我们创建了一个脚本类就可以直接通过类名来传递类中的成员函数这个类中只有公用的成员函数是一个工具类。不过我们在 Bind 函数中一次性调用脚本类的成员函数只调用一次Bind 便可以自动处理脚本类中的所有函数。 》》为什么 Instance 需要是指针类型 首先我们需要接受一个脚本类句柄也就是一个脚本类对象来设置脚本组件中的函数所以我们需要一个 ScriptableEntity 对象。可是这个对象 Instance 为什么需要是指针类型呢 1.因为我们初始化的时候将其指定为 nullptr空指针 Instance 不是指针类型的话这样的定义就是错误的。 2.我们使用 new、delete 进行生命周期的管理这要求 Instance 是指针变量。 》》向下转换的概念及用例 向下转换指的是将父类的指针或引用转换为子类的指针或引用。 这样做可以方便的操作子类的成员函数。比如此处T 是 ScriptableEntity 的子类当 ScriptableEntity* 类型的变量 instance 被传入的时候instance-只能调用 ScriptableEntity 的成员函数 GetComponent( ) 如果将 ScriptableEntity* 类型的变量 instance 向下转换为子类 T* 类型的变量instance- 就能调用 T 类的成员函数 OnCreate( )。 》》注意 ((T*)instance和 (T*)instance 是有区别的 1. ((T*)instance)-OnCreate() 将 instance 强制转换为 T* 类型然后通过 - 运算符调用 T 类型的 OnCreate() 成员函数。 2. (T*)instance-OnCreate()受运算符优先级影响这行代码首先对 instance 使用 - 运算符然后将结果转换为 T* 类型这并不能调用到 T 类型的成员函数。 》》为什么有的函数需要 [] 捕获外部变量有的函数则是 [ ] 不捕获外部变量 对于 Instantiate 和 DestroyInstance 他们需要访问 Lambda 表达式作用域之外的 Instance 成员变量所以使用 [] 捕获 Lambda 外部变量通常是 this 指针并将其作为引用。 对于 OnCreate … 这三个函数只需要使用传递给他们的参数没有访问或修改外部变量因此捕获列表可以为空。 》》》》enTT 中的 view 有一个成员函数 each这个函数是什么有什么用怎么使用 概述在 enTT 中 view 用于返回具有特定组件的所有 实体而 each 函数可以为 view 获取的所有实体执行用户指定的操作。这个操作通常是通过回调函数比如 lambda 表达式来实现的。 功能 each 函数会遍历视图中的所有实体和它们的组件并对每个实体及其组件执行指定的操作。这个操作是通过传入的回调函数实现的。 参数 each 函数接受一个回调函数作为参数。回调函数通常是一个 lambda 表达式表达式中指定两个参数实体 ID 和对应的组件引用。 ADDED NEW: ( I havent try that yet) 2024-9-10 21:43 》》》》位移矩阵的[3][0], [3][1], [3][2] 是 x,y,z ---------Native Scripting ( with virtual function) ------- 》》》》What is the V-Table? 概念 在 C 中虚函数表V-table是支持多态性的一个机制。当类中包含虚函数时编译器会为该类生成一个虚函数表。 结构 虚函数表是一个指针数组每个元素指向某个类中虚函数的具体实现。 工作原理 虚函数表的生成 每个包含虚函数的类都有一个虚函数表。虚函数表的元素按照虚函数在类中声明的顺序排列每个元素指向虚函数的实际实现。 虚表指针vptr 每个对象实例中包含一个指向其虚函数表的指针vptr。这个指针在对象创建时被初始化为指向相应类的虚函数表。 函数调用 当通过基类指针或引用调用虚函数时程序会通过 vptr 查找虚函数表并调用正确的实现。这允许在运行时动态绑定到正确的函数实现从而实现多态性。 示例 如果两个不同的子类都继承了相同的父类并对父类中的虚函数进行了不同的定义这时虚函数表就为不同子类的变量调用相应的虚函数以防程序运行错误。 》》》》代码理解为什么移动主相机的时候幕后的相机也做了相同的位移请查看注释以便理解 为了实现每个实体使用一样的脚本规范但各自移动的效果我选择这样的方式从回调函数中顺便获取 CameraComponent 然后通过 primary 作为限制条件进行更新。 》》》》这一次提交中 1.    我将脚本类的定义放在其他文件中然后在EditorLayer.cpp中包含其头文件这样更加整齐。 2.1  这个脚本类子类/派生类的定义我就放在ScriptableEntity父类/基类的代码下面这样更加简洁。 2.2  我将脚本类的声明和定义设置在两个文件中分离开来。 3.    我现在将脚本组件的更新操作放在Scene.cpp新建的OnScript函数中而不是放在Scene.cpp的OnUpdate函数中这样便于理解。 》》我在描述(description中这样总结 Scene.cppScene.h: Separate NativeScripts update from Scene::OnUpdate( ), now it was in new function---Scene::OnScript( ). Scene.cpp: Update functions calling ways.   I also added a condition to make every entity moving individually, check it in view.each(). Component.h: Changed args in std::funciton ,delete std::function which OnCreateFunction/OnDestroyFunction/OnUpdateFucntion used.  And because of this changes, we also need to change Scene::OnScript( ) so that it can running correctly. EditorLayer.cpp: Removed the defination of ScriptCameraController and define it in external file ( Now in ScriptableEntity.h ScriptableEntity.cpp), looks better.  And we use new script update function:OnScript( ). ScriptableEntity.h: Declaring ScriptableEntity class and ScriptCameraController class. ScriptableEntity.cpp: Defining ScriptableEntity class and ScriptCameraController class. 译文 Scene.cppScene.h将 NativeScript 的更新与 Scene::OnUpdate( ) 分开现在它位于新函数---Scene::OnScript( ) 中。 Scene.cpp更新函数调用方式。 我还添加了一个条件使每个实体单独移动在 view.each() 中检查它。 Component.h更改了 std::funciton 中的参数删除了 OnCreateFunction/OnDestroyFunction/OnUpdateFucntion 使用的 std::function。 并且由于这些变化我们还需要更改 Scene::OnScript( ) 以便它能够正确运行。 EditorLayer.cpp删除了 ScriptCameraController 的定义并将其定义在外部文件中现在在 ScriptableEntity.h 和 ScriptableEntity.cpp 中看起来更好。 并且我们使用了新的脚本更新函数OnScript( )。 ScriptableEntity.h声明 ScriptableEntity 类和 ScriptCameraController 类。 ScriptableEntity.cpp定义ScriptableEntity类和ScriptCameraController类。 -------Scene Hierarchy panel----------- 》》》》关于 Trello board我用其记录了待办的事务一般是一些维护。   接下来的七八集会比较硬核比较枯燥虽然没有这么夸张。大部分时间在处理 ImGui我会进行细致的学习。 最近很久没有更新一部分原因是时间安排一部分原因是我喜欢先看一部分视频然后再去实现代码也许我会快节奏的上传代码也许不会但我尽量理解所有逻辑。 》》》》Scene Hierarchy场景的层次结构 》》》》enTT basic_registry::each() 现在还能使用吗 问题 为了获取当前注册表中所有实体我对 entt::registry 调用 each() 成员函数但实现代码的过程中我发现 entt::registry 已经没有 each() 成员函数这让我倍感疑惑。 思考与解释 在仔细查找与考证之后我猜想是enTT更新了entt.hpp文件或者将basic_registry::each()放在其他文件中去了。总之entt.hpp中已经找不到basic_registry::each()的定义。 考证 Cherno 4 年前使用的 entt.hpp 中我找到 可是在2024-6-11日更新的entt.hpp中没有basic_registry作用域下的each()函数只有basic_view、basic_group等作用域下的each()函数 所以我不得已改变之前的实现方法。 解决方案1 由于我们在 CreateEntity() 函数中对每一个实体都默认添加了 TagComponent所以我通过 view() 获取所有包含 TagComponent 的实体既全部实体然后通过 each() 函数遍历这些实体。效果与之前相当。 值得一提的是 m_Context-m_Registry.view(); 这样的代码在语法上应该是正确的但在实际使用中不允许空模板参数我不是很明白为什么。 解决方案2(正解 其实YouTube下的评论还是很有帮助的 》》帖子/文档 查看论坛 c - How can I access all entities in an entt::registry? - Stack Overflow 为啥昨天就没看到这个帖子呢 》》》》下载doxygen: https://www.doxygen.nl/ 先前版本的doxygen文档 不知道怎么的我翻阅到entt先前的文档然后查阅了先前的设计。找到了这个阴魂不散的each 不过这好像是entt.hpp之前版本的数据所以each()的使用方法参考性不大。于是我决定下载一个doxygen看看能不能识别一下现如今最新的entt.hpp. 下载网址 https://www.doxygen.nl/  可以查看笔记“概念与操作”中的这里有细致步骤 》》》》doxygen 下载doxygen: https://www.doxygen.nl/ 最新版本的 enTT 构架 进入浏览器按照指示选项卡打开查看 在所有class中我们找到basic_registry,并打开。 在这里你可以查找类型、成员函数等等。 可以看到最新版本的 entt 中entt::basic_registry 并没有 each() 函数 》》》》ImGui::TreeNodeEx() 函数 概念 ImGui::TreeNodeEx 用于在 ImGui 的 UI 中创建一个树节点这个节点可以显示为展开或折叠状态并且可以包含子节点。这个函数也具有一些额外的功能允许你设置节点的样式、图标、标志等。 函数签名 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags 0);bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* label); 参数说明 ptr_id 用于指定树节点的唯一 ID。通常情况下你可以使用 label 作为 ID如果你有自定义的 ID 则可以使用这个参数。 flags 树节点的标志用于控制节点的行为如是否可以被折叠、是否显示图标等。 label 树节点的标签显示在节点的前面。 函数返回值 ImGui::TreeNodeEx 的返回值表示该节点是否被展开。具体来说 返回 true 则节点被展开意味着可以显示其子节点。 返回 false 则节点未展开子节点不可见。 常用的 ImGuiTreeNodeFlags 标志 ImGuiTreeNodeFlags_DefaultOpen 默认节点是展开的。 ImGuiTreeNodeFlags_Framed 节点带有边框。 ImGuiTreeNodeFlags_SpanAllColumns 节点标签占据整行。 ImGuiTreeNodeFlags_Leaf 节点是叶子节点没有子节点。 ImGuiTreeNodeFlags_Bullet 显示子节点的标记如圆点。 MORE 》》为什么 Flags 的定义是 10 的形式 为什么设置 Flags 的时候需要将上述 Flag 进行或( | )运算 10 表示 1 这个二进制数字左移 0 个单位1 1 指 1 左移 1 个单位1 2 同理。所有Flag 被定义成二进制0000, 0001, 0010,0100,1000 … 直到1000 0000 0000 0000)。 这时如果对其中几个 Flag 进行或运算得到的结果正好可以包含所选取的 Flag 数据。Eg: 或运算 (OR) 001 | 101 ---- 101 》》》》Flags 中的三元运算符是为了计算什么结果为了达到什么效果 前提在后续的代码中我们通过代码得知使用者是否用鼠标单击确定了某一个实体如果该实体被选中m_SelectionContext就会被更新为选中的实体。 解释 在实体未被选中前entity有值但m_SelectionContext为空所以 Flags 中的 ImGuiTreeNodeFlags_Selected 不会相应。如果实体被选中则三元运算符成立 ImGuiTreeNodeFlags_Selected 相应效果是高亮指定节点。 》》》》树节点的ID为什么需要这样转换 (uint64_t)entity 将 uint32_t 类型的 entity 转换为 uint64_t 类型。这样做的目的是将 entity 转换为一个较大的整数类型避免在某些平台上直接将 uint32_t 转为 void* 时可能出现的问题。 例如64 位系统上的 void* 大小通常是 64 位而 32 位系统上的 void* 大小是 32 位。为了确保在所有平台上转换的一致性将 uint32_t 转换为 uint64_t 可以避免由于直接将 32 位值强制转换为 64 位指针可能出现的数据丢失或对齐问题。 (void*)(uint64_t)entity 将 uint64_t 类型的 entity 转换为 void*。这是因为 ImGui 的 TreeNodeEx 函数要求节点 ID 是 void* 类型的。 》》》》在运算符 ! 的设计中return !(*this other);是什么意思为什么 1. return !(*this other); 这句代码的意思是什么 *this other  表示调用刚才设计的 operator 方法来检查当前对象和 other 对象是否相等。 return !(*this other); 这个表达式表示返回当前对象和 other 对象是否不相等的结果。 2.为什么在定义中使用 *this 而不是 this this This 在 c 中一般是指向对象的指针在这里是指向当前 Entity 对象的指针Nut::Entity*)。 *this *在这里起到解引用的作用指的是解引用 this 指针之后得到的当前对象的引用 (Nut::Entity)。 而我们的运算符重载是作用在 Nut::Entity 上的。 》》》》Where Here! 我在哪里用过这个运算符重载来着 》》》》为什么在初始化 entity 的时候填入的参数需要是RefScene::get() ? 分析 Entity类在初始化的时候需要填入uint32_t类型的ID 和 Scene*类型的场景指针但是 m_Context 现在是 RefScene这是一个智能指针 std::shared_ptr, 而不是Scene* 这种裸指针。 解决方案 也就是说RefScene现在是不可用的指针类型所以我们需要获取指向 scene 类型对象的原始指针Scene*这就需要使用 std::_Ptr_baseNut::Scene::get() 函数。 定义 》》》》一个想法我觉得没必要让现有的两个摄像机独立移动。 因为在游戏引擎的实际使用中例如Unity)你在世界空间world camera下对所有物体进行的调整在其他摄像机视角下都应该是同步的。 Eg.你在世界空间中调整了游戏角色 Cheryes 的初始位置那么在游戏运行时你使用的运行时摄像机所看到的也应该是游戏角色 Cheryes 被调整后的位置。 》》》》注意需要确保在绘制 TreeNode 之后调用 ImGui::IsItemClicked()否则导致绘制错误当鼠标单击一个节点时该节点之下的节点也会高亮响应 WRONG RIGHT AND… ImGui::IsItemClicked() 是个函数 绘制错误原因 当你调用 ImGui::IsItemClicked() 时它检查的是当前帧中是否有用户点击事件发生在上一个绘制的项上。 如果你在绘制 TreeNode 之前调用 IsItemClicked()它会检测点击事件但是此时 TreeNode 还没有实际绘制出来。因此这个函数可能会错误地报告点击状态因为它基于之前的状态而不是你当前正在绘制的节点。 结论解决方案 所以确保在绘制 TreeNode 之后调用 ImGui::IsItemClicked()这样你可以确保点击状态是基于实际绘制的节点。 ----------Properties Panel-------------- 》》》》ImGui::InputText( ) 函数释义 ImGui::InputText 是 ImGui 的一个函数用于显示一个文本输入框。 函数签名 ImGui::InputText(Tag, buffer, sizeof(buffer)); 函数参数 Tag 是输入框的标签用于在界面上标识输入框。 buffer 是一个字符数组或类似的缓冲区用于临时存储用户在输入框中输入的文本。 sizeof(buffer) 是缓冲区的大小确保输入文本不会超过这个大小。 函数返回值: ImGui::InputText 函数会返回一个布尔值bool。 如果用户在输入框中更改了内容返回值为 true如果用户没有更改内容或者输入框没有被操作返回值为 false。 所以我们使用条件判断是否被更改若被更改则对 tag 进行数据更新。 if ( ImGui::InputText(Tag, buffer, sizeof(buffer) ) ){tag std::string(buffer);} 》》》》(void*)typeid(TransformComponent).hash_code() 的作用 typeid 运算符 功能:  typeid 是一个运算符用于在运行时获取对象或类型的类型信息。它返回一个 std::type_info 对象该对象包含了类型的元数据。 用法:  typeid(Expression) 可以用来获取一个表达式的类型信息typeid(Type) 获取一个类型的类型信息。 参数注意 参数必须是一个类型或表达式 std::type_info 定义:  std::type_info 是一个类用于描述 C 中的类型。 主要功能: name(): 返回一个指向表示类型名称的 C 风格字符串的指针。 hash_code(): 返回一个无符号整数表示类型的哈希值。这个值是类型的唯一标识符.( 其具体值和算法是实现定义的不同的编译器可能会有不同的实现 ) typeid().hash_code() 的作用 唯一标识:  typeid(Type).hash_code() 生成一个类型的哈希值可以用作唯一标识符。这在需要对类型进行区分的情况下非常有用比如 ImGui 的树节点 ID。 稳定性: hash_code() 返回某个类型的唯一标识唯一哈希值这在同一程序的执行期间是稳定的即同一类型的哈希值不会改变。 关系和使用 typeid 生成 std::type_info 对象:  typeid 运算符生成一个 std::type_info 对象通过它可以访问类型的信息。 hash_code 用于获取哈希值:  std::type_info 对象的 hash_code() 成员函数用于获取该类型的哈希值。通常用于类型的唯一标识。 》》什么是元数据 概念元数据Metadata是指关于数据的数据。它提供了关于某一数据集的结构、内容、格式、来源和上下文等信息。元数据通常用来帮助用户理解和管理数据方便数据的搜索、组织和使用。 》》》》ImGui::IsMouseDown( ) 的定义 参数的定义注意查看注释。 》》IsMouseDown 和 IsMouseClicked 的区别 IsMouseDown() 的效果是敲击一次鼠标便一直执行某事件直至条件改变。 而IsMouseClicked()的效果是敲击一次鼠标则某事件运行只一次随后停止等待下一次鼠标的敲击即事件只发生在函数触发的那一帧。 》》》》strcpy 和 strcpy_s 的区别 strcpy: 复制字符串到目标缓冲区不进行边界检查。如果目标缓冲区不够大可能导致缓冲区溢出增加安全风险。 strcpy_s: 是一种安全版本要求指定目标缓冲区的大小。进行边界检查如果目标缓冲区太小将返回错误并不会进行复制从而避免溢出。 》》》》待改进 当你在Scene Hierarchy 窗口中单击鼠标左键时有两种情况 此时鼠标在窗口的节点上单击 则m_SelectionContext被更新为Entity 鼠标在窗口内的其他地方单击比如空白处 则m_SelectionContext被更新为空 但是当单击节点所处行时本应高亮的节点行在更新过程中取消了高亮即节点所处行中有部分范围被认为是空白而不认为该范围属于节点。 》》值得一提 Clip-Camera 在此时ImGui::DragFloat 对图像的位移不起作用因为在 Scene.cpp 中 Scene::OnUpdate() 只对主相机进行渲染而Clip-Camera是第二相机Primary 为 false,所以数据上的更改不会体现出来看起来像是图像没有移动一样。 -------Camera component UI--------- 》》》》一些函数的概念 ImGui::BeginCombo( ) 释义用于创建下拉选择框 签名bool ImGui::BeginCombo(const char* label, const char* previewValue, ImGuiComboFlags flags 0); 参数 label:下拉框的标签用于识别这个组合框。 previewValue:显示在组合框上方的预览值通常是当前选中的项的名称。 flags (可选):用于设置组合框的行为如是否支持多选、是否显示箭头等。 可以使用以下标志 ImGuiComboFlags_None无标志。 ImGuiComboFlags_PopupAlignLeft对齐方式。 ImGuiComboFlags_NoPreview不显示预览。 其他可用的组合标志。 返回值返回值指示组合框是否被打开。               如果返回 true表示组合框当前处于打开状态。               如果返回 false则表示组合框未打开。 ImGui::SetItemDefaultFocus( ) 释义用于设置当前项目为默认聚焦项的函数。 在弹出窗口或菜单打开时调用此函数可以确保某个特定的选项在用户打开时即被高亮显示从而提高用户体验。 签名bool ImGui::Selectable(const char* label, bool selected false, ImGuiSelectableFlags flags 0, ImVec2 size ImVec2(0, 0)); 参数 label:可选择项的文本标签。 selected (可选):布尔值指示该项是否应被视为选中。 flags (可选):可选的标志用于控制项的行为例如是否允许多选。 size (可选):可选择项的大小。 返回值没有返回值。 它的主要作用是设置当前项为默认聚焦项使其在下次导航时自动获得焦点。 ImGui::Selectable( ) 释义用于创建可选择项的函数。            它通常用于列表、菜单或组合框中让用户能够选择其中的一项。 签名bool ImGui::Selectable(const char* label, bool selected false, ImGuiSelectableFlags flags 0, ImVec2 size ImVec2(0, 0)); 参数 label (const char*):显示在可选择项旁边的文本标签。 selected (bool, 可选):指示该项当前是否被选中默认为 false。 flags (ImGuiSelectableFlags, 可选):用于控制可选择项的行为的标志例如是否允许多选、是否禁用等。 size (ImVec2, 可选):指定可选择项的大小默认为 (0, 0)表示自动适应。 返回值 true 表示该项被选中用户点击了该项否则返回 false。 ImGui::Checkbox( ) 释义用于创建复选框的函数允许用户在两种状态之间切换选中或未选中。           它的主要用途是获取布尔值输入通常用于设置开关或选项。 签名bool ImGui::Checkbox(const char* label, bool* v) 参数 label:复选框的标签显示在复选框旁边。 v:指向一个布尔变量的指针用于存储复选框的状态。 返回值该函数会更新传入的布尔变量反映复选框的当前状态。 》》》》使用提示视频中Cherno并没有遇到这种问题可能是我的疏漏 两个摄像机只有一个可以被标识为主相机而我们对当前的主相机进行操作此时需要注意使用perspective 透视投影的摄像机类型时为了呈现出正确结果或者说在合适的视角下才能观察到透视中的深度变换我们需要进行一些操作。 无论你是先对物体进行位移还是先对摄像机实体进行透视的转换你都需要在第一次切换摄像机之后拉伸一下屏幕中的视口然后调整摄像机实体的 z 轴。 导致这个问题的原因是没有刷新视口。(不过依旧要调整一下矩阵的 z 轴才能看到正确结果 具体原因是这样的我们知道在 EditorLayer 中只有在“Viewport”窗口被调整大小的时候才会调用 OnViewportResize( ) 函数来更新视口中的渲染结果。 但是在我们对投影矩阵进行切换的时候viewport 窗口的大小并没有变化这导致此时并不会对视口中的渲染结果进行更新。 所以我们需要在 combo box 切换矩阵类型的时候对视口进行更新尽管此时没有调整 viewport 窗口的大小。 》》为了解决这个问题我们需要在 Hierarchy Panel 中获取到正确的 Viewport 窗口大小然后根据大小进行视口的更新。 我便在 EditorLayer 中创建了一个单例然后在 Hierarchy Panel 中通过这个单例使用 EditorLayer 的成员函数。 你可能会问我为什么这么做 这样做的原因是我们使用窗口 Viewport 并在其中使用 ImGui::Image() 来渲染帧缓冲中的结果那么如果要对视口对渲染结果进行刷新 我们就需要获取到 Viewport 窗口的 Size。 如果不在 ImGui::Begin(viewport) 和 ImGui::End() 之间使用获取大小的函数ImGui::GetContentRegionAvail() 反而在其他窗口的范围内使用ImGui::GetContentRegionAvail() ,就会导致获取的大小来自其他窗口这是错误的。 所以我需要从 EditorLayer 中拿到正确的 ViewportSize然后在 HierarchyPanel 中对当下的场景 m_Context 使用 OnViewportResize() 函数这样便能在调换矩阵类型的时候对渲染结果进行更新。 》》》》一些设计上的闲聊关于 Combo box 的高亮/焦点显示 我总觉得 ImGui::SetItemDefaultFocus() 有点多余因为我认为 ImGui::SetItemDefaultFocus() 和高亮显示一个选项的效果差不多这恰好和 ImGui::Seletable() 的第二个参数重复。 但是 SetItemDefaultFocus() 实际上是一个用于设置当前项为默认焦点的函数焦点和高亮是有区别的。当你打开 Combo Box不使用鼠标选择选项而是用键盘上下方向键选择选项你就会发现不同 Eg.焦点1 Eg.焦点2 Eg.高亮1 Eg.高亮2 》》设计闲聊关于单例的 Get() 函数 》Instance 是一个指针类型的变量那么在 static EditorLayer Get(){ return *s_Instance; } 中 *s_Instance 和 EditorLayer中的 *, 是什么意思有什么作用 *s_Instance 前提: s_Instance 是一个指向 EditorLayer 类型的指针。 解释: 使用 * 操作符对 s_Instance 进行解引用意味着我们获取的是指针所指向的对象。如果 s_Instance 是一个有效的指针那么 *s_Instance 将返回一个 EditorLayer 类型的对象的引用。 EditorLayer EditorLayer 是一个引用类型表示函数返回一个引用类型的变量。而且返回引用可以避免复制对象并且允许调用者直接操作原始对象。 》》》》什么是透视矩阵中的FOV什么是 Vertical FOV?什么是 Horizontal FOV? You Can Check The Image With   https://images.app.goo.gl/ZfQ9JsY8txMRdbL78 定义 在许多3D应用和游戏中FOV 常用于表示相机的整体视野但在一些上下文中尤其是需要精确控制的情况下会特别指出 Vertical FOV 和 Horizontal FOV。 Vertical FOV 专指相机在垂直方向上的视场角影响场景在上下方向的可视范围。 Horizontal FOV 专指相机在水平方向上的视场角影响场景左右方向的可视范围。 计算 不过垂直视场角和水平视场角是可以相互转换计算的比如 常见的垂直视场角值 一般推荐值 对于大多数3D应用和游戏垂直视场角通常在 60° 到 90° 之间。 60° 适用于较为逼真的视图常用于模拟和一些角色扮演游戏。 75°-90° 更广的视野适合快节奏的第一人称射击游戏。 改变 FOV 的影响 更大 FOV 效果可以看到更多的场景适合广角镜头效果。 缺点可能导致物体在视野边缘变形视角畸变。 更小 FOV 效果视野范围变窄适合强调特定对象或细节。 缺点提供更逼真的透视效果但会使场景看起来更拥挤。 --------Drawing component UI---------- 》》》》关于图像混合出错的情况大约在原视频14分钟 在代码中RedSquare 是后绘制的实体。这导致 Alpha 值设置的透明度只有 Blue Square 能够使用出来 问题理解 关于混合的相关知识可以查看 混合 - LearnOpenGL CN --------Transform Component UI------ 》》》》这一集的难点大多涉及到 ImGui 函数的使用和 ImGui 函数的设计逻辑So lets check that out. 》》》》我有一个疑问为什么这里需要将 Rotation 处理三次 Glm::rotate() 函数这是因为我们现在需要处理图像绕三个轴旋转的情况而不是像之前一样只处理绕Z轴旋转的情况了。 之前的 现在的 》》》》一些函数 ImGui::PushID() 是一个用于为 ImGui 界面元素生成唯一 ID 的函数。 参数可以接受一个整数、字符串或指针推送一个 ID 到栈中使得后续的 ImGui 元素在使用相同的 ID 时不会发生冲突。 提示 使用 PushID( ) 和相应的 PopID( ) 可以帮助你管理和区分不同的元素特别是在循环或复杂的界面中。因为这样可以确保每个元素的状态如焦点、选中状态等是独立的。 ImGui::Columns(2); 是一个用于创建多列布局的函数它会将当前窗口分成指定数量的列在这里是 2 列。在调用此函数后接下来的 ImGui 元素会按列布局。 ImGui::NextColumn(); 用于切换到下一列允许你在定义的列中添加更多元素。使用它时如果你在第一列后调用 NextColumn()将会移动到第二列继续添加元素。 ImGui::SetColumnWidth(0, columnWidth); 用于特定列宽度的函数。 参数 columnIndex指定要设置宽度的列的索引从 0 开始。 width要设置的列宽度单位为像素。 这个函数通常在设置列布局后使用以确保列的宽度符合你的需求。 使用示例 ImGui::Columns(2);             // 将窗口调整为两列   ImGui::Text(Column 1, Row 1); ImGui::NextColumn();           // 切换到第二列   ImGui::Text(Column 2, Row 1); ImGui::SetColumnWidth(0, 100); // 设置列宽为100像素   ImGui::Columns(1);             // 将窗口调整回一列 ImGui::PushMultiItemsWidths (3, ImGui::CalcItemWidth()) 用于设置多个 ImGui 元素的宽度的函数。具体来说它能够为后续的多个控件在这里是 3 个推送一个统一的宽度设置。一般是为每个控件平均分配填入的总宽度 第一个参数 itemCount表示你将要设置宽度的项目数量。 第二个参数 itemWidth每个控件的宽度。此处填 ImGui::CalcItemWidth()这个宽度通常会基于当前窗口的大小和布局返回数值 统一宽度这个函数可以确保多个相关元素例如多个输入框或按钮在视觉上对齐提供更好的体验。 动态调整使用 CalcItemWidth() 使得宽度根据窗口大小自动调整避免在窗口大小变化时出现布局问题。 此处的设计参数说明 使用示例 ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); ImGui::InputText(Label 1, buffer1); ImGui::InputText(Label 2, buffer2); ImGui::InputText(Label 3, buffer3); ImGui::PopItemWidth();    // 记得恢复之前的宽度设置 ImGui::CalcItemWidth() 用于计算当前控件宽度的函数它根据当前布局和可用空间动态返回一个合适的宽度值。 PushStyleVar 和 PushStyleColor 在修改样式上的不同 PushStyleVar功能   用于推送一个样式变量的值通常是用于控制布局和控件的外观。 PushStyleColor功能用于推送一个颜色样式变量的值通常是用于修改控件的颜色。 ImGui::PushStyleVar( ) ImGuiStyleVar_ItemSpacing这是一个样式变量表示项目之间的间距。 ImVec2{ 0, 0 }这个值表示项目之间的水平和垂直间距都设置为 0。 用途 通过设置 ItemSpacing 为 0可以使得元素之间没有间距从而使它们更加紧凑适用于希望节省空间的布局。 示例 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{ 0, 0 }); // 添加一些紧凑的 ImGui 元素 ImGui::Button(Button 1); ImGui::Button(Button 2); ImGui::PopStyleVar(); // 恢复之前的 ItemSpacing 设置 参数功能该函数用于推送一个新的样式变量到样式堆栈中。样式变量可以影响 ImGui 中许多元素的显示方式。 ImGui::PushStyleColor( ) ImGuiCol_Button指定要改变颜色的元素这里是按钮。 ImVec4{ 0.8f, 0.1f, 0.15f, 1.0f }表示按钮的新颜色使用 RGBA 格式。 用途 通过改变按钮的颜色可以使界面更具个性化或者用于强调特定的操作。 示例 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.8f, 0.1f, 0.15f, 1.0f }); // 创建一个使用自定义颜色的按钮 if (ImGui::Button(Custom Color Button)) {     // 按钮点击事件 } ImGui::PopStyleColor(); // 恢复之前的按钮颜色设置 参数功能这个函数用于推送一个新的颜色设置到样式堆栈中改变特定元素的颜色。 提示 记得在适当的时候调用 PopStyleVar 或 PopStyleColor 来恢复之前的设置。 注意 如何确定函数设计的样式是哪一个范围的 堆栈界限ImGui::PushStyleColor() 使用堆栈的概念来管理样式。每次调用 PushStyleColor() 时它会将当前颜色推入堆栈。对应的 PopStyleColor() 调用则会弹出最近的颜色设置。 堆栈的界限是通过调用的次数来管理的因此你可以通过调用 PopStyleColor() 来返回到之前的状态。 是否需要将 PushStyleColor( )/PushStyleVar( ) 写在实际渲染控件的代码之前 调用顺序是的PushStyleColor() 需要在控件绘制之前调用。这样可以确保在该控件绘制期间使用新的颜色设置。如果你在控件绘制后调用效果将不会反映在该控件上。因此正确的顺序是先推送样式然后绘制控件最后弹出样式。 参数参考表1 参数参考表2 ImGui::SameLine( ) 用于将后续控件放置在同一行的函数。 它的主要作用是让用户在同一行上排列多个控件而不是默认的垂直排列。 使用方法在调用 SameLine() 之后紧接着的控件将会被放置在上一控件的右侧。你可以在任何需要的地方调用这个函数只要你想要控件在同一行显示。 示例 ImGui::Text(Label 1);   ImGui::SameLine();      // 将下一个控件放在同一行 ImGui::Button(Button 1);   ImGui::SameLine();      // 继续在同一行 ImGui::Button(Button 2); 》》》》关于 ImGui::DragFloat(##X, values.x); 中的 ##X 什么是 ImGui 中的 ## 1. 控件标识符的意义 在 ImGui 中每个控件例如按钮、滑块等需要一个唯一的标识符。这个标识符能够帮助 ImGui 跟踪每个控件的状态、位置和其他属性。 如果没有唯一标识符ImGui 将无法正确管理多个控件的状态尤其是在同一窗口中动态生成多个相似控件时。 举个例子 如果多个控件使用相同的视觉标签如 ButtonImGui 将只会创建一个控件而不是多个。这意味着只有最后一个控件的状态会被保留前面的控件将被覆盖。 通过使用 ##即使视觉标签相同只要标识符不同ImGui 就会将它们视为独立的控件。 2. ## 的原理 将视觉标签与内部标识符分离 使用 ## 用来分隔控件的显示标签和其标识符。例如Button##UniqueID 中Button 是用户在界面上看到的文本而 UniqueID 是 ImGui 用来跟踪该控件的内部标识符。 3. 实际示例 ImGui::Button(Button##1); ImGui::Button(Button##2); 在这个例子中 第一个按钮的显示文本是 Button但它的唯一标识符是 Button##1。 第二个按钮的显示文本也是 Button但它的唯一标识符是 Button##2。 4. ## 可以单独使用吗,比如 ##X ? 此时控件上显示什么 ##X 可以单独使用。在这种情况下控件上不会显示任何文本因为 ## 前面没有标签。控件的标识符是 X 》》》》关于按钮尺寸 ButtonSize 的设计 float lineHeight GImGui-Font-FontSize GImGui-Style.FramePadding.y * 2.0f; ImVec2 buttonSize { lineHeight 3.0f, lineHeight }; 代码解析 计算 lineHeight行高 GImGui-Font-FontSize 这是当前使用的字体的大小。文本的高度实际上也是按钮的基本高度的一部分。 GImGui-Style.FramePadding.y 这是按钮的垂直内边距padding用于给按钮内部内容如文本留出空间。FramePadding.y 的值会增加到按钮的高度中以确保文本不会紧贴按钮的边缘。 GImGui-Style.FramePadding.y * 2.0f 这里乘以 2 是因为按钮的顶部和底部都有同样的内边距以此来确保文本在按钮中间且上下边界不与按钮的上下边缘粘连。 计算 buttonSize按钮大小 buttonSize.x 按钮宽度设置为 lineHeight 3.0f这里的 3.0f 是额外增加的宽度视觉效果更好一定的区域大小也方便使用者点击。 buttonSize.y 按钮高度设置为 lineHeight。 》》》》GImGui 是什么 GImGui 在 ImGui 中是一个全局变量类型为一个指向 ImGui 上下文的指针。GImGui 通常用于访问 ImGui 的状态和功能它允许你在自定义的代码中直接访问 ImGui 的内部数据和功能。 In imgui.cpp) Part of ImGuiContext in imgui_internal.cpp) 》》》》标准化颜色选择器 https://rgbcolorpicker.com/  is very good. 》》》》度数转化的设计 前两步就是把数据进行单位上的转换然后交给一个临时变量通过使用 DragFloat 控制临时变量以度数为单位的角度实现手动调整数据的交互操作。此时虽然在控件上我们调整的是度数制的角度但是我们可以获取到弧度制之下角度的数值变化。 第三步是重点将调整过后的角度度数制转换到弧度制上然后重新传递给位移组件现在应该称之为变换组件中的旋转成员变量 Rotation如此一来我们成功的调整了角度。调整旋转角度之后的图像将会在 OnUpdate() 函数调用之后呈现出渲染结果。 ---- Adding/Removing Entity UI Adding/Removing Component UI----- 》》》》Check-List 》》》》我突然意识到摄像机和物体的移动逻辑应该是相反的虽然我不知道这是否需要实现 比如按下 A 键对于物体来说是向右移但对于摄像机来说是左移。对于同一个物体来说移动物体和移动摄像机的逻辑应该是相反的 Eg. Camera类中: 我们为Camera类型的摄像机mainCamera传入了求逆inverse之后的矩阵 OrthographicCamera 中 OrthographicCamera类中 Update() 函数也进行了求逆操作使用逆矩阵为正交相机类型的摄像机更新内置的视图矩阵。 》》》》ImGui 函数 BeginPopupContextItem( ) 用于创建上下文菜单(Context menu)的入口。它通常在一个 UI 元素如按钮或其他可交互控件被右击时使用以便显示相应的弹出菜单。适用于需要为特定 UI 元素提供右键操作的场景。 label:  一个字符串用作弹出菜单的标识符通常传入一个唯一的字符串。 此函数通常与 ImGui::BeginPopup( ) 和 ImGui::EndPopup( ) 配合使用。 上下文关联: ImGui::BeginPopupContextItem( ) 会根据上一个渲染的控件如按钮、输入框等来确定触发上下文菜单的控件。你可以在调用该函数之前渲染你的控件这样 ImGui 可以知道哪个控件被右键点击。 传递标识符: 当你调用 BeginPopupContextItem(MyPopup) 时MyPopup 是一个唯一的标识符用于识别这个弹出菜单。ImGui 内部会跟踪这个标识符和最近的控件之间的关系。 注意特点参数释义 ImGui::MenuItem( ) 用于在菜单或上下文菜单中创建一个可选择的菜单项构建复杂的菜单结构。用户可以点击这些菜单项来执行相关的操作。 label: 菜单项的显示文本。 selected:可选如果设置为 true菜单项将被标记为选中。 enabled:可选如果设置为 false菜单项将变为不可用灰色。 支持动态状态例如是否选中或启用。 可以与 ImGui::BeginMenu 和 ImGui::EndMenu 配合使用形成完整的菜单。 主要特点参数说明释义 用法示例 示例1 // 创建一个按钮if (ImGui::Button(Right Click Me)){// 按钮被点击时可能会处理某些操作}// 按钮检测右键点击然后开始打开上下文菜单Context Menu)if (ImGui::BeginPopupContextItem(MyContextMenu)){if (ImGui::MenuItem(Option 1)){// 处理选项1}if (ImGui::MenuItem(Option 2)){// 处理选项2}ImGui::EndPopup();} 示例2(引出一些疑问 // 创建一个按钮if (ImGui::Button(Add Component))// 按钮检测鼠标点击然后打开弹出菜单Popup MenuImGui::OpenPopup(AddComponent);// 如果OpenPopup() 允许打开窗口if (ImGui::BeginPopup(AddComponent)){if (ImGui::MenuItem(Camera)){// 处理选项1ImGui::CloseCurrentPopup();}if (ImGui::MenuItem(Sprite Renderer)){// 处理选项2ImGui::CloseCurrentPopup();}ImGui::EndPopup();} 》》》OpenPopup( )有什么用和BeginPopup()有什么关系 ImGui::OpenPopup( ) 标记一个弹出窗口Popup为“打开”状态。 该函数是 void没有返回任何值。 当你调用 ImGui::OpenPopup(name) 时它会将指定名称的弹出窗口设置为打开并允许在下一帧中渲染。虽然该函数没有返回值但它的调用效果会影响后续的窗口逻辑。 if (ImGui::Button(Open Popup)) {     ImGui::OpenPopup(MyPopup); } if (ImGui::BeginPopup(MyPopup)) {     ImGui::Text(This is a popup!);     ImGui::EndPopup(); } 示例使用方式返回值释义 ImGui::BeginPopup( ) 函数用于创建一个弹出窗口Popup Menu const char* name弹出窗口的唯一标识符。通常是一个字符串必须在同一帧中保持唯一。你可以使用字符串字面量或字符串变量。 ImGuiWindowFlags flags 0可选的窗口标志。可以用来设置弹出窗口的一些属性比如是否可关闭、是否有滚动条等。常用的标志包括 ImGuiWindowFlags_NoTitleBar没有标题栏。 ImGuiWindowFlags_AlwaysAutoResize窗口自动调整大小以适应内容。 ImGuiWindowFlags_Modal模态弹窗。 bool函数返回一个布尔值表示弹出窗口是否成功打开。如果弹出窗口被成功打开则返回 true否则返回 false。 如果弹出窗口的名字与其他窗口重复或者该弹出窗口没有被显示例如没有在之前调用 ImGui::OpenPopup(name)则会返回 false。 返回值参数释义 关系 ImGui::BeginPopup( ) 与 ImGui::OpenPopup( ) 存在一定关系并且会受到其影响。 ImGui::BeginPopup( ) 返回值受 OpenPopup( ) 影响。 如果指定名称的弹出菜单已经被请求打开即之前调用过 ImGui::OpenPopup(menu_name)并且该菜单尚未关闭则 BeginPopup 返回 true允许你在其内部渲染菜单项。 ImGui::OpenPopup( ) 是请求打开某个弹出菜单的函数。只有在调用了这个函数后对应的 ImGui::BeginPopup( ) 才会返回 true并开始渲染菜单。 如果你没有调用 OpenPopup( )即使你调用了 BeginPopup( )它也会返回 false因此无法进入菜单项的渲染逻辑。 总结如果没有请求打开该菜单或者菜单已经关闭则返回 false此时不会执行菜单内部的渲染代码。返回值: 》》》BeginPopupContextItem() 和 BeginPopup() 打开菜单的逻辑有什么不同 为什么 BeginPopupContextItem() 可以直接在目标控件之后调用而 BeginPopup() 还需要额外使用 OpenPopup() 判断控件是否允许打开菜单BeginPopupContextItem() 不需要 OpenPopup() 这个函数吗 触发机制 BeginPopup(): 需要程序员在代码中定义何时打开弹出菜单通常结合其他控件的逻辑例如按钮点击之后使用 OpenPopup()  允许菜单打开。 BeginPopupContextItem(): 不需要程序员在代码中定义何时打开弹出菜单函数会自动处理右键点击事件适合用在按钮、文本等控件上但无需按钮添加额外的事件处理逻辑只要在控件之后使用 BeginPopupContextItem( ) 即可 。 总结 所以说BeginPopupContextItem() 打开菜单的逻辑是自动的。BeginPopupContextItem() 不需要在前面显式调用 ImGui::OpenPopup(MyPopup)因为它会自动在用户右键点击时打开对应的弹出菜单。 而 BeginPopup() 打开菜单的逻辑是手动的BeginPopup() 需要在之前显式调用 OpenPopup()控件触发之后得到打开菜单的许可为按钮添加上打开菜单的逻辑。 》》》CloseCurrentPopup() 和EndPopup() 分别是什么函数有什么作用有什么不同为什么要分别调用 ImGui::CloseCurrentPopup() 该函数用于关闭当前打开的弹出菜单popup。 void ImGui::CloseCurrentPopup(); 无参数: CloseCurrentPopup() 不接受任何参数。 无返回值: 此函数没有返回值 (void)。它的作用是直接关闭当前活动的弹出菜单或模态窗口。 用途: 当你希望在某个交互例如点击某个按钮或进行其他操作后关闭当前的弹出菜单时可以调用此函数。 上下文: 该函数只能在当前弹出菜单的上下文中调用。如果没有打开的弹出菜单它不会产生任何效果。 说明返回值参数函数签名释义 ImGui::EndPopup() 该函数用于结束弹出菜单的定义。 void ImGui::EndPopup(); 无参数: EndPopup() 不接受任何参数。 无返回值: 此函数没有返回值 (void)。它的作用是结束当前的弹出菜单或模态窗口的渲染。 用途: EndPopup() 用于标记弹出菜单的结束。每次调用 BeginPopup() 时必须对应一次调用 EndPopup()以确保正确的渲染和状态管理。 上下文: 该函数只能在匹配的 BeginPopup() 调用之后使用。如果没有在 BeginPopup() 的上下文中调用 EndPopup()则可能导致未定义行为。 说明返回值参数函数签名释义 函数的不同之处 使用场景: 当用户选择了某个菜单项并执行操作之后例如 Camera 或 Sprite Renderer可以调用这个函数关闭菜单。它会立即终止当前弹出菜单的显示。 使用场景: 在你调用 ImGui::BeginPopup() 后需要用这个函数来标记菜单的结束。这是结构性要求确保 ImGui 知道你已经完成了对该弹出菜单内容的定义。 ImGui::EndPopup();ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();  用来关闭当前显示的弹出菜单。注重于交互行为渲染要求关闭菜单显示不再渲染菜单 ImGui::EndPopup();   用来结束当前代码中菜单的定义。注重于框架结构代码要求标记菜单结束进行后台处理 注意  通常情况下CloseCurrentPopup 是在某个选择操作后调用的而 EndPopup 则是在所有菜单项定义完成后调用的。 《《《   拓展Popup Menu 和 Context Menu Popup Menu 和 Context Menu 是两种常见的用户界面元素、 Popup Menu Popup Menu 是一种浮动的菜单可以在用户界面中的任何位置显示用来提供额外的选项或功能。 通常与当前上下文无关与某个操作如按钮点击、特定事件相关联。 通过用户的点击操作如按钮、图标等来触发需要手动添加触发逻辑。   用户点击一个按钮弹出一个菜单包含多个操作选项。 示例:特点:定义: Context Menu Context Menu 是一种特殊类型的 Popup Menu在用户鼠标点击的具体位置打开提供与所点击对象直接相关的操作。 它与用户当前的上下文密切相关用户当前正在操作或关注的对象。   通过右键点击某个 UI 元素如文件、文本、图标等来打开不需要手动添加逻辑。   用户在桌面上右键点击一个文件弹出一个菜单显示“打开”、“删除”、“重命名”等与该文件相关的选项。 示例:特点:定义: 》》》》发现一个问题关于代码为什么没有中断 前提在 DrawEntityNode() 函数中我们会在删除实体之后将 m_SelectionContext 同步清空。 原因这是因为如果在某种情况下触发事件使得 DrawEntityNode() 删除了 entityDrawComponents() 函数中使用的 m_SelecationContext 会因为访问已经被销毁的内存而导致中断错误。 DrawEntityNode() 中我们使 m_SelectionContext entity;  如果删除了 entity那么 m_SelectionContext 会因为访问被销毁的内存而报错因为我们在 DrawComponents() 中使用了错误的 m_SelectionContext 调用顺序如下 可是在我的代码中就算我没有在 DrawEntityNode() 中清空m_Selecation也不会导致报错中断 我猜这可能是因为 DrawTreeNode() 函数中使用的是复制传递而不是引用传递。这导致 DrawTreeNode() 尽管删除了 entity但 m_SelectionContext 中的值依然可以保持有效在随后的使用中 m_SelectionContext 可以一直保持可用的数值所以不会中断报错。 可是我这里的代码和 Cherno 没有什么差别为什么 Cherno 可以正常的触发断点而我不可以呢 希望能够发现错误或者原因之所在只可能是代码遗漏/一些仓库或规范的更新所导致的。 》》》》ImGui::BeginPopupContextWindow( ) 函数。 ImGui::BeginPopupContextWindow() 版本高于V1.87提供三个参数的函数重载 ImGui::BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mouse_button 1, bool also_over_items false); str_id: 类型const char* 用途为弹出菜单指定一个唯一的标识符。 mouse_button: 类型ImGuiMouseButton 默认值1代表右键 用途指定触发弹出菜单的鼠标按钮。可设置为 0左键、1右键或 2中键。 also_over_items: 类型bool 默认值false 用途如果设置为 true弹出菜单将在窗口内部的任何项上被右键点击所触发。这意味着即使是在子项上点击右键也可以显示弹出菜单。 参数说明函数原型 ImGui::BeginPopupContextWindow() 版本低于V1.87只能使用两个参数的重载 用于创建上下文菜单。它通常用于响应用户的右键点击或其他指定按钮并显示一个弹出菜单。 ImGui::BeginPopupContextWindow(const char* str_id NULL, ImGuiPopupFlags popup_flags 0); str_id: 类型const char* 默认值NULL 用途用于为弹出菜单指定一个唯一的标识符。如果多个弹出菜单使用相同的 ID则只有最后一个被打开。可以使用字符串常量或动态生成的字符串作为 ID。 popup_flags: 类型ImGuiPopupFlags 默认值0 用途用于设置弹出菜单的行为。可以选择的标志包括 ImGuiPopupFlags_None: 默认值。ImGuiPopupFlags_NoOpenOverExistingPopup: 如果有其他弹出窗口已经打开新的弹出窗口将不会显示。ImGuiPopupFlags_AlwaysOpen: 永远显示弹出窗口当条件不满足时也会强制打开。其他标志可以参考 ImGui 的文档。参数函数签名释义 BeginPopupContextItem() 和 BeginPopupContextwindow() 有什么不同 分析         BeginPopupContextWindow 是针对整个窗口的右键菜单。 BeginPopupContextItem 是针对特定 UI 元素的右键菜单。 结论 如果你希望在用户右键点击窗口任何地方时显示菜单使用 BeginPopupContextWindow( )。 如果你希望只在某个具体元素上右键点击时显示菜单使用 BeginPopupContextItem( )。 》》》》代码中的问题/设计BeginPopupContextWindow() 的参数、使用上的 bug 问题一函数使用问题解决 Cherno 在程序中创建 PopupContextWindow 时使用新版 ImGui 中的重载即 PopupContextWindow() 拥有三个参数 if (ImGui::BeginPopupContextWindow(0, 1, false)) 但由于我的 ImGui 版本低于1.87所以函数没有第三个参数可以使用如果需要实现类似的效果就需要手动设置 ImGuiPopupFlags if (ImGui::BeginPopupContextWindow(0, 1 | ImGuiPopupFlags_NoOpenOverItems)) 那么这个参数实现的是什么效果呢 如果不添加这个设置/参数只填入默认值 或者禁用覆盖效果v1.87之后版本) 则会得到以下的结果 如上述所示如果不进行特别要求禁止窗口进行覆盖操作 - ImGuiPopupFlags_NoOpenOverItems那么程序在运行时将会出现一个错误无论右键单击树节点/窗口空白处都只会触发 PopupContextWindow 设置的弹出菜单这其中的原因正是没有进行额外的设置。 除非我们通过参数禁止弹出菜单将在窗口内部的任何项上被右键点击所触发即如果有其他弹出窗口已经打开新的弹出窗口将不会显示。 问题二鼠标单击树节点右侧空白时触发的是 ContextWindow 而非亟待解决 这可能与树节点的判定范围有一定关系。 》》》》绘制时出现的问题按钮被窗口覆盖了一部分 尽管我计算了合适的按钮的大小并使用计算出来的按钮宽高长度绘制按钮。但是为何设置按钮位置的时候我明明使用窗口宽度减去了按钮宽度按钮依旧被窗口覆盖了一部分 原因 内边距和外边距 ImGui 窗口通常有默认的内边距Style.WindowPadding和外边距Style.ItemSpacing。如果不考虑这些因素计算的宽度可能会导致按钮超出可见区域。 而且 GetWindowWidth() 返回的是包括边框的宽度而按钮实际上可能需要稍微往左移动一点。 解决方案 1.使用 GetContentRegionAvail() ImGui::GetContentRegionAvail() 返回的是窗口中可用区域的宽度不包括任何边框、标题栏和内边距我们可以使用 ImGui::GetContentRegionAvail().x 获取分量计算可用空间。 Eg. float availableWidth ImGui::GetContentRegionAvail().x; ImGui::SameLine(availableWidth - buttonWidth); 2.调整按钮位置 如果你知道按钮的宽度可以稍微减小计算结果来留出一些空间. Eg. Float availableWidth ImGui::GetWindowWidth(); ImGui::SameLine(availableWidth - buttonWidth - extraSpace); -------- Make Editor look good ------- 》》》》My different 因为我在处理 CameraController 的时候使用了 SceneHierarchyPanel 的非静态成员变量 m_Context 所以我需要更改一下捕获方式。 》》》》关于 ImGuiTreeNodeFlags_Framed ImGuiTreeNodeFlags_FramePadding 的定义 默认情况 ImGuiTreeNodeFlags_Framed 作用: 这个标志使树节点具有一个框架frame样式。这意味着树节点会有一个边框看起来像是一个独立的可点击区域通常用于视觉上将节点与背景区分开。 使用场景: 如果你想要节点在视觉上更突出或者希望它们看起来像按钮可以使用这个标志。 ImGuiTreeNodeFlags_FramePadding 作用: 这个标志会在树节点的内容周围添加内边距padding。这会增加节点内容与其边框之间的空间使得节点的内容看起来不那么拥挤。 使用场景: 当你希望树节点中的文本或其他内容如图标与边框保持一定距离从而提高可读性和美观时可以使用这个标志。
http://www.yingshimen.cn/news/38608/

相关文章:

  • 做油漆的网站php淘宝商城网站源码
  • 网站导航条内容美食网站代做
  • 滑县网站建设价格网站项目进度
  • wordpress怎样建立多站点wordpress评论不显示头像
  • 织梦生成手机网站像那种代刷网站怎么做
  • 九江做网站开发需要多少钱网站流量工具
  • 做网站有什么意义重庆网站哪里好
  • 成品网站源码68w68wordpress 谷歌字体
  • 做我男朋友的网站高端个性化网站建设
  • 郑州网站建设公司代运营长沙手机网站建设哪些内容
  • 网站开发 定制 合同 模板网站做二级站
  • 网站建设优化推广杭州企业标识设计
  • 大连最好的网站制作公司福建省住房与城乡建设部网站
  • 中国建设监理协会网站查询成绩做购彩网站是怎么盈利的
  • 网站开发工程师需要什么技术自建网站平台有哪些
  • 网站做收款要什么条件国外好的网站
  • 佛山做网站的公司有哪些成都专业网站推广
  • 天津手机网站公司做装修行业营销型网站
  • 门户网站字体软件网站是怎么做的
  • 怎样给建设的网站提意见黑色网站素材
  • 内乡网站建设台州网站建设网站推广
  • 想做个网站都需要什么广州网站建设 粤icp
  • 手机网站 uiwordpress生成程序
  • 公众号做网站各大网站网址目录
  • 网站注册可以免费吗白云区建网站设计
  • 巢湖建设网站网络推广渠道有哪些方式
  • 想做一个驾校的招生网站应该怎么做good work wordpress
  • 怎么搭建自己的网站挣钱广州百度竞价托管
  • 河东网站建设小程序代理模板
  • 网站建设与规划实验心得体会湘潭网站建设 要选磐石网络