Jump to content

Blogs

Luawerks Updated

Luawerks has been updated to 1.2.7, making some small adjustments and fixes to the system. If you have previously purchased Luawerks, this update is available for free on the Leadwerks Marketplace. Please note: Luawerks is not ready for Leadwerks 4.6 and it's recommended to use Luawerks with Leadwerks 4.5. Following changes include: Added an optional Map selection menu. "New Game" will default call a panel in which all the maps in your Maps directory will be listed, Note: This will only work if the maps are not included in your zip as raw Lua functions are called. This will also not work if the Lua sandbox is enabled. Re-located error.mdl to a sub directory "Models/Developer" Added widget.mdl and axis.mdl for assistance for visualizing matrix results. When in-game, "Exit to Menu" will show instead of "Quit." This will release the world and the player will be returned to the main menu. Arrange locations of various functions. The names remain the same. Added various functions needed for this update.   About Luawerks Luawerks is a Lua framework for video games developed on the Leadwerks Game Engine. It supplies developers with additional functions, handles the game loop and allows them to debug their code with a developers console. With all that out of the way, you can focus on making your game fun! You can purchase Luawerks from the Leadwerks Marketplace for $9.99. For documentation and bug reporting, please visit the GitHub page.

reepblue

reepblue

Loading GLTF Files

