Jump to content

reepblue

Developers
  • Posts

    2,509
  • Joined

  • Last visited

Blog Entries posted by reepblue

  1. reepblue
    Back in July, I set out to create a base for creating a version of Cyclone with Ultra Engine. I've gotten a lot of feedback from the release and started to conceptualize ideas on how to prevent or limit on making the same mistakes.
    One major goal I had was to compartmentalize the system. This should just exist on it's own and game code should just lay on top of this. This was possible thanks to the event system which cuts down on the number of pointers that need to be passed. Components can just listen to events from the program and act accordingly. 
    Lastly, I've made the decision to open source this on GitHub as this is the kind of thing that users think about when it's too late. People just want to work on their cool games and not worry about the window or if a setting will be applied correctly. 
    So here are 6 reasons/features of the Ultra Game System! 
    1. Window Management
    One of the top complaints I got with Cyclone is that users could not resize the window in-game. This was an engine limitation of Leadwerks This is now possible in Ultra Engine but it needs some elbow grease to make it work. The Game System does this for you. All you need to do is call GetProgram()->ResizeApp() and the graphics window class will recreate the window and framebuffer for you. By default, pressing F11 will swap between windowed mode and full screen. 
    2. Out-Of-Game Settings
    The Game System has an isolated window application for changing settings. The idea behind this is so there's a way to modify any window or graphics setting if an in-game one is unavailable. You probably shouldn't ship with just this solution as many people prefer to edit any setting within the game itself. 
    This is accessible by using the -settings flag with the program.
    3. Action Based Input System
    Years of research into this paid off when the Input Update for Cyclone released. The knowledge was carried over for the Game System. Only this time it's more dynamic! 
    Simply set the controls in your main.cpp file:
    // Define default controls. static void InstallControls(shared_ptr<GameController> controller) { if (controller == NULL) return; // Program actions controller->SetAction("Pause", BUTTON_KEY_ESCAPE); controller->SetAction("Terminate", BUTTON_KEY_END); controller->SetAction("ConsoleApp", BUTTON_KEY_F1); controller->SetAction("Fullscreen", BUTTON_KEY_F11); controller->SetAction("Screenshot", BUTTON_KEY_F2); controller->SetAction("Quick Save", BUTTON_KEY_F5); controller->SetAction("Quick Load", BUTTON_KEY_F6); // Camera ButtonAxis moveaxis = { BUTTON_KEY_W, BUTTON_KEY_S, BUTTON_KEY_A, BUTTON_KEY_D }; controller->SetAction("Movement", moveaxis, "InGameControls"); controller->SetAction("Camera", AXIS_MOUSE, "InGameControls"); controller->SetAction("Sprint", BUTTON_KEY_SHIFT, "InGameControls"); controller->SetAction("Crouch", BUTTON_KEY_CONTROL, "InGameControls"); controller->SetAction("Climb", BUTTON_KEY_Q, "InGameControls"); controller->SetAction("Desent", BUTTON_KEY_E, "InGameControls"); controller->SetAction("Jump", BUTTON_KEY_SPACE, "InGameControls"); // Settings controller->SetSetting("Raw Mouse", false); controller->SetSetting("Inverse Mouse", false); controller->SetSetting("Mouse Smoothing", 0.0f); controller->SetSetting("Mouse Look Speed", 1.0f); } Then deriving your components off of the GameObject class, you can use GetInput() for input functionality. 
    virtual void UpdateInput() { // Movement if (allowmovement) { float speed = movespeed / 60.0f; if (GetInput()->Down("Sprint")) { speed *= 10.0f; } else if (GetInput()->Down("Crouch")) { speed *= 0.25f; } if (GetInput()->Down("Climb")) GetEntity()->Translate(0, speed, 0); if (GetInput()->Down("Desent")) GetEntity()->Translate(0, -speed, 0); auto axis = GetInput()->Axis("Movement"); GetEntity()->Move(axis.x * speed, 0, axis.y * speed); } } Best part is the "Controls" tab will reflect whatever you have defined!

    4. User Input via Console
    Having a developer console is essential for developing any game! The Game System has a very simple but flexible console that doesn't need any commands registered beforehand. To define a new command, just poll the EVENT_CONSOLEEXECUTE id in your component's ProcessEvent function.
    virtual void Start() { Listen(EVENT_CONSOLEEXECUTE, GetProgram()); } virtual bool ProcessEvent(const Event& e) { if (e.id == EVENT_CONSOLEEXECUTE) { auto line = e.text.Split(" "); auto cmd = line[0].ToString(); if (line.size() > 1 && !line[1].empty()) { if (cmd == "crosshair") { bool hide = (bool)line[1].ToInt(); hudcamera->SetHidden(!hide); Print(QuoteString(cmd) + " has been set to: " + line[1]); } } } } 5. Sound Managment
    A layer of functionality in the Game System allows for cleaner sound origination and playback. You can store sound variables (files, volume, pitch, range, etc) within a JSON script.
    { "audioProfile": { "file": "Sound/radio_dreamlandloop_mono.wav", "volume": 0.5, "range": 35.0, "loop": true } } Then load the file with the GameSpeaker class.
    auto file = "Sound/Profiles/Radio.json"; shared_ptr<GameSpeaker> speaker = CreateGameSpeaker(file, GetEntity()->GetPosition()); The GameSpeaker has Save/Load functions so the speaker's time and state can be restored. Here's an example of creating and restoring a GameSpeaker.
    virtual bool Load(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const LoadFlags flags) { Print("Loading component " + QuoteWString(name)); if (speaker) { if (!properties["componentspeaker"].is_null()) speaker->Load(properties["componentspeaker"], binstream, scene, flags); } else { auto file = "Sound/Profiles/Radio.json"; speaker = CreateGameSpeaker(file, GetEntity()->GetPosition()); if (!properties["componentspeaker"].is_null()) speaker->Load(properties["componentspeaker"], binstream, scene, flags); } return true; } virtual bool Save(table& properties, shared_ptr<Stream> binstream, shared_ptr<Map> scene, const SaveFlags flags) { properties["componentspeaker"] = {}; if (speaker) speaker->Save(properties["componentspeaker"], binstream, scene, flags); return true; } As of right now, the sound system doesn't support audio filters as I feel that the filter should be applied within a volume (or to the listener) and not the sound/speaker itself. I'm still thinking about how that should work. 
    6. Better Render Layer Managment
    Intergrated today, The Canvas class is a great way to ensure that there is one camera per render layer. I ran into an issue where a component would draw 2D graphics to the framebuffer, but when there were multiple instances of them, multiple cameras were being made and drawing over each other. Using GetCanvas() can prevent this from happening.
    auto canvas = GetCanvas(world, RENDERLAYER_HUD); auto sprite = CreateSprite(world, 1.0f, 1.0f); sprite->SetPosition((float)sz.x / 2, (float)sz.y / 2); RenderToCanvas(sprite, canvas); The Game System will be my main focus until the Ultra Engine version of Cyclone starts development which will use this as a foundation.  I'll also be working on other small things to share so be on the lookout for those! 
  2. reepblue
    For who those who don't know (or remember) Vectronic was my first person puzzler project I was developing from 2013-2016 starting with the Source Engine and then onto Leadwerks. The goal of the project was to create a puzzle game and allow people the assets to make their own puzzles. Although it never saw it's end, you can still play the demo here.
    So what happened? Vectronic was how I pretty much learned a lot about programing, art and game design in general. I kept going back working on it for a month or so and then stop because I would hit a road block with assets, code, performance, or time. I made a lot of cool things too that just never saw the light. Over the years however, I also noticed I don't have patience to work on one game alone for months at a time. I do however like to play around with stuff so I began to think: "What if I had a project for me to tinker with and post it publicly every so often?" I'm more busy than I was in the past so time is more limited. However, I've probably written Vectronic game code over 50 times and I now know the right way of doing such. So what's the plan?
    After some experimentation, I've decided to go ahead and start a new project taking everything I did and learned with Vectronic and making a base template for people can use for their own purposes. My packaged game will be free to download and play (Because it's just me playing around) while the assets and will be posted on the Leadwerks Marketplace for a charge.The package will contain all assets to build your own test chambers, and you'll also get the original source files too! My release game will act as an outlet to my tinkerings and provide as an advertisement to the asset pack.
    With the code, the idea is that everything will be modular and users can easily create their own power balls to do various of things. The power balls can now be applied to any entity under certain conditions so there is no need for a VecBox.lua script anymore. There will be plenty of examples for many to see how things were done. I learn best by references and examples, and having code, art, and maps accessible should help new users into learning game design, or aiding developers in assets to start their ideas.
    I've decided to change the name to Vec-Tec as the asset pack will be in the Vec-Tec Research Center. Also with the new approach, I think it's time to drop the name Ive been using for four years. You can give Vec-Tec a try right now here!  It's very minimal, there is no art, but the foundation is there. VR is now gonna be a thing with this, If you have a VR HMD, you can play any map in Roomscale VR!
    I hope this is a start of a remarkable development. I hope you are interested and see value in this  project. You can find the project page here for the latest updates and releases.
  3. reepblue
    Sorry for the whole week of silence, I ran into quite into a number of issues the previous weeks, but I'm ready to talk about them more, and how I it got resolved.
     
    Newton Physics (Or how Leadwerks uses this physics engine) has a problem when a non-physics object collides with a physics object. You expect that if a entity using the Move() function would use it's own shape to push anything out of the way. What actually happens is that since the entity has no mass, the entity pretends the physical object isn't there, and just clips right through it. This is most likely like this by design, but for Vectronic it was a huge issue.
     
    The advertised way of doing platforms is to use the Joint class so the entity will have mass and interact with other physics objects. However, the Vecboxes need to be heavy, I'm talking about setting the mass to 80 to prevent the object from bouncing like a kickball. Increasing the overall world gravity effects the player, and I want the box to be lighter when it's activated by the 0g Ball, which makes it easier to throw. Problem was that if a box was to go on these platforms with the mass I need them to be, the box would overwhelm the joint, and break it.
     
    Platforms is something I see important to the project as by now (in the earlier puzzles) they are used for demonstration of how the 0g cube works, and are in the majority of maps I have already made. If platforms can't work, I was afraid I was back on the bus of looking for another suitable engine for the game. Luckly Einlander came to the rescue and set me something that worked better than all my other attempts.
     
    Einlander's script moves the object from it's resting point to another position similar to the sliding door script. It also updates it's Physics Position and Omega in a function called in UpdateWorld. The result was exactly what I want minus a slight jitter in the box, but I'll take it. After experimenting with it though, I noticed that if a box was only partially on it, the platform would ignore the box, and go right through it; a similar problem I kept having. My fix was to create a trigger that's spawned with the platform with it's shape to handle this case, and behavior with the 0g cube. It's still not 100% perfect, but it's better than what I had before. The project was now back in gear.
     
    Another concern I was having was questioning "How much fun is my game, or is it just work?". Running around, shooting power balls at boxes so they change their properties sounds like a good concept on paper, demo maps, but imagine a full 2 hour game of just that? I was worried that just focusing on that would get really boring; so boring that it might even make people to stop short and refund the game.
     
    As if it was not clear already, Vectronic is a tribute to Portal as if it wasn't for that game, I would not be here talking to you right now. I started to think about Portal, and I started to look at it from a weird light: How would Portal be if momentum (Flinging) wasn't part of the game? There are five chambers, and the BTS areas that require or focus on using Portals to fling over gaps, and a good chunk of code is dedicated to make sure the momentum multiplies when going through each portal until the player or object hits it's max speed. But imagine if the game didn't have it, you'd have a game where you walk into walls to move mostly boxes around. Again, this might not seem so bad, but imagine playing it for 2+ hours with only the original elements; it will be similar to what I have now with Vectronic.
     
    The issue I think is the lack of Player Mobility. From my observations, people like to use their weapon to get to an extreme height. Join any Team Fortress 2 server and you'll see Soilders and Demomans blast jumping, and Scouts double jumping everywhere. With PUNT had something called Punt jumping which was like Rocket Jumping, but you could only do it on certain materials. From the feedback I got, people were most excited about that. Only issue is that it required skill, and it was never fully taught.
     
    I'm currently experimenting with this bounce jumping idea. Imagine rocket jumping only on certain materials, but all you need is to double tap Space. I've got only one map done as a proof of concept and to introduce the idea to others to see if it worked. Two days ago, I shipped the build early while this was still being figured out to see if it was worth the effort. I got a positive vibe from their feedback, I got told it was fun, although they didn't fully understand how to control it. Regardless, this might be because I was calculating how high the jump should be based on the time you're falling, not the speed, but it shows that it's not perfect yet.
     
    I should have more information about it next week (hopefully!). I want to go back and forth with my pre-alpha testers until it's a fun and fully understandable mechanic.
  4. reepblue
    It's been quite a while since I posted here. All is well, and I now have some time to talk to you on where I've been and what I wish to do in the future.
    I started a job back in August and my enormous amount of free time dwindled to a few hours a day. However, I now have an income to support myself and buy new toys. I wanted to get into the Linux ecosystem and further distance myself from the Windows world. So for about $300, I've built a PC with an i3-6100, 4GB of DDR4 Ram, put it in a Mini-ITX case and ran Linux off an SSD. I love the machine, but there isn't really much I can use it for. I didn't put in a GPU in it due to driver inconsistencies, so everything runs off the integrated GPU, which can run HL2/Portal at 60fps at 720p; neat!

    I always wanted to dabble into programming for 2D. While working on 3D games is fun, working on 2D games can go a lot quicker and require less resources and as a solo developer that sounded dandy being the limited time I have now. I decided to buy a Raspberry Pi, and use SDL as my drawing library. I will not go into full detail, but the base operations are done, they just need the kinks worked out. Anyway, here is a tablet I assembled. 

    I have two of em! I had to buy an element 14 PSU and it was cheaper to buy it with another Pi than stand alone; which in the end worked out as programming on that screen is dreadful. All in all, my last few months have been me doing that. I never really booted my new Linux machine nor my Windows PC.
    About a week ago or so, Josh contacted me about a concept I uploaded privately and how he wants it on the new game page. Although I don't think the prototype is good enough to be up there (it crashes, slow loading, etc), I'm definitely interested on picking it back up, and maybe opening it up as some sort of community project thing. All the code is done, it just needs optimization really. I accidentally deleted my source files but Josh was kind enough to decript the zip file. That got me interested back in that Mini-ITX PC I've built back in September. I have a RX480 in the closet that my neighbor gave me a while back (And thank frick, cause those GPU prices are ridiculous these days), and I didn't put it in any PCs before because of the rumblings of poor drivers on Linux, and I was happy with my 750ti for the most part. But I was thinking, maybe if I upgraded the processor and RAM, and installed windows on it I can make PC that's more powerful and smaller than my current Windows PC. 
    Newegg for better or worse is (or was) having a Intel Sale and I got a i5-7600k for $200. I also picked up another 4GB of ram (cause prices are high on that too) in which bumps the machine from a dual core with 4GB of RAM to a Quad-core with 8GB of RAM! All I really need is a new Windows Licence and I'd like to keep my Linux install so I might buy a fresh SSD, but I think I'll just reuse the current one I have. I really hate that I went full circle back to windows, but as long as I keep using Linux and get more and more comfortable with it, if I have to jump ship, I can comfortably do so. I'm just not ready to abandon Windows completely right now.
    Today, is when I realized that I finally have a machine that can do VR. I decided to look at prices for the Rift and Vive to see if the prices went down as the demand settled. And to my luck (again for better or worse) Oculus was having a sale. I decided to go for it as I really don't have the space for full room VR, but now that Leadwerks is coming out with official VR support, I'd like to finally experiment with it! 
    So my biggest worry is jamming everything in that small case. Yes, I have a modular power supply, and everything is still tight! Really hope I can fit the GPU in there! 
  5. reepblue
    Before we were all locked in our homes, I was working on a lot of code for Leadwerks 4 for a reboot of concept I've made back in 2017. While I decided to shelf it again to wait for the new engine (Because the game will run faster, look better, VR, etc) I'm going to share you some of my core code which is cross compatible between the new and old engine. I'm sharing this as I see most people just using stock example code for window creation and setting saving without knowing how it all works. This is more of a show than tell kind of article, and most things are just copy and paste what's in my code at the moment, but it should give you an idea. In this article, I'll be showing you code for both Leadwerks 4 and 5, but most of this was written and tested in Leadwerks 4. 
     
    Parsing Arguments.
    Leadwerks 4 has a simple command that does this automatically. This makes it so you can give the app flags on how it should startup/behave on startup. 
    System::ParseCommandLine(argc, argv); In Leadwerks 5, you need to store it in your own std::map.
    auto commandline = ParseCommandLine(argc, argv); To be honest, the new way is much better. You see, in Leadwerks 4, System::ParseCommandLine() stored the values in a engine defined std::map which is also associated with System::LoadSettings() and System::SaveSettings(). This will cause unwanted results as flags you may only want to happen only when called in a batch file will save to the cfg file saved by default to the AppData folder in Windows. There is a separate map for both Arguments and Settings, but I think the issue is with if you use System::Set/GetProperty().
    The best option is to keep your arguments and setting separate. In Leadwerks 5, your std::map is yours, so you're already off to a good start.
     
    Actual Settings
    Now we can take in flags/arguments from launch settings, we now need values to be written on the disk. First we need to build the directory in which will act as our data folder for the user. Where this folder should be is up to you, but it should be in the user's directory on Windows, or the user's home directory in Linux. Here's my code for both Leadwerks 4 and 5. 
    First, we need to know the applications name. We use this name value in multiple areas. Leadwerks 4 as a value System::AppName, but in Leadwerks 5, we'll be creating our own variable. We'll also define the settings directory string path here too.
    #ifndef LEADWERKS_5 //std::string AppName = "MyGame"; std::string settingsdir = ""; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void App::SetName(const std::string& pszAppName) { System::AppName = pszAppName; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- std::string App::GetName() { return System::AppName; } #else std::wstring AppName = L"MyGame"; std::wstring settingsdir = L""; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void App::SetName(std::wstring pszAppName) { AppName = pszAppName; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- std::wstring App::GetName() { return AppName; } #endif  
    Now we have a name variable, we can use it to build our settings directory. 
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void BuildSettingsDirectory() { #ifndef LEADWERKS_5 // If settingsdir is not "", we did this already, no need to do it again. if (settingsdir != "") return; #if defined (_WIN32) auto t = FileSystem::GetDocumentsPath() + "/" + App::GetName(); #else auto t = FileSystem::GetAppDataPath() + "/." + String::Lower(App::GetName()); #endif if (FileSystem::GetFileType(t) == 0) { System::Print("Building Settings folder under:" + t); FileSystem::CreateDir(t); } settingsdir = t; #else // If settingsdir is not "", we did this already, no need to do it again. if (settingsdir != L"") return; #if defined (_WIN32) auto t = FolderLocation(FOLDER_DOCUMENTS) + L"/" + AppName; #else auto t = FolderLocation(FOLDER_APPDATA) + L"/." + Lower(AppName); #endif if (FileType(t) == 0) { Print(L"Building Settings folder under:" + t); CreateDir(t); } settingsdir = t; #endif }  
    You'll now have the path to your settings directory stored in settingsdir. You can use this string to store files there easily like below: (Raw copy and paste, ignore my macros)
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void App::TakeScreenShot() { auto framebuffer = Framebuffer::GetCurrent(); if (framebuffer != NULL) { auto screenshot_path = GetSettingsPath() + "/Screenshots"; if (FileSystem::GetFileType(screenshot_path) == 0) { Msg("Building Screenshots folder under:" + screenshot_path); FileSystem::CreateDir(screenshot_path); } framebuffer->Screenshot(screenshot_path + "/" + App::GetName() + "_" + Timing::GetCurrentDate() + ".tga"); } else { Msg("Error: Failed to take screenshot."); } }  
    A long time ago, @Crazycarpet gave me code to parse a text file like a Leadwerks 4 config file or a Leadwerks 4 mat file. I turned that functionality into a class, and instead of having the engine phrase the information, I do this myself. This is a direct copy and paste of my scopes to give you an idea.
    // To Load auto settingspath = settingsdir + "/settings.cfg"; if (FileSystem::GetFileType(settingspath) == 0) { appsettings.fullscreen = false; appsettings.screenwidth = 1280; appsettings.screenheight = 720; appsettings.display = 0; appsettings.antialias = 1; //2 appsettings.lightquality = 1; appsettings.terrainquality = 1; appsettings.texturequality = Texture::GetDetail(); appsettings.waterquality = 1; appsettings.afilter = Texture::GetAnisotropy(); appsettings.verticalsync = false; appsettings.hdrmode = true; } else { auto config = LoadConfig(settingspath); appsettings.fullscreen = config->GetBoolValue("fullscreen", false); appsettings.screenwidth = config->GetIntValue("screenwidth", 800); appsettings.screenheight = config->GetIntValue("screenheight", 600); appsettings.display = config->GetIntValue("display", 0); appsettings.antialias = config->GetIntValue("antialias", 1); appsettings.lightquality = config->GetIntValue("lightquality", 1); appsettings.terrainquality = config->GetIntValue("terrainquality", 1); appsettings.texturequality = config->GetIntValue("texturequality", Texture::GetDetail()); appsettings.waterquality = config->GetIntValue("waterquality", 1); appsettings.afilter = config->GetIntValue("afilter", Texture::GetAnisotropy()); appsettings.verticalsync = config->GetBoolValue("verticalsync", false); appsettings.hdrmode = config->GetBoolValue("hdrmode", true); config = NULL; } //---------------------// // To Save auto config = CreateConfig(settingspath); config->WriteKeyValue("fullscreen", to_string(appsettings.fullscreen)); config->WriteKeyValue("screenwidth", to_string(appsettings.screenwidth)); config->WriteKeyValue("screenheight", to_string(appsettings.screenheight)); config->WriteKeyValue("display", to_string(appsettings.display)); config->WriteKeyValue("antialias", to_string(appsettings.antialias)); config->WriteKeyValue("lightquality", to_string(appsettings.lightquality)); config->WriteKeyValue("terrainquality", to_string(appsettings.terrainquality)); config->WriteKeyValue("texturequality", to_string(appsettings.texturequality)); config->WriteKeyValue("waterquality", to_string(appsettings.waterquality)); config->WriteKeyValue("afilter", to_string(appsettings.afilter)); config->WriteKeyValue("tfilter", to_string(appsettings.tfilter)); config->WriteKeyValue("verticalsync", to_string(appsettings.verticalsync)); config->WriteKeyValue("hdrmode", to_string(appsettings.hdrmode)); bool b = config->WriteOut(); config = NULL; return b;  
    Settings vs Arguments
    Since both settings and arguments are different tables, we must have a way to check if ether the setting or argument is enabled. The best way I chose to do this is to make bool functions that check if ether is true.
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool App::IsFullScreen() { if (appsettings.fullscreen == true || GetArgument("fullscreen") == "1") return true; return appsettings.fullscreen; }  
    Building The Window (Finally)
    Now it's time to actually build the window with the information we have via settings file, or augments passed in a command line. 
    For Leadwerks 4, I do something like this:
    Leadwerks::Window* CreateEngineWindow(std::string pszTileName, Leadwerks::Window* parent) { // Are we fullscreen? const bool fullscreenmode = App::IsFullScreen(); // Set the name of the app the same as the window. App::SetName(pszTileName); // Load Settings. App::LoadSettings(); //Load any zip files in main directory Leadwerks::Directory* dir = Leadwerks::FileSystem::LoadDir("."); if (dir) { for (std::size_t i = 0; i < dir->files.size(); i++) { std::string file = dir->files[i]; std::string ext = Leadwerks::String::Lower(Leadwerks::FileSystem::ExtractExt(file)); if (ext == "zip" || ext == "pak") { Leadwerks::Package::Load(file, KEY); } } delete dir; } // Create a window Leadwerks::Window* window; std::string windowtitle = App::GetName(); if (IsDebug()) windowtitle = windowtitle + " [Debug]"; if (fullscreenmode) { iVec2 gfx = System::GetGraphicsMode(Leadwerks::System::CountGraphicsModes() - 1); window = Leadwerks::Window::Create(windowtitle, 0, 0, gfx.x, gfx.y, Leadwerks::Window::Fullscreen, parent); } else { window = Leadwerks::Window::Create(windowtitle, 0, 0, App::GetScreenWidth(), App::GetScreenHeight(), Leadwerks::Window::Titlebar | Leadwerks::Window::Center, parent); } EngineWindow::current = window; window->Activate(); return window; }  
    This is my current Leadwerks 5 code:
    shared_ptr<Window> CreateEngineWindow(std::wstring pszTileName, shared_ptr<Window> parent) { //Get the primary display auto display = App::GetDisplay(); // Are we fullscreen? const bool fullscreenmode = App::IsFullScreen(); // Set the name of the app the same as the window. App::SetName(pszTileName); // Load Settings. App::LoadSettings(); // Create a window shared_ptr<Window> window; std::wstring windowtitle = App::GetName(); if (IsDebug()) windowtitle = windowtitle + L" [Debug]"; if (fullscreenmode) { auto gfxmodes = display->GraphicsModes(); auto gfx = gfxmodes[gfxmodes.size() - 1]; window = CreateWindow(display, App::GetName(), 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN, parent); } else { Vec2 displayscale = display->GetScale(); window = CreateWindow(display, windowtitle, 0, 0, App::GetScreenWidth() * displayscale.x, App::GetScreenHeight() * displayscale.y, WINDOW_TITLEBAR | WINDOW_CENTER, parent); } return window; }  
    As you might have picked up, I store all of the core app functionality in a singleton class called "App". This is what the header looks like to give you an idea.
    class App { public: #ifndef LEADWERKS_5 static void SetName(const std::string& pszAppName); static std::string GetName(); #else static void SetName(std::wstring pszAppName); static std::wstring GetName(); #endif static void ParseCommandLine(int argc, const char* argv[]); static bool CheckArgument(std::string pszKey); static std::string GetArgument(std::string pszKey); static int GetArgumentInt(std::string pszKey); static float GetArgumentFloat(std::string pszKey); std::map<std::string, std::string> Arguments(); static bool LoadSettings(); static bool SaveSettings(); static AppSettings GetSettings(); #ifndef LEADWERKS_5 static std::string GetSettingsPath(); #else static std::wstring GetSettingsPath(); #endif static void Shutdown(); static bool Running(); static void EnableVR(); static bool IsDevMode(); static bool IsFullScreen(); static unsigned int GetScreenWidth(); static unsigned int GetScreenHeight(); static unsigned int GetDisplayIndex(); #ifdef LEADWERKS_5 static shared_ptr<Display> GetDisplay(); #endif static unsigned int GetAntialias(); static unsigned int GetLightingQuality(); static unsigned int GetTerrainQuality(); static unsigned int GetTextureQuality(); static unsigned int GetWaterQuality(); static unsigned int GetAnisotropicFilter(); static bool GetTrilinearFilter(); static bool GetVerticalSync(); static bool GetHDRMode(); static void TakeScreenShot(); };  
    Bonus: Load JSON Files Easily
    Leadwerks 5 includes the json library included, but as of time of writing, there is no simple way to load the format into something usable. Josh shared his approach using the engine's Stream Class. The json library can also be included in Leadwerks 4 with little to no effort! This is a very nice format to have in your base app code.
    #if defined (EOF) #undef EOF #endif nlohmann::json ReadJSON(const std::string& pszFileName) { #ifndef LEADWERKS_5 auto stream = FileSystem::ReadFile(pszFileName); auto path = FileSystem::RealPath(pszFileName); if (stream == NULL) { Print("Error: Failed to load JSON script \"" + path + "\""); //stream->path = L""; return NULL; } //Check for starting bracket bool started = false; while (!stream->EOF()) { //auto c = stream->ReadLine(); auto c = String::Chr(stream->ReadUChar()); if (String::Trim(c) == "") continue; if (c != "{") return false; started = true; break; } if (!started) return false; stream->Seek(0); #else auto stream = ReadFile(pszFileName); auto path = RealPath(pszFileName); if (stream == NULL) { Print(L"Error: Failed to load JSON script \"" + path + L"\""); //stream->path = L""; return NULL; } //Check for starting bracket bool started = false; while (!stream->Ended()) { auto c = Chr(stream->ReadByte()); if (Trim(c) == "") continue; if (c != "{") return NULL; started = true; break; } if (!started) return false; stream->Seek(0); #endif std::vector<uint8_t> data; auto sz = stream->GetSize(); data.resize(sz); stream->Read(data.data(), sz); nlohmann::json j3; try { j3 = nlohmann::json::parse(data); } catch (std::exception & e) { std::cout << e.what() << std::endl; return false; } if (j3.type() != nlohmann::json::value_t::object) return NULL; #ifndef LEADWERKS_5 stream->Release(); #endif stream = NULL; return j3; } #ifdef LEADWERKS_5 nlohmann::json WReadJSON(const std::wstring& pszFileName) { auto stream = ReadFile(pszFileName); auto path = RealPath(pszFileName); if (stream == NULL) { Print(L"Error: Failed to load JSON script \"" + path + L"\""); //stream->path = L""; return NULL; } //Check for starting bracket bool started = false; while (!stream->Ended()) { auto c = Chr(stream->ReadByte()); if (Trim(c) == "") continue; if (c != "{") return NULL; started = true; break; } if (!started) return false; stream->Seek(0); std::vector<uint8_t> data; auto sz = stream->GetSize(); data.resize(sz); stream->Read(data.data(), sz); nlohmann::json j3; try { j3 = nlohmann::json::parse(data); } catch (std::exception & e) { std::cout << e.what() << std::endl; return false; } if (j3.type() != nlohmann::json::value_t::object) return NULL; stream = NULL; return j3; } #endif  
    This type of code are things you can do right now in the Leadwerks 5 beta while you wait for the rest of the engine to come online. As for me, I already have this code done and this layer of stuff works flawlessly and produces the same results on both engines. If there is anything else you want me to share based on core.
  6. reepblue
    As I mentioned in my previous post, throughout my time of developing Vectronic on the Source Engine, the project never had more than four maps because so much time and worry was focused on the concern of how it's going to look. While the overall art style, and the balance of visual noise is importation for a project like this, I was spending more time on this than making more maps and puzzles.
     
    I decided to once again start from scratch and only focus on the maps and gameplay for the time being. With this mindset, I've gotten close to seven maps shelled out in dev textures, placeholder models, and no lighting. On top of those seven maps, I've got two more ideas down on paper. With this method of developing, perfecting the how the elements interact with each other is much easier to do.
    I can easily change a puzzle, or how a puzzle is solved if something ends up not working the way I intended it to, without moving a handful of models and lights.
     



     
    As you can see, there is only a white ambient Light being used, Doors just appear and disappear, and platforms use the Sliding Door Script. Things are coded to be as simple as possible in this era of development. I even made the two ball colors primary red and blue for simplistic identification. As I said before, if the game is fun like this, It's only going to get better from here!
     



     
    With how fast maps are moving, additional elements that were not in the demo were needed quicker. And the new puzzle elements such as trip lasers and ball launchers only have basic functionality built in to not slow down the development of new maps. If something needs to be tweaked, it can easily be done with little worry of it breaking something else. The elements are being developed with the maps that use them, not in some zoo developer map with code that might be needed like I've done before.
     
    I'm making it so that each idea is it's own map. Although the engine would be able to handle a few rooms in one map, puzzle games are different then story driven games. Puzzlers need to be perfectly balanced. If you introduce something too soon, too late, or too many new things at once, the learning curve will not be well, a learning curve, and you can overwhelm, or bore the player. Having each idea or puzzle tied to one map, I can easily switch the order, cut out, or add in maps in-between without much headache. I might combine maps when the order of them settle in the future, but it might just be the really small ones.
     



     
    I've currently been making maps in order of progression, but I think in the future, I'm just going to make puzzles not worrying about the order of progression, and workout how (or if at all) they'll fit in the game. My goal is to get ten or fifteen maps done before I even think about how everything is going to look. At the rate I'm going, I should be back in Blender and Paint.net by next month! I have ideas about updating the visual style, but nothing I plan on talking about yet.
     
    Sorry about not posting this article Friday like I said I was going to. I want to write a development blog weekly for my sake of keeping me motivated, so expect at least that.
  7. reepblue
    Besides fixing the UV's for the VecDispenser, Adding in the box model, and some added sounds thanks to ThirstyPanther, most of the work this week was done with adjusting scripts so that the game as a whole plays better.
     
     
    Picking Up Objects
     
    Last week, I mention that I adjusted the pickup system so that it will auto center the object when it's picked up. While the stock code for the pickup system is good for occasionally picking up objects, in Vectronic, you're gonna be picking up boxes a lot. Not only that, but some puzzles require you to throw the boxes straight a head. The stock code locks the position and of the pickup with the picker position point which produces results like this.
     



     
    The pickup system needed to feel natural as possible. I wanted the object to tween from the current location to the center of the player's crosshair, much how the pickup system is in Source. It only took an additional transformed point from the camera, and using Math:Curve to translate the two positions together. The box position will always now be centered with the crosshair when picked up.
     



     
    This fix caused a few more problems which I'm still tweaking, I've added collision back to the picked up object, prevented the player picking up the object if they are standing on it, and also made the player auto drop it if the speed of the object is going too fast due to movement speed or violent collisions. It's not perfect now, but I feel that it's better than the stock pickup system, at least for this game.
     
     
    Platforms
     
    Another fix I've made is with the moving platforms. One major I've found with the sliding door script is that if a heavy object is on the joint object, the joint will fail, and fall through the floor. Since the boxes need to be heavy so they don't bounce really high, I felt like this was not a good system for the platforms.
     
    In the demo, I've made a script that just moves the model's position over time, but further playing with this script showed me that there was issues actually pushing the player up, while jointed objects did not have this issue.
     
    So I went back to the sliding door script with the problem of heavy objects effecting them, but lifting the player is fine. I solved this by making the joint object invisible and it's collision set to only collide with the player, and nothing else. Then, have the platform model as it's child which collides with everything else. This solved both problems as heavy boxes don't screw up the platforms as it's not colliding with the platform, but the player and objects can ride with the lifts without any issues!
     



     
    I still need to work on a better way to set the distance, as right now you have to guess and tweak the distance values which I don't seem to like very much. I plan on switching it to a location point instead. Overall, the platforms need to be more user friendly as right now it's just a modded version of the sliding door script. I might work a bit on that this week.
     
    I'll continue to work little by little to get everything as solid as I can. There is also a map in this set that I feel that's not as fun, informative or needed that I want to adjust or replace before working on any new maps. The goal is to get all this basic stuff perfect before continuing.
  8. reepblue
    I recently updated the Leadwerks Extended Executable with more shaders, and hot fixes. Fetch and pull this update in git. I wanted to post this yesterday, but I was having issues.
     
    Fix 1: No more crashing upon Alt+Tabbing
    This fix is eventually gonna be made engine side, but I was not sure if Josh plans on that being pushed to the default branch, or the beta branch. While the beta and default branch are synced, I jumped the gun and fixed it.
     

    //---------------------------------------------------------\\ // This will be fixed engine side in a future version of LE.
    // For now, we are gonna apply this patch.
    if (window->Active())
    {
    // Update the world.
    world->Update();
    }
     
    if (window->Minimized() == false)
    {
    world->Render();
    }
    //---------------------------------------------------------\\
     
    2: Fixed saving conflict between the options and advanced options panel.
    Before I went to bed last night, I noticed that if you edited a property on the advanced panel, then edited a property on the options menu, the old value you just changed on the advanced panel would reset. This has been fixed by reloading the xml file before it saves values.
     
    3: Fullscreen mode will now always get the current display resolution.
    In the initial release, the application would get the size of the display and store it in the settings.xml file. Although this was a good idea, I realized that this could be bad as a display can change if the user want's to play your game on his 1080p TV while they have a 900p monitor. If fullscreen is enabled, it will now ignore the resolution stored in the settings file, and just fetch the current display size from the System. The screenwidth and screenheight properties are now only used for windowed mode.
     
    4: More Shaders!
    I've added animated versions for the color-emission, cubemap, and detail model shaders.
     
    I'm currently working on some documentation for this, and will update the opening post of the tread with Google Doc links to them. I've also set a Trello board for this so you can see what's next on the list for this project. Hopefully it will not become a wasteland like my Vectronic Trello.
     
    Although I feel that this is solid how it is, continue to add your input and suggestions.
  9. reepblue
    Luawerks has been updated this morning, making the console more responsive and a new tab for the Options Menu for user's to bind keys to actions.
    Actions have always been part of Luawerks, but until now, there wasn't really a motive to use them. Now, the Action class look in the config to check what key the user binded to that action. You can read more on how this works here. Like the console, this may be improved more in the future. 
     
    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Workshop Store for $9.99.
    For documentation and bug reporting, please visit the GitHub page.

  10. reepblue
    Luawerks has been updated to 1.2.6, making some small adjustments and fixes to the system. If you have previously purchased Luawerks, this update is available for free on the Leadwerks Marketplace.
    Following changes include:
    Fixed flag typo correcting LUAWORKS to LUAWERKS Moved error.mat/tex to the Materials/Common folder Added a useuisystem boolean to disable the menu, Adjusted VR mode not to call any UI elements. At this time, this update is only exclusive to the Leadwerks Marketplace.
    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Marketplace for $9.99.
    For documentation and bug reporting, please visit the GitHub page.
     
  11. reepblue
    The Leadwerks API has standard Boolean functions that detect when the end user has pressed a key. While this is very simple and easy to understand, the issue comes when you wish to support binding of actions. Instead calling functions when a certain key was pressed or held, a better way to detect key events is to assign a key to an action. (e.g: Is the Jump key pressed). In Luawerks, I've written an action script in which returns the window call if the key defined in the user's settings is hit. The downsides is that you had to do this for every action in your game and that this was a system written for Luawerks so it wasn't very portable for non-lua/Leadwerks projects.
    function Action:MoveForward() if self.suspend==true then return false end local key_forward = GetKeyInt(System:GetProperty("key_forward","w")) return window:KeyDown(key_forward) end For those who don't know, SDL is an Open Source library that comes in handy for things like Window management, input, and such. So over the weekend, I've decided to sit down and create my own Input System in which would be allow for action bindings, portable for any type of project while supporting Game Pads outside of Steam.
    The first step was to manually poll all inputs and treat them all the same. For the mouse and keyboard, this was just seeing if a key was hit, and registering that event to a map.
    namespace RInput_KM { void SimulateButton(const Sint32& pKey, bool bDown); const float ButtonDown(const Sint32& pKey); void ShowMouse(); void HideMouse(); void SetMousePosition(const int x, const int y, bool bGlobal = false); void ModMousePosition(const int x, const int y, bool bGlobal = false); void UpdateMousePosition(); void SimulateMouse(const int x, const int y); const int GetMouseX(); const int GetMouseY(); void SetMouseWheelPosition(const int y); const bool OnMouseWheelUp(); const bool OnMouseWheelDown(); // Returns the long to a character. const char* GetButtonName(const Sint32& pButton); // Returns the character to a long const Sint32 GetButtonIndex(const char* pButton); void Enable(); void Disable(); void FlushKeyboard(); void FlushMouse(); void Flush(); } When it comes to buttons, it doesn't matter if the mouse button was pressed or a key stroke. What matters is that a button on the keyboard and mouse combo was pressed. I treat the keyboard and mouse as one controller. You can also "Turn off" the mouse and keyboard if you want.
    namespace RInput_GamePad { typedef enum { ENUM_GAMEPAD_NULL = -1, ENUM_GAMEPAD_ONE, ENUM_GAMEPAD_TWO, ENUM_GAMEPAD_THREE, ENUM_GAMEPAD_FOUR, ENUM_GAMEPAD_MAX = ENUM_GAMEPAD_FOUR } GamePadIndex; typedef struct { SDL_GameController* controller; const char* pszDeviceName; bool bEnabled; std::map<Uint8, bool> mControllerButtons; } gamepad_t; void Connect(const Sint32& pWhich); void Disconnect(const Sint32& pWhich); gamepad_t GetDeviceFromPort(const GamePadIndex& pPort); // Digital input: void SimulateButton(const Sint32& pWhich, const Uint8& pButton, bool bDown); const float ButtonDown(const Uint8& pButton, const GamePadIndex& iIndex = ENUM_GAMEPAD_ONE); const char* GetButtonName(const Uint8& pButton); const Uint8 GetButtonIndex(const char* pButton); // Analog input: void UpdateAxisMotions(const Sint32& pWhich, const Uint32& pAxis); const Sint16 GetAxisValue(const Sint32& pWhich, const Uint32& pAxis, bool bFlip = false); const float GetAxisFloat(const Sint32& pWhich, const Uint32& pAxis, bool bFlip = false); void Flush(const Sint32& pWhich); void FlushAll(); } Next was the game pads. which were a bit more challenging as you need to consider multiple game pads and the valves of analog inputs.
    If you were paying attention, You would notice that the ButtonDown functions for both the keyboard+mouse and the game pad are returning floats. While it may be limiting for certain applications, I've created "fake buttons" for events for when the trigger is pressed, or if the left stick is to the left. All input returns a float ranging from 0.0 to 1.0. For digital inputs like buttons, this will be a 1 or 0, but for analog the value depending the range from the resting point to the max will called. So if the left stick is all the way to the left, you'd get 1; half way 0.5. This is much better then dealing with direct Uint32 values.
    Last was to merge both controllers into one entry point in which we use in our app to register our actions, and check to see if they are being called.
    namespace RInput { typedef enum { CONTROLLER_KEYBOARDMOUSE, CONTROLLER_GAMEPAD } Controllers_t; void Init(SDL_Window* pWindow); void InitSDL(const void *data); void SetActiveDevice(const Controllers_t& pDevice); Controllers_t GetActiveDevice(); const char* GetActiveDeviceAsString(); const char* GetGamePadDeviceAsString(const int pPort); const Sint8 GetGamePadCount(); void TestEvents(const SDL_Event& pEvent); void PollEvents(); // <- Use this function if you're not using SDL event polling. void Flush(const Controllers_t& pController); void FlushAll(); //==================================================================== typedef struct { Sint32 key; Uint8 button; bool bDown; bool bHit; } action_t; const float GetActionInput(action_t& pButton); void RegisterAction(const std::string& pActionName, Sint32 iKey, Uint8 iButton, bool bConistant); void ModifyAction(const std::string& pActionName, Sint32 iKey, Uint8 iButton); action_t& GetAction(const std::string& pActionName); bool LoadActionsFromFile(const char* pszPath); void UpdateGamePadStickAsMouse(const Sint32& pWhich, const Sint8& pAxis); } Actions can be bind to a with key and a button. bDown is used to check if the action is being held down while bHit is a flag to check if this is something that should be called once per pressed (Like a key to pick up a box or something.) One top of all this, Actions can have their key/button bindings changed via an XML file. Here's an example how to use this with Leadwerks.
    #include "Leadwerks.h" using namespace Leadwerks; #include "rinput/rinput.h" int main() { Leadwerks::Window* window = Window::Create(); RInput::InitSDL(window->hwnd); Leadwerks::Context* context = Context::Create(window); World* world = World::Create(); Camera* camera = Camera::Create(); camera->SetRotation(35, 0, 0); camera->Move(0, 0, -6); Light* light = DirectionalLight::Create(); light->SetRotation(35, 35, 0); light->SetShadowMode(0); Model* model = Model::Box(); model->SetColor(1.0, 0.0, 0.0); model->SetPosition(0, 0, 0); model->SetShadowMode(0); RInput::RegisterAction("moveforward", KEYBOARD_W, GAMEPAD_BUTTON_LSTICK_UP, true); RInput::RegisterAction("movebackward", KEYBOARD_S, GAMEPAD_BUTTON_LSTICK_DOWN, true); RInput::RegisterAction("moveleft", KEYBOARD_A, GAMEPAD_BUTTON_LSTICK_LEFT, true); RInput::RegisterAction("moveright", KEYBOARD_D, GAMEPAD_BUTTON_LSTICK_RIGHT, true); RInput::RegisterAction("jump", KEYBOARD_SPACE, GAMEPAD_BUTTON_A, false); RInput::LoadActionsFromFile("controller.xml"); while (window->Closed() == false) { RInput::PollEvents(); float x = 0; float z = 0; float u = RInput::GetActionInput(RInput::GetAction("moveforward")); float d = RInput::GetActionInput(RInput::GetAction("movebackward")); float l = RInput::GetActionInput(RInput::GetAction("moveleft")); float r = RInput::GetActionInput(RInput::GetAction("moveright")); if (u != 0) { z += 0.05 + (u /10); } if (d != 0) { z -= 0.05 + (d / 10); } if (l != 0) { x -= 0.05 + (l / 10); } if (r != 0) { x += 0.05 + (r / 10); } model->Move(x, 0, z); x = 0; z = 0; Leadwerks::Time::Update(); world->Update(); world->Render(); context->DrawStats(0, 0, false); context->Sync(false); } return 0; }  
    The Result:
     
    There are many things I didn't add for the sake of time or I wasn't sure how to implement like supporting action for multiple controllers on the front end. But this does what I was set out to do, and although I haven't tried, I'm confident that with little to no work, this code will work on macOS and Linux. As long as there is SDL2 on the platform, this will work.
    Now no matter if the project is Leadwerks, Turbo or just an SDL2 project, I now have a portable input system with controller support which you can find here.
  12. reepblue
    In late 2014, I made the bold decision to focus less with Source Engine mods and pick up an actual engine I could use without legal conflicts or worrying about knowing the right people. At the time, it was like all other options were telling me to forget everything I've picked up through the years of making popular maps and mods and learn something that's completely unique to their software. CSG tools were declared too uncool, and any engine that was nice enough to support it enforced that any and all CSG objects should be removed.
    And there I found Leadwerks. The editor looked and acted like Valve's Hammer Editor and I could code my game in C++. While the Source Engine didn't really use modern C++ (It was more C with classes and a bunch of macros if anything anything) I felt more open to learn this than anything else at the time. Also, my internet wasn't great at the time so the small install size was a plus.
    Only thing that threw me off from the start was that a lot wasn't done for you. With Leadwerks, YOU are in charge of window creation and YOU are in charge of how maps are loaded and cleared. Any engine I've tried, this is done for you and they give you documentation on how their system works for you to read which I'm not a big fan of. 
    After I shipped the Vectronic Demo in 2015, I wanted to have a nice C++ foundation framework to work on. Back then, there wasn't an Actor class to attach C++ functionality to entitles, the animation functionality was in a Lua script, and back then there was no editor materials so you had to make trigger boxes invisible. I wanted something that didn't need Lua to function and make life easier to make maps. I also wanted code for window and world handling to be automatic- to "just work", This started as the LEX template. 
    Looking back, the LEX template is a huge mess of code that did all of that. A demo game was made for a game tournament in 2015 or 2016 (I forgot tbh) called Crawler's Den. The entire game was written in C++. During the development time, my animation code I translated from the Lua variant got adapted into the base engine which made life much easier. This also made animations faster and easier for everybody. I also took time to look into cubemap reflections which I think really pushed for the probes in 4.1. In the end, the template was unstable and hard to work with. I was often afraid to clear and load a new map due to raw pointer issues.
    I instead dropped the idea in favor of a similar system using Lua. Luawerks was everything I wanted LEX to be but much safer. It was more adoptable as most people only have the Standard Edition of Leadwerks. I finished the code and pushed it to the market place.  
    However, I really wanted to make a game with C++ and at this point VR was coming online which C++ is better suited for. 
    For the past 3 or so years, I was writing testing, scrapping and recycling code figuring out the "best" way of a system. I tried to do a translation of Luawerks to C++ but decided against it early on. I figured out why my code was crashing before, what objects I can Release when the entity deletes and overall became better at programming. However, a new engine was on the way and I didn't want to write code if I just had to redo it again.
    I threw the 5 bucks a month for the early subscription at the time for access of the SDK of the new engine. Back then, I thought the new engine was just going to be "Leadwerks but faster" but that wasn't the case. I made a few macro's and #ifdef's to accommodate for this. Over time however, function names were changed, renamed, or removed completely. and the new engine slowly began to do things differently than Leadwerks. 
    It came to a point in which how my systems would work needed to be completely different for both engines. In late 2019, I decided to cut the code in half making one folder called "Core" for core app functionality. That part of the code would be translated for the new engine and tested occasionally. This is also the time when I decided to really look into input functions. The "Game" code was code I would just create as normal and worry about it converting it later a file at a time.  You can play the results here. 
    In early 2020, I started a game project using this base but shelved the project due to the fact I was not happy with getting 150fps on a simple scene. This was also pushed when I threw in a fast VR implantation and thought it was really cool (But really slow!). I also was getting random crashes with an actor over something so something was still up with my design. The new engine was coming a long so I re-structured my base yet again. 
    As of today, after many years of work and research I now have a very clean framework that works on both Leadwerks AND the new engine. I see this as more important than ever now as I expect a lot of people are gonna come in for the new engine and be in the same position I was in 2014. 
    At minimum, this is how your main function should look like and will compile with both engines!
    #include "pch.h" #include "gamewindow.h" #include "stage.h" int main(int argc, const char* argv[]) { // Create Window App auto window = CreateGameWindow("FPSGame"); // Create a stage. auto stage = CreateStage(window); stage->LoadSceneFile("Maps/start.map"); // App Loop. while (window->Closed() == false) { stage->Update(); } } This code will create a window, load a map/scene file and update accordingly. Not only for my games, but this also works really well with my current "non-game" project! 
    I wish to go into how into this deeper at a later date, but I thought I should bring context to the last 5 years. Although I didn't get to build a game within that time, I had a blast figuring all this out. 
    I'll leave off with screen shots. Remember. it's same code base doing the same thing for each engine!
    Leadwerks 4:

    New Engine:

     
  13. reepblue
    Luawerks has been updated to easily transform any game into a Seated VR compatible one. All what's needed is the application to be launched with "-vr". All what has been added is the tweak discussed here. 
    While Luawerks isn't really ideal for Room scale VR, seated on the other hand works better. The center tracking call gets called every time the game gets unpaused. At this time, the UI doesn't work with VR. Keep in-mind that there might be some tweaking to your game to make it 100% VR compatible. For example, if you launch the FPS template map in VR, the bullets should be based on where the player is looking not the mouse position. Don't want have VR? No problem, this only effects the core files (Window, System, Time) and has no other changes to the UI. VR mode is just a bonus. Of course, you can force it to always boot into VR mode.
    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Workshop Store for $9.99.
    For documentation and bug reporting, please visit the GitHub page.
  14. reepblue

    Cyclone
    It's been roughly over a year since active development started on this project. Today, I'm excited to announce that Cyclone now has a "Coming Soon" page on the Steam Store! This marks a huge milestone for the project and I'm looking forward to getting this out as soon as possible. 
    Cyclone is planned on being released as an Early Access title. Releasing into Early Access will grant a layer of feedback, bug reports, and ideas that can help build the remainder features, as well as additional content. So much was done, yet there's much more to do before the planned Early Access release scheduled for Q2 2022! 
    If you are looking forward to Cyclone, please consider adding it to your wishlist and/or giving it a follow to be informed about future updates. 
     
  15. reepblue

    Cyclone
    The time finally came to revamp the main menu for Cyclone. Doing menu work for me is really hard to get into but once I'm knee deep into it, it's all I want to do. I decided now is the time to work on a semi-final menu system due to the following.
    I just finished Steam Input and the menu wasn't compatible. The white text on a bright background was hard to make out. I only had a "New Game" option. There was no way to jump to a map you wanted to play without the console enabled. The first step was to figure out how to integrate the GUI with Steam input. The Leadwerks GUI system is from my experience is an unfinished predecessor of the same GUI calls of the Ultra App Kit. The input was really for a mouse and keyboard but I didn't want to bother learning imgui or another API. One thing that made it really complex is that Leadwerks uses Lua scripts for the GUI draw and event queue commands, 
    In the end, I just did a Steam Input binding to control the mouse with the touchpad or a joystick and told the OS to simulate the left mouse being pressed when the action is called. I don't know if this will get me any approval badges, but now you can control the menu with just the controller. The extra parameters don't seem to do anything but I kept them in. Regardless, it works. We don't have to call any "Legacy Keys" for the bindings!
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CycloneMenu::OnUpdate() { if (!Hidden()) { if (Input::ActionHit(ACTION_MENUSELECT)) { EventQueue::Emit(Event::MouseDown, Window::GetCurrent(), Key::LButton, Window::GetCurrent()->GetMousePosition().x, Window::GetCurrent()->GetMousePosition().y); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CycloneMenu::ProcessEvent(const Leadwerks::Event iEvent) { ... else if (iEvent.id == Event::MouseDown) { // A hacky way of doing this. #ifdef _WIN32 INPUT Inputs[3] = { 0 }; Inputs[0].type = INPUT_MOUSE; Inputs[0].mi.dx = Window::GetCurrent()->GetMousePosition().x; // desired X coordinate Inputs[0].mi.dy = Window::GetCurrent()->GetMousePosition().y; // desired Y coordinate //Inputs[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; Inputs[1].type = INPUT_MOUSE; Inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; Inputs[2].type = INPUT_MOUSE; Inputs[2].mi.dwFlags = MOUSEEVENTF_LEFTUP; SendInput(3, Inputs, sizeof(INPUT)); #endif ... } } Now, controlling the mouse with a joystick isn't the best approach. I also had to make my buttons much bigger so it was harder to miss the bounds of the button when using the controller. I also kept all my UI elements in the center of the screen.


    Next was the options menu. I used choice boxes on everything to make it less Mouse centric,

     
    Finally came the new "Play" menu which I wanted to revamp from just a simple button that would load the "start" map. Right now, I'm forecasting only to ship with 10 maps but what about any new maps later on? Would I just add it onto the 10? What if I want to make a set of maps that focus on a theme or a new element? Would that break the progression? What about when custom maps are loaded? People would be going to the "Custom Maps" box more than the "New Game" button by then. It's not like it's a story or anything to justify those maps being held at a different standard than other maps.
    I decided to take a similar approach to the campaigns in Left 4 Dead. The chapters shipped with the game are loaded into the menu first. Then the application looks for anything under the addon's folder and loads those in. Each chapter can have a collection of maps that are easily selected by the user. 

    { "name": "Cyclone 101", "author": "Reep Softworks", "maps": { "Level 0": { "filepath": "Maps/lvl0.map", "imagepath": "Materials/GUI/background_lvl0_artpass1.tex" }, "Level 1": { "filepath": "Maps/lvl1.map" }, "Level 2": { "filepath": "Maps/lvl2.map" } } } Custom maps work the same way no special treatment. This all works with a json script and the application can locate the script in the zip package. Also, if no image is defined for a map, a "NO IMAGE" texture substitutes in.  Just click start, and off you go!


     
    { "name": "MyCoolAddon", "author": "reepblue", "maps": { "The Cool Addon Map": { "filepath": "Maps/addontest.map", "imagepath": "Materials/GUI/loadingscreen.tex" } } } One thing I'm not sure about is splitting my 10 maps in 3 chapters. I did this after feedback of the list being so empty with just one entry. Hopefully I'll have time to make new maps. Being that now I'm free to work on small collections instead of one long track should be motivating. A goal of arranging the maps like this is so I can justify the game based on the amount of content over "how long" it is. 
    It should be really easy to integrate Steam Workshop with this but I'm not going worry about it now, Tonight I'm happy I can play Cyclone with my Steam Controller without reaching for the mouse and keyboard!
  16. reepblue
    While we have Leadwerks today to make our apps, Turbo is currently in development. To make my projects more long term, I decided to take a step back from my VR project and create a wrapper class that apps can use to more easily transfer to the new engine.  Keep in-mind, this doesn't cover everything and I'm used the last beta from November for testing so things can change. Regardless, as long as that bridge is made, I can always go back and edit the Turbo stuff as more things come online. The goal is to make the Leadwerks stuff compatible with the new tech.
    This code works on both engines.
    #include "GameEngine.hpp" using namespace ENGINE_NAMESPACE; int main(int argc, const char *argv[]) { auto window = Create_Window("MyGame"); auto context = Create_Context(window); auto world = Create_World("Maps/testrender.map"); while (window->Closed() == false) { if (window->KeyHit(KEY_ESCAPE)) break; UpdateWorld(); RenderWorld(); #ifndef TURBO_ENGINE context->Sync(true); #endif } return 0; } As you can see, I went with with the cleaner style of the new engine. The only thing is ugly is the context syncing for Leadwerks, I didn't want wrap that since context syncing is totally different between Leadwerks and the new engine.
    Marcos!
    To comply with the new engine's integer values, I've defined a few macros that is only defined in Leadwerks. While this doesn't cover everything right now, So far I got the Window stuff, Collision, and all keys defined!
    #ifndef TURBO_ENGINE // ------------- // Window: // ------------- #define WINDOW_TITLEBAR Window::Titlebar #define WINDOW_RESIZABLE Window::Resizable #define WINDOW_HIDDEN Window::Hidden #define WINDOW_CENTER Window::Center #define WINDOW_FULLSCREEN Window::FullScreen // ------------- // Color: // ------------- #define COLOR_DIFFUSE Leadwerks::Color::Diffuse #define COLOR_SPECULAR Leadwerks::Color::Specular #define COLOR_EDIT Leadwerks::Color::Edit // ------------- // Collision: // ------------- #define COLLISION_NONE Collision::None #define COLLISION_PROP Collision::Prop #define COLLISION_SCENE Collision::Scene #define COLLISION_CHARACTER Collision::Character #define COLLISION_TRIGGER Collision::Trigger #define COLLISION_DEBRIS Collision::Debris #define COLLISION_COLLIDE Collision::Collide // Not Turbo'd #define COLLISION_PROJECTILE Collision::Projectile // Not Turbo'd #define COLLISION_LINEOFSIGHT Collision::LineOfSight // Not Turbo'd #define COLLISION_FLUID Collision::Fluid // Not Turbo'd #define COLLISION_LASTENTRY COLLISION_FLUID // Not Turbo'd  
    Shared Pointers vs Raw
    The new engine uses smart pointers to replace the current reference system. I needed macros to address this to prevent my code being #ifdef/#endif living Hell.
    // Smart Pointers vs Raw Pointers #ifdef TURBO_ENGINE #define ENGINE_OBJECT(x) std::shared_ptr<x> #define ENGINE_OBJECT_RELEASE(x) if (x != nil) { x->Free(); x = nil; } #define ENGINE_REFERENCE(x) std::weak_ptr<x> #define ENGINE_CAST(_class_, _pointer_) dynamic_pointer_cast<_class_>(_pointer_); #else #define ENGINE_OBJECT(x) x* #define ENGINE_OBJECT_RELEASE(x) if (x != nil) { x->Release(); x = nil; } #define ENGINE_REFERENCE(x) x* #define ENGINE_CAST(_class_, _pointer_) (_class_*)_pointer_ #endif To my surprise, you don't delete an entity alone calling something like "model = NULL". This is because the world class keeps a list of all entities in the world via a shared pointer. Instead (for right now) you call the Free command to totally remove the model. Another thing to note is that with Leadwerks, ENGINE_REFERENCE is the same as ENGINE_OBJECT. while ENGINE_REFERENCE is a weak pointer.
     
    Setters and Getters.
    First thing first, I needed to redefine the the Setters/Getters system from Leadwerks. While the decision to remove this functionality opens the door for more possiblites thanks to the new engine's architecture, In my five years of using Leadwerks, I never had a needed more than one world/context/window for most use cases.
    //----------------------------------------------------------------------------- // Purpose: We still need Getters/Setters to comply with LE code. //----------------------------------------------------------------------------- namespace ENGINE_NAMESPACE { void SetCurrentWindow(ENGINE_OBJECT(Window) pWindow); void SetCurrentContext(ENGINE_OBJECT(Context) pContext); void SetCurrentWorld(ENGINE_OBJECT(World) pWorld); void SetCurrentCamera(ENGINE_OBJECT(Camera) pCamera); ENGINE_OBJECT(Window) GetCurrentWindow(); ENGINE_OBJECT(Context) GetCurrentContext(); ENGINE_OBJECT(World) GetCurrentWorld(); ENGINE_OBJECT(Camera) GetCurrentCamera(); } While in Turbo, the object are assigned to pointers in the cpp file, In Leadwerks, this is just a wrapper for the Set/GetCurrent functions.
    Then we have wrapper functions for every create and load function. So we just do this to set our currents.
    // Creates a window. ENGINE_OBJECT(Window) Create_Window(const std::string& pszTitle, const int x, const int y, const int iWidth, const int iHeight, const int iStyle, ENGINE_OBJECT(Window) pParent) { #ifndef TURBO_ENGINE auto window = Window::Create(pszTitle, x, y, iWidth, iHeight, iStyle, pParent); #else auto window = CreateWindow(pszTitle, x, y, iWidth, iHeight, iStyle, pParent); #endif SetCurrentWindow(window); #ifdef WIN32 window->SetActive(); #endif return window; } // Creates a Context. ENGINE_OBJECT(Context) Create_Context(ENGINE_OBJECT(Window) pWindow) { #ifndef TURBO_ENGINE auto context = Context::Create(pWindow); #else auto context = CreateContext(pWindow); #endif SetCurrentContext(context); return context; }  
    World
    How the world loads scenes between the two engines is completely different! In Turbo, the map is loaded to a shared pointer in which you can throw around your application while in Leadwerks it's just a static function call. As the goal is to just make a compatibility layer,  I just had to do something like this:
    // Loads a scene into the current world. bool Load_Scene(const std::string& pszPath, void hook(ENGINE_OBJECT(Entity) entity, ENGINE_OBJECT(Object) extra)) { #ifdef TURBO_ENGINE if (m_pCurrentWorld.lock() == nil) return false; if (FileSystem::GetFileType(pszPath) == 0) { Msg("Error: Failed to load scene file " + ENGINE_NAMESPACE::FileSystem::FixPath(pszPath) + "\"...") return false; } auto scene = Turbo::LoadScene(GetCurrentWorld(), pszPath); // There is no C++ maphooks rn. :( return scene != nil; #else return Map::Load(pszPath, hook); #endif } // Creates a world. ENGINE_OBJECT(World) Create_World(const std::string& pszPath) { #ifdef TURBO_ENGINE auto world = CreateWorld(); #else auto world = World::Create(); #endif SetCurrentWorld(world); if (pszPath != "") { if (Load_Scene(pszPath) == false) { Msg("Error: Failed to load scene \"" + pszPath + "\"..."); } } return world; }  
    Creating Entities
    Entities in Turbo can be assigned to any world pointer upon creation while with Leadwerks, it creates the entity to the world that's set to current. This was a easy fix as again, we are are just doing a compatibility layer. While the style is more Turbo like, there is no world argument, it grabs the current pointer just like Leadwerks.
    // Creates a Pivot. ENGINE_OBJECT(Pivot) Create_Pivot(ENGINE_OBJECT(Entity) pParent) { #ifdef TURBO_ENGINE if (pParent != nil) return CreatePivot(pParent); return CreatePivot(GetCurrentWorld()); #else return Pivot::Create(pParent); #endif } // Creates a Camera. ENGINE_OBJECT(Camera) Create_Camera() { #ifdef TURBO_ENGINE return CreateCamera(GetCurrentWorld()); #else return Camera::Create(); #endif } // Creates a Listener. ENGINE_OBJECT(Listener) Create_Listener(ENGINE_OBJECT(Entity) pParent) { #ifdef TURBO_ENGINE return CreateListener(GetCurrentWorld(), pParent); #else return Listener::Create(pParent); #endif }  
    Lights are different in the new engine. Instead of lights being their own classes, the new "CreateLight" function returns "Light". Again, I solved this with Macros.
    // Creates a Point Light. ENGINE_OBJECT(CLASS_POINTLIGHT) Create_PointLight(ENGINE_OBJECT(Entity) pParent) { #ifdef TURBO_ENGINE return CreateLight(GetCurrentWorld(), LIGHT_POINT, pParent); #else return CLASS_POINTLIGHT::Create(pParent); #endif } // Creates a Spot Light. ENGINE_OBJECT(CLASS_SPOTLIGHT) Create_SpotLight(ENGINE_OBJECT(Entity) pParent) { #ifdef TURBO_ENGINE return CreateLight(GetCurrentWorld(), LIGHT_SPOT, pParent); #else return CLASS_SPOTLIGHT::Create(pParent); #endif } // Creates a Directional Light. ENGINE_OBJECT(CLASS_DIRECTIONALLIGHT) Create_DirectionalLight(ENGINE_OBJECT(Entity) pParent) { #ifdef TURBO_ENGINE return CreateLight(GetCurrentWorld(), LIGHT_DIRECTIONAL, pParent); #else return CLASS_DIRECTIONALLIGHT::Create(pParent); #endif } And loading things are the same concept. . . .
    // Load a Sound file. ENGINE_OBJECT(Sound) Load_Sound(const std::string& pszPath, const int iFlags) { #ifndef TURBO_ENGINE return Sound::Load(pszPath, iFlags); #else return LoadSound(pszPath, iFlags); #endif }  
    That's about it. I'm going to use this going forward updating as I go and hopefully doing this will make the transition less painful when the time comes.
  17. reepblue
    As you may have known, I've been dabbling with input methods for a while now using SDL2. Since then, I've learned how to do similar functions using the Leadwerks API. The goal was to make a inout system that's easily re-bindable, and allows for controllers to "just work". My first research of a goof system comes from a talk at Steam DevDays 2016 as they discuss how to allow integration with the Steam Controller. 
     
    My thought was: "If I can create my own Action System, I can bind any controller with any API I want". The SDL experiments was a result of this, but they ended up being sloppy when you tried to merge the window polling from SDL into Leadwerks.
    The next goal was to remove SDL2 out of the picture. I've created functions to allow reading and simulations of button presses with the Leadwerks Window class.
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool InputSystem::KeyHit(const int keycode) { auto window = GetActiveEngineWindow(); if (keycode < 7) return window->MouseHit(keycode); return window->KeyHit(keycode); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool InputSystem::KeyDown(const int keycode) { auto window = GetActiveEngineWindow(); if (window != NULL) { if (keycode < 7) return window->MouseDown(keycode); return window->KeyDown(keycode); } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void InputSystem::SimulateKeyHit(const char keycode) { auto window = GetActiveEngineWindow(); if (window != NULL) { if (keycode < 7) window->mousehitstate[keycode] = true; window->keyhitstate[keycode] = true; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void InputSystem::SimulateKeyDown(const char keycode) { auto window = GetActiveEngineWindow(); if (window != NULL) { if (keycode < 7) window->mousedownstate[keycode] = true; window->keydownstate[keycode] = true; } } The simulate keys are very important for controllers. for this case, we would trick the window class thinking a key was pressed on the keyboard. The only direct input we would need from the controller is the value analog sticks which I haven't touch as of yet.
     Using JSON, we can load and save our bindings in multiple Action Sets!
    { "keyBindings": { "actionStates": { "Menu": { "selectActive": 1, "selectDown": 40, "selectLeft": 37, "selectRight": 39, "selectUp": 38 }, "Walking": { "crouch": 17, "firePrimary": 1, "fireSecondary": 2, "flashLight": 70, "interact": 69, "jump": 32, "moveBackward": 83, "moveForward": 87, "moveLeft": 65, "moveRight": 68, "reloadWeapon": 82 } } } } You may want a key to do something different when your game is in a certain state. For this example, when the Active Action Set is set to "Menu", Only KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, and KEY_LBUTTON will work. You can still hover over your buttons with the mouse, but when it comes time to implement the controller for example, you'd just call GetActionHit(L"selectActive") to select the highlighted/active button. If the state is set to walking, then all those keys for Menu gets ignored in-favor of the walking commands. All keys/buttons are flushed between switching states!
    Here's example code of this working. "Interact" gets ignored when "Menu" is set as the default action and vise-versa.
    while (window->KeyDown(KEY_END) == false and window->Closed() == false) { if (window->KeyHit(KEY_TILDE)) { if (InputSystem::GetActiveActionSet() == L"Menu") { SetActionSet(L"Walking"); } else { SetActionSet(L"Menu"); } } // Under "Menu" if (GetActionHit(L"selectUp")) { DMsg("selectUp!"); } // Under "Walking" if (GetActionHit(L"interact")) { DMsg("interact!"); } } Only things I didn't implement as of yet is actual controller support and saving changes to the json file which might need to be game specific. I also want to wait until the UI is done before I decide how to do this.
    As for controllers, we can use SteamInput, but what if your game isn't on Steam? You can try to implement XInput yourself if you want. I tried to add Controller support with SDL2, but people reported issues. And then, what about VR controllers? What matters right now is that we have room to add these features later on. All I need to do is use the GetActionHit/Down Commands and the rest doesn't matter.
  18. reepblue
    Luawerks has been updated to 1.2.7, making some small adjustments and fixes to the system. If you have previously purchased Luawerks, this update is available for free on the Leadwerks Marketplace. Please note: Luawerks is not ready for Leadwerks 4.6 and it's recommended to use Luawerks with Leadwerks 4.5.
    Following changes include:
    Added an optional Map selection menu. "New Game" will default call a panel in which all the maps in your Maps directory will be listed, Note: This will only work if the maps are not included in your zip as raw Lua functions are called. This will also not work if the Lua sandbox is enabled. Re-located error.mdl to a sub directory "Models/Developer" Added widget.mdl and axis.mdl for assistance for visualizing matrix results. When in-game, "Exit to Menu" will show instead of "Quit." This will release the world and the player will be returned to the main menu. Arrange locations of various functions. The names remain the same. Added various functions needed for this update.  

    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Marketplace for $9.99.
    For documentation and bug reporting, please visit the GitHub page.
  19. reepblue
    I think I've finally finished my approach in input for my project. This is the final result.
    //========= Copyright Reep Softworks, All rights reserved. ============// // // Purpose: // //=====================================================================// #include "stdafx.h" #include "gamewindow.h" #include "gameworld.h" int main(int argc, const char* argv[]) { App::ParseCommandLine(argc, argv); auto window = CreateGameWindow("GameTemplate"); auto world = CreateGameWorld(window); if (App::GetArgument("vr") != "") App::EnableVR(); auto camera = Camera::Create(); camera->SetPosition(0, 0, -1); ActionConfig::SetActiveSet("Walking"); while (App::Running()) { if (GetActionHit("jump")) { DMsg("jump!"); } if (GetActionHit("interact")) { DMsg("interact!"); } world->Render(); } return 0; } We no longer care what key or button is pressed, we are now concerned what action is being sent to us.Two json files are auto generated in "CreateGameWindow()". One for the mouse and keyboard, and the other for a gamepad with SDL2. These files can be modified by the user (hard) or an app when I actually write it (easier).
    { "keyBindings": { "actionStates": { "Menu": { "pause": 27, "selectActive": 13, "selectDown": 40, "selectLeft": 37, "selectRight": 39, "selectUp": 38 }, "Walking": { "crouch": 17, "firePrimary": 1, "fireSecondary": 2, "flashLight": 70, "interact": 69, "jump": 32, "moveBackward": 83, "moveForward": 87, "moveLeft": 65, "moveRight": 68, "pause": 27, "reloadWeapon": 82 } } } } { "joyBindings": { "actionStates": { "Menu": { "joySelect": 0, "pause": 6, "selectActive": 0, "selectDown": 12, "selectLeft": 13, "selectRight": 14, "selectUp": 11 }, "Walking": { "crouch": 7, "firePrimary": 16, "fireSecondary": 15, "flashLight": 3, "interact": 2, "joyLook": 1, "joyMovement": 0, "jump": 0, "pause": 6, "reloadWeapon": 1 } } } } You may notice that "jump" and "joyMovement" are both 0, but they get treated differently. I've added a GetActionValue() function which returns a Vec2 for analog sticks and triggers. Triggers are both read as buttons and analog values. You still have to write code in how you want to treat the sticks, but the question of the left stick or right stick is bindable now.
    There are also Action Sets a collection of buttons can be set based on what state the game is at. Driving is not the same as walking, nor it's the same as flying a plane.
    VR can also have it's own entry, but I feel you should use SteamInput for that since the Engine is dependent on SteamVR for it's VR implementation. I've could have just used SteamInput at the start, but then my binding code would only work if the game is on Steam.
    My code was compiled on Leadwerks 4.6 beta, but thanks to my wrapper functions and back and forth compiling, it should just work in the new engine.
  20. reepblue

    Cyclone
    From its initial concept in 2017 through the last sixteen months of development, I am ecstatic to announce that the free demo of Cyclone is now available in preparation for the June Steam Next Fest!  This demo includes three maps from the main game showcasing the functionalities of Cyclone and various puzzle elements.  
    The demo is available right on the Steam Store page!
    Cyclone on Steam (steampowered.com)
    While this is both an exciting and a scary time. I can't stress enough that your feedback will be very helpful as we get closer to the Early Access release date of June 23rd, 2022. Please let me know what you think publicly right here on UltraEngine.com, privately via a DM or my inbox of sferriolo@reepsoftworks.com.


  21. reepblue

    Cyclone
    Back in the summer of 2017, I started experimenting with the idea of a puzzle game by placing launch pads on surfaces. The first build was very sloppy, and there wasn't any wall launching at this time. This build however was the first step of the experimenting process. Only one map was ever made and I just reused old Vectronic assets. 

     
    I shelved this concept until Winter of 2020 asI developed an itch to work on something and I wanted to get familiar with GIMP. This build again only had one main map along with other sample ideas but it was just another demo. 
    Cyclone Prototype - Work in Progress - Ultra Engine Community - Game Engine for VR
    Although I really liked the direction of the project, I didn't develop further on that iteration after that March, but I did show it to people to get thoughts which were mostly positive. At this time, I wanted to wait until the Ultra Engine shipped to revisit the concept yet again, but eventually I got bored of waiting and figured I'd have to spend a lot of time learning the new systems Instead of using the almost seven years of working with Leadwerks. The plan was to get a small game out within a year. I set my scope to be a simple Portal clone with 10 maps. Anything else I probably would have got overwhelmed and bailed out like I did many times before with other projects.
    I first started working on the foundation code base. I came up with the idea of a Stage Class with it handling all the Actors. The Stage class handles time, map loading, transitions, temp decals, and is responsible for attaching actors to the entitles in the editor via a Lua script. It's also is in-charge of ensuring that a Camera is present at all times. After every map load, a camera is created in which something like a Player actor would point to as it's Camera. It all works very nicely. 
    //========= Copyright Reep Softworks, All rights reserved. ============// // // Purpose: // //=====================================================================// #ifndef STAGE_H #define STAGE_H #if defined( _WIN32 ) #pragma once #endif #include "pch.h" enum StageEvent { /// <summary> // NULL; Use this for the main menu. This is the default. /// </summary> STAGE_EVENTNULL = 0, /// <summary> // Intermission; the event(s) between plays. /// </summary> STAGE_EVENTINTERMISSION, /// <summary> // Transition; Intermission between scenes. /// </summary> STAGE_EVENTTRANSITION, /// <summary> // Play; In-Game /// </summary> STAGE_EVENTPLAY }; struct TransitionData { std::string szMapPath; std::string szLandmarkName; Leadwerks::Entity* pLandmarkEntity; Leadwerks::Vec3 vLandmarkDistance; Leadwerks::Vec3 vCamRot; Leadwerks::Quat qCamQuat; Leadwerks::Vec3 vVelo; void Clear() { szMapPath = ""; szLandmarkName = ""; vLandmarkDistance = Leadwerks::Vec3(); vCamRot = Leadwerks::Vec3(); qCamQuat = Leadwerks::Quat(); vVelo = Leadwerks::Vec3(); pLandmarkEntity = NULL; } }; class StageActor; class GameMenu; class Stage : public Leadwerks::Object { protected: Leadwerks::World* m_pWorld; Leadwerks::Camera* m_pCamera; Leadwerks::Context* m_pFramebuffer; GameMenu* m_pMenu; StageEvent m_hEvent; bool m_bVSync; int m_intFrameLimit; std::string m_szCurrentSceneName; std::string m_szPreviousSceneName; //std::string m_szNextSceneName; bool m_bShowStats; Leadwerks::Vec3 m_vGravity; uint8_t m_intPhysSteps; TransitionData m_hTransitionData; std::vector<StageActor*> m_vActors; void ClearScene(const bool bForce = true); bool LoadSceneFile(const std::string& pszFilePath, const bool bForce = true); void SetStageEvent(const StageEvent& hEvent, const bool bFireFunction = true); void PreTransition(); void PostTransition(); GameMenu* GetGameMenu(); public: Stage() {}; Stage(Leadwerks::Window* pWindow); virtual ~Stage(); // Time: void Pause(const bool bFireOutput = true); void Resume(const bool bFireOutput = true); const bool Paused(); uint64_t GetTime(); const bool TogglePause(const bool bHandleMouse = false); void Update(); void Clear(); bool SafeLoadSceneFile(const std::string& pszFilePath); StageEvent GetCurrentEvent(); Leadwerks::Entity* FindEntity(const std::string& pszName); StageActor* FindActor(const std::string& pszName); Leadwerks::Decal* CreateTempDecal(Leadwerks::Material* pMaterial); //std::string GetCurrentScene(); const bool InPlay(); void Reload(); void DrawStats(const bool bMode); void ToggleStats(); const bool ConsoleShowing(); void SetPhysSteps(const uint8_t iSteps); void SetVSync(const bool bState); Leadwerks::World* GetWorld(); Leadwerks::Camera* GetCamera(); Leadwerks::Context* GetFrameBuffer(); Leadwerks::Widget* GetGameMenuHUD(); void Transition(const std::string& pszFilePath, Leadwerks::Entity* pLandmark); void ShowHUD(); void HideHUD(); //Virtual functions: virtual void Start() {}; virtual void OnUpdate() {}; virtual void OnTick() {}; virtual void PostRender(Leadwerks::Context* pFrameBuffer) {}; virtual void OnNoPlay() {}; virtual void OnPostLoad(Leadwerks::Entity* pEntity) {}; virtual void OnIntermission() {}; virtual void OnTransition() {}; virtual void OnPlay() {}; virtual void OnProcessEvent(const Leadwerks::Event iEvent) {}; virtual void OnPaused() {}; virtual void OnResume() {}; virtual void ScheduleRestart() {}; static Stage* Create(Leadwerks::Window* pWindow); friend class StageActor; }; extern Stage* ActiveStage; extern Stage* CreateStage(Leadwerks::Window* pWindow); #endif // STAGE_H The first few months of development of this system still crashed randomly as I was hard loading the map instead of checking if a string was not empty every frame like how it's set up in the MyGame example. I went back to that approach and the crashing stopped. This did cause very hard to find bug which caused random restarts in then Release build. The issue ended up being that the string I was checking wasn't declared as empty. 
    With a great foundation by February 2021 , I went to getting the Cyclone stuff online. I mostly copied from the 2020 build but it was good enough to get started.  A quick model later, and I had my item dropper in game.
    https://cdn.discordapp.com/attachments/226834351982247936/815726095198322698/unknown.png
    The item dropper was the first item I had to rethink how physics objects would work. The original idea was to have the boxes be an actor that made the impact sounds and other effects when I got to it. However, the entity's actor doesn't get copied when it's instanced.  
    The solution was to have the item dropper create a the box model, and after it instances the model, it also assigns a collision hook to that model. I also apply modifications to the box friction and dampening.
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- uint64_t BoxImpactNoiseLastSoundTime; void PuzzleBoxCollisionHook(Entity* entity, Entity* entity2, const Vec3& position, const Vec3& normal, const float speed) { auto self = entity; auto target = entity2; if (self->GetWorld()->GetWaterMode()) { if (self->GetPosition().y < self->GetWorld()->GetWaterHeight()) { DMsg("Puzzlebox below water level. Deleting."); ReleaseGamePlayObject(self); return; } } float fixedSpeed = __FixSpeed(speed); float flSoftThreshhold = 1.5f; float flHardThreshhold = 4.0f; long intMaxFrequency = 300; if (fixedSpeed > flSoftThreshhold) { int collisiontype = target->GetCollisionType(); if (collisiontype == COLLISION_PROP || collisiontype == COLLISION_SCENE) { long t = Leadwerks::Time::GetCurrent(); if (t - BoxImpactNoiseLastSoundTime > intMaxFrequency) { BoxImpactNoiseLastSoundTime = t; if (fixedSpeed > flHardThreshhold) { // HARD self->EmitSound(GetImpactHardSound(), 20.0f, 0.5f, 1.0f, false); } else { // SOFT self->EmitSound(GetImpactSoftSound(), 20.0f, 0.5f, 1.0f, false); } } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Leadwerks::Model* CreatePuzzleBox(const bool bAttactHook) { auto mdl = Model::Load(PATH_PUZZLEBOX_MDL); ApplyBoxSettings(mdl); if (bAttactHook) mdl->AddHook(Entity::CollisionHook, (void*)PuzzleBoxCollisionHook); return mdl; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Leadwerks::Model* SpawnPuzzleBox(Leadwerks::Model* ModelReference, const Vec3& vPosition) { if (ModelReference == NULL) { ModelReference = CreatePuzzleBox(); } auto newitem = (Model*)ModelReference->Instance(); newitem->SetPosition(vPosition); ApplyBoxSettings(newitem); newitem->AddHook(Entity::CollisionHook, (void*)PuzzleBoxCollisionHook); newitem->Show(); return newitem; } Although adding hooks to entities isn't really considered official, this is only one case out of many that I do this. It's really nice to write a quick function without creating and assigning an actor. The item dropper got improvements down the line to fix some physics issues. (I made the dropper very slippery so there was no friction and it stopped acting weird.) 
    The months rolled on and I kept chipping away at the project. My maps count was increasing every month but there something really bothering me. It was the fly movement.
    In Cyclone, I wanted a way for the player to launch themselves across rooms and deadly pits. I originally just applied velocity to the player character but the results felt terrible. I noticed that the wall launch was smoother if you held the button of the direction you were flying down. This gave me a nice arch instead of a sharp line. I ended up completely rewriting my player code from the ground up. Doing so allowed me to break apart what my Player actually was.
    First. there's the base Player class. This class alone will give you a spectator camera with input controls. Everything regarding the camera is defined in this base class. I also spent the time to work how HUD hints would work. The HUD uses the Leadwerks GUI system but for the HUD, I kept it to simple image drawing on the framebuffer. 
    The base class also controls ambient sound, although it's still a work in progress.
    Base Player Improvements - Work in Progress - Ultra Engine Community - Game Engine for VR
    //========= Copyright Reep Softworks, All rights reserved. ============// // // Purpose: // //=====================================================================// #ifndef PLAYERACTOR_H #define PLAYERACTOR_H #if defined( _WIN32 ) #pragma once #endif #include "pch.h" #include "Input.h" #include "../Classes/StageActor.h" enum CharacterMoveState { CHARACTERSTATE_IDLE = 0, CHARACTERSTATE_WALKING, CHARACTERSTATE_JUMPING, CHARACTERSTATE_FALLING, CHARACTERSTATE_FLYING }; enum ScreenEffect { SCREENEFFECT_NONE = 0, SCREENEFFECT_FADEIN, SCREENEFFECT_FADEOUT, SCREENEFFECT_FLASH, SCREENEFFECT_BLIND, }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class CommentaryData { public: std::string devname = ""; uint8_t index = 0; uint8_t maxindex = 0; Source* speaker = NULL; ~CommentaryData() { if (speaker) { speaker->Release(); speaker = NULL; } } }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class PlayerActor : public StageActor { PickInfo* m_pLookTrace; // HUD: bool m_bShowHint; bool m_bShowCommentary; bool m_bShowGameOver; uint64_t m_u64GameOverTime; CharacterMoveState m_hState; std::weak_ptr<CommentaryData> m_pCommentaryData; protected: Listener* m_pListener; bool m_bIsAlive; bool m_bAllowCrosshair; bool m_bSuspendMovement; bool m_bSuspendLook; bool m_bCrouched; bool m_bCrouching; bool m_bWantsToCrouch; Vec3 m_vPushForce; Vec3 m_vCamRotation; Vec3 m_vCameraOffset; float m_flEyeHeight; bool m_bFreeLook; bool m_bFreeMove; Pivot* m_pRotator; uint64_t m_u64QuickSpinInitTime; bool m_bSpinLockX; double m_dCamTopAngle; double m_dCamBtmAngle; float m_flEyeTraceDistance; float m_flInteractDistance; float m_flPickRadius; Action ACTION_MOVEFORWARD; Action ACTION_MOVEBACKWARD; Action ACTION_MOVERIGHT; Action ACTION_MOVELEFT; Action ACTION_INTERACTION; // Effects: ScreenEffect m_hEffectType; Vec4 m_vCurtianColor; float m_flCurtianRate; bool m_bZoomed; bool m_bZoomedIn; Vec3 m_vCamRotationOffset; Vec3 m_vSmoothedCamRotationOffset; const bool ActionHit(const Action actionname); const bool ActionDown(const Action actionname); void SetVelocity(const Vec3 vVelo); void AddForce(const Vec3 vForce); void ChangeMovementState(const CharacterMoveState hState); public: PlayerActor(); virtual ~PlayerActor(); virtual void Start(); virtual void UpdateWorld(); virtual void UpdateMatrix(); virtual bool IsPlayer() { return true; }; virtual bool IsAlive(); virtual void Kill(); virtual void Respawn() {}; void Kick(); virtual void Spawn() {}; virtual void UpdateKeyBindings(); // Quick functions for locking movement/look: virtual void SuspendMovement(const bool bValue) { m_bSuspendMovement = bValue; }; virtual void SuspendLook(const bool bValue) { m_bSuspendLook = bValue; }; // Camera functions: Camera* GetCamera(); Vec3 GetEyePosition(); Vec3 GetEyeAngles(); Quat GetEyeQAngles(); const float GetEyeHeight(); const float GetCrouchedEyeHeight(); virtual const bool CanUnCrouch() { return true; }; virtual void SetEyeHeight(const float flHeight); virtual void SetCameraAngles(Leadwerks::Vec3 vRot); virtual void Teleport(const Vec3& pNewPos, const Vec3& pNewRot, const Vec3& pNewVelocity); const bool FireUseOnEntity(Entity* pEntity); void SetFreeLookMode(const bool bMode); void SetFreeMoveMode(const bool bMode); void UpdateFreeLookMode(); void UpdateFreeMoveMode(); void QuickSpin(); virtual void UpdateCameraHeight(const bool bCrouchState); const float GetEyeTraceDistance() { return m_flEyeTraceDistance; }; Entity* GetEyeLookingAt(); const Vec3 GetEyeLookPositon(); PickInfo* GetEyeTrace() { return m_pLookTrace; }; const float GetPickRadius(); void ForceLookAtPoint(const Vec3& vPos, const bool bLockX = false); const Line3D GetLine(); // Movement + Physics virtual void SetPhysicsMode(const int iMode); const int GetPhysicsMode(); void SetWalkSpeed(const float iSpeed); const int GetWalkSpeed(); virtual void HandleMovement(); virtual const bool IsCrouched(); const bool IsAirbone(); virtual void HandleInteraction(); virtual void SetNoClipMode(const bool bState) {}; CharacterMoveState GetMovementState() { return m_hState; }; Vec3 GetVelocity(); virtual void Push(const int x, const int y, const int z); void ForceJump(const float fJump); // Interaction virtual void OnSuccessfullUse(Entity* pHitEntity) {}; virtual void OnUnSuccessfullUse() {}; virtual bool ShouldSkipUseTest() { return false; }; // Post drawing: void ShowCrosshair(const bool bShow); virtual void PostRender(Context* context); virtual void DrawHUD(Context* pContext); virtual void DrawCurtian(Context* pContext); Widget* GetHUD(); // Effects: virtual void HandleScreenEffects(); void SetCurtianColor(const Vec3& pColor); void PlayScreenEffect(const ScreenEffect iScreenEffect, const Vec3 vColor = Vec3(0), const float flRate = 0.1f); void PlayQuickFlash(const Vec3 vColor = Vec3(1)); void ClearScreenEffect(); void ZoomIn(const float fNewFov, const float flRate); void ZoomOut(const float flRate); // HUD Hint: void ShowHUDHint(const Action actionname, const std::string& pszMessage); void HideHUDHint(); void ShowCommentaryPanel(std::shared_ptr<CommentaryData> pData); void HideCommentaryPanel(); void ShowGameOverScreen(const std::string& pszMessage); }; extern const bool IsPlayerActor(Leadwerks::Entity* pEntity); extern PlayerActor* ToPlayerActor(Leadwerks::Entity* pEntity); extern PlayerActor* GetPlayerActor(); extern const bool IsPlayerLookingAtEntity(Leadwerks::Entity* pEntity); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- class PlayerProxy : public StageActor { uint64_t m_u64TriggerTime; uint64_t m_u64DelayTime; protected: PlayerActor* GetPlayer(); const bool IsPlayerLookingAtMe(); public: PlayerProxy(); virtual void Start(); virtual void UpdatePhysics(); virtual void ReceiveSignal(const std::string& inputname, Entity* sender); virtual void Trigger() {}; }; class CommentaryNode : public PlayerProxy { bool m_bActive; std::shared_ptr<CommentaryData> m_hData; public: CommentaryNode(); virtual ~CommentaryNode(); virtual void Start(); virtual void UpdateWorld(); virtual bool Use(StageActor* pActor); virtual void Detach(); friend class CommentaryNode; }; class ProxyEndGame : public PlayerProxy { public: virtual void Trigger(); }; class ProxyCurtain : public PlayerProxy { ScreenEffect m_hType; uint64_t m_u64ShowTime; uint64_t m_u64TimeOut; public: // This needs to be PostStart as the player may be placed/loaded after this actor! virtual void PostStart(); virtual void UpdateWorld(); virtual void Trigger(); }; class ProxyHint : public PlayerProxy { uint64_t m_u64ShowTime; uint64_t m_u64TimeOut; public: virtual void Start(); virtual void UpdateWorld(); virtual void Trigger(); }; class ProxyAmbientSound : public PlayerProxy { Source* m_pSpeaker; float m_flMaxVolume; bool m_bActive; public: virtual void Start(); virtual void UpdatePhysics(); void Activate(); virtual void Detach(); friend class ProxyAmbientSound; }; class ProxyLookAtMe : public PlayerProxy { Model* m_pBox; public: virtual void Start(); Model* GetTargetBox(); }; #endif // PLAYERACTOR_H Next, I made an FPSPlayer which is built on top on the PlayerActor class. This transfroms the base class into a FPS character controller with interaction and physics pickup. It also has base code for a weapon. There is also a CyclonePlayer but it really is just a Start function that controls if it should give the player the weapon or not.
    //========= Copyright Reep Softworks, All rights reserved. ============// // // Purpose: // //=====================================================================// #ifndef FPSPLAYER_H #define FPSPLAYER_H #if defined( _WIN32 ) #pragma once #endif #include "pch.h" #include "PlayerActor.h" struct InteractPickupStoredData { float mass; int collisiontype; Vec2 dampinging; Vec2 friction; }; class FPSPlayer; class FPSWeapon : public StageActor { protected: Pivot* m_pSwayPivot; bool m_bHolstered; Model* m_pViewModel; Vec3 m_vModelOffset; Vec3 m_vBaseRotation; FPSPlayer* m_pOwner; public: FPSWeapon(); virtual void Holster() {}; virtual void Unholster() {}; virtual void UpdateHolster() {}; virtual void Fire(const int iMode = 0) {}; virtual void TestPickupState() {}; virtual void Reload() {}; virtual void BeginJump() {}; virtual void ReAdjustViewModel(const float flPlayerFOV); friend class FPSPlayer; }; class FPSWeaponPickup : public StageActor { void GiveWeapon(Entity* entity); public: virtual void Collision(Entity* entity, const Vec3& position, const Vec3& normal, float speed); virtual bool Use(StageActor* pActor); }; class FPSPlayer : public PlayerActor { // Movement + Physics bool m_bCrouchedOldState; bool m_bWalking; float m_flUpdateTick; uint64_t m_intLastStepTime; bool m_bLeftStep; std::string m_pszFootStepSounds[2]; Model* m_pCorpse; uint64_t m_u64PushLaunchTime; Vec3 m_flLastImpactSpeed; // Interaction Vec3 m_vCarryPosition; Quat m_vCarryQuat; Vec3 m_flThrowVelocity; float m_flPickDistance; float m_flCarryDistance; float m_flHoldThreshold; Pivot* m_pRotationCorrection; Joint* m_pEffector; Entity* m_pCarryEntity; protected: Action ACTION_JUMP; Action ACTION_CROUCH; Action ACTION_ZOOM; Action ACTION_FIREPRIMARY; Action ACTION_FIRESECONDARY; Action ACTION_RELOAD; // Weapon Pivot* m_pWeaponTag; //FPSWeapon* m_pActiveWeapon; virtual void PickupObject(Entity* pEntity); virtual void UpdateHeldObject(); virtual void ThrowObject(); void SetThrowVelocity(Vec3 vVelocity); public: FPSPlayer(); virtual ~FPSPlayer(); virtual void Spawn(); virtual void Kill(); virtual void Detach(); virtual void UpdateWorld(); virtual void UpdatePhysics(); virtual void Collision(Entity* entity, const Vec3& position, const Vec3& normal, float speed); virtual void UpdateKeyBindings(); virtual void Respawn(); // Movement + Physics virtual const bool IsCrouched(); virtual const bool CanUnCrouch(); virtual void HandleCrouching(); virtual void HandleCharacterController(); virtual void SetNoClipMode(const bool bState); virtual void UpdateMovement(Vec2 vMovement); virtual void OnUpdateMovement() {} virtual void OnPerStep(); virtual void OnStopMovement() {}; virtual void OnJump(); virtual void OnLand(); virtual bool GetCrouched() { return false; }; const bool IsFlying(); virtual void Push(const int x, const int y, const int z); const float GetFallSpeed(); // Interaction virtual void OnSuccessfullUse(Entity* pHitEntity); virtual void OnUnSuccessfullUse(); virtual bool ShouldSkipUseTest(); virtual void ForceDropObject(); const bool HoldingObject(); // Weapon void GiveWeapon(); FPSWeapon* GetWeapon(); virtual void BuildWeapon(FPSWeapon* pWeapon); void AdjustViewModel(); }; extern const bool IsFPSPlayer(Leadwerks::Entity* pEntity); extern FPSPlayer* ToFPSPlayer(Leadwerks::Entity* pEntity); #endif // FPSPLAYER_H The magic of getting the fly code to work correctly is in this bit. I also force the player to crouch in a ball to make it feel way less awkward. It was really tricky, but it all worked out.
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void FPSPlayer::UpdatePhysics() { HandleCharacterController(); UpdateHeldObject(); // This fixes a crash when dying. if (GetEntity()->GetPhysicsMode() == Entity::CharacterPhysics) { // Stop moving me if we're landed and it's been a bit since we were last pushed. if (m_u64PushLaunchTime > 0) { if (GetStage()->GetTime() > m_u64PushLaunchTime + 100) { if (m_vPushForce != Vec3(0) && !IsAirbone()) { DMsg("PlayerActor: Resetting push launch timer."); //GetEntity()->charactercontroller->stepheight = 0.51; m_vPushForce = Vec3(0); SetVelocity(m_vPushForce); m_bWantsToCrouch = false; m_u64PushLaunchTime = 0; } } } } if (GetMovementState() != CHARACTERSTATE_FALLING && GetEntity()->GetVelocity().y < -4.5f) { ChangeMovementState(CHARACTERSTATE_FALLING); } else if (GetMovementState() == CHARACTERSTATE_FALLING && GetEntity()->GetVelocity().y >= 0.0f) { ChangeMovementState(CHARACTERSTATE_IDLE); OnLand(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void FPSPlayer::HandleCharacterController() { if (GetStage()->ConsoleShowing() || m_bSuspendMovement) return; if (GetEntity()->GetPhysicsMode() == Entity::RigidBodyPhysics) return; float jumpinput = m_vPushForce.y; Vec2 movement = Input::GetActionAxis(Input::ToActionAxis(ACTION_MOVEFORWARD, ACTION_MOVEBACKWARD, ACTION_MOVELEFT, ACTION_MOVERIGHT, GAMEPADAXIS_LSTICK)); float speed = g_player_walkspeed; // Handle Crouching HandleCrouching(); if (IsCrouched()) speed = g_player_walkspeed / 2; auto l = movement.Length(); if (l > 0.0) { movement.x = (movement.x / l) * speed; movement.y = (movement.y / l) * speed; } // Handle Jumping if (ActionHit(ACTION_JUMP)) { if (!IsAirbone()) { jumpinput = g_player_jumpforce; ChangeMovementState(CHARACTERSTATE_JUMPING); OnJump(); if (movement.y != 0) movement.y = movement.y * 1.6; if (movement.x == 0 || movement.y == 0) OnPerStep(); } } if (m_u64PushLaunchTime > 0) { if (m_vPushForce.x != 0 || m_vPushForce.z != 0) { movement = Vec2(0); movement += GetEntity()->GetVelocity(true).xz(); //DMsg(movement.ToString()); // Hackory to make the player crouch properly while flying. m_bWantsToCrouch = false; m_bCrouched = true; m_vCameraOffset.y = GetCrouchedEyeHeight(); GetEntity()->SetInput(0, movement.y, movement.x, m_vPushForce.y, m_bCrouched, g_player_maxdecel, g_player_mindecel, true); } } else { UpdateMovement(movement); GetEntity()->SetInput(GetEyeAngles().y, movement.y, movement.x, jumpinput, m_bCrouched, g_player_maxdecel, g_player_mindecel, true); } m_vPushForce.y = 0; } Along with some adjustments to the Cyclone code, Everything was working much better. I continued to make tweaks and changes as I continued along.  A nice bonus was this fixed Cyclones on Ramps which didn't work with my old code which helped me to get another map done.
    Revamped Cyclone Behavior - Work in Progress - Ultra Engine Community - Game Engine for VR
    After 6 months of development, I'm very happy to say that today I hit my first milestone. I have 10 playable maps with everything feeling acceptable. Most gameplay functions are online AND today I squeezed an Addon System to allow custom maps. The game runs with the Lua Sandbox enabled so custom maps can code new content in Lua!
     
    However, the entire game looks like this.

    I think it's safe to say that it's time to close Visual Studio for a bit and open Blender and Gimp on a daily basis. I'll have to open Visual Studio again to code the indicator strips and other effects but I'm really excited to just zone out and work models and textures. (although I don't see myself as the best in that area.) If you're actually interested in helping, my DM is open.
    Right now the plan is to get some maps art up for some screenshots and by late September/October set up Cyclone for Steamworks for an Early Access release Q1 2022. I can beat the game within a few minutes but with the project having zero story it can get more content in the future by me or others thanks to the addon system.
    First thing I need is a logo....
  22. reepblue
    I've uploaded a new release last night correcting the player's pickup to be much more reliable. It should be very rare for a case in which the player is in a object and releases it causing the player to fly backward. I could have kept objects solid but that caused another can of worms. I also posted this on my OneDrive due to issues of people not being able to download it from this site.
    Yesterday I was working on implementation of the power balls and the launcher. I've written a rough draft of this a few weeks ago and I had it fully working in normal player code. When I started to work on the VR version, that's when I started to run into some problems. First, I didn't like how the code was looking. It involved a lot of referencing of entities and classes. What made it hard is that the VR implementation was different from the normal players. What didn't help was after too many restarts of my application with VR enabled, the entirety of Steam would just crash. So developing for VR was very frustrating.
    With Turbo coming a long, I wanted my code to work with little modification as possible. I've written a function wrapper that'll only be called if the application is ran under Leadwerks. This both will save time in replacing these functions when the time comes, and get's me used to the new functions. One thing I'd still have to fix is how Lua is implemented in the new engine. Most of the heavy functionalities are in classes which will defiantly keep self.entity for referring to the entity it's being used on. However somethings will still need to be changed and there are a lot of unknowns right now.
    With that being said, I think it's safe to say it'll be best to focus on the art and assets going forward. With the amount of time I have to make these assets, I'm sure more clarification with Lua scripts with the new engine will surface, maybe even a beta to play with. But with it being Leadwerks or Turbo, they both need assets. VR mode will also be dropped for now. It'll still be there, just not fully supported. You'll need to run the app with -devmode to see the VR tab now. After the project transitions into Turbo, VR development will continue.
    I shall be going back to my Linux machine with my ultra wide color calibrated monitor to work on these assets. I've created a lot of Vectronic themed stuff in the past and I'm going to go through it and see what I can savage from it. All my models are done in Blender, and I want to create all textures in GIMP this way my files can be used with free and open sourced tools. Also, if there is any Linux developers who wish to use this package, they don't have to be hit in the face with Maya and Photoshop files.
    Releases will slow down, but I'll be posting screenshots of my work soon.
     
  23. reepblue
    After sixteen months of week-by-week development, I'm happy to announce that the anticipated spiritual successor of the Portal mod, Blue Portals is now available for sale as an Early Access title on Steam!
     
    I want to take a moment to thank [b]everyone[/b] who has played the demo and gave feedback. There's still work to be done! Being an Early Access title allows Cyclone to continue keep players involved in the development process to help it evolve into the best it can be! 
    I hope you're all excited as I am to see where Cyclone will be in the near future!
     
    Please enjoy!
     
  24. reepblue
    Luawerks has been updated to 1.2.8, making some small adjustments and fixes to the system. If you have previously purchased Luawerks, this update is available for free on the Leadwerks Marketplace.
    Following changes include:
    Fixed GUI code for Leadwerks Game Engine 4.6 Removed the feature "allowfullscreenfromeditor" as it was causing conflicts with fullscreen. Added ignoreeditorwinsettings bool setting to force the game to ignore editor launch settings. (Sorry for the delay..)
    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Marketplace for $9.99.
    For documentation and bug reporting, please visit the GitHub page.
  25. reepblue
    I'm excited to announce that Cyclone will be participating in Steam Next Fest 2022! While I'll not be live streaming, a playable demo will be available during the festival. The demo will act like an even softer release than the Early Access release, which should roll into the planned release date of June 23rd, 2022!

     
    Add it to your wish list on Steam: https://store.steampowered.com/app/1803590/Cyclone/
    Check out the itch.io Page: https://reepblue.itch.io/cyclone
    Join my public Discord: https://discord.gg/Hh7Ss9fWaW
×
×
  • Create New...