Leadwerks Company Blog

  • entries
    147
  • comments
    923
  • views
    251,302

About this blog

Entries in this blog

Josh

Three years ago I realized we could safely distribute Lua script-based games on Steam Workshop without the need for a binary executable.  At the time this was quite extraordinary.
http://www.develop-online.net/news/update-leadwerks-workshop-suggests-devs-can-circumvent-greenlight-and-publish-games-straight-to-steam/0194370

Leadwerks Game Launcher was born.  My idea was that we could get increased exposure for your games by putting free demos and works in progress on Steam.  At the same time, I thought gamers would enjoy being able to try free indie games without the possibility of getting viruses.  Since then there have been some changes in the market.

  • Anyone can publish a game to Steam for $100.
  • Services like itch.io and GameJolt have become very popular, despite the dangers of malware.

Most importantly, the numbers we see on the Game Launcher just aren't very high.  My own little game Asteroids3D is set up so the user automatically subscribes to it when the launcher starts.  Since March 2015 it has only gained 12,000 subscribers, and numbers of players for other games are much lower.  On the other hand, a simple game that was hosted on our own website a few years back called "Furious Frank" got 22,000 downloads.  That number could be much higher today if we had left it up.

So it appears that Steam is good for selling products, but it is a lousy way to distribute free games.  In fact, I regularly sell more copies of Leadwerks Game Engine than I can give away free copies of Leadwerks Game Launcher.

This isn't to say Game Launcher was a failure.  In many cases, developers reported getting download counts as high or higher than IndieDB, GameJolt, and itch.io.  This shows that the Leadwerks brand can be used to drive traffic to your games.

On a technical level, the stability of Leadwerks Game Engine 4 means that I have been able to upgrade the executable and for the most part games seamlessly work with newer versions of the engine.  However, there are occasional problems and it is a shame to see a good game stop working.  The Game Launcher UI could stand to see some improvement, but I'm not sure it's worth putting a lot of effort into it when the number of installs is relatively low.

Of course not all Leadwerks games are written in Lua.  Evayr has some amazing free C++ games he created, and we have several commercial products that are live right now, but our website isn't doing much to promote them.  Focusing on distribution through the Game Launcher left out some important titles and split the community.

Finally, technological advancements have been made that make it easier for me to host large amounts of data on our site.  We are now hooking into Amazon S3 for user-uploaded file storage.  My bill last month was less than $4.00.

A New Direction

It is for these reasons I have decided to focus on refreshing our games database and hosting games on our own website.  You can see my work in progress here.
https://www.leadwerks.com/games

The system is being redesigned with some obvious inspiration from itch.io and the following values in mind:

  • First and foremost, it needs to look good.
  • Highly customizable game page.
  • Clear call to action.

There are two possible reasons to post your game on our site.  Either you want to drive traffic to your website or store page, or you want to get more downloads of your game.  Therefore each page has very prominent buttons on the top right to do exactly this.

Each game page is skinnable with many options.  The default appearance is sleek and dark.

Image6.thumb.jpg.29bc76364b7e74d307fd5dc5177f0411.jpg

You can get pretty fancy with your customizations.

Image5.thumb.jpg.09565a53730acbeb44b1f9bd561a13d5.jpg

Next Steps

The templates still need a lot of work, but it is 80% done.  You can begin playing around with the options and editing your page to your liking.  Comments are not shown on the page yet, as the default skin has to be overridden to match your page style, but they will be.

You can also post your Game Launcher games here by following these steps:

  • Find your game's file ID in the workshop.  For example if the URL is "http://steamcommunity.com/sharedfiles/filedetails/?id=405800821" then the file ID is "405800821".
  • Subscribe to your item, start Steam, and navigate to the folder where Game Launcher Workshop items are stored:
    C:\Program Files (x86)\Steam\steamapps\workshop\content\355500
  • If your item is downloaded there will be a subfolder with the file ID:
    C:\Program Files (x86)\Steam\steamapps\workshop\content\355500\405800821
  • Copy whatever file is found in that folder into a new folder on your desktop.  The file might be named "data.zip" or it could be named something like "713031292550146077_legacy.bin".  Rename the file "data.zip" if it is.
  • Copy the game launcher game files located here into the same folder on your desktop:
    C:\Program Files (x86)\Steam\steamapps\common\Leadwerks Game Launcher\Game
  • When you double-click "game.exe" (or just "game" on Linux) your game should now run.  Rename the executable to your game's name, including the Linux executable if you want to support Linux.
  • Now zip up the entire contents of that folder and upload it on the site here.

You can also select older versions of Game Launcher in the Steam app properties if you want to distribute your game with an older executable.

Save the Games

There are some really great little games that have resulted from the game tournaments over the years, but unfortunately many of the download links in the database lead to dead links in DropBox and Google Drive accounts.  It is my hope that the community can work together to preserve all these fantastic gems and get them permanently uploaded to our S3 storage system, where they will be saved forever for future players to enjoy.

If you have an existing game, please take a look at your page and make sure it looks right.

  • Make any customizations you want for the page appearance.
  • Clean up formatting errors like double line breaks, missing images, or dead links.
  • Screenshots should go in the screenshot field, videos should go in the video field, and downloads should go in the downloads field.

Some of the really old stuff can still be grabbed off our Google drive here.

I appreciate the community's patience in working with me to try the idea of Game Launcher, but our results clearly indicate that a zip download directly from our website will get the most installs and is easiest for everyone.

Josh

Leadwerks has historically had a small group of customers outside of the game industry who use our software for simulations, training, and visualization.  Customers using our software products include NASA, Lockheed Martin, Northrop Grumman, and the British Royal Navy.  Today I am happy to announce that in response to overwhelming demand we are now offering our services to build custom VR applications with Leadwerks.

 

 

This puts us in head-to-head competition with other services firms who are mostly using the Unity3D engine to put out quick results.  However, longstanding design decisions going back years have put Leadwerks Software in a position that gives us very strong advantages in the VR market.  In this article I will explain how we are leveraging our unique competitive advantages to provide the most compelling results for your VR project.

Leadwerks vs. Unity3D for Virtual Reality

Most of our competitors have tried to take shortcuts by building on a platform with severe limitations, using the Unity 3D engine together with the C# programming language. This 3D engine is primarily used for mobile games, and the C# programming language was originally created for event-driven business applications.

We on the other hand have built our own 3D development system that is specifically designed to capture the maximum capabilities of VR.  Our 3D engine is built specifically for high-end PCs, with graphical fidelity and performance as our overarching principles. We use the C++ programming language which is the standard for any computationally intensive code, including operating systems, device drivers, high-frequency trading software, and virtual reality applications, which must operate at a steady 90 frames per second to prevent nausea. Our development approach brings several significant competitive advantages we can now offer to you.

C/C++ Interoperability

Virtually all major scientific and engineering libraries like MATLAB, etc. are written in C or C++.  Because our VR development platform is written in pure C++ we can seamlessly integrate with all of your existing C and C++ code. For example, actual satellite control code could be compiled into a simulation and run seamlessly to test how the spacecraft would react to a variety of simulated conditions. All scientific and engineering code libraries are easily accessible from a Leadwerks project.

 

Competitors using C# and the Unity 3D engine will encounter roadblocks when they attempt to interface with C/C++ code. An intermediate wrapper has to be written that converts object-oriented code into procedural commands. This process is time-intensive and prone to breakage when APIs change with new versions. Integration of C/C++ code with Leadwerks, on the other hand, is instantaneous and seamless.