With an understanding of JSON for Modern C++ (an excellent library) I was able to dive into the new GLTF 2.0 file format and create a model loader. Here's what I've got so far: Although the file format is JSON-based, vertex and indice data is loaded from either a binary .bin file, or packed away at the end of the ASCII file. You can open GLTF files in NotePad and edit them, but GLB files are a mix of ASCII and binary. The model above loads in about 16 milliseconds. I did find some big shortcomings in the file format, however, and I am going to list them here. 1. It's needlessly complicated The way data is stored is ridiculously complex. Accessors, bufferviews, and buffers? Why not just provide an array of values. The designers of this spec must have been on some serious drugs when they came up with this. I understand why they did this, they are trying to make resource reuse as efficient as possible, and allow the file format to dictate how data is stored internally, because they are writing this from the perspective of a graphics API designer. Guess what? NO ONE is going to use it like that. Everyone is just going to load the data into their own mesh structures and send it to the GPU however they want. Allowing the file format to control how data is stored internally in the engine is a level of stupid I can barely conceive of. Why does this matter? Well, if you are trying to get people to adopt your file format you make it as simple as possible. Collada was a disaster, and I personally know one programmer who nope'd out of there after taking one look at the GLTF specification due to bad experiences with Collada. 2. It's not a model file format GLTF is a scenes (plural, we'll get to that later) file format and it's being shoehorned into something else. There is no guarantee of a single top-level node, so my loader has to create a root model to parent multiple nodes to if they are encountered. 3. It's not meant for games GLTF stands for "GL Transmission Format" which sounds like an STD but is meant for serving up 3D scenes in a web browser. Thus, it uses JPEG and PNG files for textures and does not support the most common texture format for games, DDS. It doesn't even support Khronos' own KTX texture format that no one uses. Why does this matter? Widespread support for GLTF is one of the compelling reasons to use it. When you select a GLTF file in Windows 10 Explorer, it shows the model in a 3D preview window. Really nice! But if the model stores a DDS texture, it will fail to load! (It still loads successfully in Microsoft's 3D object viewer included in Windows 10.) I plan on using DDS textures in the new engine, but if all our 3D models won't display in Explorer, why use the format? There's also no support for or concept of material files stored in external files. All materials are packed into the model file. I'm not sure how we are going to handle this. 4. It even sucks at what it's supposed to be The design of the GLTF format is a giant catch-22. Check this out: All model and material data is stored in the file format, and there is no loading of models from external files (like other GLTF files). But it's a scene file format, right? Most games have more than one setting. You don't want to have all your models copied to all your different scene files redundantly. GLTF solves this by including multiple scenes in the file, with a value to indicate the first "map" to load. So all your models, textures, and scenes are getting packed into a single file! Here's the kicker: There is no support for model compression, and this is supposed to be a file format for sending to web browsers. No problem, let's just use some ZIP compression to compress the whole file, right? Ha! Now you have to decompress ONE GIANT FILE containing EVERY MODEL AND SCENE used in your ENTIRE GAME just to load one scene! The concept of having editable asset files and a separate file format for the published game is straight out of 1994. We're not going back to WAD files and we're not going to pack all our game content into one giant uncompressed file. I actually have a soft spot for WAD files. Leadwerks 6? But Waitaminute... Okay, so those criticisms aside, it's still pretty good. We have a fast-loading open-source 3D model format with widespread support. It's designed with the concept of PBR materials built-in. And with extensions, this file format that was not designed for game models is being shoehorned into a standard that is already ahead of FBX. So in the new engine we are going to try to use GLTF as the main model format, not just as an intermediate format we convert models from. There are still some unanswered questions that will be challenging to resolve, but I think this is going to give us the best workflow in the future. Oh, and GLTF destroys OpenGEX based on one simple factor: binary data. You do not want to load vertex arrays from text as it will cause very slow load times. The whole concept of an "exchange format" is pretty dumb. It never works and it isn't what people want. People want an editable game-ready format, and GLTF while not perfect is pretty close to that.

Josh

Josh

Driving car experience with 4.6

That's what I obtained for driving experience with 4.6. For me it's ok, just the distance of the wheels to the cars are growing too much while getting speed. Sounds have to get upgraded too   I add a video that show car driving possibilities in the down-scaled world I want to create with an own scaled character controller. The idea is to obtain with this a bigger world lol So everything is under scaled, the car too. It's not GTA 6 but it's very ok for what I expect because I like when the car you drive is shaking on the road, as too much polished is not realistic enough, as you could not feel the ruggedness of the world you are playing in . I think the cars in gta 5 are definitvly too polished as you cannot reverse them for example, making the game a way too sanitized.

             

Marcousik

Marcousik

Fun with JSON

I realized there are two main ways a plugin is going to be written, either as a Lua script or as a DLL. So I started experimenting with making a JSON file that holds the plugin info and tells the engine where to load it from: { "plugin": { "title": "Game Analytics", "description": "Add analytics to your game. Visit www.gameanalytics.com to create your free account.", "author": "© Leadwerks Software. All Rights Reserved.", "url": "https://www.turboengine.com", "library": "GameAnalytics.dll" } } { "plugin": { "title": "Flip Normals", "description": "Reverse faces of model in Model Editor.", "author": "© Leadwerks Software. All Rights Reserved.", "url": "https://www.turboengine.com", "scriptfile": "FlipNormals.lua" } } I like this because the plugin info can be loaded and displayed in the editor without actually loading the plugin. I also wanted to try using a JSON file to control script properties. For example, this file "SlidingDoor.json" goes in the same folder as the script and contains all the properties the engine will create when the script is attached to an entity: { "script": { "properties": { "enabled": { "label": "Enabled", "type": "boolean", "value": true, "description": "If disabled the door will not move until it is enabled." }, "distance": { "label": "Distance", "type": "float", "value": [1,0,0], "description": "Distance the door should move, in global space." }, "opensound": { "label": "Open Sound", "type": "sound", "value": null, "description": "Sound to play when door opens." }, "closedelay": { "label": "Close Delay", "type": "integer", "value": 2000, "minvalue": 0, "description": "The number of milliseconds a door will stay open before closing again. Set this to 0 to leave open." } } } } I like that it is absolutely explicit, and it allows for more information than the comments allow in Leadwerks 4. There is actually official tools for validating the data. The json data types map very closely to Lua. However, it is more typing than just quickly writing a line of Lua code. While we're at it, let's take a look at what a JSON-based scene file format might look like: { "scene": { "entities": [ { "type": "Model", "name": "main door", "id": "1c631222-0ec1-11e9-ab14-d663bd873d93", "path": "Models/Doors/door01.gltf", "position": [0,0,0], "euler": [90,0,0], "rotation": [0,0,0,1], "scale": [1,1,1], "matrix": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], "mass": 10, "color": [1,1,1,1], "static": false, "scripts": [ { "path": "Scripts/Objects/Doors/SlidingDoor.lua", "properties": { "distance": [1,0,0], "movespeed": 5 } }, { "path": "Scripts/Objects/Effects/Pulse.lua" } ] } ] } }  

Josh

Josh

Plugins Expanded

