VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
ComponentFactory.hpp
Go to the documentation of this file.
1
6
7#pragma once
9#include "components/ErrorUtils.hpp"
10#include "components/PathUtils.hpp"
12#include "components/ColorTypes.hpp"
14#include <nlohmann/json.hpp>
15#include <functional>
16#include <unordered_map>
17#include <string>
18#include <glm/glm.hpp>
19#include <components/Mesh.hpp>
20
21#if DEBUG
22#include <ImReflect.hpp>
23namespace vex
24{
25 ImTextureID GetEditorIcon_Internal(const std::string &name);
26}
27
28 template <typename T>
29 void DrawAssetSlot(const char *label, T &value, ImTextureID icon, const char *hint)
30 {
31 ImGui::PushID(label);
32
33 float thumbnailSize = 32.0f;
34 std::string ext = std::filesystem::path(value.c_str()).extension().string();
35 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
36
37 bool good = true;
38 if constexpr (std::is_same_v<T, vex::mesh_asset_path>)
39 {
40 good = vex::AssetExtensions::IsValid(ext, vex::AssetExtensions::Mesh);
41 }
42 else if constexpr (std::is_same_v<T, vex::texture_asset_path>)
43 {
44 good = vex::AssetExtensions::IsValid(ext, vex::AssetExtensions::Texture);
45 }
46 else if constexpr (std::is_same_v<T, vex::audio_asset_path>)
47 {
48 good = vex::AssetExtensions::IsValid(ext, vex::AssetExtensions::Audio);
49 }
50
51 ImTextureID fileIcon = vex::GetEditorIcon_Internal("file");
52 ImTextureID drawIcon = (value.empty()) ? fileIcon : icon;
53 if (drawIcon == 0)
54 {
55 drawIcon = fileIcon;
56 }
57
58 ImGui::BeginGroup();
59
60 if (ImGui::ImageButton("##AssetIcon", drawIcon, {thumbnailSize, thumbnailSize}, ImVec2(0, 0), ImVec2(1, 1)))
61 {
63 }
64
65 ImGui::SameLine();
66 ImGui::BeginGroup();
67
68 static ImGuiID activeAssetID = 0;
69 ImGuiID currentID = ImGui::GetID("##PathInput");
70
71 if (activeAssetID == currentID)
72 {
73 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0))
74 {
75 ImGui::SetKeyboardFocusHere(0);
76 }
77
78 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
79
80 if (ImGui::InputText("##PathInput", (std::string *)&value, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
81 {
82 activeAssetID = 0;
83 }
84
85 if (!ImGui::IsItemActive() && (ImGui::IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsMouseClicked(ImGuiMouseButton_Right)))
86 {
87 activeAssetID = 0;
88 }
89 }
90 else
91 {
92 static std::unordered_map<std::string, std::pair<bool, double>> fileCache;
93 bool fileExists = true;
94
95 if (!value.empty())
96 {
97 double currentTime = ImGui::GetTime();
98 auto &entry = fileCache[value];
99
100 if (currentTime - entry.second > 5.0f)
101 {
102 entry.first = std::filesystem::exists(vex::GetAssetPath(value));
103 entry.second = currentTime;
104 }
105 fileExists = entry.first;
106 }
107
108 std::string displayStr;
109 bool colorPushed = false;
110 if (value.empty())
111 {
112 displayStr = "Empty (" + std::string(hint) + ")";
113 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
114 colorPushed = true;
115 }
116 else
117 {
118 displayStr = std::filesystem::path(value.c_str()).filename().string();
119 if (!fileExists || !good)
120 {
121 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.1f, 0.1f, 1.0f));
122 colorPushed = true;
123 }
124 }
125
126 if (ImGui::Selectable(displayStr.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick))
127 {
128 activeAssetID = currentID;
129 }
130
131 if (colorPushed)
132 {
133 ImGui::PopStyleColor();
134 }
135
136 if (!value.empty() && ImGui::IsItemHovered())
137 {
138 ImGui::SetTooltip("%s", value.c_str());
139 }
140 }
141 ImGui::EndGroup();
142
143 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.47f, 0.05f, 0.05f, 1.0f));
144 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.71f, 0.10f, 0.10f, 1.0f));
145 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.30f, 0.03f, 0.03f, 1.0f));
146 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.94f, 0.85f, 0.85f, 1.0f));
147
148 if (ImGui::Button("Clear", ImVec2(0, 0)))
149 {
150 value.clear();
151 }
152
153 ImGui::PopStyleColor(4);
154 ImGui::EndGroup();
155
156 if (ImGui::BeginDragDropTarget())
157 {
158 if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("ASSET_ITEM"))
159 {
160 std::string fullPath = (const char *)payload->Data;
161
162 std::string ext = std::filesystem::path(fullPath).extension().string();
163 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
164
165 bool valid = true;
166
167 if constexpr (std::is_same_v<T, vex::mesh_asset_path>)
168 {
169 valid = vex::AssetExtensions::IsValid(ext, vex::AssetExtensions::Mesh);
170 }
171 else if constexpr (std::is_same_v<T, vex::texture_asset_path>)
172 {
173 valid = vex::AssetExtensions::IsValid(ext, vex::AssetExtensions::Texture);
174 }
175 else if constexpr (std::is_same_v<T, vex::audio_asset_path>)
176 {
177 valid = vex::AssetExtensions::IsValid(ext, vex::AssetExtensions::Audio);
178 }
179
180 if (valid)
181 {
182 value = std::filesystem::relative(fullPath, vex::GetAssetDir()).string();
183 }
184 }
185 ImGui::EndDragDropTarget();
186 }
187 ImGui::PopID();
188 }
189
190 inline void tag_invoke(ImReflect::ImInput_t, const char *label, vex::mesh_asset_path &value, ImSettings &settings, ImResponse &response)
191 {
192 DrawAssetSlot(label, value, vex::GetEditorIcon_Internal("mesh"), "Mesh");
193 }
194
195 inline void tag_invoke(ImReflect::ImInput_t, const char *label, vex::texture_asset_path &value, ImSettings &settings, ImResponse &response)
196 {
197 DrawAssetSlot(label, value, vex::GetEditorIcon_Internal("texture"), "Texture");
198 }
199
200 inline void tag_invoke(ImReflect::ImInput_t, const char *label, vex::audio_asset_path &value, ImSettings &settings, ImResponse &response)
201 {
202 DrawAssetSlot(label, value, vex::GetEditorIcon_Internal("audio"), "Audio");
203 }
204
205 inline void tag_invoke(ImReflect::ImInput_t, const char *label, vex::asset_path &value, ImSettings &settings, ImResponse &response)
206 {
207 DrawAssetSlot(label, value, vex::GetEditorIcon_Internal("file"), "Asset");
208 }
209
210 inline void tag_invoke(ImReflect::ImInput_t, const char* label, glm::vec4& value, ImSettings& settings, ImResponse& response) {
211 ImGui::DragFloat4(label, &value.x);
212 }
213
214 inline void tag_invoke(ImReflect::ImInput_t, const char* label, glm::vec3& value, ImSettings& settings, ImResponse& response) {
215 ImGui::DragFloat3(label, &value.x);
216 }
217
218 inline void tag_invoke(ImReflect::ImInput_t, const char* label, glm::vec2& value, ImSettings& settings, ImResponse& response) {
219 ImGui::DragFloat2(label, &value.x);
220 }
221
222 inline void tag_invoke(ImReflect::ImInput_t, const char* label, vex::rgb& value, ImSettings& settings, ImResponse& response) {
223 ImGui::ColorEdit3(label, &value.x);
224 }
225
226 inline void tag_invoke(ImReflect::ImInput_t, const char* label, vex::rgba& value, ImSettings& settings, ImResponse& response) {
227 ImGui::ColorEdit4(label, &value.x);
228 }
229
230 IMGUI_REFLECT(vex::rgb, x, y, z);
231 IMGUI_REFLECT(vex::rgba, x, y, z, w);
232 IMGUI_REFLECT(glm::vec2, x, y);
233 IMGUI_REFLECT(glm::vec3, x, y, z);
234 IMGUI_REFLECT(glm::vec4, x, y, z, w);
235 IMGUI_REFLECT(vex::MeshData, meshPath);
236
237 inline void tag_invoke(ImReflect::ImInput_t, const char* label, glm::quat& value, ImSettings& settings, ImResponse& response) {
238 glm::vec3 euler = glm::degrees(glm::eulerAngles(value));
239 if (ImGui::DragFloat3(label, &euler.x)) {
240 value = glm::quat(glm::radians(euler));
241 }
242 }
243
244 inline void tag_invoke(ImReflect::ImInput_t, const char* label, glm::ivec2& value, ImSettings& settings, ImResponse& response) {
245 ImGui::DragInt2(label, &value.x);
246 }
247 inline void tag_invoke(ImReflect::ImInput_t, const char* label, glm::ivec3& value, ImSettings& settings, ImResponse& response) {
248 ImGui::DragInt3(label, &value.x);
249 }
250
251 IMGUI_REFLECT(glm::quat, x, y, z, w);
252 IMGUI_REFLECT(glm::ivec2, x, y);
253 IMGUI_REFLECT(glm::ivec3, x, y, z);
254#else
255 #define IMGUI_REFLECT(...);
256#endif
257
258namespace vex {
259
260 template<typename T>
261 void GenericComponentInspector(GameObject& obj) {
262 #if DEBUG
263 if (obj.HasComponent<T>()) {
264 std::string name = typeid(T).name();
265 size_t lastColon = name.rfind("::");
266 size_t lastBracket = name.find(']');
267 std::string extracted = (lastColon != std::string::npos)
268 ? name.substr(lastColon + 2, (lastBracket != std::string::npos ? lastBracket : name.length()) - (lastColon + 2))
269 : name;
270
271 ImGui::PushID(name.c_str());
272 if (ImGui::CollapsingHeader(extracted.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
273 auto& component = obj.GetComponent<T>();
274
275 ImReflect::Input("##data", component);
276
277 ImGui::Dummy(ImVec2(5.0f, 5.0f));
278
279 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.47f, 0.05f, 0.05f, 1.0f));
280 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.71f, 0.10f, 0.10f, 1.0f));
281 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.30f, 0.03f, 0.03f, 1.0f));
282 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.94f, 0.85f, 0.85f, 1.0f)); // White Text
283
284 if (ImGui::Button("Remove")) {
285 obj.GetEngine().getRegistry().remove<T>(obj.GetEntity());
286 }
287
288 ImGui::PopStyleColor(4);
289 }
290 ImGui::PopID();
291 }
292 #else
293 (void)obj;
294 #endif
295 }
296
298class ComponentRegistry {
299public:
300 using ComponentLoader = std::function<void(GameObject&, const nlohmann::json&)>;
301 using ComponentSaver = std::function<nlohmann::json(GameObject&)>;
302 using ComponentInspector = std::function<void(GameObject&)>;
303 using ComponentChecker = std::function<bool(const GameObject&)>;
304 using ComponentCreator = std::function<void(GameObject&)>;
305
306 static ComponentRegistry& getInstance();
307
317 template<typename T>
318 void registerComponent(const std::string& name, bool isDynamic = false) {
319 loaders[name] = [](GameObject& obj, const nlohmann::json& j) {
320 if (!obj.HasComponent<T>()) {
321 if constexpr (std::is_default_constructible_v<T>) {
322 obj.AddComponent<T>(T());
323 }
324 }
325 if (obj.HasComponent<T>()) {
326 T& comp = obj.GetComponent<T>();
327 j.get_to(comp);
328 }
329 };
330
331 savers[name] = [](GameObject& obj) -> nlohmann::json {
332 if (obj.HasComponent<T>()) {
333 return obj.GetComponent<T>();
334 }
335 return nullptr;
336 };
337
338 inspectors[name] = &GenericComponentInspector<T>;
339
340 checkers[name] = [](const GameObject& obj) {
341 return obj.HasComponent<T>();
342 };
343
344 creators[name] = [](GameObject& obj) {
345 if (!obj.HasComponent<T>()) {
346 if constexpr (std::is_default_constructible_v<T>) {
347 obj.AddComponent<T>(T());
348 } else {
349 log("Error: Component '%s' is not default constructible.", typeid(T).name());
350 }
351 }
352 };
353
354 registeredNames.push_back(name);
355
356 if (isDynamic) {
357 dynamicComponents.push_back(name);
358 }
359 }
360
364 void unregisterComponent(const std::string& name);
365
369
376 bool loadComponent(GameObject& obj, const std::string& type, const nlohmann::json& json);
377
382 nlohmann::json saveComponent(GameObject& obj, const std::string& type);
383
388
393 void createComponent(GameObject& obj, const std::string& name);
394
396 std::vector<std::string> getMissingComponents(const GameObject& obj);
397
399 const std::unordered_map<std::string, ComponentInspector>& getInspectors() const;
400
402 const std::vector<std::string>& getRegisteredNames() const;
403
404 #if DEBUG
405 void SetEditorIcon(const std::string &name, ImTextureID icon);// { m_editorIcons[name] = icon; }
406 ImTextureID GetEditorIcon(const std::string &name);// { return m_editorIcons[name]; }
407 #endif
408
409private:
410 ComponentRegistry() = default;
411 std::vector<std::string> registeredNames;
412 std::vector<std::string> dynamicComponents;
413 std::unordered_map<std::string, ComponentLoader> loaders;
414 std::unordered_map<std::string, ComponentSaver> savers;
415 std::unordered_map<std::string, ComponentInspector> inspectors;
416 std::unordered_map<std::string, ComponentChecker> checkers;
417 std::unordered_map<std::string, ComponentCreator> creators;
418 #if DEBUG
419 std::unordered_map<std::string, ImTextureID> m_editorIcons;
420 #endif
421};
422
423}
424
426#define VEX_CAT_IMPL(a, b) a##b
427#define VEX_CAT(a, b) VEX_CAT_IMPL(a, b)
428#define VEX_UNIQUE_NAME(prefix) VEX_CAT(prefix, __LINE__)
429
430#ifdef GAME_MODULE_EXPORTS
436 #define REGISTER_COMPONENT(Type, ...) \
437 namespace vex { NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, __VA_ARGS__) } \
438 IMGUI_REFLECT(Type, __VA_ARGS__) \
439 namespace { \
440 struct VEX_UNIQUE_NAME(Registrar_) { \
441 VEX_UNIQUE_NAME(Registrar_)() { \
442 vex::ComponentRegistry::getInstance().registerComponent<Type>(#Type, true); \
443 } \
444 }; \
445 static VEX_UNIQUE_NAME(Registrar_) VEX_UNIQUE_NAME(g_registrar_); \
446 }
447
449 #define REGISTER_COMPONENT_CUSTOM(Type, ...) \
450 IMGUI_REFLECT(Type, __VA_ARGS__) \
451 namespace { \
452 struct VEX_UNIQUE_NAME(Registrar_) { \
453 VEX_UNIQUE_NAME(Registrar_)() { \
454 vex::ComponentRegistry::getInstance().registerComponent<Type>(#Type, true); \
455 } \
456 }; \
457 static VEX_UNIQUE_NAME(Registrar_) VEX_UNIQUE_NAME(g_registrar_); \
458 }
459
460#else
466 #define REGISTER_COMPONENT(Type, ...) \
467 namespace vex { NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, __VA_ARGS__) } \
468 IMGUI_REFLECT(Type, __VA_ARGS__) \
469 namespace { \
470 struct VEX_UNIQUE_NAME(Registrar_) { \
471 VEX_UNIQUE_NAME(Registrar_)() { \
472 vex::ComponentRegistry::getInstance().registerComponent<Type>(#Type, false); \
473 } \
474 ~VEX_UNIQUE_NAME(Registrar_)() { \
475 vex::ComponentRegistry::getInstance().unregisterComponent(#Type); \
476 } \
477 }; \
478 static VEX_UNIQUE_NAME(Registrar_) VEX_UNIQUE_NAME(g_registrar_); \
479 }
480
482 #define REGISTER_COMPONENT_CUSTOM(Type, ...) \
483 IMGUI_REFLECT(Type, __VA_ARGS__) \
484 namespace { \
485 struct VEX_UNIQUE_NAME(Registrar_) { \
486 VEX_UNIQUE_NAME(Registrar_)() { \
487 vex::ComponentRegistry::getInstance().registerComponent<Type>(#Type, false); \
488 } \
489 ~VEX_UNIQUE_NAME(Registrar_)() { \
490 vex::ComponentRegistry::getInstance().unregisterComponent(#Type); \
491 } \
492 }; \
493 static VEX_UNIQUE_NAME(Registrar_) VEX_UNIQUE_NAME(g_registrar_); \
494 }
495#endif
Contains basic components like transform, camera, name components..
This file defines the GameObject class.
This file defines MeshData struct and all other structs needed for it.
Allows for imgui and json serialization of components, for easy loading, saving and drawing them.
Class registering GameComponents mainly used to load them in SceneManager.
nlohmann::json saveComponent(GameObject &obj, const std::string &type)
Serializes a component on a GameObject to JSON.
const std::unordered_map< std::string, ComponentInspector > & getInspectors() const
Get component inspectors.
void registerComponent(const std::string &name, bool isDynamic=false)
Template function to register a component type with the system.
void clearDynamicComponents()
Clears all components registered as dynamic.
bool loadComponent(GameObject &obj, const std::string &type, const nlohmann::json &json)
Loads data into a component on a GameObject from JSON.
void createComponent(GameObject &obj, const std::string &name)
Creates and attaches a specific component to a game object.
std::vector< std::string > getMissingComponents(const GameObject &obj)
Get missing components for a game object.
const std::vector< std::string > & getRegisteredNames() const
Get registered component names.
void unregisterComponent(const std::string &name)
Unregisters a component type and removes all associated callbacks.
void drawInspectorForObject(GameObject &obj)
Draws the ImGui inspector for all components on the object.
Its base class for all game objects, eg. Player, Enemy, Weapon. Your Class needs to inherit from it.
bool HasComponent() const
Function that checks if the GameObject has a component.
T & GetComponent()
Function that returns reference to a requested component.
void AddComponent(const T &comp)
Function that adds a component to the GameObject.
std::string VEX_EXPORT GetAssetDir()
Gets the current asset directory override.
Definition PathUtils.cpp:29
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
std::string VEX_EXPORT GetAssetPath(const std::string &relativePath)
Resolves the absolute path of an asset based on the current build configuration.
Definition PathUtils.cpp:73
Mesh data structure for loading and managing mesh data.
Definition Mesh.hpp:41
Base wrapper for generic assets. Currently doesnt add any functionality. Its used mainly for custom f...
Specific wrapper for Audio.
Specific wrapper for Meshes.
Represents an RGB color value. Used mainly for fancy rendering in editor.
Definition ColorTypes.hpp:7
Represents an RGBA color value. Used mainly for fancy rendering in editor.
Specific wrapper for Textures.