Search the Community
Showing results for tags 'c++'.
-
-
C++ Ultra Beginner's Guide #2 - making and using components
Dreikblack posted a blog entry in Ultra Tutorials
In this tutorial we will make a newcomponent, which will be moving an entity to way points and movement start will be activated by trigger zone Let's start with making WayPoint component: In the Ultra Editor click plus button in Project tab: Now open Visual Studio. Refresh Soution Editor if it was already open to see new component files in Source/Component/Logic folder. Include into project WayPoint.h and WayPoint.cpp. Add WayPoint include to Source/ComponentSystem.h #include "Components/Logic/WayPoint.h" In RegisterComponents() add: RegisterComponent<WayPoint>(); You might do RegisterComponent first and add include with VS help Open WayPoint.json "properties" is a list of component's fields avaible for edit in the Editor "name" - actual name of property that will be used in code later "label" - just a name to display in the Editor "value" - initial value that property will have after map load by default. Can be changed in the Editor for specific entity. "options" - allows to choose int value in Combo Box in the Editor. First option - 0 value Default value here also defines type of this property. New component made via editor have all possible types. Replace WayPoint.json content with: { "component": { "properties": [ { "name": "nextPoint", "label": "Next point", "value": null }, { "name": "doStayOnPoint", "label": "Do stay on point", "value": false } ] } } nextPoint - another WayPoint, where platform will move to once reach this one doStayOnPoint - wait for command before moving to next WayPoint Take a note that the Editor sees only json which could be same for LUA and C++ projects which allows to work on same map even if people have different engine versions (Pro/Standard) or make a level before programming components. Replace WayPoint.h content with: #pragma once #include "UltraEngine.h" using namespace UltraEngine; class WayPoint : public Component { protected: //another WayPoint's entity, where platform will move to once reaching this one //it should be weak pointer for avoiding circular dependency when entities can't be removed from scene because they keep shared pointers to each other std::weak_ptr<Entity> nextPoint; public: //wait for command before moving to next WayPoint bool doStayOnPoint; WayPoint(); //override specifier ensures that the method is virtual and is overriding a virtual function from a base class //it means that this class method will be called even if class instance was casted to base class //it allows to change class behaviour from base one //Start is called when Load() of all components was called already void Start() override; //will be called on map load bool Load(table& t, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) override; //Can be used to save current component state on map save bool Save(table& t, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) override; //get next WayPoint's entity shared_ptr<Entity> getNextPoint() const; //can be used to specify what should and what should not be copied to new class instance shared_ptr<Component> Copy() override; }; Replace WayPoint.cpp content with: #pragma once #include "UltraEngine.h" #include "WayPoint.h" using namespace UltraEngine; WayPoint::WayPoint() { //name should always match class name for correct component work name = "WayPoint"; doStayOnPoint = false; } void WayPoint::Start() { //empty for this case } bool WayPoint::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) { //internally entity saves in the Editor as String unique id //can be empty if this way point is final if (properties["nextPoint"].is_string()) { std::string id = properties["nextPoint"]; nextPoint = scene->GetEntity(id); if (properties["doStayOnPoint"].is_boolean()) { doStayOnPoint = properties["doStayOnPoint"]; } } return true; } bool WayPoint::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) { if (nextPoint.lock()) { properties["nextPoint"] = nextPoint.lock()->GetUuid(); properties["doStayOnPoint"] = doStayOnPoint; } return true; } shared_ptr<Entity> WayPoint::getNextPoint() const { return nextPoint.lock(); } shared_ptr<Component> WayPoint::Copy() { return std::make_shared<WayPoint>(*this); } Let's create our floating object component and call it WayMover WayMover.json: { "component": { "properties": [ { "name": "moveSpeed", "label": "Move Speed", "value": 4.0 }, { "name": "nextPoint", "label": "Next point", "value": null }, { "name": "doDeleteAfterMovement", "label": "Del after move", "value": false } ], "inputs": [ { "name": "DoMove" } ], "outputs": [ { "name": "EndMove" } ] } } moveSpeed - how fast entity will move to way point doDeleteAfterMovement - auto remove entity when it's reach final waypoint. Can be used for door, that goes into walls or floor inputs - it's commands for components, that usually triggered by another components via flowgrough outputs - commands that component sends to other components inputs via FireOutputs("EndMove"); in the component code WayMover.h: #pragma once #include "UltraEngine.h" using namespace UltraEngine; class WayMover : public Component { protected: float moveSpeed; bool isMoving; std::weak_ptr<Entity> nextPoint; bool doDeleteAfterMovement; //need scene pointer to remove entity if doDeleteAfterMovement true //should be weak_ptr to avoid circular dependency std::weak_ptr<Map> sceneWeak; public: WayMover(); std::shared_ptr<Component> Copy() override; bool Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) override; bool Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) override; //CallMethod procceds input signals std::any CallMethod(shared_ptr<Component> caller, const WString& name, const std::vector<std::any>& args) override; void Update() override; //called when reaching next WayPoint void moveEnd(); }; WayMover.cpp: #pragma once #include "UltraEngine.h" #include "WayMover.h" #include "../Logic/WayPoint.h" using namespace UltraEngine; WayMover::WayMover() { name = "WayMover"; moveSpeed = 4.0f; isMoving = false; doDeleteAfterMovement = false; } shared_ptr<Component> WayMover::Copy() { return std::make_shared<WayMover>(*this); } bool WayMover::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) { if (properties["moveSpeed"].is_number()) { moveSpeed = properties["moveSpeed"]; } if (properties["isMoving"].is_boolean()) { isMoving = properties["isMoving"]; } if (properties["doDeleteAfterMovement"].is_boolean()) { doDeleteAfterMovement = properties["doDeleteAfterMovement"]; } if (properties["nextPoint"].is_string()) { std::string id = properties["nextPoint"]; nextPoint = scene->GetEntity(id); } sceneWeak = scene; return Component::Load(properties, binstream, scene, flags, extra); } bool WayMover::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) { properties["moveSpeed"] = moveSpeed; properties["isMoving"] = isMoving; properties["doDeleteAfterMovement"] = doDeleteAfterMovement; if (nextPoint.lock()) { properties["nextPoint"] = nextPoint.lock()->GetUuid(); } return Component::Save(properties, binstream, scene, flags, extra); } std::any WayMover::CallMethod(shared_ptr<Component> caller, const WString& name, const std::vector<std::any>& args) { //start moving by triggering by another component if (name == "DoMove") { isMoving = true; } return false; } void WayMover::Update() { if (!isMoving) { return; } auto entity = GetEntity(); auto wayPoint = nextPoint.lock(); if (!entity || !wayPoint) { return; } //60 HZ game loop, change to own value if different to keep same final speed float speed = moveSpeed / 60.0f; auto targetPosition = wayPoint->GetPosition(true); //moving to point with same speed directly to point no matter which axis auto pos = entity->GetPosition(true); float distanceX = abs(targetPosition.x - pos.x); float distanceY = abs(targetPosition.y - pos.y); float distanceZ = abs(targetPosition.z - pos.z); float biggestDelta = distanceZ; if (distanceX > distanceY && distanceX > distanceZ) { biggestDelta = distanceX; } else if (distanceY > distanceX && distanceY > distanceZ) { biggestDelta = distanceY; } float moveX = MoveTowards(pos.x, targetPosition.x, speed * (distanceX / biggestDelta)); float moveY = MoveTowards(pos.y, targetPosition.y, speed * (distanceY / biggestDelta)); float moveZ = MoveTowards(pos.z, targetPosition.z, speed * (distanceZ / biggestDelta)); entity->SetPosition(moveX, moveY, moveZ); if (entity->GetPosition(true) == targetPosition) { moveEnd(); } } void WayMover::moveEnd() { auto wayPoint = nextPoint.lock(); bool doStay = false; if (wayPoint) { doStay = wayPoint->GetComponent<WayPoint>()->doStayOnPoint; wayPoint = wayPoint->GetComponent<WayPoint>()->getNextPoint(); nextPoint = wayPoint; } if (doStay || !wayPoint) { isMoving = false; FireOutputs("EndMove"); //deleting entity if need to, after reaching final way point if (!doStay && !wayPoint && doDeleteAfterMovement && !sceneWeak.expired()) { auto scene = sceneWeak.lock(); scene->RemoveEntity(GetEntity()); } } } Now we can use just made component in practice. One of things that can be made is door or secret wall activated by player actions and this door will move a little bit inward and then to the side inside of wall. After that invisible now door will be removed. Create a walls with a empty place between them. Create couple of Empty/pivots and attach WayPoints to them. First WayPoint place a same place where door will be, but offset a bit deep into. In Scene tab grab and drag 2nd WayPoint to Nex Point field of 1st WayPoint. Place 2nd WayPoint insde of the wall. Create a door between walls. Attach WayMover component to it. Grab and drag 1st WayPoint to door's WayMover Next Point field. Enable "Del after Move" in WayMover component Create a box before door, make its collision type a trigger: Add Collision Trigger component to it. Open Flowgraph (2nd button at left side of the Editor). Drag and Drop trigger and door to it from Scene tab. In different order, but same result in video format: Result should looks something like that in game: New components and map: NewComponentsTutorFiles.zip On GitHub: https://github.com/Dreikblack/CppTutorialProject/tree/2-making-and-using-components -
0 downloads
Component to move an entity to WayPoints: Move Speed - how much velocity entity will have while moving doDeleteAfterMovement - auto remove entity when it's reach final waypoint. Can be used for door, that goes into walls or floor Input "DoMove" - make entity move to next point Output "EndMove" - happens when entity stops after reaching final way point or if this way poiont has enabled doStayOnPoint -
0 downloads
Simple component which can be used to naviage bots or objects nextPoint - null if it's a final onem otherwise add another entity with this component to make a chain doStayOnPoint - can be checked by object that uses WayPoints to find out if it should stay after reaching this point and wait a command before moving to next one -
C++ Beginner's Guide - first map, Main Menu, Loading Screen and GUI
Dreikblack posted a blog entry in Ultra Tutorials
Prerequisites https://www.ultraengine.com/learn/cppsetup?lang=cpp Install Ultra Engine Pro Check the environment variable ULTRAENGINE (typically "C:\Program Files\Ultra Engine" or if you got Ultra Engine through Steam it will be "C:\Program Files (x86)\Steam\steamapps\common\Ultra Engine Pro"). Install Visual Studio 2022 Community Edition During the installation process, make sure to select the "Desktop development with C++" option Create C++ project with Blank Project template Map Create a simple map call start.ultra with a brush as floor and a Empty aka pivot with FPSPlayer component How to do it: Now in Visual Studio (VS) you can compile and run main.cpp to see how it’s looks in game: You can add skybox in Edit > World Settings (at top buttons in the Editor) by choosing skybox.dds from Materials\Environment\Default Main menu, loading screen and GUI Create a simple menu with couple brushes and camera and name this map menu.ultra main.cpp with a code just for Loading screen almost without anything else: #include "UltraEngine.h" #include "ComponentSystem.h" using namespace UltraEngine; shared_ptr<Window> window; shared_ptr<Framebuffer> framebuffer; //loading screen vars shared_ptr<World> loadingWorld; shared_ptr<Camera> loadingCamera; shared_ptr<Sprite> loadingText; shared_ptr<Sprite> loadingBackground; //for using in main loop shared_ptr<World> currentWorld; shared_ptr<Interface> currentUi; int main(int argc, const char* argv[]) { //Need it to make component from map's entites start working RegisterComponents(); //Computer screens auto displays = GetDisplays(); window = CreateWindow("Ultra Engine Game", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR); framebuffer = CreateFramebuffer(window); //need a dedicated world to be able render and to stop renedering loading screen wheen needed loadingWorld = CreateWorld(); float centerX = float(framebuffer->GetSize().x) * 0.5f; float centerY = float(framebuffer->GetSize().y) * 0.5f; float labelHeight = float(framebuffer->GetSize().y) * 0.2f; loadingBackground = CreateSprite(loadingWorld, framebuffer->size.width, framebuffer->size.height); loadingBackground->SetColor(0.2, 0.2, 0.2); loadingBackground->SetRenderLayers(2); auto font = LoadFont("Fonts/arial.ttf"); loadingText = CreateSprite(loadingWorld, font, "LOADING", labelHeight, TEXT_CENTER | TEXT_MIDDLE); loadingText->SetPosition(centerX, centerY + labelHeight * 0.5f); //0 layer - no render, 1 - default render, we will use 2 for UI and sprites loadingText->SetRenderLayers(2); //Creating camera for sprites, which needs to be orthographic (2D) for UI and sprites if they used as UI loadingCamera = CreateCamera(loadingWorld, PROJECTION_ORTHOGRAPHIC); loadingCamera->SetPosition(centerX, centerY, 0); //camera render layer should match with stuff that you want to be visible for this camera. RenderLayers is a bit mask, so you can combine few layers, but probably you don't need it in most cases loadingCamera->SetRenderLayers(2); currentWorld = loadingWorld; //simple minimum game loop while (window->Closed() == false) { //getting all events from queue - input, UI etc. while (PeekEvent()) { const Event ev = WaitEvent(); //You need to do it for UI in 3D scene if (currentUi) { currentUi->ProcessEvent(ev); } } if (currentWorld) { //Update game logic (positions, components etc.). By default 60 HZ and not depends on framerate if you have 60+ FPS currentWorld->Update(); //2nd param is VSync (true by default), 3rd is fps limit. Can by changed dynamically. currentWorld->Render(framebuffer); } } //if get here by closing window application will ends/closes return 0; } Now if we start a game we will see this loading screen: Before making new classes it's better to do enable "Show All Files" mode to be able see actual folders in the project: And once we have a screen we can create a menu to load in. But let's get prepared to a future a bit and make custom events. We will need it to use Event system with own events. Add CustomEvents.h to Source folder: #pragma once #include "UltraEngine.h" namespace UltraEngine { //load a game map const EventId EVENT_GAME_START = EventId(1001); //load a main menu const EventId EVENT_MAIN_MENU = EventId(1002); } If you created a file manually then do Refresh in Solution Explorer. Remember to include a class files to a project via sub menu called with right click on file in Solution Explorer. Create MainMenu in Source folder. MainMenu.h: #pragma once #include "UltraEngine.h" using namespace UltraEngine; //Object is convenient class for a parent class due As() - casting, and Self() methods class MainMenu : public Object { protected: //constructor - you can think about it as a first method that will be called //protected in this case because we will use create() method for making a new Manu MainMenu(); //shared_ptr is smart pointer that will destroy object automatically once all pointer of this objects are out of scope //in perfect world you need to have maximum only one shared pointer to specific class instance/object as class member, because otherwise object may stay in memory when you don't expect it //for cases when you need pointer without keeping an object you can use std::weak_ptr and it's method .lock() to get a shared_ptr for specific code scope //2D camera for GUI shared_ptr<Camera> uiCamera; //Scene is map entities in first place and you need to keep as long as you need to keep map object shared_ptr<Scene> scene; void init(shared_ptr<Framebuffer> framebuffer); public: static std::shared_ptr<MainMenu> create(shared_ptr<Framebuffer> framebuffer); shared_ptr<Interface> ui; shared_ptr<World> world; }; MainMenu.cpp: #include "UltraEngine.h" #include "MainMenu.h" #include "CustomEvents.h" //default empty constructor MainMenu::MainMenu() = default; //it's better to always use shared_ptr and it's a static dedicated method to make one std::shared_ptr<MainMenu> MainMenu::create(shared_ptr<Framebuffer> framebuffer) { //struct are kinda mini classes, using it in create methods for proper accesses struct Struct : public MainMenu {}; auto instance = std::make_shared<Struct>(); instance->init(framebuffer); return instance; } static bool NewGameButtonCallback(const Event& ev, shared_ptr<Object> extra) { EmitEvent(EVENT_GAME_START, nullptr, 0, 0, 0, 0, 0, nullptr, "start.ultra"); return true; } static bool ExitButtonCallback(const Event& ev, shared_ptr<Object> extra) { //to close application exit(0); return true; } void MainMenu::init(shared_ptr<Framebuffer> framebuffer) { world = CreateWorld(); scene = LoadMap(world, "Maps/menu.ultra"); //Load a font auto font = LoadFont("Fonts/arial.ttf"); //Create user interface auto frameSize = framebuffer->GetSize(); ui = CreateInterface(world, font, frameSize); ui->SetRenderLayers(2); //to make backgrount transparent ui->root->SetColor(0.0f, 0.0f, 0.0f, 0.0f); //Create camera for GUI uiCamera = CreateCamera(world, PROJECTION_ORTHOGRAPHIC); uiCamera->SetPosition(float(frameSize.x) * 0.5f, float(frameSize.y) * 0.5f, 0); uiCamera->SetRenderLayers(2); //for correct rendering above 3D scene uiCamera->SetClearMode(CLEAR_DEPTH); //Menu buttons auto newGameButton = CreateButton("New game", frameSize.width / 2 - 100, 125, 200, 50, ui->root); ListenEvent(EVENT_WIDGETACTION, newGameButton, NewGameButtonCallback); auto exitButton = CreateButton("Exit", frameSize.width / 2 - 100, 200, 200, 50, ui->root); ListenEvent(EVENT_WIDGETACTION, exitButton, ExitButtonCallback); } Let's get back to main.cpp. We have files to include: #include "MainMenu.h" #include "CustomEvents.h" Also we need menu as global var: shared_ptr<MainMenu> menu; Add those fuctions above main one, we will need them for event system: static bool StartGameEventCallback(const Event& e, shared_ptr<Object> extra) { //nothing just for now return true; } static bool MainMenuEventCallback(const Event& e, shared_ptr<Object> extra) { menu = MainMenu::create(framebuffer); //switching current render and update targets for loop currentWorld = menu->world; currentUi = menu->ui; return true; } Put this above main loop: //to show Loading screen before Main Menu loadingWorld->Render(framebuffer); //ListenEvent are needed to do something in callback function when specific even from specfic source (or not, if 2nd param is nullptr) emitted ListenEvent(EVENT_GAME_START, nullptr, StartGameEventCallback); ListenEvent(EVENT_MAIN_MENU, nullptr, MainMenuEventCallback); //let's try it out! EmitEvent(EVENT_MAIN_MENU); Just in case if something one wrong, current main.cpp file main.cpp It's time to to take a look on our fresh menu! If you did not blink you could also see a Loading screen, at lest in debug mode. Unfortunately white screen before Loading screen can be noticed way easier and i don't know yet a way to fix it. We need to modify FPSPlayer component just a little bit. Add to its header FPSPlayer.h bool doResetMousePosition = true; And new condition for mouse position reset in FPSPlayer.cpp in Update() if (doResetMousePosition) { window->SetMousePosition(cx, cy); } One big thing left - a game to play. Add Game class to project: Game.h: #pragma once #include "UltraEngine.h" #include "Components/Player/FPSPlayer.h" using namespace UltraEngine; class Game : public Object { protected: shared_ptr<Camera> uiCamera; shared_ptr<Scene> scene; shared_ptr<FPSPlayer> player; shared_ptr<Widget> menuPanel; Game(); void init(shared_ptr<Framebuffer> framebuffer, WString mapPath); public: //to show/hide game menu on Esc static bool GameMenuButtonCallback(const Event& ev, shared_ptr<Object> extra); static std::shared_ptr<Game> create(shared_ptr<Framebuffer> framebuffer, WString mapPath); shared_ptr<Interface> ui; shared_ptr<World> world; }; Game.cpp: #include "UltraEngine.h" #include "Game.h" #include "CustomEvents.h" Game::Game() = default; std::shared_ptr<Game> Game::create(shared_ptr<Framebuffer> framebuffer, WString mapPath) { struct Struct : public Game {}; auto instance = std::make_shared<Struct>(); instance->init(framebuffer, mapPath); return instance; } bool Game::GameMenuButtonCallback(const Event& ev, shared_ptr<Object> extra) { if (KEY_ESCAPE == ev.data && extra) { auto game = extra->As<Game>(); bool isHidden = game->menuPanel->GetHidden(); game->menuPanel->SetHidden(!isHidden); //we can get a game window anywhere, but take in mind that it will return nullptr, if window is not active ;) auto window = ActiveWindow(); //checking just in case if we actually got a window if (window) { //hiding cursor when hiding a menu and vice versa window->SetCursor(isHidden ? CURSOR_DEFAULT : CURSOR_NONE); } if (game->player) {//to stop cursor reset to center when menu on game->player->doResetMousePosition = !isHidden; } } //If the callback function returns false no more callbacks will be executed and no event will be added to the event queue. return false; } static bool MainMenuButtonCallback(const Event& ev, shared_ptr<Object> extra) { EmitEvent(EVENT_MAIN_MENU); return true; } static bool ExitButtonCallback(const Event& ev, shared_ptr<Object> extra) { exit(0); return true; } void Game::init(shared_ptr<Framebuffer> framebuffer, WString mapPath) { world = CreateWorld(); scene = LoadMap(world, mapPath); for (auto const& entity : scene->entities) { auto foundPlayer = entity->GetComponent<FPSPlayer>(); if (foundPlayer) { player = foundPlayer; break; } } auto font = LoadFont("Fonts/arial.ttf"); //Create user interface for game menu auto frameSize = framebuffer->GetSize(); ui = CreateInterface(world, font, frameSize); ui->SetRenderLayers(2); ui->root->SetColor(0.0f, 0.0f, 0.0f, 0.0f); uiCamera = CreateCamera(world, PROJECTION_ORTHOGRAPHIC); uiCamera->SetPosition(float(frameSize.x) * 0.5f, float(frameSize.y) * 0.5f, 0); uiCamera->SetRenderLayers(2); uiCamera->SetClearMode(CLEAR_DEPTH); //widgets are stays without extra shared pointers because parent widet, ui->root in this case, keep them //to remove widget you should do widget->SetParent(nullptr) menuPanel = CreatePanel(frameSize.width / 2 - 150, frameSize.height / 2 - 125 / 2, 300, 250, ui->root); auto menuButton = CreateButton("Main menu", 50, 50, 200, 50, menuPanel); ListenEvent(EVENT_WIDGETACTION, menuButton, MainMenuButtonCallback); auto exitButton = CreateButton("Exit", 50, 150, 200, 50, menuPanel); ListenEvent(EVENT_WIDGETACTION, exitButton, ExitButtonCallback, nullptr); //we don't need game menu on screen while playing menuPanel->SetHidden(true); //and we will need it once hitting Esc button ListenEvent(EVENT_KEYUP, nullptr, GameMenuButtonCallback, Self()); //take in mind that extra param will be kept as shared_ptr in callback ^ } Add new Game include and global var to main.cpp: #include "Game.h" shared_ptr<Game> game; Update function callbacks: static bool StartGameEventCallback(const Event& e, shared_ptr<Object> extra) { //destroying a main menu menu.reset(); //to show loading screen loadingWorld->Render(framebuffer); game = Game::create(framebuffer, "Maps/" + e.text); //switching current render and update targets for loop currentWorld = game->world; currentUi = game->ui; //to avoid crash when engine will try to proccess prev ui, catching in main loop throw std::invalid_argument("Game"); return true; } bool MainMenuEventCallback(const Event& e, shared_ptr<Object> extra) { //destroying a game instance if one existed game.reset(); //to show loading screen loadingWorld->Render(framebuffer); menu = MainMenu::create(framebuffer); //switching current render and update targets for loop currentWorld = menu->world; currentUi = menu->ui; throw std::invalid_argument("Main menu"); return true; } Find EmitEvent(EVENT_MAIN_MENU) and wrap in try-catch: try { EmitEvent(EVENT_MAIN_MENU); } catch (const std::invalid_argument& e) { //do nothing } Same for ui processing: try { if (currentUi) { currentUi->ProcessEvent(ev); } } catch (const std::invalid_argument& e) { //Stop processing old ui } And in the end we have a game with own menu: All created and modified classes: FPS game with menu.zip Maps: Maps.zip Repository: https://github.com/Dreikblack/CppTutorialProject/tree/menu-loading-screen-and-gui -
This component generates a Mouse Movement Controller featuring a free-look mode. Typically, such character controllers find application in ARPG (Action Role-Playing Game) or RTS (Real-Time Strategy) games. In this setup, you observe the character from a top-down perspective and direct their movement to a specified position by clicking the left mouse button. The Component itself need the terrain name and the name for the walk animation. Also your character needs an idle animation. You can switch into the "free look mode" by pressing the tab key. Within the "free look mode" you are able to move the camera with "W" "A" "S" "D" free around. If you press tab again you switch the mode back to the character. 1. C++ Header #pragma once #include "UltraEngine.h" #include "GatherWaterTask.h" using namespace UltraEngine; class MouseMovementController : public Component { private: shared_ptr<Map> scene; shared_ptr<NavMesh> nav_mesh; bool freeLook = false; public: string terrainName; string animationName; Vec3 destination; shared_ptr<NavAgent> agent; shared_ptr<Terrain> terrain; shared_ptr<Camera> camera; MouseMovementController(); void Start(); void Update(); bool Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags); bool Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags); shared_ptr<Component> Copy(); }; 2. C++ Source File #include "UltraEngine.h" #include "MouseMovementController.h" MouseMovementController::MouseMovementController() { this->name = "MouseMovementController"; } /// <summary> /// Setup the Controller /// </summary> void MouseMovementController::Start() { auto entity = GetEntity(); ///Create the camera and attach them to the player character this->camera = CreateCamera(entity->GetWorld()); Vec3 currentcameraposition = TransformPoint(0, 15, 5, entity, nullptr); //this->camera->SetParent(entity); this->camera->SetPosition(currentcameraposition, true); this->camera->SetRotation(Vec3(45, 0, 0)); this->camera->SetFov(40); this->camera->Point(entity); ///Creates a navmesh ToDo: Has to changed when the NavMesh bug is fixed this->nav_mesh = CreateNavMesh(entity->GetWorld(), 5, 8, 8); this->nav_mesh->SetPosition(0, 0, 0); this->nav_mesh->Build(); ///Create a new NavAgent for the player character this->agent = CreateNavAgent(this->nav_mesh); entity->Attach(agent); } //Update method, called once per loop void MouseMovementController::Update() { auto entity = GetEntity(); auto window = ActiveWindow(); auto world = entity->GetWorld(); if (window) { ///Set the destination if the player hits the left mouse button if (window->MouseHit(MOUSE_LEFT)) { auto mousePosition = window->GetMousePosition(); auto pick = this->camera->Pick(window->GetFramebuffer(), mousePosition.x, mousePosition.y, 0, true); if (pick.success) { auto pos = pick.position; if (pick.entity->name == this->terrainName) { this->destination = pos; } } } ///Move to the destination if the destination is not null if (this->destination != NULL) { if (entity->position.DistanceToPoint(this->destination) > 1.0f) { this->agent->Navigate(this->destination); auto model = entity->As<Model>(); model->Animate(this->animationName); } else { this->agent->Stop(); auto model = entity->As<Model>(); model->Animate("idle"); this->destination == NULL; } } ///Toggle the freelook mode if (window->KeyHit(KEY_TAB)) { if (this->freeLook) { this->freeLook = false; } else { this->freeLook = true; Vec3 currentcameraposition = TransformPoint(0, 15, 0, GetEntity(), nullptr); this->camera->SetPosition(currentcameraposition, true); this->camera->SetRotation(Vec3(90, 0, 0)); } } ///Position the camera according to the selected mode if (!this->freeLook) { Vec3 currentcameraposition = TransformPoint(0, 15, 5, GetEntity(), nullptr); this->camera->SetPosition(currentcameraposition, true); this->camera->Point(entity); } else { Vec3 camPos = this->camera->GetPosition(); if (window->KeyDown(KEY_W)) { camPos.z += 0.5f; } else if (window->KeyDown(KEY_S)) { camPos.z -= 0.5f; } else if (window->KeyDown(KEY_A)) { camPos.x -= 0.5f; } else if (window->KeyDown(KEY_D)) { camPos.x += 0.5f; } this->camera->SetPosition(camPos); } } } bool MouseMovementController::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags) { this->scene = scene; this->terrainName = (String)properties["terrainName"]; this->animationName = (String)properties["animationName"]; for (auto e : scene->entities) { if (e->name == terrainName) { this->terrain = std::static_pointer_cast<Terrain>(e); } } return true; } bool MouseMovementController::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags) { properties["terrainName"] = (String)terrain->name; properties["animationName"] = (String)this->animationName; return true; } shared_ptr<Component> MouseMovementController::Copy() { return std::make_shared<MouseMovementController>(*this); } 3. JSON File { "component": { "properties": [ { "name": "terrainName", "label": "Terrain Name", "value": "" }, { "name": "animationName", "label": "Animation Name", "value": "" } ] } }
-
I've been spending the past few days trying to figure out how to add SDL2 to Leadwerks, as it has an additional Lua binding that would be nice to have. Not to mention the gamepad support (which is the main reason because just adding XInput only would give support for XBox 360 and Dualshock 3 controllers). Not having any experience with an engine like Leadwerks, application building, or libraries has severely impacted my ability to figure out what to do in my current situation. I know I need to include the library and expose it to Lua, but I don't know how to do that, and I don't know how to actually add it to the engine either. I've taken a look at some resources online, such as lazyfoo, and it hasn't really helped me understand. Any help or other resources that could be passed along to me would be greatly appreciated. Thanks!
- 4 replies
-
- programing
- lua
-
(and 2 more)
Tagged with:
-
Version 1.0.0
13 downloads
This small component makes it possible to play and stop animations for models. Various settings are available: AutoPlay: Actually self-explanatory. If this option is activated, the animation is played directly at startup. Loop: If activated, the animation is played in a loop. Name: The name of the animation. Speed: The speed of the animation. Bleed (255): Animation bleeding. In addition, there are two inputs for the Nodegraph: "PlayAnimation" and "StopAnimation". These functions can also be called via C++. -
Wanted to get a start on these after today's discussion. Here is the first draft of the logic components. I'm kind of limited on what I can do without any argument support. I've made the following: Auto - Used to start things on Start or Level Load. (Inspired by logic_auto) Relay - Used as a switch or a delay between connections. You can toggle the component to cut or rejoin connections. (Inspired by logic_relay) RelayTimer - Fires outputs on every interval. (Inspired by logic_timer) PrintMessage - Simply prints whatever to console. This is only my first draft to continue the discussion! Logic.zip
-
This component will copy the rotation of another entity, with adjustable smoothing. MatchRotation.zip
-
Hello I have just got Ultra Engine and have not got it to compile yet. I added missing ATL modules inside visual studio installer, made a new c++ project multiple times and set the optimization to disabled in the project settings. Need help at fixing these errors. The Project is called StereoWindow and i have the exact same problem with another project.
-
I am working my way through some of the sample code and making slight modifications. I am stuck with a very simple problem. I cannot manage to get a model to successfully render a texture. I am using slightly modified code from the leadwerks docs and a stock material. Any insight to my challenged code/thought process is appreciated. #include "App.h" using namespace Leadwerks; App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {} App::~App() { delete world; delete window; } bool App::Start() { window = Window::Create(); context = Context::Create(window); world = World::Create(); camera = Camera::Create(); camera->SetRotation(5, 0, 0); camera->Move(0, 0, -6); Light* light = DirectionalLight::Create(); light->SetRotation(35, 35, 0); Model* model = NULL; model = Model::Create(); model->SetColor(1.0, 0.0, 1.0); model->SetPosition(0, 0, 0); material = Material::Load("Materials/Concrete/concrete_dirty.mat"); Surface* surface = model->AddSurface(); surface->AddVertex(-0.5, -0.5, 0, 0, 0, -1); surface->AddVertex(0.5, -0.5, 0, 0, 0, -1); surface->AddVertex(0.5, 0.5, 0, 0, 0, -1); surface->AddVertex(-0.5, 0.5, 0, 0, 0, -1); surface->AddTriangle(2, 1, 0); surface->AddTriangle(0, 3, 2); surface->UpdateAABB(); model->SetMaterial(material,1); model->UpdateAABB(Entity::LocalAABB | Entity::GlobalAABB); return true; } bool App::Loop() { if (window->Closed() || window->KeyDown(Key::Escape)) return false; Time::Update(); world->Update(); world->Render(); context->Sync(); return true; }
-
-
Over the past 2 months or so, I was given an assignment to create a program as a part of a college assignment, I chose to use this API As it was recently recommend to me by a friend, I went with an app to help people play dungeons and dragons, as someone who has been playing it for a while I constantly found myself forgetting things or misplaying for various reasons. Included in the app so far, is a character tab that keeps track of everything needed by a player , a pop out dice roller, a spell search engine that can pop out and display more information about a given spell I am currently working on a way to incorporate stat blocks to allow for a greater utility to the user. Would love to hear some feedback.
-
Since this was a hot topic recently, I've written code to preload model files from a map and display a progress bar. Then, when you load the map, it should load much faster since you already "preloaded" the models and the ones in the map will be instances (copies) of existing ones. This is an alternative to Map::Load's hook, which may be a better way to go about this. #include "Leadwerks.h" using namespace Leadwerks; std::string mapfile = "maps/map.map"; string model_file[100]; // Stores list of model files in map Model *preload_model[100]; // Array to preload model files to int modelcount=0; // Counts total number of models found in map file int main(int argc, const char *argv[]) { Leadwerks::Window* window = Leadwerks::Window::Create(); Context* context = Context::Create(window); // Load map as just a file Stream* stream = FileSystem::ReadFile(mapfile); if(stream==NULL) { cout << "Could not open map file to read." << endl; exit(1); } // Put MDL paths/files in a list and get count while(!stream->EOF()) { string line = stream->ReadLine(); // If what we read is a model line (ends with .mdl) if(line.size()>3 && line.compare(line.size() - 4, 4, ".mdl") == 0) model_file[modelcount++]=line; } stream->Release(); cout << "Number of model files in map: " << modelcount << endl; for(int i=0; i<modelcount; i++) { cout << "Preloading file #" << i << ":" << model_file[i] << endl << endl; preload_model[i]=NULL; preload_model[i]=Model::Load(model_file[i]); // You can check if model was properly loaded here (should not be NULL) preload_model[i]->Hide(); // Preload model should not be seen // Draw progress bar context->SetColor(0.0, 0.0, 0.0); context->Clear(); context->SetColor(1.0, 0.0, 0.0); float barwidth = (float)(context->GetWidth())-20.0; barwidth *= (float)(i+1); barwidth /= (float)modelcount; context->DrawRect(10, 10, (int)barwidth, 20); // Remove the below delay line. It's put in just to show progress bar effect Sleep(500); context->Sync(); } // You can then load the map here and it should be much faster // since you already loaded the models in it above // Map::Load(mapfile); // Game code here return 0; }
-
I'm working on a little project where I need to make some simple image processing such as rotation, brightness, contrast and so on. FreeImage is a nice lib for those things and I noticed that it's already included in the FITextueLoader Plugin so that's nice. Is there a way to access the pixel-data in Pixmap so it an be used with the FreeImage functions? Actually something like this: FIBITMAP* Pixmap2FreeImage( std::shared_ptr<Pixmap> pixmap ) { FIBITMAP* bmp = //??? pixmap->???? // ???? return bmp; } std::shared_ptr<UltraEngine::Pixmap> FreeImage2Pixmap(FIBITMAP* source) { std::shared_ptr<UltraEngine::Pixmap> pixmap = std::make_shared<UltraEngine::Pixmap>( ); // ??? return pixmap; }
- 1 reply
-
- c++
- ultra app kit
-
(and 1 more)
Tagged with:
-
My program is working fine in Windows when I use '\n' in a c++ string to get a newline in the text of a label. I have now compiled it on Linux and the newline character is displayed as a character block as if the character cannot be displayed. Is there a way to get Linux to execute the same way as Windows. Here is sample code (a slightly modified version of the label sample code)? #include "UltraEngine.h" using namespace UltraEngine; int main(int argc, const char* argv[]) { //Get the displays auto displays = GetDisplays(); //Create a window auto window = CreateWindow("Ultra Engine", 0, 0, 800, 600, displays[0]); //Create User Interface auto ui = CreateInterface(window); //Create widget auto label1 = CreateLabel("Label", 20, 20, 120, 30, ui->root); auto label2 = CreateLabel("Border\nLabel", 20, 50, 120, 60, ui->root, LABEL_BORDER | LABEL_CENTER | LABEL_MIDDLE); while (window->Closed() == false) { WaitEvent(); } return 0; }
-
Hi, I have encountered a problem using threads with the help of classes. I have been using sample codes so far as well as browsing the forum, but unfortunately I cannot find an answer to this problem. Following the guide about threads I got something like this Unfortunately I don't understand why a function of void type not belonging to a class passes in the compilation without any problem, but in the other direction it doesn't. I apologize for bothering you with probably simple problems and for my poor English Thanks
-
Built to power a new generation of game development tools, Ultra App Kit provides an easy-to-use C++ programming SDK for creating desktop GUI applications. Unlike other alternatives like Dear ImGui, the Ultra App Kit GUI renders in retained mode rather than immediate mode, and is specifically designed for desktop GUI applications. This makes applications snappy and responsive when resizing or refreshing a window. DPI scaling is baked into the design for resolution-independent graphics on any screen. The GUI can be combined with an embedded OpenGL viewport, or combined with a 3D game engine to make custom editors and game development tools. Check out the video tutorials and read the documentation to learn more. Ultra App Kit can be purchased in our store. API Design C++ shared pointers everywhere Extensible widgets system Extensive documentation with examples for each command UI Features Resolution independent for any DPI scale Load SVG vector images Set widget icons Change mouse cursor Custom color schemes stored in JSON files Supported Widgets Label Button (push, checkbox, radio, and toggle styles) ProgressBar TextField TextArea ComboBox ListBox Slider (scrollbar, trackbar, and stepper styles) Draggable multi-select TreeView Create your own custom widgets Additional Features File I/O File system watcher Memory allocation and management Image loading, saving, processing Package system for loading files from compressed / encrypted archives Plugin system Thread management String manipulation (split, search, conversion, etc.) Message boxes and file / folder requester
-
This will create a "spinner" widget like in 3ds max. You can use SetRange() to control the min and max values. Download: Spinner.h Example: #include "UltraEngine.h" #include "Spinner.h" using namespace UltraEngine; int main(int argc, const char* argv[]) { //Get displays auto displays = GetDisplays(); //Create window auto mainwindow = CreateWindow("Spinner", 0, 0, 800, 600, displays[0]); //Create user interface auto ui = CreateInterface(mainwindow); iVec2 sz = ui->root->ClientSize(); //Create spinners auto ispinner = CreateSpinner(20, 20, 100, 30, ui->root, SPINNER_INTEGER); auto fspinner = CreateSpinner(20, 60, 100, 30, ui->root, SPINNER_FLOAT); auto nspinner = CreateSpinner(20, 100, 100, 30, ui->root, SPINNER_FLOAT | SPINNER_NORMALIZE); //Show the window mainwindow->Show(); mainwindow->Activate(); //Easier to work with: ui->SetScale(2); while (true) { const Event event = WaitEvent(); switch (event.id) { case EVENT_WIDGETACTION: if (event.source == ispinner) Print(event.data); if (event.source == fspinner or event.source == nspinner) { Print(event.source->As<Widget>()->GetProgress()); } break; case EVENT_WINDOWCLOSE: if (event.source == mainwindow) return 0; break; } } return 0; }
-
I worked the half day on that problem and I think I give the information to everyone who also has this problem with fog. In the Editor fog looks a little bit like fog. Without implementig fog in C++ source, no fog is visible in C++. Loading a map after craeting a camera only a touch of a color is visible in the very far, and fog is not working as it should. I mean with map not a terrain, I loaded a map with only a plattform and some prefabs. I have not tested with terrain. So load a map before creating a camera. Nothing works correct if you not do that.
-
First step C++ First Player game start
Bytecroc posted a blog entry in [C++] First Player game start
Since 3 years I have not programmed anything with Leadwerks, that has nothing to do with Leadwerks it was a real life thing. So I must learn the stuff from the beginning. My natural language is not English, I hope you can forgive me a mistake or two . If you want test the code you make a new project and a map. I made some test objects to see if climbing fails or not and under which steps I can crouch and on which step I can jump. I make this blog for me to save the source code. Two times in my life I lost nearly all my source I programmed by HardDrive crashes. So it is a nice thing to have that online. And for beginnes to have a start code. In your project folder, by me in "documents/Leadwerks/Projects/.... <-- here are my Projects" you have a subfolder also named projects: "documents/Leadwerks/Projects/ProjectName/projects/" by me in the folder windows is the VisualStudio 17 project: "ProjectName.sln" << Start this with Doubleclick, but first install VisualStudio. I delete all the stuff inside main.cpp, App.cpp and app.h and put the following code in main.cpp change your mapname > "YouMapName.map" from your project and compile. I can not say whats the menu names in english because I have the german GUI. For VisualStudio tutorials look in the Web or YouTube, I am sure there are dozens of them. #include "Leadwerks.h" #include <string> using namespace Leadwerks; int screenwidth = GetSystemMetrics(0); int screenheight = GetSystemMetrics(1); Window* window; Context* context; World* world; Model* thePlayer; Camera* theCam; // declare functions that we need, they are used once so I made them inline for faster code. inline void Start(); inline void loop(); inline void DrawInfo(); //crouch, crawl, jog, run // used for mouse handling Vec3 mausdata = Vec3(0, 0, 0); float mausx = 0.0; float mausy = 0.0; // used for player movement Vec3 mdat = Vec3(0, 0, 0); Vec3 ppos = Vec3(0, 0, 0); Vec3 velo = Vec3(0, 0, 0); float walk = 0.0; float strafe = 0.0; float jump = 0.0; float crouch = 0.0; float strafespeed = 2.0; float movespeed = 2.4; float jumpspeed = 5.0; bool cdown = 0; bool sdown = 0; // used for mainloop bool mainloop = true; // mainfunction int main(int argc,const char *argv[]) { // hold the mainfuntion small and insert what we need with an inline function. Start(); // mainloop while (mainloop) { loop(); // Here is the most mailoop stuff if (window->Closed() || window->KeyDown(Key::Escape)) mainloop = false; //Quit if ESC is pressed Leadwerks::Time::Update(); world->Update(); world->Render(); // For holding the mainloop small put all drawing stuff inside an inline function. DrawInfo(); context->Sync(true); } return 0; } // Inline functions we use only once. inline void DrawInfo() { // For the first, all the drawing stuff in this funtion. context->SetBlendMode(Blend::Alpha); context->SetColor(1.0, 1.0, 1.0); context->DrawText("Camera Rotation: " + theCam->GetRotation().ToString(), 2, 2); context->DrawText("Player Position: " + thePlayer->GetPosition().ToString(), 2, 40); context->DrawText("Player Position: " + std::to_string(ppos[0]), 2, 70); context->DrawText("Player Position: " + std::to_string(ppos[1]), 2, 100); context->DrawText("Player Position: " + std::to_string(ppos[2]), 2, 130); context->DrawText("UPS: " + String(Time::UPS()), 2, 160); context->DrawText("Velocity: " + thePlayer->GetVelocity().ToString(), 2, 190); // a way to check if the player is not on ground, for possibly later use. if (thePlayer->GetAirborne()) { context->DrawText("In the air", 2, 220); } context->SetBlendMode(Blend::Solid); } inline void Start() { // Window //window = Window::Create("",0,0, 2560, 1440, Window::Fullscreen); //window = Window::Create("", 0, 0, screenwidth, screenheight, Window::Fullscreen); window = Window::Create("", 0, 0, screenwidth, screenheight); context = Context::Create(window); window->Maximize(); // The world world = World::Create(); // The player that we need for "First Player Sight" thePlayer = Model::Create(); thePlayer->SetPhysicsMode(Entity::CharacterPhysics); thePlayer->SetPosition(0, 5, 0); thePlayer->SetMass(1); // Put the camera as a child to the player, so we can move and look up and down separately theCam = Camera::Create(thePlayer); theCam->SetPosition(0,1.65,0); // Font we use for drawing operations Font* font = Font::Load("Fonts/Arial.ttf", 12); context->SetFont(font); // Map //Map::Load("Maps/TheWorld.map"); Map::Load("Maps/BetonMap.map"); // <<<<----- put here your mapname ............................................. } inline void loop() { ppos = thePlayer->GetPosition(); mausdata = window->GetMousePosition(); mausx += (mausdata[0] - window->GetWidth() / 2) * 0.1; mausy += (mausdata[1] - window->GetHeight() / 2) * 0.1; window->SetMousePosition(window->GetWidth() / 2, window->GetHeight() / 2); // not Shift: walk if (!window->KeyDown(Key::Shift) && sdown == 1) { sdown = 0; movespeed = 2.4; } //Shift: run if (window->KeyDown(Key::Shift) && sdown == 0) { sdown = 1; movespeed = 5.0; } // not CTRL: stay if (!window->KeyDown(Key::ControlKey) && cdown == 1) { cdown = 0; movespeed = 3.0; ppos = thePlayer->GetPosition(); theCam->SetPosition(0,1.65, 0); crouch = 0; } // CTRL: crouch if (window->KeyDown(Key::ControlKey) && cdown == 0) { cdown = 1; movespeed = 1.0; crouch = 1; ppos = thePlayer->GetPosition(); theCam->SetPosition(0,0.95,0); } // Walk with movespeed when press Key W walk = (window->KeyDown(Key::W) - window->KeyDown(Key::S)) * movespeed; strafe = (window->KeyDown(Key::D) - window->KeyDown(Key::A)) * strafespeed; // check if player is on ground, this means velo[1] = "y direction" has no velocity = 0 // in this way the player kann not jump if he is already in the air. velo = thePlayer->GetVelocity(); if (velo[1] == 0.0) { jump = window->KeyHit(Key::Space) * jumpspeed; } else { jump = window->KeyHit(Key::Space) * 0.0; } if (mausy > 50.0) mausy = 50.0; if (mausy < -90.0 ) mausy = -90.0; thePlayer->SetInput(mausx, walk, strafe, jump, crouch); //void SetInput(float angle, float move, float strafe = 0, float jump = 0, const bool crouch = false, const float maxaccel = 1, const float maxdecel = 0.5, const bool detailed = false, const float maxrotationspeed = 5.0) theCam->SetRotation(mausy, 0, 0); mdat = theCam->GetRotation(); } The next step I will made is to put the code into a class. Use the C Key for toggle crouching and use the CTRL for faster runnig, the I have walk with Key W, jogging with Shift+W and running with CTRL and W. -
Leadwerks C++ Noob here... So I'm programmatically creating a bunch of floor tiles out of PolyMesh for my player to walk around on, and it works great except for one strange behavior. When I call Translate() on my tile Model class it slowly erodes/squishes the PolyMesh data until it is gone completely, and then my player just falls through. If I don't translate, no problem. I looked into the docs a bit and I see that PolyMesh isn't recommended for moving objects. However when I try the same with ConvexHull() I am getting no collision data at all. My tiles are basically small flat planes created from 2 triangles to make a rectangle. On create... /* * Generate a cTile * ofs of entire tile position from center * dim is width and length|depth */ Model* make_ctile(Vec3 ofs, Vec3 dim) { Model* cmodel = Model::Create(); cmodel->SetColor(1., 1., 1.); Surface* csurface = cmodel->AddSurface();//Surface::Create();// Vec3 dn = Vec3(0, 1, 0); // up csurface->AddVertex(-dim.x, 0, -dim.y, dn.x, dn.y, dn.z); // bl csurface->AddVertex(dim.x, 0, -dim.y, dn.x, dn.y, dn.z); // br csurface->AddVertex(dim.x, 0, dim.y, dn.x, dn.y, dn.z); // tr csurface->AddVertex(-dim.x, 0, dim.y, dn.x, dn.y, dn.z); // tl // CW csurface->AddTriangle(2, 1, 0); // tr, br, bl csurface->AddTriangle(0, 3, 2); // bl, tl, tr csurface->Update(); csurface->UpdateNormals(); ; Shape* cshape = Shape::PolyMesh(csurface); cmodel->SetShape(cshape); cshape->Release(); cmodel->SetPhysicsMode(Entity::RigidBodyPhysics); cmodel->SetCollisionType(Collision::Prop); ; cmodel->Translate(ofs); return cmodel; } On update... for (int i = 0; i < tilex; i++) { // Pull tiles tiles[i].tile->Translate(0, 0, tile_speed); } Included is a view of the collision working when there is no translation, and also a view of the collision failing after translation has started. I also included a screenshot of the output when I use ConvexHull to generate the shape, which seems to not have any collision data whatsoever. Shape* cshape = Shape::ConvexHull(csurface);// PolyMesh(csurface); I'm not really sure what to try next.
-
Hello community, long time no see. I am working on my own graphical user interface, for my super duper rpg game :). The use separate textures for each button state, etc. I consider it not effective! It is better to load the texture with the atlas of the whole GUI once. And use her. In order to draw a texture from the atlas, we need to slightly modify the standard shader (drawimage), and save it under a different name (drawimagerect). Shader #version 400 uniform vec4 drawcolor; uniform sampler2D texture0; uniform vec4 rect; in vec2 vTexCoords0; out vec4 fragData0; void main(void) { ivec2 ts = textureSize(texture0, 0); vec4 coord = vec4(rect.x / ts.x, rect.y / ts.y, rect.z / ts.x, rect.w / ts.y); vec2 uv = coord.xy + (vTexCoords0 * coord.zw); fragData0 = drawcolor * texture(texture0,uv); } Now we can draw a texture from the atlas: void drawImageRect(Texture* texture, float x, float y, Vec4 rect) { Context* context = Context::GetCurrent(); context->SetShader(Shader::Load("Shaders/Drawing/drawimagerect.shader")); context->GetShader()->SetVec4("rect", rect); context->DrawImage(texture, x, y, rect.z, rect.w); context->SetShader(NULL); } void drawImageRect(Texture* texture, float x, float y, float width, float height, Vec4 rect) { Context* context = Context::GetCurrent(); context->SetShader(Shader::Load("Shaders/Drawing/drawimagerect.shader")); context->GetShader()->SetVec4("rect", rect); context->DrawImage(texture, x, y, width, height); context->SetShader(NULL); } Animation To play the animation we need to get the coordinates of all the frames in the atlas. Naturally, we won't do this manually, will write a small algorithm. bool isEmptyFrame(char* pixels, int textureWidth, iVec2 position, iVec2 frameSize, const bool alpha=true, const iVec3 colorBg=iVec3()) { unsigned char r, g, b, a; int level = 0; for (int y = position.y; y < position.y + frameSize.y; y++) { for (int x = position.x; x < position.x + frameSize.x; x++) { int p = (y * textureWidth + x) * 4; memcpy(&r, pixels + p + 0, 1); memcpy(&g, pixels + p + 1, 1); memcpy(&b, pixels + p + 2, 1); memcpy(&a, pixels + p + 3, 1); if (!alpha) { if ((int)r == colorBg.r && (int)g == colorBg.g && (int)b == colorBg.b) { level++; } else { return false; } } else { if ((int)a == 0) { level++; } else { return false; } } } } float percent = (float)((float)level / (float)(frameSize.x * frameSize.y)) * 100.0f; if (percent >= 100.0f) { return true; } return false; } void getFrames(Texture* texture, iVec2 framesize, std::vector<Vec4> &frames) { if (texture == nullptr) return; int horizontLine = texture->GetWidth() / framesize.x; int verticalLine = texture->GetHeight() / framesize.y; int frameCount = horizontLine * verticalLine; int currentHorizont = 0; int currentVertical = 0; iVec2 framePosition = iVec2(); iVec2 frameSize = framesize; char* pixels = (char*)malloc(texture->GetMipmapSize(0) * 8); texture->GetPixels(pixels); System::Print((std::string)"Get frames from texture atlas \"" + texture->GetPath() + "\"..."); // Push first frame int skipCount = 0; if (!isEmptyFrame(pixels, texture->GetWidth(), framePosition, frameSize)) { frames.push_back(Vec4((float)framePosition.x, (float)framePosition.y, (float)frameSize.x, (float)frameSize.y)); } else { skipCount++; System::Print((std::string)"Frame #0" + " is empty. (skip)"); } for (int i = 1; i < frameCount; i++) { if (currentHorizont < horizontLine - 1) { currentHorizont++; framePosition.x = frameSize.x * currentHorizont; } else { if (currentVertical < verticalLine - 1) { currentVertical++; framePosition.x = 0; framePosition.y = frameSize.y * currentVertical; currentHorizont = 0; } } if (!isEmptyFrame(pixels, texture->GetWidth(), framePosition, frameSize)) { frames.push_back(Vec4((float)framePosition.x, (float)framePosition.y, (float)frameSize.x, (float)frameSize.y)); } else { skipCount++; System::Print((std::string)"Frame #" + std::to_string(i) + " is empty. (skip)"); } } System::Print((std::string)"Frame count: " + std::to_string(frames.size()) + ", skip: " + std::to_string(skipCount)); free(pixels); } Now that we have all the frames, we can play the animation. Texture* atlas = Texture::Load("Textures/atlas.tex"); std::vector<Vec4> frames; getFrames(atlas, iVec2(96, 96), frames); float frame = 0.0f; float frameend = frames.size(); float framebegin = 0.0f; float speed = 0.1f; //Loop frame += Time::GetSpeed() * speed; frame = fmodf(frame, frameend - framebegin) + framebegin; //Draw drawImageRect(atlas, 25.0f, 25.0f, frames[(int)frame]); Updates: [04.04.2020] [+] Added check for empty frames.