This month I was working on a lot of NASA projects using Leadwerks. My goal with this is to build up enough business that I can start hiring more people to help. It's nice to make money using my own tools, and it gives me a lot of ideas for the new engine. This morning I felt like experimenting with the design of plugins in the new editor a bit. I came up with two types of plugins. One will add an new menu item to the model editor that flips the normals of the current model. The ProcessEvent() function intercepts a GUI event in the editor and performs new functionality: Plugin.title = "Flip Model Normals" Plugin.description = "Reverses the faces of all triangles in the model editor." function Plugin:Load() local menu = Editor.modelEditor.menu:FindMenu("Plugins") if menu~=nil then self.menuitem = menu:AddItem("Flip Normals") end end function Plugin:Unload() if self.menuitem~=nil then self.menuitem:Free() end end function Plugin:ProcessEvent(event) if event.id = EVENT_MENUACTION then if event.source = self.menuitem then if Editor.modelEditor.model~=nil then self:FlipNormals(Editor.modelEditor.model) Editor.modelEditor:Redraw() return false end end end return true end function Plugin:FlipNormals(model) local n,k,lod,mesh,i,a,b,c for n=0,#model.lods do lod = model.lods[n] for k=0,#lod.meshes do mesh = lod.meshes[k] mesh:Unlock() for i=0,#mesh.indices/3 do a = mesh.indices[i*3+0] b = mesh.indices[i*3+1] c = mesh.indices[i*3+2] mesh.indices[i*3+0] = c mesh.indices[i*3+2] = a end mesh:Lock() end end end Another type of plugin adds some new functionality to your game. This one is different because it loads a function from a dynamically linked library and passes the Lua state to a function: In this case, I decided to try separating analytics off into its own plugin, simply because it is a self-contained system that doesn't interfere with the rest of the engine: Plugin.name = "Example" Plugin.title = "Example Plugin" Plugin.description = "This is an example plugin." function Plugin:GetPath() local ext="" if GetOS()=="Windows" then ext = "dll" elseif GetOS()=="Linux" then ext = "so" elseif GetOS()=="MacOS" ext = "dylib" end return self.name..ext end function Plugin:Load() local f = package.loadlib(self:GetPath(),"Load") if type(f)=="function" then f(GetLuaState()) end end function Plugin:Unload() local f = package.loadlib(self:GetPath(),"Unload") if type(f)=="function" then f() end end The source code for the dynamic library would be something like this: #include "GameAnalytics.h" bool Load(sol::state* L) { L->set_function("EnableAnalytics", EnableAnalytics); } void Unload() { } bool EnableAnalytics(const bool state) { return true; } Once the plugin is loaded, the Lua state would be passed to the DLL (or .so, or .dylib) and the new commands would get inserted into the Lua state. So what does a plugin consist of? A ,lua or precompiled .luac file is required. If the plugin uses C++ code, a .dll, .so, and .dylib are required. Optional source code and project(s) for the dynamically linked library. And what can a plugin do? Add new features to the editor. Add new Lua commands to use in your game. That's all for now.

Josh

Josh

Leadwerks Game Engine 4.6 Beta Update

The beta branch on Steam is now updated. Version 4.6 introduces peer-to-peer multiplayer capabilities with a new multiplayer game template. Check out the new features in the documentation: P2P Lobby Voice The physics library is updated to the latest version of Newton. The editor has some enhancements as well: Model editor view range is calculated from model extents, so if you load a model that is huge it won't be invisible. Model editor displays number of limbs as well as vertices and triangles. Settings file is now saved any time changes are made in the project manager or the options editor. Menu added for Leadwerks Marketplace. Paid Workshop items are no longer accepted. Menu item added for Discord chat. Please test this out and report any errors in the bug reports forum, as this is pretty close to the final version 4.6.

Admin

Admin

Vehicle Development

I've made progress with the new vehicle system and it is shaping up nicely. The vehicle wheels consist of a slider joint with a spring (the strut) connected to a pivot, connected to the wheel by a hinge joint (the axle). If the wheel can be steered, an additional pivot is inserted between the strut and axle, with a motorized hinge to control steering. There were two big problems in addition to this that need to be solved in order to make a vehicle that is stable at high speeds. First, the mass matrix of the tires needs to be spherical. The mass matrix is the distribution of mass across an object. A brick and a 2x4 piece of lumber probably have about the same mass, but have a different mass matrix. Therefore the brick should spin more easily than the lumber. If you don't make the mass matrix for the tires spherical you will get bad wobbling at high speeds, like this video shows: When the mass matrix is fixed this problem goes away. The vehicle gets up to 90 MPH, and although there are other issues, there is no tire wobble. The other issue that needs to be solved can be seen in the video above. At high speeds the tire collisions become inaccurate and the vehicle bounces a lot. We need to replace the default collision with a volume raycast coming from the top position the wheel can sit on the shock, going down to the extended position of the strut. This is the part I haven't done yet, but I know it can be done. I think the new vehicle system will offer a lot of flexibility and possibilities for future features since it is mostly made with the standard physics features.

Josh

Josh

Vehicle WIP in Leadwerks 4.6

Here's a look at the new vehicle system that is being developed. The API has been simplified so you simply create a vehicle, add as many tires as you want, and start using it. The AddTire() command now accepts a model and the dimensions of the tire are calculated from the bounds of the mesh. class Vehicle { int AddTire(Model* model, bool steering=false, const float spring=500.0f, const float springrelaxation = 0.1f, const float springdamping = 10.0f); void SetGas(const float accel); void SetBrakes(const float brakes); void SetSteering(const float angle); static Vehicle* Create(Entity* entity); }; A script will be provided which turns any vehicle model into a ready-to-use playable vehicle. The script searches for limb names that start with "wheel" or "tire" and turns those limbs into wheels. If they are positioned towards the front of the car, the script assumes the wheels can be turned with steering. You can also reverse the orientation of the vehicle if the model was designed backwards. There is one big issue to solve still. When a vehicle drives on flat terrain the tires catch on the edges of the terrain triangles and the whole vehicle bounces around badly. I'm looking into how this can be solved with custom collision overrides. I do not know how long this will take, so it might or might not be ready by Christmas.