Performance

Nausea is a serious consideration in VR. If a discrepancy exists between the inputs received by the operator’s ocular and vestibular systems, it will result in motion sickness. An engineering tool designed to be used for long periods of time must maintain a steady 90 frames per second, allowing only 11 milliseconds for each frame render. Unfortunately, C# is a memory-managed language meaning it suffers overall slower performance, as well as periodic pauses in program execution while garbage collection is performed. All of our code is written in C++ and will perform at the maximum speed allowed by the hardware. This allows us to create richer VR applications with expanded capabilities while our competitors will run into performance problems that cause unpleasant physiological symptoms.

 

QkXl7JzKEvV4XHqb4qdu1vJz-hqPTZds_z5_ZyL3gNpk8zlEv7aC6jNSFfsi8pRzeO78zbuoNFCHB7TBZacMhI18TLWNZotlPP9QGTKAf_NkqtS3UM660baJScMh-5LEPXOv8FxI

Benchmark showing execution time of C++ vs. C#.

Source: https://www.codeproject.com/Articles/212856/Head-to-head-benchmark-Csharp-vs-NET

Source Code Modification

Because we developed our own 3D engine we have full access to the entire source code and can make modifications to expand its capabilities (5). For example, we learned that some aerospace clients were experiencing problems with 32-floating point precision in some applications, so we re-compiled our software using 64-bit floating points, raising the maximum area we can simulate up to one cubic light year with sub-millimeter precision. Because our competitors do not have source code access to the 3D engine they are using, their ability to elastically scale their capabilities and customize their 3D engine for your needs will be greatly impeded.

Accuracy of Simulated Physics

Our software features a fast and stable Newtonian physics system that provides the most realistic physics simulation possible at real-time speeds. As the video above demonstrates, this can be used to simulate robotic arms and other moving mechanical features with a high degree of realism.

 

The Unity physics system was designed for games and runs on the graphical processing unit (GPU). GPUs are good at performing massive parallel processing computations but are not good at problems that involve a lot of data exchange. Unfortunately, colliding objects are a problem that involves a high degree of data exchange between threads, so the accuracy of the simulation is compromised. This has two significant consequences. First, physics in Unity tend to be much less stable than in Leadwerks, making it difficult to simulate complex jointed systems like a robotic arm. A video showing the difference can be seen here.

 

sw2sFZi1XRuv81Pb614YC0AmqhhI5vbAmUdwAHmmDqyrY5ut8BNdowyOLR97dSghlJY20d_wk2jj0MEXTfSE9VRe8sTRhUwrieuxycH_UFdMb__LkFUbkxv_fDEfMDra9vF7YpEa

Rigid body stacking test: Leadwerks physics (green) are stable while Unity physics (yellow) spontaneously collapse.

Second, physics in Unity are non-deterministic. This means that each time a simulation is run, the result will be different, making it very difficult to predict outcomes. The Leadwerks physics system is deterministic and will provide the exact same result each time it is run, even if new objects are introduced into the simulation.

The competitive advantages we can put to work for your VR project are summarized below.  Simply put, we can build applications that are bigger, faster, and have more capabilities.

 

 

Other firms using Unity

Leadwerks VR Services

Primary platform of 3D engine

Mobile phones

High-end PCs

C/C++ Interoperability

Requires C# wrapper

Seamless

Performance

Slower with GC pauses, results in nausea

Fastest possible performance

3D engine source code modification

No

Yes

Physics simulation

Unstable, non-deterministic

Stable, deterministic

Maximum range with sub-mm precision

Eight kilometers

One light year

If you are interested in taking advantage of our capabilities to build VR applications send us an email, or catch me at I/ITSEC later this week.

Admin

A beta build of version 4.5 is now available on the beta branch on Steam.  This updates the engine to the latest Newton 3.14.  Versions 4.5 and 5 beta are now compiling side-by-side with the same source code.  Because of major engine changes in version 5, some bugs may need to be resolved before the final release.  Some preliminary information on updating C++ projects can be found in this thread.

Version 4.5 is planned to include official support for VR (both Vive and Oculus) and a new improved vehicle system.

blogentry-1364-0-78931300-1491429191.jpg

Josh

Our most recent game tournament was a smashing success.  We had fewer entries this time, but they more than made up for it with the great quality of this round of games.  Without further ado I am happy to present the entries...

Behind Enemy Lines

Wow!  This game by burgelkat features a variety of missions from blowing up drug manufacturing facilities to sabatoging a plane.  Although the same mechanic is usually used, the action never gets old and you will keep playing just to find out what will happen next.  You may recognize the voice acting from our own Jorn Theunissen (Aggror) on the forum.  You don't want to miss this one!

5a0e1fdd2ffdf_BehindEnemyLines2017-11-1615-48-37-11.thumb.jpg.58169cea5bb69ff9cb0a5f96388a2900.jpg

5a0e1fdfe520f_BehindEnemyLines2017-11-1616-00-32-31.thumb.jpg.da5d726ceec7a5d032bc8cd7c24a34f6.jpg

5a0e1fe19bb7f_BehindEnemyLines2017-11-1615-48-22-14.thumb.jpg.e52f380f5610f06bb4cf1127e29ff195.jpg

Nightmare Prism

Nirvana is popular right now, and the SNES classic edition was just released.  In case that's not enough 1990's nostalgia for you, here is the excellent Nightmare Prism by AngelWolf.  Clever level design with lots of traps and well-placed enemies will keep you on your toes as you frag your way through three levels of hellish onslaughts.

The Cemetery

Third on our list of games is an explicitly Halloween-themed title with tombstones and pumpkins aplenty.  The Cemetery by Rozsoft is a short but suspenseful experience putting you into an old graveyard at night in search of your disappeared friends.  Best played late at night with the lights out!

5a0e235de412e_TheCemetery2017-11-1616-41-09-68.thumb.jpg.897bf549ae489c74f545f6a046cad1fe.jpg

5a0e235f90f36_TheCemetery2017-11-1616-42-38-35.thumb.jpg.1ecf855b181017e6c9a282c9182de218.jpg

Dread Loop

This title by member "FortifyThisMFker!" brings us back to the 90's shooter theme with a center-mounted gun and an arena of enemies.  After dispatching your foes you can select an upgrade for your weapon, health, or suit, which makes for some interesting choices.  But I've got to be honest, seeing the giblets fly is what really makes this game fun.  Try it out!

5a0e249361092_DreadLoop2017-11-1616-48-11-67.thumb.jpg.8231b53ff9289a03836933d8d73c65e8.jpg

5a0e24904f411_DreadLoop2017-11-1616-49-16-46.thumb.jpg.89c7f3dbd68d79097c2d2b03b9ae16c0.jpg

Exit Zed

In Exit Zed by mdgunn you will explore a scientific facility in search of zombies to high-five...except that the way you like to give high-fives is with your handy dandy fully automatic Bunsen burner tool (patent pending).  The game is obviously unfinished and you might stumble across some doorways leading to nothingness, but the sound effect of your trusty scientific tool alone makes this worth playing.

5a0e26f0a1ef5_escape_zed2017-11-1616-53-59-56.thumb.jpg.aca9893d57864b280a23812a4c92066f.jpg

