Jump to content

reepblue

Developers
  • Posts

    2,486
  • 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
    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.
  3. 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.
  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
    After a whole summer of working on the LEX template, and learning new things about the engine, I'm happy to say that Vectronic will soon be back on track in full development!
     
    Before I go into full detail, I wish to mention that the Vectronic Demo has been updated with Josh's optimization edits, and the VecBall has a new effect I've made during the development of LEX. If you had sluggish performance before, I recommend you check out the update!
     
    Now that I've got a nice foundation with a menu, a precache system, now what? I was thinking I would just be "upgrading" the Vectronic project to use the LEX template, but looking back on it, I'm not really happy how it is. Back in May, I really just ported textures, sounds, and models from my Source build to Leadwerks to see if a game like Vectronic would work on Leadwerks. Of course, I was still pretty new to lua scripting, and very use to pre-built lighting and such so I did struggle a bit, the demo map was not optimized, and most of my code is a mess. To wrap it up, I mostly did not know what I was doing. I still to this day see that demo map as a map from hell; I've had so many issues with it, but I'm happy how it turned out.
     
    Now 6 months later, I feel like I've learned a lot and ready to get back onto my main project again. I decided the best way to start is to start a new project based off of the LEX project, and redo most things. I've restarted this project many times over for the past few years, but this should be the last time.
     
    Back when I was developing this project on the Source Engine, I've felt like I needed assets to make a game. I thought if I had everything, textures, models, sounds and other assets done, I could build levels like a LEGO set; having all the pieces, and knowing where everything goes. It's a good theory, but it never really worked.
     




    A old Source build of Vectronic.


     
    I only had a few maps per build. The last build had 3 maps, and the build before that had 4 maps. There was never a set of interesting and fun levels, just a
    . Thing was, I was so worried about how things functioned and looked that no build ever got more then a few maps, I was distracted by other things that are not important in the pre-alpha stage of things, and so were my testers, which did not help move the project further ether.. 
    Learning from my mistakes, this time the project is going to start blank almost like the past few years never happened. Doing the reboot this way will force me to worry about scripts, and the actual puzzles. This will also make it easier to optimize the maps when it is time to bring in the assets, and not have what happened with the demo map. No lights, no models, just CSG and a few scripts. If the game is fun with just that, it's easy sailing from there!
     
    Now, that does not mean I'm gonna waste more time by re-scripting, re-modeling, and re-texturing things that are already done. There are somethings I think that can be better, and I do have ideas I wish to try, but not everything needs to be scrapped, somethings just need a revision. I eventually want to give Vectronic a look that only Leadwerks can give as the demo does look like it's running in Source. But right now, the basics need to be done!
     
    I hope to update every week on progress, I'm feeling this Friday I'll talk more about how I'm doing things and short term goals.
  6. reepblue
    It's been over a year ago since I've released the Vectronic Demo using Leadwerks. The demo was composed of assets I've made ported from the Source Engine and my lack of understanding of the API and lua scripting at the time. (I'm used to coding everything in C++) As of writing, it has 1,221 subscribers on the Steam Workshop, 25 out of 26 positive ratings, and is the second most subscribed item! So there is sigh that this as a MVP (Minimum Viable Product) was a success. And again, the Game Launcher is in Beta..
     
    Since the demo, the engine got a lot of improvements and features, and I've learned a lot more about game development in general. (Models, Texture making, etc.) Last summer, I sat down to develop a base application for my projects. I wanted the world, context, and window management to be out of the way while making the full game, and I was concerned about a UI as it was a sticking point when I did a Source branch change from the 2013 SDK to the Alien Swarm Branch in favor of better development tools. (That engine is really fragmented when it comes to things like that....)
     
    Once I finished that application; LEX, I then started over developing the game using that template. Two months went by, I've done some interesting puzzles in dev textures.but I felt that one element was taking over the vecball power elements. It was more fun to use that than fire balls at boxes. (And it was more work than the Vecballs) So, I paused development on that project to research if that 'thing I added' would be a better focusing point.
     
    I only spent a week on it. I didn't want to work on it too much, just enough to see if it'll work. It turned out, that minus some placement calculation, it was really fun. As you can see, I don't want to talk about it in-case I decide to go with it.
     
    November was approaching again, and it's a special time for me. In 2010, I released my Portal mod Blue Portals with mostly positive reviews. Every November, I like to do something to give a reason for people to revisit it. Last year (2014) I fixed the mod so it'd work with the steampipe update, and last November, I decided to put the mod on Steam as an "Anniversary Edition"
     
    Although we had massive support about Blue Portals on Steam, there was things I wasn't happy about, and prevented me from putting it on Steam earlier. Some things didn't age well and I've made some poor design choices. There was also some criticism over the difficulty curve (which is kind of common with Portal mods), and somethings just broke. I can spend a few months patching it up, or I can take the extra time and reevaluate the entire project, and have it be a quality mod on Steam. Plus, if the difficulty curve was adjusted, more people can actually play it!
     
    This first started as a revamp project, but slowly became it's separate entity with it's own needs. I'm learning a few new things I've haven't known before, and it's interesting how some concepts/techniques I've learned using Leadwerks came into play here. I was first very worried about Vectronic being on the back burner, but then I realized that it's actually a good thing I take a break from Vectronic for the following reasons:
     

    I can experiment with marketing, community management, updates with little to no risk. I can try something and see how it goes and decide what I did right and/or wrong with that decision and use it in the future.
    I can get used to Steamworks, and have an idea going into Steam Greenlight how it's gonna be, which is a plus over most indie devs who need to take time to learn that stuff.
    Blue Portals: Anniversary Edition can be/will be used as an advertising piece for future projects, I can comfort future consumers with an example of our work before they vote on Greenlight or purchase it on Steam.
    I was really unsure where to take the project, so coming back with a clear head would help. I have a bunch of ideas now, but not gonna jump into anything now.

     
     
    I see the Blue Portals: Anniversary Edition project (BPAE) as a stepping stone for marketing and consumer confidence. Since the mod can be downloaded with zero risk to a consumer, I feel better taking chances with it then I would a product I'd wish to sell. So what's the battle plan?
     

    Continue to develop BPAE. I'm aiming for a December release. Since most research and development have been conducted five years ago, I think if my team and I keep working at it, we can have a polished product on Steam which in return can help sales with Vectronic. I plan on starting to experiment with Marketing next month. I really want to find that "How Early is the right time to show?" mark. My first guess is "When the project has an established direction, but nothing is 100% final just yet.". Which for this project is short of 6 months of development.
     
    Continue to chip away at LEX2 as it's going be the new foundation. Unlike it's predecessor, the solution is very minimal and allows for easy C++ entity implementations. I want to participate in the next Tournament using it to make sure it's all solid and good.
     
    When BPAE is wrapping up, start programming the yucky gross parts like the player and some custom collision rules. Start thinking how everything will tie together and use the new things I learned with BPAE while making design and development choices. I think doing this while BPAE becomes a touch up project will allow me to not feel like I'm doing the same things on two projects.
     
    Start the true cycle of development for Vectronic once BPAE is submitted to be released on Steam.

     
     
    So that's the plan. Taking this one at a time. I'll keep you posted on what I'm doing like always. Although Blue Portals: Anniversary Edition isn't a Leadwerks project, I still think me writing my findings with it can benefit others while making their games. We're all learning here!
  7. reepblue
    With the recent popular substance shader by Shadmar, more talk and focus has been about cubemap reflections surfaced in the community. Reflections in Leadwerks were done in the past with the SSLR post process shader which made all surfaces reflect the world in real time.
     
     
     
     
    However, it had the following problems:
    It was applied to ALL materials in the scene. If you had a material that wouldn't normally reflect such as concrete or wood, the shader would still effect those materials. Basically, the texture artist had little control.
    The post process shader needed to be first on the list, and some people didn't understand that.
    You could adjust the reflections by the model's alpha color. But if you had any shader that relied on the alpha mask for map blending or something else, you're out of luck!
    The reflections distorted based on camera angle, and not really great for in-game action.
    Like everything OpenGL, there is/was a chance it wouldn't work well with AMD gpus.

     
    This was all we had until in February, Mattline1 posted a blog on his PBR system, which included a block of code that built a world cubemap and swapped the cubemap texture with it. Thankfully, he uploaded is source code so anyone who has the professional edition could download his code and have a look.
     
    While his implementation is very nice, it has the following design flaws for a typical Leadwerks user.
    You needed to use only the PBR shader. Normal Leadwerks shaders didn't render well.
    Needed a Post process effect to adjust the gamma of the scene.

     
    Weeks later, Shadmar uploaded his version of a Substance/PBR shader which allowed users to still use their existing shaders, and gave artists more to work. Since it's a shader, and not a full implementation like Matt's, it didn't have the world reflection system. I've been busy with a lot of Source engine work, but seeing how much interest this is getting lately, I decided to take a few days off and see if I can make my own cubemap system.
     
     
    The Implementation
     
    A disclaimer, this is not a full "How-To" as you'll need to add it to how your application, and it'll most likely do things differently than how I have my application set up.
     
    First, we need to figure out how we are gonna get a world cubemap to a texture. While it's not the most "next-gen" way of doing this, I decided to base my system off of the Source engine because, well I know how it works, and it's pretty simple. It goes like this:
    Mapper places a env_cubemap entity near a shinny surface. (Any material set to use cubemaps.)
     
    Mapper compiles and runs the engine.
     
    Mapper than types "buildcubemaps" into the console.
     
    For each env_cubemap entity, 6 "pictures" are taken (up, down, left, right, forward, and back). than those textures are saved within the bsp.
     
    The map restarts, and the surface will use the near by cubemap texture.

     
    Great, that seems very straight forward, and we can remove steps 2 and 3 as there is no compling in Leadwerks, and everything should just work. The first thing we need is a 3D point in the world that'll act as our env_cubemap.
     

    class Cubemap : public Pivot
    {
    public:
    Cubemap(Vec3 pos = NULL); //lua
    virtual ~Cubemap() {}
    ...
    ...
    ...
    };

     
    We build off of the pivot class because we want this to actually exist in our world, not be a "puppeteer" for a pivot entity. We also have the position being set in the constructor as the only way to get our custom entity into the world is by a lua script. Using ToLua++ we can make a script that we can attach to a pivot/sprite.
     

    function Script:Start() local n = Cubemap:new(self.entity:GetPosition(true)) self.entity:Hide() end
     
    This script will spawn our cubemap entity at our editor placed pivot, then hide the editor placed pivot. Basically, we swap the entities during level load.
     
    This cubemap entity job is to search for near by entites, (Which I currently have set to it's aabb radius + a range float) then change it's surface material with a copy of what it had, only with a different cubemap texture. Early in development, I just have the entity fix the cubemap texture to the current world's skybox to make sure that this would work.
     

     
     
    void Cubemap::SetReflectionTextureToSkybox(Leadwerks::Surface* surf)
    {
    if (WORLD == NULL) { return; }
    if (WORLD->skyboxpath == "") { return; }
    if (!surf) { return; }
     
    Material* mat = surf->GetMaterial();
    if (!mat) { return; }
     
    // Make this faster, if there is any indication of a surface not using the substance shader, abort the cubemap application!
     
    // If there is no cubemap assigned, then we can skip this object.
    if (mat->GetTexture(6) == nullptr) return;
     
    // if the metal value is 0, we can skip.
    int matspecmetalness = mat->GetColor(COLOR_SPECULAR).w;
    if (matspecmetalness == 0 ) return;
     
    Material* copy = (Material*)mat->Copy();
     
    if (copy != NULL)
    {
    Texture* cubeTexture = Texture::Load(WORLD->skyboxpath);
    copy->SetTexture(cubeTexture, 6);
    surf->SetMaterial(copy);
     
    cubeTexture->Release();
    copy->Release();
    }
    }

     
    Before we go into building the world cubemaps, we need to add this to the entity's header. This will make it so we can pass a texture to the entity:
     

    Texture* SavedMap;
    Texture* GetCubemap()
    {
    return SavedMap;
    }

     
    Now to the cubemap factory, which on map load will find all of our cubemap entites, do the texture building or each entity, Set SavedMap to the new cubemap texture, then tell the cubemap entity to look for near by entities so it can do it's replacement job. Thankfully, all the yucky stuff has been done by Matt, so I just needed to cut the stuff I didn't need, and make it handle multiple level loads and disconnects to work with my worldmanager class.
     
    Here's a few snip of this process.
     

     
    bool CubemapFactory::initialize()
    {
    Release();
    ...
    ...
    ...
     
    int i = 0;
    do
    {
    if (WORLD->GetEntity(i)->GetKeyValue("cubemap", "0") == "1")
    {
    Cubemap* pCubemap = dynamic_cast<Cubemap*>(WORLD->GetEntity(i));
    if (pCubemap != NULL)
    {
    // Build the cubemap
    cubeTexture = Leadwerks::Texture::CubeMap(
    reflectionResolution,
    reflectionResolution,
    Leadwerks::Texture::RGBA,
    Leadwerks::Texture::Mipmaps
    );
     
    cubeTexture->BuildMipmaps();
    Leadwerks::OpenGL2Texture* gl2cubeTexture = dynamic_cast<Leadwerks::OpenGL2Texture*>(cubeTexture);
    glActiveTexture(gl2cubeTexture->GetGLTarget());
    glBindTexture(GL_TEXTURE_CUBE_MAP, gl2cubeTexture->gltexturehandle);
    glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
    glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
     
     
    // Apply it!
    GenerateReflection(pCubemap->GetPosition()); //<-- generate first reflection texture
    GenerateReflection(pCubemap->GetPosition()); //<-- use initial texture to generate correct texture
    if (cubeTexture != nullptr)
    {
    pCubemap->SavedMap = cubeTexture;
    }
     
    // Tell the cubemap to look for near by entities, and replace it's cubemap texture with cubeTexture
    pCubemap->SearchForEntites();
    cubeTexture->Release();
    }
    }
    i++;
    } while (i < WORLD->CountEntities());
    ...
    ...
    ...
    CubemapFactory::init = true;
    }

     
    GenerateReflection() is pretty much the same as Matt's PBR project. That function actually does the "camera work", and saves the result in a Texture. This is done on the fly during level transition.
     
     
    Skipping Dynamic Objects
     
    There is just one problem however. Everything is being rendered when the cubemaps are being built. So any enemies, or move-able objects will be baked into a reflection. To fix this, we need to have all objects that can move (Mostly have mass) be hidden, and then re-shown after we've finished the building of cubemaps.
     
    Pretty much.
     

    #define S_NULL ""
    #define ON "1"
     
    void ShowMoveables(Entity* entity)
    {
    if (entity != NULL)
    {
     
    if (entity->Hidden() == true && entity->GetKeyValue("hide_for_cubemap", S_NULL) == ON)
    {
    entity->Show();
    entity->AddForce(Vec3(0, 0, 0)); // <-Wake me!
    entity->SetKeyValue("hide_for_cubemap", S_NULL);
    }
    }
    }
     
    void HideMoveables(Entity* entity)
    {
    if (entity != NULL)
    {
    // Things to look for ar entites with the mass > 0, or have their shadow mode non static!
    if (entity->GetMass() == 0 || entity->GetShadowMode() == Light::Static)
    return;
     
    // Only hide the non-hidden!
    if (entity->Hidden() == false)
    {
    entity->SetKeyValue("hide_for_cubemap", ON);
    entity->Hide();
    }
    }
    }
     
    void MapHook(Entity* entity, Object* extra)
    {
    // For cubemap generation, we don't want anything that's set to move to be built in with our cubemaps.
    HideMoveables(entity);
    }
     
     
    bool WorldManager::Connect(std::string pMapname)
    {
    ...
     
    if (Map::Load(pMapname, &MapHook) == false)
    return false;
    ...
    return true;
    }
     
     
    //--------------------------------------
    // For cubemap building!
    //--------------------------------------
    void WorldManager::MakeMoveablesVisible()
    {
    vector<Entity*>::iterator iter = entities.begin();
    for (iter; iter != entities.end(); iter++)
    {
    Entity* entity = *iter;
    ShowMoveables(entity);
    }
    }

     
    Then back in the initialize function of our cubemap factory before we return true.
     

     
    // Lastly, redraw moveables!
    worldmanager.MakeMoveablesVisible();

     
    And now anything that has mass, or has it's ShadowMode set to Dynamic will not be baked into the reflection!
     
     
    Cleaning up
     
    To make it so that this can work with multiple level loads and when the game "disconnects" (Not in a map), we need to reset everything.
     
    Cubemap factory:
     

     
    void CubemapFactory::Render()
    {
    if (WORLD != NULL || worldmanager.IsConnected() == true)
    {
    if (!CubemapFactory::init) { CubemapFactory::initialize(); }
    WORLD->Render();
    }
    }
     
    void CubemapFactory::Release()
    {
    System::Print("Clearing Cubemaps!");
    CubemapFactory::init = false;
    if (cubeTexture != NULL)
    {
    cubeTexture->Release();
    }
    CubemapFactory::currentContext = nullptr;
    CubemapFactory::reflectionCamera = nullptr;
    CubemapFactory::cubeBuffer = nullptr;
    CubemapFactory::cubeTexture = nullptr;
    }

     
    And instead of calling world->Clear in my world manager, I instead call this function:
     

     
    void WorldManager::ClearWorld()
    {
    CubemapFactory::Release();
    world->Clear();
    }

     
    Calling CubemapFactory::Release() may not be necessary as it's called everytime the factory is initialized, but it doesn't hurt to be safe!
     
     
    Conclusion
     
    It was a ride getting this to work correctly. I'm still not happy about how the cubemaps look for near by entities, but it's better than what we have now, which is nothing, Apperently, Josh is concidering this as a engine feature so all of this work might just be temp until he adds it in, which hopefully will be smoother and less error prone!
     
     
    But what we have a system that works like this:
    Engine loads a map, world created, etc.
     
    A map hook hides any entities that has mass or a dynamic shadow mode.
     
    Cubemap Factory takes it's "pictures" much like Source does when buildcubemaps is executed.
     
    Each cubemap texture is given to the right Cubemap entity, then released.
     
    The cubemap looks for near by entites and swaps the model surface's default cubemap with the one it got from the factory.
     
    Cubemap factory tells the world manager to re-show the entities it hid.
    Things continue as normal.

     


     
     
    So that's it! Thanks for reading!
  8. 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.
  9. 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:

     
  10. reepblue
    There has been some discussion regarding on how to set collision shapes for your models. For 95% of models, you should be building shapes with the Model Viewer as described here. In some cases, the model artist might want a custom shape to be made. In this post, I'll be going over how I import models into Leadwerks, and building custom shapes.
    A few notes first. I use Blender; Blender 2.79b to be exact. I haven't get got the hang of 2.80 and until the new engine's art pipeline is fully online, I don't see a use for it. Leadwerks 4 uses Blinn-Phong rendering so the PBR stuff makes no sense for Leadwerks 4. So for this, I'll be posting screenshots from 2.79b. I should also mentioned that a feature I use in my process isn't present in the Linux build of the editor, which is the collapse tool. (Tools->Collapse). Doing the collapsing via a terminal will cause the models to crash the editor. This seems to be a known bug, as you don't see that feature in the Linux editor.
    Lets say you created a tube model such as this one and you want the player and objects to go into the tube:

    If you tried to make a shape doing the Concave settings, not only it'll be really slow to generate, but the results will not be good. We could make a shape based on the wire frame, but this is a high poly model. What we need to do is make a new mesh, import both models  to the editor, collapse them both, build the shapes for both, and delete the low poly model while making the high poly read the low poly's generated shape.
     
    First to get it out of the way, apply the scale and rotation of the model. This will make Y forward and the scale will be (1,1,1) when you import it into Leadwerks.

     
    Next we need a low poly model.

    This is the same proportions as our high poly. Apply the scale and rotation as the same as the high poly. I also set the max draw time to solid, but this is optional.

    Next, name your High poly and the low poly you're going to be using for the shape appropriately.

    Now lets, export each object as a FBX. For this my high poly is going out as tube.fbx, and my low poly shape is going out as tubeshape.fbx. Here are my export settings:

    If you saved the files in a Leadwerks project while the editor was opened, the editor would have auto convert the files to the .mdl file format. Open the high poly model (tube.fbx) and first collapse it and give it any shape. (Give it a box shape to save time.) you need to assign a shape to the high poly so the mdl file is linked to a phys file. Do the same with the low poly, but you're gonna set the shape as poly mesh.


    Close the model viewer, and then go into the directory where the models are placed. We are now going to delete the box shape of our high poly, and trick it into loading the low poly shape by renaming the shape file of the low poly to be what the previous shape of the high poly was. In other words, we are making tubeshape.phy into tube.phy.
    Before:

    After:

    Notice the time stamp and the size of tubeshape.phy from before being the same as tube.phy in the after screen cap. This should be your end result.

    Notice that the shape isn't sold but now a tube. Objects can go into the tube with no issues. Now, there is another way that uses limb names to generate physics automatically. However, there are a lot of issues I came across using this method such as the shape not being parented to the model when the model moved via physics or a joint. With this way, you have a custom shape, the model is optimized because it doesn't have any children nodes, and everything is clean and tidy!

     
  11. 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. 
     
  12. 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!
     
  13. reepblue

    Cyclone
    As a teenager, I've spent thousands of hours with the Source SDK. It was really cool how Valve gave people the real tools to develop and create maps and mods. Overtime I got to see and take apart games to see how they actually work.
    Overtime, the indie engine market took over, and making Source mods is now just a terrible experience. Hammer crashes more frequently and the SDK branch of the engine has old bugs that were never patched. I want the excitement I had of making maps and cool features to be part of Cyclone as a package. Leadwerks has a very intuitive interface, and with a Lua Sandbox, many possibilities open up.
    As of the last update shipped today, anyone with a license of Leadwerks Game Engine has the ability to create custom maps and scripts and share them with others. This is the first step to see if anyone would be interested if they had the tools in their holster.
    There's now a batch file called "create_mod_env.bat" which will create a Leadwerks project within the game directory by default. You can edit the batch file to define a different path if you wish. You'll also receive a few example maps showing the logic of the game. Import the project in Leadwerks, and you should be good to go! When you're ready to share, zip up the map and chapter script and tell people to install it in a folder called "Addons" within the game's directory.
    While this is only the first draft of this, I'm interested to see where it will end up. I hope you all find it interesting and motivating.
  14. 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
  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
    I almost let a whole week go by without writing a blog, ahh! I've been doing a lot of thinking and experimenting over the last few days, and talking to other people about Vectronic.
     
    Last week, I talked about improving functionalities of the pickup system and the platforms. Although the pickup system is near done, the platform system, well they need work. I made a topic about them here, and kind of left me on a stand still on the project because I see them as a vital piece of the game, and they need to be perfect 100% of the time.
     
    As I mentioned before Vectronic always seemed to feel and play like a demo. there was never more than 3 maps, and the ideas were very basic. The method of slopping out maps quickly, and fine tuning them later is fixing that as there are way more maps than ever, but unfortunately I'm noticing a problem with the actual core gameplay itself, something I would never notice with just a few demo maps.
     
    It seems as though although the box has cool effects attached to them, they mostly need environment elements to really make them shine; which is a problem. The question is, is it the powers attached to the balls, or is it that the balls can only activate the boxes. And if the balls can activate other elements, how would they work, and what problems would it solve? I went back to the project what Vectronic morphed out of to see if those ideas can fix problems that Vectronic has.
     




     
    In the old project, the power was tied to each box, and the player would fire electric bolts at them to activate or deactivate them. I had a cube that reversed it's gravity, (Along with other cubes because I didn't know how to make it so only THAT cube flipped) one that stayed static in the air, a cube that exploded, and a cube that did nothing at all. This method had problems, which I talked about here.
     
    The idea of the flip cube was something I really loved, but I couldn't figure out how to only make that one cube reverse gravity while the others remained normal. Out of curiosity, I decided to quickly script this cube and simply have it flip when the F key was pressed. I got the effect I wanted, but it's not flawless. what I did was when the cube was told to flip, it would use a joint to go to the ceiling. You can ride it up, but the player would clip through the ceiling which I think I can fix on the player side, and you can't pick up a box when the box wants to go up; but again I think I can fix that.I also have ideas on how to make the ghost cube more fun and interesting.
     
    But then comes the question is this really a problem, or is it just me working on an idea as a demo for such a long time that I find it boring? I'm asking a lot of people for input and one person plans on give me a playable prototype of his idea so I'm looking forward to that. I want Vectronic to be the best it can be, and if it ends up being different than the demo or what it ever was, but a lot more fun, so be it.
     
    Besides gameplay, I've also been playing with tolua++, and getting ideas what I wish to do with LEX in the future. There is also the Halloween Game Tournament so maybe my head will clear up when I'm working on that. There have also been some experimentation on art, but I'm not ready to talk about that yet.
  17. reepblue
    Another week, another weekly report. Vectronic is going more smoothly then ever before taking things one at a time, and just shelling out maps and ideas quickly. Last week, I did a few improvements with how the player picks up objects, and fixing the issue of shooting projectiles through objects. I did not shell any more maps because I thought it would be better to focus on a small amount of puzzles at a time, perfecting them before making more maps. For this, I needed help.
     
    As of this week, I quietly put the first test build on in the Dropbox folder which used to host the Source build, and asked a small number of people to test out movement, mouse control, and if everything feels right before I make things more complex.
     
    From the first week of testing, I'm confident that the project is moving in the right direction. From one tester who has played all other builds of Vectronic told me that this current build has better puzzle direction and clarity, and full of gameplay unlike previous builds that was just a bunch of mechanics, pretty rooms, but no gameplay at all. I got recommended a few changes I should make, like making the box heavier so when it crashes to the floor after floating in the air, it does not bounce off a button, or adding a plane of glass to block a more frustrating solution. A few tweaks still need to be made, solutions should just work, and not fully relying on the physics engine. I recall this being a problem with Quadrum Conundrum.
     
    Word of advice though, I learned that play testers hate fully ambient lit rooms, so before you release for testing, atleast put fill lights in. One tester told me it was hard to perceive the room with a full ambient light. I have one map with fill lights as a test, and he reported that when he got to that map, he had a better experience.
     
    I also found out that this method on leak protection didn't work when you packaged your games. It failed to initialize Steamworks although Steam was running. Not sure if this has to do with how packaged games work, or something I did wrong. I could of just uploaded the raw project, but that's a difference of 88MB vs 18MB. I'll work on this for the next build which I plan on posting a new test build biweekly.
     
    VecBall Design
     
    Yesterday, instead of writing this blog post, I was in an art mood, and wanted to put something back online with a model and sounds. I was using the old ball sprite and particle effects from the Demo, and I was really unhappy on how the dispenser looked in Leadwerks compared to how it was in Source. I decided if I was gonna touch anything art related, it was gonna be the basic foundation of the Vecballs themselves.
     
    I already had an idea of how I wanted my balls to look in the previous build. I'm a huge fan of how the Portals where in Portal in pre-release builds, and I wanted to reference that.
     



     
    This portal design required a large amount of textures for the mask, overlay, and other things that portals need. These portals took a whopping 174MB of VRAM PER PORTAL. and this was 2006/2007. I'm told that Valve cut it only because they ran out of memory on the Xbox360. Besides them being memory hoggers, I'm happy that I have them in a mod where they work exactly like how they did in previous builds.
     
    Unlike Portals, I can do a sprial/swirl effect with my vecballs without going over budget. I really liked the idea of the balls being a swirling ball of energy, so this could work. I made a quick texture and effect before I focused more on LEX. I later put what I've did in the Vectronic Demo.
     
    I pretty much just copy and pasted my swirl edits I had before into this build with a few changes. It still needs work, but it's pretty much the same as it is in the Demo, at least for now.
     



     
    Now to the dispenser. What makes Vectronic diffrent from Portal is that you loose your vecballs after each level, requiring you to recollect them in the next map. When I first sat down for the design of the dispensers, I sorta saw them as the item pickup bases in multiplayer games like Unreal Tournament. In Source, the particles stood tall and really stood out. But when I was transferring the design to Leadwerks, no matter what, they appeared washed out. This might be because of the colors, but I could not replicate how they appeared in Source.
     
    So I decided to take the dispenser back to the drawing board. I felt that the bases were too small, and needed a face lift. I quickly made this out of a cylinder in blender, added some base colors, and called it a day. UV sheet is still bad though, something I always have problems with.
     



     
    Attached a few emitters and sprites to it and got this in-engine.
     



     
    This is still not final as the ball sprite can still get washed out in the environment. I'm not gonna worry about it until I start messing with post processing effects and the overall look of the maps. I'm also open to suggestions. It's good for now, and it's a hell of a lot better then how they are in the demo!
     
    So that wraps this weeks report up. I guess this weeks goal is to ask more people to test the seven maps I have and seeing how I can improve those and other things before moving forward.
  18. reepblue
    I've started on my quest to make a full C++ has started today. I've noticed that LEX2 via the project manager didn't copy over as clean as it should, so I had to make the project from scratch which wasn't a big deal (for me). I should look into a way of getting that fixed.
     
    After setting the project up, I noticed a few errors I was getting after I commented out the LUA_GAME preprocessor. It was a quick fix, and development went under way after that.
     
    For this project, I'll need things that's already available in Lua as part of the SDK. The high priorities are the first person player, animation manager, first person weapon (the pistol) and the Crawler's AI. As I said before, I don't want to convert SDK scripts to C++, but instead write the code myself. I'll look at the Lua stuff only if I get stuck.
     
    Today I found out that not everything will work when you convert Lua code to C++. I decided on the player as I've written it for C++ with my actor class, then converted it to Lua because of problems. Now I want to but it back to C++ as a puppeteer. It took a few hours line by line converted the code. When it was all done, I ran into a few issues.
     
    First off was HOW to spawn it? I could use a puppeteer script like I mentioned in my last blog, but the player should be spawned dead last. So the best method was to create the player in LEX2's OnPostMapLoad function.
     

    // This function is called when the program finished loading a map.
    void OnPostMapLoad()
    {
    Entity* puppet = WORLD->FindEntity("Player");
    FPSPlayer* player = new FPSPlayer(puppet);
    }

     
    This function is called at the every end of the level loading process. This is mostly so we don't hear the weapon being picked up while the map is loading, or a spawn sound like the Vectronic Demo.
     
    I was then running into null values that were driving me crazy. I went back and predefined all empty values as NULL and I was still getting a crash. It turns out, I didn't have the bool function returning the right value.
     
    Next was the footstep sounds. As I said before, I copied my fully working Lua player script and I noticed that the footstep sounds were playing too fast. I defined the value lastfootsteptime as an int in the header as 0, but when I did a system print of that value, it'd always return as 1. the fix was to define the variable before the footstep function instead of in the header as so:
     

    int lastfootsteptime = 0;
    void FPSPlayer::UpdateFootsteps()
    {
    if (lastfootsteptime == NULL) { lastfootsteptime = 0; }
     
    // If we're moving and not in the air, play a step sound.
    if (input[0] != 0 || input[1] != 0)
    {
    float speed = entity->GetVelocity().xz().Length();
    if (entity->GetAirborne() == false)
    {
    if (speed > MoveSpeed*0.5)
    {
    long t = Time::GetCurrent();
    long repeatdelay = 500;
    if (t - lastfootsteptime > repeatdelay)
    {
    lastfootsteptime = t;
    PlayFootstepsSound();
    }
    }
    }
    }
    }

     
    Last, I had to fix a Gimbal lock with picked up objects. This was because all variables in Lua and C++ work differently. So when going the lines one by one, I stored rotation values in a Vec3, and not a Quat.
     
    After the first day of my summer game, I got a player that walks around, jumps, can crouch under tight spaces with working sounds! I just got a few things to tweak before going to the next thing which will probably be the animation manager.
  19. reepblue
    With the new engine coming along and me noticing limits with Cyclone's current architecture, it's time for me to start thinking about the mistakes I've made and how would avoid repeating them with the new engine. Luckly, Ultra Engine is far more flexible than Leadwerks thanks to smart pointers and the event system. I wish to share some of my plans going forward.
    Log, Log, Log Everything!
    I personally never look at my log files. I instead look at the CMD output and the debugger to see what's going on. However, end users don't have that luxury. The engine will automatically log its warnings and error messages, but it'll not tell you when that message was printed or the events that led up to it. Creating your own log stream like this will give you far more control over your log file.
    // Prepare the log file. auto logfile = WriteFile("Game.log"); // Main loop bool running = true; while (running) { while (PeekEvent()) { const auto e = WaitEvent(); if (e.id == EVENT_PRINT) logfile->WriteLine(e.text.ToString()); } } logfile->Close(); logfile = NULL; You can easily do add time stamp before the message, so you have an idea when the event actually happened. You can use real world clock time or the current application time.
    Do Nothing OS Specific (Unless It's Aesthetic)
    Cyclone is a Win32 application. Period. It works wonderfully on Proton and there isn't any reason for me to install an outdated version of Ubuntu and waste time making a native build for Linux that'll only sometimes work. I took the liberty of using the Windows API to create my own Input System, XInput integration, and the Splash Window. I also created a custom DLL for my Input System so the Action Mapper application would use the same system as the game. 
    Going forward, I want my code to work wherever Ultra Engine can run on. This means sticking to the API for mostly everything unless I need to use a 3rd party library (Like Steamworks). Still, 98% of your users will be using Windows so you might as well add some nice touches like the application knowing what theme the user is using and dressing your application accordingly like this.
    Launch Arguments != Settings
    If you've programmed with Leadwerks, you should be familiar with System::SetProperty() and System::GetProperty(). This by default will register a launch argument as a setting and then save it to a settings file. Cyclone works like this too, but arguments don't get saved to the settings file to avoid confusion. 
    I never used arguments to override settings. I only used -devmode, -console, -fullscreen, +screenwidth and +screenheight. So, instead of mixing launch arguments with user settings, I think just going to keep them separated and only use the arguments to tell the application how to launch. 
    Everything Will Be One
    Cyclone ships with 2 applications. One being the main game, and the other being the Action Mapper for key binding. They both share a dynamic library. This is because Cyclone is made using Leadwerks and the Action Mapper is made using Ultra App Kit. I want to make a Console window to remove it from the UI and a dedicated window for graphic settings would be handy. My new approach will make these separate windows that can be called with a launch command. 
    Compartmentalize Everything
    I ran into many conflicts when adding working on the Flag Run update. Cyclone was structured to be a Puzzle game, and here I was adding clocks and an auto reload system. I had to do a lot of edits to my code to make it work and not break the older maps. 
    Because of how Leadwerks worked, I had to do a lot of mixing and cross referencing of classes. In the end, it's all very messy now and it discourages me from adding to it more. My game application code will only create the window, framebuffer and camera. It's also will be responsible handle scenes and emit events. The components should do everything else. I plan to make a component for the always existing camera entity and have it listen to events when to show UI elements or change its settings. I can make the UI and settings components separate too! 
    Since there will be multiple "apps" in one application, I kind of have to do this. Thankfully, I don't have to do things like create my own reflection system this time...
    Get The Essentials Right, The First Time
    When you're making your first game, you generally want it up and running as soon as possible. Everything else critical you'll say "Meh, I'll worry about that later". Why would you spend time on things like an Input System or A Sound System if you don't even know the game is fun? A month or so later, you find yourself figuring out how the new systems you just built should be integrated with the existing game code. It's not funny, it's not fun. 
    I already have a game, and I know what works so I don't need to repeat that again. And being that the engine isn't 100% yet, I have plenty of time to get the essentials right. Here is what I'll be spending the first few months on.
    Ensuring that windows display and activate correctly. A splash window should show up, and the next window should be brought forward 100% of the time. Cyclone has a bug right now which a game window doesn't get brought to the top although I'm using every API call to make it do so. Settings system should be in-place before drawing a 3D cube to a framebuffer.  I have a very good input system in Cyclone already, but it relies heavily on WinAPI to work. I also include XInput code which gets overwritten by Steam Input anyway. The input system will now need to use the engine's API instead of Windows.  This system will work exactly the same as I have it today, but I think I'm going to leave out controller integration for now. I really liked using FMOD, but to lower dependencies, I think I should try the new OpenAL-Soft sound engine. I plan for my new system to read json scripts for sound entries that contain data for the Speaker and close captioning to display on the screen.    I plan to get cracking within a few weeks! I set up my PC with the RTX 3600 running Windows 10 to develop and stream to Discord! I noticed a lot of slow down with my GTX 1050 running the engine in fullscreen so it's time to use something with more power. I am keeping an eye out for a good price on a 4070 Ti though.  
  20. reepblue
    For some reason, I've been seeing a lot of questions on how to add actors created into C++ recently. My first attempt on this was in 2016 with Crawler's Den; A modern remake of the SDK example level. That was on a version of Leadwerks in which there was no official Actor system in place. Today, the engine has an Actor class which can be attached to any entity, but besides some loose examples, it's not well documented. Also there is no official way on linking your entities in the editor with your actors. But today I wish to share with you some insight on how I managed to get not only actors attaching in my maps, but also allowing interaction with the flowgraph system.
    Disclaimer: This is more copy and paste code only tested with Leadwerks 4.
    Base Actor And Actor Factory
    First, you'll want to make a base class off the engine's Actor class that'll allow us to easily grab values from the lua file. Yes, we can have an Actor and a Script attached to the same entity at a time! The idea is that the lua script will be our definition file for our actor to load on map load.
    #ifndef BASEACTOR_H #define BASEACTOR_H #if defined( _WIN32 ) #pragma once #endif #include "stdafx.h" #define ACTOR_KEYVALUE "classname" class BaseActor : public Actor { public: void SetBoolValue(const std::string& pValue, const bool pDefault = false); bool GetBoolValue(const std::string& pValue, const bool pDefault = false); void SetFloatValue(const std::string& pValue, const float pDefault = 0); float GetFloatValue(const std::string& pValue, const float pDefault = 0); void SetIntValue(const std::string& pValue, const int pDefault = 0); int GetIntValue(const std::string& pValue, const int pDefault = 0); void SetStringValue(const std::string& pValue, const std::string& pDefault = ""); std::string GetStringValue(const std::string& pValue, const std::string& pDefault = ""); Vec2* GetVec2Value(const std::string& pValue, Vec2* pDefault = 0); Vec3* GetVec3Value(const std::string& pValue, Vec3* pDefault = 0); Vec4* GetVec4Value(const std::string& pValue, Vec4* pDefault = 0); Entity* GetEntityValue(const std::string& pValue, Entity* pDefault = NULL); void FireOutput(const std::string& pEvent); }; #endif #include "stdafx.h" #include "baseactor.h" //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void BaseActor::SetBoolValue(const std::string& pValue, const bool pDefault) { #ifndef LEADWERKS_5 GetEntity()->SetBool(pValue, pDefault); #else GetEntity()->SetBoolean(pValue, pDefault); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool BaseActor::GetBoolValue(const std::string& pValue, const bool pDefault) { if (GetEntity() == nullptr) return pDefault; #ifndef LEADWERKS_5 if (entity->GetBool(pValue) != pDefault) { return entity->GetBool(pValue); } #else if (GetEntity()->GetBoolean(pValue) != pDefault) { return GetEntity()->GetBoolean(pValue); } #endif return pDefault; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void BaseActor::SetFloatValue(const std::string& pValue, const float pDefault) { #ifndef LEADWERKS_5 GetEntity()->SetFloat(pValue, pDefault); #else GetEntity()->SetNumber(pValue, (float)pDefault); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float BaseActor::GetFloatValue(const std::string& pValue, const float pDefault) { if (GetEntity() == nullptr) return pDefault; #ifndef LEADWERKS_5 if (entity->GetFloat(pValue) != NULL) { return entity->GetFloat(pValue); } #else if (GetEntity()->GetNumber(pValue) != NULL) { return (float)GetEntity()->GetNumber(pValue); } #endif return pDefault; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void BaseActor::SetIntValue(const std::string& pValue, const int pDefault) { GetEntity()->SetString(pValue, to_string(pDefault)); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int BaseActor::GetIntValue(const std::string& pValue, const int pDefault) { if (GetEntity() == nullptr) return pDefault; #ifndef LEADWERKS_5 if (entity->GetFloat(pValue) != NULL) { return static_cast<int>(entity->GetFloat(pValue)); } #else if (GetEntity()->GetNumber(pValue) != NULL) { double x = GetEntity()->GetNumber(pValue); // stored as 54.999999... x = x + 0.5 - (x < 0); // x is now 55.499999... int y = (int)x; // truncated return y; } #endif return pDefault; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void BaseActor::SetStringValue(const std::string& pValue, const std::string& pDefault) { GetEntity()->SetString(pValue, pDefault); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- std::string BaseActor::GetStringValue(const std::string& pValue, const std::string& pDefault) { if (GetEntity() == nullptr) return pDefault; #ifndef LEADWERKS_5 if (entity->GetString(pValue) != "") { return entity->GetString(pValue); } #else if (GetEntity()->GetString(pValue) != "") { return GetEntity()->GetString(pValue); } #endif return pDefault; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vec2* BaseActor::GetVec2Value(const std::string& pValue, Vec2* pDefault) { if (GetEntity() == nullptr) return pDefault; #ifndef LEADWERKS_5 Vec2* test = static_cast<Vec2*>(entity->GetObject(pValue)); if (test != NULL) { return test; } #else Vec2* test = (Vec2*)GetEntity()->GetObjectPointer(pValue); if (test != NULL) { return test; } #endif return pDefault; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vec3* BaseActor::GetVec3Value(const std::string& pValue, Vec3* pDefault) { if (GetEntity() == nullptr) return pDefault; #ifndef LEADWERKS_5 Vec3* test = static_cast<Vec3*>(entity->GetObject(pValue)); if (test != NULL) { return test; } #else Vec3* test = (Vec3*)GetEntity()->GetObjectPointer(pValue); if (test != NULL) { return test; } #endif return pDefault; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vec4* BaseActor::GetVec4Value(const std::string& pValue, Vec4* pDefault) { if (GetEntity() == nullptr) return pDefault; #ifndef LEADWERKS_5 Vec4* test = static_cast<Vec4*>(entity->GetObject(pValue)); if (test != NULL) { return test; } #else Vec4* test = (Vec4*)GetEntity()->GetObjectPointer(pValue); if (test != NULL) { return test; } #endif return pDefault; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Entity* BaseActor::GetEntityValue(const std::string& pValue, Entity* pDefault) { if (GetEntity() == nullptr) return pDefault; #ifndef LEADWERKS_5 Entity* test = static_cast<Entity*>(entity->GetObject(pValue)); if (test != NULL) { return test; } #else Entity* test = (Entity*)GetEntity()->GetObjectPointer(pValue); if (test != NULL) { return test; } #endif return pDefault; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void BaseActor::FireOutput(const std::string& pEvent) { if (GetEntity() == NULL) return; #ifndef LEADWERKS_5 if (entity->component != NULL) { entity->component->CallOutputs(pEvent); } #else GetEntity()->FireOutputs(pEvent); #endif } Now we need a actor factory of some kind. There is probably an easier and modern way of doing this, but this is how I do it. First we create a header file called actorfactory.h
    #ifndef ACTORFACTORY_H #define ACTORFACTORY_H #if defined( _WIN32 ) #pragma once #endif #include "stdafx.h" extern void BaseActorFactory(Entity* pEntity, Object* pObject); #endif // ACTORFACTORY_H Then in actorfactory.cpp, I have the following. 
    #include "stdafx.h" #include "baseweapon.h" #include "clientcamera.h" #include "doors.h" #include "noise.h" #include "fpsplayer.h" #include "logicactors.h" #include "pointmessage.h" #include "propspawner.h" #include "point_transition.h" #include "platform.h" #include "triggers.h" #include "vrplayer.h" #define ATTACH_NAME_TO_ACTOR(_name_, _actor_) if (test == _name_) actor = new _actor_() void BaseActorFactory(Entity * pEntity, Object * pObject) { auto classname = GetEntityKeyValue(pEntity, ACTOR_KEYVALUE); if (classname != "") { BaseActor* actor = NULL; std::string entname = pEntity->GetKeyValue("name", "STATIC_MESH"); std::string test = String::Lower(classname); // Global actor is a dummy actor with values for other actors. if (test == "global") { if (g_mGlobalActor == NULL) { actor = new BaseActor(); g_mGlobalActor = actor; } } ATTACH_NAME_TO_ACTOR("ambient_generic", AmbientGeneric); ATTACH_NAME_TO_ACTOR("door_sliding", SlidingDoor); ATTACH_NAME_TO_ACTOR("door_rotating", RotatingDoor); ATTACH_NAME_TO_ACTOR("point_path", PointPath); ATTACH_NAME_TO_ACTOR("point_message", PointMessage); ATTACH_NAME_TO_ACTOR("point_transition", PointTransition); ATTACH_NAME_TO_ACTOR("point_weapon_pickup", PointWeaponPickup); ATTACH_NAME_TO_ACTOR("prop_spawner", PropSpawner); ATTACH_NAME_TO_ACTOR("logic_relay", LogicRelay); ATTACH_NAME_TO_ACTOR("logic_branch", LogicBranch); ATTACH_NAME_TO_ACTOR("logic_counter", LogicCounter); ATTACH_NAME_TO_ACTOR("train", TrainActor); ATTACH_NAME_TO_ACTOR("trigger_once", CollisionTrigger); ATTACH_NAME_TO_ACTOR("trigger_multiple", CollisionTriggerMultiple); ATTACH_NAME_TO_ACTOR("volume", BaseVolume); ATTACH_NAME_TO_ACTOR("volume_push", PushVolume); ATTACH_NAME_TO_ACTOR("volume_hurt", HurtVolume); ATTACH_NAME_TO_ACTOR("weapon_pickup", WeaponPickup); if (BaseActor::AttachActor(pEntity, actor, true)) { pEntity->SetKeyValue(ACTOR_KEYVALUE, classname); System::Print("Attached actor: \"" + classname + "\" to \"" + entname + "\"."); } else { System::Print("Error: failed to attach actor: \"" + classname + "\" to \"" + entname + "\"."); } } } Each actor gets a "classname" assigned to it. This can be anything you want and it's defined here. I went with a quake naming scheme for personal preference.
    We then call this function in our map load hook as so. This will pass each entity loaded though our factory when the map file is being loaded in.
    if (Map::Load(pszPath, &BaseActorFactory) == false) { Msg("Failed to load \"" + pszPath + "\" as path is invaild/missing from disk!"); return false; }  
    Custom Actors
    Now we have the platform to build our actor, lets do so! Here's sample code of a relay actor. I'm picking this class as it shows an input and output functions being used that'll work with the flowgraph as well as loading values.
    #ifndef LOGICACTORS_H #define LOGICACTORS_H #if defined( _WIN32 ) #pragma once #endif #include "stdafx.h" #include "baseactor.h" class LogicRelay : public BaseActor { long m_intDelayTime; long m_intTriggerTime; public: virtual void Start(); virtual void ReceiveSignal(const std::string& inputname, Entity* sender); virtual void UpdateWorld(); }; #endif #include "stdafx.h" #include "logicactors.h" //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void LogicRelay::Start() { BaseToggle::Start(); m_intDelayTime = GetIntValue("delay", 0); //FireOutput("OnStart"); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void LogicRelay::ReceiveSignal(const std::string& inputname, Entity* sender) { auto _event = String::Lower(inputname); if (_event == "trigger") { m_intTriggerTime = Timing::GetCurrent(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void LogicRelay::UpdateWorld() { if (m_intTriggerTime > 0) { if (Timing::GetCurrent() > m_intTriggerTime + m_intDelayTime) { FireOutput("OnTrigger"); } } } Last we write the lua script:
    --[[ Purpose: A Logic entity that is used for relaying outputs. ]]-- Script.classname="logic_relay" Script.delay=-0--int "Delay" function Script:Trigger()--in end function Script:Outputs() self.component:CallOutputs("OnStart") self.component:CallOutputs("OnTrigger") end Compile your C++ code and attach your script to your entity. If all is well, you should have a working actor in your game! 
     
    Remarks
    This is the best and simple way of getting actors to work in your game much like the lua alternative. However, as a C++ user I can ensure you that your adventure isn't over. First you're going to have to figure out how actor members are deleted and when they should be deleted. The clear function in the world class will release all engine actors, but not assets so you need to take that into consideration. Also, I personally ran into issues with sound Source classes (Now called Speaker in Leadwerks 5) not being recreated when the map was reloaded. I ended up having to make my own source management system. To be honest, I forgot how it works since I haven't really touched my project in months. 
    Even after all my workarounds, I still have things not releasing properly sometimes, but if you're smarter than me, I'm sure you can figure it out.
     
    But I wrote this blog so if anyone had questions on how to use actors, You or I can point them here. Happy coding! 
  21. 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.
  22. reepblue

    Code
    Cyclone was shaping up visually, but I was getting feedback about the sound implementation not being so good. I previously created a script system that defined all my sounds under profiles. This was a step in the right direction and made it easier to tweak sounds without recompiling code. However, no matter how much I played with variables, I never was able to satisfy the complaints. 
    After I showcased my first gameplay trailer, I decided to finally sit down and really understand FMOD. I was really overwhelmed at first as I was looking at the core api, but then I was suggested to use the FMOD Studio API instead. 
    FMOD studio is a nifty program that allows much more fine control how sounds are played in a 3D environment. You can also add parameters to alter the sounds with code in real time. In this example, the engine sound can be changed via the RPM and Load values given. (You can check the examples of the SDK on how to do this.)

    FMOD Studio packs everything into bank files. You can have as many banks as you want, but for my game, I only use one. 
    To load a bank file, I just do a for loop in a directory called Media.
    void Initialize() { if (!initialized) { Msg("Initializing FMOD sound driver..."); FMOD::Studio::System::create(&system); system->getCoreSystem(&coreSystem); coreSystem->setSoftwareFormat(0, FMOD_SPEAKERMODE_DEFAULT, 0); system->initialize(1024, FMOD_STUDIO_INIT_NORMAL, FMOD_INIT_NORMAL, NULL); system->setNumListeners(1); system->update(); //Load any .bank files in main directory Leadwerks::Directory* dir = Leadwerks::FileSystem::LoadDir("Media/"); 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 == "bank") { std::string fullpath = Leadwerks::FileSystem::RealPath("Media/" + file); Msg("Loading FMOD bank file \"" + fullpath + "\"..."); FMOD::Studio::Bank* bnk = NULL; FMOD_RESULT err = system->loadBankFile(fullpath.c_str(), FMOD_STUDIO_LOAD_BANK_NORMAL, &bnk); if (err == FMOD_ERR_FILE_NOTFOUND || err == FMOD_ERR_FILE_BAD) Msg("Error: Failed to load FMOD bank file \"" + fullpath + "\"..."); } } delete dir; } initialized = true; } } Another thing to note is that you manually need to update FMOD each frame. I've created my own wrapper update function and call it in my Stage class. This also calls my Initialize function.
    void Update() { FMODEngine::Initialize(); if (!initialized) Initialize(); if (system != NULL) system->update(); } Now, that we have the basics in, we first need a listener which was actually the hardest part. At first, I wasn't sure how I can match the location and rotation calculations from Leadwerks to FMOD. I first tried implementing the position and thankfully, FMOD uses the Left-Handed system by default. 
    Rotation was a bit difficult for me but grabbing random code from stackoverflow saved me again and pushing the Y rotation 90 degrees made the 1:1 match. 
    void UpdateListenerPosition(Leadwerks::Entity* pOwner) { if (pOwner == NULL || system == NULL) return; Leadwerks::Vec3 entity_position = pOwner->GetPosition(true); // Position the listener at the origin FMOD_3D_ATTRIBUTES attributes = { { 0 } }; attributes.position.x = entity_position.x; attributes.position.y = entity_position.y; attributes.position.z = entity_position.z; float radians = Leadwerks::Math::DegToRad(pOwner->GetRotation(true).y + 90); float fx = cos(radians); float fz = sin(radians); attributes.forward.x = -fx; attributes.forward.z = fz; attributes.up.y = 1.0f; system->setListenerAttributes(0, &attributes); } There's nothing to create since a listener is initialized with the system. You just need to update its attributes each frame. I call the above function in my UpdateMatrix hook for my main camera. 
    Last, we need a way to play simple sounds. 2D and 3D sounds are defined in the bank file, but to play a sound with no 3D settings, it's as simple as the following.
    void EmitSound(const std::string& pszSoundEvent) { FMOD::Studio::EventDescription* pDest; std::string fulldsp = "event:/" + pszSoundEvent; system->getEvent(fulldsp.c_str(), &pDest); FMOD::Studio::EventInstance* instance; pDest->createInstance(&instance); instance->start(); instance->release(); } And to play a sound at an entity's location, this has been working for me so far.
    void EmitSoundFromEntity(const std::string& pszSoundEvent, ENTITY_CLASS pEntity) { FMOD::Studio::EventDescription* pDest; std::string fulldsp = "event:/" + pszSoundEvent; system->getEvent(fulldsp.c_str(), &pDest); FMOD::Studio::EventInstance* instance; pDest->createInstance(&instance); FMOD_3D_ATTRIBUTES attributes = { { 0 } }; attributes.forward.z = 1.0f; attributes.up.y = 1.0f; attributes.position.x = pEntity->GetPosition(true).x; attributes.position.y = pEntity->GetPosition(true).y; attributes.position.z = pEntity->GetPosition(true).z; instance->set3DAttributes(&attributes); instance->start(); instance->release(); } All you need to pass is the name of the event. If you decided to use subfolders, you'll need to include them in the parameter. It acts like a virtual directory.
    For anything with more control (such as loops, playback, etc) we're going to need our own Source/Speaker class.  This class just stores the event instance and acts like the standard Source/Speaker class.  I also support both the engine's OpenAL implementation and FMOD via a switch, but my actors only care about this game speaker class and the EmitSound functions. 
    class GameSpeaker { #ifdef USE_FMOD FMOD::Studio::EventDescription* m_pDest; FMOD::Studio::EventInstance* m_pEventInstance; int m_iLength; #endif // USE_FMOD Leadwerks::Source* m_pOpenALSpeaker; protected: Leadwerks::Vec3 m_vPosition; int m_iState; public: GameSpeaker(); ~GameSpeaker(); void Play(); void Stop(); void Pause(); void Resume(); void Reset(); void SetVolume(const float flVolume); void SetPitch(const float flPitch); void SetPosition(const Leadwerks::Vec3& vPosition); void SetTime(const float flTime); const float GetVolume(); const float GetPitch(); const float GetTime(); const int GetState(); const float GetLength(); const Leadwerks::Vec3& GetPosition(); void BuildSpeaker(const std::string& pszEventName); static GameSpeaker* Create(const std::string& pszEventName); static const int Stopped; static const int Playing; static const int Paused; }; extern std::shared_ptr<GameSpeaker> CreateGameSpeaker(const std::string& pszEventName); I still have very minor things to work out, but overall, this has been a success. This also could be implemented exactly the same way in Ultra Engine if you're reading this in the future. 
    Basic FMOD Implementation in Cyclone - Work in Progress - Ultra Engine Community - Game Engine for VR
  23. reepblue
    Premake is multiplication project maker.Unlike CMake, it simply generates a project file for the given IDE giving you a clean result. You only need the one light weight executable and a lua script for this to work.  I've spent today setting it up with Leadwerks. I haven't tested Linux yet, but it should work.
    My premake5.lua file:
    g_LeadwerksHeaderPath = "./Engine/Include" g_LeadwerksLibPath = "./Engine/Libs" function GlobalSettings() -- Include Directories includedirs { "%{prj.name}", "%{g_LeadwerksHeaderPath}", "%{g_LeadwerksHeaderPath}/Libraries/SDL2-2.0.10/include", "%{g_LeadwerksHeaderPath}/Libraries/NewtonDynamics/sdk/dgCore", "%{g_LeadwerksHeaderPath}/Libraries/NewtonDynamics/sdk/dgNewton", "%{g_LeadwerksHeaderPath}/Libraries/libvorbis/include", "%{g_LeadwerksHeaderPath}/Libraries/libogg/include", "%{g_LeadwerksHeaderPath}/Libraries/openssl/include", "%{g_LeadwerksHeaderPath}/Libraries/VHACD/src/VHACD_Lib/inc", "%{g_LeadwerksHeaderPath}/Libraries/glslang", "%{g_LeadwerksHeaderPath}/Libraries/freetype-2.4.7/include", "%{g_LeadwerksHeaderPath}/Libraries/OpenAL/include", "%{g_LeadwerksHeaderPath}/Libraries/NewtonDynamics/sdk/dMath", "%{g_LeadwerksHeaderPath}/Libraries/NewtonDynamics/sdk/dgTimeTracker", "%{g_LeadwerksHeaderPath}/Libraries/NewtonDynamics/sdk/dContainers", "%{g_LeadwerksHeaderPath}/Libraries/NewtonDynamics/sdk/dCustomJoints", "%{g_LeadwerksHeaderPath}/Libraries/RecastNavigation/RecastDemo/Include", "%{g_LeadwerksHeaderPath}/Libraries/RecastNavigation/DetourCrowd/Include", "%{g_LeadwerksHeaderPath}/Libraries/RecastNavigation/DetourTileCache/Include", "%{g_LeadwerksHeaderPath}/Libraries/RecastNavigation/DebugUtils/Include", "%{g_LeadwerksHeaderPath}/Libraries/RecastNavigation/Recast/Include", "%{g_LeadwerksHeaderPath}/Libraries/RecastNavigation/Detour/Include", "%{g_LeadwerksHeaderPath}/Libraries/tolua++-1.0.93/include", "%{g_LeadwerksHeaderPath}/Libraries/lua-5.1.4", "%{g_LeadwerksHeaderPath}/Libraries/glew-1.6.0/include/GL", "%{g_LeadwerksHeaderPath}/Libraries/glew-1.6.0/include", "%{g_LeadwerksHeaderPath}/Libraries/enet-1.3.1/include", "%{g_LeadwerksHeaderPath}/Libraries/zlib-1.2.5", "%{g_LeadwerksHeaderPath}/Libraries/freetype-2.4.3/include" } -- Global Defines: defines { "__STEAM__", "_CUSTOM_JOINTS_STATIC_LIB", "FT2_BUILD_LIBRARY", "LEADWERKS_3_1", "DG_DISABLE_ASSERT", "OPENGL", "_NEWTON_STATIC_LIB", "_STATICLIB" } -- Windows Exclusive: filter "system:windows" systemversion "latest" pchsource "%{prj.name}/stdafx.cpp" links { "libcryptoMT.lib", "libsslMT.lib", "Rpcrt4.lib", "crypt32.lib", "libcurl.lib", "msimg32.lib", "lua51.lib", "steam_api.lib", "ws2_32.lib", "Glu32.lib", "libovrd.lib", "OpenGL32.lib", "winmm.lib", "Psapi.lib", "OpenAL32.lib", "SDL2.lib", "Leadwerks.lib" } libdirs { "%{g_LeadwerksLibPath}/Windows/x86", "%{g_LeadwerksLibPath}/Windows/x86/%{cfg.buildcfg}" } defines { "PSAPI_VERSION=1", "PTW32_STATIC_LIB", "PTW32_BUILD", "_NEWTON_USE_LIB", "_LIB", "DG_USE_NORMAL_PRIORITY_THREAD", "GLEW_STATIC", "WINDOWS", "WIN32", "OS_WINDOWS", "PLATFORM_WINDOWS", "_WIN_32_VER" } buildoptions { "/D \"SLB_LIBRARY\"", } flags { "NoMinimalRebuild" } linkoptions { "/NODEFAULTLIB:MSVCRT.lib", "/NODEFAULTLIB:MSVCRTD.lib" } -- Linux Exclusive: filter "system:linux" systemversion "latest" linkoptions { "-ldl", "-lopenal", "-lGL", "-lGLU", "-lX11", "-lXext", "-lXrender", "-lXft", "-lpthread", "-lcurl", --"-lSDL2", "%{g_LeadwerksLibPath}/Linux/libluajit.a", "%{gameDir}/libopenvr_api.so" } defines { "ZLIB", "PLATFORM_LINUX", "unix", "_POSIX_VER", "_POSIX_VER_64", "DG_THREAD_EMULATION", "DG_USE_THREAD_EMULATION", "GL_GLEXT_PROTOTYPES", "LUA_USE_LINUX", "_GLIBCXX_USE_CXX11_ABI", "_CUSTOM_JOINTS_STATIC_LIB" } linkoptions { "%{g_LeadwerksLibPath}/Linux/%{cfg.buildcfg}/Leadwerks.a" } -- Debug Build: filter "configurations:Debug" runtime "Debug" symbols "on" targetsuffix ".debug" defines { "DEBUG", "_DEBUG" } if os.target() == "windows" then links { "newton_d.lib", "dContainers_d.lib", "dCustomJoints_d.lib" } end -- Release Build: filter "configurations:Release" runtime "Release" optimize "on" if os.target() == "windows" then buildoptions { "/MP" } links { "newton.lib", "dContainers.lib", "dCustomJoints.lib" } end end function GenerateLuaApp() workspace "PremakeTest" architecture "x86" --architecture "x86_64" startproject "LuaApp" configurations { "Debug", "Release" } -- Test application project "LuaApp" kind "ConsoleApp" language "C++" location "%{prj.name}" staticruntime "on" -- Project Directory: projectDir = "%{prj.name}/" -- Game Directory: gameDir = _WORKING_DIR .. "/../Projects/%{prj.name}" -- OBJ Directory objdir (projectDir .. "%{cfg.buildcfg}_%{prj.name}") targetdir (gameDir) files { "%{prj.name}/**.h", "%{prj.name}/**.cpp" } pchheader "stdafx.h" -- Custom Defines defines { "__TEST_ME_", } GlobalSettings() end newaction { trigger = "luaapp", description = "Builds the stock lua app", execute = GenerateLuaApp() } if _ACTION == "luaapp" then GenerateLuaApp() end Then I just have batch file that builds the project file.
    "devtools/premake5.exe" vs2017 luaapp pause My impressions are more positive on this than CMake as CMake creates a lot of extra files to do real time updating if the CMakeList file is updated. Premake simply just builds the project file and that's it. It really reminds me of VPC stuff I had to deal with in my modding days. Really interested to how codelite projects generate on Linux.
  24. reepblue

    Code
    Level transitions are the old school way of getting your player from one map to another. Here I implemented a system much like HL2. Not much else to say, but I should be ready to talk more soon. (Hopefully!) 
     
  25. reepblue
    For the past few months, (on top of working on my greenlit mod) I've been developing a new foundation for Vectronic that will take care of all the 'internal yucky bits' such as window/context/world management, and other features and functions. I really wasn't happy how the first implementation came out as I felt like I was taking too much from the developer, and it was messy and confusing to do something simple like adding a loading screen.
     
    LEX2 is more lua friendly, not bloated with scripts that you may or may not use, and has more comments than my Blue Portals 2 video in the code! You just need the executables, App.lua, and engine.pak which contains standard assets that the application will call if something is missing.
     
    You can play with it here. I've used the latest beta with probes so if you don't have the probe beta, don't load map 07 I guess. Here are somethings you might notice looking at scripts or general usage.
     
     
    Developer Console:
     

     
    LEX2 comes with a developer console for visually debugging your game while you play. Draw stats, show physics, show navigation, or change settings on the fly. All commands use the standard system properties.
     

     
    To access the console, the game must be started with -dev or launched from the editor. When the game is paused, it the tilde key (`) and start typing. Hit Enter to submit your command. You can use PageUp and PageDn to scroll through pass commands.
     
    You can type 'help' to report a list of current commands. There is no display box, so it's best used when the game is windowed with the log from the script editor visible.
     
     
    Asset Manager:
     

    self.sound = Sound:Load(AssetManager:Check("Sound/MySound.wav))
     
    Never crash again because you moved something or mistyped an asset. AssetManager:Check() returns a error asset from engine.pak if what you've sent is not found. The game will not start at all unless the error assets are found at startup.
     
    You can also easily preload models for later use with AssetManager:Precache(string). You can also make a Precache list with AssetManager:PrecacheGlobal(string). This is great if you have a model or prefab that might spawn at anytime in any map.
     
     
    PostStart():
     
    PostStart is a command fired after the level loads instead of during it like Start does.
     

    function Script:Start() self.source = Source:Create() self.source:SetVolume(self.volume/100) self.source:SetPitch(self.pitch) self.source:SetLoopMode(self.loop) self.source:SetRange(self.range) self.source:SetPosition(self.entity:GetPosition(true)) end function Script:PostStart() local sound = Sound:Load(AssetManager:Check(self.soundfile)) if sound~=nil then self.source:SetSound(sound) if self.playing==true and self.enabled==true then self.source:Play() end sound:Release() sound=nil end end
     
     
    Input System:
     

    if Input:Forward(true) then self.input[1]=self.input[1]+1 end if Input:Backward(true) then self.input[1]=self.input[1]-1 end if Input:Right(true) then self.input[0]=self.input[0]+1 end if Input:Left(true) then self.input[0]=self.input[0]-1 end
     
    You can now allow your player to change their controls. by calling a general function. Controls can be controlled with System:SetProperty or by editing settings.config. The control commands are based off of the default steam controller layout. I just need too add previous/next weapon, but wasn't 100% sure how I was gonna do that since it may be a scroll wheel.
     
     
    Basic UI System:
     
    Use Lua to create your own UI with LEX2's exposed UI classes. If you replace the app start and loop functions with this code, it'll make a square in the corner with a text button that says "Leadwerks".
     

    --This function will be called once when the program starts function App:Start() Rect = UIRectangle:Create(4,4,200,200) Rect:SetColor(40, 40, 40) Rect:SetBorderLineWidth(4) Rect:SetBorderColor(92, 92, 92) Rect:EnableBorder(true) Rect:Hide() local testfont = Font:Load(AssetManager:Check("Fonts/consola.ttf"), 20) Rect3 = UIText:Create(Rect:GetCenter().x, Rect:GetCenter().y, "Leadwerks", testfont, "Test"); Rect3:Center(); Rect3:SetShadowMode(false) Rect3:SetAsInteractive(true) Rect:AddChild(Rect3); return true end --This is our main program loop and will be called continuously until the program ends function App:Loop() context:SetBlendMode(Blend.Alpha) -- Here is code to darken the scene when the game is paused. if self:IsPaused() and self:IsConnected() then context:SetColor(0,0,0,0.5) context:DrawRect(0,0,context:GetWidth(),context:GetHeight()) end Rect:Update() if (Rect3:GetMouseEvent() == Rect3.MouseLeftUp) then System:Print("Pressed!") elseif(Rect3:GetMouseEvent() == Rect3.MouseOver) then Rect3:SetColor(100, 100, 100) elseif (Rect3:GetMouseEvent() == Rect3.MouseLeftDown) then Rect3:SetColor(80, 80, 80) else Rect3:SetColor(255, 255, 255) end if window:KeyHit(Key.F11) then if (Rect:IsVisible()) then Rect:FadeOut() else Rect:FadeIn() end end context:SetBlendMode(Blend.Solid) return true end
     
    I haven't really did much with the UI as it's a future feature of the engine, plus I've been playing with other stuff. But with UIRectangle and UIText is pretty much the foundation of buttons, sliders and other stuff anyway. Or you can use another UI library if you want.
     
    Again, you can play with it by downloading the project here:
    https://dl.dropboxusercontent.com/u/16218991/Requests/LEX2Test.7z
     
    I'm really stress testing it now making sure I don't have to touch anything when I'm ready to revist Vectronic. Speaking of Vectronic, it's almost been a year since I've released the demo. I might later this week write a blog about it and my battle plan for it.
×
×
  • Create New...