Josh

Josh

Peer-to-peer Networking in Leadwerks Game Engine 4.6

I'm wrapping up the new multiplayer capabilities for Leadwerks 4.6, and I am very excited about what this offers developers. We saw previously how the lobby system is used to create or join games, and how to retrieve Steam IDs for messaging. Once we have joined a game we can start sending messages with the P2P::Send command, which includes a few overloads: static bool P2P::Send(uint64 steamid, const int messageid, const int channel = 0, const int flags = 0); static bool P2P::Send(uint64 steamid, const int messageid, std::string& data, const int channel = 0, const int flags = 0); static bool P2P::Send(uint64 steamid, const int messageid, Bank* data, const int channel = 0, const int flags = 0); static bool P2P::Send(uint64 steamid, const int messageid, Stream* data, const int channel = 0, const int flags = 0); static bool P2P::Send(uint64 steamid, const int messageid, const void* data, const int size, const int channel = 0, const int flags = 0); Each message has an ID and can be followed by additional data in the form of a string, bank, or stream. Voice chat is handled automatically, but the rest is up to you to decide what data the messages should contain. I provide examples for text chat, joining and leaving a game, and movement. Receiving messages from other players is extremely simple: static Message* P2P::Receive(const int channel = 0); The message object has information contained within it: class Message { public: int id; Stream* stream; uint64 userid }; We can evaluate messages based on their ID. For example, here is a text chat message being received: auto message = P2P::Receive() if (message) { if (message->id == MESSAGE_CHAT && message->stream != nullptr) { Print(message->stream->ReadString()); } } A new multiplayer game template will be included, with out-of-the-box support for text and voice chat, public servers, and player movement. You can download the test app and try it out here: Thanks to everyone who has helped me test this!

Josh

Josh

Discord Server is Back!

After using it for a few weeks, I previously deleted our Discord server for the following reasons: Community activity was being taken from our site to Discord. They weren't really providing anything of value. There have been some problems with our Cometchat integration lately. I have noticed new messages are not popping up like they should, and selecting the link to view a user's profile does not work. So I set to fixing these issues. Upgrading CometChat to the latest version my account has access to did not solve these issues, and it introduced a new problem where the chat bar was not being hidden when the user logs out. The company has changed their pricing to a much more expensive subscription plan. With minimum features I would be paying $50 monthly, and I still don't know if these issues are fixed in the latest version. More expensive versions also support audio and video chat, but in the last when I have tried these they have always been dodgy. In order to add support for these additional features, which I don't know have improved, it would cost $99/mo. total. At this point I decided that Discord is in fact providing a valuable service for us and I am going to rely on it from now on. Our built-in chat system is now disabled and I encourage you to join me on Discord. This time it is here to stay: https://discord.gg/ukkSVnF

Josh

Josh

Introducing Voice Chat in Leadwerks 4.6

The next update will include a new networking system with built-in voice chat for multiplayer games. Voice communication is built into your game and can be enabled simply by turning it on when a specific key is pressed: void Voice::SetRecording(window->KeyDown(Key::T)) You can selectively filter out users so your voice only gets sent to your own team or to a specific player: void Voice::SetFilter(const uint64 steamid, const bool state) When another player sends their voice data to you, the engine will automatically receive and play the sound. You can also retrieve a sound source for a specific player and make adjustments to it. This can be used to enable 3D spatialization and position the sound source where the player is, for VR voice chat. Source* Voice::GetPlayerSource(const uint64_t steamid) When I first implemented this the sound was sometimes choppy. I added some automatic adjustment of the pitch to slow down the sound a bit if new data is not received yet, and to speed it up if it falls too far behind. This seems to work really well and I'm sure it must be a common technique in applications like Twitch and Skype. A new multiplayer game template will be provided that shows how to set up the framework for a multiplayer game. Here's a video preview of the voice communication in action.  

Josh

Josh

Introducing the Lobby System in Leadwerks 4.6

