Jump to content

reepblue

Developers
  • Posts

    2,661
  • Joined

  • Last visited

About reepblue

Recent Profile Visitors

110,506 profile views

reepblue's Achievements

Mentor

Mentor (12/14)

  • Well Followed
  • Problem Solver
  • Dedicated
  • Conversation Starter
  • Very Popular

Recent Badges

1.3k

Reputation

23

Community Answers

  1. Has this main CPP file changed since the script editor was introduced? It seems like the internal debugger no longer works with this code.
  2. I've been tinkering with Leadwerks 5 ever since it first went into Alpha. I enjoy writing abstraction classes and figuring out what's the best way to package a lot of functionality into a simple class. I most of the time have the right intentions but often fall flat on my face with the first attempts. Over time, I find myself rewriting and restructuring the classes over and over until I feel I have something solid like if it was part of the official API. In this article, I wish to share my top five classes that I can't live without! Convars/ConCommands Coming from Source, the lack of console commands was very alien to me at first. I was so used to being able to change data through a simple input box. This desire to have one goes all the way back when I started with Leadwerks 3. Eventually, I've written a pretty basic ConCommand system thanks to @Crazycarpet and I used it in Cyclone. There were no console variables (Cvars) as I thought they were pointless. Over time, I started to learn the difference between commands and variables and wanted to support both in Leadwerks 5. They are both static members and functions in the application that get generated at run time. Here's an example of a console command. void CC_Quit(std::vector<String> arg) { EmitEvent(EVENT_QUIT, NULL); } static ConCommand quit("quit", CC_Quit, CVAR_DEFAULT, "Quits the application"); Commands need a name, a description and can accept arguments, The CVAR_DEFAULT flag just tells the program what kind of command it is. There's also CVAR_CHEAT if you want the command or Cvar to only trigger if cheats are enabled. void CC_MSAA(String arg) { if (arg.empty()) return; auto cam = GetCamera(); if (cam) { cam->SetMsaa(arg.ToInt()); } } ConVar cam_msaa("msaa", "1", CVAR_SAVE, "Sets antialasing mode.", CC_MSAA); Here's an example of a Console Variable (CVar). It looks like a command but there's a slight difference here. First, a default value must be set. You'll notice that Cvars support CVAR_SAVE which the current value will be dumped to a file if SaveConVars(path) is used. Not all Cvars need a function callback. In this example, if msaa was called, the callback would apply the new msaa setting to the camera. The valve can act like a static member if that's all you need. You exclude both commands and variables with ExecuteCommand. const bool ExecuteCommand(const String& input, const bool quiet, const bool firecallback); You can simply use this system for your settings file instead of using a json file. Input System When I was developing Cyclone, I knew I couldn't ship without a way for people to change their keybindings. I actually spent a large amount of time making a complex system to support not only bindings, but also multiple forms of input. The idea of the Input System is that in your code, you just need to call the action (such as "Jump") and the backend will figure out what key or button it is. When Cyclone actually shipped, I learned that Steam Input just takes over XInput functionality. The only two devices you'll need to worry about is the Keyboard/Mouse and Steam Input. Steam Input is a whole can of worms, and the implementation needs to be suited to your game explicitly. Most projects just need a better alternative to using the hard coded window functions. To create the input system, all it needs is the window to listen to. auto window = CreateWindow(); auto input = CreateInputSystem(window); Then you can load your bindings like so. input->LoadBindings("keybindings.json"); These are the main commands for the input system, it's mostly a keyvalue system. There was an abstract system for action sets, but I didn't find them all useful. void SetActionBind(const String& name, const int button); int GetActionBind(const String& name); const bool ActionDown(const String& name); const bool ActionHit(const String& name); const bool ActionReleased(const String& name); Vec2 ActionAxis(const String& up, const String& down, const String& left, const String& right); The class is designed to be used as a singleton. This allows you to call the member from anywhere! auto input = GetInput(); if input->ActionHit("Jump") { Jump(); } ActionAxis turns for actions into a Vec2. This will make it easier to support controller sticks. Vec2 move = input->ActionAxis("MoveForward", "MoveBackward", "MoveLeft", "MoveRight"); Mouse look for FPS games is the only thing that can be tricky. All I felt like I can do is provide alternative methods to avoid using ActiveWindow(). if (input->GetWindowActive() and freelook) { iVec2 center = input->GetWindowCenter(); auto mpos = input->GetMousePosition(); input->SetMousePosition(center); auto centerpos = input->GetMousePosition(); // Blah blah blah.... } The input system is actually tied to the concommand system we've discussed earlier! You (or your players) can bind keys to commands or bind actions to keys with bind and bindaction respectfully. Object Pools and Queues These quick wrapper classes save a lot of copying and pasting. For Cyclone, I had a lot of cases in which I needed to manage entities in a vector. I didn't have to manage them much once added, but I needed to know what was added, what left, and not have things be pushed more than once. ObjectPool does just this. auto pool = CreateObjectPool() pool->Add(entity1); pool->Add(entity2); pool->Add(entity3); Instead of writing that long std::find method to check if an entity is in the container, I use ObjectPool::Find(). It's a lot nicer and easier to use! ObjectQueue is a little different. It's like ObjectPool but it only holds a certain number of objects. When a new object gets added to a full list, the old one gets removed. This is all thanks to std::queue. I used this in Cyclone for managing energy ball impact decals. I currently have it set to delete the entity which can cause potential problems, I may just remove the object from the queue and allow for a callback. Graphics Window If you played Cyclone, you'll find a disclaimer on the settings window stating that you need to reset the game to see the window mode changes. This is because of the GUI class in Leadwerks 4 needing a window at all times. If you delete the window and/or GUI, the program will crash! The 2D drawing system works differently in Leadwerks 5. The system looks for a camera to render on to meaning you can destroy the window and framebuffer, and make a new one to render onto! We do this because it's currently not possible to resize the window with SetShape or any other functions with a framebuffer attached. The wrapper class just manages the window and framebuffer pointers. Every time it's requested to "resize", the class will just delete the pointers and rebuild them. It also sends an event broadcasting the new sizes so any UI elements can adjust. The GraphicsWindow class also has the input system built in so there's no need to worry about that. A neat feature is that I've added functionality to toggle between full screen mode with F11. This makes testing much more enjoyable and easier! Player Camera Last but not least is the PlayerCamera class. You'll run into a lot of situations where you need to locate the player's camera. That's easy, you just find your player component and retrieve the camera from that, right? Ok, but what happens if the scene clears and so does your camera. All the changes you've done to it need to be re-applied. This is a bigger issue when it comes to UI setup. It's probably best you have the player class as a global member. Ok, but why does is abstraction class warranted? Well, there's a nice effect I like to have and that's the Outline effect. It takes a bit of work to setup and actually requires 2 additional cameras to the mix. It would be nice if I can just tell the camera to make something glow. And managing settings can be complicated being that some settings are stored with the world while the rest are stored with the camera. What about Post Processing and handling that? Not all players will be able to run the game with all the effects enabled! Plus, with post effects, there's a certain order they need to be stacked. The PlayerCamera Class just compacts all of that into one wrapper. // Create the camera. auto camera = CreatePlayerCamera(world) camera->SetPosition(0,0,-4) camera->TogglePostEffectState(POSTEFFECT_BLOOM, true); // Add a box, make it glow! auto mdl = CreateBox() camera->AddEntityToGlowList(mdl); Settings are ether on/off or a settings level. enum SettingMode { SETTING_DISABLED = 0, SETTING_LOW, SETTING_MEDIUM, SETTING_HIGH, SETTING_ULTRA }; // Settings virtual void SetFov(const float fov); virtual const float GetFov(); virtual void SetGamma(const float gamma); virtual const float GetGamma(); virtual void SetSsr(const bool mode); virtual const bool GetSsr(); virtual void SetRefraction(const bool mode); virtual const bool GetRefraction(); virtual void SetTessellation(const SettingMode setting); virtual const SettingMode GetTessellation(); virtual void SetMsaa(const SettingMode setting); virtual const SettingMode GetMsaa(); virtual void SetLightQuality(const SettingMode setting); virtual const SettingMode GetLightQuality(); virtual void SetShadowQuality(const SettingMode setting); virtual const SettingMode GetShadowQuality(); Like the input system, this is intended to be used as a singleton. If CreatePlayerCamera() is called another time, it'll just return the existing static pointer. auto camera = ActiveCamera(); camera->SetMsaa(SETTING_HIGH); Conclusion After years of playing around with the engine. These are the types of classes I keep revisiting. Now, I feel like I got them perfect. They are self-contained and don't necessary rely on each other with the exception of the GraphicsWindow class and the Input System for convenience. I'm interested in hearing your feedback and suggestions for other classes you feel are necessary in projects! Oh, did I mention these are all exposed to Lua?
  3. Ok, I was under the impression that they weren't' exposed because they didn't auto complete.
  4. So far, I've found that AddText isn't exposed. TEXTAREA_SCROLLDOWN isn't exposed. Lock/Unlock isn't exposed. --Get the displays local displays = GetDisplays() --Create a window local window = CreateWindow("Console", 0, 0, 800 * displays[1].scale, 600 * displays[1].scale, displays[1], WINDOW_CENTER | WINDOW_TITLEBAR) local ui = CreateInterface(window) local parent = ui.background local x = 8 local y = 8 local sz = parent:ClientSize() local log = CreateTextArea(x, y, sz.x - x * 2, sz.y - 40 - y, parent, TEXTAREA_SCROLLDOWN) log:SetLayout(1, 1, 1, 1) local textentryfield = CreateTextField(x, sz.y - 32, sz.x - x * 2, 24, parent) textentryfield:SetLayout(1, 1, 0, 1) while not window:Closed() do local ev = WaitEvent() --ui:ProcessEvent(ev) if ev.id == EVENT_PRINT then if log.text == "" then log:AddText(ev.text) else log:AddText("\n" .. ev.text) end elseif ev.id == EVENT_WIDGETACTION then if ev.source == textentryfield then textentryfield:Lock() Print("] " .. ev.text) textentryfield:Activate() textentryfield:SetText("") textentryfield:UnLock() end elseif ev.id == EVENT_WINDOWCLOSE then return 0 end end
  5. #include "Leadwerks.h" using namespace Leadwerks; int main(int argc, const char* argv[]) { //Get the displays auto displays = GetDisplays(); //Create a window auto window = CreateWindow("Leadwerks", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR); //Create a world auto world = CreateWorld(); //Create a framebuffer auto framebuffer = CreateFramebuffer(window); //Create a camera auto camera = CreateCamera(world); camera->SetClearColor(0.125); camera->SetFov(70); camera->SetPosition(0, 0, -3); camera->Listen(); //Create a light auto light = CreateBoxLight(world); light->SetRotation(35, 45, 0); light->SetRange(-10, 10); //Create a box auto box = CreateBox(world); box->SetColor(0, 0, 1); //Sound auto sound = LoadSound("https://raw.githubusercontent.com/Leadwerks/Documentation/master/Assets/Sound/notification.wav"); auto speaker = CreateSpeaker(sound); speaker->SetLooping(true); speaker->SetPosition(box->GetPosition(true)); speaker->Play(); speaker->SetRange(10); //Main loop bool swapped = false; while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false) { //Add filter when space key is pressed if (window->KeyHit(KEY_SPACE)) { swapped = not swapped; if (swapped) speaker->SetFilter(AUDIOFILTER_REVERB_SEWERPIPE); else speaker->SetFilter(AUDIOFILTER_NONE); } //Move and turn with the arrow keys - best experienced with headphones if (window->KeyDown(KEY_UP)) camera->Move(0, 0, 0.1); if (window->KeyDown(KEY_DOWN)) camera->Move(0, 0, -0.1); if (window->KeyDown(KEY_LEFT)) camera->Turn(0, -1, 0); if (window->KeyDown(KEY_RIGHT)) camera->Turn(0, 1, -0); world->Update(); world->Render(framebuffer); } return 0; } It'll work the first time, then it'll work when you go to set it back to none. However, when you try it again, it doesn't work.
  6. There's no HUD icons with the FPSPlayer because when we put this together, the UI system wasn't finalized. To be honest, I think this is a great opportunity to show off how to set up the outline effect.
  7. It will still generate a json file like so. { "autogenerated": true, "component": { "inputs": {} , "outputs": {} , "properties": {} } }
  8. I'm noticing that the editor will take the liberty and clear json files on the stock components as they don't have their variables commented. The editor has made me have to redo a few scripts. I was under the impression that the editor would leave these alone if autogenerated was false or not defined. I'd like to be able to turn this off if possible.
  9. The setting works per material, but the entity values aren't being applied. indicator_strip_shared.zip
  10. I brought this up in the discord and now I'm posting it here. It seems that models that have a collision mesh get additional padding on the 2D grid view. This can make it hard to scale certain models like pipes. No collision box After adding a box collision shape.
  11. All good with 572.70 with my 4070. I kind of don't want to upgrade now seeing what's happening. :|
  12. Agreed. I've brought this up multiple times. I only used the local gizmo to see what was considered an entity's forward direction.
  13. K3nt on Discord has been begging for this feature for the longest time. You can hide faces in the editor. There's no need for a tool brush. I suggested this back in 2023.
  14. I'd like to see thumbnail images of the prefabs in the editor like I could in Leadwerks 4.
      • 5
      • Upvote
  15. Like I said on a workshop meeting, I like to take an existing particle effect and use it as a starting point for my own effect. Having a library of effects would be very helpful.
×
×
  • Create New...