5a0e26f38e967_escape_zed2017-11-1616-54-16-35.thumb.jpg.ba6b8f1d546cb2569b08d7059036ce56.jpg

Dissension

Available in Leadwerks Game Launcher, Dissension is another nail-biting SciFi shooter from Garlic Waffle.  Despite the cartoonish graphics, his games really frightening.  This one is sure to keep you on the edge of your seat!

previewfile_1170731468.jpg.a4f504f99bb7f2edef1937a64db9dc77.jpg

5a0e298e76cc6_game2017-11-1617-08-47-84.thumb.jpg.63b706d2e2bcafa92c1bec41a528258c.jpg

Sewer Survival

Garlic Waffle has gone into overtime and brought your TWO free games to play this tournament!  In Sewer Survival you play to make it out of an underground prison.  Expect clever puzzles, loads of enemies, and not a lot of hit points.

previewfile_1187466135.thumb.jpg.64a826d52f6cead9ff02b362e8e51862.jpg

5a0e2b6291db2_game2017-11-1617-17-36-27.thumb.jpg.a24d0912aa3b4df6c7324c85e9d959bc.jpg

Josh

Today we are pleased to announce the release of Leadwerks Game Engine: Enterprise Edition, a standalone version of our popular 3D development software. The Enterprise Edition allows business users to install and use Leadwerks without the need for the Steam client. The new product joins the existing Standard Edition with Lua scripting and the Professional Edition with C++ and Visual Studio support, both sold on Steam.

The Enterprise Edition has already been approved for sale through NASA’s ACES approval program for software, and NASA has joined a lineup of business customers using Leadwerks products that includes Lockheed Martin, Northrop Grumman, and the British Royal Navy.

In the near future the capabilities of our software will continue to expand to support the needs of business customers in the VR and simulation spaces. The development of Leadwerks Game Engine 5 is being directed with a focus on performance, hyperrealism, and improved ease of use.

Leadwerks Game Engine: Enterprise Edition is available to purchase for $499 on our website.

04A8AB5435AB62692F134DE1B847E92067E86BFC.jpeg

Image courtesy of NASA Satellite Servicing Projects Division.

Josh

