Jump to content

Andy90

Members
  • Posts

    189
  • Joined

  • Last visited

Everything posted by Andy90

  1. It would be good if we could get more input options for components in the editor. Such as lists for entities, strings, floats, Vec3 and files. Like in the example bellow.
  2. There is a problem with the prefabs. If you create a prefab, drag it into the scene and then save the scene, it spawns in the game in the wrong location or is not visible at all. (due to the wrong positioning)
  3. It's your decision, of course, but I personally don't think it's contemporary that a scene can only have one terrain.
  4. so its not possible to create an world out of multiple terrains ?
  5. In this article, I would like to delve into the concept of "Game Logs" and explain why they play an important role. First, however, we should clarify what exactly a Game Log is. The Game Log allows us to log and temporarily store various actions in the game. Furthermore, with the help of the Game Log, we can inform independent systems about various actions. A good example of this is a simple quest system, similar to the one in World of Warcraft. In such systems, we want to complete quests by, for example, looting a specific item, killing a certain enemy, or interacting with a specific NPC. But how are the individual quests supposed to know exactly when we loot an item? Sure, one could simply search through the inventory. However, that would only work for quest types where we are supposed to loot an item, and not for those where we are supposed to kill a specific enemy or talk to an NPC. This is where the Game Log proves to be extremely useful, as our quests can simply search through the entries to update themselves accordingly. In the past few days, I have been working on implementing such a system in C++. It is important to note that this is not a component; instead, a "Global.h" file is required, where the log is defined as a global variable. GameLog.h #pragma once #include "UltraEngine.h" using namespace UltraEngine; /// <summary> /// Event types /// </summary> enum GameLogEvent { GAME_LOG_LOOT_ITEM, GAME_LOG_KILL_ENEMY }; /// <summary> /// Game Log entrys /// </summary> struct GameLogEntry { String uuid; uint64_t timestamp; GameLogEvent event; table data; }; /// <summary> /// GameLogListner forward declaration /// </summary> class GameLogListener; /// <summary> /// Handles the game log entrys /// </summary> class GameLog { private: vector<shared_ptr<GameLogListener>> m_listeners; vector<GameLogEntry> m_log; public: void Log(GameLogEvent event, table eventData); void AddListener(shared_ptr<GameLogListener> listener); void LogLootItem(String itemName, int itemCount); void LogKilledEnemy(String enemyName); void Clean(uint64_t time); vector<GameLogEntry> FetchLog(uint64_t startTime); }; GameLog.cpp #include "UltraEngine.h" #include "GameLog.h" #include "GameLogListener.h" void GameLog::Log(GameLogEvent event, table eventData) { GameLogEntry entry; entry.uuid = Uuid(); entry.timestamp = Millisecs(); entry.event = event; entry.data = eventData; m_log.push_back(entry); for (auto listener : m_listeners) { if (listener->Event == event) { listener->Callback(eventData); } } Print("Added data to gamelog"); } void GameLog::AddListener(shared_ptr<GameLogListener> listener) { m_listeners.push_back(listener); } void GameLog::LogLootItem(String itemName, int itemCount) { table data; data["itemName"] = itemName; data["itemCount"] = itemCount; Log(GAME_LOG_LOOT_ITEM, data); } void GameLog::LogKilledEnemy(String enemyName) { table data; data["enemyName"] = enemyName; Log(GAME_LOG_KILL_ENEMY, data); } void GameLog::Clean(uint64_t time) { vector<GameLogEntry> newEntrys; for (auto entry : m_log) { auto diff = Millisecs() - entry.timestamp; if (diff <= time) { newEntrys.push_back(entry); } else { Print("Entry " + entry.uuid + " out of time!"); } } m_log = newEntrys; } vector<GameLogEntry> GameLog::FetchLog(uint64_t startTime) { vector<GameLogEntry> result; for (auto entry : m_log) { if (entry.timestamp > startTime) { result.push_back(entry); } } return result; } In the file GameLog.h, we find an enum with various events, to which more can be added. Currently, there are entries for looting an item and killing an enemy. Furthermore, the structure "GameLogEntry" defines various variables, including a string for a UUID, a long variable for the timestamp, the event, and the data of the entry. Following that is the main class, which contains various functions as well as two lists. One list is for so-called hooks, and the other is for the log itself. Here's an overview of the functions: Log(): Adds entries to the log. AddListener(): Adds a callback (no callback is needed in this article). LogLootItem(): Simplifies adding a loot event to the game's log. LogKilledEnemy(): Simplifies adding a kill event. Clean(uint64_t time): Deletes all entries older than the specified parameter in milliseconds. vector<GameLogEntry> FetchLog(uint64_t startTime): Returns a list of log entries whose timestamp is greater than the start time in the parameter. If we want to determine whether we have looted an item or killed a specific enemy, we can simply retrieve the log. It is important to store the time of the last query so that we only receive the entries that are still unknown to us. void VitalUI::UpdateNotifications() { m_notifyer->ClearItems(); auto fetchTime = Millisecs() - 5000; auto log = g_log.FetchLog(fetchTime); for (auto entry : log) { if (entry.event == GAME_LOG_LOOT_ITEM) { m_notifyer->AddItem("Looted " + String(entry.data["itemName"]) + " (X" + String(entry.data["itemCount"]) + ")"); } } if (m_notifyer->items.size() > 0) { m_notifyer->Redraw(); } } To ensure that the log does not grow infinitely, it is advisable to clear it regularly. In my implementation, I clear the log every 6 minutes in the main loop. That should be sufficient to process all events. while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false) { while (PeekEvent()) { const Event ev = WaitEvent(); g_ui->GetInterface()->ProcessEvent(ev); switch (ev.id) { case EVENT_WIDGETACTION: break; case EVENT_WINDOWCLOSE: return 0; break; } } world->Update(); world->Render(framebuffer, false, 0); g_log.Clean(60000); } As you can see, a Game Log has become extremely practical and indispensable in modern games. If you have any questions about this article, feel free to contact me. Additionally, it's worth mentioning that the log can be used for many other purposes and is not limited to just a quest system. Gamelog.zip
  6. I think its an issue with the model itself. Check this model Vampire.rar
  7. well i my case a simple stamp tool is enough. Because right now you cant controll the height in the sculpt mode
  8. okey i think this would be the easy way to do it. Instead of replace some mesh verticies. Thanks
  9. ah so in summory you added all cloths to the character and just hide all the cloth meshs. When a player equip some shirt you show this mesh again.
  10. With this straightforward component, you can specify the pick mode for the entity. Options include None, Mesh, and Collider. The pick mode is automatically and recursively configured for each child entity. Pickmode.zip
  11. Hey, as I'm porting my game from Leadwerks to Ultra, I've come across some important terrain tools that are currently not available in the engine. Terrain Stamping: Having a function to raise or lower the terrain at a given height would be beneficial. For example, I'm trying to create a harbor, but I'm unable to create smooth terrain around the harbor base model. Terrain Smoothing: There are times when you want to smooth out the terrain to eliminate sharp edges.
  12. i think you would need the bone weights for it.
  13. I'm considering a clothing system in Ultra and how I could implement it. Essentially, there are two approaches. One is to simply split the mesh into four parts (head, body, legs, and feet) and create different meshes with clothes for each part. The second, and in my opinion better, approach is to handle the clothes separately from the player model itself. This means having a blank model for your player and attaching the clothes to it. However, I'm not sure if this is possible. Additionally, I'm uncertain about how to do this.
  14. I think you need an gltf not glb file.
  15. The scene list for the entity picker insidee the components is empty
  16. I think it was about the lod generation. The lod generation should be not related to the terrain only.
  17. Hello, when you change a map the usage memory increase but never decrease. This means there is something allive what should not be allive. You can reproduce it with this code. Just hit multiple times T and see how the memory increases every time. #include "UltraEngine.h" #include "ComponentSystem.h" //#include "Steamworks/Steamworks.h" using namespace UltraEngine; int main(int argc, const char* argv[]) { #ifdef STEAM_API_H if (not Steamworks::Initialize()) { RuntimeError("Steamworks failed to initialize."); return 1; } #endif RegisterComponents(); auto cl = ParseCommandLine(argc, argv); //Load FreeImage plugin (optional) auto fiplugin = LoadPlugin("Plugins/FITextureLoader"); //Get the displays auto displays = GetDisplays(); //Create a window auto window = CreateWindow("Ultra Engine", 0, 0, 1280 * displays[0]->scale, 720 * displays[0]->scale, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR); //Create a framebuffer auto framebuffer = CreateFramebuffer(window); //Create a world auto world = CreateWorld(); //Load the map WString mapname = "Maps/start.ultra"; if (cl["map"].is_string()) mapname = std::string(cl["map"]); auto scene = LoadMap(world, mapname); //Main loop while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false) { world->Update(); world->Render(framebuffer); #ifdef STEAM_API_H Steamworks::Update(); #endif if (window->KeyHit(KEY_T)) { scene = LoadMap(world, mapname); } } #ifdef STEAM_API_H Steamworks::Shutdown(); #endif return 0; }
  18. Painting on a texture on top of another results in Artifacts at the borders. https://streamable.com/hgdv1l You can download an example project here: https://1drv.ms/u/s!Aum5gwXzTrOy9WGT-qiEPXh1arTO?e=pBfovc
  19. This is interesting. I dont know this. Maybe its possible with this to load files from a server.
  20. I think. Its from Mixamo its an model and animation libary which is used very often.
  21. @Joshdid you found allready a solution for this issue? Got the same problem. Just drag the character into the scene. Scale it to 1.0 and play the animation. Wired things happen Here is the gltf from the model. Human_2.zip
  22. Due to several inquiries about how I created my inventory, crafting, and storage systems, I would like to explain my approach in more detail. In this blog post, I won't use any code but rather focus on explaining the underlying logic. 1. The InventoryItem Class The foundation of these systems is an Item class, which I abstractly created to represent different types of items (materials, placeable objects, weapons, consumables, etc.). The reason for an abstract class is straightforward: it allows me to pack all items into a vector and call defined functions based on the situation, such as OnPickup, OnDrop, and OnUse. Additionally, I've created an Item Definition JSON, which defines items, including details like icon, name, maximum stack size, and item type. However, this isn't crucial and can be skipped. 2. The Gridview Widget After defining my base class, I created a custom widget for a GridView, as I introduced in a previous post. However, this widget doesn't use the previously created Item class; instead, it has its own GridView items. Why? Because it's crucial to separate data from the GUI, and I can use it in various other projects. The GridView serves to visually represent the items, and we'll delve into how it works later. 3. How to Display InventoryItems within the Widget Now that I have the basic Item class and a GridView for displaying inventory content, let's move on to the inventory itself. The Inventory class is not a component; rather, it's a simple C++ class that includes a vector for items and various functions to add, remove, and retrieve items. Additionally (for simplicity), I created a reference to a Grid Widget for the inventory. When a change in items occurs, this GridView is updated. The process involves removing GridView items and adding new GridView items based on the inventory's items. Now I have an inventory that shows the items the player has in their inventory. 4. New Items for the Player Now let's talk about how the player obtains items, which is quite simple. For this, I created a component that assigns items to the inventory based on various conditions, such as the passage of time. 5. The Crafting UI & Concept Since we can now gather items and see them in the inventory, let's do more with these items. We want to craft ItemZ from ItemX and ItemY. For crafting itself, I've also created a component (named Workbench) and its own UI. This is a bit more complex, so I'll try to explain it as precisely as possible. The UI has two GridViews and a ListView, along with various other widgets that aren't as important. What's crucial is that I only have one UI managing the workbenches. For this, I created two global variables: one for the panel (the UI) and one for the component that's currently the owner of the UI. When the player opens the UI using a workbench, the UI owner is also set simultaneously. It can only be reopened after being closed, as the owner is then set back to a null pointer. When opening, I load all recipes into the ListView. The recipes themselves are defined in a JSON file. When the player selects an item they want to craft, the required materials are loaded into the first GridView. This serves purely as visual information and has no further functions. 6. Drag & Drop Items Now let's discuss how I allow the player to place items from their inventory into the crafting window. For this, I created another class called ItemMover. Its task is to temporarily pick up an item and then transfer it. I use the events sent by the GridView. When the player performs a mouse drag, I now pick up the item(s) from the inventory slot and set it as a reference to the ItemMover. If I then receive the MouseDown event on another GridView, I check if the ItemMover has an item. If so, I add the item from the Mover to the Workbench's vector, update the UIs, and delete the item from the Mover. To make this look visually appealing with drag and drop, I created a simple widget that becomes visible when the ItemMover holds an item and is always set to the mouse's position. It becomes invisible on item drop. 7. The Crafting itself Crafting itself is again quite simple. When the player clicks the button, the Workbench checks if the vector of materials (using the Item class again) has enough of the required items. If yes, the item is crafted and added to the vector of materials. 8. Get the Crafted items into the inventory To allow the player to now take out the crafted item, we again use the ItemMover and drag-and-drop functions. 9. Done! As you can see, the whole system is quite complex, making it challenging to provide any specific files due to many dependencies among themselves and with other components. I hope I have explained it comprehensibly. If you have further questions, feel free to ask them in the comments. Watch the video how it works https://streamable.com/r2d0x5
  23. yeah but look within the ultra editor and place it into the scene. its super small. And if you play an animation some wired things happens.
  24. but indeed something with the rigged models are wrong. Eighter no one of us knows how to export that we can use it within ultra or the import itself got problems. Here you have an Model with the same problem. https://1drv.ms/u/s!Aum5gwXzTrOywAa7_gzAjoeWozU1?e=cIvaNX
×
×
  • Create New...