The new Lobby system in Leadwerks 4.6 allows you to create a public listing of your multiplayer game for others to join. This uses the Steamworks library. You can tap into the Steamworks lib by piggybacking on the default "SpaceWar" example game, which uses the Steam app ID 480. This is set up by default when you create a new Leadwerks project. Now you might think of a lobby as a place where people hang out and chat before the game starts. You can treat it like this, but it's best to keep the lobby up as the game is running. This will allow other people to find the game to join, if it isn't full or if someone leaves the game. So a lobby is better described as a publicly advertised game others can join. Creating a new lobby to advertise our game is easy. We just call a command to create it, and then we will set two string values. The game title is set so that we can later retrieve only lobbies that are running this particular game. (This should be done if you are using the SpaceWar app ID instead of your own Steam ID.) We also add a short description that can display information about the game. Lobby* mylobby = Lobby::Create(); mylobby->SetKey("game", "MyGameTitle"); mylobby->SetKey("description", "Here is my lobby!"); It's also easy to retrieve a list of lobbies: int count = Lobby::Count(); for (int i = 0; i < count; ++i) { Lobby* lobby = Lobby::Get(i); } We can use GetKey() to look for lobbies that are running the same game we are: if (lobby->GetKey("game") == "MyGameTitle") We can retrieve the owner of the lobby with this command that will return a Steam ID: uint64 steamid = lobby->GetOwner(); Once you have that Steam ID, that is all you need to start sending messages through the new P2P networking system. More on that later. And we can also retrieve a list of all members in the lobby: int count = lobby->CountMembers(); for (int k = 0; k < count; ++k) { uint64 steamid = lobby->GetMember(k); } After a lobby is created, it will have one member, which will be the same Steam ID as the owner. Joining a lobby is simple enough: if (lobby->Join()) System::Print("Joined lobby!"); And leaving is just as easy: lobby->Leave() Now here's the magical part: When the owner of the lobby leaves, the lobby is not destroyed. Instead, the system will automatically choose another player to become the owner of that lobby, so the multiplayer game can keep going! The lobby will not be destroyed until everyone leaves the game.

Josh

Josh

Leadwerks Game Engine 4.6 Beta Available

A new build is available on the beta branch. This changes the model picking system to use a different raycasting implementation under-the-hood. Sphere picking (using a radius) will also now correctly return the first hit triangle. You will also notice much faster loading times when you load up a detailed model in the editor! Additional parameters have been added to the Joint::SetSpring command: void Joint::SetSpring(const float spring, const float relaxation = 1.0f, const float damper = 0.1f) The classes for P2P networking, lobbies, and voice communication have been added but are not yet documented and may still change.

Josh

Josh

Digital Reorganization

It's nice to have my big monster computer back, and everything is just the same as I left it. I have a triple-boot machine with Windows 10 and both 32 and 64 bit versions of Ubuntu 16.04. This is easier than trying to set up multi-arch compiling on one install of the OS, though I look forward to the day I no longer have to bother with it. I'm running out of hard drive space on my Windows 10 500 GB SSD so I ordered a 1 TB SSD, and I plan to transfer the 500 GB SSD into my laptop to replace the small SSD it came with. The laptop also contains a terabyte HDD, which was supposed to function as an add-on storage drive, but I am going to install Ubuntu on that. Then I will be able to develop for Windows and Linux on the go, wherever I am. The only thing that would be better than this would be a triple-boot Macbook Pro (maybe someday). Actually, that wouldn't be better. A 17" screen is something I can actually work on, and a 15" screen is no good for programming. So this is the best there is. A 3 TB external HD is being used to back up the Amazon S3 bucket (nearly 60 GB now!), and I copied the entire contents of the 32-bit Ubuntu install onto it as a backup. There was 20 or so "special files" the system could not copy so I don't know if it a true backup, but I think I will be okay. I am attempting to do the same with the 64-bit install files running from the 32-bit OS, but so far I keep getting errors during the copy process. I know you can make an image of the disk, but the disk image is the full size of the hard drive, not just the files it contained, and frankly since it is Linux I expect something else will break in the restore process, so I am not worrying about that. I'm also porting our SVN repositories over to a new service that offers more storage space, although it is quite a lot more expensive. My local Leadwerks working copy has been reverted back to the exact version that was used to release the last build. I'm going to go through manually and re-insert the changes I want from the last six months, because most of the work done was on the new engine. I think there will be less mistakes if I do it this way, since the source got chopped up a lot with #ifdef statements before I finally broke Turbo off from Leadwerks and put it in its own repo. The next Leadwerks update will include bug fixes, a new vehicles system, and peer-to-peer networking through Steam.

Josh

Josh

A Crate and Thoughts.