Our website stores a lot of user generated content in the forum of images and attachments.  Before Leadwerks Game Engine was on Steam the demands were even higher, since we had our own downloads and gallery sections that stored data on our server.  Since the implementation of Steam screenshots and Workshop a lot of that has been offloaded onto the Steam servers, relieving our server from some of the data storage and transfer costs.  (If you're interested, all our old content is archived on Google drive here.)

Currently our website weighs in at about 35 GB of data.  This is backed up online daily, and offline about once a month.  The entire server is usually burned onto a Blu-Ray disc and saved away.  The time and storage space this takes is considerable, and as the site keeps growing this approach will not be sustainable.

Screen Shot 2017-10-14 at 5.29.36 PM.png

Amazon S3 allows you to store files in the cloud with an API to write and save files, at a cost of $0.023 (that's 2.3 cents) per GB per month.  I've hooked into the service to offload all user attachments, images, and profile pictures onto Amazon's servers.  This leaves our core site data at an easily manageable 2.5 GB, which I can easily burn onto a DVD.

Now that all user data is stored in the AWS system for dirt cheap prices, we can easily grow the amount of content on our site without it impacting the site responsiveness, backup time, or having much impact on operating costs.  Stay tuned and I will tell you how we are going to use this for game development.

 

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.banner2.jpg.f624604f9cb6d36b68abc991e3f9a9cc.jpg

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.

Josh

Leadwerks 5 is going to be developed alongside 4.5 with an extended period of beta testing and feedback.  My goal is to build the world's most advanced game design software, tailored to the needs of our community and clients.  Development will first focus on a new programming API updated for C++11 and then a completely new editor using Leadwerks GUI and based on the workflow developed with our current editor.

The first beta will support the following features right away:

These features are already working.  Here is a working build you can try right now:
Leadwerks5.zip

This is the actual source code used to make this example.  Note that C++11 shared pointers have been implemented for all objects and the API has been simplified to make your code more readable:

#include "Leadwerks.h"

using namespace Leadwerks;

int main(int argc, const char *argv[])
{
	auto window = CreateWindow();
	auto context = CreateContext(window);
	auto world = CreateWorld();

	auto camera = CreateCamera(world);
	camera->Move(0, 0, -3);

	auto light = CreateDirectionalLight(world);
	light->SetRotation(35, 35, 0);
	light->SetShadowMode(0);

	auto model = CreateBox(world);
	model->SetColor(0.0, 1.0, 0.92);

	while (not window->Closed())
	{
		if (window->KeyHit(EscapeKey)) break;
		if (model) model->Turn(0, 1, 0);
		if (window->KeyHit(SpaceKey)) model = nullptr;
		world->Update();
		world->Render(context);
	}
	return 0;
}

The beta will be available to developers who want to try the very latest game development technology, with a subscription plan of just $4.99 a month, available through our website.

Josh

I've begun implementing unicode in Leadwerks Game Engine 5.  It's not quite as simple as "switch all string variables to another data type".

First, I will give you a simple explanation of what unicode is.  I am not an expert so feel free to make any corrections in the comments below.

When computers first started drawing text we used a single byte for each character.  One byte can describe 256 different values and the English language only has 26 letters, 10 numbers, and a few other characters for punctuation so all was well.  No one cared or thought about supporting other languages like German with funny umlauts or the thousands of characters in the Chinese language.

Then some people who were too smart for their own good invented a really complicated system called unicode.  Unicode allows characters beyond the 256 character limit of a byte because it can use more than one byte per character.  But unicode doesn't really store a letter, because that would be too easy.  Instead it stores a "code point" which is an abstract concept.  Unfortunately the people who originally invented unicode were locked away in a mental asylum where they remain to this day, so no one in the real world actually understands what a code point is.

There are several kinds of unicode but the one favored by nerds who don't write software is UTF-8.  UTF-8 uses just one byte per character, unless it uses two, or sometimes four.  Because each character can be a different length there is no way to quickly get a single letter of a string.  It would be like trying to get a single byte of a compressed zip file; you have to decompress the entire file to read a byte at a certain position.  This means that commands like Replace(), Mid(), Upper(), and basically any other string manipulation commands simply will not work with UTF-8 strings.

Nonetheless, some people still promote UTF-8 religiously because they think it sounds cool and they don't actually write software.  There is even a UTF-8 Everywhere Manifesto.  You know who else had a manifesto?  This guy, that's who:

Karl_Marx.thumb.jpg.1ece92f31b80f2b01f9a2b0bd51b0381.jpg

Typical UTF-8 proponent.

Here's another person with a "manifesto":

Theodore_Kaczynski.jpg.c537d9a7ae01f7b53e773b19a7c0bc49.jpg

The Unabomber (unibomber? Coincidence???)

The fact is that anyone who writes a manifesto is evil, therefore UTF-8 proponents are evil and should probably be imprisoned for crimes against humanity.  Microsoft sensibly solved this problem by using something called a "wide string" for all the windows internals.  A C++ wide string (std::wstring) is a string made up of wchar_t values instead of char values.  (The std::string data type is sometimes called a "narrow string").  In C++ you can set the value of a wide string by placing a letter "L" (for long?) in front of the string:

std::wstring s = L"Hello, how are you today?";

The C++11 specification defines a wchar_t value as being composed of two bytes, so these strings work the same across different operating systems.  A wide string cannot display a character with an index greater than 65535, but no one uses those characters so it doesn't matter.  Wide strings are basically a different kind of unicode called UTF-16 and these will actually work with string manipulation commands (yes there are exceptions if you are trying to display ancient Vietnamese characters from the 6th century but no one cares about that).

For more detail you can read this article about the technical details and history of unicode (thanks @Einlander).

First Pass

At first I thought "no problem, I will just turn all string variables into wstrings and be done with it".  However, after a couple of days it became clear that this would be problematic.  Leadwerks interfaces with a lot of third-party libraries like Steamworks and Lua that make heavy use of strings.  Typically these libraries will accept a chr* value for the input, which we know might be UTF-8 or it might not (another reason UTF-8 is evil).  The engine ended up with a TON of string conversions that I might be doing for no reason.  I got the compiler down to 2991 errors before I started questioning whether this was really needed.

Exactly what do we need unicode strings for?  There are three big uses:

  • Read and save files.
  • Display text in different languages.
  • Print text to the console and log.

Reading files is mostly an automatic process because the user typically uses relative file paths.  As long as the engine internally uses a wide string to load files the user can happily use regular old narrow strings without a care in the world (and most people probably will).

Drawing text to the screen or on a GUI widget is very important for supporting different languages, but that is only one use.  Is it really necessary to convert every variable in the engine to a wide string just to support this one feature?

Printing strings is even simpler.  Can't we just add an overload to print a wide string when one is needed?

I originally wanted to avoid mixing wide and narrow strings, but even with unicode support most users are probably not even going to need to worry about using wide strings at all.  Even if they have language files for different translations of their game, they are still likely to just load some strings automatically without writing much code.  I may even add a feature that does this automatically for displayed text.  So with that in mind, I decided to roll everything back and convert only the parts of the engine that would actually benefit from unicode and wide strings.

Second Try + Global Functions

To make the API simpler Leadwerks 5 will make use of some global functions instead of trying to turn everything into a class.  Below are the string global functions I have written:

std::string String(const std::wstring& s);
std::string Right(const std::string& s, const int length);
std::string Left(const std::string& s, const int length);
std::string Replace(const std::string& s, const std::string& from, const std::string& to);
int Find(const std::string& s, const std::string& token);
std::vector<std::string> Split(const std::string& s, const std::string& sep);
std::string Lower(const std::string& s);
std::string Upper(const std::string& s);

There are equivalent functions that work with wide strings.

std::wstring StringW(const std::string& s);
std::wstring Right(const std::wstring& s, const int length);
std::wstring Left(const std::wstring& s, const int length);
std::wstring Replace(const std::wstring& s, const std::wstring& from, const std::wstring& to);
int Find(const std::string& s, const std::wstring& token);
std::vector<std::wstring> Split(const std::wstring& s, const std::wstring& sep);
std::wstring Lower(const std::wstring& s);
std::wstring Upper(const std::wstring& s);

The System::Print() command has become a global Print() command with a couple of overloads for both narrow and wide strings:

void Print(const std::string& s);
void Print(const std::wstring& s);

The file system commands are now global functions as well.  File system commands can accept a wide or narrow string, but any functions that return a path will always return a wide string:

std::wstring SpecialDir(const std::string);
std::wstring CurrentDir();
bool ChangeDir(const std::string& path);
bool ChangeDir(const std::wstring& path);
std::wstring RealPath(const std::string& path);
std::wstring RealPath(const std::wstring& path);

This means if you call ReadFile("info.txt") with a narrow string the file will still be loaded even if it is located somewhere like "C:/Users/约书亚/Documents" and it will work just fine.  This is ideal since Lua 5.3 doesn't support wide strings, so your game will still run on computers around the world as long as you just use local paths like this:

LoadModel("Models/car.mdl");

Or you can specify the full path with a wide string:

LoadModel(CurrentDir() + L"Models/car.mdl");

The window creation and text drawing functions will also get an overload that accepts wide strings.  Here's a window created with a Chinese title:

Image1.jpg.987ae08e739ae771c6a6bda0bef35ec0.jpg

So in conclusion, unicode will be used in Leadwerks and will work for the most part without you needing to know or do anything different, allowing games you develop (and Leadwerks itself) to work correctly on computers all across the world.

Josh

I have implemented C++11 shared pointers into Leadwerks Game Engine 5 and the following program now works.  When you press the space key the box variable is set to NULL and the visible box on the screen disappears:

#include "Leadwerks.h"

using namespace Leadwerks;

int main(int argc, const char *argv[])
{
	auto window = CreateWindow();
	auto context = CreateContext(window);
	auto world = CreateWorld();

	auto camera = CreateCamera(world);
	camera->Move(0,0,-5);
	camera->SetClearColor(0,0,1);

	auto light = CreateDirectionalLight(world);

	auto box = CreateBoxModel(world);

	while (not window->Closed())
	{
		if (window->KeyHit(SpaceKey)) box = NULL;
		if (box) box->Turn(0, 1, 0);
		world->Update();
		world->Render(context);
	}
	return 0;
}

Shared pointers provide the ease of use of a memory-managed language like C# but run much faster because they aren't using full garbage collection.  The one thing you have to watch out for is circular references.  In the example below, neither a or b would ever be deleted from memory, since they reference each other:

class thing
{
	shared_ptr<thing> friend;
};

auto a = make_shared<thing>();
auto b = make_shared<thing>();
a.friend = b;
b.friend = a;
a = NULL;
b = NULL;

This problem can be solved by using weak pointers:

class thing
{
	weak_ptr<thing> friend;
	shared_ptr<thing> GetFriend();  
};

shared_ptr<thing> thing::GetFriend()
{
	return friend.lock();
}

auto a = make_shared<thing>();
auto b = make_shared<thing>();
a.friend = b;
b.friend = a;
a = NULL;
b = NULL;

When using shared pointers you need to think about hierarchical relationships.  For example, a parent entity stores a list of shared pointers for its child entities, but each child uses a weak pointer to store its parent.  This makes it so children will not be deleted as long as you hold onto the parent entity:

class Entity
{
	std::list<shared_ptr<Entity> > kids;
	weak_ptr<Entity> parent;
};

These relationships are not always immediately obvious.  For example you might think that a world should control when entities are in scope, but that would not allow our first example above.  Instead, entity creation functions return a shared_ptr and the world octree only stores weak_ptr values to entities.  When you want to delete an entity, just set it to NULL.  If you have large collections of entities you can just store them in a list and clear the list when you are ready.  There is no World::Clear() function since the entities are managed by your own code.  I'm not 100% sure on how map loading will work with this, but so far it is working out well.

One thing to watch out for is that Self() or shared_from_this() cannot be accessed in the object constructor or destructor.  This will require some changes to how Leadwerks handles destruction of some objects, since previous versions uses manual deletion and removed lots of list iterators from different parts of the game world.  You might run into situations where you have a lot of weak pointers stored in lists like this:

for (std::list<weak_ptr<Entity> > it = list.begin(); it!=list.end(); it++)
{
	auto entity = (*it).lock();
	//do some stuff
}

Here is how you can modify the loop to handle the situation where an expired weak pointer is encountered (the object was deleted):

std::list<weak_ptr<Entity> > it = list.begin();
while (it != list.end())
{
	auto entity = (*it).lock();
	if (entity == NULL)
	{
		list.erase(it++);
		continue;
	}
	//do some stuff
	it++;
}

This will skip deleted objects and trim the list so that it doesn't grow out of control.

The new paradigm of C++11 shared pointers requires a little bit different approach but is much easier to use and does a good job of preventing mistakes especially when your game gets more complex.

Josh

All classes in Leadwerks are derived from a base Object class.  In Leadwerks 5 we separate simple and complex objects with the new SharedObject class.

Simple objects like a Vec3 ( a three-dimensional vector), an AABB (axis-aligned bounding box), and other items are all derived from the Object class.  Simple objects are created with constructors.  When we make one object equal to another the value is copied from one variable to another, but the two variables are still separate objects.  Below, A and B have the same value but are separate objects:

Vec3 a = Vec3(1,2,3);
Vec3 b = a;

Shared objects are things you don't want to copy, because they involve more than just some numbers.  These always use C++11 shared pointers and use a Create function.  Below, A and B refer to the same object:

shared_ptr<World> a = CreateWorld();
shared_ptr<World> b = a;

The SharedObject class has a couple of functions to make life easier.  Instead of casting pointers with some funny syntax we can use the Cast() method.  Here's an example of casting in Leadwerks 4:

Entity* entity = Model::Load("car.mdl");
Model* model = (Model*)entity;

And here's how it works in Leadwerks 5:

shared_ptr<Entity> entity = LoadModel("car.mdl");
shared_ptr<Model> model = entity->Cast<Model>();

Instead of using "this" inside a class method you can use Self() to get a shared pointer to the object itself:

class MyActor : public Actor
{
	void MyFunction()
	{
		//MyActor* me = this;
		shared_ptr<SharedObject> me = Self();
	}
}

Self() will always return a shared_ptr<SharedObject> value, so you can use Cast() if you need a specific type of object (and match the behavior of "this"):

class MyActor : public Actor
{
	void MyFunction()
	{
		//MyActor* me = this;
		shared_ptr<MyActor> me = Cast<MyActor>();
	}
}

Instead of calling delete or Release to destroy a shared object, all you have to do is set its variable to NULL:

shared_ptr<Model> model = LoadModel("car.mdl");
model = NULL;// poof!

And of course we can always use the auto keyword to make things really simple:

auto model = LoadModel("car.mdl");

Shared objects use automatic reference counting to give you the ease of use of a garbage-collected language, together with the blazing performance of modern C++.  These features are set to make Leadwerks Game Engine 5 the easiest and most cutting-edge development system in the history of game programming.

Josh

Leadwerks Game Engine 5 moves Leadwerks forward into the future with massive performance increases and more advanced features, it also makes game development easier than ever before with three great new programming features.

Shared Pointers

I could write a whole article about the benefits of shared pointers.  Shared pointers are basically a simple form of garbage collection that relieves you from the need to manually delete objects, but doesn't suffer from the slow speed of full garbage collection.like C# uses.

In Leadwerks 4 we need to keep track of object reference counts by using the Release command when we are done with them:

Texture *texture = Texture::Load("Materials/Bricks/brick01.tex");
Material *material = Material::Create()
material->SetTexture(texture);
Model *model = Model::Box();
box->SetMaterial(material);
material->Release();
if (texture) texture->Release();

In Leadwerks 5 we never have to worry about calling Release() or (less commonly used) AddRef() because reference counts of shared pointers are tracked automatically.  The code below would cause a memory leak in Leadwerks 4 but is perfectly fine to use in Leadwerks 5:

auto material = Material::Create()
material->SetTexture(Texture::Load("Materials/Bricks/brick01.tex"));
auto model = Model::Box();
box->SetMaterial(material);

This even works with entities.  As soon as a variable goes out of scope the object is deleted:

if (true)
{
	auto model = Model::Box();
}// automatic deletion occurs here

1303-and-itsgone.jpg.cb17acc011c706142b7d8cf55eecd733.jpg

We can prevent deletion of an object by either keeping the variable in our code, or keeping a container that holds it.  In the case of map loading, the Map:Load() will return a structure that contains all the entity handles so they stay in memory.  Want to delete a camera?  It's easy:

camera = NULL;// poof!

There are still some details to work out but so far this approach is working great.

Another great benefit of shared pointers is that you can completely eliminate the need for big complicated destructors like this one.  All those objects will be automatically deleted when they go out of scope:

OpenGLCamera::~OpenGLCamera()
{
	for (int i=0; i<8; ++i)
	{
		if (shader_ambient[i])
		{
			shader_ambient[i]->Release();
			shader_ambient[i]=NULL;
		}
		for (int n = 0; n < 2; ++n)
		{
			for (int q = 0; q < 2; ++q)
			{
				for (int p = 0; p < 2; ++p)
				{
					if (shader_directional[i][n][q][p])
					{
						shader_directional[i][n][q][p]->Release();
						shader_directional[i][n][q][p] = NULL;
					}
					if (shader_directional_volume[i][n][q][p])
					{
						shader_directional_volume[i][n][q][p]->Release();
						shader_directional_volume[i][n][q][p] = NULL;
					}
					if (shader_point[i][n][q][p])
					{
						shader_point[i][n][q][p]->Release();
						shader_point[i][n][q][p] = NULL;
					}
					if (shader_point_volume[i][n][q][p])
					{
						shader_point_volume[i][n][q][p]->Release();
						shader_point_volume[i][n][q][p] = NULL;
					}
					if (shader_spot_volume[i][n][q][p])
					{
						shader_spot_volume[i][n][q][p]->Release();
						shader_spot_volume[i][n][q][p] = NULL;
					}
					if (shader_environment[i][n][q][p])
					{
						shader_environment[i][n][q][p]->Release();
						shader_environment[i][n][q][p] = NULL;
					}
					for (int usedecal = 0; usedecal < 2; usedecal++)
					{
						if (shader_spot[i][n][q][usedecal][p])
						{
							shader_spot[i][n][q][usedecal][p]->Release();
							shader_spot[i][n][q][usedecal][p] = NULL;
						}
					}
				}
			}
		}
	}
}

That entire function has been commented out in Leadwerks Game Engine 5. :D

Finally, shared pointers eliminate a problem that is the bane of all programmers' existence.  When used correctly, you can you can say goodbye to invalid and uninitialized pointers forever!  Yet shared pointers, unlike garbage collection, are fast to use and don't slow your game down.

Automatic Typing

The use of C++11 in Leadwerks Game Engine 5 gives us automatic typing of variables with the auto keyword.  We can simply something like this:

shared_ptr<Model> model = Model::Box();

To just this:

auto model = Model::Box();

If you have long complicated type names this makes life a million times easier:

for (std::list<shared_ptr<Entity> >::iterator it = list.begin(); it!= list.end(); it++)
{
	shared_ptr<Entity> entity = *it;
	entity->SetPosition(1,2,3);
}

Here's the same code simplified with auto (and range-based loops thanks to @Roland and @thehankinator.  Look how much more readable it is:

for (auto entity : list)
{
	entity->SetPosition(1,2,3);
}

This isn't completely new, but Leadwerks 5 is the first version built exclusively for C++11 features.

More Explicit API

Leadwerks 4 uses several bound states to store a current world and context.  These are global variables that are mostly invisible to the user.  This causes problems with multithreaded programming because two threads might try to change or access the bound state at the same time.

World *world1 = World::Create();
Model *model1 = Model::Box();

World *world2 = World::Create();
Model *model2 = Model::Box();
World::SetCurrent(world1);

Model *model3 = Model::Box();

Quick, which world does "model3" belong to?  Leadwerks 5 gets rid of the concept of bound states and requires you to explicitly specify the object you want to use.  Here's how the above code would look in Leadwerks 5:

auto world1 = World::Create();
auto model1 = Model::Box(world1);

auto world2 = World::Create();
auto model2 = Model::Box(world2);

auto model3 = Model::Box(world1);

Not only is the second example easier to understand, it's also one line shorter.

In a similar manner, the idea of a "current" context will be eliminated.  When you render a world you will need to explicitly specify which context to render to:

world->Render(context)

These programming features will make it easier than ever to code games with Leadwerks.

Josh

Today I am excited to announce plans for the release of the first Leadwerks 5 beta version.  Leadwerks 5 will roll out sooner rather than later, employing an extended beta period during which versions 4 and 5 will live side-by-side, using the same code base, with preprocessor definitions to compile each version.  This allows me to fix small problems without forking the code, while I can implement new changes in version 5.  The first features implemented will be the use of smart pointers for all shared objects, and unicode support for all strings.

A subscription model will be available for access to the Leadwerks 5 beta, at a modest price of just $4.99/month for enthusiasts who want access to the most cutting-edge game development technology as it is developed.  This will be available through the Leadwerks.com site, and will not use Steam (at least at first).  I feel it is important for the company's future to start building a recurring revenue stream, and I want to create something that does not rely on any middleman who may arbitrarily change or discontinue the terms of the service they are providing.  The Leadwerks 5 beta will implement breaking changes as it is developed, and is not meant for use in a production environment, so I do not recommend moving any commercial projects from version 4 to 5.  Leadwerks 4.x will continue to receive updates and new features until the final version 5 is released.

Leadwerks 5 is designed to be the most advanced game engine in the world, combining improved ease of use with massive performance, and a special emphasis on VR.  Thank you for supporting the next generation of game development technology.

Josh

Leadwerks Game Engine 5 is being designed to make use of shared pointers.  This eliminates manual reference counting, which has probably been the most difficult part of programming games with Leadwerks.  Here are three concepts you must understand before you start using smart pointers in Leadwerks 5,

Don't Create Multiple Shared Pointers from One Object

When a shared pointer goes out of scope, it deletes the object it references.  If another smart pointer was created separately that references that object, the other smart pointer will now point to an object that has been deleted!  Set breakpoints in the example below and you will see the problem.  The object is deleted while the second smart pointer still references it.  Any attempt to use the second smart pointer will cause an error:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	Thing* thing = new Thing;

	shared_ptr<Thing> p1 = shared_ptr<Thing>(thing);
	shared_ptr<Thing> p2 = shared_ptr<Thing>(thing);

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

Instead, initialize a smart pointer once and copy it.  Here is the correct way:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	Thing* thing = new Thing;

	shared_ptr<Thing> p1 = shared_ptr<Thing>(thing);
	shared_ptr<Thing> p2 = p1;

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

It's even better to eliminate the new keyword entirely and create object and smart pointer in one step:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	shared_ptr<Thing> p1 = make_shared<Thing>();
	shared_ptr<Thing> p2 = p1;

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

The point is, you create the first smart pointer and thereafter all code should pass that around.  You never need to access the pointer directly.

Of course the use of auto makes everything a lot simpler:

auto p1 = make_shared<Thing>();
auto p2 = p1;

Parent / Child Relationships

If you have an object that is some kind of "child" of a parent object, you probably want that parent to keep a smart pointer to the child that keeps the child from being deleted.  However, if the child has a smart pointer to the parent you are creating a circular reference that will never be deleted from memory.  Think of the parent as the owner of the child.  Something other than the child must keep the parent in memory, but sometimes the child wants to retrieve the parent object in a bit of code.  Therefore, for the child member we will use a shared pointer, and for the parent member we will use a weak pointer:

class Thing
{
public:
	shared_ptr<Thing> child;
	weak_ptr<Thing> parent;
	~Thing();
	shared_ptr<Thing> GetParent();
};

The GetParent() function would look like this:

shared_ptr<Thing> Thing::GetParent()
{
	return parent.lock();
}

You can modify existing functions that access a parent by adding one line of code.  Here is one such function as it would appear in Leadwerks 4, where the parent member is just a regular old stupid pointer:

void Thing::AccessParent()
{
	if (parent != NULL)
	{
		//do some stuff to parent here
	}
}

The updated version for Leadwerks 4 creates a new shared_ptr<Thing> variable (with auto) and locks the weak pointer to make a shared pointer.  The rest of the code works seamlessly:

void Thing::AccessParent()
{
	auto parent = this->parent.lock();
	if (parent != NULL)
	{
		//do some stuff to parent here
	}
}

Class Functions Should Never Returns Themselves

The following code illustrates a problematic issue:

class Thing
{
public:
	shared_ptr<Thing> GetSelf()
};

shared_ptr<Thing> Thing::GetSelf()
{
	return shared_ptr<Thing>(this);
}

int main(int argc, const char *argv[])
{
	shared_ptr<Thing> p1 = make_shared<Thing>();
	shared_ptr<Thing> p2 = p1->GetSelf();
}

The GetSelf() function creates a new shared pointer that has no relation to the first one.  Both of these shared pointers will attempt to delete the object when they go out scope.  Only one will win. :blink:

I did a search throughout the entire Leadwerks Engine project and found only three instances of the phrase "return this;".  The easiest way to fix this problem would be to eliminate this type of behavior altogether by having a parent get the object instead of the object returning itself.  For example if you have a recursive function that is structured like this:

Thing* Thing::FindChild(const std::string& name)
{
	if (name == this->name) return this;
	for (auto it = kids.begin(); it != kids.end(); it++)
	{
		auto child = (*it)->FindChild(name);
		if (child) return child;
	}
	return NULL;
}

You can restructure it like this:

shared_ptr<Thing> Thing::FindChild(const std::string& name)
{
	for (auto it = kids.begin(); it != kids.end(); it++)
	{
		if (name == (*it)->name) return this;
		auto child = (*it)->FindChild(name);
		if (child) return child;
	}
	return NULL;
}

I considered deriving all complex objects from a "SharedObject" class with a weak pointer that referenced itself:

class SharedObject
{
	weak_ptr<SharedObject> self;
};

However, this requires the "self" member to be set when the object is created and is tedious to use.  I think it's easier to just eliminate functions that return the object itself.

Josh

This shows the fundamental difference between shared pointers and manual reference counting.

Leadwerks 4:

void Material::SetTexture(Texture* texture, const int index)
{
	if (this->texture[index] != texture)
	{
		if (this->texture[index]) this->texture[index]->Release();
		this->texture[index] = texture;
		if (this->texture[index]) this->texture[index]->AddRef();
	}
}

Leadwerks 5:

void Material::SetTexture(shared_ptr<Texture> texture, const int index)
{
	this->texture[index] = texture;
}

The second example automatically does the same thing as the first (basically) but you don't have to worry about making mistakes.  When the texture is no longer referred to by any variable, it will be automatically deleted from system and video memory.

Josh

Previously, I talked a little bit about shared pointers in C++11.  These use automatic reference counting to track how many variables are pointing to an object.  When the object is no longer being used, it is automatically deleted.  This is similar to garbage collection, but doesn't involve the massive overhead of garbage-collected systems.  In fact, shared pointers simply automate something we were already doing with the Release() and AddRef() commands in Leadwerks 4.

A weak pointer is like a shared pointer, but having a weak pointer to an object does not prevent the object from being deleted.  Weak pointers are a great way to handle situations that previously could have resulted in an invalid pointer.  Let's say you have an AI class for an enemy in your game that stores an entity it is attacking:

class Monster : public Actor
{
	int health;
	weak_ptr<Entity> target;
  
	virtual void UpdateWorld();
};

To make use of the weak pointer, we convert it into a shared pointer.  At that point, the temporary shared pointer will prevent the object from being deleted:

void Monster::UpdateWorld()
{
	shared_ptr<Entity> enemy = this->target.lock();
	if (enemy != nullptr)
	{
		//do some stuff
	}
}

If something causes the monster's target entity to be deleted, the program will work just fine without having to go through and find all variables that point to that entity.

You can check if a weak pointer's object has been deleted with the expired() command, since creating a new shared pointer involves a certain amount of overhead:

void Monster::UpdateWorld()
{
	if (!this->target.expired())
	{
		shared_ptr<Entity> enemy = this->target.lock();
		if (enemy != nullptr)
		{
			//do some stuff
		}
	}
}

However, you still need to perform the check to see if the resulting shared pointer is nullptr/NULL because theoretically another thread could have caused the object to be deleted right after the expired() call returns false.

If you want to clean up a list of weak pointers as you discover expired pointers, you can structure it like this:

auto it = items.begin();
while (it!=items.end())
{
	auto item = (*it).lock();
	if (item != nullptr)
	{
		item->Update();
		it++;
	}
	else
	{
		items.erase(it++);
	}
}

If we add in the optional expired() check, it gets kind of complicated, but this is not something you should really need to do much:

auto it = items.begin();
while (it!=items.end())
{
	if (!(*it).expired())
	{
		auto item = (*it).lock();
		if (item != nullptr)
		{
			item->Update();
			it++;
		}
		else
		{
			items.erase(it++);
		}
	}
	else
	{
		items.erase(it++);
	}
}

Here's a real example in the Leadwerks source code.  The LEADWERKS_5 preprocessor definition is used to modify behavior in 4/5.  In version 4 the list contains pointers, and in version 5 it contains weak pointers:  Version 4 could get complicated because the entity would have to store an iterator to remove it from this list, in case it is deleted before this routine is called inside the World::Update() function.  In version 5 we just check if the weak pointer is valid and skip it if the entity was deleted.  At the end of the routine, the whole list is cleared, so there are no worries about deleting each iterator.

bool World::UpdateNavigation()
{
	//Invalidate navmesh tiles that intersect moved entities
	for (auto it = updatenavigationlist.begin(); it != updatenavigationlist.end(); it++)
	{
#ifdef LEADWERKS_5
		auto entity = (*it).lock();
		if (entity == nullptr) continue;
#else
		auto entity = (*it);
#endif
		entity->updatenavigationneeded = false;
		for (int i = 0; i<entity->relevantnavtiles.size(); i++)
		{
			entity->relevantnavtiles[i]->Invalidate();
		}
		entity->relevantnavtiles.clear();
		if (entity->navigationmode) navmesh->InvalidateTiles(entity->aabb);
	}
	updatenavigationlist.clear();

	//Update the navmesh
	return navmesh->Update();
}

Weak pointers make game programming easier and less susceptible to errors in Leadwerks Game Engine 5.

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.

MV5BYjBhMjY5YjUtYmYwNC00MmFhLTkxMTktYzMzMTA5ODZmNDRiL2ltYWdlXkEyXkFqcGdeQXVyNjY1OTEzMzc@._V1_.thumb.jpg.e5ac13f4d4d3ac3fdb4d0cfe60a92cdd.jpg

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

fail.png.347760c26276528eb8485ca387b945f9.png

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?:

Image1.jpg.cc444a79c4bd2362b7f852164c59c61d.jpg

At this point I was all like:

3511d9767c96ce281df5a9a69f6b88ed5654af83fe3ca8a1b99b2d225d1ad268.jpg.c43ee36c3195a4028f289f711b9fe7af.jpg

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:

19957008_10155303550526183_4315704447448976980_o.jpg.c50007531937828de42285fa53b28b89.jpg

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.

ricklol.jpg.951adbf4dd4beb02b2bea1302842905e.jpg

I contacted SpreadShirt.com about the matter and received the following explanation:

Quote

Hello Josh,

Thank you for your email.

Unfortunately this item is currently out of stock in some sizes and colors.

Our product manager has not informed us of when more stock will be arriving as the company American Apparel is currently going through a transition phase while ownership is transferred to the brand Gildan.

The sizes and colors will be available online again once we've received the stock from the new supplier.

Please feel free to contact us if you have any further questions.

Sincerely,

Ariel
Customer Service

The first and most pertinent question is why is the little mermaid working in customer service for SpreadShirt?

character_disneyprincess_ariel_262253c9.jpg.39101c734b08bd50c40ef51132a7ffd0.jpg

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.

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.

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.

Admin

 Leadwerks Software today announced the release of version 4.4 of their topselling game engine on Steam.  This version adds a new GUI system, support for inverse kinematics, and enhanced visuals.  The free update goes out today to over 20,000 paid users on Steam.

Leadwerks Game Engine 4.4 sees the introduction of Leadwerks GUI, a system for creating resolution-independent in-game menus.  Custom GUI elements can be created with Lua script, or an item can be selected from a number of pre-built GUI scripts including buttons, tabbed panels, slider controls, and more.  Leadwerks GUI is designed for the future, with support for 4K and 8K displays, as well as VR menus of any resolution.

Screenshots_screenshot261.thumb.jpg.7dcfff75c87b0cbb2de0b29a6cc353eb.jpg

Inverse kinematics are now supported, with two new joints that provide fine control over the orientation of physics bodies.  These can be used to control the end effector in an IK setup, or for precise control of moving objects.  The Leadwerks physics system has been updated to the latest Newton Dynamics 3.14.

Screenshots_screenshot260.thumb.jpg.4f38884321d5acfcc722e70cb98ef1fd.jpg

Post-processing effects have been updated to enhance visual quality.  Bloom, HDR iris adjustment, SSAO, and fog have all been updated for improved graphics.  A new shader has been added to the vegetation system that scattering millions of rocks across a landscape to make terrain more detailed and realistic.

Screenshots_screenshot259.thumb.jpg.7fe75bab83a6be87c1aa01f197efa93a.jpg

The Leadwerks learning materials have been converted into an all-new documentation system.  This allows easier searching and browsing of tutorials, API documentation, and examples.  A new series of tutorials has been added to teach the basics of C++ programming, in addition to the existing lessons on Lua scripting.

Leadwerks Game Engine can be purchased at a discount during the Steam summer sale.  All Leadwerks games have Steamworks support integrated out-of-the-box and are ready to publish through Steam Direct.

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:

hqdefault.jpg.cf269ad4a7036e236bea5c2c548370bf.jpg

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.

screenshot87.thumb.jpg.7b4c13865c90793491607d1c51d97abc.jpg

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:

screenshot86.thumb.jpg.935126777fe6969577af35ed90a56ce0.jpg

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

screenshot88.thumb.jpg.06759dc664b3ddc4a83fccb708829251.jpg

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.

screenshot89.thumb.jpg.ff8a556c6495748cf6f2a8e29285cf03.jpg

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.

6E2A043636BB6DDB7A8B92E833683FF8D279AE95

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:

EB844FB4B1F76913614C81770FA39B54448A32F4

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.

D7E8DF18C44CF0FBA92288DE0A88DE68AD547DF5

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.

screenshot79.thumb.jpg.d0b7479732e9eebd8327919c94417f97.jpg

screenshot78.thumb.jpg.3b7da31d6aeddeb6bddb70da3203002d.jpg

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

02D895D6B1E7A266EFCED9C564FDBF150809BF36

543598A48A470EB317E09367A88616C7727751D3

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.

50F8CF2FD29EC0F5E43CAB6BB908CA43BF17BDD3

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.

screenshot80.jpg

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.

screenshot81.thumb.jpg.dcffd712d42c95fac07d69de36086904.jpg

You can try Leadwerks Game Engine 4.4 right now by opting into the beta branch on Steam.

Josh

This tutorial demonstrates how to create a high-quality skybox for Leadwerks Game Engine using Vue.

Download

Required Third-Party Programs

Loading the Example

Run Vue and select the File > Open menu item.  Extract the zip file above and open the file "Cloudy Blue Skies.vue".

ccs-1-0-90786400-1443279174_thumb.jpg

Atmosphere and Clouds

You can modify the appearance of the sky with the Atmosphere Editor. Select the Atmosphere > Atmosphere Editor menu item to open this dialog.

ccs-1-0-74507200-1443279390_thumb.jpg

The clouds tab lets you adjust various properties of the cloud layers and add new ones. Skyboxes look best with multiple layers of different kinds of clouds, so don't expect to get the perfect look with just one layer.

ccs-1-0-34038100-1443279521_thumb.jpg

The load button to the right side of the cloud layer list will let you select from a wide range of different cloud types.

ccs-1-0-33700000-1443279536_thumb.jpg

Experiment with different cloud layers to get the look you want. The "Detail amount" setting in particular will really enhance the image, but don't overdo it. You can right-click and drag the mouse to look around in the main panel, so be sure to take a look around to see how the clouds affect the entire sky.

Lighting

To edit the sunlight properties in Vue, select the sunlight object in the World Browser on the right side of the main window.

ccs-1-0-12557900-1443280022.jpg

You can match the exact rotation of the default sunlight angle in Leadwerks to make your skybox line up exactly to the scene lighting. The default sunlight angle in Leadwerks is (55,-35,0). In Vue this corresponds to the values (145,0,215). To get these values we add 90 degrees to the pitch and subtract the yaw from 180. Note in Vue the order of the yaw and roll are switched.

ccs-1-0-03849300-1443279932.jpg

The sun color is very important for the overall composition of our image. In real life we're used to seeing a very high range of light levels in the sky. Computer monitors cannot represent the same range of colors, so images can easily become washed out and lose details. We want to adjust the sun color so we can get the most detail within the spectrum of a 32-bit color display. Like the rotation, the sun color can be modified in the sun properties.

ccs-1-0-14094400-1443280269.jpg

If the sunlight color is too bright, the image will be overexposed and the cloud shape will become washed out.

ccs-1-0-47473100-1443280650_thumb.jpg

If the sunlight is too dark, it will look gray and desaturated.

ccs-1-0-24631500-1443280414_thumb.jpg

The right sun brightness will give a balanced look between bright spots and shadows. This is the part in the process that requires the most artistic sense. It's a good idea to look at some screenshots or photos for comparison as you adjust your settings.

ccs-1-0-79686900-1443284709_thumb.jpg

You will get quite a lot of variation in brightness across the sky, so be sure to take a look around the whole scene when you are adjusting lighting. You can also adjust the main camera's exposure value to vary the brightness of the rendered image.

If you want to hide the sun from view you can do this by setting the "Size of the sun" and "Size of the corona" settings both to zero under the "Sun" tab in the Atmosphere Editor.

Exporting

To export our skybox the exporter module must be installed. Select the File > Export Sky menu item and the export dialog will appear.

ccs-1-0-61001600-1443280903_thumb.jpg

The "Supporting geometry" setting should be set to "Cube". Set the X value to 1536 and the Y value to 2048. This controls the width and height of the saved image. When we press the OK button, the sky will be rendered out into a vertical cube cross with those dimensions. Each face of the cubemap will be 512x512 pixels.

ccs-1-0-89630000-1443281014_thumb.jpg

By default, your skybox will be exported to the file "Documents\e-on software\Vue 2015\Objects\Atmosphere.bmp". The exported cube cross is a nonstandard orientation. To convert this into a cubemap strip ready to load in Leadwerks, use the FixVueCubemap.exe utility posted above.  Drag your exported image file onto the executable, and it will save out a cube strip in PNG format that is ready to load in Leadwerks.

ccs-1-0-34054400-1443281151_thumb.jpg

Importing

To import your skybox into Leadwerks, just drag the cubemap strip PNG file onto the Leadwerks main window. Open the converted texture from the Leadwerks Asset Browser. Set the texture mode to "Cubemap", uncheck the "Generate Mipmaps" checkbox, and check the clamp mode for the X, Y, and Z axes. Press the Save button to reconvert the texture and it will appear in a 3D view.

ccs-1-0-76222900-1443281537_thumb.jpg

You can use the skybox in the current map by selecting it in the scene settings.

ccs-1-0-23907200-1443281670.jpg

Disabling mipmap generation will reduce the size of a 1024x1024 cubemap from 32 to 24 mb. Due to the way the image is displayed, mipmaps aren't needed anyways.

Final Render

For the final render, we want each cubemap face to be 1024x1024 pixels. However, we can get a better quality image if we render at a larger resolution and then downsample the image. In Vue, select the File > Export menu item again to open the export dialog. This time enter 6144 for the X value and 8192 for the Y value. Don't press the OK button until you are ready to take a long break, because the image will take a long time to render. When you're done you will have a huge image file of your skybox with a 2048x2048 area for each cubemap face.

If we resize the image file in a regular paint program, it will create seams along the edges of the cubemap faces. Instead, we're going to pass a parameter to the conversion utility to tell it to downsample the image by a factor of 50%. The "downsample.bat" file is set up to do this, so just double-click on this to launch the executable with the correct parameters. The resulting cubemap strip will be 6144x1024 pixels, with a 1024x1024 area for each face. However, due to the original rendering resolution this will appear less grainy then if we had rendered directly to this resolution.

Import this texture into Leadwerks as before and enjoy your finished high-quality skybox. Always do a low-resolution pass before rendering the final image, as it can take a long time to process.

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.

Josh

In-Game Menu

Along with Leadwerks GUI, Leadwerks 4.4 adds an in-game menu that is available with the default Lua scripted game.  You can use this to allow your users to adjust settings in the game, or provide a more sophisticated way to quit the game than simply pressing the escape key.

Image1.thumb.jpg.3c376f0b4bad8d43e3c7f6608fbcf18a.jpg

The default window size has been changed to 1280x720 when run from the editor.  Your game will now run in fullscreen mode by default when it is launched outside the editor.

All of these changes are contained in the Main.lua and Menu.lua scripts, and can all be modified or removed to your heart's content.

A full build is available now on the beta branch.