Jump to content

Josh

Staff
  • Posts

    23,315
  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh
    I am now in possession of the official Leadwerks USB drives.  Weighing in at 16 GB, they look fantastic.

    These will be shipped out shortly, along with the posters from the last game tournament.  I've been working behind the scenes on some big things that took my attention, but I am not yet ready to reveal any of this.  Because of the timing on this, we are going to do a Leadwerks Fall Games Festival starting in September.
    I am going to spend the rest of the summer in Europe, where I will be accompanied by my handy-dandy Gigabyte Brix Pro provided to me by Intel at the first Steam Dev Days.  Thanks Intel!:

    I will be working on NAT punch-through, Leadwerks 5, and making bug fixes.  I will also be meeting with developers in countries with a much cheaper cost of living, for the purpose of potentially expanding the company's development capabilities so I can bring you more cool stuff.
  2. Josh
    Previously, I laid out a pretty complete design of the racing game template I want to build. There's definitely enough there to build out the concept with very little left unanswered.
     
    We are going to have a marble game template, because of its simplicity and ease of learning. However, unless the template really looks like a game people would want to play, it doesn't offer enough "carrot" to inspire people. This idea is less well-defined than the racing game, so I am only in the idea stage right now. There's two big aspects of this I want to figure out: level design and game mechanics.
    Level Design
    If you look at a lot of this type of game on Steam, they pretty much all just use some random blocks or generic levels and place a ball in it. This is what I do not want:
     

     
    A game of this type should have a highly ordered repeating geometry.
     

     

     
    These examples are more orthogonal than I would like, but it's a start. I definitely want the base of it to be a checkered pattern. I do not want random blocks. See how the Sonic level design uses some trim along the edges to make it look nicer?
     
    Here's an example of some more organic elements mixed into the design:
     

     
    Here's a nice curve. Not exactly what I want, but it provides some ideas:
     

     
    I like the curving plastic pieces here:
     

     
    It should look like a toy kit you put together. The wooden elements are also pretty cool.
     
    So here's my quick summary of what I want in the level design:
    Mostly orthogonal and diagonal brushes with a checkboard pattern. Lush vegetation to break up edges and make the world less boxy. Curved pre-formed pieces that look like plastic and wooden toys.  
    Overall I want the world to feel like an old Nintendo fantasy world with an adventurous feel. The music might not be 8-bit, but should have some of that style:
    http://www.playonloop.com/2017-music-loops/doctor-gadget/
     
    The marble itself should have a distinctive look. Maybe a reflective silver ball-bearing, using the SSG effect to reflect the world around it?
    Here are some concept textures.
    +




    Mechanics
    I am pretty foggy on this because I don't play this type of game. Fans? Moving platforms? Loop-de-loops? Got any suggestions or examples from other games you would like to see me implement? If you have a video of gameplay, that's even better.
    The idea is to implement a few reusable mechanics that can be recombined in interesting ways, so that someone can make a finished game just by recombining them and making additional levels. I also favor mechanics that are associated with a specific model so that they are easily recognizable, like fans, magnets, saws, etc.
    Tell me your ideas in the comments below.
  3. Josh
    I have proven beyond a shadow of a doubt that I am very bad at shipping posters.  The Winter Game Tournament was completed in January and I have yet to deliver your prizes.  If you have a job opening for someone to ship prizes, or to ship anything at all, I am probably the worst candidate you could ever hope to find to fill said position.  Seriously.
    Part of the problem (although it's still not an excuse) has been that it is actually incredibly difficult to have custom USB keychains made.  These took a long time because my description of black monochrome printing with the text vertical aligned was too apparently too complicated.

    The first proof I got used a beautiful white-on-silver-so-light-it's-practically-white color scheme.

    The second attempt's failure was slightly more subtle.  Slightly.  Is it just me, or does the logo obviously beg to be centered by the vertical center of the text, instead of the image dimensions?  Is this really that hard?:

    At this point I was all like:

    So I drew them a picture of something that to me seemed extremely obvious.  Finally, I got the corrected proof of our glorious USB keychain and have authorized a limited production of this one-of-a-kind item.  I will give props to the graphics designer for choosing the dark gray (subtle blue?) print color, which is much better than #000000:

    All I wanted was a little taste in design.
    So here's what I am going to do.  Each entrant in the Winter Games Tournament will receive one limited-edition poster, with one limited-edition Leadwerks USB keychain inside the poster tube, plus one Leadwerks sticker (which I have a ton of).  Because of the delay and the fact that I suck at shipping prizes, I am bumping up the 4GB USB drive to a whopping 16GB.  That's four times the jigglebytes, and enough to back up the entire Leadwerks.com website on.
    In other news, the acquisition of American Apparel by Gilden Activewear has actually affected our supply chain for the fabulous Leadwerks line of clothing.  The official Leadwerks hoodie in our signature gray color is currently only available in sizes small and extra small.

    I contacted SpreadShirt.com about the matter and received the following explanation:
    The first and most pertinent question is why is the little mermaid working in customer service for SpreadShirt?

    The second question is when will the official Leadwerks hoodie become available again?  The garment can be gotten in other colors, but I do not feel that any of these less appealing colors adequately represent the brand of our game engine.  This is most distressing and I will continue to look for a solution.
    The 2017 Summer Game Tournament will proceed once I have shipped the prizes out from the previous tournament.  However, as I have proven that I am not a reliable shipper of prizes, the tournament is going back to our original prizeless model.  A roundup of entries from all game developers will be posted at the end and fun will be had by all.
  4. Josh
    Leadwerks 4.x will see a few more releases, each remaining backwards compatible with previous versions.  Leadwerks 5.0 is the point where we will introduce changes that break compatibility with previous versions.  The changes will support a faster design that relies more on multi-threading, updates to take advantage of C++11 features, and better support for non-English speaking users worldwide.  Changes are limited to code; all asset files including models, maps, shaders, textures, and materials will continue to be backwards-compatible.
    Let's take a look at some of the new stuff.
    Shared Pointers
    C++11 shared pointers eliminate the need for manual reference counting.  Using the auto keyword will make it easier to update Leadwerks 4 code when version 5 arrives.  You can read more about the use of shared pointers in Leadwerks 5 here.
    Unicode Support
    To support the entire world's languages, Leadwerks 5 will make use of Unicode everywhere.  All std::string variables will be replaced by std::wstring.  Lua will be updated to the latest version 5.3.  This is not compatible with LuaJIT, but the main developer has left the LuaJIT project and it is time to move on.  Script execution time is not a bottleneck, Leadwerks 5 gains a much longer window of time for your game code to run, and I don't recommend people build complex VR games in Lua.  So I think it is time to update.
    Elimination of Bound Globals
    To assist with multithreaded programming, I am leaning towards a stateless design with all commands like World::GetCurrent() removed.  An entity needs to be explicitly told which world it belongs to upon creation, or it must be created as a child of another entity:
    auto entity = Pivot::Create(world); I am considering encapsulating all global variables into a GameEngine object:
    class GameEngine { public: std::map<std::string, std::weak_ptr<Asset> > loadedassets; shared_ptr<GraphicsEngine> graphicsengine; shared_ptr<PhysicsEngine> physicsengine; shared_ptr<NetworkEngine> networkengine; shared_ptr<SoundEngine> soundengine; shared_ptr<ScriptEngine> scriptengine;//replaces Interpreter class }; A world would need the GameEngine object supplied upon creation:
    auto gameengine = GameEngine::Create(); auto world = World::Create(gameengine); When the GameEngine object goes out of scope, the entire game gets cleaned up and everything is deleted, leaving nothing in memory.
    New SurfaceBuilder Class
    To improve efficiency in Leadwerks 5, surfaces will no longer be stored in system memory, and surfaces cannot be modified once they are created.  If you need a modifiable surface, you can create a SurfaceBuilder object.
    auto builder = SurfaceBuilder::Create(gameengine); builder->AddVertex(0,0,0); builder->AddVertex(0,0,1); builder->AddVertex(0,1,1); builder->AddTriangle(0,1,2); auto surface = model->AddSurface(builder); When a model is first loaded, before it is sent to the rendering thread for drawing, you can access the builder object that is loaded for each surface:
    auto model = Model::Load("Models\box.mdl", Asset::Unique); for (int i=0; i<model->CountSurfaces(); ++i) { auto surface = model->GetSurface(i); shared_ptr<SurfaceBuilder> builder = surface->GetBuilder(); if (builder != nullptr) { for (int v=0; v < surface->builder->CountVertices(); ++v) { Vec3 v = builder->GetVertexPosition(v); } } } 98% of the time there is no reason to keep vertex and triangle data in system memory.  For special cases, the SurfaceBuilder class does the job, and includes functions that were previously found in the Surface class like UpdateNormals().  This will prevent surfaces from being modified by the user when they are in use in the rendering thread.
    A TextureBuilder class will be used internally when loading textures and will operate in a similar manner.  Pixel data will be retained in system memory until the first render.  These classes have the effect of keeping all OpenGL (or other graphics API) code contained inside the rendering thread, which leads to our next new feature...
    Asynchronous Loading
    Because surfaces and textures defer all GPU calls to the rendering thread, there is no reason we can't safely load these assets on another thread.  The LoadASync function will simply return true or false depending on whether the file was able to be opened:
    bool result = Model::LoadASync("Models\box.mdl"); The result of the load will be given in an event:
    while (gameengine->eventqueue->Peek()) { auto event = gameengine->eventqueue->Wait(); if (event.id == Event::AsyncLoadResult) { if (event.extra->GetClass() == Object::ModelClass) { auto model = static_cast<Model>(event.source.get()); } } } Thank goodness for shared pointers, or this would be very difficult to keep track of!
    Asynchronous loading of maps is a little more complicated, but with proper encapsulation I think we can do it.  The script interpreter will get a mutex that is locked whenever a Lua script executes so scripts can be run from separate threads:
    gameengine->scriptengine->execmutex->Lock(); //Do Lua stuff gameengine->scriptengine->execmutex->Unlock(); This allows you to easily do things like make an animated loading screen.  The code for this would look something like below:
    Map::LoadASync("Maps/start.map", world); while (true) { while (EventQueue::Peek()) { auto event = EventQueue::Wait(); if (event.id == Event::AsyncLoadResult) { break; } } loadsprite->Turn(0,0,1); world->Render();// Context::Sync() might be done automatically here, not sure yet... } All of this will look pretty familiar to you, but with the features of C++11 and the new design of Leadwerks 5 it opens up a lot of exciting possibilities.
  5. Josh
    C++11 introduces shared pointers, a powerful language feature that provides easy memory management without the overhead of garbage collection.  The example below should look familiar to you:
    #include "Leadwerks.h" using namespace Leadwerks; int main(int argc, const char *argv[]) { shared_ptr<Leadwerks::Window> window = Leadwerks::Window::Create(); shared_ptr<Context> context = Context::Create(window); shared_ptr<World> world = World::Create(); shared_ptr<Camera> camera = Camera::Create(); camera->SetRotation(35, 0, 0); camera->Move(0, 0, -6); shared_ptr<Light> light = DirectionalLight::Create(); light->SetRotation(35, 35, 0); shared_ptr<Model> model = Model::Box(); model->SetColor(1.0, 0.0, 0.0); model->SetPosition(-4, 0, 0); while (true) { if (window->Closed() || window->KeyDown(Key::Escape)) return false; Leadwerks::Time::Update(); world->Update(); world->Render(); context->Sync(); } //Everything will get automatically deleted when we return from this function return 0; } Using the auto keyword simplifies everything (and it makes this code compatible with Leadwerks 4):
    #include "Leadwerks.h" using namespace Leadwerks; int main(int argc, const char *argv[]) { auto window = Leadwerks::Window::Create(); auto context = Context::Create(window); auto world = World::Create(); auto camera = Camera::Create(); camera->SetRotation(35, 0, 0); camera->Move(0, 0, -6); auto light = DirectionalLight::Create(); light->SetRotation(35, 35, 0); auto model = Model::Box(); model->SetColor(1.0, 0.0, 0.0); model->SetPosition(-4, 0, 0); while (true) { if (window->Closed() || window->KeyDown(Key::Escape)) return false; Leadwerks::Time::Update(); world->Update(); world->Render(); context->Sync(); } //Everything will get automatically deleted when we return from this function return 0; } Now things get interesting.  This function would normally cause a horrible memory leak, but with shared pointers everything is fine:
    void SaveTexture(shared_ptr<Texture> tex) { auto bank = Bank::Create(tex->GetMipmapSize()); tex->GetPixels(bank->buf); bank->Save("pixeldata.dat"); } Yet shared pointers can still equal nullptr:
    auto bank = Bank::Create(); bank.reset(); Debug::Assert(bank==nullptr); You can even simply set a shared pointer to nullptr, and if that was the last pointer that referenced it, it gets deleted!
    auto bank = Bank::Create(); bank = nullptr;// auto deletion here! How to Delete an Entity
    The code below will not delete the entity, because a shared pointer is still stored in the world.
    auto entity = Pivot::Create(); entity = nullptr; The entity must be have its world set to NULL, and the shared pointer must be set to NULL or go out of scope:
    entity = Pivot::Create(); entity->SetWorld(nullptr); entity = nullptr; Children use a weak pointer to the parent, so they will not cause self-referencing.
    Asset Management
    You no longer have to worry about calling Release() when loading assets:
    auto material = Material::Create(); auto texture = Texture::Load("mytex.tex"); material->SetTexture(texture); texture = nullptr; Unused assets will automatically be deleted if they go out of scope:
    auto material = Material::Create(); auto texture = Texture::Load("mytex.tex"); material->SetTexture(texture); texture = nullptr; material = nullptr; But if they are in use, they will be retained in memory:
    auto material = Material::Create(); auto texture = Texture::Load("mytex.tex"); material->SetTexture(texture); model->SetMaterial(material); texture = nullptr; material = nullptr; In conclusion, shared pointers automate many of the tasks we have been doing manually with the Leadwerks reference counting system and the AddRef and Release commands.
  6. Josh
    Back around February I started working on a website update that included the following:
    Responsive design everywhere. SSL everywhere. Visual improvement of website. Updated documentation system. Tutorials for C++ programming basics. Update forum software to new major version. Forum moved to new URL. All of that is now pretty much done.  These changes improve the online Leadwerks experience and are independent from the software itself, so it was a good idea to get them done now.
    Since September I've had more time to think about Leadwerks Game Engine 5, and although I am not completely sold on Vulkan, I think it's a good plan.
    Leadwerks 5 is all about performance and user experience with VR as a prime target.
    Multithreaded Architecture
    Separate threads for navmesh updating, physics, game logic, culling, and rendering.  The rendering thread loops at a constant 60 or 90 (for VR) frames per second regardless of what your game is doing.  This gives your game logic four times more time to run, while independently maintaining a constant framerate.  The design I have in mind will make Leadwerks 5 the fastest game engine, ever, and choosing Leadwerks for VR will be a no-brainer.
    Leadwerks Editor 5
    A new editor will be written in C++ using Leadwerks GUI, which will give us the same appearance on Windows, Linux, and Mac.  Functionality will be pretty close to the existing editor, but with more room to grow and a few improvements.  Because it's written in C++ parts of the editor can be exposed to Lua, and an editor API will be provided for making Lua mods and plugins.  By default, it will use a dark theme to be easy on the eyes.  A standalone script editor may be provided as well.
    PBR Material System with Substance Support
    The lighting model will use a more advanced lighting equation and substance PBR materials (metalness and roughness) will be loaded natively.
    Shared Pointers
    The reference counting system in the Object class will be replaced with C++11 shared pointers.  This gives you the performance of C++ with ease of use like a garbage-collected language.
    64-bit
    The engine and editor will be released as a 64-bit build only.
    Game Templates
    More game templates will be provided.  Fortunately we can add these now and updates for Leadwerks 5 will be minimal.
    Open-Source Components
    Source code to some parts of the engine and editor may be provided on the Leadwerks GitHub account.  For example, I may make a standalone open-source script editor or publish some of the engine classes for the community to play with.
    Platforms
    Leadwerks 5 will launch on Windows, Linux, and Mac.  The improved compatibility of Leadwerks 5 means we could do crazy things like run the editor on an iPad, but I'm going to stick with what I know sells.
    Enterprise Edition
    A standalone version that does not use Steam will be sold in bundles to companies that require this.
    Pricing
    A monthly plan may be introduced at around $5-20 per month.  Pricing for a perpetual license for the standard and pro editions would most likely be the same as now ($99-199), with a discount for early adopters / upgrades.  The enterprise version would probably be about $1000 per seat with a discount for schools.
    If you found this blog interesting, please consider using the social media share buttons below to share it.
  7. Josh
    After a lot of research and development, Leadwerks GUI is almost ready to release.  The goal with this system was to create an in-game GUI that was customizable, extendable, and could also serve as a windowed GUI for application development in the future.
    Widgets
    The GUI system is based on the Widget class.  Once a GUI is created on a rendering context you can add widgets to it.  Each widget is a rectangular container with a padding area.  The widgets can be arranged in a hierarchy and their bounds will clip the contents of their child widgets, both for rendering and mouse interaction.
    The GUI is automatically rendered onto the screen inside the call to Context:Sync(),
    Widget Scripts
    Each widget has a Lua script to control rendering and behavior, similar to the way Lua scripts work in our entity system.  The script assigned to a widget controls what type of widget it is, how it looks, and how it interacts with mouse and keyboard input.  A set of widget scripts are provided to create a variety of controls including buttons, checkboxes, text entry boxes, list boxes, text display areas, choice boxes, sliders, and more.
    You can create your own widget scripts to add new types of controls, like for an RPG interface or something else.  The script below shows how the tabber widget is implemented.
    --Styles if Style==nil then Style={} end if Style.Panel==nil then Style.Panel={} end Style.Panel.Border=1 Style.Panel.Group=2 --Initial values Script.indent=1 Script.tabsize = iVec2(72,28) Script.textindent=6 Script.tabradius=5 function Script:Start() self.widget:SetPadding(self.indent,self.indent,self.tabsize.y+self.indent,self.indent) end function Script:MouseLeave() if self.hovereditem~=nil then self.hovereditem = nil local scale = self.widget:GetGUI():GetScale() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1) --self.widget:Redraw() end end function Script:Draw(x,y,width,height) local gui = self.widget:GetGUI() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) local scale = self.widget:GetGUI():GetScale() local n local sel = self.widget:GetSelectedItem() --Draw border gui:SetColor(0) gui:DrawRect(pos.x,pos.y+self.tabsize.y*scale,sz.width,sz.height-self.tabsize.y*scale,1) --Draw unselected tabs for n=0,self.widget:CountItems()-1 do if n~=sel then self:DrawTab(n) end end --Draw selected tab if sel>-1 then self:DrawTab(sel) end ---Panel background gui:SetColor(0.25) gui:DrawRect(pos.x+1,pos.y+self.tabsize.y*scale+1,sz.width-2,sz.height-self.tabsize.y*scale-2) end function Script:DrawTab(n) local gui = self.widget:GetGUI() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) local scale = self.widget:GetGUI():GetScale() local s = self.widget:GetItemText(n) local textoffset=2*scale if self.widget:GetSelectedItem()==n then textoffset=0 end local leftpadding=0 local rightpadding=0 if self.widget:GetSelectedItem()==n then gui:SetColor(0.25) if n>0 then leftpadding = scale*1 end rightpadding = scale*1 else gui:SetColor(0.2) end gui:DrawRect(-leftpadding+pos.x+n*(self.tabsize.x)*scale,textoffset+pos.y,rightpadding+leftpadding+self.tabsize.x*scale+1,self.tabsize.y*scale+self.tabradius*scale+1,0,self.tabradius*scale) gui:SetColor(0) gui:DrawRect(-leftpadding+pos.x+n*(self.tabsize.x)*scale,textoffset+pos.y,rightpadding+leftpadding+self.tabsize.x*scale+1,self.tabsize.y*scale+self.tabradius*scale+1,1,self.tabradius*scale) if self.widget:GetSelectedItem()~=n then gui:SetColor(0) gui:DrawLine(pos.x+n*self.tabsize.x*scale,pos.y+self.tabsize.y*scale,pos.x+n*self.tabsize.x*scale+self.tabsize.x*scale,pos.y+self.tabsize.y*scale) end if self.hovereditem==n and self.widget:GetSelectedItem()~=n then gui:SetColor(1) else gui:SetColor(0.7) end gui:DrawText(s,pos.x+(n*self.tabsize.x+self.textindent)*scale,textoffset+pos.y+self.textindent*scale,(self.tabsize.x-self.textindent*2)*scale-2,(self.tabsize.y-self.textindent*2)*scale-1,Text.VCenter+Text.Center) end function Script:MouseDown(button,x,y) if button==Mouse.Left then if self.hovereditem~=self.widget:GetSelectedItem() and self.hovereditem~=nil then self.widget.selection=self.hovereditem local scale = self.widget:GetGUI():GetScale() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1) EventQueue:Emit(Event.WidgetAction,self.widget,self.hovereditem) end elseif button==Mouse.Right then if self.hovereditem~=self.widget:GetSelectedItem() and self.hovereditem~=nil then EventQueue:Emit(Event.WidgetMenu,self.widget,self.hovereditem,x,y) end end end function Script:KeyDown(keycode) if keycode==Key.Right or keycode==Key.Down then local item = self.widget:GetSelectedItem() + 1 if item<self.widget:CountItems() then self.widget.selection=item local scale = self.widget:GetGUI():GetScale() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1) EventQueue:Emit(Event.WidgetAction,self.widget,item) end elseif keycode==Key.Left or keycode==Key.Up then local item = self.widget:GetSelectedItem() - 1 if item>-1 and self.widget:CountItems()>0 then self.widget.selection=item local scale = self.widget:GetGUI():GetScale() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1) EventQueue:Emit(Event.WidgetAction,self.widget,item) end elseif keycode==Key.Tab then local item = self.widget:GetSelectedItem() + 1 if item>self.widget:CountItems()-1 then item=0 end if self.widget:CountItems()>1 then self.widget.selection=item local scale = self.widget:GetGUI():GetScale() local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1) EventQueue:Emit(Event.WidgetAction,self.widget,item) end end end function Script:MouseMove(x,y) local prevhovereditem = self.hovereditem self.hovereditem = nil local scale = self.widget:GetGUI():GetScale() local sz = self.widget:GetSize(true) if x>=0 and y>=0 and x<sz.width and y<self.tabsize.y*scale then local item = math.floor(x / (self.tabsize.x*scale)) if item>=0 and item<self.widget:CountItems() then self.hovereditem=item end end if self.hovereditem==self.widget:GetSelectedItem() and prevhovereditem==nil then return end if self.hovereditem==nil and prevhovereditem==self.widget:GetSelectedItem() then return end if prevhovereditem~=self.hovereditem then local pos = self.widget:GetPosition(true) local sz = self.widget:GetSize(true) self.widget:GetGUI():Redraw(pos.x,pos.y,sz.width,self.tabsize.y*scale+1) end end Widget Rendering
    Widgets are buffered and rendered with an advanced system that draws only the portions of the screen that need to be updated.  The GUI is rendered into a texture, and then the composite image is drawn onscreen.  This means you can have very complex interfaces rendering in real-time game menus with virtually no performance cost.
    By default, no images are used to render the UI so you don't have to include any extra files in your project.
    Widget Items
    Each widget stores a list of items you can add, remove, and edit.  These are useful for list boxes, choice boxes, and other custom widgets.
    GUI Events
    Leadwerks 4.4 introduces a new concept into your code, the event queue.  This stores a list of events that have occurred.  When you retrieve an event it is removed from the stack:
    while EventQueue:Peek() do local event = EventQueue:Wait() if event.source == widget then print("OK!") end end Resolution Independence
    Leadwerks GUI is designed to operate at any resolution.  Creation and positioning of widgets uses a coordinate system based on a 1080p monitor, but the GUI can use a global scale to make the interface scale up or down to accommodate any DPI including 4K and 8K monitors.  The image below is rendering the interface at 200% scaling on a 4K monitor.

    A default script will be included that you can include from Main.lua to build up a menu system for starting and quitting games, and handling common graphical features and other settings.

    Leadwerks GUI will be released in Leadwerks Game Engine 4.4.
  8. Josh
    A new build is available on the beta branch.  I am putting it out tonight because I want to give you guys as much lead time as possible.
    I have not tested all hardware yet, and there were a lot of shader updates, so there could easily be a compile error on AMD or Intel cards.  Will test those out in the morning.
    New camera commands for fog:
    virtual void SetFogColor(const float r, const float g, const float b, const float a); virtual void SetFogAngle(const float start, const float stop); virtual void SetFogRange(const float start, const float stop); virtual void SetFogMode(const bool mode); virtual Vec4 GetFogColor(); virtual Vec2 GetFogAngle(); virtual Vec2 GetFogRange(); virtual bool GetFogMode(); This is scheduled to be released on the default branch this Tuesday.
    Also useful:
     
  9. Josh
    Leadwerks Game Engine 4.4, scheduled for release soon, features some updated and enhanced visual effects.  In this blog I will talk about some of the adjustments I made.  Having "The Zone" scene that Aggror recreated actually helped a lot to see how shaders could be improved.
    Bloom / Iris Adjustment / HDR
    The bloom and iris adjustment shaders have been updated to give bloom a wider and softer blur.  Iris adjustment is faster and more intense now, which will make the outdoors areas seem very bright when you are indoors, and the indoors areas will look very dark when you are outdoors.


    SSAO
    The SSAO shader has multiple passes added to it, resulting in a much smoother yet crisp result. 



    Vegetation Shaders
    A new vegetation shader called "groundrocks" will make objects align to the terrain.  This lets you easily paint clusters of rocks all across a landscape, resulting in a nice chunky look that breaks up boring heightmap terrain.

    Built-in Fog
    Although several fog shaders have been available in the Workshop for some time, version 4.4 adds a built-in distance fog you can use to make spooky scenes.

    The fog is built into several shaders in order to give correct reflections.  It works great with water.  Notice the reflection is also foggy, and the water itself is affected by fog, giving a nice misty look to the far side of the lake.

    You can try Leadwerks Game Engine 4.4 right now by opting into the beta branch on Steam.
  10. Josh
    Distance fog is one of the most basic visual effects in 3D graphics, going back to the 1990s.  Here is the effect in the Quake 3 Arena map "Fatal Instinct", which was shrouded in a dense orange fog:

    Leadwerks Game Engine 2 had this available as a built-in effect, while the more flexible effects system of Leadwerks 3/4 had several Workshop shaders available to use, including one by Klepto and another one more recently added by myself.  However, this has not been part of the official SDK until version 4.4.  Why is that?
    The Problem
    When water is rendered in Leadwerks, a low-quality render is performed of the world with the camera scale inverted on the Y axis.  Because the reflection is distorted by ripples, we render to a lower-resolution buffer with all settings on low and effects disabled.  This is important because it gives a faster performance and image quality that is still acceptable.  The water plane also uses occlusion culling so that the extra pass is only rendered if part of the water plane is visible.  All post-processing effects are disabled in the reflection pass, which makes good sense, except in the case of fog.  If the world is shrouded in dense fog but the reflection in the water is clear, it creates an obvious problem.  In the screenshot below, a post-processing effect is applied to the world.  Although the water does have fog applied to it, the reflected image on the water does not have any fog, creating a stark problem, because post-processing effects are disabled in the reflection pass.

    Pre-Rendering Fog
    One option would have been to allow the user to mark some post effects as visible in reflection passes, but that seemed complicated and error-prone.  I came up with the idea build a simple fog calculation into the first ambient or directional lighting pass.  Here it is applied in the directional light pass:

    And here is what happens when the fog effect is applied in the directional light pass in the water reflection as well:

    We can modify the water shader itself to add the same fog calculation to the surface of the water.  Now our water matches the world.

    Removing Artifacts
    There was still more to do.  We are rendering fog first and other stuff later.  Anything rendered after the main lighting pass has to use the fog calculation to substract its effect from the scene.  For example SSAO will add shaded areas on top of fog, which we definitely don't want.  See the bridge and trees in the distance.

    The solution is to add the same fog calculation into the SSAO shader:
    float fogeffect = 0.0f; if (fogmode==true) { fogeffect = clamp( 1.0 - (fogrange.y - length(worldCoord - cameramatrix[3].xyz)) / (fogrange.y - fogrange.x) , 0.0, 1.0 ); fogeffect*=fogcolor.a; if (fogeffect==1.0f) { fragData0 = outputcolor; return; } } And then use the fog level to lessen the impact of the effect:
    fragData0 = outputcolor * fogeffect + outputcolor * (sumao / float(passes)) * (1.0 - fogeffect); The underwater artifacts are a separate issue that were solved by adding another calculation.  Here is the result:

    The same fog calculation had to be added to all light shaders, light volumes, and probes, to make sure lights faded into the fog.  Other effects can make use of four new built-in uniforms which will provide all the fog information the shader needs:
    uniform bool fogmode; uniform vec2 fogrange; uniform vec4 fogcolor; uniform vec2 fogangle; On the client side, eight new commands have been added to the camera class to control the fog appearance, which are also available in Lua:
    virtual void SetFogColor(const float r, const float g, const float b, const float a); virtual void SetFogAngle(const float start, const float stop); virtual void SetFogRange(const float start, const float stop); virtual void SetFogMode(const bool mode); virtual Vec4 GetFogColor(); virtual Vec2 GetFogAngle(); virtual Vec2 GetFogRange(); virtual bool GetFogMode(); This was the solution I've had in mind for some time, but I haven't had a chance to implement it until now.  It works really well and provides robust fog that looks correct under a wide variety of settings.

  11. Josh
    A new build is up on the beta branch, for Windows and Linux, with Lua and C++ support.  This is a full update.
    Version 4.4 will be announced June 26th, and this will go on the default branch some time next week.
    I was not able to get the new vehicles working in time, and It will have to be added in an update.  Version 4.4 will have vehicles disabled.  If you need this functionality, stick with version 4.3.
    A big thanks for all the people who are helping to test it.
  12. Josh
    An update is up which saves all menu settings into your game's config file.  When your program calls System:SetProperty() the inputted key-value pair is saved in a list of settings.  Your game automatically saves these settings to a file when it closes, located in C:\Users\<USERNAME>\AppData\local\<GAMENAME>\<GAMENAME>.cfg.
    The contents of the config file will look something like this:
    anisotropicfilter=8 antialias=1 lightquality=1 screenheight=720 screenwidth=1280 session_number=2 terrainquality=1 texturedetail=0 trilinearfilter=1 verticalsync=1 waterquality=1 When your game runs again, these settings will be automatically loaded and applied.  You can override config settings with a command line argument.  However, command lines arguments will not be saved in the config file.
    This has been my plan for a long time, and is the reason why your game is not set to use the editor settings.  Setting for running your game in real-time should be separate from editor settings.
  13. Josh
    Leadwerks Game Engine 4.4 has been updated on the beta branch on Steam.
    Networking finished and documented. GUI finished. All new physics features finished. The character controller physics and picking up objects has been improved and made smoother.  There is a problem with the player sliding down slopes, as seen in the FPS Character Controller example map.  I will work this out.
    I also noticed during testing that picking up some objects in the FPS / AI map will freeze the game.  Will check it out.
    I have not actually tested compiling on Linux yet, because my Linux machine is in the office and I am at home right now.  I'm heading in this afternoon, at which point I will complete Linux testing.
    The only other problem is that vehicles are not working yet.  I'm not sure yet how I will proceed with this.
    Updating C++ Projects
    The following changes are needed to update your C++ projects:
    Visual Studio
    Add these include header search directories:
    $(LeadwerksHeaderPath)\Libraries\NewtonDynamics\packages\thirdParty\timeTracker Add these input libraries:
    newton_d.lib;dContainers_d.lib;dCustomJoints_d.lib; (debug) newton.lib;dContainers.lib;dCustomJoints.lib; (release) Code::Blocks
    Add these include header search directories:
    $(LeadwerksPath)/Include/Libraries/NewtonDynamics/packages/thirdParty/timeTracker You also need the dev files for libcurl:
    sudo apt-get install libcurl4-openssl-dev This is pretty much the finished 4.4, so please test it and post any bug reports you have.  Thank you.
  14. Josh
    In Leadwerks 4.3 we integrated GameAnalytics.com into our software, both in the editor and in the engine, as a tool developers can use to track their player statistics.  A number of events were set up in the editor to fire when certain actions were performed, in order to gain better insight into how people were using Leadwerks.  Here are the results.
    The most popular primitives
    Unsurprisingly, boxes are by far the most popular primitive created in Leadwerks Editor.  The community has created more than 20,000 boxes since analytics were enabled.  What blows my mind is that cylinders are actually the second-most commonly used primitive, rather than wedges.  Users created 1753 cylinders but only 1358 wedges in the given time period!  Even more shocking is that spheres are more popular than cones, with 985 spheres created versus just 472 cone primitives.
    This causes me to question my assumptions of how people use Leadwerks, and how items in the interface should be prioritized.

    The Workshop Store is used heavily
    Leadwerks users installed more than 5000 items from the Workshop.  It also looks like people install many items, as sometimes the number of installs exceed the number of times the Workshop interface is opened.

    People buy Workshop items directly through the Steam Client
    Although users are buying Workshop items in high quantities, it appears that their primary route is through the Steam store interface, rather than the built-in Workshop browser.  People browse the items in the Steam client and then use the Workshop browser to install their purchased items.  The numbers for Workshop Store purchases are much higher than the number of people clicking the buy button in the editor.
    If you're interested in selling your 3D models or textures through the Leadwerks Workshop Store, it's easy to get started and you can earn money directly from Steam.  Contact us to learn more or stop by the forum.
  15. Josh
    A new easy-to-use networking system is coming soon to Leadwerks Game Engine.  Built on the Enet library, Leadwerks networking provides a fast and easy way to quickly set up multiplayer games.  Each computer in the game is either a server or a client.  The server hosts the game and clients can join and leave the game at will.  On the other hand, when the server leaves the game, the game is over!

    Creating a Client
    You can soon create a client with one command in Leadwerks:
    client = Client:Create() To connect to a server, you need to know the IP address of that computer:
    client:Connect("63.451.789.3") To get information from the other computer, we simply update the client and retrieve a message:
    local message = client:Update() if message.id == Message.Connected then print("Connected to server") elseif message.id == Message.Disconnected then print("Disconnected from server") elseif message.id == Message.Chat then print("New chat message: "..message.stream:ReadString()); end You can even send messages, consisting of a simple message ID, a string, or a stream.
    client:Send(Message.Chat,"Hello, how are you today?") There are two optional flags you can use to control the way your messages are sent.  If you specify Message.Ordered, your packets will arrive in the order they were sent (they won't necessarily otherwise).  You can use this for updating the position of an object, so that the most recent information is always used.  The Message.Reliable flag should be used for important messages that you don't want to miss.  UDP packets are not guaranteed to ever arrive at their destination, but messages sent with this flag are.  Just don't use it for everything, since it is slower!
    When we're ready to leave the game, we can do that just as easily:
    client:Disconnect() A dedicated server does not have anyone playing the game.  The whole computer is used only for processing physics and sending and receiving information.  You can create a dedicated server, but it's better to let your players host their own games.  That way there's always a game to join, and you don't have to buy an extra computer and keep it running all the time.
    Creating a Server
    Your game should be able to run both as a client and as a server, so any player can host or join a game.  Creating the game server is just as easy.
    local server = Server:Create(port) Once the server is created, you can look up your IP address and ask a friend to join your game.  They would then type the IP address into their game and join.
    The server can send and receive messages, too.  Because the server can be connected to multiple clients, it must specify which client to send the message to.  Fortunately, the Message structure contains the Peer we received a message from.  A peer just means "someone else's computer".  If your computer is the client, the server you connect to is a peer.  If your computer is the server, all the other clients are peers:
    local message = client:Update() if message.id == Message.Connected then player2 = message.peer end You can use the peer object to send a message back to that computer:
    server:Send(peer, Message.Chat, "I am doing just great! Thanks for asking.") If you want to boot a player out of your game, that's easy too:
    server:Disconnect(peer) The broadcast command can be used to send the same message out to all clients:
    server:Broadcast(Message.Chat, "I hope you are all having a great time in my cool chat program!") Public Games
    You can make your game public, allowing anyone else in the world who has the game to play with you.  You specify a name for your game, a description of your server, and call this command to send a message to the Leadwerks server:
    server:Publish("SuperChat","My SuperChat Server of Fun") All client machines, anywhere in the world, can retrieve a list of public games and choose one to join:
    for n=0,client:CountServers("SuperChat")-1 do local remotegame = client:GetServer(n) print(remotegame.address) print(remotegame.description) end This is a lot easier than trying to type in one person's IP address.  For added control, you can even host a games database on your own server, and redirect your game to get information from there.
  16. Josh
    I've taken your suggestions and incorporated the fixes into the new documentation system here:
    https://www.leadwerks.com/learn
     
    All classes and commands are alphabetized, with classes listed first.
     
    All pages in the API Reference should be working now.
     
    If a page does not appear in the old docs, or if a command does not have an example, it will not appear in the new docs, as I am not changing the content right now.
     
    Please let me know if the table of contents of pages have any errors.
  17. Josh
    The upcoming changes to the Steam curation process may mean we experience greatly reduced visibility in the Steam store, and I am preparing for that possibility. The plan for quite a while has been to double down on the ease of use and learning that differentiates Leadwerks from every other game development system on the market.
    To that end, I have begun translating the documentation into a format that is easier to view and search. The welcome page has been done away with and will be replaced by a start page similar to Visual Studio that displays recent news, allows project selection and creation, and encourages the user to become active in the community. By default, the documentation will probably open automatically in a CHM file the first time the program starts.
    We're relying much less on IPB (our community software) nowadays, and most of the website is being pulled out of that system. The gallery page, for example, now runs with no connection to the forum installation. Eventually we will upgrade the community software to the latest major version, and this move will minimize any disruption to the website.
    Meanwhile, the game templates are in the planning stage. The marble platformer template is actually generating a lot of cool ideas and it could be easily repurposed to make an engaging third-person platformer game. I think this will be a fantastic introduction to game development and inspire a lot of creative variations.
    The social media accounts are now adjusted to be more selective with the content they repost.
    The goal is to make new users the best possible experience learning to make games. It's not enough just to have certain features, I want new users to be religiously enthusiastic when they see what they can do with Leadwerks.
  18. Josh
    The forum software has been updated to a major new version.  This completes my effort to give the entire website responsive design, and ensures we continue to receive security updates.  The responsive design part is really for SEO, but it is kind of cool to be able to browse the entire site on your phone without zooming in.
    Documentation has been switched over to the new system here, which is independent from the forum software:
    https://www.leadwerks.com/learn
    The entire site is now using SSL on every page, again for SEO purposes.
    There are a few bits that need improvement, but overall it's a solid upgrade and I think you will find the new site to be very helpful.  I've added a new Q & A forum where you can get technical assistance and rate the answers.  Code formatting has been improved a lot, and the notifications system does a really good job of catching replies to your posts.  (If you're receiving a lot of emails you can disable this in your account settings.)
    Finally, you can now set a cover image to show at the top of your profile, which is pretty cool.
  19. Josh
    I'm asking the community to look over the new documentation system and find any mistakes before I sign off on the project. The tutorials are not completed. The API Reference should be completed, although the main API Reference page is not showing anything right now.
    https://www.leadwerks.com/learn
     
    Here are my notes to the developer who is working on the docs:
     
    Please review the material and make sure everything meets your satisfaction, and leave your comment below if needed. I figure many sets of eyes will be more efficient than just me looking through it. This is your documentation, and I want to make sure it's the best for you. Thanks for your help!
  20. Josh
    The table of contents and index can now be used to navigate to a page:
    https://www.leadwerks.com/learn
     
    Search is also working.
     
    Almost all of the commands are filled in, with examples for Lua and C++, updated to use the standard main() entry point, or in the case of Lua, just a main script with no function.
     
    Function arguments and syntax will also be displayed in C++ or Lua data types. So if you have the docs set to Lua, you will just see "number" instead of "const float blah blah blah..."
     
    I am going to review the material and then I will ask the community to go through it before I give my freelancer the final OK.
     

  21. Josh
    The new docs system is mostly working now:
    https://www.leadwerks.com/learn?page=API-Reference_Object_Entity_SetPosition
     
    Features:
    Treeview list of all tutorials and API classes and functions.
    Alphabetical index (generated automatically from the table of contents).
    Search (with autogenerated search index).
    Switch back and forth between languages, with cookies to remember your preference.
    Entire examples are automatically selected when you click on them.

    Todo:
    Table of contents doesn't yet navigate.
    I would like to pretty-up the URLs using .htaccess rewrite rules.

     
    Documentation is being loaded from XML files. I find this easier than a database, which is quite opaque to me. You can access some of the XML files being used here. MartyJ even made a cool template for the data on his site here.
     
    At this point I think I can outsource the actual content creation to Upwork. This will involve copying and formatting our existing documentation into XML files, as well as updating all the examples to work by just using a main() function instead of the old App() class that we don't really need. I will be posting the job on that site soon.
  22. Josh
    I've been getting good results storing documentation data in XML files. This allows me to make changes to the way documenation is displayed, without updating hundreds or thousands of pages. I can also write documentation without as much HTML markup. For example, bullets can automatically be inserted in line breaks for the syntax info. Javascript-based search engines usually require all page contents to be store in an array, so this gives us an easy way to collect that data.
     
    Since XML is a known standard, this makes it a bit easier to outsource the documentation content.
     
    Here is a sample file I am working with.

    <?xml version="1.0"?>
    <page>
    <title>SetPosition</title>
    <description>Sets the position of an entity in 3-dimensional space, using local or global coordinates.</description>
    <cppsyntax>
    void Entity::SetPosition(const float x, const float y, const float z, const bool global = false)
    void Entity::SetPosition(const Vec3& position, const bool global = false)
    </cppsyntax>
    <luasyntax>
    nil Entity:SetPosition(number x number y, number z, bool global = false)
    nil Entity:SetPosition(Vec3 position, bool global = false)
    </luasyntax>
    <parameters>
    x: X component of the specified position.
    y: Y component of the specified position.
    z: Z component of the specified position.
    position: the position to set.
    global: indicates whether the position should be set in global or local space.
    </parameters>
    <remarks>
    An entity can be positioned in local or global coordinates. Local coordinates are relative to the entity parent's space.
     
    If the entity does not have a parent, local and global coordinates are the same.
     
    <img src='img/552px-Space.png' />
     
    Leadwerks uses a left-handed coordinate system. This means that if you hold your left hand as shown below, your middle finger, index finger, and thumb will point in the directions of the X, Y, and Z axes, respectively.
    <img src='img/image2.jpg' />
    </remarks>
    <luaexample>
    --Create a window
    window = Window:Create()
    context = Context:Create(window)
    world = World:Create()
    local camera = Camera:Create()
    camera:SetRotation(35,0,0)
    camera:Move(0,0,-6)
    local light = DirectionalLight:Create()
    light:SetRotation(35,35,0)
    --Create a model
    model = Model:Box()
    while true do
    if window:Closed() or window:KeyHit(Key.Escape) then return false end
    model:SetPosition(Math:Sin(Time:GetCurrent()/10.0),0,0)
    Time:Update()
    world:Update()
    world:Render()
    context:Sync()
    end
    </luaexample>
    <cppexample>
    #include "Leadwerks.h"
    using namespace Leadwerks;
    int main(int argc, const char *argv[])
    {
    Leadwerks::Window* window = Window::Create();
    Context* context = Context::Create(window);
    World* world = World::Create();
    Camera* camera = Camera::Create();
    camera->SetRotation(35, 0, 0);
    camera->Move(0, 0, -4);
    Light* light = DirectionalLight::Create();
    light->SetRotation(35, 35, 0);
    //Create a model
    Model* model = Model::Box();
    while (true)
    {
    if (window->Closed() || window->KeyDown(Key::Escape)) return false;
    model->SetPosition(Math::Sin(Time::GetCurrent() / 10.0), 0, 0);
    Leadwerks::Time::Update();
    world->Update();
    world->Render();
    context->SetBlendMode(Blend::Alpha);
    context->DrawText(model->GetPosition().ToString(), 2, 2);
    context->Sync();
    }
    return 0;
    }
    </cppexample>
    </page>
     
    Even the table of contents can be store in XML, which provides a listing of all pages and a way to automatically create the search index and a list of all topics:

    <?xml version="1.0"?> <contents>
    <topics>
    <topic>
    <title>Tutorials</title>
    <openstate>true</openstate>
    <topics>
    <topic>
    <title>1. Editor</title>
    <openstate>true</openstate>
    <topics>
    <topic><title>1.1 Editor Interface</title><openstate>true</openstate></topic>
    <topic><title>1.2 Scene Panel</title></topic>
    <topic><title>1.3 Textures</title></topic>
    <topic><title>1.4 Materials</title></topic>
    <topic><title>1.5 Models</title></topic>
    <topic><title>1.6 Terrain</title></topic>
    </topics>
    </topic>
    <topic>
    <title>2. Games</title>
    <topics>
    <topic><title>2.1 Marble Platformer</title></topic>
    </topics>
    </topic>
    <topic>
    <title>3. Lua Programming</title>
    <topics>
    <topic><title>3.1 Introduction to Lua</title></topic>
    </topics>
    </topic>
    <topic>
    <title>4. C++ Programming</title>
    <topics>
    <topic><title>4.1 Introduction to C++</title></topic>
    </topics>
    </topic>
    </topics>
    </topic>
    <topic>
    <title>Script Reference</title>
    <topics>
    <topic><title>AI</title></topic>
    <topic><title>Analytics</title></topic>
    </topics>
    </topic>
    <topic>
    <title>API Reference</title>
    <topics>
    <topic>
    <title>Object</title>
    <openstate>true</openstate>
    <topics>
    <topic><title>Asset</title></topic>
    <topic><title>Analytics</title></topic>
    </topics>
    </topic>
    </topics>
    </topic>
    </topics>
    </contents>
  23. Josh
    I'm not entirely happy with the output of HelpNDoc for a few reasons. It outputs a great variety of formats, but doesn't do a spectacular job of any. I hoped to use CHM for local help but these do not scale well on high-resolution displays, and the technology will not be updated by Microsoft. HelpNDoc's HTML output is okay but not great, so I started experimenting with my own HTML to get exactly the look and functionality I want.
     
    RIght now I have a javascript-based treeview for navigation, breadcrumb links, syntax highlighting for Lua and C++, responsive design, the ability to switch between C++ and Lua documentation on the same page, and the code examples select the entire block when you click on them. There's still more work to do, but so far this is the most promising method that is giving the best results. I am not sure yet if this will be a lot of PHP files or a utility program that spits out a bunch of HTML files.
     

     

     
    I'm going to focus on the format of the data and then I'll post a job on Upwork and find someone else to do the grunt work of filling in all the content.
  24. Josh
    Our website has been updated with a new look and responsive design. Here are a few highlights.
     
    Landing page:

     
    Product pages:

     
    Screenshots used in the site from games will display the title and author when you hover the mouse over them.
     
    Responsive layout scaled for phones:

     
    Clearer writing that says exactly what Leadwerks does and who it is for:

     
    Dark gallery and video pages:

     
    Sleek screenshot pages:

     
    I left the Workshop pages as-is for now. The forum software is going to be updated and a new skin will be designed for the new version of the community software.
  25. Josh
    I have a few preview images to show you my progress with the implementation of my design for the website refresh. Enjoy.
     

     

     

     
    Here's a new version of the view image page. It's pretty bold. The image stretches to its max dimensions, and a dark background is shown beyond that.
     

     
    Working with a dark background for the video and gallery pages, and it feels pretty natural. Information pages with lots of text use the light theme, while image-heavy artsy sections use a dark theme so you can focus on the content.
     

     
    This is close to finished:
     

×
×
  • Create New...