Jump to content

Josh

Staff
  • Posts

    23,504
  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh
    I am going to share my tips on how to be a great programmer, or anything else for that matter.
     
    The first thing you need to do every day is eat properly. I recommend the following:
     
    Breakfast
    2 pieces of toast (whole wheat bread, the heavier the better).
    One banana or orange.
    Finally, eat pure egg whites until you can't eat anything more. They have no cholesterol, no fat, they are pure protein, and they will give you tons of energy.

    Lunch
    Get a deli sandwich. Chicken, turkey, and occasionally roast beef are good. Avoid pastrami or anything with a ton of cheese or grease.

    Dinner
    Salmon fillet. This has the Omega-3 fats you need to be smart.
    Rice for carbs.
    Salad or grilled vegetables.

    Additionally, you should take a multivitamin to make sure you get any nutrients you missed during the day.
     
    The second thing you need to do is go running every day. Two miles is good enough to get a decent workout, but short enough that it can be repeated daily. Start gradually, if needed. It will make a big difference in how you feel every day.
     
    It is perfectly possible to skip these things for a short time, but as soon as you start eating junk food like pizza and soda, you will find you won't have the energy to run. Then you will lose the energy to prepare proper meals, and start falling back on more prepackaged convenience food, which will make you more tired, and you will cycle downwards into lethargy.
     
    Are you depressed or tired? Unless you are following my regiment above, you really have no excuse. Try this for three weeks and then see how you feel. Together, we can make the Leadwerks community healthier and more productive.
  2. Josh
    Compiling Leadwerks on Linux using the Code::Blocks IDE wasn't very hard. I particularly like the way Code::Blocks handles files in projects. Instead of creating your own "fake" file system in the project explorer, you just add the folder or files you want, and they are organized in a hierarchy that matches the file system.
     
    I found the X windowing system to be extremely easy to work with. I've implemented windowed OpenGL contexts on Windows, OSX, iOS, and Android, and Linux was by far the most straightforward and simple.
     
    One problem I ran into was that some Leadwerks classes conflict with Linux classes. Our Windows, Font, and Time classes match other classes in the Linux headers. I got around this by prefixing the classes with the "Leadwerks::" namespace, but some better solution should be found.
     
    Upon my first attempt to run Leadwerks, all I got was a blue screen. I fell back from OpenGL 4 to OpenGL 2 and started playing with the OpenGL matrix commands (a relic of the fixed-function pipeline I never even use anymore). When I called glOrtho() with the context width and height, I produced an OpenGL INVALID_VALUE error. From there it wasn't hard to figure out the cause...
     

    int Window::GetClientWidth) { return 0;/*don't forget to add this later*/ }
     
    Once I implemented the missing function, everything worked fine. So I am pleased to unveil the very first screenshot in the history of the universe of Leadwerks running in Linux using OpenGL 4:
     

    Behold, the green square of triumph!
     
    Though this is a simple example, we have a full 3D engine with deferred lighting and a really unique terrain system, so I don't think it will be long now before we can start showing off some great Linux graphics. Linux has the fastest possible performance for 3D rendering, so it will be interesting to see what we can do with it.
  3. Josh
    I'm happy to say I nailed the design of the Leadwerks 3 workflow overall, but there are a couple of places where there's still some friction. Because the community is going to soon be moving a lot of content into the Steam Workshop, now is a good time to take a look at those issues. It's also smart to address them before the Blender exporter is written.
     
    A new update has been pushed out to the beta branch on Steam that contains a lot of improvements in this area. However, both the map and model file format version have been incremented to support new features. That means your work saved in the beta build can only be opened by the newer builds, until a full update goes out.
     
    FBX Material Generation
    When FBX files are imported, their material files will now be automatically generated, if they aren't already present. A shader will be chosen based on what textures are present in the file, and it will even detect animated models and assign an animation shader when appropriate. This makes it dead simple to get models into Leadwerks.
     
    Collision Shapes in Models
    This came up because I found that 95% of the models I import are just static props that need a simple compound collision shape. First I tried using a convex decomposition algorithm in HACD, the same library Bullet Physics uses. This is a pretty impressive bit of code that takes a polygonal mesh and turns it into an approximate series of convex pieces:

     
    However, trying to generate low-poly physics geometry from a visual mesh had unpredictable results. Trying to generate a convex decomposition from a concave hand-modeled mesh was even worse; you had the tedium of hand-modeling the physics shape, combined with the unpredictability of an automated algorithm.
     
    So I abandoned that approach and decided to make something as simple as possible, that would handle most of the cases the average user encounters. You can now add solid convex pieces in your models, and as long as the name of the limb is "collision" it will get loaded as part of a default physics shape. The existing system of physics shapes remains unchanged, and all your existing objects will still work just as before.

     
    Multi-Animation FBX Files
    Leadwerks will now load multiple animations from FBX files, and will use the name of the animation sequence found in the files. Oh, by the way we now support...
     
    Named Animations
    Animations can now have a name assigned to them, and the animation commands now include an overload to specify a sequence name like "Run", "Walk", "Idle", etc. This makes it easy to mix and match character models because you don't have to worry about which animation index you want to use.
     
    This video shows how these great new features work:


     
    But wait, there's more!
     
    Script-based Post Effects
    We've also got script-based post effects in the editor! This allows a wider range of effects than pure shader effects did. You can modify the script in real-time and see the results immediately, although it is possible to crash the editor if you aren't careful:
     
    A samples bloom script effect is included that is a combination of mine and Shadmar's code. It is interesting that the real-time shader visualization of Leadwerks 3 has allowed us to create a much higher-quality bloom effect than what Leadwerks 2 could do. This is because I could quickly modify code, press F5, and see the results right away instead of having to restart the editor just to change a shader.
     

     
    Workshop files can now be selected in the file open dialog, so I expect to see lots of great new effects come from the community soon, especially from Shadmar and Klepto2.
     
    Batch Material Generation
    You just imported a folder full of textures and you don't want to create materials one-by-one. What do you do? The new Tools > Batch Generate Materials menu is the right solution for you! This feature will auto-generate materials for the entire current directory shown in the asset browser. And if you want some quick normal maps, just select the Tools > Batch Generate Normal Maps menu item. This will allow you to quickly import tons of textures and publish them to the Leadwerks Workshop in no time at all.
  4. Josh
    I think of the Leadwerks community as a big loosely affiliated collaborative game studio. I see people working together to add value in different ways. Some are dedicated to a single project, and others bounce around and participate in different projects. I like that everyone can just find what they like doing best and provide value in that way.
     
    Voluntary collaboration, without being too restrictive, allows us to achieve economies of scale, while diversifying risk. Basically that means it's more cost-effective to batch some things together, like promotion and content production. At the same time, we remain innovative and keep taking risks with new ideas.
     
    We're going to build Leadwerks into a series of educational materials and services that is capable of taking any person of reasonable intelligence and commitment from total noob to professional game developer. I want to have a well-defined series of lessons that once completed, tell you everything you need to know to program games, design artwork, or manage a team of developers working on a title. Leadwerks can help with promotion and distribution. You'll see a lot more of this idea with the Leadwerks Workshop on Steam.
     
    The steps below plot the progress a Leadwerker goes through as they gain xp:
     
    Education
    Tutorials and documentation

    Collaboration
    Communication tools on Leadwerks.com
    Steam Workshop

    Promotion
    Games page
    Leadwerks feeds and social media accounts

    Distribution
    TBA

     
    I think more can be done in each of the areas above as we continue to grow. Just some thoughts this evening.
  5. Josh
    One of the coolest things I'm seeing in Leadwerks 3 is a lot of minigames being made. These are finished products, without super high expectations, but they are complete, don't take long to make, and some are quite fun. Seeing these gave me an idea for a feature that's been in development in the background for some time, and when you see what it does, it will explain some of the design decisions I've made over the last few months.
     
    The next build on the beta branch on Steam will support Lua sandboxing. Lua games can be sandboxed by passing "-sandbox" in the command line. This prevents the application from accessing file write and OS commands, and prevents Lua from loading external libraries. When games are launched from the editor, this is turned on by default:

     
    Why is this cool? Sandboxing makes Lua code safe to run on any computer. Along with assets, games can now be published in the Steam Workshop beta. The editor has a new game player that lets you select a game you are subscribed to and play it, like the ones below from YouGroove and Rick:

     
    This minimizes the editor window and launches the Lua executable found in the "Templates\Lua" folder. The selected game is passed to the executable in the command line, and the executable loads all game assets, scripts, and maps from an encrypted zip file containing the game contents. Because our own executable is being run with only sandboxed Lua code, it's safe to run any game in the Workshop. And most of the Lua games found in the downloads section of this site work with no changes, due to the stability and backwards compatibility of the Leadwerks 3 API.

     
    We're filling the Workshop with content right now, including assets from "The Zone" project from Leadwerks 2, as well as third party content from popular artists. When the Workshop launches next month, you will also be able to publish your Lua games straight to Steam, with no approval process and no waiting period. You can update your game at any time to keep all your fans up to date, and you can safely play other people's games while always running the latest build of the Leadwerks executable.
  6. Josh
    Here's an example of something called "derivative works" which is something the Leadwerks Workshop on Steam allows us to do.
     
    Shadmar uploaded a model from Leadwerks 2 and added some special effects to it in the Workshop here:
    http://steamcommunity.com/sharedfiles/filedetails/?id=312811332

     
    I subscribed to the item, and used the model and sound to make my own Workshop item here:
    http://steamcommunity.com/sharedfiles/filedetails/?id=312846032

     
    When you load my item up in the editor, it will automatically subscribe to and pull the needed contents from Shadmar's original item. If he updates his item, the changes will automatically show up in mine.
  7. Josh
    The implementation of the Leadwerks Workshop has always been planned in two steps:
    Initial implementation using the Steam interface.
    Implementation of built-in interface in editor.

     
    I've been working on the second part for a couple days, and the results are pretty awesome so far. A built-in browser lets you view all items in the Workshop, search, and sort by rating or date uploaded.
     

     
    When you click on an item, you can view its detail view. Pressing the "Install" button will install the item into your current project. (If you aren't already subscribed to the item, you will be automatically.)
     

     
    This simplifies the process of acquiring items from the Workshop, since you no longer have to switch between Leadwerks and Steam, and you don't have to restart Leadwerks to get your item. I have always found the "subscribe" functionality a little confusing, and in the future I hope to make that part of it completely invisible to the end user.
     
    The fact that people are still using the old Downloads area sometimes tells me that the Workshop is not as easy as it could be. Basically, it's competing with zip file downloads. It needs to be easier than downloading and unzipping a zip file. I think we're getting there, because the new browser takes fewer steps.
     

  8. Josh
    As discussed before, sales in the Workshop Store have been measly compared to what the DLCs regularly do. People just don't want to use it. Without revenue coming in, I can't convince third parties that it is a good idea to sell through this system (it isn't), and no new products will get added. We need a large library of content to be available for use in Leadwerks, but this isn't working. So I am moving on to another approach.
     
    The "Workshop" menu item in the website header has been placed under the "Community" menu where it is less prominent. Any products you purchased through the Workshop Store will remain available, forever. The Workshop will continue to be a place for the community to share files.
     
    I considered associating DLC purchases with Workshop items so that the two systems were merged, at least from the user's perspective, but decided against it, for now. Discovery takes place in the Steam Store, not the Workshop interface, and there are more important issues to tackle first. I may tie these together in the future so that installing a DLC goes through the Workshop interface. What we need right now is to have lots of content available, and to make sure that content is selling so that more will be added. If that isn't happening, nothing else matters.
     
    A new update adds a tab in the Workshop window titled "DLC". This is where all your purchased DLCs will be listed, and this is how they can be installed to your project.
     

     
    Okay, so monetizing the Workshop failed, and I am calling it now. Now what?
     
    At the same time the Workshop was struggling, our DLC sales of model packs have been very, very good. In fact, the DLCs will regularly sell more in a single day than the Workshop Store has done during its entire three month existence. It's clear that one approach works and the other does not.
     
    What I don't like about DLCs is they require more oversight by me, they can really only be sold in packs, and not individual models, the quality control and standards have to be higher and thus more limiting, and there is a limited amount of store space available to show them. Any revenue splits I do have to be calculated manually, and me doing that myself each month just isn't an option. On Monday I am meeting with an accounting firm so that they can handle this task for me each month. I plan to start releasing third-party model packs as DLCs, where they will actually get sales, and I will outsource the handling of royalty payments so I don't have to deal with it.
     
    This didn't work out the way I planned, but the fact remains we have a method of selling content that works and provides sales for third parties comparable to the other model stores they sell content through. We just need to recognize our own unique strengths and play to them instead of fighting what the user wants.
  9. Josh
    The combobox and listbox widget scripts are now updated. The combobox is presently using a more game-like style that jus shows the currently selected item. You can change the selection by clicking on the widget with the mouse, pressing keyboard keys, or scrolling the mouse wheel.
     
    The listbox includes a slider that automatically appears when needed. In order to make rendering faster, the draw function calculates the first and last visible items, and only iterates through those items while drawing. That means that whether the list contains ten items or 1000, it will render at the exact same speed.
     
    I have not yet figured out how to handle holding the mouse to continue scrolling the slider, since there is no MouseRepeat event in Windows (and probably not in Linux either).
     


     
    You can get this update now on the beta branch on Steam.
  10. Josh
    A new build is available on the beta branch on Steam. This updates everything, C++ and Linux included.
     
    A bug where the editor skipped navmesh calculation on brushes is fixed.
     
    We're updated to the latest version of the Steamworks SDK, which may fix some Workshop upload and download issues.
     
    Animation now calculates more than twice as fast as previously. This was achieved by optimizing the animation code, implementing some inline functions, and by changing the way the matrix updating works. The UnlockMatrix() command will now accept a single integer parameter for the update type. Zero will perform no updating. This should only be used if the entity's 4x4 matrix has been manually set. (So really, it should never be used by you.) 1 is the default setting which updates the entity's bounding boxes, octree node, etc. 2 is used to only update the entity's 4x4 matrix, which is much faster.
     
    The animation manager script is modified to use the fastest method on bones:

    --Unlock entity matrix if any animations were applied if doanimation==true then self.entity:UnlockMatrix(2) end
     
    If you are using large numbers of animated characters, another easy way to make things even faster is to only animate them every Nth frame. If you have a large number of characters the lack of continuous motion each frame won't really be noticeable.
     
    Update your project to get the latest Steamworks DLL.
  11. 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.
  12. 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.
  13. Josh
    Fall is in the air.  The leaves are changing colors, people are bundling up, and game developers are itching to try their hand at another community game tournament.  How does it work?  For 30 days, the Leadwerks community builds small playable games.  Some people work alone and some team up with others.  At the end of the month, on Halloween day, we release our projects to the public and play each other's games.  The point is to release something short and sweet with a constrained timeline, which has resulted in many odd and wonderful mini games for the community to play.
    WHEN: The tournament begins Sunday, October 1, and ends Tuesday, October 31st at the stroke of midnight.
    HOW TO PARTICIPATE: Publish your Halloween-or-other-themed game to Steam Workshop or upload it to itch.io before the deadline. You can work as a team or individually. Use blogs to share your work and get feedback as you build your game. If you need models for your game, we've got a Halloween Model Pack for you to use for free from Leadwerks Workshop.
    Games must have a preview image, title, and contain some minimal amount of gameplay (there has to be some way to win the game) to be considered entries. It is expected that most entries will be simple, given the time constraints.
    On November 1st we will post a roundup blog featuring your entries.
  14. Josh
    Leadwerks Game Engine 4.6 will feature a new peer-to-peer networking system that solves the NAT punch-through problem and provides an easy way to create a public list of game servers. Together with this and Leadwerks GUI which was released last year, we will soon have the tools to make a great deal of new types of games. Previously we have been focused on single-player games but the ability to create multiplayer games with fast reliable networking opens up a lot of new and fun possibilities.
    In many ways, multiplayer games are actually easier to make than single-player games. Most of your gameplay tends to arise through the interaction of the players, so making things fun centers around creating environmental challenges. Coding-wise, multiplayer games tend to use a small core program and don't need a lot of extensive per-object scripting.
    Since we have the tools to do it, there are two community projects I want to carry out this year. By "community project" I mean I am going to write the code, put it out there, and anyone who wants to join in is welcome.
    Gears Arena
    This is simply going to be a modern version of Quake 3 Arena, with the option to play in VR. It can serve as a basis for many different types of games, and a lot of fun can be had with level design alone. New game modes like CTF can be added, or new weapons. I fully intend to make a gun that shoots chickens that then attack your enemies, a well as a grenade launcher that shoots grenades that split into more grenades. It will be all the fun of modding without having to rip out a lot of pre-existing code from the base game.

    Gears Rally
    The code from the previous project can be easily reconfigured into a multiplayer racing game. Maybe we can even add weapons.

    Now that the base systems of networking and the GUI are nearly in place, both these projects will be achievable in a very small amount of code, and can serve as a basis of derivative games.
  15. Josh
    A big update for the beta of the upcoming Turbo Game Engine is now available, adding support for VR and Lua script!
    VR Rendering
    Turbo Game Engine now supports VR rendering, with support for true single-pass stereoscopic rendering on Nvidia GPUs. Other hardware will use a double-rendering path that is still faster than Leadwerks. To turn VR on simply call EnableVR(). Controllers are not yet supported, just rendering.
    Lua Scripting
    Lua script is now supported in Turbo! The amount of classes and functions is limited, but the foundation for full Lua support is in. Here are some of the great features in the new system.
    No Script Object
    All scripts operate on the entity itself. Instead of this:
    function Script:Update() self.entity:Turn(1,0,0) end You type this:
    function Object:Update() self:Turn(1,0,0) end This is especially nice when it comes to functions that retrieve another entity since you don't have to type "entity.script" to get Lua values and functions:
    function Object:Collision(entity,position,normal,speed) entity:Kill() end Smart Pointers
    Lua garbage collection now works together with C++11 smart pointers. You will never have to deal with invalid pointers or object deletion again. There is no Release() anymore. Just set your variable to nil to delete an object:
    local box = CreateBox(world) box = nil If you want the object to be collected immediately, you can force a GC step like this:
    local box = CreateBox(world) box = nil collectgarbage() Best of all, because Lua runs on the game logic thread separate from the rendering thread, it's perfectly fine to use Lua high-performance applications, even in VR. A pause in the game execution for Lua garbage collection will not pause the rendering thread.
    Multiple Scripts
    You can add any number of scripts to an object with the AddScript command:
    entity->AddScript("Scripts/Object/test.lua") Scripts on Any Object (Experimental)
    Now all object types can have scripts, not just entities:
    material->AddScript("Scripts/Object/Appearance/Pulse.lua") Set and Get Script Values in C++
    You can easily set and get values on any object, whether or not it has had a script added to it:
    entity->SetValue("health",100); Print(entity->GetNumberValue("health")); Vector Swizzle
    Using getters and setters I was able to implement vector swizzles. If you write shaders with GLSL you will be familiar with this convenient  feature:
    local a = Vec3(1,2,3) local b = a.xz --equivalent to Vec2(a.x,a.z) In Lua you can now return any combination of vector elements, using the XYZW or RGBA names. The code below will swap the red and blue elements of a color:
    local color = Vec4(1,0,0.5,1) local color = color.bgra Not only can you retrieve a value, but you can assign values using the swizzle:
    local v = Vec3(1,2,3) v.zy = Vec2(1,2) Print(v) --prints 1,2,1 Note there are presently only two engine script hooks that get called, Start() and Update().
    Simpler Uber Shaders
    I decided to do away with the complicated #ifdef macros in the shaders and use if statements within a single shader:
    //Diffuse Texture if (texturebound[0]) { color *= texture(texture0,texcoords0); } This makes internal shader management MUCH simpler because I don't have to load 64 variations of each shader. I am not sure yet, but I suspect modern GPUs will handle the branching logic with no performance penalty. It also means you can do things like have a material with just a color and normal map, with no need for a diffuse map. The shader will just adjust to whatever textures are present.
    A C++ Turbo program now looks like this:
    #include "Turbo.h" using namespace Turbo; int main(int argc, const char *argv[]) { //Create a window auto window = CreateWindow("MyGame", 0, 0, 1280, 720); //Create a rendering context auto context = CreateContext(window); //Create the world auto world = CreateWorld(); //This only affects reflections at this time world->SetSkybox("Models/Damaged Helmet/papermill.tex"); shared_ptr<Camera> camera; auto scene = LoadScene(world, "Maps/start.map"); //Create a camera if one was not found if (camera == nullptr) { camera = CreateCamera(world); camera->Move(0, 1, -2); } //Set background color camera->SetClearColor(0.15); //Enable camera free look and hide mouse camera->SetFreeLookMode(true); window->HideMouse(); while (window->KeyHit(KEY_ESCAPE) == false and window->Closed() == false) { //Camera movement if (window->KeyDown(KEY_A)) camera->Move(-0.1, 0, 0); if (window->KeyDown(KEY_D)) camera->Move(0.1, 0, 0); if (window->KeyDown(KEY_W)) camera->Move(0, 0, 0.1); if (window->KeyDown(KEY_S)) camera->Move(0, 0, -0.1); //Update the world world->Update(); //Render the world world->Render(context); } return 0; } If you would like to try out the new engine and give feedback during development, you can get access now for just $5 a month.
    I am now going to turn my attention to Leadwerks 4.6 and getting that ready for the Christmas season.
    The next step in Turbo development will probably be physics, because once that is working we will have a usable game engine.
  16. Josh
    Previously I described how multiple cameras can be combined in the new renderer to create an unlimited depth buffer. That discussion lead into multi-world rendering and 2D drawing. Surprisingly, there is a lot of overlap in these features, and it makes sense to solve all of it at one time.
    Old 2D rendering systems are designed around the idea of storing a hierarchy of state changes. The renderer would crawl through the hierarchy and perform commands as it went along, rendering all 2D elements in the order they should appear. It made sense for the design of the first graphics cards, but this style of rendering is really inefficient on modern graphics hardware. Today's hardware works best with batches of objects, using the depth buffer to handle which object appears on top. We don't sort 3D objects back-to-front because it would be monstrously inefficient, so why should 2D graphics be any different?
    We can get much better results if we use the same fast rendering techniques we use for 3D graphics and apply it to 2D shapes. After all, the only difference between 3D and 2D rendering is the shape of the camera projection matrix. For this reason, Turbo Engine will use 2D-in-3D rendering for all 2D drawing. You can render a pure 2D scene by setting the camera projection mode to orthographic, or you can create a second orthographic camera and render it on top of your 3D scene. This has two big implications:
    Performance will be incredibly fast. I predict 100,000 uniquely textured sprites will render pretty much instantaneously. In fact anyone making a 2D PC game who is having trouble with performance will be interested in using Turbo Engine. Advanced 3D effects will be possible that we aren't used to seeing in 2D. For example, lighting works with 2D rendering with no problems, as you can see below. Mixing of 3D and 2D elements will be possible to make inventory systems and other UI items. Particles and other objects can be incorporated into the 2D display.
    The big difference you will need to adjust to is there are no 2D drawing commands. Instead you have persistent objects that use the same system as the 3D rendering.
    Sprites
    The primary 2D element you will work with is the Sprite entity, which works the same as the 3D sprites in Leadwerks 4. Instead of drawing rectangles in the order you want them to appear, you will use the Z position of each entity and let the depth buffer take care of the rest, just like we do with 3D rendering. I also am adding support for animation frames and other features, and these can be used with 2D or 3D rendering.

    Rotation and scaling of sprites is of course trivial. You could even use effects like distance fog! Add a vector joint to each entity to lock the Z axis in the same direction and Newton will transform into a nice 2D physics system.
    Camera Setup
    By default, with a zoom value of 1.0 an orthographic camera maps so that one meter in the world equals one screen pixel. We can position the camera so that world coordinates match screen coordinates, as shown in the image below.
    auto camera = CreateCamera(world); camera->SetProjectionMode(PROJECTION_ORTHOGRAPHIC); camera->SetRange(-1,1); iVec2 screensize = framebuffer->GetSize(); camera->SetPosition(screensize.x * 0.5, -screensize.y * 0.5); Note that unlike screen coordinates in Leadwerks 4, world coordinates point up in the positive direction.

    We can create a sprite and reset its center point to the upper left hand corner of the square like so:
    auto sprite = CreateSprite(world); sprite->mesh->Translate(0.5,-0.5,0); sprite->mesh->Finalize(); sprite->UpdateBounds(); And then we can position the sprite in the upper left-hand corner of the screen and scale it:
    sprite->SetColor(1,0,0); sprite->SetScale(200,50); sprite->SetPosition(10,-10,0);
    This would result in an image something like this, with precise alignment of screen pixels:

    Here's an idea: Remember the opening sequence in Super Metroid on SNES, when the entire world starts tilting back and forth? You could easily do that just by rotating the camera a bit.
    Displaying Text
    Instead of drawing text with a command, you will create a text model. This is a series of rectangles of the correct size with their texture coordinates set to display a letter for each rectangle. You can include a line return character in the text, and it will create a block of multiple lines of text in one object. (I may add support for text made out of polygons at a later time, but it's not a priority right now.)
    shared_ptr<Model> CreateText(shared_ptr<World> world, shared_ptr<Font> font, const std::wstring& text, const int size) The resulting model will have a material with the rasterized text applied to it, shown below with alpha blending disabled so you can see the mesh background. Texture coordinates are used to select each letter, so the font only has to be rasterized once for each size it is used at:

    Every piece of text you display needs to have a model created for it. If you are displaying the framerate or something else that changes frequently, then it makes sense to create a cache of models you use so your game isn't constantly creating new objects. If you wanted, you could modify the vertex colors of a text model to highlight a single word.

    And of course all kinds of spatial transformations are easily achieved.

    Because the text is just a single textured mesh, it will render very fast. This is a big improvement over the DrawText() command in Leadwerks 4, which performs one draw call for each character.
    The font loading command no longer accepts a size. You load the font once and a new image will be rasterized for each text size the engine requests internally:
    auto font = LoadFont("arial.ttf"); auto text = CreateText(foreground, font, "Hello, how are you today?", 18); Combining 2D and 3D
    By using two separate worlds we can control which items the 3D camera draws and which item 2D camera draws: (The foreground camera will be rendered on top of the perspective camera, since it is created after it.) We need to use a second camera so that 2D elements are rendered in a second pass with a fresh new depth buffer.
    //Create main world and camera auto world = CreateWorld(); auto camera = CreateCamera(world); auto scene = LoadScene(world,"start.map"); //Create world for 2D rendering auto foreground = CreateWorld() auto fgcam = CreateCamera(foreground); fgcam->SetProjection(PROJECTION_ORTHOGRAPHIC); fgcam->SetClearMode(CLEAR_DEPTH); fgcam->SetRange(-1,1); auto UI = LoadScene(foreground,"UI.map"); //Combine rendering world->Combine(foreground); while (true) { world->Update(); world->Render(framebuffer); } Overall, this will take more work to set up and get started with than the simple 2D drawing in Leadwerks 4, but the performance and additional control you get are well worth it. This whole approach makes so much sense to me, and I think it will lead to some really cool possibilities.
    As I have explained elsewhere, performance has replaced ease of use as my primary design goal. I like the results I get with this approach because I feel the design decisions are less subjective.
  17. Josh
    Leadwerks 3 / 4 was aimed at beginners who were completely new to game development. Since we were the first game engine on Steam, this made a lot of sense at the time, and the decision resulted in a successful outcome. However, in the next engine we are taking a different approach. (This is a direct result of Steam Direct.)
    Enterprise is a new market I am pursuing, and we have a lot of interest from aerospace and defense VR developers. The fact that I am American also helps here. There are special features these customers need that aren't necessarily needed by game developers, but I think you will like some of the possibilities this unlocks.
    For game developers, I have been moving back to an approach more like Leadwerks 2 where we focus on extreme high-end PC game technology, so that comes down to graphical quality and performance. I think most people here will be pretty happy with that direction. We're going to sell on Steam, Humble Store, Amazon, Microsoft store, Mac App Store, and direct from our website. Less importance will be attached to Steam, as they are just one more storefront we sell through. We're not going to use Steam Workshop.
    For pricing of the non-enterprise version, I am thinking $59.99 / $99.99 standard / pro with a monthly subscription option at $4.99 / $9.99. This is actually cheaper than the pricing of Leadwerks, but I think keeping things under $100 is better for the consumer market.
    The paid beta subscription is going to end before the end of the year, and it will be replaced with an open beta. The reason I did this was because I wanted a very small group of people testing the early betas (really more of an alpha), and I wanted to test out our subscriptions system. During that time we found and fixed a couple of small issues, so this was a good idea. A big thanks to all who participated and bought me many espressos.  ☕
    Finally, we are not going to call Leadwerks 5 "Turbo Game Engine". The name just isn't sticking for me, and I don't think we can get rid of the :"retro" connotations it has. My new technology has developed quite a lot since then, and speed is not the only advantage it brings to the table. I do have a new name in mind, but I am not ready to announce it yet. Until then, I will refer to the new engine as "Leadwerks 5 beta".
  18. Josh
    Analytics is a feature I have long thought was a good candidate to be moved into a plugin.
    It is an optional features that only some users care about. It imports some pretty big third-party libraries into the engine. It requires an extra DLL be distributed with your game. It's a totally self-contained features that is easy to separate out from the rest of the engine. I have uploaded my code for Analytics in Leadwerks here as a new empty Visual Studio project:
    https://github.com/Leadwerks/PluginSDK/tree/master/Game Analytics
    This will be a good test case to see how we can implement API plugins. An API plugin is an optional module that adds new commands to the engine. It should work with C++, Lua, and C#.
    How do we do this? I'm not sure what the best way is. Do we want to try to make an object-oriented API like the rest of the engine, or should we just stick to a simpler procedural API? I putting this out now so we can discuss and determine the best way to handle this.
    Here is some info on using sol to expose an API from a DLL:
    https://github.com/ThePhD/sol2/tree/develop/examples/require_dll_example
    If we can make this plugin work then it will serve as an example for how all API plugins should be integrated. Please take a look at tell me how you would like this to work.
  19. Josh

    Articles
    There are three new features in the upcoming Ultra Engine (Leadwerks 5) that will make game input better than ever before.
    High Precision Mouse Input
    Raw mouse input measures the actual movement of the mouse device, and has nothing to do with a cursor on the screen. Windows provides an interface to capture the raw mouse input so your game can use mouse movement with greater precision than the screen pixels. The code to implement this is rather complicated, but in the end it just boils down to one simple command expose to the end user:
    Vec2 Window::MouseMovement(const int dpi = 1000); What's really hilarious about this feature is it actually makes mouse control code a lot simpler. For first-person controls, you just take the mouse movement value, and that is your camera rotation value. There's no need to calculate the center of the window or move the mouse pointer back to the middle. (You can if you want to, but it has no effect on the raw mouse input this command measures.)
    The MousePosition() command is still available, but will return the integer coordinates the Windows mouse cursor system uses.
    High Frequency Mouse Look
    To enable ultra high-precision mouse look controls, I have added a new command:
    void Camera::SetFreeLookMode(const bool mode, const float speed = 0.1f, const int smoothing = 0) When this setting is enabled, the mouse movement will be queried in the rendering thread and applied to the camera rotation. That means real-time mouse looking at 1000 FPS is supported, even as the game thread is running at a slower frequency of 60 Hz. This was not possible in Leadwerks 4, as mouse looking would become erratic if it wasn't measured over a slower interval due to the limited precision of integer coordinates.
    XBox Controller Input
    I'm happy to say we will also have native built-in support for XBox controllers (both 360 and One versions). Here are the commands:
    bool GamePadConnected(const int controller = 0) bool GamePadButtonDown(const GamePadButton button, const int controller = 0) bool GamePadButtonHit(const GamePadButton button, const int controller = 0) Vec2 GamePadAxisPosition(const GamePadAxis axis, const int controller = 0) void GamePadRumble(const float left, const float right, const int controller = 0) To specify a button you use a button code:
    GAMEPADBUTTON_DPADUP GAMEPADBUTTON_DPADDOWN GAMEPADBUTTON_DPADLEFT GAMEPADBUTTON_DPADRIGHT GAMEPADBUTTON_START GAMEPADBUTTON_BACK GAMEPADBUTTON_LTHUMB GAMEPADBUTTON_RTHUMB GAMEPADBUTTON_LSHOULDER GAMEPADBUTTON_RSHOULDER GAMEPADBUTTON_A GAMEPADBUTTON_B GAMEPADBUTTON_X GAMEPADBUTTON_Y GAMEPADBUTTON_RTRIGGER GAMEPADBUTTON_LTRIGGER And axes are specified with these codes:
    GAMEPADAXIS_RTRIGGER GAMEPADAXIS_LTRIGGER GAMEPADAXIS_RSTICK GAMEPADAXIS_LSTICK These features will give your games new player options and a refined sense of movement and control.
  20. Josh

    Articles
    I recently fired up an install of Ubuntu 20.04 to see what the state of development on Linux is now, and it looks like things have improved dramatically since I first started working with Linux in 2013. Visual Studio Code looks and works great on Linux, and I was able to load the Ultra Engine source code and start compiling, although there is still much work to do. The debugger is fantastic, especially after using the Code::Blocks debugger on Linux, which was absolutely sadistic. (They're both using GDB under the hood but the interface on Code::Blocks was basically unusable.) Regardless of whatever their motivation was, Microsoft (or rather, the developers of the Atom editor VS Code was based on) has made a huge contribution to usability on the Linux desktop. 

    Settings up C++ compilation in Visual Studio Code is rather painful. You have to edit a lot of configuration files by hand to specify the exact command line GCC should use, but it is possible:
    { "tasks": [ { "type": "cppbuild", "label": "C/C++: g++ build active file", "command": "/usr/bin/g++", "args": [ "-g", "-D_ULTRA_APPKIT", "./Libraries/Plugin SDK/GMFSDK.cpp", "./Libraries/Plugin SDK/MemReader.cpp", "./Libraries/Plugin SDK/MemWriter.cpp", "./Libraries/Plugin SDK/TextureInfo.cpp", "./Libraries/Plugin SDK/Utilities.cpp", "./Libraries/Plugin SDK/half/half.cpp", "./Libraries/freeprocess/freeprocess.c", "./Libraries/s3tc-dxt-decompressionr/s3tc.cpp", "./Libraries/stb_dxt/stb_dxt.cpp", "./Classes/Object.cpp", "./Classes/Math/Math_.cpp", "./Classes/Math/Vec2.cpp", "./Classes/Math/Vec3.cpp", "./Classes/Math/Vec4.cpp", "./Classes/Math/iVec2.cpp", "./Classes/Math/iVec3.cpp", "./Classes/Math/iVec4.cpp", "./Classes/String.cpp", "./Classes/WString.cpp", "./Classes/Display.cpp", "./Classes/IDSystem.cpp", "./Classes/JSON.cpp", "./Functions.cpp", "./Classes/GUI/Event.cpp", "./Classes/GUI/EventQueue.cpp", "./Classes/Language.cpp", "./Classes/FileSystem/Stream.cpp", "./Classes/FileSystem/BufferStream.cpp", "./Classes/FileSystem/FileSystemWatcher.cpp", "./Classes/GameEngine.cpp", "./Classes/Clock.cpp", "./Classes/Buffer.cpp", "./Classes/BufferPool.cpp", "./Classes/GUI/Interface.cpp", "./Classes/GUI/Widget.cpp", "./Classes/GUI/Panel.cpp", "./Classes/GUI/Slider.cpp", "./Classes/GUI/Label.cpp", "./Classes/GUI/Button.cpp", "./Classes/GUI/TextField.cpp", "./Classes/GUI/TreeView.cpp", "./Classes/GUI/TextArea.cpp", "./Classes/GUI/Tabber.cpp", "./Classes/GUI/ListBox.cpp", "./Classes/GUI/ProgressBar.cpp", "./Classes/GUI/ComboBox.cpp", "./Classes/GUI/Menu.cpp", "./Classes/Window/LinuxWindow.cpp", "./Classes/Timer.cpp", "./Classes/Process.cpp", "./Classes/FileSystem/StreamBuffer.cpp", "./Classes/Multithreading/Thread.cpp", "./Classes/Multithreading/Mutex.cpp", "./Classes/Multithreading/ThreadManager.cpp", "./Classes/Loaders/Loader.cpp", "./Classes/Loaders/DDSTextureLoader.cpp", "./Classes/Assets/Asset.cpp", "./Classes/Plugin.cpp", "./Classes/Assets/Font.cpp", "./Classes/FileSystem/Package.cpp", "./Classes/Graphics/Pixmap.cpp", "./Classes/Graphics/Icon.cpp", "./AppKit.cpp" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": [ "$gcc" ], "group": { "kind": "build", "isDefault": true }, "detail": "Task generated by Debugger." } ], "version": "2.0.0" } Our old friend Steam of course looks great on Linux and runs perfectly.

    But the most amazing thing about my install of Ubuntu 20.04 is that it's running on the Windows 10 desktop:

    Hyper-V makes it easy to create a virtual machine and run Linux and Windows at the same time. Here are some tips I have found while working with it:
    First, make sure you are allocating enough hard drive space and memory, because you are unlikely to be able to change these later on. I tried expanding the virtual hard drive, and after that Ubuntu would not load in the virtual machine, so just consider these settings to be locked. I gave my new VM 256 GB hard drive space and used the dynamic memory setting to allocate memory as-needed.
    Second, by default Ubuntu is only going to give you a 1024x768 window. You can change this but it requires a little work. Open the terminal in Ubuntu and type this command:
    sudo nautilus /etc/default/grub This will open the file browser and select the GRUB file, which stores system settings. Open the file with a text editor. Since the file was opened from a nautilus window with super-user permissions, the text editor will also be launched with permissions.
    Find this line of text:
    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash" Change it to this. You can set the screen resolution to whatever value you prefer:
    GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=hyperv_fb:1920x1080" Close the text editor and the nautilus window. In the terminal, run this command:
    sudo update-grub Restart the virtual machine and your new screen resolution will work. However, the DPI scaling in Ubuntu seems to be not very reliable. My laptop uses a 1920x1080 screen, and at 17" this really needs to be scaled up to 125%. I could not get "fractional scaling" to work. The solution is to set your Windows desktop resolution to whatever value feels comfortable at 100% scaling. In my case, this was 1600x900. Then set Ubuntu's screen resolution to the same. When the window is maximized you will have a screen that is scaled the way you want.

    Checkpoints are a wonderful feature that allow you to easily walk back any changes you make to the virtual machine. You can see the steps I went through on to install all the software I wanted to work on Linux:

    If I ever mess anything up, I can easily revert back to whatever point I want. You can probably transfer the virtual machine between computers very easily, too, although I have not tried this yet. Changing the VM disk size, however, requires that you delete all checkpoints. So like I said, don't do that. My disk size is set to 256 GB, but in reality the whole VM is only taking up about 25 GB on my real hard drive.
    The "type clipboard text" feature in Hyper-V does not work at all with Linux, so don't even try.
    Pro tip: If you ever get stuck in the virtual machine, you can press Ctrl + Alt + Left arrow to bring the focus back to the Windows desktop.
    Hyper-V isn't going to allow you to play games with Vulkan or OpenGL, but for other software development it seems to work very well. It's a bit slow, but the convenience of running Linux on the Windows desktop and being able to switch back and forth instantly more than makes up for it.
    Super Bonus Tip
    I found that regardless of how much disk space you allocate for the virtual machine, the Windows default install of Ubuntu 20.04 will still only use 12 GB on the main drive partition. If you try to resize this in the built-in tools, an error will occur, something like "Failed to set partition size on device. Unable to satisfy all constraints on the partition."
    To resize the partition you must install gparted:
    sudo apt install gparted When you run the application it will look like this. Right-click on the ext4 partition and select the Resize/Move menu item:

    Set the new partition size and press the Resize button. 
    Extreme Double Bonus Tip
    If at any time you are running gparted you see an error like "Not all of the space available to /dev/sdb appears to be used, you can fix the GPT to use all of the space" you should press the Fix button. If you don't do this, the partition resize won't work and will display some errors.
    After the partition resize, the VM files on Windows are still only using about 21 GB, so it looks like Hyper-V doesn't allocate more disk space than it has to.
    Crazy Extra Tip
    Both VS Code and Brave browser (and probably Chrome) have an option to use a much better looking "custom titlebar" instead of the ugly GTK+ titlebar buttons.
  21. Josh
    Until now, all my experiments with voxel cone step tracing placed the center of the GI data at the world origin (0,0,0). In reality, we want the GI volume to follow the camera around so we can see the effect everywhere, with more detail up close. I feel my productivity has not been very good lately, but I am not being too hard on myself because this is very difficult stuff. The double-blind nature of it (rendering the voxel data and then using that data to render an effect) makes development very difficult. The intermediate voxel data consists of several LODs and is difficult to visualize. My work schedule lately has been to do nothing for several days, or just putter around with some peripheral tasks, and then suddenly solve major problems in a short two-hour work session.
    Here you can see a single GI stage following the camera around properly. More will be added to increase the area the effect covers, and the edges of the final stage will fade out the effect for a smooth transition:

    My new video project1.mp4 This all makes me wonder what "work" is when you are handling extremely difficult technical problems. I have no problem working 8+ hours a day on intermediate programming tasks, but when it comes to these really advanced problems I can't really be "on" all day. This morning, I went for a walk, for seven miles. Was I subconsciously working during that time, so that I can later sit down and quickly solve a problem I was completely stuck on previously?
    I definitely underestimated the difficulty of making this feature work as a robust engine feature that can be used reliably. There is a lot of nuance and small issues that come up when you start testing in a variety of scenes, and this information could easily fill an hour-long talk about the details of voxel cone step tracing. However, there is just one more step, to make the moving volumes work with multiple GI stages. Once that is working I can proceed with more testing, look for artifacts to eliminate, and optimize speed.
    This is the last big feature I have to finish. It seems fitting that I should get one final big challenge before completing Ultra Engine, and I am enjoying it.
  22. Josh
    Package plugins are now supported in Ultra Engine 1.0.2. This allows the engine to handle other package formats besides just ZIP. In order to test this out, I created the Quake Loader plugin, which currently supports the following formats used in the original Quake game:
    PAK WAD BSP (textures) SPR LMP Why Quake? Well, the original Quake game is what got me into game development through modding, but Quake is also great for testing because it's so weird. This game was written before hardware accelerated graphics existed, so it has a lot of conventions that don't match modern 3D standards:
    All animation is vertex morphing (no skeletal animation) Texture coordinates are in pixels / integers instead of floating point values Three different types of "packages" (PAK, WAD, BSP), the latter two of which can be stored inside the first (packages-in-packages) Image files are stored seven different ways: LMP image files SPR sprite files Color pallette ('@') Mipmapped texture in WAD ('D') Console picture ('E') Status bar pictures ('B') Mipmapped textures stored in BSP Each of the image storage methods store data in slightly different ways. Sometimes the width and height are packed into the image data, sometimes they are found elsewhere in a separate structure, sometimes they are hard-coded, and sometimes you have to guess the image dimensions based on the data size. It's a mess, and it seems like this was made up on-the-fly by several people, but this was cutting-edge technology at the time and I don't think usability outside the specified game they were working on was a big concern.
    There are some interesting challenges there, but this forms the basis of several dated but influential games. As time went by, the design became more and more sane. For example, Quake 3 just stores textures as TGA images in a ZIP file with the extension changed to PK3. So I figure if I can make the plugin system flexible enough to work with Quake then it will be able to handle anything.
    Originally I tried implementing this using HLLib from Nem's Tools. This library supports Quake formats and more, but I had a lot of problems with it, and got better results when I just wrote my own PAK loading code. WAD files were an interesting challenge. There is no real Quake texture format, so loading the raw texture data from the package made no sense. The most interesting breakthrough in this process was how I handled WAD textures. I finally figured out I can make the WAD loader return a block of data that forms a DDS file when a texture is loaded, even through the texture name has no extension. This tricks the engine into thinking the WAD package contains a DDS file which can easily be loaded, when the reality is quite different. This introduces an important concept, that package plugins don't necessarily have to return the exact data they contain, but instead can return files in a ready-to-use format, and the game engine will never know the difference.
    The resulting plugin allows you to easily extract and load textures from the Quake game files. This code will extract textures from a single WAD file.
    #include "UltraEngine.h" using namespace UltraEngine; void main(int argc, const char* argv[]) { // Load Quake file loader plugin auto qplg = LoadPlugin("Plugins/QuakeLoader"); // Load FreeImage texture plugin auto fiplg = LoadPlugin("Plugins/FITextureLoader"); // WAD to download WString wadfile = "bigcastle"; // Download a WAD package DownloadFile("https://www.quaketastic.com/files/texture_wads/" + wadfile + ".wad", wadfile + ".wad"); // Load the package auto wad = LoadPackage(wadfile + ".wad"); // Create a subdirectory to save images in CreateDir(wadfile); // Read all files in the package auto dir = wad->LoadDir(""); for (auto file : dir) { // Load the image auto pm = LoadPixmap(file); // Save as a PNG file if (pm) pm->Save(wadfile + "/" + file + ".png"); } OpenDir(wadfile); } All the files found in the WAD package are saved as PNG images. BSP files will work exactly the same way.

    This code will load a Quake package, extract all the textures from all the maps in the game, and save them to a folder on the desktop. This is done by detecting packages-in-packages (WAD and BSP), which return the file type '3', indicating that they can be treated both as a file and as a folder. Since none of these package formats use any compression, pixmaps can be easily loaded straight out of the file without extracting the entire BSP. Since Quake PAK files don't use compression, the whole system just turns into a very complicated blob of data with pointers that store data all over the place:
    #include "UltraEngine.h" using namespace UltraEngine; void main(int argc, const char* argv[]) { // Load Quake file loader plugin auto qplg = LoadPlugin("Plugins/QuakeLoader"); // Load FreeImage texture plugin auto fiplg = LoadPlugin("Plugins/FITextureLoader"); // Path to Quake game WString qpath = "C:/Program Files (x86)/Steam/steamapps/common/Quake"; //WString qpath = "D:/SteamLibrary/steamapps/common/Quake"; // Load game package auto pak = LoadPackage(qpath + "/id1/PAK1.PAK"); //Change the current directory to load files with relative paths ChangeDir(qpath + "/id1"); // Create a folder to save images in WString savedir = GetPath(PATH_DESKTOP) + "/Quake"; CreateDir(savedir); // Load the main package directory auto dir = pak->LoadDir("maps"); for (auto file : dir) { // Print the file name Print(file); // Get the file type int type = FileType("maps/" + file); // If a package-in-a-package is found load its contents (BSP and WAD) if (type == 3) { auto subdir = pak->LoadDir("maps/" + file); for (int n = 0; n < subdir.size(); ++n) { // Load the texture (plugin will return DDS files) auto pm = LoadPixmap("maps/" + file + "/" + subdir[n]); // Save to PNG if (pm) pm->Save(savedir + "/" + subdir[n] + ".png"); } } } OpenDir(savedir); } Here is the result:

    Since all of these images can be loaded as pixmaps, does that mean they can also be loaded as a texture? I had to know the answer, so I tried this code:
    #include "UltraEngine.h" using namespace UltraEngine; int main(int argc, const char* argv[]) { //Get the displays auto displays = GetDisplays(); //Create a window auto window = CreateWindow("Ultra Engine", 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->SetPosition(0, 0, -2); camera->SetFov(70); //Create a light auto light = CreateBoxLight(world); light->SetRotation(45, 35, 0); light->SetRange(-10, 10); light->SetColor(2); //Create a model auto model = CreateBox(world); // Load Quake file loader plugin auto qplg = LoadPlugin("Plugins/QuakeLoader"); // WAD to download WString wadfile = "bigcastle"; // Download a WAD package DownloadFile("https://www.quaketastic.com/files/texture_wads/" + wadfile + ".wad", wadfile + ".wad"); // Load the package auto wad = LoadPackage(wadfile + ".wad"); // Load a texture from the WAD package auto tex = LoadTexture("sclock"); // Create a material auto mtl = CreateMaterial(); mtl->SetTexture(tex); // Apply the material to the box model->SetMaterial(mtl); //Main loop while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false) { model->Turn(0, 1, 0); world->Update(); world->Render(framebuffer); } return 0; } Here is the result, a texture loaded straight out of a Quake WAD file!

    To reiterate, testing with Quake files helped me to come up with two important design features:
    Packages-in-packages, indicated with the file type '3'. Packages do not necessarily have to return the same exact data they contain, and can instead prepare the data in a ready-to-use format, when that is desirable. The decision to base file type detection on the contents of the file instead of the file name extension worked well here, and allowed me to load the extension-less WAD texture names as DDS files. Some of these images came from "bigcastle.wad". I don't know who the original author is.
    If you are interested, you can read more about the weird world of Quake file formats here, although I must warn you that specification is not complete! 
  23. Josh
    Well, the single-state lua update is out and I am ready to start making tutorials again.
     
    Someone in the forum pointed out the game Fuel to me. This is an offroad racing game with a nearly infinite world. The data is streamed off the hard drive, basically like its treating the drive as if it were extended RAM. The game got bad reviews, but I think it's great. I went driving for at least 30 minutes and covered mountains, desert, redwood forests, and ravines. I'm in love with the terrain engine. If only they had used that to make S.T.A.L.K.E.R...
     
    There's much to do with our existing technology before I start inventing new technology again, but the possibilities are intriguing.
  24. Josh
    I got selection and rendering order working correctly, which is really the problem I wanted to solve with this. Here's a screenshot of the work in progress:
     



×
×
  • Create New...