Jump to content

reepblue

Developers
  • Posts

    2,524
  • Joined

  • Last visited

Blog Entries posted by reepblue

  1. reepblue

    Code
    I've spent the last few months pressing buttons, clicking joysticks and shaking my computer mouse to solve the solution input for Cyclone. Back when it shipped in June, I've created a system that allowed users to assign keys to actions, in which the game would detect as input. My player code never knew what button was pressed; it just knew what action was caused. This is very similar to how Steam Input works.
    There were a few flaws with my original system. Some of which didn't surface until I shipped.
    I stored the Keycode as int32 values. Unless someone had a chart, they couldn't easily bind their keys. Things got really confusing when it came to international keyboards. Not all keycodes are universal. My system only supported buttons. Actions for axis controls were not a thing and were hard coded. The system relied on the Leadwerks API which would cause making a utility app kind of tricky. Mouse aim wasn't generally liked. I wanted a library that did nothing but input. But I got dead libraries or libraries that needed dependencies to work. I wanted Steam Input, but for the Keyboard and Mouse. I knew I couldn't let this sit based on the amount of feedback I was getting because from this. Since nobody else thought it was necessary to make one, it looked like I had to make an input system in-which input has a name.
    Before we get into input, I first had to re-arrange how my repo file structure was set up. I had Cyclone set up as a generic Leadwerks project, but this wasn't going to work if I wanted to create shared libraries and utilities.  Before I did anything stupid with Cyclone, I made a new repo to figure out how everything should be laid out. I decided to follow a file structure much like Valve has their Source engine and use premake to generate the files. Shell scripts are used to generate projects via WSL. This allowed me to compile for Windows AND Linux on the same machine and I did casual build tests on macOS. I never want to write an input system ever again.
     
    Before I could do any form of action detection, I needed to create a "Driver" sort of speak and have the operating system pump events into it. This interface class allows classes to be derived from it and work no matter what driver is created.
    class INPUTSYTEM_API IInputDriver { public: IInputDriver() {}; virtual ~IInputDriver() {}; virtual void EnablePumpEvents(const bool bState) = 0; virtual void PumpButtonDown(const button_t btn, const int controller = 0) = 0; virtual void PumpButtonUp(const button_t btn, const int controller = 0) = 0; virtual void PumpButtonCodeDown(const ButtonCode& btncode, const int controller = 0) = 0; virtual void PumpButtonCodeUp(const ButtonCode& btncode, const int controller = 0) = 0; virtual void PumpMouseWheelDir(const int dir) = 0; virtual void PumpAxis(const AxisCode& axiscode, const float x, const float y, const int controller = 0) = 0; virtual void PumpPointer(const PointerCode& pointer, const int x, const int y, const int controller = 0) = 0; virtual void PumpRumble(const float left, const float right, uint64_t duration_ms, const int controller = 0) = 0; virtual const bool ButtonDown(const ButtonCode& btncode, const int controller = 0) = 0; virtual const bool ButtonHit(const ButtonCode& btncode, const int controller = 0) = 0; virtual const bool ButtonReleased(const ButtonCode& btncode, const int controller = 0) = 0; virtual const bool ButtonAnyDown() = 0; virtual const bool ButtonAnyHit() = 0; virtual AxisVector GetAxis(const AxisCode& axiscode, const int controller = 0) = 0; virtual AxisVector GetButtonAxis(const ButtonCode& up, const ButtonCode& down, const ButtonCode& left, const ButtonCode& right) = 0; virtual void UpdateController(const int controller) = 0; virtual void SuspendControllerInput(const bool bState) = 0; virtual void Flush() = 0; virtual void FlushButtons() = 0; virtual void FlushAxis() = 0; virtual ButtonCode GetLastButtonPressed() = 0; virtual ButtonCode StringToButtonCode(const std::string& btnstring) = 0; virtual const char* ButtonCodeToString(const ButtonCode& btncode) = 0; virtual void SetCursorPosition(const int x, const int y) = 0; virtual void CenterCursorPosition() = 0; virtual ScreenPosition GetCursorPosition() = 0; virtual void MouseVisibility(const bool bState, const bool bRecenter) = 0; virtual void SetPointerPosition(const PointerCode& pointer, const int x, const int y) = 0; virtual ScreenPosition GetPointerPosition(const PointerCode& pointer) = 0; virtual void CenterPointerPosition(const PointerCode& pointer) = 0; virtual void SetActiveDevice(InputDevice device) = 0; virtual InputDevice GetActiveDevice() = 0; virtual bool DeviceChanged() = 0; virtual void Cleanup() = 0; }; You may notice that there is button_t and ButtonCode. The type: button_t is the unit32_t type from the OS and the ButtonCode is button_t reassigned to my own enum structure. This way, my end values remain consistent between OS and drivers. For example, WinAPI pumps Virtual Keyboard flag values while Coco/X11 will pump its own flag definitions. If you wanted to use SDL, all you need to do is make an SDL driver for it and the code on top of it will not care.
    Also note that there are no separate functions to detect button and key presses. There are only Buttons, Axis, and Pointer values when it comes to my input library. I was also going to omit the cursor commands, but it's part of the desktop environment and really can't be ignored. The Set/GetCursorPosition is there for redundancy really. 
    The pump functions are made public to allow external pumping of events. For Windows, you can "hijack" the Window polling with a custom function. Here's an example of how I did it in Leadwerks and the same thing can be done in Ultra Engine for Windows exclusive games.
    LRESULT CALLBACK MyWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { InputSystem::ProcInputWin32(hwnd, message, wparam, lparam); return Leadwerks::WndProc(hwnd, message, wparam, lparam); } int main(int argc, const char* argv[]) { auto window = Leadwerks::Window::Create("Game", 0, 0, 1280, 720, Leadwerks::Window::Titlebar | Leadwerks::Window::Center); if (window == NULL) return 1; // Init the input system. InputSystem::Init(window->hwnd); #ifdef _WIN32 // Swap the callback with ours WNDPROC OldProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr( hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(MyWndProc))); #endif .... That's all it takes to start my input system. Just pass the window handle to the Init function and the library will load the rest. I only have a WinAPI driver right now, but the idea is that of you were building on Linux or macOS, the driver will be created. I still need to work out how polling and key events are processed on those platforms.
    We can get a button press as easily as this:
    auto b = InputSystem::GetDriver()->ButtonHit(InputSystem::BUTTON_KEY_ESCAPE); Josh provided me with insight on using the raw mouse value to return a Vector valve so you can easily obtain it by:
    auto a = InputSystem::GetDriver()->GetAxis(InputSystem::AXIS_MOUSE); You can even do neat things like this:
    if (InputSystem::GetDriver()->ButtonAnyDown()) { auto btn = InputSystem::GetDriver()->GetLastButtonPressed(); std::cout << InputSystem::GetDriver()->ButtonCodeToString(btn) << " is held down!" << std::endl; }  
    Great, we now have an input API like everyone else. Although it's richer and more flexible than what Leadwerks provides, we're not done. We need another layer on top of the driver in which our game will actually use.
    Meet the Action Controller!
    class INPUTSYTEM_API IActionController { public: int32_t controller_port = 0; IActionController() {}; virtual ~IActionController() {}; virtual const int32_t GetControllerID() = 0; virtual void SetActionSet(const char* actionsetid) = 0; virtual const char* GetActionSet() = 0; virtual bool Down(const char* actionid, const char* actionsetid = "") = 0; virtual bool Hit(const char* actionid, const char* actionsetid = "") = 0; virtual bool Released(const char* actionid, const char* actionsetid = "") = 0; virtual AxisVector Axis(const char* actionid, const char* actionsetid = "") = 0; virtual void Rumble(const float leftmotor, const float rightmotor, uint64_t duration_ms = 10) = 0; virtual void FlushAllInput(const bool bCenterPointDevice = false) = 0; virtual const bool NoActionSets() = 0; virtual const Action GetAction(const char* actionid, const char* actionsetid = "") = 0; virtual const int ButtonCount(const char* actionid, const char* actionsetid = "") = 0; virtual const int AxisCount(const char* actionid, const char* actionsetid = "") = 0; virtual const ButtonCode GetButton(const char* actionid, const int index = 0, const char* actionsetid = "") = 0; virtual const AxisCode GetAxis(const char* actionid, const int index = 0, const char* actionsetid = "") = 0; virtual const ButtonAxis GetButtonAxis(const char* actionid, const char* actionsetid = "") = 0; virtual const PointerCode GetPointDevice() = 0; virtual const bool IsKBM() = 0; virtual const float GetSetting(const char* setting, const float defaultsetting = 0) = 0; virtual void SetPointDevice(const PointerCode& pointdevice) = 0; virtual void SetPointDevicePosition(const int x, const int y) = 0; virtual ScreenPosition GetPointDevicePosition() = 0; virtual void CenterPointDevice() = 0; virtual void TogglePointerVisibility(const bool bShow) = 0; virtual const bool GetPointerVisibility() = 0; // Writting of data virtual void SetSetting(const char* setting, const float fValue) = 0; virtual void RegisterActionSet(const char* actionsetid) = 0; virtual void BindAction(const char* actionsetid, const char* actionid, const ButtonCode& buttoncode) = 0; virtual void BindAction(const char* actionsetid, const char* actionid, const AxisCode& axiscode) = 0; virtual void BindAction(const char* actionsetid, const char* actionid, const ButtonAxis& buttonaxis) = 0; virtual void ClearAction(const char* actionsetid, const char* actionid) = 0; virtual IInputDriver* GetDriver() = 0; friend class IInputDriver; };  
    Did notice that we pass strings instead of button codes? So instead of checking if the Space bar is pressed, we check if the Space action is pressed.
    // Bad, don't do this. const bool b = InputSystem::GetDriver()->ButtonHit(InputSystem::BUTTON_KEY_SPACE) if (b) pPlayer->Jump(); // Do this! auto controller = InputSystem::GetDriver()->GetController(); if (controller->Hit("Jump")) pPlayer->Jump(); The keys can be ether assigned in code or loaded form a JSON file. All actions are converted to lower case to prevent confusion between "Jump" and "jump". Actions can have multiple buttons binded to it which is good for also storing controller buttons. The controller also supports having 4 buttons act as an axis which should be used for movement.
    It's easy to create an action controller. 
    auto action_controller = InputSystem::CreateActionController("actioncontroller.json"); Don't have a script yet? You can directly bind buttons and axis values after its creation and save the results to get started.
    auto action_controller = InputSystem::CreateActionController(""); const char* action_set = "TestActionSet"; action_controller->RegisterActionSet(action_set); action_controller->BindAction(action_set, "Camera", InputSystem::AXIS_MOUSE); action_controller->BindAction(action_set, "Camera", InputSystem::AXIS_GAMEPAD_RSTICK); action_controller->BindAction(action_set, "Jump", InputSystem::BUTTON_KEY_SPACE); action_controller->BindAction(action_set, "Jump", InputSystem::BUTTON_KEY_C); action_controller->BindAction(action_set, "Jump", InputSystem::BUTTON_GAMEPAD_A); InputSystem::ButtonAxis move_axis = { InputSystem::BUTTON_KEY_W, InputSystem::BUTTON_KEY_S, InputSystem::BUTTON_KEY_A, InputSystem::BUTTON_KEY_D }; action_controller->BindAction(action_set, "Move", move_axis); action_controller->BindAction(action_set, "Move", InputSystem::AXIS_GAMEPAD_LSTICK); InputSystem::SaveControllerProfile(action_controller, "inputtest.json");  
    This is all well and good, but the real payout will be to have an app that can easily bind raw input to actions. For this, I had to use Ultra App Kit as I needed something compatible with my Win32 libraries. Otherwise, I would have used the full engine.
    Like I said, it's pretty straight forward to hook this up with the Ultra API.
    #ifdef _WIN32 LRESULT CALLBACK MyWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { InputSystem::ProcInputWin32(hwnd, message, wparam, lparam); return UltraEngine::Window::WndProc(hwnd, message, wparam, lparam); } #endif std::shared_ptr<UltraEngine::Window> BuildWindow(const int w, const int h) { //Get displays auto displays = GetDisplays(); //Create window auto mainwindow = CreateWindow("Action Mapper", 0, 0, w, h, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR | WINDOW_HIDDEN); mainwindow->SetMinSize(w, h); #ifdef _WIN32 // Get device context HWND hwnd = mainwindow->GetHandle(); HDC hdc = GetDC(hwnd); // Load the icon for window titlebar and taskbar HICON icon = LoadIconA(GetModuleHandle(NULL), (LPCSTR)101); SendMessage(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(icon)); // Swap the callback with ours WNDPROC OldProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr( hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(MyWndProc))); #endif InputSystem::Init(); InputSystem::GetDriver()->EnablePumpEvents(false); InputSystem::GetDriver()->SuspendControllerInput(true); return mainwindow; } I first initialize the driver, then tell it to not pump any OS events into it. This is because I wanted the events to pump to my driver only when requested by my bind button class. When that is pressed, the button gets disabled until InputSystem::GetDriver()->ButtonAnyHit() returns true and saves the last pressed key. I didn't need to pass the window handle as that's only needed to toggle the visibility of the cursor. 

    This ended up working really well, and I'm happy how it came out. But you know what would be really customer friendly? What if this had a native Linux build? Ultra App Kit has Linux64 libraries, and my entire build environment uses premake with WSL so building one was no issue. The issue is that I didn't look into X11 and I don't wanna hold the next update much longer.
    Using my input library, I just pump the events from the Ultra API into my WinAPI driver and most keys worked. I had to fix Control, Alt, and Shift, but it was pretty easy. This only gets used with the Linux build. 
    #ifdef __linux__ #define VK_LSHIFT 0xA0 #define VK_RSHIFT 0xA1 #define VK_LCONTROL 0xA2 #define VK_RCONTROL 0xA3 #define VK_LMENU 0xA4 #define VK_RMENU 0xA5 #endif namespace UltraInputWrapper { // We need to convert some keys to translate // engine values to Windows VK values. const int Convert(const int in) { int key = in; if (in == UltraEngine::KEY_CONTROL) return VK_LCONTROL; //if (in == UltraEngine::KEY_CONTROL) return VK_RCONTROL; if (in == UltraEngine::KEY_SHIFT) return VK_LSHIFT; //if (in == UltraEngine::KEY_SHIFT) return VK_RSHIFT; if (in == UltraEngine::KEY_ALT) return VK_LMENU; //if (in == UltraEngine::KEY_ALT) return VK_RMENU; return key; } // For non-Windows, push engine values. // Since UltraEngine uses the same values as WinAPI, it should be fine.. void PollIntoDriver(const Event& e) { switch (e.id) { case EVENT_KEYDOWN: InputSystem::GetDriver()->PumpButtonDown(Convert(e.data)); break; case EVENT_KEYUP: InputSystem::GetDriver()->PumpButtonUp(Convert(e.data)); break; case EVENT_MOUSEDOWN: InputSystem::GetDriver()->PumpButtonDown(Convert(e.data)); break; case EVENT_MOUSEUP: InputSystem::GetDriver()->PumpButtonUp(Convert(e.data)); break; default: break; } } }  
    The final step was to gut the old input system out of Cyclone and replace it with my action controller. I also had to make it co-exist with Steam Input which I found out nulls out my XInput calls. I wrote a new singleton class that checks the state of both my action controller and Steam Input. Then it was making sure the right glyphs showed up per action.
    One last thing I want to share is a little bit of how my GameController class works. Since this follows the same ideology as Steam Input, it all works nicely.
    // Button Example const bool GameController::OnJump() { if (actioncontroller == NULL) return false; return actioncontroller->Hit("Jump") || SteamInputController::Hit(SteamInputController::eControllerDigitalAction_Jump); } // Axis Example Leadwerks::Vec2 GameController::GetMovementAxis() { Leadwerks::Vec2 ret = Leadwerks::Vec2(0); if (actioncontroller != NULL) { // Shared movement between KB and Controller. InputSystem::AxisVector moveAxis = actioncontroller->Axis("Move"); ret = Leadwerks::Vec2(moveAxis.x + SteamInputController::Axis(SteamInputController::eControllerAnalogAction_MoveControls).x, moveAxis.y + SteamInputController::Axis(SteamInputController::eControllerAnalogAction_MoveControls).y); } ret.x = Leadwerks::Math::Clamp(ret.x, -1.0f, 1.0f); ret.y = Leadwerks::Math::Clamp(ret.y, -1.0f, 1.0f); return ret; }  
    This was an absolute time vampire, but I'm glad it's done minus the Coco/X11 drivers. Cyclone will be updated soon with this and hopefully this fixes international bindings. I couldn't find any information on the subject, and my virtual Turkish keyboard works properly. I'm just hoping the storing of the VK values is enough.
    I can easily build this for Windows, Mac and Linux to work with Leadwerks or the upcoming Ultra Engine. This is how we should be programming our input for our games. Stop using window->KeyHit(). I plan on releasing the binaries so everyone can have better input code. 
     
  2. reepblue
    Leaks of games happen, and they still happen. You can spend months and months working on your game, only for a server attack or the wrong person to share it with their buddies; which then goes public accidentally, (or intentional). Having an unintended work in progress build out there can give people the wrong idea about your game because there are people out there who think the development process is pretty straight forward.
     
    Let's take a look at the Half-Life 2 leak. The game, and the entire engine code was leaked and the entire thing was very unstable, It was also found out that all those
    were scripted, and broke when you actually play them. A lot of doubts and concerns about the game were made as they felt like they were lied to after seeing videos and screenshots not representing the leak at all. Of course, this later on generated a community of it's own, but that's a rare scenario. 
    Since Leadwerks is far more stable than a Source Engine build from 2003, you can have months of playable work leaked. If you indeed plan to sell your game on Steam or Desura, you most likely don't want you're game freely available. So how can we prevent this?
     
    If you already got a SteamAppID from Valve after a successful Greenlight campaign, there is nothing to worry as users need a key to access your game. But what about if all to your game is dev textures, rough code, and programmer art? If you only have the indie edition, or only use Lua scripts for your game, you can use the Game Launcher, and set your game to "Friends-Only". For games written in C++, or have a modified application, we can't do that as our program does not get uploaded to the workshop. With the Standard Edition of Leadwerks however, we can use more of the Steamworks API.
     
    A friend of mine was talking to me about how an old Source mod use to detect if an invalid Steam user tried to boot the mod, it would not boot. Once I heard this, I was eager to see if I could get a similar system to work.
     
    Every Steam user has a public unique ID that comes in many different forms. Steam and games use this to gather information about the user, among other things. In this case, we are going going to focus on the 64bit FriendID number. Thanks to the Leadwerks API, we can access Steamworks features without our game being on Steam!
     
    Let's us first start with creating a bool function under the main.cpp.
     

    bool CheckSteamID() {
    if (Steamworks::Initialize())
    {
    }
     
    System::Print("Error: Failed to initialize Steam.");
    return false;
    }
     
    So far so good! We make it a bool function so if we are not a fail to load Steamworks, or have the wrong ID, it will return false and close the program. Now let's get the users ID number with:
     

    uint64_t user = Steamworks::GetUserID();  
    As I said before, SteamID's come in various forms. The easiest to get is the 64 bit FriendID which appears in profile urls such as http://steamcommunity.com/profiles/76561197996502882/
     
    As you can see, my SteamID is 76561197996502882. For the rest of this article, I'm gonna use mine in place of where you should put yours. A way to get yours is to go on a site such as http://ihavenfi.net/steamid. Popping in the STEAM_0:0:******** format after putting your ID-less profile URL will give you your FriendID. Remember, these numbers are public, they aren't like credit card numbers, or anything.
     
    So let's continue! For this example, I'm going to use My SteamID. So let's make a simple if statement comparing the account trying to play this game with a hard coded value.
     

    bool CheckSteamID() {
    if (Steamworks::Initialize())
    {
    uint64_t user = Steamworks::GetUserID();
    if (user == 76561197996502882) // Me!
    {
    return true;
    }
    else
    {
    System::Print("Error: Invaild SteamID!");
    return false;
    }
     
    System::Print("Error: Failed to initialize Steam.");
    return false;
    }
     
    With this code, (and I hope you replaced the SteamID), your game will only work for you if you're logged into your account. But you need beta testers, and what about the other developers? Let's Continue by adding more IDs!
     
    As another example, let's grab Josh's SteamID. After all, he's the CEO and should be able to play any LE game he wants. Although his profile is private, we can still grab the ID-less URL which is: http://steamcommunity.com/id/Josh
     
    Pop it in the converter and we get: STEAM 0:0:6085996. Let's pop that value in again and we finally have his FriendID which is: 76561197972437720
     
    Now let's edit that if statement.
     

    if (user == 76561197996502882 // Me! || user == 76561197972437720) // Josh
    {
    return true;
    }
     
    Now, Josh can play our game if he's logged into his Steam account. The best way to get ID's is to make a Steam Group for your testers this way you have all the profiles listed that you need to code in. We're not done yet, we still need to call this function near the start of the main function.
     

    if (!CheckSteamID()) return 0;
     
    Just make sure you comment that out when your game does get to be on steam, or when it releases elsewhere with a different DRM measure if you desire.
     
    With this somewhat messy way, you can restrict your games to certain steam accounts without having your games on steam during development. As a bonus, let's welcome the user in the console to make the tester feel they're special. Put this before the function returns true.
     

    std::string username = Steamworks::GetUserName(user); System::Print("Hello there, " + username + "!");
     
    And that's it! There is most likely other ways of doing this, but this way is the simplest and easiest. If you hard code FriendIDs, and use the stand-alone publishing for beta builds, your game is just a bunch of unplayable bites and bits if it falls into the wrong hands!
  3. 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.
  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
    Luawerks has been updated to 1.2.6, making some small adjustments and fixes to the system. If you have previously purchased Luawerks, this update is available for free on the Leadwerks Marketplace.
    Following changes include:
    Fixed flag typo correcting LUAWORKS to LUAWERKS Moved error.mat/tex to the Materials/Common folder Added a useuisystem boolean to disable the menu, Adjusted VR mode not to call any UI elements. At this time, this update is only exclusive to the Leadwerks Marketplace.
    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Marketplace for $9.99.
    For documentation and bug reporting, please visit the GitHub page.
     
  6. 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.
  7. reepblue
    Hi,
     
    I've been reading and posting here and there on the forums for quite some time now, and being that my current project is taking shape, I think it's time that I introduce myself, and in the next post, I'll share some goals of my project.
     
    Quick Bio:
     
    As a Kid, I've had an interest in game development with my hundreds of hours I put into RPGMaker 2000/XP. At that time, I was mostly inspired by The Legend of Zelda series and other top down games that could be made in the program.
     
    When I was a young teen, I was not really into PC games. I stuck to my old Nintendo consoles while my brother would play Counter-Strike: Source on a daily basis. I remember him once showing me a custom map he made for CSS that be based off our local 7-Eleven store. He was showing me how he made the textures, and a bunch of other simple things. I can't recall if I was blown away, or wanted him to stop talking so I could do my thing to be honest.
     
    After a while, my interest in game development started to fade, and I got more into graphic design for websites and such. But then game called Portal. I was not into first person shooters, but I did like Puzzle games, and this game was one of a kind when it released. My brother got The Orange Box package and I played the living hell out of Portal. The game was so interesting to me and there came a point where I would turn on cheats, take the metal rectangles, move them into position, and save the game. Then a "custom puzzle" would be loaded like loading a saved state.
     
    And it begins!:
     
    To make a long story short, I eventually got The Orange Box for the PC, got a budget build, and made a lot of maps that never got released. Portal 1's gameplay elements where just a collection of base engine entities that worked together. I spent months on end learning how to make breakable glass, the indicator strips, buttons, doors, you name it! After I felt like I had enough knowledge of map making, and the I/O system, I decided to work on my first mappack called Blue Portals.
     
     



     
    The project was on and off. I was working on another project at the time, so these maps were totally remade from the ground up with a new art design.
     



     
    Through out the years and other side projects, Blue Portals was released on November 29th, 2010. The mod got mix reviews, and looking back on the project, I can say that it's not for everyone. But I can say that it does have a cult following.
     
    With the release of Portal 2, of course I was excited to work on custom maps. I first started to to work on a Blue Portals 2 to fix the flaws of the original, and to add other cool elements. But due to the different FileSystem structure of that branch of Source, and my lack of knowledge of it at the time, I canned it. It was a good thing too, because DLC1 made the unused Adhesion Gel weird which BP2 used.
     
    I've made some small mappacks such as A Little Higher, Tornate, and Feather Away. I also worked on most of the test chambers for Alive & Kicking while I was making new game concepts still within the Source Engine. After a while, Portal started to get boring, and the second DLC for Portal 2 (PTI) made me lose interest in making Portal puzzles because now anyone could do it, and it killed the art for me.
     
    I started a project in the Source Engine called Punt and to sum it up, it was me trying to find what I want as my own puzzle game.
     




     




     
    After a while, I got the gist that the main core concept of the Puntgun was not fun. That, and the code base was a mess due to the Puntgun being a mod of the gravitygun, and poor tracer code. Also, Valve released a new SDK base to replace the one from 2007, and I really wanted my mod to be less like Portal gameplay wise.
     
    In the next entry, I'll talk more about my project, the goals and why I decided to port it to LE3.
     
    In the meantime, you can checkout my Youtube channel to see what I've done throughout the years. (I hope to put Leadwerks related content on it soon!)
    https://www.youtube.com/user/reepblue
     
    And here is my blogger which as more of my writings and thoughts:
    http://reepblue.blogspot.com/
     
    Thank you for reading/watching. I hope you are willing to hear more!
  8. reepblue
    I recently updated the Leadwerks Extended Executable with more shaders, and hot fixes. Fetch and pull this update in git. I wanted to post this yesterday, but I was having issues.
     
    Fix 1: No more crashing upon Alt+Tabbing
    This fix is eventually gonna be made engine side, but I was not sure if Josh plans on that being pushed to the default branch, or the beta branch. While the beta and default branch are synced, I jumped the gun and fixed it.
     

    //---------------------------------------------------------\\ // This will be fixed engine side in a future version of LE.
    // For now, we are gonna apply this patch.
    if (window->Active())
    {
    // Update the world.
    world->Update();
    }
     
    if (window->Minimized() == false)
    {
    world->Render();
    }
    //---------------------------------------------------------\\
     
    2: Fixed saving conflict between the options and advanced options panel.
    Before I went to bed last night, I noticed that if you edited a property on the advanced panel, then edited a property on the options menu, the old value you just changed on the advanced panel would reset. This has been fixed by reloading the xml file before it saves values.
     
    3: Fullscreen mode will now always get the current display resolution.
    In the initial release, the application would get the size of the display and store it in the settings.xml file. Although this was a good idea, I realized that this could be bad as a display can change if the user want's to play your game on his 1080p TV while they have a 900p monitor. If fullscreen is enabled, it will now ignore the resolution stored in the settings file, and just fetch the current display size from the System. The screenwidth and screenheight properties are now only used for windowed mode.
     
    4: More Shaders!
    I've added animated versions for the color-emission, cubemap, and detail model shaders.
     
    I'm currently working on some documentation for this, and will update the opening post of the tread with Google Doc links to them. I've also set a Trello board for this so you can see what's next on the list for this project. Hopefully it will not become a wasteland like my Vectronic Trello.
     
    Although I feel that this is solid how it is, continue to add your input and suggestions.
  9. reepblue
    As I mentioned in my previous post, throughout my time of developing Vectronic on the Source Engine, the project never had more than four maps because so much time and worry was focused on the concern of how it's going to look. While the overall art style, and the balance of visual noise is importation for a project like this, I was spending more time on this than making more maps and puzzles.
     
    I decided to once again start from scratch and only focus on the maps and gameplay for the time being. With this mindset, I've gotten close to seven maps shelled out in dev textures, placeholder models, and no lighting. On top of those seven maps, I've got two more ideas down on paper. With this method of developing, perfecting the how the elements interact with each other is much easier to do.
    I can easily change a puzzle, or how a puzzle is solved if something ends up not working the way I intended it to, without moving a handful of models and lights.
     



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



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



     
    I've currently been making maps in order of progression, but I think in the future, I'm just going to make puzzles not worrying about the order of progression, and workout how (or if at all) they'll fit in the game. My goal is to get ten or fifteen maps done before I even think about how everything is going to look. At the rate I'm going, I should be back in Blender and Paint.net by next month! I have ideas about updating the visual style, but nothing I plan on talking about yet.
     
    Sorry about not posting this article Friday like I said I was going to. I want to write a development blog weekly for my sake of keeping me motivated, so expect at least that.
  10. 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.
  11. reepblue
    Sorry for the whole week of silence, I ran into quite into a number of issues the previous weeks, but I'm ready to talk about them more, and how I it got resolved.
     
    Newton Physics (Or how Leadwerks uses this physics engine) has a problem when a non-physics object collides with a physics object. You expect that if a entity using the Move() function would use it's own shape to push anything out of the way. What actually happens is that since the entity has no mass, the entity pretends the physical object isn't there, and just clips right through it. This is most likely like this by design, but for Vectronic it was a huge issue.
     
    The advertised way of doing platforms is to use the Joint class so the entity will have mass and interact with other physics objects. However, the Vecboxes need to be heavy, I'm talking about setting the mass to 80 to prevent the object from bouncing like a kickball. Increasing the overall world gravity effects the player, and I want the box to be lighter when it's activated by the 0g Ball, which makes it easier to throw. Problem was that if a box was to go on these platforms with the mass I need them to be, the box would overwhelm the joint, and break it.
     
    Platforms is something I see important to the project as by now (in the earlier puzzles) they are used for demonstration of how the 0g cube works, and are in the majority of maps I have already made. If platforms can't work, I was afraid I was back on the bus of looking for another suitable engine for the game. Luckly Einlander came to the rescue and set me something that worked better than all my other attempts.
     
    Einlander's script moves the object from it's resting point to another position similar to the sliding door script. It also updates it's Physics Position and Omega in a function called in UpdateWorld. The result was exactly what I want minus a slight jitter in the box, but I'll take it. After experimenting with it though, I noticed that if a box was only partially on it, the platform would ignore the box, and go right through it; a similar problem I kept having. My fix was to create a trigger that's spawned with the platform with it's shape to handle this case, and behavior with the 0g cube. It's still not 100% perfect, but it's better than what I had before. The project was now back in gear.
     
    Another concern I was having was questioning "How much fun is my game, or is it just work?". Running around, shooting power balls at boxes so they change their properties sounds like a good concept on paper, demo maps, but imagine a full 2 hour game of just that? I was worried that just focusing on that would get really boring; so boring that it might even make people to stop short and refund the game.
     
    As if it was not clear already, Vectronic is a tribute to Portal as if it wasn't for that game, I would not be here talking to you right now. I started to think about Portal, and I started to look at it from a weird light: How would Portal be if momentum (Flinging) wasn't part of the game? There are five chambers, and the BTS areas that require or focus on using Portals to fling over gaps, and a good chunk of code is dedicated to make sure the momentum multiplies when going through each portal until the player or object hits it's max speed. But imagine if the game didn't have it, you'd have a game where you walk into walls to move mostly boxes around. Again, this might not seem so bad, but imagine playing it for 2+ hours with only the original elements; it will be similar to what I have now with Vectronic.
     
    The issue I think is the lack of Player Mobility. From my observations, people like to use their weapon to get to an extreme height. Join any Team Fortress 2 server and you'll see Soilders and Demomans blast jumping, and Scouts double jumping everywhere. With PUNT had something called Punt jumping which was like Rocket Jumping, but you could only do it on certain materials. From the feedback I got, people were most excited about that. Only issue is that it required skill, and it was never fully taught.
     
    I'm currently experimenting with this bounce jumping idea. Imagine rocket jumping only on certain materials, but all you need is to double tap Space. I've got only one map done as a proof of concept and to introduce the idea to others to see if it worked. Two days ago, I shipped the build early while this was still being figured out to see if it was worth the effort. I got a positive vibe from their feedback, I got told it was fun, although they didn't fully understand how to control it. Regardless, this might be because I was calculating how high the jump should be based on the time you're falling, not the speed, but it shows that it's not perfect yet.
     
    I should have more information about it next week (hopefully!). I want to go back and forth with my pre-alpha testers until it's a fun and fully understandable mechanic.
  12. 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.
  13. reepblue
    Very happy on how everything is coming along. Blue Portals: Anniversary Edition development is going a tad slower than I wanted it too, but it's coming along nicely. We've finally shipped that
    in late August, and the mod made even more progress since. We just got some unknowns to work out like voice acting and music. I got some puzzles in the pipeline, and the mod right now has 11 playable maps.  
    LEX2 is getting re-organized. I went back to the CMD window based dev console as I plan on adding in the final console with the new GUI once that's done and well documented. I also implemented a Convar system along with a new conommand system. Pretty much, a new foundation is in place, and I shifted my focus to be easier lua development for now. I'll write about it more when I know more on how I wish to accomplish this.
     
    Steam Dev Days is next week! I've spent the last month going through my archive of experiments and prototypes, along with making demo maps for Blue Portals: Anniversary Edition. Chris (my texture artist) is bringing his Surface to showcase our stuff. If you're gonna be attending the event, come find us! We've got a lot to show off and talk about. I'll most likely be posting on my twitter through out the event.
     
    Unfortunately, the surface ran any Leadwerks project terribly, so we are only limited to our Source projects.
     
    That's really it. I know I usually write an essay for blog posts, but I've been busy lately in preparation for Steam Dev Days. I'm really excited, as I never been on that side of the country before!
  14. reepblue
    Luawerks has been updated this morning, making the console more responsive and a new tab for the Options Menu for user's to bind keys to actions.
    Actions have always been part of Luawerks, but until now, there wasn't really a motive to use them. Now, the Action class look in the config to check what key the user binded to that action. You can read more on how this works here. Like the console, this may be improved more in the future. 
     
    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Workshop Store for $9.99.
    For documentation and bug reporting, please visit the GitHub page.

  15. reepblue
    The Leadwerks API has standard Boolean functions that detect when the end user has pressed a key. While this is very simple and easy to understand, the issue comes when you wish to support binding of actions. Instead calling functions when a certain key was pressed or held, a better way to detect key events is to assign a key to an action. (e.g: Is the Jump key pressed). In Luawerks, I've written an action script in which returns the window call if the key defined in the user's settings is hit. The downsides is that you had to do this for every action in your game and that this was a system written for Luawerks so it wasn't very portable for non-lua/Leadwerks projects.
    function Action:MoveForward() if self.suspend==true then return false end local key_forward = GetKeyInt(System:GetProperty("key_forward","w")) return window:KeyDown(key_forward) end For those who don't know, SDL is an Open Source library that comes in handy for things like Window management, input, and such. So over the weekend, I've decided to sit down and create my own Input System in which would be allow for action bindings, portable for any type of project while supporting Game Pads outside of Steam.
    The first step was to manually poll all inputs and treat them all the same. For the mouse and keyboard, this was just seeing if a key was hit, and registering that event to a map.
    namespace RInput_KM { void SimulateButton(const Sint32& pKey, bool bDown); const float ButtonDown(const Sint32& pKey); void ShowMouse(); void HideMouse(); void SetMousePosition(const int x, const int y, bool bGlobal = false); void ModMousePosition(const int x, const int y, bool bGlobal = false); void UpdateMousePosition(); void SimulateMouse(const int x, const int y); const int GetMouseX(); const int GetMouseY(); void SetMouseWheelPosition(const int y); const bool OnMouseWheelUp(); const bool OnMouseWheelDown(); // Returns the long to a character. const char* GetButtonName(const Sint32& pButton); // Returns the character to a long const Sint32 GetButtonIndex(const char* pButton); void Enable(); void Disable(); void FlushKeyboard(); void FlushMouse(); void Flush(); } When it comes to buttons, it doesn't matter if the mouse button was pressed or a key stroke. What matters is that a button on the keyboard and mouse combo was pressed. I treat the keyboard and mouse as one controller. You can also "Turn off" the mouse and keyboard if you want.
    namespace RInput_GamePad { typedef enum { ENUM_GAMEPAD_NULL = -1, ENUM_GAMEPAD_ONE, ENUM_GAMEPAD_TWO, ENUM_GAMEPAD_THREE, ENUM_GAMEPAD_FOUR, ENUM_GAMEPAD_MAX = ENUM_GAMEPAD_FOUR } GamePadIndex; typedef struct { SDL_GameController* controller; const char* pszDeviceName; bool bEnabled; std::map<Uint8, bool> mControllerButtons; } gamepad_t; void Connect(const Sint32& pWhich); void Disconnect(const Sint32& pWhich); gamepad_t GetDeviceFromPort(const GamePadIndex& pPort); // Digital input: void SimulateButton(const Sint32& pWhich, const Uint8& pButton, bool bDown); const float ButtonDown(const Uint8& pButton, const GamePadIndex& iIndex = ENUM_GAMEPAD_ONE); const char* GetButtonName(const Uint8& pButton); const Uint8 GetButtonIndex(const char* pButton); // Analog input: void UpdateAxisMotions(const Sint32& pWhich, const Uint32& pAxis); const Sint16 GetAxisValue(const Sint32& pWhich, const Uint32& pAxis, bool bFlip = false); const float GetAxisFloat(const Sint32& pWhich, const Uint32& pAxis, bool bFlip = false); void Flush(const Sint32& pWhich); void FlushAll(); } Next was the game pads. which were a bit more challenging as you need to consider multiple game pads and the valves of analog inputs.
    If you were paying attention, You would notice that the ButtonDown functions for both the keyboard+mouse and the game pad are returning floats. While it may be limiting for certain applications, I've created "fake buttons" for events for when the trigger is pressed, or if the left stick is to the left. All input returns a float ranging from 0.0 to 1.0. For digital inputs like buttons, this will be a 1 or 0, but for analog the value depending the range from the resting point to the max will called. So if the left stick is all the way to the left, you'd get 1; half way 0.5. This is much better then dealing with direct Uint32 values.
    Last was to merge both controllers into one entry point in which we use in our app to register our actions, and check to see if they are being called.
    namespace RInput { typedef enum { CONTROLLER_KEYBOARDMOUSE, CONTROLLER_GAMEPAD } Controllers_t; void Init(SDL_Window* pWindow); void InitSDL(const void *data); void SetActiveDevice(const Controllers_t& pDevice); Controllers_t GetActiveDevice(); const char* GetActiveDeviceAsString(); const char* GetGamePadDeviceAsString(const int pPort); const Sint8 GetGamePadCount(); void TestEvents(const SDL_Event& pEvent); void PollEvents(); // <- Use this function if you're not using SDL event polling. void Flush(const Controllers_t& pController); void FlushAll(); //==================================================================== typedef struct { Sint32 key; Uint8 button; bool bDown; bool bHit; } action_t; const float GetActionInput(action_t& pButton); void RegisterAction(const std::string& pActionName, Sint32 iKey, Uint8 iButton, bool bConistant); void ModifyAction(const std::string& pActionName, Sint32 iKey, Uint8 iButton); action_t& GetAction(const std::string& pActionName); bool LoadActionsFromFile(const char* pszPath); void UpdateGamePadStickAsMouse(const Sint32& pWhich, const Sint8& pAxis); } Actions can be bind to a with key and a button. bDown is used to check if the action is being held down while bHit is a flag to check if this is something that should be called once per pressed (Like a key to pick up a box or something.) One top of all this, Actions can have their key/button bindings changed via an XML file. Here's an example how to use this with Leadwerks.
    #include "Leadwerks.h" using namespace Leadwerks; #include "rinput/rinput.h" int main() { Leadwerks::Window* window = Window::Create(); RInput::InitSDL(window->hwnd); Leadwerks::Context* context = Context::Create(window); World* world = World::Create(); Camera* camera = Camera::Create(); camera->SetRotation(35, 0, 0); camera->Move(0, 0, -6); Light* light = DirectionalLight::Create(); light->SetRotation(35, 35, 0); light->SetShadowMode(0); Model* model = Model::Box(); model->SetColor(1.0, 0.0, 0.0); model->SetPosition(0, 0, 0); model->SetShadowMode(0); RInput::RegisterAction("moveforward", KEYBOARD_W, GAMEPAD_BUTTON_LSTICK_UP, true); RInput::RegisterAction("movebackward", KEYBOARD_S, GAMEPAD_BUTTON_LSTICK_DOWN, true); RInput::RegisterAction("moveleft", KEYBOARD_A, GAMEPAD_BUTTON_LSTICK_LEFT, true); RInput::RegisterAction("moveright", KEYBOARD_D, GAMEPAD_BUTTON_LSTICK_RIGHT, true); RInput::RegisterAction("jump", KEYBOARD_SPACE, GAMEPAD_BUTTON_A, false); RInput::LoadActionsFromFile("controller.xml"); while (window->Closed() == false) { RInput::PollEvents(); float x = 0; float z = 0; float u = RInput::GetActionInput(RInput::GetAction("moveforward")); float d = RInput::GetActionInput(RInput::GetAction("movebackward")); float l = RInput::GetActionInput(RInput::GetAction("moveleft")); float r = RInput::GetActionInput(RInput::GetAction("moveright")); if (u != 0) { z += 0.05 + (u /10); } if (d != 0) { z -= 0.05 + (d / 10); } if (l != 0) { x -= 0.05 + (l / 10); } if (r != 0) { x += 0.05 + (r / 10); } model->Move(x, 0, z); x = 0; z = 0; Leadwerks::Time::Update(); world->Update(); world->Render(); context->DrawStats(0, 0, false); context->Sync(false); } return 0; }  
    The Result:
     
    There are many things I didn't add for the sake of time or I wasn't sure how to implement like supporting action for multiple controllers on the front end. But this does what I was set out to do, and although I haven't tried, I'm confident that with little to no work, this code will work on macOS and Linux. As long as there is SDL2 on the platform, this will work.
    Now no matter if the project is Leadwerks, Turbo or just an SDL2 project, I now have a portable input system with controller support which you can find here.
  16. reepblue
    Luawerks has been updated to 1.2.7, making some small adjustments and fixes to the system. If you have previously purchased Luawerks, this update is available for free on the Leadwerks Marketplace. Please note: Luawerks is not ready for Leadwerks 4.6 and it's recommended to use Luawerks with Leadwerks 4.5.
    Following changes include:
    Added an optional Map selection menu. "New Game" will default call a panel in which all the maps in your Maps directory will be listed, Note: This will only work if the maps are not included in your zip as raw Lua functions are called. This will also not work if the Lua sandbox is enabled. Re-located error.mdl to a sub directory "Models/Developer" Added widget.mdl and axis.mdl for assistance for visualizing matrix results. When in-game, "Exit to Menu" will show instead of "Quit." This will release the world and the player will be returned to the main menu. Arrange locations of various functions. The names remain the same. Added various functions needed for this update.  

    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Marketplace for $9.99.
    For documentation and bug reporting, please visit the GitHub page.
  17. reepblue
    Luawerks has been updated to easily transform any game into a Seated VR compatible one. All what's needed is the application to be launched with "-vr". All what has been added is the tweak discussed here. 
    While Luawerks isn't really ideal for Room scale VR, seated on the other hand works better. The center tracking call gets called every time the game gets unpaused. At this time, the UI doesn't work with VR. Keep in-mind that there might be some tweaking to your game to make it 100% VR compatible. For example, if you launch the FPS template map in VR, the bullets should be based on where the player is looking not the mouse position. Don't want have VR? No problem, this only effects the core files (Window, System, Time) and has no other changes to the UI. VR mode is just a bonus. Of course, you can force it to always boot into VR mode.
    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Workshop Store for $9.99.
    For documentation and bug reporting, please visit the GitHub page.
  18. reepblue
    I've uploaded a new release last night correcting the player's pickup to be much more reliable. It should be very rare for a case in which the player is in a object and releases it causing the player to fly backward. I could have kept objects solid but that caused another can of worms. I also posted this on my OneDrive due to issues of people not being able to download it from this site.
    Yesterday I was working on implementation of the power balls and the launcher. I've written a rough draft of this a few weeks ago and I had it fully working in normal player code. When I started to work on the VR version, that's when I started to run into some problems. First, I didn't like how the code was looking. It involved a lot of referencing of entities and classes. What made it hard is that the VR implementation was different from the normal players. What didn't help was after too many restarts of my application with VR enabled, the entirety of Steam would just crash. So developing for VR was very frustrating.
    With Turbo coming a long, I wanted my code to work with little modification as possible. I've written a function wrapper that'll only be called if the application is ran under Leadwerks. This both will save time in replacing these functions when the time comes, and get's me used to the new functions. One thing I'd still have to fix is how Lua is implemented in the new engine. Most of the heavy functionalities are in classes which will defiantly keep self.entity for referring to the entity it's being used on. However somethings will still need to be changed and there are a lot of unknowns right now.
    With that being said, I think it's safe to say it'll be best to focus on the art and assets going forward. With the amount of time I have to make these assets, I'm sure more clarification with Lua scripts with the new engine will surface, maybe even a beta to play with. But with it being Leadwerks or Turbo, they both need assets. VR mode will also be dropped for now. It'll still be there, just not fully supported. You'll need to run the app with -devmode to see the VR tab now. After the project transitions into Turbo, VR development will continue.
    I shall be going back to my Linux machine with my ultra wide color calibrated monitor to work on these assets. I've created a lot of Vectronic themed stuff in the past and I'm going to go through it and see what I can savage from it. All my models are done in Blender, and I want to create all textures in GIMP this way my files can be used with free and open sourced tools. Also, if there is any Linux developers who wish to use this package, they don't have to be hit in the face with Maya and Photoshop files.
    Releases will slow down, but I'll be posting screenshots of my work soon.
     
  19. reepblue
    Luawerks has been updated to 1.2.8, making some small adjustments and fixes to the system. If you have previously purchased Luawerks, this update is available for free on the Leadwerks Marketplace.
    Following changes include:
    Fixed GUI code for Leadwerks Game Engine 4.6 Removed the feature "allowfullscreenfromeditor" as it was causing conflicts with fullscreen. Added ignoreeditorwinsettings bool setting to force the game to ignore editor launch settings. (Sorry for the delay..)
    About Luawerks
    Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun!
    You can purchase Luawerks from the Leadwerks Marketplace for $9.99.
    For documentation and bug reporting, please visit the GitHub page.
  20. 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.
  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
    I think I've finally finished my approach in input for my project. This is the final result.
    //========= Copyright Reep Softworks, All rights reserved. ============// // // Purpose: // //=====================================================================// #include "stdafx.h" #include "gamewindow.h" #include "gameworld.h" int main(int argc, const char* argv[]) { App::ParseCommandLine(argc, argv); auto window = CreateGameWindow("GameTemplate"); auto world = CreateGameWorld(window); if (App::GetArgument("vr") != "") App::EnableVR(); auto camera = Camera::Create(); camera->SetPosition(0, 0, -1); ActionConfig::SetActiveSet("Walking"); while (App::Running()) { if (GetActionHit("jump")) { DMsg("jump!"); } if (GetActionHit("interact")) { DMsg("interact!"); } world->Render(); } return 0; } We no longer care what key or button is pressed, we are now concerned what action is being sent to us.Two json files are auto generated in "CreateGameWindow()". One for the mouse and keyboard, and the other for a gamepad with SDL2. These files can be modified by the user (hard) or an app when I actually write it (easier).
    { "keyBindings": { "actionStates": { "Menu": { "pause": 27, "selectActive": 13, "selectDown": 40, "selectLeft": 37, "selectRight": 39, "selectUp": 38 }, "Walking": { "crouch": 17, "firePrimary": 1, "fireSecondary": 2, "flashLight": 70, "interact": 69, "jump": 32, "moveBackward": 83, "moveForward": 87, "moveLeft": 65, "moveRight": 68, "pause": 27, "reloadWeapon": 82 } } } } { "joyBindings": { "actionStates": { "Menu": { "joySelect": 0, "pause": 6, "selectActive": 0, "selectDown": 12, "selectLeft": 13, "selectRight": 14, "selectUp": 11 }, "Walking": { "crouch": 7, "firePrimary": 16, "fireSecondary": 15, "flashLight": 3, "interact": 2, "joyLook": 1, "joyMovement": 0, "jump": 0, "pause": 6, "reloadWeapon": 1 } } } } You may notice that "jump" and "joyMovement" are both 0, but they get treated differently. I've added a GetActionValue() function which returns a Vec2 for analog sticks and triggers. Triggers are both read as buttons and analog values. You still have to write code in how you want to treat the sticks, but the question of the left stick or right stick is bindable now.
    There are also Action Sets a collection of buttons can be set based on what state the game is at. Driving is not the same as walking, nor it's the same as flying a plane.
    VR can also have it's own entry, but I feel you should use SteamInput for that since the Engine is dependent on SteamVR for it's VR implementation. I've could have just used SteamInput at the start, but then my binding code would only work if the game is on Steam.
    My code was compiled on Leadwerks 4.6 beta, but thanks to my wrapper functions and back and forth compiling, it should just work in the new engine.
  23. reepblue
    Before we were all locked in our homes, I was working on a lot of code for Leadwerks 4 for a reboot of concept I've made back in 2017. While I decided to shelf it again to wait for the new engine (Because the game will run faster, look better, VR, etc) I'm going to share you some of my core code which is cross compatible between the new and old engine. I'm sharing this as I see most people just using stock example code for window creation and setting saving without knowing how it all works. This is more of a show than tell kind of article, and most things are just copy and paste what's in my code at the moment, but it should give you an idea. In this article, I'll be showing you code for both Leadwerks 4 and 5, but most of this was written and tested in Leadwerks 4. 
     
    Parsing Arguments.
    Leadwerks 4 has a simple command that does this automatically. This makes it so you can give the app flags on how it should startup/behave on startup. 
    System::ParseCommandLine(argc, argv); In Leadwerks 5, you need to store it in your own std::map.
    auto commandline = ParseCommandLine(argc, argv); To be honest, the new way is much better. You see, in Leadwerks 4, System::ParseCommandLine() stored the values in a engine defined std::map which is also associated with System::LoadSettings() and System::SaveSettings(). This will cause unwanted results as flags you may only want to happen only when called in a batch file will save to the cfg file saved by default to the AppData folder in Windows. There is a separate map for both Arguments and Settings, but I think the issue is with if you use System::Set/GetProperty().
    The best option is to keep your arguments and setting separate. In Leadwerks 5, your std::map is yours, so you're already off to a good start.
     
    Actual Settings
    Now we can take in flags/arguments from launch settings, we now need values to be written on the disk. First we need to build the directory in which will act as our data folder for the user. Where this folder should be is up to you, but it should be in the user's directory on Windows, or the user's home directory in Linux. Here's my code for both Leadwerks 4 and 5. 
    First, we need to know the applications name. We use this name value in multiple areas. Leadwerks 4 as a value System::AppName, but in Leadwerks 5, we'll be creating our own variable. We'll also define the settings directory string path here too.
    #ifndef LEADWERKS_5 //std::string AppName = "MyGame"; std::string settingsdir = ""; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void App::SetName(const std::string& pszAppName) { System::AppName = pszAppName; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- std::string App::GetName() { return System::AppName; } #else std::wstring AppName = L"MyGame"; std::wstring settingsdir = L""; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void App::SetName(std::wstring pszAppName) { AppName = pszAppName; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- std::wstring App::GetName() { return AppName; } #endif  
    Now we have a name variable, we can use it to build our settings directory. 
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void BuildSettingsDirectory() { #ifndef LEADWERKS_5 // If settingsdir is not "", we did this already, no need to do it again. if (settingsdir != "") return; #if defined (_WIN32) auto t = FileSystem::GetDocumentsPath() + "/" + App::GetName(); #else auto t = FileSystem::GetAppDataPath() + "/." + String::Lower(App::GetName()); #endif if (FileSystem::GetFileType(t) == 0) { System::Print("Building Settings folder under:" + t); FileSystem::CreateDir(t); } settingsdir = t; #else // If settingsdir is not "", we did this already, no need to do it again. if (settingsdir != L"") return; #if defined (_WIN32) auto t = FolderLocation(FOLDER_DOCUMENTS) + L"/" + AppName; #else auto t = FolderLocation(FOLDER_APPDATA) + L"/." + Lower(AppName); #endif if (FileType(t) == 0) { Print(L"Building Settings folder under:" + t); CreateDir(t); } settingsdir = t; #endif }  
    You'll now have the path to your settings directory stored in settingsdir. You can use this string to store files there easily like below: (Raw copy and paste, ignore my macros)
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void App::TakeScreenShot() { auto framebuffer = Framebuffer::GetCurrent(); if (framebuffer != NULL) { auto screenshot_path = GetSettingsPath() + "/Screenshots"; if (FileSystem::GetFileType(screenshot_path) == 0) { Msg("Building Screenshots folder under:" + screenshot_path); FileSystem::CreateDir(screenshot_path); } framebuffer->Screenshot(screenshot_path + "/" + App::GetName() + "_" + Timing::GetCurrentDate() + ".tga"); } else { Msg("Error: Failed to take screenshot."); } }  
    A long time ago, @Crazycarpet gave me code to parse a text file like a Leadwerks 4 config file or a Leadwerks 4 mat file. I turned that functionality into a class, and instead of having the engine phrase the information, I do this myself. This is a direct copy and paste of my scopes to give you an idea.
    // To Load auto settingspath = settingsdir + "/settings.cfg"; if (FileSystem::GetFileType(settingspath) == 0) { appsettings.fullscreen = false; appsettings.screenwidth = 1280; appsettings.screenheight = 720; appsettings.display = 0; appsettings.antialias = 1; //2 appsettings.lightquality = 1; appsettings.terrainquality = 1; appsettings.texturequality = Texture::GetDetail(); appsettings.waterquality = 1; appsettings.afilter = Texture::GetAnisotropy(); appsettings.verticalsync = false; appsettings.hdrmode = true; } else { auto config = LoadConfig(settingspath); appsettings.fullscreen = config->GetBoolValue("fullscreen", false); appsettings.screenwidth = config->GetIntValue("screenwidth", 800); appsettings.screenheight = config->GetIntValue("screenheight", 600); appsettings.display = config->GetIntValue("display", 0); appsettings.antialias = config->GetIntValue("antialias", 1); appsettings.lightquality = config->GetIntValue("lightquality", 1); appsettings.terrainquality = config->GetIntValue("terrainquality", 1); appsettings.texturequality = config->GetIntValue("texturequality", Texture::GetDetail()); appsettings.waterquality = config->GetIntValue("waterquality", 1); appsettings.afilter = config->GetIntValue("afilter", Texture::GetAnisotropy()); appsettings.verticalsync = config->GetBoolValue("verticalsync", false); appsettings.hdrmode = config->GetBoolValue("hdrmode", true); config = NULL; } //---------------------// // To Save auto config = CreateConfig(settingspath); config->WriteKeyValue("fullscreen", to_string(appsettings.fullscreen)); config->WriteKeyValue("screenwidth", to_string(appsettings.screenwidth)); config->WriteKeyValue("screenheight", to_string(appsettings.screenheight)); config->WriteKeyValue("display", to_string(appsettings.display)); config->WriteKeyValue("antialias", to_string(appsettings.antialias)); config->WriteKeyValue("lightquality", to_string(appsettings.lightquality)); config->WriteKeyValue("terrainquality", to_string(appsettings.terrainquality)); config->WriteKeyValue("texturequality", to_string(appsettings.texturequality)); config->WriteKeyValue("waterquality", to_string(appsettings.waterquality)); config->WriteKeyValue("afilter", to_string(appsettings.afilter)); config->WriteKeyValue("tfilter", to_string(appsettings.tfilter)); config->WriteKeyValue("verticalsync", to_string(appsettings.verticalsync)); config->WriteKeyValue("hdrmode", to_string(appsettings.hdrmode)); bool b = config->WriteOut(); config = NULL; return b;  
    Settings vs Arguments
    Since both settings and arguments are different tables, we must have a way to check if ether the setting or argument is enabled. The best way I chose to do this is to make bool functions that check if ether is true.
    //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool App::IsFullScreen() { if (appsettings.fullscreen == true || GetArgument("fullscreen") == "1") return true; return appsettings.fullscreen; }  
    Building The Window (Finally)
    Now it's time to actually build the window with the information we have via settings file, or augments passed in a command line. 
    For Leadwerks 4, I do something like this:
    Leadwerks::Window* CreateEngineWindow(std::string pszTileName, Leadwerks::Window* parent) { // Are we fullscreen? const bool fullscreenmode = App::IsFullScreen(); // Set the name of the app the same as the window. App::SetName(pszTileName); // Load Settings. App::LoadSettings(); //Load any zip files in main directory Leadwerks::Directory* dir = Leadwerks::FileSystem::LoadDir("."); if (dir) { for (std::size_t i = 0; i < dir->files.size(); i++) { std::string file = dir->files[i]; std::string ext = Leadwerks::String::Lower(Leadwerks::FileSystem::ExtractExt(file)); if (ext == "zip" || ext == "pak") { Leadwerks::Package::Load(file, KEY); } } delete dir; } // Create a window Leadwerks::Window* window; std::string windowtitle = App::GetName(); if (IsDebug()) windowtitle = windowtitle + " [Debug]"; if (fullscreenmode) { iVec2 gfx = System::GetGraphicsMode(Leadwerks::System::CountGraphicsModes() - 1); window = Leadwerks::Window::Create(windowtitle, 0, 0, gfx.x, gfx.y, Leadwerks::Window::Fullscreen, parent); } else { window = Leadwerks::Window::Create(windowtitle, 0, 0, App::GetScreenWidth(), App::GetScreenHeight(), Leadwerks::Window::Titlebar | Leadwerks::Window::Center, parent); } EngineWindow::current = window; window->Activate(); return window; }  
    This is my current Leadwerks 5 code:
    shared_ptr<Window> CreateEngineWindow(std::wstring pszTileName, shared_ptr<Window> parent) { //Get the primary display auto display = App::GetDisplay(); // Are we fullscreen? const bool fullscreenmode = App::IsFullScreen(); // Set the name of the app the same as the window. App::SetName(pszTileName); // Load Settings. App::LoadSettings(); // Create a window shared_ptr<Window> window; std::wstring windowtitle = App::GetName(); if (IsDebug()) windowtitle = windowtitle + L" [Debug]"; if (fullscreenmode) { auto gfxmodes = display->GraphicsModes(); auto gfx = gfxmodes[gfxmodes.size() - 1]; window = CreateWindow(display, App::GetName(), 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN, parent); } else { Vec2 displayscale = display->GetScale(); window = CreateWindow(display, windowtitle, 0, 0, App::GetScreenWidth() * displayscale.x, App::GetScreenHeight() * displayscale.y, WINDOW_TITLEBAR | WINDOW_CENTER, parent); } return window; }  
    As you might have picked up, I store all of the core app functionality in a singleton class called "App". This is what the header looks like to give you an idea.
    class App { public: #ifndef LEADWERKS_5 static void SetName(const std::string& pszAppName); static std::string GetName(); #else static void SetName(std::wstring pszAppName); static std::wstring GetName(); #endif static void ParseCommandLine(int argc, const char* argv[]); static bool CheckArgument(std::string pszKey); static std::string GetArgument(std::string pszKey); static int GetArgumentInt(std::string pszKey); static float GetArgumentFloat(std::string pszKey); std::map<std::string, std::string> Arguments(); static bool LoadSettings(); static bool SaveSettings(); static AppSettings GetSettings(); #ifndef LEADWERKS_5 static std::string GetSettingsPath(); #else static std::wstring GetSettingsPath(); #endif static void Shutdown(); static bool Running(); static void EnableVR(); static bool IsDevMode(); static bool IsFullScreen(); static unsigned int GetScreenWidth(); static unsigned int GetScreenHeight(); static unsigned int GetDisplayIndex(); #ifdef LEADWERKS_5 static shared_ptr<Display> GetDisplay(); #endif static unsigned int GetAntialias(); static unsigned int GetLightingQuality(); static unsigned int GetTerrainQuality(); static unsigned int GetTextureQuality(); static unsigned int GetWaterQuality(); static unsigned int GetAnisotropicFilter(); static bool GetTrilinearFilter(); static bool GetVerticalSync(); static bool GetHDRMode(); static void TakeScreenShot(); };  
    Bonus: Load JSON Files Easily
    Leadwerks 5 includes the json library included, but as of time of writing, there is no simple way to load the format into something usable. Josh shared his approach using the engine's Stream Class. The json library can also be included in Leadwerks 4 with little to no effort! This is a very nice format to have in your base app code.
    #if defined (EOF) #undef EOF #endif nlohmann::json ReadJSON(const std::string& pszFileName) { #ifndef LEADWERKS_5 auto stream = FileSystem::ReadFile(pszFileName); auto path = FileSystem::RealPath(pszFileName); if (stream == NULL) { Print("Error: Failed to load JSON script \"" + path + "\""); //stream->path = L""; return NULL; } //Check for starting bracket bool started = false; while (!stream->EOF()) { //auto c = stream->ReadLine(); auto c = String::Chr(stream->ReadUChar()); if (String::Trim(c) == "") continue; if (c != "{") return false; started = true; break; } if (!started) return false; stream->Seek(0); #else auto stream = ReadFile(pszFileName); auto path = RealPath(pszFileName); if (stream == NULL) { Print(L"Error: Failed to load JSON script \"" + path + L"\""); //stream->path = L""; return NULL; } //Check for starting bracket bool started = false; while (!stream->Ended()) { auto c = Chr(stream->ReadByte()); if (Trim(c) == "") continue; if (c != "{") return NULL; started = true; break; } if (!started) return false; stream->Seek(0); #endif std::vector<uint8_t> data; auto sz = stream->GetSize(); data.resize(sz); stream->Read(data.data(), sz); nlohmann::json j3; try { j3 = nlohmann::json::parse(data); } catch (std::exception & e) { std::cout << e.what() << std::endl; return false; } if (j3.type() != nlohmann::json::value_t::object) return NULL; #ifndef LEADWERKS_5 stream->Release(); #endif stream = NULL; return j3; } #ifdef LEADWERKS_5 nlohmann::json WReadJSON(const std::wstring& pszFileName) { auto stream = ReadFile(pszFileName); auto path = RealPath(pszFileName); if (stream == NULL) { Print(L"Error: Failed to load JSON script \"" + path + L"\""); //stream->path = L""; return NULL; } //Check for starting bracket bool started = false; while (!stream->Ended()) { auto c = Chr(stream->ReadByte()); if (Trim(c) == "") continue; if (c != "{") return NULL; started = true; break; } if (!started) return false; stream->Seek(0); std::vector<uint8_t> data; auto sz = stream->GetSize(); data.resize(sz); stream->Read(data.data(), sz); nlohmann::json j3; try { j3 = nlohmann::json::parse(data); } catch (std::exception & e) { std::cout << e.what() << std::endl; return false; } if (j3.type() != nlohmann::json::value_t::object) return NULL; stream = NULL; return j3; } #endif  
    This type of code are things you can do right now in the Leadwerks 5 beta while you wait for the rest of the engine to come online. As for me, I already have this code done and this layer of stuff works flawlessly and produces the same results on both engines. If there is anything else you want me to share based on core.
  24. 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! 
  25. 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!) 
     
×
×
  • Create New...