Search the Community
Showing results for tags 'C++'.
The search index is currently processing. Current results may not be complete.
-
5 downloads
These components allow you to have different colors outlines for your entities. Add MultipleOutlinesCamera Component your main 3D camera. You can set color via method or component field in the Editor or component method SetColor() Add MultipleOutlinesEntity Component to entity that you want to have an outline. You can disable/enable outline per entity with MultipleOutlinesEntity methods or all via MultipleOutlinesCamera.Free -
C++ Beginner's Guide - first map, Main Menu, Loading Screen and GUI
Dreikblack posted a blog entry in Ultra Tutorials
Prerequisites https://www.leadwerks.com/learn/cppsetup?lang=cpp Install Ultra Engine Pro / Leadwerks 5 Pro Tutorial was updated for 0.9.9 version Check the environment variable LEADWERKS (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 "Leadwerks.h" #include "ComponentSystem.h" using namespace Leadwerks; 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("Leadwerks 5 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); loadingBackground->SetPosition(centerX, centerY); 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 "Leadwerks.h" namespace Leadwerks { //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 "Leadwerks.h" using namespace Leadwerks; //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 //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 "Leadwerks.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(); shared_ptr<Camera> camera; //you can get entity by tag or iterate all of them to find it for (auto const& entity : scene->entities) { auto foundCamera = entity->As<Camera>(); if (foundCamera) { camera = foundCamera; } } ui = CreateInterface(camera, font, frameSize); ui->SetRenderLayers(2); //to make backgrount transparent ui->root->SetColor(0.0f, 0.0f, 0.0f, 0.0f); //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); 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 "Leadwerks.h" #include "Components/Player/FPSPlayer.h" using namespace Leadwerks; class Game : public Object { protected: 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 "Leadwerks.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); shared_ptr<Camera> camera; //FPSPlayer component creates a camera so it will not be in scene's entities, but always in world's ones for (auto const& entity : world->GetEntities()) { auto foundPlayer = entity->GetComponent<FPSPlayer>(); if (foundPlayer) { player = foundPlayer; } auto foundCamera = entity->As<Camera>(); if (foundCamera) { camera = foundCamera; } } 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); //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 -
In this tutorial we will add features to real-time tactic/strategy game in Ultra Engine fro prev tutorial: Plan: 1. Add flag model to use when pointing where to go for units 2. Make save/load system with quick save 3. Make blood particle emitter which will be used on hits and use it in Unit::Methood 4. Make blood decals which will be on the ground after death and spawn it in Unit::Kill 5. Download and use sounds for units getting damage. In this tutorial used 0.9.9 engine version. Build number can be found in the Editor Help->About Flag Flag will represent a place that player units will try to reach. Model can be found here: https://sketchfab.com/3d-models/triangle-flag-adadd59fd56d44f7b0354d1caba38f95 Download .glb version and put it to new folder "triangle_flag" in Models. Convert to mdl in the Editor. By default this model is too small. Set Scale 5.0 in Transform tab and in Tools click Reset Transform (works properly atm only for non-animated models) to save new size for good. Also click on Collapse in Tools to unite all meshes so it would be shown like single entity in a scene (otherwise this entity will have some kids which is not critical, but it's better to make it simpler). triangle_flag.zip Flag prefab: 1. Put triangle_flag model to scene 2. In Physics Collision type - None and Pick Mode - None 3. Attach WayPoint component 4. In Appearance tab in add "Save" to tags - it will be needed eventually for game save/load system 5. Save as prefab called FlagWayPoint in Prefabs folder FlagWayPoint.zip Updating classes Let's update Unit class for using getting an entity as target point and save system later. Add new method to Unit.h: void goTo(shared_ptr<Entity> targetPointEntity, bool isForced = false); In Unit.cpp method implementation: void Unit::goTo(shared_ptr<Entity> targetPointEntity, bool isForced) { if (targetPointEntity) { isForcedMovement = isForced; targetPoint = targetPointEntity; goTo(); } } Add to Start() a new tag: entity->AddTag("Save"); And also in Start() after that: if (seq != -1) { //to disable pain state at end of pain animation model->skeleton->AddHook(seq, count - 1, EndPainHook, Self()); } Add this: if (health <= 0) { seq = model->FindAnimation(deathName); int count = model->CountAnimationFrames(seq); model->Animate(deathName, 1.0f, 250, ANIMATION_ONCE, count - 1); } It's needed to make Unit make lie down in death animation if it was killed when game was save. Add a little bit below after healthBar initialization nex tline: healthBar->SetScale((float)health / (float)maxHealth, 1); So health bar would have correct size after a game loading. We also want to save selected state after a loading so add first line to Load and second to Save: if (properties["isSelected"].is_boolean()) isSelected = properties["isSelected"]; properties["isSelected"] = isSelected; After line this line in Kill method: ListenEvent(EVENT_TIMERTICK, removeEntityTimer, RemoveEntityCallback, Self()); Add this: //not saving if supposed to be deleted anyway entity->RemoveTag("Save"); First version of TopDownCamera had an issue that would appear after game load: Update init() method to make it update pivot after game load: if (gameCamera && !targetPivot.lock()) { gameCamera->Listen();//for positional sound gameCamera->SetSweptCollision(true);//for removing pop up effect after quick move/turn gameCamera->SetRotation(CAMERA_PITCH, gameCamera->GetRotation(true).y, gameCamera->GetRotation(true).z); auto targetPivotShared = CreatePivot(gameCamera->GetWorld()); targetPivotShared->SetPickMode(PICK_NONE); sceneWeak.lock()->AddEntity(targetPivotShared); targetPivot = targetPivotShared; } //setting position and rotation here in case of game load gameCamera->SetParent(nullptr); auto targetPivotShared = targetPivot.lock(); auto targetPosition = getCirleCenter(gameCamera->GetPosition(true), gameCamera->GetRotation(true)); targetPosition.y = 0; targetPivotShared->SetPosition(targetPosition); targetPivotShared->SetRotation(0, gameCamera->GetRotation(true).y, gameCamera->GetRotation(true).z); gameCamera->SetParent(targetPivotShared); return true; TopDownCamera.zip In StrategyController.cpp update Start to add tag for Save next to another tag line entity->AddTag("Save"); To Save method add saving selected units: properties["selectedUnits"] = {}; int index = 0; for (auto const& selectedUnitWeak : selectedUnits) { auto selectedUnit = selectedUnitWeak.lock(); if (selectedUnit) { properties["selectedUnits"][index] = selectedUnit->GetUuid(); index++; } } Add their loading to Load: selectedUnits.clear(); if (properties["selectedUnits"].is_array()) { for (int i = 0; i < properties["selectedUnits"].size(); i++) { auto unit = scene->GetEntity(properties["selectedUnits"][i]); if (unit) { selectedUnits.push_back(unit); } } } Find this line for (auto const& entityWeak : selectedUnits) { Replace a code from starting from line above this line and ended 8 lines lower with this snippet: } else if (!selectedUnits.empty()) { auto flag = LoadPrefab(camera->GetWorld(), "Prefabs/FlagWayPoint.pfb"); if (flag) { flag->SetPosition(pick.position); } for (auto const& entityWeak : selectedUnits) { auto entityUnit = entityWeak.lock(); if (entityUnit && entityUnit->GetComponent<Unit>()) { if (flag) { entityUnit->GetComponent<Unit>()->goTo(flag, true); } else { entityUnit->GetComponent<Unit>()->goTo(pick.position, true); } } } } Or just download full class: StrategyController.zip Custom Save/Load system Current official system have a lot of flaws which are listed here: Most of them will be fixed, but something like no loading entities, which were added while run time like flags in this game, may still persist. This one is resolved by loading prefabs. You can also add manual recreation for most types of entities, depends on needs, but prefabs should be enough in most cases. Game class have a lot of changes, which were made to create custom save system. Whole Game.h: #pragma once #include "Leadwerks.h" #include "Components/Player/FPSPlayer.h" using namespace Leadwerks; class Game : public Object { protected: shared_ptr<Scene> scene; shared_ptr<FPSPlayer> fpsPlayer; shared_ptr<Widget> menuPanel; shared_ptr<Widget> gameSavedLabel; //hide gameSavedLabel on timer shared_ptr<Timer> gameSavedLabelTimer; vector<shared_ptr<Entity>> prefabCache; Game(); void init(shared_ptr<Framebuffer> framebuffer, WString mapPath); //update basic entity properties - position, rotation, tags void loadEntity(shared_ptr<Entity> entity, table entityTable); void loadGame(table saveTable); void saveGame(WString saveName); static bool QuickSaveGameCallback(const UltraEngine::Event& ev, shared_ptr<UltraEngine::Object> extra); static bool HideGameSavedLabelCallback(const UltraEngine::Event& ev, shared_ptr<UltraEngine::Object> extra); 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); //for game loading static std::shared_ptr<Game> create(shared_ptr<Framebuffer> framebuffer, table saveTable); shared_ptr<Interface> ui; shared_ptr<World> world; }; Game.cpp: #include "Leadwerks.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; } std::shared_ptr<Game> Game::create(shared_ptr<Framebuffer> framebuffer, table saveTable) { struct Struct : public Game {}; auto instance = std::make_shared<Struct>(); std::string mapName = saveTable["MapPath"]; instance->init(framebuffer, WString(mapName)); instance->loadGame(saveTable); 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); if (game->fpsPlayer) { //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); } game->fpsPlayer->doResetMousePosition = !isHidden; } //If the callback function returns false no more callbacks will be executed and no event will be added to the event queue. //to avoid double call return false; } return true; } 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); shared_ptr<Camera> camera; //FPSPlayer creates a camera so it will not be in scene's entities, but always in world's ones for (auto const& entity : world->GetEntities()) { auto foundPlayer = entity->GetComponent<FPSPlayer>(); if (foundPlayer) { fpsPlayer = foundPlayer; break; } auto foundCamera = entity->As<Camera>(); if (foundCamera) { camera = foundCamera; } } auto font = LoadFont("Fonts/arial.ttf"); //Create user interface for game menu auto frameSize = framebuffer->GetSize(); ui = CreateInterface(camera, font, frameSize); ui->SetRenderLayers(2); ui->root->SetColor(0.0f, 0.0f, 0.0f, 0.0f); //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 - 300 / 2, 300, 250, ui->root); gameSavedLabel = CreateLabel("GAME SAVED", frameSize.width / 2 - 100, 50, 200, 30, ui->root); gameSavedLabel->SetFontScale(2.0f); gameSavedLabel->SetHidden(true); 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 ^ ListenEvent(EVENT_KEYUP, nullptr, QuickSaveGameCallback, Self()); //caching prefabCache.push_back(LoadPrefab(world, "Prefabs/BloodPuddle.pfb")); prefabCache.push_back(LoadPrefab(world, "Prefabs/BloodHit.pfb")); prefabCache.push_back(LoadPrefab(world, "Prefabs/FlagWayPoint.pfb")); prefabCache.push_back(LoadPrefab(world, "Prefabs/WayPoint.pfb")); for (auto const& prefab : prefabCache) { if (prefab) { prefab->SetHidden(true); } } } void Game::loadEntity(shared_ptr<Entity> entity, table entityTable) { if (entityTable["position"].is_array() && entityTable["position"].size() == 3) { entity->SetPosition(entityTable["position"][0], entityTable["position"][1], entityTable["position"][2], true); } if (entityTable["rotation"].is_array() && entityTable["rotation"].size() == 3) { entity->SetRotation(entityTable["rotation"][0], entityTable["rotation"][1], entityTable["rotation"][2], true); } if (entityTable["tags"].is_array()) { for (int i = 0; i < entityTable["tags"].size(); i++) { entity->AddTag(std::string(entityTable["tags"][i])); } } } void Game::loadGame(table saveTable) { //old-new entity id vector<std::pair<String, String>> uuids; std::set<String> newEntities; //entites that was not in scene will be deleted once Components will be loaded and gets needed entities std::set<shared_ptr<Entity>> entitiesToRemoveFromScene; //iterating std::map by key (uuid) and value (entityTable) instead of pair for (auto& [uuid, entityTable] : saveTable["SavedEntities"]) { auto entity = scene->GetEntity(uuid); //load properties for saved entity that was initially on map if (entity) { loadEntity(entity, entityTable); //or if it was not we can recreate prefab at least } else if (entityTable["prefabPath"].is_string()) { //spawn saved entity that was not initially on map auto spawnedEntity = LoadPrefab(world, String(entityTable["prefabPath"])); if (!spawnedEntity) { continue; } scene->AddEntity(spawnedEntity); if (entityTable["isInScene"].is_boolean() && !entityTable["isInScene"]) { entitiesToRemoveFromScene.insert(spawnedEntity); } loadEntity(spawnedEntity, entityTable); uuids.push_back(std::pair(uuid, spawnedEntity->GetUuid())); newEntities.insert(spawnedEntity->GetUuid()); } } //delete not saved entities for (auto const& entity : world->GetTaggedEntities("Save")) { //does newEntities containes curent entity if (newEntities.find(entity->GetUuid()) != newEntities.end()) { //skip new entity continue; } //if supposed to be saved and was not due being removed when save was made then remove it from scene table entityTable = saveTable["SavedEntities"][entity->GetUuid()]; if (entityTable.empty()) { scene->RemoveEntity(entity); } } //saving table++ as a string auto saveString = String(saveTable.to_json()); //replace entities ids so component would use new ones for (auto const& [oldUuid, newUuid] : uuids) { saveString = saveString.Replace(oldUuid, newUuid); } //converting back to table++ from string saveTable = ParseJson(saveString); //Load saved data to components for (auto const& entity : world->GetTaggedEntities("Save")) { auto& entityTable = saveTable["SavedEntities"][entity->GetUuid()]; for (auto const& component : entity->components) { component->Load(entityTable, nullptr, scene, LOAD_DEFAULT, nullptr); } } //starting components now when all data is there for (auto const& entity : world->GetTaggedEntities("Save")) { for (auto const& component : entity->components) { component->Start(); } } //removing from scene entites that scene had not for (auto const& entity : entitiesToRemoveFromScene) { scene->RemoveEntity(entity); } } void Game::saveGame(WString saveName) { table saveTable; //saving map path to use it later to load correct map saveTable["MapPath"] = RelativePath(scene->path).ToUtf8String(); saveTable["SavedEntities"] = {}; for (auto const& entity : world->GetTaggedEntities("Save")) { table entityTable; for (auto const& component : entity->components) { component->Save(entityTable, nullptr, scene, SAVE_DEFAULT, nullptr); } //just to make save file more readable if (!entity->name.empty()) { entityTable["name"] = entity->name.ToUtf8String(); } //saving position and rotation of entity to restore them in Load auto position = entity->GetPosition(true); entityTable["position"] = {}; entityTable["position"][0] = position.x; entityTable["position"][1] = position.y; entityTable["position"][2] = position.z; auto rotation = entity->GetRotation(true); entityTable["rotation"] = {}; entityTable["rotation"][0] = rotation.x; entityTable["rotation"][1] = rotation.y; entityTable["rotation"][2] = rotation.z; entityTable["tags"] = {}; //to remove it from scene later once everything restored inc case if only components supposed to keep this entity entityTable["isInScene"] = scene->GetEntity(entity->GetUuid()) ? true : false; int tagIndex = 0; for (auto& tag : entity->tags) { entityTable["tags"][tagIndex] = tag.ToUtf8String(); tagIndex++; } //save prefab path to be able restore entity if it was added to scene later as prefab auto prefab = entity->GetPrefab(); if (prefab) { entityTable["prefabPath"] = RelativePath(prefab->GetPath()).ToUtf8String(); } //using entity id as key for its properties saveTable["SavedEntities"][entity->GetUuid()] = entityTable; } SaveTable(saveTable, saveName); //showing "Game Saved" labl for 2 seconds gameSavedLabel->SetHidden(false); gameSavedLabelTimer = UltraEngine::CreateTimer(2000); ListenEvent(EVENT_TIMERTICK, gameSavedLabelTimer, HideGameSavedLabelCallback, Self()); } bool Game::QuickSaveGameCallback(const UltraEngine::Event& ev, shared_ptr<UltraEngine::Object> extra) { if (KEY_F5 == ev.data && extra) { auto game = extra->As<Game>(); game->saveGame("QuickSave.save"); } return true; } bool Game::HideGameSavedLabelCallback(const UltraEngine::Event& ev, shared_ptr<UltraEngine::Object> extra) { if (extra && extra->As<Game>()) { auto game = extra->As<Game>(); game->gameSavedLabel->SetHidden(true); game->gameSavedLabelTimer->Stop(); game->gameSavedLabelTimer = nullptr; } return false; } Game.zip Add new functions to main.cpp: void LoadGame(WString savePath) { table saveTable = LoadTable(savePath); if (saveTable == nullptr) { return; } menu.reset(); //to avoid random error in World::Update() game.reset(); loadingWorld->Render(framebuffer); if (currentWorld) currentWorld.reset(); if (currentUi) currentUi.reset(); game = Game::create(framebuffer, saveTable); currentWorld = game->world; currentUi = game->ui; } bool QuickLoadGameCallback(const Event& event, shared_ptr<Object> extra) { if (KEY_F9 == event.data) { LoadGame("QuickSave.save"); } return true; } And add ListenEvent next to other similar lines: ListenEvent(EVENT_KEYUP, nullptr, QuickLoadGameCallback); main.zip Now you can save and load game with F5 and F9 respectively Making Particle Effect Download this blood texture made by TobiasM: https://opengameart.org/content/blood-splat It's not optimal for particle emitter, but good enough for learning purpose. Put into Materials\Particles folder and convert to .dds in the Editor. Generate a material from dds texture. Add to any scene particle emitter: In Appearance tab of Particles choose a recently made material. Now we able to see result when we will change this material properties. Open material: 1. Choose Lambertian shader family - it has better perfomance than PBR and it can make a difference eventually with many particle emitters which produce dozens particles. 2. In Blend tab check Transparent checkbox. Now it particles would look like that: Open Particles tab of Particle Emitter in entity properties: 1. Particle count - how many particles of this emitter exist at same time, left it as 50 2. Emission shape - shape of area where particles will be spawn 3. Emission Area - size of this area (width, height, depth). If 0 then particles spawns at same point. Left it as 0. 4. Burst frequency - how often particles spawns. Will be 800 5. Burst count - how much particles spawns in every burst. Enter 50 6. Colors - how particle looks at spawn and despawn 7. Velocity - direction and speed - make it 0 8. Acceleration - same but it's accel 9. Size - width and height of particle. No depth, because every particle is just a billboard sprite. Makie it 0.2/0.2 10. Radius. First value - relative size (i.e. 1.0 is 100%) for particle at start and second value is size when particles despawns. Value - 1.0/1.0 11. Turbulence - random speed in random direction. With 0 velocity it will make particles move in different directions. Make it 300.0 12. Rotation speed - how fast particle rotate. Make it 0 Download ParticleEffect to ParticleEffect component to Components\Appearance folder. Include into project and add to ComponentSystem as usual. Add this component to an emitter. It's needed to make blood hit effect to dissapear before it would burst again. Temporary checkbox on and Duration 700. Name emitter as BloodHit and save it as prefab "BloodHit.phb" in Prefabs folder Let's use it for units. In Unit.cpp in Damage method add next code somewhere above auto now = world->GetTime(); auto bloodHit = LoadPrefab(world, "Prefabs/BloodHit.pfb"); auto entity = GetEntity(); if (bloodHit) { auto bloodHitPosition = entity->GetPosition(true); //to lift it up to entity center bloodHitPosition.y = bloodHitPosition.y + entity->GetBounds(BOUNDS_GLOBAL).size.height / 2; bloodHit->SetPosition(bloodHitPosition, true); //prefabs component are not started after its load so will do it manually for (auto const& component : bloodHit->components) { component->Start(); } } Making decal Copy blood_splat.dds texture to Materials\Decals folder. Generate a new material from it and name BloodPuddle. Edit this material: 1. Keep PBR as shader family. 2. In Blend tab check Transparent checkbox. 3. In Surface Metalness 0, and Roughness 50. Add new decal to scene - 256x256 size with 16 height (or 250x250 and 20) Choose new material in appearance. Save this decal as prefab wiht BloodPuddle name. We will spawn this decal after unit death. In Unit::Kill() method add above auto model = entity->As<Model>(); line following code: auto scene = sceneWeak.lock(); auto bloodPuddle = LoadPrefab(entity->GetWorld(), "Prefabs/BloodPuddle.pfb"); if (bloodPuddle && scene) { auto bloodHitPosition = entity->GetPosition(true); bloodPuddle->SetPosition(bloodHitPosition, true); //to keep it scene->AddEntity(bloodPuddle); } Pain sounds Download sounds for Warrok: https://opengameart.org/node/132772 And for Paladin: https://opengameart.org/content/pain-sounds-by-emopreben In Sounds folder create Units subfolder and put there bear_02.ogg from 1st pack and "Painsounds v2 - Track 5 - Urggh.ogg" from 2nd Add to properties in Unit.json: { "label": "Pain sound", "name": "painSound", "value": "", "filecategory": "SOUND" }, New member in Unit.h header: shared_ptr<Sound> painSound; In Load method in Unit.cpp will load attached in the Editor sound: if (properties["painSound"].is_string()) painSound = LoadSound(std::string(properties["painSound"])); And play this sound in Damage() above auto now = world->GetTime(); line: if (painSound) { entity->EmitSound(painSound); } Unit class: FinalUnit.zip Now open Paladin prefab and add their pain sound in Unit component. Same for Warror. Delete units from Strategy map and add them again from prefabs. Remember to break prefab after that. strategy.zip Cache In debug mode you could notice micro-freezes when prefabs are loaded every time when it's not at the map. Thats because unload from memory an assets that have no shared_pointer to them. We can fix by making cache in Game class. Add to Game.h new member: vector<shared_ptr<Entity>> prefabCache; And at the end of init method in Game.cpp: //caching prefabCache.push_back(LoadPrefab(world, "Prefabs/BloodPuddle.pfb")); prefabCache.push_back(LoadPrefab(world, "Prefabs/BloodHit.pfb")); prefabCache.push_back(LoadPrefab(world, "Prefabs/FlagWayPoint.pfb")); for (auto const& prefab : prefabCache) { if (prefab) { prefab->SetHidden(true); } } GameCache.zip Now Debug mode will be smoother since assets loaded on game load and not in run time when components uses them. In Release mode it would be also necessary to do for heavy assets. Final version on github: https://github.com/Dreikblack/CppTutorialProject/tree/4-save-load-particles-decals Unit.zip
-
In this tutorial we will make a simple real-time tactic/strategy game in Ultra Engine. Plan: 1. Download and import ground material, 2 characters models with animations 2. Create Unit component with bot behavior. 3. Add control over player units. 4. Making prefabs and new map In this tutorial used 0.9.9 engine version. Asset import Download and unpack ground material from there in Materials\Ground folder Make in Models folder Characters in it Warrok and Paladin subfolders. Now login into https://www.mixamo.com/ Find there Paladin character and download with these settings without animations to get T-Pose as standard bind pose: Now we need animations from the Sword And Shield series (type Shield into the search field on top to filter them): Idle, Run, lash, Impact, Death. Hit "In Place" Checkbox to avoid character offset while animations: Put these .fbx to Paladin folder. Convert them into .mdl with right click in project tab of the Editor if there were not already If material was not created correctly then take pngs textures from "Paladin WProp J Nordstrom.fbm" folder which was created by converted and put in Paladin folder Convert them to DDS with Right Click. You can delete png and fbx files now. Now Paladin should look textured. Rename "Paladin WProp J Nordstrom" intro "Paladin" just for convenience. Open Paladin.mdl with double click to open Model Viewer. Let's load animations into this model from models with animations ("Sword And Shield Idle.mdl" etc): We need to rename those animations to use them properly later - open the Model tab, select animation to rename. You can play it to find out which is what. Lower will appear Animation panel where you can click on name to rename it: Let's call these animations: Idle, Attack, Pain, Death. There is also animation with T-Pose which can be deleted with Tools->Remove Sequence. We don't need animations files anymore and Paladin folder content should looks like that: One more thing needs to be done for model - Collider. In View toggle Show Collider, open Model tab, select SKIN_MESH and in Physics choose Cylinder collider. Offset and Size settings: We need worth enemy for our paladins - download Warrok model and animations from Mutant series - Idle, Run, Dying, Punch. For pain i chose "Standing React Large From Right" animation. Next do the same stuff as it was with Paladin - material, animations etc. In Transform make scale 0.85 so it would match Paladin size. Collider settings: Unit component After preparing character models we now need a component which will have: Unit params: health, speed, damage etc. Playing animations Bot behaviour - move and attack nearby enemies Input for player to move and attack something specific Create Unit component in AI folder via Editor or manually in "Source\Components\AI" Unit.json, Unit.h, Unit.cpp files and include last two into project. Unit.json: { "component": { "properties": [ { "name": "enabled", "label": "Enabled", "value": true }, { "name": "isFullPlayerControl", "label": "Full Player Control", "value": false }, { "name": "isPlayer", "label": "Is Player Unit", "value": false }, { "name": "team", "label": "Team", "value": 1, "options": [ "Neutral", "Good", "Bad" ] }, { "name": "health", "label": "Health", "value": 100 }, { "name": "maxHealth", "label": "Max Health", "value": 100 }, { "name": "speed", "label": "Speed", "value": 3.0 }, { "name": "attackRange", "label": "Attack Range", "value": 2.0 }, { "name": "attackDamage", "label": "Attack Damage", "value": 30 }, { "name": "attackFrame", "label": "Attack Frame", "value": 5 }, { "name": "painCooldown", "label": "Pain Cooldown", "value": 1000 }, { "name": "decayTime", "label": "Decay Time", "value": 10000 }, { "name": "target", "label": "Target", "value": null }, { "name": "targetPoint", "label": "Target Point", "value": null }, { "name": "attackName", "label": "Attack Name", "value": "Attack" }, { "name": "idleName", "label": "Idle Name", "value": "Idle" }, { "name": "painName", "label": "Pain", "value": "Pain" }, { "name": "deathName", "label": "Death", "value": "Death" }, { "name": "runName", "label": "Run", "value": "Run" } ], "inputs": [ { "name": "Enable" }, { "name": "Disable" } ] } } Parameters descriptions can be found in Unit.h: #pragma once #include "UltraEngine.h" #include "../BaseComponent.h" using namespace Leadwerks; //abstract class which will be a parent for other units classes such as Beast and Hunter //partly based of Enemy/Monster/Player default classes class Leadwerks : public BaseComponent { protected: //so it could be added for entity with FPS Player component bool isFullPlayerControl = false; int health = 100; int maxHealth = 100; //used for AI navigation, weak_ptr just to make sure that component will not keep it if stays after map unload somehow std::weak_ptr<NavMesh> navMesh; //unique per entity so shared_ptr //NavAgent used to create to plot navigation paths in NavMesh std::shared_ptr<NavAgent> agent; //how far AI see its enemies in meters float perceptionRadius = 10; //how long to pursue when out of radius float chaseMaxDistance = perceptionRadius * 2; //is target a priority bool isForcedTarget = false; //target to follow and attack if possible std::weak_ptr<Entity> targetWeak; //to avoid fighting bool isForcedMovement = false; //which distance to point should be to reach it float targetPointDistance = 0.5f; //place to reach std::shared_ptr<Entity> targetPoint; //is attack animation playing bool isAttacking = false; //when attack started uint64_t meleeAttackTime = 0; //do damage in meleeAttackTiming after attack start int attackFrame = 5; float attackRange = 2.0f; int attackDamage = 30; //pain/git hit state bool isInPain = false; //can't start new pain animation immediately to avoid infinite stugger int painCooldown = 300; //when pain animation started uint64_t painCooldownTime; //how fast unit is float speed = 3.0; //when to try scan again uint64_t nextScanForTargetTime = 0ULL;//unsigned long long //animations names WString attackName; WString idleName; WString painName; WString deathName; WString runName; //health bar above unit shared_ptr<Tile> healthBar; shared_ptr<Tile> healthBarBackground; bool isSelected = false; //to keep camera pointer for unit health bars std::weak_ptr<Camera> cameraWeak; //to be able to remove entity inside of component later std::weak_ptr<Scene> sceneWeak; //time in ms before delete model after a death, 0 of disabled int decayTime = 10000; shared_ptr<Timer> removeEntityTimer; static bool RemoveEntityCallback(const UltraEngine::Event& ev, shared_ptr<UltraEngine::Object> extra); virtual void scanForTarget(); bool goTo(); //pick filter static bool RayFilter(shared_ptr<Entity> entity, shared_ptr<Object> extra); //attack target if in range static void AttackHook(shared_ptr<Skeleton> skeleton, shared_ptr<Object> extra); //disable attacking state static void EndAttackHook(shared_ptr<Skeleton> skeleton, shared_ptr<Object> extra); //disable pain state static void EndPainHook(shared_ptr<Skeleton> skeleton, shared_ptr<Object> extra); public: int team = 0;//0 neutral, 1 player team, 2 enemy bool isPlayer = false; Unit(); shared_ptr<Component> Copy() override; void Start() override; bool Load(table& t, shared_ptr<Stream> binstream, shared_ptr<Scene> scene, const LoadFlags flags, shared_ptr<Object> extra) override; bool Save(table& t, shared_ptr<Stream> binstream, shared_ptr<Scene> scene, const SaveFlags flags, shared_ptr<Object> extra) override; //deal a damage to this unit by attacker void Damage(const int amount, shared_ptr<Entity> attacker) override; //kill this unit by attacker void Kill(shared_ptr<Entity> attacker) override; bool isAlive(); void Update() override; bool isEnemy(int otherUnitTeam) const; void goTo(Vec3 positionToGo, bool isForced = false); void attack(shared_ptr<Entity> entityToAttack, bool isForced = false); void select(bool doSelect = true); }; #pragma once #include "Leadwerks.h" #include "Unit.h" #include "../Logic/WayPoint.h" using namespace Leadwerks; Unit::Unit() { name = "Unit"; attackName = "Attack"; idleName = "Idle"; painName = "Pain"; deathName = "Death"; runName = "Run"; } shared_ptr<Component> Unit::Copy() { return std::make_shared<Unit>(*this); } void Unit::Start() { auto entity = GetEntity(); auto model = entity->As<Model>(); //for custom save/load system entity->AddTag("Unit"); if (!isFullPlayerControl) { //checking efficiently if Unit have a nav mesh if (!navMesh.expired()) { //1 m radius because of Beast long model, 0.5 would better otherwise, 2 m height agent = CreateNavAgent(navMesh.lock(), 0.5, 2); agent->SetMaxSpeed(speed); agent->SetPosition(entity->GetPosition(true)); agent->SetRotation(entity->GetRotation(true).y); entity->SetPosition(0, 0, 0); //becase models rotated by back entity->SetRotation(0, 180, 0); entity->Attach(agent); } entity->SetCollisionType(COLLISION_PLAYER); entity->SetMass(0); entity->SetPhysicsMode(PHYSICS_RIGIDBODY); } if (model) { auto seq = model->FindAnimation(attackName); if (seq != -1) { int count = model->CountAnimationFrames(seq); //to disable attack state at end of attack animation model->skeleton->AddHook(seq, count - 1, EndAttackHook, Self()); //to deal damage to target at range at specific animation frame model->skeleton->AddHook(seq, attackFrame, AttackHook, Self()); } seq = model->FindAnimation(painName); if (seq != -1) { int count = model->CountAnimationFrames(seq); //to disable pain state at end of pain animation model->skeleton->AddHook(seq, count - 1, EndPainHook, Self()); } } auto world = entity->GetWorld(); shared_ptr<Camera> camera; for (auto const& cameraEntity : world->GetTaggedEntities("Camera")) { camera = cameraEntity->As<Camera>(); break; } if (!camera) { for (auto const& cameraEntity : world->GetEntities()) { camera = cameraEntity->As<Camera>(); if (camera) { break; } } } cameraWeak = camera; if (!isFullPlayerControl) { int healthBarHeight = 5; healthBar = CreateTile(camera, maxHealth, healthBarHeight); if (team == 1) { healthBar->SetColor(0, 1, 0); } else { healthBar->SetColor(1, 0, 0); } //to put it before health bar healthBar->SetOrder(1); healthBarBackground = CreateTile(camera, maxHealth, healthBarHeight); healthBarBackground->SetColor(0.1f, 0.1f, 0.1f); healthBarBackground->SetOrder(0); } BaseComponent::Start(); } bool Unit::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Scene> scene, const LoadFlags flags, shared_ptr<Object> extra) { sceneWeak = scene; if (properties["isFullPlayerControl"].is_boolean()) isFullPlayerControl = properties["isFullPlayerControl"]; if (properties["isPlayer"].is_boolean()) isPlayer = properties["isPlayer"]; if (properties["team"].is_number()) team = properties["team"]; if (properties["health"].is_number()) health = properties["health"]; if (properties["maxHealth"].is_number()) maxHealth = properties["maxHealth"]; if (properties["attackDamage"].is_number()) attackDamage = properties["attackDamage"]; if (properties["attackRange"].is_number()) attackRange = properties["attackRange"]; if (properties["attackFrame"].is_number()) attackFrame = properties["attackFrame"]; if (properties["painCooldown"].is_number()) painCooldown = properties["painCooldown"]; if (properties["enabled"].is_boolean()) enabled = properties["enabled"]; if (properties["decayTime"].is_number()) decayTime = properties["decayTime"]; if (properties["attackName"].is_string()) attackName = properties["attackName"]; if (properties["idleName"].is_string()) idleName = properties["idleName"]; if (properties["deathName"].is_string()) deathName = properties["deathName"]; if (properties["painName"].is_string()) painName = properties["painName"]; if (properties["runName"].is_string()) runName = properties["runName"]; if (properties["target"].is_string()) { std::string id = properties["target"]; targetWeak = scene->GetEntity(id); } else { targetWeak.reset(); } if (properties["targetPoint"].is_string()) { std::string id = properties["targetPoint"]; targetPoint = scene->GetEntity(id); } else { targetPoint = nullptr; } if (properties["isForcedMovement"].is_boolean()) isForcedMovement = properties["isForcedMovement"]; if (properties["position"].is_array() && properties["position"].size() == 3) { GetEntity()->SetPosition(properties["position"][0], properties["position"][1], properties["position"][2]); } if (properties["rotation"].is_array() && properties["rotation"].size() == 3) { GetEntity()->SetRotation(properties["rotation"][0], properties["rotation"][1], properties["rotation"][2]); } navMesh.reset(); if (!scene->navmeshes.empty()) { navMesh = scene->navmeshes[0]; } return BaseComponent::Load(properties, binstream, scene, flags, extra); } bool Unit::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Scene> scene, const SaveFlags flags, shared_ptr<Object> extra) { properties["isFullPlayerControl"] = isFullPlayerControl; properties["isPlayer"] = isPlayer; properties["team"] = team; properties["health"] = health; properties["enabled"] = enabled; if (targetWeak.lock()) { properties["target"] = targetWeak.lock()->GetUuid(); } if (targetPoint) { properties["targetPoint"] = targetPoint->GetUuid(); } properties["isForcedMovement"] = isForcedMovement; auto position = GetEntity()->GetPosition(true); properties["position"] = {}; properties["position"][0] = position.x; properties["position"][1] = position.y; properties["position"][2] = position.z; auto rotation = GetEntity()->GetRotation(true); properties["rotation"] = {}; properties["rotation"][0] = rotation.x; properties["rotation"][1] = rotation.y; properties["rotation"][2] = rotation.z; return BaseComponent::Save(properties, binstream, scene, flags, extra); } void Unit::Damage(const int amount, shared_ptr<Entity> attacker) { if (!isAlive()) { return; } health -= amount; auto world = GetEntity()->GetWorld(); if (!world) { return; } auto now = world->GetTime(); if (health <= 0) { Kill(attacker); } else if (!isInPain && now - painCooldownTime > painCooldown) { isInPain = true; isAttacking = false; auto model = GetEntity()->As<Model>(); if (model) { model->StopAnimation(); model->Animate(painName, 1.0f, 100, ANIMATION_ONCE); } if (agent) { agent->Stop(); } } if (healthBar) { //reducing health bar tile width healthBar->SetScale((float)health / (float)maxHealth, 1); } //attack an atacker if (!isForcedMovement && !isForcedTarget) { attack(attacker); } } void Unit::Kill(shared_ptr<Entity> attacker) { auto entity = GetEntity(); if (!entity) { return; } auto model = entity->As<Model>(); if (model) { model->StopAnimation(); model->Animate(deathName, 1.0f, 250, ANIMATION_ONCE); } if (agent) { //This method will cancel movement to a destination, if it is active, and the agent will smoothly come to a halt. agent->Stop(); } //to remove nav agent entity->Detach(); agent = nullptr; //to prevent it being obstacle entity->SetCollisionType(COLLISION_NONE); //to prevent selection entity->SetPickMode(PICK_NONE); isAttacking = false; healthBar = nullptr; healthBarBackground = nullptr; if (decayTime > 0) { removeEntityTimer = UltraEngine::CreateTimer(decayTime); ListenEvent(EVENT_TIMERTICK, removeEntityTimer, RemoveEntityCallback, Self()); } } bool Unit::isAlive() { return health > 0 && GetEntity(); } bool Unit::RemoveEntityCallback(const UltraEngine::Event& ev, shared_ptr<UltraEngine::Object> extra) { auto unit = extra->As<Unit>(); unit->removeEntityTimer->Stop(); unit->removeEntityTimer = nullptr; unit->sceneWeak.lock()->RemoveEntity(unit->GetEntity()); return false; } void Unit::scanForTarget() { auto entity = GetEntity(); auto world = entity->GetWorld(); if (world) { //We only want to perform this few times each second, staggering the operation between different entities. //Pick() operation is kinda CPU heavy. It can be noticeable in Debug mode when too much Picks() happes in same game cycle. //Did not notice yet it in Release mode, but it's better to have it optimized Debug as well anyway. auto now = world->GetTime(); if (now < nextScanForTargetTime) { return; } nextScanForTargetTime = now + Random(100, 200); auto entityPosition = entity->GetPosition(true); Vec3 positionLower = entityPosition; positionLower.x = positionLower.x - perceptionRadius; positionLower.z = positionLower.z - perceptionRadius; positionLower.y = positionLower.y - perceptionRadius; Vec3 positionUpper = entityPosition; positionUpper.x = positionUpper.x + perceptionRadius; positionUpper.z = positionUpper.z + perceptionRadius; positionUpper.y = positionUpper.y + perceptionRadius; //will use it to determinate which target is closest float currentTargetDistance = -1; //GetEntitiesInArea takes positions of an opposite corners of a cube as params for (auto const& foundEntity : world->GetEntitiesInArea(positionLower, positionUpper)) { auto foundUnit = foundEntity->GetComponent<Unit>(); //targets are only alive enemy units if (!foundUnit || !foundUnit->isAlive() || !foundUnit->isEnemy(team) || !foundUnit->GetEntity()) { continue; } float dist = foundEntity->GetDistance(entity); if (dist > perceptionRadius) { continue; } //check if no obstacles like walls between units auto pick = world->Pick(entity->GetBounds(BOUNDS_RECURSIVE).center, foundEntity->GetBounds(BOUNDS_RECURSIVE).center, perceptionRadius, true, RayFilter, Self()); if (dist < 0 || currentTargetDistance < dist) { targetWeak = foundEntity; currentTargetDistance = dist; } } } } void Unit::Update() { if (!GetEnabled() || !isAlive()) { return; } auto entity = GetEntity(); auto world = entity->GetWorld(); auto model = entity->As<Model>(); if (!world || !model) { return; } if (isFullPlayerControl) { return; } //making health bar fllow the unit auto window = ActiveWindow(); if (window && healthBar && healthBarBackground) { auto framebuffer = window->GetFramebuffer(); auto position = entity->GetBounds().center; position.y += entity->GetBounds().size.height / 2;//take top position of unit shared_ptr<Camera> camera = cameraWeak.lock(); if (camera) { //transorming 3D position into 2D auto unitUiPosition = camera->Project(position, framebuffer); unitUiPosition.x -= healthBarBackground->size.x / 2; healthBar->SetPosition(unitUiPosition.x, unitUiPosition.y); healthBarBackground->SetPosition(unitUiPosition.x, unitUiPosition.y); bool doShow = isSelected || (health != maxHealth && !isPlayer); healthBar->SetHidden(!doShow); healthBarBackground->SetHidden(!doShow); } } //can't attack or move while pain animation if (isInPain) { return; } bool isMoving = false; //ignore enemies and move if (isForcedMovement && goTo()) { return; } //atacking part if (!isMoving) { auto target = targetWeak.lock(); // Stop attacking if target is dead if (target) { float distanceToTarget = entity->GetDistance(target); bool doResetTarget = false; if (distanceToTarget > chaseMaxDistance && !isForcedTarget) { doResetTarget = true; } else { for (auto const& targetComponent : target->components) { auto targetUnit = targetComponent->As<Unit>(); if (targetUnit && !targetUnit->isAlive()) { doResetTarget = true; isForcedTarget = false; } break; } } if (doResetTarget) { target.reset(); targetWeak.reset(); if (agent) { agent->Stop(); } } } if (isAttacking && target != nullptr) { //rotating unit to target float a = ATan(entity->matrix.t.x - target->matrix.t.x, entity->matrix.t.z - target->matrix.t.z); if (agent) { agent->SetRotation(a + 180); } } if (!target) { scanForTarget(); } if (target) { float distanceToTarget = entity->GetDistance(target); //run to target if out of range if (distanceToTarget > attackRange) { if (agent) { agent->Navigate(target->GetPosition(true)); } model->Animate(runName, 1.0f, 250, ANIMATION_LOOP); } else { if (agent) { agent->Stop(); } //start attack if did not yet if (!isAttacking) { meleeAttackTime = world->GetTime(); model->Animate(attackName, 1.0f, 100, ANIMATION_ONCE); isAttacking = true; } } return; } } if (targetPoint && goTo()) { return; } if (!isAttacking) { model->Animate(idleName, 1.0f, 250, ANIMATION_LOOP); if (agent) { agent->Stop(); } } } bool Unit::RayFilter(shared_ptr<Entity> entity, shared_ptr<Object> extra) { shared_ptr<Unit> thisUnit = extra->As<Unit>(); shared_ptr<Unit> pickedUnit = entity->GetComponent<Unit>(); //skip if it's same team return pickedUnit == nullptr || pickedUnit && pickedUnit->team != thisUnit->team; } void Unit::AttackHook(shared_ptr<Skeleton> skeleton, shared_ptr<Object> extra) { auto unit = std::dynamic_pointer_cast<Unit>(extra); if (!unit) { return; } auto entity = unit->GetEntity(); auto target = unit->targetWeak.lock(); if (target) { auto pos = entity->GetPosition(true); auto dest = target->GetPosition(true) + target->GetVelocity(true); //attack in target in range if (pos.DistanceToPoint(dest) < unit->attackRange) { for (auto const& targetComponent : target->components) { auto base = targetComponent->As<BaseComponent>(); if (base) { base->Damage(unit->attackDamage, entity); } } } } } void Unit::EndAttackHook(shared_ptr<Skeleton> skeleton, shared_ptr<Object> extra) { auto unit = std::dynamic_pointer_cast<Unit>(extra); if (unit) { unit->isAttacking = false; } } void Unit::EndPainHook(shared_ptr<Skeleton> skeleton, shared_ptr<Object> extra) { auto unit = extra->As<Unit>(); if (unit) { unit->isInPain = false; if (unit->isAlive() && unit->GetEntity()->GetWorld()) { unit->painCooldownTime = unit->GetEntity()->GetWorld()->GetTime(); } } } bool Unit::isEnemy(int otherUnitTeam) const { return team == 1 && otherUnitTeam == 2 || team == 2 && otherUnitTeam == 1; } void Unit::goTo(Vec3 positionToGo, bool isForced) { auto entity = GetEntity(); if (entity) { isForcedMovement = isForced; targetPoint = CreatePivot(entity->GetWorld()); targetPoint->SetPosition(positionToGo); goTo(); } } bool Unit::goTo() { bool doMove = false; auto entity = GetEntity(); auto model = entity->As<Model>(); if (targetPoint && agent) { doMove = agent->Navigate(targetPoint->GetPosition(true), 100, 2.0f); if (doMove) { //checking distance to target point on nav mesh float distanceToTarget = entity->GetDistance(agent->GetDestination()); if (distanceToTarget < targetPointDistance) { auto wayPoint = targetPoint->GetComponent<WayPoint>(); if (wayPoint && wayPoint->getNextPoint()) { targetPoint = wayPoint->getNextPoint(); doMove = true; } else { targetPoint.reset(); doMove = false; } } else { doMove = true; } } if (doMove && model) { model->Animate(runName, 1.0f, 250, ANIMATION_LOOP); } } return doMove; } void Unit::attack(shared_ptr<Entity> entityToAttack, bool isForced) { targetWeak.reset(); if (!entityToAttack || !entityToAttack->GetComponent<Unit>() || entityToAttack->GetComponent<Unit>()->team == team) { return; } targetPoint.reset(); isForcedMovement = false; isForcedTarget = isForced; targetWeak = entityToAttack; } void Unit::select(bool doSelect) { isSelected = doSelect; } For Unit class you need the WayPoint component from the previous tutorial. Also can be download here: Remember adding new component to ComponentSystem.h Unit files:Unit.zip Strategy Controller component Strategy Controller will be used to control player units: Selecting a unit by left click, doing it with Control will add new unit to already selected Clicking on something else reset unit selection Holding left mouse button will create selection box that will select units in it once button released Right click to make units go somewhere ignoring enemies or attacking specific enemy Its path will be: "Source\Components\Player" StrategyController.json: { "component": { "properties": [ { "name": "playerTeam", "label": "Player Team", "value": 1 } ] } } StrategyController.h #pragma once #include "Leadwerks.h" using namespace Leadwerks; class StrategyController : public Component { protected: vector<std::weak_ptr<Entity>> selectedUnits; //Control key state bool isControlDown = false; int playerTeam = 1; std::weak_ptr<Camera> cameraWeak; shared_ptr<Tile> unitSelectionBox; //first mouse position when Mouse Left was pressed iVec2 unitSelectionBoxPoint1; //height of selection box float selectHeight = 4; //mouse left button state bool isMouseLeftDown = false; //draw or hide selection box void updateUnitSelectionBox(); bool selectUnitsByBox(shared_ptr<Camera> camera, shared_ptr<Framebuffer> framebuffer, iVec2 unitSelectionBoxPoint2); void deselectAllUnits(); static bool RayFilter(shared_ptr<Entity> entity, shared_ptr<Object> extra); public: StrategyController(); ~StrategyController() override; 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; void Update() override; void Start() override; bool ProcessEvent(const Event& e) override; }; StrategyController.cpp #include "Leadwerks.h" #include "StrategyController.h" #include "../AI/Unit.h" StrategyController::StrategyController() { name = "StrategyController"; } shared_ptr<Component> StrategyController::Copy() { return std::make_shared<StrategyController>(*this); } StrategyController::~StrategyController() = default; void StrategyController::Start() { auto entity = GetEntity(); entity->AddTag("StrategyController"); //Listen() needed for calling ProcessEvent() in component when event happen Listen(EVENT_MOUSEDOWN, nullptr); Listen(EVENT_MOUSEUP, nullptr); Listen(EVENT_MOUSEMOVE, nullptr); Listen(EVENT_KEYUP, nullptr); Listen(EVENT_KEYDOWN, nullptr); //optimal would be setting component to a camera if (entity->As<Camera>()) { cameraWeak = entity->As<Camera>(); } else { //otherwise let's get it by tag for (auto const& cameraEntity : GetEntity()->GetWorld()->GetTaggedEntities("Camera")) { cameraWeak = cameraEntity->As<Camera>(); break; } } // 1/1 size for pixel accuarcy scaling unitSelectionBox = CreateTile(cameraWeak.lock(), 1, 1); //transparent green color unitSelectionBox->SetColor(0, 0.4f, 0.2, 0.5f); unitSelectionBox->SetHidden(true); //to make sprite transparent auto material = CreateMaterial(); material->SetShadow(false); material->SetTransparent(true); material->SetPickMode(false); //Unlit removes any effect that would light draw on material material->SetShaderFamily(LoadShaderFamily("Shaders/Unlit.fam")); unitSelectionBox->SetMaterial(material); } bool StrategyController::Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags, shared_ptr<Object> extra) { Component::Load(properties, binstream, scene, flags, extra); if (properties["playerTeam"].is_number()) playerTeam = properties["playerTeam"]; return true; } bool StrategyController::Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags, shared_ptr<Object> extra) { Component::Save(properties, binstream, scene, flags, extra); properties["playerTeam"] = playerTeam; return true; } void StrategyController::Update() { updateUnitSelectionBox(); } bool StrategyController::ProcessEvent(const Event& e) { auto window = ActiveWindow(); if (!window) { return true; } auto mousePosition = window->GetMousePosition(); auto camera = cameraWeak.lock(); switch (e.id) { case EVENT_MOUSEDOWN: if (!camera) { break; } if (e.data == MOUSE_LEFT) { unitSelectionBoxPoint1 = iVec2(mousePosition.x, mousePosition.y); isMouseLeftDown = true; //move or attack on Right Click } else if (e.data == MOUSE_RIGHT) { //getting entity under cursor auto pick = camera->Pick(window->GetFramebuffer(), mousePosition.x, mousePosition.y, 0, true); if (pick.success && pick.entity) { auto unit = pick.entity->GetComponent<Unit>(); if (unit && unit->isAlive() && unit->team != playerTeam) { for (auto const& entityWeak : selectedUnits) { auto entityUnit = entityWeak.lock(); if (entityUnit && entityUnit->GetComponent<Unit>()) { entityUnit->GetComponent<Unit>()->attack(pick.entity, true); } } } else { for (auto const& entityWeak : selectedUnits) { auto entityUnit = entityWeak.lock(); if (entityUnit && entityUnit->GetComponent<Unit>()) { entityUnit->GetComponent<Unit>()->goTo(pick.position, true); } } } } } break; case EVENT_MOUSEUP: if (!camera) { break; } //unit selection on Left Click if (e.data == MOUSE_LEFT) { if (!selectUnitsByBox(camera, window->GetFramebuffer(), iVec2(mousePosition.x, mousePosition.y))) { auto pick = camera->Pick(window->GetFramebuffer(), mousePosition.x, mousePosition.y, 0, true); if (pick.success && pick.entity) { auto unit = pick.entity->GetComponent<Unit>(); if (unit && unit->isPlayer && unit->isAlive()) { if (!isControlDown) { deselectAllUnits(); } selectedUnits.push_back(pick.entity); unit->select(); } else { deselectAllUnits(); } } else { deselectAllUnits(); } } isMouseLeftDown = false; } break; case EVENT_MOUSEMOVE: break; case EVENT_KEYUP: if (e.data == KEY_CONTROL) { isControlDown = false; } break; case EVENT_KEYDOWN: if (e.data == KEY_CONTROL) { isControlDown = true; } break; } return true; } void StrategyController::deselectAllUnits() { for (auto const& entityWeak : selectedUnits) { auto entityUnit = entityWeak.lock(); if (entityUnit && entityUnit->GetComponent<Unit>()) { entityUnit->GetComponent<Unit>()->select(false); } } selectedUnits.clear(); } void StrategyController::updateUnitSelectionBox() { if (!isMouseLeftDown) { unitSelectionBox->SetHidden(true); } else { auto window = ActiveWindow(); if (window) { auto mousePosition = window->GetMousePosition(); iVec2 unitSelectionBoxPoint2(mousePosition.x, mousePosition.y); iVec2 upLeft(Min(unitSelectionBoxPoint1.x, unitSelectionBoxPoint2.x), Min(unitSelectionBoxPoint1.y, unitSelectionBoxPoint2.y)); iVec2 downRight(Max(unitSelectionBoxPoint1.x, unitSelectionBoxPoint2.x), Max(unitSelectionBoxPoint1.y, unitSelectionBoxPoint2.y)); //don't show Selection Box if it's only few pixels and could be single click to select unit if ((downRight.x - upLeft.x < 4) || (downRight.y - upLeft.y < 4)) { unitSelectionBox->SetHidden(true); return; } unitSelectionBox->SetPosition(upLeft.x, upLeft.y); auto width = downRight.x - upLeft.x; auto height = downRight.y - upLeft.y; //changing sprite size via scale, just size is readonly unitSelectionBox->SetScale(width, height); unitSelectionBox->SetHidden(false); } } } bool StrategyController::selectUnitsByBox(shared_ptr<Camera> camera, shared_ptr<Framebuffer> framebuffer, iVec2 unitSelectionBoxPoint2) { if (!unitSelectionBox || unitSelectionBox->GetHidden() || !camera || !framebuffer) { return false; } iVec2 upLeft(Min(unitSelectionBoxPoint1.x, unitSelectionBoxPoint2.x), Min(unitSelectionBoxPoint1.y, unitSelectionBoxPoint2.y)); iVec2 downRight(Max(unitSelectionBoxPoint1.x, unitSelectionBoxPoint2.x), Max(unitSelectionBoxPoint1.y, unitSelectionBoxPoint2.y)); auto pick1 = camera->Pick(framebuffer, upLeft.x, upLeft.y, 0, true, RayFilter); auto pick2 = camera->Pick(framebuffer, downRight.x, downRight.y, 0, true, RayFilter); if (!pick1.success || !pick2.success) { return false; } deselectAllUnits(); //first param GetEntitiesInArea should has lower coordinates than second Vec3 positionLower = Vec3(Min(pick1.position.x, pick2.position.x), Min(pick1.position.y, pick2.position.y), Min(pick1.position.z, pick2.position.z)); Vec3 positionUpper = Vec3(Max(pick1.position.x, pick2.position.x), Max(pick1.position.y, pick2.position.y), Max(pick1.position.z, pick2.position.z)); positionUpper.y = positionUpper.y + selectHeight; for (auto const& foundEntity : camera->GetWorld()->GetEntitiesInArea(positionLower, positionUpper)) { auto foundUnit = foundEntity->GetComponent<Unit>(); //targets are only alive enemy units if (!foundUnit || !foundUnit->isAlive() || !foundUnit->isPlayer || foundUnit->team != playerTeam) { continue; } selectedUnits.push_back(foundUnit->GetEntity()); foundUnit->select(); } return true; } bool StrategyController::RayFilter(shared_ptr<Entity> entity, shared_ptr<Object> extra) { shared_ptr<Unit> pickedUnit = entity->GetComponent<Unit>(); //skip if it's unit return pickedUnit == nullptr; } StrategyController.zip Also we need top down camera component, you can find it here, if you don't have it yet: Prefabs Create Prefabs folder in project root folder. Open the Editor, add camera to empty scene, call it StrategyCamera and add to it TopDownCamera and StrategyController components. You might also want to change a FOV in Camera tab in entity properties. To make a prefab do right click on camera in Scene tab and press "Save as Prefab": Once you want to change something without having to do it on every map, just open the prefab .pfb file and do a change there. Now create a Units subfolder in the Prefabs folder for two units. Add to scene Paladin model. Add Paladin component, click on "Is Player Unit" checkbox and change Attack Frame to 40 as it's a frame when sword will hit a target: Save as Prefab in Pefabs/Units as Paladin.pfb You can remove now Paladin from scene and add Warrok. In its Unit component team will be Bad, health and max health 120, speed 2.0, attack range 1.5, attack damage 25, and Attack Frame 22 as it's attack faster. Simple Strategy map creation Create big flat brush as a ground Add Navigation map - change Agent Radius to 0.5 and Agent Height 2.0 so Warrok could fit it. Tile size be default is 8 m² = Voxel size (0.25) * Tile Resolution (32). It's better not to touch those until you know what you are doing. To change Nav Mesh size just change Tile count in first line to make it fit created ground. For 40 m² it will be 5x5 tiles. Drag a ground material from Project tab "\Materials\Ground\Ground036.mat" to the ground. If it's blurry, select brush, choose Edit Face mode and increase a scale there. Select translate mode and drag'n'drop to scene prefabs StrategyCamera, Warrok and Paladin. You can copy entities with Shift + dragging. To edit a prefab entity you will need to "break" prefab. To do it click on the Lock icon in properties and Ok in the dialog. Simple map is ready. You can make it a little bit complicated by adding couple cycled WayPoints for Warroks. Add WayPoint to Target Point. strategy.zip In Game.cpp update GameMenuButtonCallback function to avoid cursor being hidden: 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); if (game->player) { //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); } 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. //to avoid double call return false; } return true; } GameAndMenu.zip In result should be something like that: Final version here: https://github.com/Dreikblack/CppTutorialProject/tree/3-making-strategy-game GameAndMenu.zip StrategyController.zip
-
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 0.9.9 engine version used. Let's start with making WayPoint component: In the Leadwerks 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. 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 "Leadwerks.h" using namespace Leadwerks; 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 "Leadwerks.h" #include "WayPoint.h" using namespace Leadwerks; 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 "Leadwerks.h" using namespace Leadwerks; 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 "Leadwerks.h" #include "WayMover.h" #include "../Logic/WayPoint.h" using namespace Leadwerks; 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 -
8 downloads
Little component that can be used for Particle Emitter that should be deleted in time Temporary - will component keep entity pointer to delete it once time is out Reducing Effect - decrease particles Velocity and Turbulence with time Duration - time before entity pointer will be deleted Case uses: explosions, blood hits, bleeding effect etc.Free -
-
5 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 doStayOnPointFree -
5 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 oneFree -
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
-
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. -
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.
-
Hello again. Implemented UTF8 support for LE4. Works fine?. context->SetBlendMode(Blend::Alpha); context->DrawText(u8"Привет мир! Hello world!", 25.0f, 25.0f); context->SetBlendMode(Blend::Solid); Add yours symbols to "familychars" and make own "family" in Font.cpp if (family==Font::English) { familychars = L"abcdefghijklmnopqrstuvwxyzабвгдеёжзийклмнопрстуфхцчшщъыьэюя АБВГДЕЁЖЗИКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=!@#$%^&*()_+[]\\{}|;':\",./<>? `~"; } Update: #include <codecvt> std::string WideStringToString(const std::wstring & str) { std::wstring_convert<std::codecvt_utf8<wchar_t>> myconv; return myconv.to_bytes(str); } std::wstring StringToWideString(const std::string & str) { std::wstring_convert<std::codecvt_utf8<wchar_t>> convert; return convert.from_bytes(str); } Gud luck?️! FontSrc.zip
-
Actually it is very simple. As usual, we need to export the functions we are interested in to the Dynamic Library (DLL), and then import them into our C# program. Let's start. C++ DLL The first thing we need is to create a project in C++ for our future DLL, and of course configure it for the Leadwerks engine Configuration for Release Right-click on the project and select Property. In the window that appears, go to the tab "С/C++" / "General". Copy and Paste to a "Additional Include Directories" this: $(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dgCore;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dgNewton;$(LeadwerksHeaderPath)\Libraries\libvorbis\include;$(LeadwerksHeaderPath)\Libraries\libogg\include;$(LeadwerksHeaderPath)\Libraries\openssl\include;$(LeadwerksHeaderPath);$(LeadwerksHeaderPath)\Libraries\VHACD\src\VHACD_Lib\inc;$(LeadwerksHeaderPath)\Libraries\glslang;$(LeadwerksHeaderPath)\Libraries\freetype-2.4.7\include;$(LeadwerksHeaderPath)\Libraries\OpenAL\include;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dMath;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dgTimeTracker;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dContainers;$(LeadwerksHeaderPath)\Libraries\NewtonDynamics\sdk\dCustomJoints;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\RecastDemo\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\DetourCrowd\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\DetourTileCache\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\DebugUtils\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\Recast\Include;$(LeadwerksHeaderPath)\Libraries\RecastNavigation\Detour\Include;$(LeadwerksHeaderPath)\Libraries\tolua++-1.0.93\include;$(LeadwerksHeaderPath)\Libraries/lua-5.1.4;$(LeadwerksHeaderPath)\Libraries/glew-1.6.0/include/GL;$(LeadwerksHeaderPath)\Libraries\glew-1.6.0\include;$(LeadwerksHeaderPath)\Libraries\enet-1.3.1\include;$(LeadwerksHeaderPath)\Libraries\zlib-1.2.5;$(LeadwerksHeaderPath)\Libraries\freetype-2.4.3\include;%(AdditionalIncludeDirectories) Go to "Preprocessor", in "Preprocessor Definitions" copy and paste this: WIN32;NDEBUG;LEADWERKS_EXPORTS;_WINDOWS;_USRDLL;PSAPI_VERSION=1;__STEAM__;_CUSTOM_JOINTS_STATIC_LIB;FT2_BUILD_LIBRARY;LEADWERKS_3_1;DG_DISABLE_ASSERT;WINDOWS;OS_WINDOWS;OPENGL;PLATFORM_WINDOWS;_WIN_32_VER;_NEWTON_USE_LIB;PTW32_STATIC_LIB;PTW32_BUILD;_NEWTON_STATIC_LIB;_LIB;DG_USE_NORMAL_PRIORITY_THREAD;GLEW_STATIC;_STATICLIB;%(PreprocessorDefinitions) Go to "Code Generation". Set these values: Enable Minimal Rebuild = Yes(/Gm) Runtime Library = Multi-threaded (/MT) Enable Function-Level-Linking = Yes (/Gy) Go to "Precompiled Header" and set: "Not Using Precompiled Headers" Now go to the "Linker" / "General" tab, then in "Additional Library Directories" copy and paste this: $(LeadwerksLibPath)\Windows\x86;$(LeadwerksLibPath)\Windows\x86\Release;C:/Leadwerks\Engine\Source\Libraries\OpenAL/libs/Win32/EFX-Util_MT;C:/Leadwerks\Engine\Source\Libraries\OpenAL/libs/Win32;%(AdditionalLibraryDirectories) Go to "Input", in "Additional Dependencies" copy and paste this: newton.lib;dContainers.lib;dCustomJoints.lib;libcryptoMT.lib;libsslMT.lib;Rpcrt4.lib;crypt32.lib;libcurl.lib;Leadwerks.lib;msimg32.lib;lua51.lib;steam_api.lib;OpenAL32.lib;ws2_32.lib;libovr.lib;newton.lib;dContainers.lib;dCustomJoints.lib;OpenGL32.lib;Glu32.lib;winmm.lib;Psapi.lib;%(AdditionalDependencies) Propery Sheet Right-click on the project and select "Add \ New Item \ Property Sheets \ Property Sheet" click Add button. Open it and paste this text: Warning! Keep in mind that the paths may be different, check them out. <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ImportGroup Label="PropertySheets" /> <PropertyGroup Label="UserMacros"> <LeadwerksHeaderPath>C:/Program Files (x86)/Steam/steamapps/common/Leadwerks\Include</LeadwerksHeaderPath> <LeadwerksLibPath>C:/Program Files (x86)/Steam/steamapps/common/Leadwerks\Library</LeadwerksLibPath> </PropertyGroup> <PropertyGroup /> <ItemDefinitionGroup /> <ItemGroup> <BuildMacro Include="LeadwerksHeaderPath"> <Value>$(LeadwerksHeaderPath)</Value> <EnvironmentVariable>true</EnvironmentVariable> </BuildMacro> <BuildMacro Include="LeadwerksLibPath"> <Value>$(LeadwerksLibPath)</Value> <EnvironmentVariable>true</EnvironmentVariable> </BuildMacro> </ItemGroup> </Project> If all the paths match, you can import it into the project. Go to menu "VIEW / Other Windows / Property Manager" Click button "Add Existing Property Sheet". On this with the project settings everything =). Now we can go directly to the code. This is part of the functions required for a simple program; you can add other functions yourself. // Leadwerks.cpp : Defines the exported functions for the DLL application. // #include "Leadwerks.h" using namespace Leadwerks; #define EXPORT extern "C" __declspec(dllexport) // ---------------------------------------------------------------------------------- // SYSTEM // ---------------------------------------------------------------------------------- EXPORT void LE_System_Initialize() { System::Initialize(); } EXPORT void LE_System_Shutdown() { System::Shutdown(); } EXPORT void LE_System_SetAppName(const char* name) { System::AppName = name; } EXPORT void LE_System_SetAppPath(const char* path) { System::AppPath = path; } // ---------------------------------------------------------------------------------- // WINDOW // ---------------------------------------------------------------------------------- EXPORT Window* LE_Window_Create(HWND hwnd) { return Window::Create(hwnd); } EXPORT void LE_Window_Free(Window* window) { delete window; } EXPORT bool LE_Window_Closed(Window* window) { return window->Closed(); } EXPORT bool LE_Window_KeyHit(Window* window, int keycode) { return window->KeyHit(keycode); } // ---------------------------------------------------------------------------------- // CONTEXT // ---------------------------------------------------------------------------------- EXPORT Context* LE_Context_Create(Window* window) { return Context::Create(window); } EXPORT void LE_Context_Free(Context* context) { delete context; } EXPORT void LE_Context_Sync(Context* context, bool sync) { context->Sync(sync); } // ---------------------------------------------------------------------------------- // WORLD // ---------------------------------------------------------------------------------- EXPORT World* LE_World_Create() { return World::Create(); } EXPORT void LE_World_Free(World* world) { delete world; } EXPORT void LE_World_Update(World* world) { world->Update(); } EXPORT void LE_World_Render(World* world) { world->Render(); } // ---------------------------------------------------------------------------------- // CAMERA // ---------------------------------------------------------------------------------- EXPORT Camera* LE_Camera_Create() { return Camera::Create(); } EXPORT void LE_Camera_SetClearColor(Camera* camera, float r, float g, float b, float a) { camera->SetClearColor(r, g, b, a); } Press "Ctrl+Shift+B" C# After a successful build, let's move on to C#. Let's create a new project "Windows Forms App" Here is the program code for an example: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace LETest { public partial class Form1 : Form { const string LEDLL = "Leadwerks.dll"; [DllImport(LEDLL)] public static extern void LE_System_Initialize(); [DllImport(LEDLL)] public static extern void LE_System_Shutdown(); [DllImport(LEDLL)] public static extern void LE_System_SetAppName(String name); [DllImport(LEDLL)] public static extern void LE_System_SetAppPath(String path); IntPtr window; [DllImport(LEDLL)] public static extern IntPtr LE_Window_Create(IntPtr hwnd); [DllImport(LEDLL)] public static extern void LE_Window_Free(IntPtr window); IntPtr context; [DllImport(LEDLL)] public static extern IntPtr LE_Context_Create(IntPtr window); [DllImport(LEDLL)] public static extern void LE_Context_Free(IntPtr context); [DllImport(LEDLL)] public static extern void LE_Context_Sync(IntPtr context, bool sync); IntPtr world; [DllImport(LEDLL)] public static extern IntPtr LE_World_Create(); [DllImport(LEDLL)] public static extern void LE_World_Free(IntPtr world); [DllImport(LEDLL)] public static extern void LE_World_Update(IntPtr world); [DllImport(LEDLL)] public static extern void LE_World_Render(IntPtr world); IntPtr camera; [DllImport(LEDLL)] public static extern IntPtr LE_Camera_Create(); [DllImport(LEDLL)] public static extern void LE_Camera_SetClearColor(IntPtr camera, float r, float g, float b, float a); Thread loopThread; bool isAppWork; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { isAppWork = true; loopThread = new Thread(EngineUpdate); loopThread.IsBackground = true; loopThread.Start(); } private void EngineUpdate() { //Initialize LE_System_Initialize(); window = LE_Window_Create(panel1.Handle); context = LE_Context_Create(window); world = LE_World_Create(); camera = LE_Camera_Create(); LE_Camera_SetClearColor(camera, 0.0f, 0.0f, 1.0f, 1.0f); //Main loop while (isAppWork) { LE_World_Update(world); LE_World_Render(world); LE_Context_Sync(context, true); } //Free LE_World_Free(world); LE_Context_Free(context); LE_Window_Free(window); LE_System_Shutdown(); } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { //isAppWork = false; loopThread.Abort(); loopThread = null; } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { isAppWork = false; } } } Copy you'r "DLL" to bin folder. UPDATE: Copy these file from you'r leadwekrs project to c# bin folder: Shaders folder dContainers.dll dContainers_d.dll dCustomJoints.dll dCustomJoints_d.dll libcurl.dll lua51.dll newton.dll newton_d.dll openvr_api.dll steam_api.dll
-
Hi! It has been a while. Here's an update on my networking library EvayrNet which is available for C++ users of Leadwerks: While implementing it into the test project in Leadwerks, I saw that the use case had some flaws which made it really hard to debug what's going on. I figured that I should be spending some time on fixing some flaws. After a few weeks I came up with the following upgrades: Debugging class Simulation mode More debugging information available Here's some detailed explanation: Debugging class I was using a lot of "printf" before which made it hard to: Find out where it's being called Disable whenever I don't need it anymore This is when I decided to make a debugging class. I replaced the printf with a custom Print function which allows you to do the same as before - except you can disable any kind of printing whenever you want (like during release builds). I also realized that capturing data is a pretty cool feature to have, which makes it easier to visualize the data you want to debug. For that I created a SaveText function which accepts a string and a filename as arguments so you can separate data like "Pings per interval", "Bytes sent per second", etc. Here is an example what you can do with it: Simulation mode This is an interesting one that I just had to implement. A connection cannot always perfect, and it can be hard to always expect a good outcome (while you might not notice it because of your tests on localhost). This is why I introduced the manipulation of the following networking stats: Minimum latency (in ms) Random latency (in ms) Packet drop percentage Packet duplication percentage It's also very easy to activate and deactivate. All you have to do is NetworkManager::StartSimulation(...) and StopSimulation(). Here is it in the command line when you activate it: More debugging information available At last I added more ways to debug the network statistics to really see what is going on. Are you not receiving data anymore? Are you flooding the network? The following information is now ready to be displayed: Newest ping (either to the server or from a specific client) Average ping (this as well^) Incoming amount of packets per second Outgoing amount of packets per second Packets per second lost Incoming data per second (in bytes) Outgoing data per second (in bytes) Current active connections (primarily for the server) That's... quite a lot more than before! Previously you could only see if you're connected and if you're the server. I hope this information will make it easier for users to visualize traffic and debug any problems they might be having. And of course, you can mix this up with the debugging class to make cool graphs like above! Final words I'm still planning on making some extra information, like on specifics to see what kind of message is creating the amount of bytes send/received. This should help debugging even more. Other than that there are still some enhancements I'd like to put in such as encryption. These can be seen here. I hope you liked this blog post! Next time I will probably be showing how to implement EvayrNet into your C++ Leadwerks project, so you can toy around with it.
-
I have been working on a simple laser deflection game that dynamically creates geometry-based beams that automatically calculate deflection, bounce angles, and allow the player to rotate deflectors to guide the beam to the goal. As I familiarize myself with the Leadwerks API, I thought I might share my experiences and code examples here if anyone is interested. Please let me know if this sounds interesting and I will continue.
- 3 comments
-
- 18
-
-
Hello everyone, So with my business software released, i'm moving back into game design. I'm putting my castle game to the side for, what I feel, a easier game to execute. With the majority of mechanics planned out, I'll begin to design the first few levels. I think i'm also going to video record a developer diary to keep people informed on the status of the development of the game. I am looking for some artists, especially 3D modeling with animation skills as well as texturing artists. anyone interested can PM me.
-
A few weeks ago i discussed my business software to a friend of mine. This friend owns an insurance business and expressed to me that my business software as a good potential, it just has to be put in front of the right people. With him insuring businesses, we agreed that he is in a better position to put the software in front of people who would benefit from it. Because of this, I have decided to make that my current priority when it comes to time set aside for software developing. This will help me fund my game project. About my business software: The software asks the user to determine what positions are needed, and how many shifts are needed for that position each day. On each shift the user puts the start time, stop time, and the number of employees assigned to that position are needed for the shift. next the manager populates a list of employees which include their name, position, max hours, days and time available for each day. all the shift and employee information is saved between uses. One all data has been inputted, the manager will press "generate Schedule" any time the manager wants a new random schedule. manual editing is still available. What this does is allow the manager, each week, to create a new random schedule at the click of a button. The only thing i have left to do now is to implement some encryption algorithms for the data and create the installer. Once available, would anyone like a demo of this software? It is designed for any place of business that has part time employees such as retail. It isn't designed for places of business who's shifts carry past midnight.
-
In follow up to Andy, I also want to do a little blog on my experience with Leadwerks 3 so far. The first week after purchasing I didn't have much time to spend on it. Luckily after that week, there was quite some time left for the start of a new adventure. Lua In contrary to Leadwerks 2.3, I now want to lay most of my focus on C++. That is kind of Ironic, since a lot more people are interested in Lua now that Leadwerks 3 has arrived. It is not that surprising since the advantages of using Lua have improved quite a lot. Ofcourse we have debugging and stepping through Lua scripts which is a huge gain. And the other thing is the flowgraph editor. Rick has provided me an extra motivation boost to try out the flowgraph editor, with his cool scripts. (Of which the volume trigger is my personal favorite ). I tried to create a little script myself and within minutes, real simple gameplay interaction was set up.(Gravity activator) Something that was a lot harder to do with Leadwerks 2.3. C++ and Shaders For my own portfolio and skill set I started with C++ and the LE3 API. Just like with LE2, the API is very easy to use. Chris and Josh have done an excellent job on describing commands for both C++ and Lua. Pretty much every command comes with a working example. In the future I also hope to learn more about shaders. Shadmar has allready created severall of them, which you should check out. Tutorials A good way to teach yourself something, is to try teaching it to others. Thats the motto I have been going with since I have started creating tutorials for Leadwerks 2.3. The road is wide open again with the arrival of Leadwerks 3. The first 5 video tutorials for using C++ with LE3 are uploaded on youtube and more are on their way. Eventually I hope to reach a point were we will create a game that uses a combination of C++ and Lua. For a complete overview of tutorials, have a look here: http://www.leadwerks...-lua-tutorials/ Future updates There are plenty of things that can be improved with Leadwerks 3. Thats a fact. I am not talking about the deferred rendering with dynamic shadows. It is really the little things that count. I am more than confident that these issues will be dealt with in the near future. As soon as Josh is back from the GDC, there will probably be updates every other day. For now, I am really happy with my purchase of Leadwerks 3. I am looking forward to all the cool stuff that this community is going to create. Jorn
-
I just finished uploading the framework so that some friends can help out with the content and scene. I already have the overall theme and game put together, I just have to make them, and in this case get help with converting models, creating materials, and so forth. The framework is going extremely well. I've been working on it more than I should be to be honest but I did spend most of the day on the important matters. Plus it's Sunday for crying out loud. Anyways, I've changed several things about the framework. Here is a brief overview: Configuration is now available in the engine. This provided the gateway to the rest of the features I had plan for the framework, such as key/mouse binds, and generic configuration information for both the "engine" and "game." The key/mouse bind configuration file looks similar to: forward=w backward=s left=a right=d jump=space leap=lshift+space crouch=c | toggle fire=leftmouse aim=rightmouse grenade_toss=middlemouse | release grenade_cook=middlemouse debugphysics=f6 | toggle use=e It's fairly straight forward but the configuration file can contain basic instructions, such as "release" and "toggle." The configuration class itself provides the rest of the support. Examine the following, which is now accessible to Lua: if input:hit("leftmouse") == 1 then -- fire logic end if input:hit(input.get("use")) == 1 then -- use logic end if input:toggle("crouch") == 1 then -- do crouch end The engine configuration is managed with: config.get("resx") The game configuration is managed with: game.get("difficulty") One last feature I've added is a way to change between behaviors. These behaviors are strictly Lua, a hybrid, and strictly C/C++/C#/BlitzMax. If you provide the file "engine.lua" into the root of the project, the framework will only execute that LUA file after creating the graphics context and managers (config, input, game, etc.). The "hybrid" is a mix of the two. The framework calls upon specific Lua files at specific times. They could be looked at like "hooks." The Lua files are located at: "/scripts/dice" Examples are "update.lua" and "flip.lua." The framework also now handles scenes. It allows Leadwerks to process the scenes at the moment but then each entity in the scene to turn into an actor by the framework. This way you can get any actor: local actor = engine:getactor("myEditorAddedActorName") actor:translate(vec3(0,0,0)) actor:setkey("health", 20) Actors have their own Lua files and due to the structure described above we should be able to swap Lua files on the fly. The plan I will be attempting is similar to the following: local actor = engine:getactor("myactor") actor:setscript("newscript.lua") actor:runscript() I assume it will work, but who knows. Since per entity/actor scripts work the flexibility with the framework is fairly polished. I'm starting on default controller mechanics, soon to get into third person characters, and so on. Once my buddies can help me out I'll have more to test mechanics. Everyone should also check out Scarlet Thread Studios work, it looks to me like an RTS/TPS style framework, similar to Diablo series. Slight modifications can turn that into an RTS, FPS/RTS, and so on. Same with Pixel Perfect's engine Neutrino, which utilizes EKI One, and is turning out fantastic. I've bugged him to lease it but he isn't budging. Sorry everyone, lol. Just playing, Pixel. MG, always awesome work. Thanks for joining up to help with the content. Macklebee, hoping you'll come aboard and help me out with Lua. I'm really not in the mood to fully learn Lua at the moment. I plan to stick with hard-coded mechanics, lol. Read the above, it explains how to force the framework to let Lua control the main loop. Figured that would be your expertise. Awesome hangout session. I never planned to make it, thought it was out of my schedule, so it was kind of unexpected, lol. I had to register with Google+ and everything. It was fun, meant to talk about more, and to everyone else instead of just Josh, but I had to go AFK; turned out to be too long. I hope I'm invited to the next hangout but I don't have a camera. Thanks for reading.
-
Leadwerks 2.5x + DarkBasic's Dark AI [Part 3]
Marleys Ghost posted a blog entry in Marleys Ghost's Blog
I thought I'd spend a little more time on this integration, using the laptop whilst watching the Olympics. The next phase was to automate the routines for NPC update and compacting the code routines. Ultimately, the idea is that after the scene is loaded, it will be parsed and flagged Dark AI objects, NPC's, Paths and Zones will be set up automatically via information taken from the relative lua scripts. Paths and Zones will be later, collision objects are done, so I was working on how to go about NPC's using this method. As some may have noticed I am using the FPSC Model Pack 53 characters. I converted about 5 or 6 characters to Leadwerks format and all the weapons for this exercise. I will eventually convert them all but I hand "prune" the bones of the rig and that takes longer than I want to spend at any one time on this side project. The FSM is still very basic but the first goal was to derive a current Animation state with each of those states having sub-FSM's to dictate the current behaviour based on the previous state and internal and external inputs. The framework is in place for the animation state which is derived by performing Boolean logic on returned values and strings from the Dark AI runtime and some stubs are in place nested inside for some basic behavioural sub-FSM's. First though I want to able to set up the scene and then place all the Dark AI objects, NPC's, Paths and Zones directly in the editor and then use the load routines to set all that up in an automagic way inside the application. The collisional objects were easy and are done with some simple tick box flagging functionality added to their property scripts. The NPC's a little more in depth , due mainly to the large amount of usable settings! lol. But I wanted to be able to place the model and then assign it some basic attributes to be read on load, this included what weapon to attach. So that was the basic NPC property script setup (wasn't that painful anyway). So using the test scene from Leadwerks 2.5x + DarkBasic's Dark AI [Part 2], and placing a few enemies and some friendly's to look after the player, this is the result of the current stage reached in the integration. -
Actors are now in place and working correctly. I actually made a small mistake yesterday and made meshes part of the actor. This incorrect because a mesh should extend an object and be it's own actor type (i.e. class EMesh : public EActor{};). This is fixed and therefore the actor creation has slightly changed, but the shortcut (EActor::Add("ActorName", "mesh.gmf")) still works as expected. Timer I need to start adding actors so there are multiple in-game types of entities. First, I need a timer, because I just love timers. Even if it was just a single timer I'd be happy. They are extremely useful. ETimer::SetTimer(1.0f, TRUE, FUNCTION); A better example: // .. function to execute by the timer void TickSpecial(void) { printf("Tick Special\n"); } // .. looping timer Timers.SetTimer(1.0f, TRUE, TickSpecial); // .. void EGame::Unload(void) { Timers.StopTimers(); } There is still more I want to add to the timers but this is a good start. I'd also like to move them to threads to support multiple timer tasks. Cameras Cameras are interesting and always seems to be a topic around Werkspace. Cameras in this engine/framework are Actors and this should be interesting. Since cameras will extend actors I already have translation and rotation. // .. automatically done but an example[/i] - 0 is the framework camera layer to fetch Cameras.Add("Camera_1", 0); Cameras.Rotate("Camera_1", vec4(0.0f, 1.0f, 0.0f)); Next was to just add helper functions that are provided by LE, such as zoom, project, unproject, etc: // zoom with interp Cameras.SetZoom("Camera_1", 1.0f, 1.0f); // project vector project = Cameras.Project(vec3(MouseX(), MouseY(), 1000.0f); I shouldn't have a problem from here having any type of camera "mode" I would like to have. I will begin working on these once I get to detailed mechanics. Cameras are now working, along with timers, and now it's time to take a break. I may keep pushing forward but shortly I have to return to my other projects until I have time to return. Until next time, and thanks for reading.
-
There are several here who already know me, but for those who don't, my name is Paul Thomas. I've been programming for a long time now; I started when I was 16 (I'm now 30) with HTML, CSS, Javascript, and CGI/Perl. Hell back then there wasn't a lot of people who even used the internet at home, lol. At least in my area, I'm sure others, especially in California, were a lot further ahead at that time in terms of technology and the interest in the technology. Through the years I've learned multiple languages from strictly web-based to software based. My interest in computers when I was 16 was to make a game, but at the time I thought it would have been much easier to prototype the whole idea in a web-based browser game. I had completed the browser game, which was the original "Eternal Crisis," and worked nicely. My plan was to update the entire web-based system, polish everything, and officially advertise (I had invited friends to play the game). That's when I learned about a "fried hard drive" and eventually learned about "backup" and how to install a hard drive. Those whom already know me, know what Eternal Crisis is, and my Blogger shows some of the history on that project. I had taken that project, along with another, over to Unreal Engine 3 because it best suit the project. Along the years of learning that engine I was using LE for prototyping ideas and so forth. While I'm not working on my own engine (temporarily titled "3D Dice"), FPS/RPG framework for UE3, or R.A.T.S., I work on my framework for Leadwerks Engine 2.5. I've never shared this framework before, in fact it always felt like I had to pull it out of a shallow grave each time I added to the framework design and programming. I'm a notebook junkie, I plan out mechanics, structures, and so forth on paper, before going over to digital. Old habbits that die hard I guess. Now I felt like sharing the progress, which isn't a lot, but it's a great start to me. It's a great start to me because it actually runs, lol. The state of the framework isn't even close to the final planned and written design, but progress is progress. Always move forward until it's finished, even if you can only pick that project up once every two weeks (by then I/you should probably take a look at your workload and fix it instead of attempting such project schedules; however this isn't vital to me and rates low on my importance scale), if it's updated then progress is moving forward. This is also harder to work with if you don't plan your software before actually programming (unless it's routine for you with available libraries for shortcuts in development). As most programmers should know, the programmers "update" isn't as glamorous as an artists "update" as it's not about visual stimulation but overall program/software flow. In the case of my LE framework (obviously untitled) it's all about providing mechanics and how that is achieved is important especially in the case of LUA access and how everything works together; from configuration/data management, to input binding, and all the way down to AI. Until occupied again by my other tasks I will eventually share the entire framework structure and I will always be showing examples of syntax; cause that's what programmers do. Just to clear the obvious questions that may come from the community: Q) Do I plan to give away code, the framework, and be an open source kind of person? A) No, not really. First of all, you would have to wait, to anyone else at the moment the framework is as useful is a partially finished library. How long you would have to wait would depend on how much time I can spend on the framework and in all honestly it's not much at all (read above, and actually read). Q) Do I plan to sell or lease the framework? A) No, don't think so. I even think that's against Leadwerks terms since it could be deemed an "FPS Creator," which is definitely in the terms. Q) Is my framework really that great? A) Nah, I mostly ramble, and I'm actually writing this to share with long time friends here at Leadwerks. Some won't even visit anywhere else to communicate because they are so used to using Leadwerks for that; it is indeed where we all met. Q) Who are my friends? A) I have none, it was a lie. Now, about this framework. This "framework" isn't the same, exactly, as the framework that comes with LE. The framework that comes with LE handles some dirty work for you when it comes to creating the worlds, cameras for those worlds, shader effects, and helper functions. The framework I'm designing is technically similar to a game engine. I personally consider Leadwerks Engine as a rendering API with physics and this framework uses that rendering API and provides mechanics. The mechanics the framework provides is what makes up the detail of the framework. INI SQL Application Graphics Game The above are considered "managers" in that they only handle what they should be managing. The "INI" only works with INI files, such as a configuration manager. The "SQL" only works with SQLite3, providing helper functions for easier SQL management, and so forth. There are more planned managers than the above, but these are what are completed in terms of programming. The only real interesting portion to discuss about the framework is within the "Game" manager itself. The game manager provides two special classes/managers; "Object" and "Actor." Actor inherits everything about an Object. What defines an Object is a game entity that is never interacted with by the player. Objects are useful as it can be used for multiple purposes without consuming a lot of resources for each "component" or "plugin" I'd like to add onto the framework. For example, a Timer would be an Object. You don't interact with a Timer in a game, but there is a timer running in the background. Example: class ETimer : public EObject { public: ETimer(void); virtual ~ETimer(void); void Initialize(void); void SetTimer(float StartTime); void StopTimer(); }; While working with the framework you would do something similar to: // EGame::EObject // EGame::Objects EObject Objects; // .. ETimer Timer; Objects.Add("Timer", Timer); // .. ETimer Timer = Objects.Get("Timer"); Timer.StartTimer(0.0); // .. Timer.StopTimer(); // inherited by EObject Timer.Unload(); An Actor inherits everything that defines an Object. The difference between the two is that an Actor is something that a player could see, hear, interact with, or can move, rotate, and so forth. If you can see how this is all going, everything starts extending the Actor, such as the character, weapon, or items. Here are some examples of working with Actors in the framework: // EGame::Actors Actors.Add("oilbarrel", "oilbarrel.gmf"); // .. Actors.Rotate("oilbarrel", vec3(10.0f, 0.0f, 10.0f)); // .. Actors.Translate("oilbarrel", vec3(10.0f, 0.0f, 0.0f)); // .. EActor barrel = Actors.Get("oilbarrel"); // rotate with interpolation barrel.Rotate(vec3(1.0f), 0.1f); barrel.Translate(vec3(0.0f, 1.0f, 0.0f), 0.1f); // .. EActor Barrel; // new name Barrel.Name = "barrel01"; // new mesh Barrel.LoadMesh("oildrum.gmf"); Actors.Edit("oilbarrel", Barrel); A quick overview of an Actor: //EActor ID Name Tag Parent Location Rotation Mesh Sounds Particles Each Actor can also have children which are also Actors. This provides another version of parent/child relationships but also provides additional benefits which will be discussed in later blogs. The ID and Name variables are provided by Object and the Object provides more variables, but is listed for importance. When creating an Actor it is automatically tagged for unique identification. In the above example "oilbarrel" is actually stored as "oilbarrel_0" and simply incremented for each Actor that is created. This is identified by the Actors "Tag". The "Name" variable is a forced name, therefore searching for an Actor by the name, with more than one Actor having the same name, the first result is returned. Actors will be automatically created properly for each entity in a scene. The framework will be using a custom scene manager and handles initial Actor creation. Programmers/Scripters can then add to the Actors list with C++ or LUA like the above examples. class MyGame : public EGame { public: void Load(void); void Update(float DeltaTime); // .. void MyGame::Load(void) { Actors.Add("custom_actor", "mymesh.gmf", "force_tag_name"); EActor actor = Actors.Get("custom_actor"); EActor actorCopy = Actors.GetTag("force_tag_name"); actor.Translate(vec3(0.0f)); } void MyGame::Update(float DeltaTime) { EActor actor = Actors.Get("custom_actor"); // interp move with speed and threshold option actor.Move(vec3(10.0f), 1.0f, 1.0f, 0.35f); } }; In upcoming blogs, when I do get the time, I'll post up some videos. Those selected for the invite only alpha testing will get their information on how to use the framework. Friends of mine that didn't get an invite and are interested in alpha testing please private message me; I most likely didn't send you the information because I figured you were busy with your own project(s). Well, out of time. Thanks for reading.
-
I am currently working with EKI One, working through its format and structure, getting to grips with the lua behavioural scripting side and trying to brush up on my c++ skills, actually brushing up on my c++ skills is an overstatement as I don't actually have any to brush up! .. lol, I have also been going through my Blitzmax code and trying to compile all the game mechanic functions and methods into a single .mod, trying to keep things tidy. Whilst going through my **** drive, I mean, my well organised storage drive, I found some DarkBasic/DarkGDK stuff, which included the Dark AI library. I have never really used DarkGDK, although I did start out a few years back with DarkBasic. So I wondered (as you do), could it be utilised in LE2.5? Having never used Dark AI (which came in a bundle, that I have never used either, but it was a bargain lol) I knew that R.T.F.M. would be in order. If it relied solely on DBobjects and inherent DarkBasic functionality then probably not. I spent half an hour looking through the commands, and an hour later had it working in an LE context. So to answer the question, it seems you can. I'd have upped a better quality image but I am told "You can upload up to 7.74K of files " Its all a work in progress, and probably will remain that way as its certainly nowhere near the solution that EKI One is, but as an exercise it wasn't a bad use of a few hours. I will post a demo in the showcase later for those interested. EDIT : Demo posted here : http://www.leadwerks...-ai/#entry45564