It's been a while since I've updated anything regarding this project. Last time, I left off with saying that I wish to focus on the art assets first due to the engine transition and how I'm going to need models regardless if it's Leadwerks and/or Turbo.  This weekend I've decided to model the very first model - The box. Boxes are very common with puzzle games, so it was ideal to get this model done and over with. There were a few things I wanted. The puzzle box must be perfectly square and it has to break if it gets crushed or something. In the past, I would have went with some science fiction box but it was hard to make gibs for it and if you needed it bigger or smaller for whatever reason, the model never looked right outside it's native size. So I decided to go with a normal wooden crate. It's simple, looks good at any size, and it can break with the gibs being easy to make. I first went online to see how other modelers went with making a crate model, but I mostly got low poly models with the normal and speculator maps doing the detail work. This makes sense if crates are just details for a map, but I didn't think this was appropriate for a model used for gameplay. Normal maps tend to wash out under some lighting conditions and scaling can ruin it too. So I ended up making a top and bottom part and the four sides are the same. I meshed in any bevels and it turned out nice. I'm not sure if the UV is done, but I've baked an AO map for now.  I would have did the texture map with it, but I'm currently not sure how to go forward on that. I like to model on my Ubuntu machine and the best raster program is gimp. Although I'm getting used to Gimp, I feel I'm more efficient with Photoshop. My original idea was to use free and open software that's multi-platform so anyone could open the raw assets. Gimp as a PSD importer, but I ran into issues loading some old PSDs I had. I think I'm going to experiment with exporting as older versions of the PSD format. Another thing I've come across was a neat program called Materialize, which allows you to build normal, speculator, metallic, rough, and height maps off of an image. I haven't tried it yet, but it looks useful since Turbo is going to do a PBR render. Sad part is it's Windows exclusive so I can't use it on my Ubuntu machine nor my iMac. (Which I was considering using it as my art machine.) I think I should establish a proper workflow before I continue to be fair. I've started other models like the button and the puzzle door. I'm not 100% satisfied with their meshes right now like I am with my crate but I'm happy to start meshing shapes again and getting ideas down.

reepblue

reepblue

Discord Server Deleted

Offloading community activity onto Discord does not benefit our community, nor does it further my goal of responsibly obtaining a Lamborghini Huracan (white with orangish leather interior, MSRP $199,000). Therefore I have deleted our Discord server. Please post on our forum here, and if I can do anything to improve the community experience, please let me know.

Josh

Josh

WE MADE IT! TECH DEMO RELEASED!

We made it, yes, sorry for this delay, but we had big problems with uploading. Now you can download demo here:  Hope you'll like it 😃 We have come a really big way of development to finally present this to you. Thanks, and join our Discord server: https://discord.gg/ZT9EU65

adams-antology

adams-antology

Basic Scene Benchmarks

I did some work optimizing the new renderer to discrete cards and came up with a new benchmark. This scene is interesting because there is not much in the scene, so the culling does not benefit much xrom multithreading, since there is so little work to do. The screen is filled with a spotlight in the foreground, and in the background a point light is visible in the distance. Both engines are running at 1280x720 resolution. This is really a pure test of deferred vs. forward lighting. AMD R9 200 Leadwerks: 448 FPS
Turbo: 648 FPS (44% faster) On this older AMD card we see that forward rendering automatically gives a significant boost in performance, likely due to not having to pass around a lot of big textures that are being written to. GEForce 1070M (Laptop) Leadwerks 4: 120 FPS
Turbo: 400 FPS (333% faster) Here we see an even bigger performance boost. However, these are two very different cards and I don't think it's safe to say yet if one GPU vendor will get more from the new engine. Intel Graphics 4000 Leadwerks 4: 67 FPS  (18% faster)
Turbo: 57 FPS This is an interesting result because integrated graphics tend to struggle with pixel fillrate, but seem to have no problems with texture bandwidth since they are using regular old CPU memory for everything. Although I think the new engine will run faster on Intel graphics (especially newer CPUs) when the scene is loaded down, it seems to run a bit slower in a nearly empty scene. I think this demonstrates that if you build a renderer specifically to take advantage of one type of hardware (discrete GPUs) it will not automatically be optimal on other types of hardware with different architectures. I commented out the lighting code and the framerate shot way up, so it definitely does seem to be a function of the pixel shader, which is already about as optimized as can be. Overall, I think the new engine will show higher relative performance the more you throw at it, but an empty scene may run a bit slower on older Intel chips since the new renderer is optimized very specifically for discrete GPUs. The parallel capabilities of discrete GPUs are relied on to handle complex lighting in a single pass, while texture bandwidth is reduced to a minimum. This is in line with the hardware trend of computing power increasing faster than memory bandwidth. This also shows that low to mid-range discrete GPUs stand to make the most gains.

Josh

Josh

Demo will be released in 11 November!

Hello, our dear subscribers! Important news - we are finally announcing the release date of Between The Realities' demo-version! It will be released in Leadwerks Game Launcher on November 11th!
In the demo version, which is a technical demonstration of our capabilities, there will be only 5 game levels, 5 types of weapons, 2 types of enemies and as well as 1/4 of the main plot.
One of these days will be released a new teaser. Stay tune!

adams-antology

adams-antology

Sample FPS Player Script in New Engine

Here is a script for a first-person player. All of this is actually working now. It's very interesting that physics, rendering, culling, and Lua game code are all executing at the same time on different threads. CPU usage in a simple scene and some physics is about 35% on a quad core processor, which is good. I think the most interesting thing here is that to break the kinematic joint used to control an object the player picks up, you simply set the variable to nil. However, I did run into one problem with the garbage collector. If you do not call collectgarbage() after setting the joint variable to nil, the joint will not get deleted, and it will still affect the entity. This is not good. I got around this by placing a call to collectgarbage() immediately after getting rid of the joint, but I don't think I like that.  I could just make the engine call the Lua garbage collector once before the world update and once before the world render. Originally, I thought Lua would be a big problem for VR applications but since the Lua game code gets its own thread, with a maximum execution time of about 16 milliseconds just for your game code, that is plenty of time to be wasteful with the GC, and I think we will be just fine even with high-performance VR games! Remember, the rendering thread runs at 90 hz but the game logic thread only runs at 60 or even 30 hz, so it's no big deal. However, I am leaning towards adding a Destroy() function that frees everything it can from an object without actually deleting it. Also note that window and context are global variables. There is no "current" window or context, so this code expect that these have been created in Lua or C++ and set as global variables. Script.lookspeed=0.1--float Script.movespeed=3--float Script.maxcarrymass=10--float Script.throwforce=100--float function Script:Start() --Player physics if self.mass==0 then self.mass=70 end self:EnablePhysics(PHYSICS_PLAYER) self.collisiontype=COLLISION_PLAYER --Set up camera self.camera=CreateCamera(self:GetWorld()) local cx=Round(context:GetWidth()/2) local cy=Round(context:GetHeight()/2) window:HideMouse() window:SetMousePosition(cx,cy) self.camerarotation=self:GetRotation(true) end function Script:Update() --Mouse look local cx = Round(context:GetWidth()/2) local cy = Round(context:GetHeight()/2) local mousepos = window:GetMousePosition() window:SetMousePosition(cx,cy) local mx = mousepos.x - cx local my = mousepos.y - cy self.camerarotation.x = self.camerarotation.x + my * self.lookspeed self.camerarotation.y = self.camerarotation.y + mx * self.lookspeed self.camerarotation.x = Clamp(self.camerarotation.x,-90,90) self.camera:SetRotation(self.camerarotation,true) --Player movement local jump=0 if window:KeyDown(KEY_SPACE) then if self:GetAirborne()==false then jump=6 end end if self:GetAirborne() then jump = 0 end local move=0 if window:KeyDown(KEY_W) then move=move+1 end if window:KeyDown(KEY_S) then move=move-1 end local strafe=0 if window:KeyDown(KEY_D) then strafe=strafe+1 end if window:KeyDown(KEY_A) then strafe=strafe-1 end self:SetPlayerInput(self.camerarotation.y, move*self.movespeed, strafe*self.movespeed, jump, false,0,0,true,0) self.camera:SetPosition(self:GetPosition(true),true) self.camera:Translate(0,1.7,0,true) --Update carried object if self.carryjoint~=nil then --Update kinematic joint local position=TransformPoint(self.carryposition,self.camera,nil) local rotation=TransformRotation(self.carryrotation,self.camera,nil) self.carryjoint:SetTarget(position,rotation) --Throw if window:MouseHit(MOUSE_LEFT) then self.carryjoint.child:AddForce(TransformNormal(0,0,1,self.camera,nil)*self.throwforce) self.carryjoint.child:EnableGravity(true) self.carryjoint=nil collectgarbage() end end --Interact with environment if window:KeyHit(KEY_E) then if self.carryjoint~=nil then --Drop carried object self.carryjoint.child:EnableGravity(true) self.carryjoint=nil collectgarbage() else --Find new object to interact with local pickinfo = PickInfo() if self.camera:Pick(0,0,10,pickinfo,0,true,0) then local entity=pickinfo.entity if entity.Activate~=nil then --Activate the object entity:Activate(self) else --Pick it up if its light enough if entity.mass>0 and entity.mass<=self.maxcarrymass then if self.carryjoint~=nil then --Drop carried object self.carryjoint=nil collectgarbage() end local p=entity:GetPosition(true) entity:EnableGravity(false) self.carryjoint=CreateKinematicJoint(p.x,p.y,p.z,entity) self.carryposition=TransformPoint(entity:GetPosition(true),nil,self.camera) self.carryrotation=TransformRotation(entity:GetQuaternion(true),nil,self.camera) end end end end end end  

Josh

Josh

Sample Sliding Door Script in Turbo / Leadwerks 5

I've successfully converter the sliding door script over to the new API. This will all look very familiar, but there are some cool points to note. The entity mass can be retrieved and set with a property as if it was just a regular variable. Underneath the hood, the engine is making method calls. I decided to prefix most field names with "slidingdoor_" to prevent other scripts from accidentally interfering. The "enabled" value, however, is meant to be shared. The Entity is the script object. No more "entity.script.entity.script..." stuff to worry about. All of this actually works in the new engine. Entity.enabled=true--bool "Enabled" Entity.slidingdoor_openstate=false--bool "Start Open" Entity.slidingdoor_distance=Vec3(1,0,0)--Vec3 "Distance" Entity.slidingdoor_movespeed=1--float "Move speed" 0,100,3 Entity.slidingdoor_opensoundfile=""--path "Open Sound" "Wav File (*wav):wav|Sound" Entity.slidingdoor_closesoundfile=""--path "Close Sound" "Wav File (*wav):wav|Sound" Entity.slidingdoor_loopsoundfile=""--path "Loop Sound" "Wav File (*wav):wav|Sound" Entity.slidingdoor_manualactivation=false--bool "Manual activate" Entity.slidingdoor_closedelay=2000--int "Close delay" function Entity:Start() self:EnableGravity(false) if self.mass==0 then self.mass=10 end --In Leadwerks 4: --if self.entity:GetMass()==0 then self.entity:SetMass(10) end -- :) self.slidingdoor_sound={} self.slidingdoor_sound.open = LoadSound(self.slidingdoor_opensoundfile) self.slidingdoor_sound.loop = LoadSound(self.slidingdoor_loopsoundfile) self.slidingdoor_sound.close = LoadSound(self.slidingdoor_closesoundfile) if self.slidingdoor_sound.loop~=nil then self.slidingdoor_loopsource = CreateSource() self.slidingdoor_loopsource:SetSound(self.slidingdoor_sound.loop) self.slidingdoor_loopsource:SetLoopMode(true) self.slidingdoor_loopsource:SetRange(50) end --if self.slidingdoor_manualactivation==false then self.Use=nil end self.slidingdoor_opentime=0 --Create a motorized slider joint local position = self:GetPosition(true) --You could also do this: --local position = self.mat[3].xyz local pin=self.slidingdoor_distance:Normalize() self.slidingdoor_joint=CreateSliderJoint(position.x,position.y,position.z,pin.x,pin.y,pin.z,self) if self.openstate then self.slidingdoor_openangle=0 self.slidingdoor_closedangle=self.slidingdoor_distance:Length() else self.slidingdoor_openangle=self.slidingdoor_distance:Length() self.slidingdoor_closedangle=0 end self.slidingdoor_joint:EnableMotor(true) self.slidingdoor_joint:SetMotorSpeed(self.slidingdoor_movespeed) end function Entity:Use() self:Open() end function Entity:Toggle() if self.enabled then if self.slidingdoor_openstate then self:Close() else self:Open() end end end function Entity:Open() if self.enabled then self.slidingdoor_opentime = CurrentTime() if self.slidingdoor_openstate==false then if self.slidingdoor_sound.open then self:EmitSound(self.slidingdoor_sound.open) end self.slidingdoor_joint:SetTarget(self.slidingdoor_openangle) self.slidingdoor_openstate=true if self.slidingdoor_loopsource~=nil then self.slidingdoor_loopsource:SetPosition(self:GetPosition(true)) if self.slidingdoor_loopsource:GetState()==SOURCE_STOPPED then self.slidingdoor_loopsource:Play() end end end end end function Entity:Close() if self.enabled then if self.slidingdoor_openstate then if self.slidingdoor_sound.close then self:EmitSound(self.slidingdoor_sound.close) end self.slidingdoor_joint:SetTarget(self.slidingdoor_closedangle) self.slidingdoor_openstate=false if self.slidingdoor_loopsource~=nil then self.slidingdoor_loopsource:SetPosition(self:GetPosition(true)) if self.slidingdoor_loopsource:GetState()==0 then self.slidingdoor_loopsource:Play() end end end end end function Entity:Disable() self.enabled=false end function Entity:Enable() self.enabled=true end function Entity:Update() --Disable loop sound if self.slidingdoor_sound.loop~=nil then local angle if self.slidingdoor_openstate then angle = self.slidingdoor_openangle else angle = self.slidingdoor_closedangle end if math.abs(self.slidingdoor_joint:GetAngle()-angle)<0.1 then if self.slidingdoor_loopsource:GetState()~=SOURCE_STOPPED then self.slidingdoor_loopsource:Stop() end else if self.slidingdoor_loopsource:GetState()==SOURCE_STOPPED then self.slidingdoor_loopsource:Resume() end end if self.slidingdoor_loopsource:GetState()==SOURCE_PLAYING then self.slidingdoor_loopsource:SetPosition(self:GetPosition(true)) end end --Automatically close the door after a delay if self.slidingdoor_closedelay>0 then if self.slidingdoor_openstate then if CurrentTime()-self.slidingdoor_opentime>self.slidingdoor_closedelay then self:Close() end end end end  

Josh

Josh

×