Jump to content

Blogs

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

Leadwerks 4.6 Development

With Christmas approaching I am now turning my attention to finishing Leadwerks Game Engine 4.6. The major features planned are peer-to-peer networking and a new vehicles system, as well as miscellaneous bug fixes. A beta build will be made available early on Steam for testing.

Josh

Josh

Turbo Game Engine Beta Update

A big update for the beta of the upcoming Turbo Game Engine is now available, adding support for VR and Lua script! VR Rendering Turbo Game Engine now supports VR rendering, with support for true single-pass stereoscopic rendering on Nvidia GPUs. Other hardware will use a double-rendering path that is still faster than Leadwerks. To turn VR on simply call EnableVR(). Controllers are not yet supported, just rendering. Lua Scripting Lua script is now supported in Turbo! The amount of classes and functions is limited, but the foundation for full Lua support is in. Here are some of the great features in the new system. No Script Object All scripts operate on the entity itself. Instead of this: function Script:Update() self.entity:Turn(1,0,0) end You type this: function Object:Update() self:Turn(1,0,0) end This is especially nice when it comes to functions that retrieve another entity since you don't have to type "entity.script" to get Lua values and functions: function Object:Collision(entity,position,normal,speed) entity:Kill() end Smart Pointers Lua garbage collection now works together with C++11 smart pointers. You will never have to deal with invalid pointers or object deletion again. There is no Release() anymore. Just set your variable to nil to delete an object: local box = CreateBox(world) box = nil If you want the object to be collected immediately, you can force a GC step like this: local box = CreateBox(world) box = nil collectgarbage() Best of all, because Lua runs on the game logic thread separate from the rendering thread, it's perfectly fine to use Lua high-performance applications, even in VR. A pause in the game execution for Lua garbage collection will not pause the rendering thread. Multiple Scripts You can add any number of scripts to an object with the AddScript command: entity->AddScript("Scripts/Object/test.lua") Scripts on Any Object (Experimental) Now all object types can have scripts, not just entities: material->AddScript("Scripts/Object/Appearance/Pulse.lua") Set and Get Script Values in C++ You can easily set and get values on any object, whether or not it has had a script added to it: entity->SetValue("health",100); Print(entity->GetNumberValue("health")); Vector Swizzle Using getters and setters I was able to implement vector swizzles. If you write shaders with GLSL you will be familiar with this convenient  feature: local a = Vec3(1,2,3) local b = a.xz --equivalent to Vec2(a.x,a.z) In Lua you can now return any combination of vector elements, using the XYZW or RGBA names. The code below will swap the red and blue elements of a color: local color = Vec4(1,0,0.5,1) local color = color.bgra Not only can you retrieve a value, but you can assign values using the swizzle: local v = Vec3(1,2,3) v.zy = Vec2(1,2) Print(v) --prints 1,2,1 Note there are presently only two engine script hooks that get called, Start() and Update(). Simpler Uber Shaders I decided to do away with the complicated #ifdef macros in the shaders and use if statements within a single shader: //Diffuse Texture if (texturebound[0]) { color *= texture(texture0,texcoords0); } This makes internal shader management MUCH simpler because I don't have to load 64 variations of each shader. I am not sure yet, but I suspect modern GPUs will handle the branching logic with no performance penalty. It also means you can do things like have a material with just a color and normal map, with no need for a diffuse map. The shader will just adjust to whatever textures are present. A C++ Turbo program now looks like this: #include "Turbo.h" using namespace Turbo; int main(int argc, const char *argv[]) { //Create a window auto window = CreateWindow("MyGame", 0, 0, 1280, 720); //Create a rendering context auto context = CreateContext(window); //Create the world auto world = CreateWorld(); //This only affects reflections at this time world->SetSkybox("Models/Damaged Helmet/papermill.tex"); shared_ptr<Camera> camera; auto scene = LoadScene(world, "Maps/start.map"); //Create a camera if one was not found if (camera == nullptr) { camera = CreateCamera(world); camera->Move(0, 1, -2); } //Set background color camera->SetClearColor(0.15); //Enable camera free look and hide mouse camera->SetFreeLookMode(true); window->HideMouse(); while (window->KeyHit(KEY_ESCAPE) == false and window->Closed() == false) { //Camera movement if (window->KeyDown(KEY_A)) camera->Move(-0.1, 0, 0); if (window->KeyDown(KEY_D)) camera->Move(0.1, 0, 0); if (window->KeyDown(KEY_W)) camera->Move(0, 0, 0.1); if (window->KeyDown(KEY_S)) camera->Move(0, 0, -0.1); //Update the world world->Update(); //Render the world world->Render(context); } return 0; } If you would like to try out the new engine and give feedback during development, you can get access now for just $5 a month. I am now going to turn my attention to Leadwerks 4.6 and getting that ready for the Christmas season. The next step in Turbo development will probably be physics, because once that is working we will have a usable game engine.

Josh

Josh

Using Multiple Entity Scripts in Turbo Game Engine

During development of Leadwerks Game Engine, there was some debate on whether we should allow multiple scripts per entity or just associate a single script with an entity. My first iteration of the scripting system actually used multiple scripts, but after using it to develop the Darkness Awaits example I saw a lot of problems with this. Each script used a different classname to store its variables and functions in, so you ended up with code like this: function Script:HurtEnemy(amount) if self.enemy ~= nil then if self.enemy.healthmanager ~= nil then if type(self.enemy.healthmanager.TakeDamage)=="function" then self.enemy.healthmanager.TakeDamage(amount) end end end end I felt this hurt script interoperability because you had to have a bunch of prefixes like healthmanager, ammomanager, etc. I settled on using a single script, which I still feel was the better choice between these two options: function Script:HurtEnemy(amount) if self.enemy ~= nil then if type(self.enemy.TakeDamage)=="function" then self.enemy.TakeDamage(amount) end end end Scripting in Turbo Game Engine is a bit different. First of all, all values and functions are attached to the entity itself, so there is no "script" table. When you access the "self" variable in a script function you are using the entity object itself. Here is a simple script that makes an entity spin around its Y axis: function Entity:Update() self:Turn(0,0.1,0) end Through some magic that is only possible due to the extreme flexibility of Lua, I have managed to devise a system for multiple script attachments that makes sense. There is no "component" or "script" objects itself, adding a script to an entity just executes some code that attached values and functions to an entity. Adding a script to an entity can be done in C++ as follows: model->AttachScript("Scripts/Objects/spin.lua"); Or in Lua itself: model:AttachScript("Scripts/Objects/spin.lua"); Note there is no concept of "removing" a script, because a script just executes a bit of code that adds values and functions to the entity. Let's say we have two scripts named "makeHealth100 and "makeHealth75". MakeHealth100.lua Entity.health=100 MakeHealth75.lua Entity.health=75 Now if you were to run the code below, which attaches the two scripts, the health value would first be set to 100, and then the second script would set the same value to 75, resulting in the number 75 being printed out: model->AttachScript("Scripts/Objects/MakeHealth100.lua"); model->AttachScript("Scripts/Objects/MakeHealth75.lua"); Print(entity->GetNumber("health")); Simple enough, right? The key point here is that with multiple scripts, variables are shared between scripts. If one scripts sets a variable to a value that conflicts with another script, the two scripts won't work as expected. However, it also means that two scripts can easily share values to work together and create new functionality, like this health regeneration script that could be added to work with any other scripts that treat the value "health" as a number. HealthRegen.lua Entity.healthregendelay = 1000 function Entity:Start() self.healthregenupdatetime = CurrentTime() end function Entity:Update() if self.health > 0 then if CurrentTime() - self.healthregenupdatetime > self.healthregendelay then self.health = self.health + 1 self.health = Min(self.health,100) end end end What about functions? Won't adding a script to an entity overwrite any functions it already has attached to it? If I treated functions the same way, then each entity could only have one function for each name, and there would be very little point in having multiple scripts! That's why I implemented a special system that copies any added functions into an internal table. If two functions with the same name are declared in two different scripts, they will both be copied into an internal table and executed. For example, you can add both scripts below to an entity to make it both spin and make the color pulse: Spin.lua function Entity:Update() self:Turn(0,0.1,0) end Pulse.lua function Entity:Update() local i = Sin(CurrentTime()) * 0.5 + 0.5 self:SetColor(i,i,i) end When the engine calls the Update() function, both copies of the function will be called, in the order they were added. But wait, there's more. The engine will add each function into an internal table, but it also creates a dummy function that iterates through the table and executes each copy of the function. This means when you call functions in Lua, the same multi-execution feature will be available. Let's consider a theoretical bullet script that causes damage when the bullet collides with something: function Entity:Collision(entity,position,normal,speed) if type(entity.TakeDamage) == "function" then entity:TakeDamage(20) end end If you have two (or more) different TakeDamage functions on different scripts attached to that entity, all of them would get called, in order. What if a function returns a value, like below?: function Entity:Update() if self.target ~= nil then if self.target:GetHealth() <= 0 then self.target = nil --stop chasing if dead end end end If multiple functions are attached that return values, then all the return values are returned. To grab multiple returned values, you can set up multiple variables like this: function foo() return 1,2,3 end a, b, c = foo() print(a) --1 print(b) --2 print(c) --3 But a more practical usage would be to create a table from the returned values like so: function foo() return 1,2,3 end t = { foo() } print(t[1]) --1 print(t[2]) --2 print(t[3]) --3 How could this be used? Let's say you had a script that was used to visually debug AI scripts. It did this by checking to see what an entity's target enemy was, by calling a GetTarget() function, and then creating a sprite and aligning it to make a line going from the AI entity to its target it was attacking: function Entity:UpdateDisplay() local target = self:GetTarget() self.sprite = CreateSprite() local p1 = self.entity:GetPosition() local p2 = target:GetPosition() self.sprite:SetPosition((p1 + p2) * 0.5) self.sprite:AlignToVector(p2 - p1) self.sprite:SetSize(0.1,(p2-p1):Length()) end Now let's imagine we had a tank with a main gun as well as an anti-aircraft gun that would ward off attacks from above, like this beauty I found on Turbosquid: Let's imagine we have two different scripts we attach to the tank. One handles AI for driving and shooting the main turret, while the other just manages the little machine gun. Both the scripts have a GetTarget() function, as the tank may be attacking two different enemies at once. We can easily modify our AI debugging script to handle multiple returned values as follows: function Entity:UpdateDisplay() local targets = { self:GetTarget() } --all returned values get put into a table for n,target in ipairs(targets) do local sprite = CreateSprite() self.sprites.insert(sprite) local p1 = self.entity:GetPosition() local p2 = target:GetPosition() sprite:SetPosition((p1 + p2) * 0.5) sprite:AlignToVector(p2 - p1) sprite:SetSize(0.1,(p2-p1):Length()) end end However, any scripts that are not set up to account for multiple returned values from a function will simply use the first returned value, and proceed as normal. This system supports both easy mix and match behavior with multiple scripts, but keeps the script code simple and easy to use. Scripts have easy interoperability by default, but if you want to make your function and variable names unique to the script it is easy to do so. Let me know if you have any other ideas for scripting in Turbo Game Engine.

Josh

Josh

True Single-Pass Stereo Rendering in Turbo Game Engine

Virtual reality rendering is very demanding on hardware for two reasons. First, the refresh rate of most VR headsets is 90 hz instead of the standard 60 hz refresh rate most computer monitors operate at. This means that rendering must complete in about 11 milliseconds instead of 16. Second, we have to render the scene twice with two different views, one for each eye. Without any special optimizations, this roughly means that we have to pack 16 milliseconds worth of rendering code into five milliseconds. There are a few optimizations we can make to improve efficiency over a naive implementation. Although VR rendering requires two different views to be rendered, the two views are only a few centimeters apart. We can perform culling for both views at once by simply widening the camera frustum a little bit so that both eyes are included the camera volume. For rendering, Nvidia provides an extension for single-pass stereo rendering. This doubles up the geometry and renders to two different viewports at once, ensuring that the engine makes the same number of draw calls when rendering in stereo or normal mode. (The same can be done using a geometry shader with Intel graphics, although it has yet to be determined if VR will be possible on this hardware.) Here is the result: Combined with all the other optimizations I've packed into Turbo, like clustered forward rendering and a multithreaded rendering architecture designed specifically for VR, this makes VR rendering blazingly fast. How fast is it? Well, it's so fast that SteamVR diagnostics thinks that it is going backwards in time. Take a look at the timer in the lower left corner here: The obvious conclusion is that we have successfully broken the speed of light barrier and time travel will be possible shortly. Start thinking about what items or information you want to gift your past self with now, so that you can be prepared when I start offering temporal tourism packages on this site.

Josh

Josh

Some Road Blocks

I've uploaded a new release last night correcting the player's pickup to be much more reliable. It should be very rare for a case in which the player is in a object and releases it causing the player to fly backward. I could have kept objects solid but that caused another can of worms. I also posted this on my OneDrive due to issues of people not being able to download it from this site. Yesterday I was working on implementation of the power balls and the launcher. I've written a rough draft of this a few weeks ago and I had it fully working in normal player code. When I started to work on the VR version, that's when I started to run into some problems. First, I didn't like how the code was looking. It involved a lot of referencing of entities and classes. What made it hard is that the VR implementation was different from the normal players. What didn't help was after too many restarts of my application with VR enabled, the entirety of Steam would just crash. So developing for VR was very frustrating. With Turbo coming a long, I wanted my code to work with little modification as possible. I've written a function wrapper that'll only be called if the application is ran under Leadwerks. This both will save time in replacing these functions when the time comes, and get's me used to the new functions. One thing I'd still have to fix is how Lua is implemented in the new engine. Most of the heavy functionalities are in classes which will defiantly keep self.entity for referring to the entity it's being used on. However somethings will still need to be changed and there are a lot of unknowns right now. With that being said, I think it's safe to say it'll be best to focus on the art and assets going forward. With the amount of time I have to make these assets, I'm sure more clarification with Lua scripts with the new engine will surface, maybe even a beta to play with. But with it being Leadwerks or Turbo, they both need assets. VR mode will also be dropped for now. It'll still be there, just not fully supported. You'll need to run the app with -devmode to see the VR tab now. After the project transitions into Turbo, VR development will continue. I shall be going back to my Linux machine with my ultra wide color calibrated monitor to work on these assets. I've created a lot of Vectronic themed stuff in the past and I'm going to go through it and see what I can savage from it. All my models are done in Blender, and I want to create all textures in GIMP this way my files can be used with free and open sourced tools. Also, if there is any Linux developers who wish to use this package, they don't have to be hit in the face with Maya and Photoshop files. Releases will slow down, but I'll be posting screenshots of my work soon.  

reepblue

reepblue

A value for everyone?

Hi Leadwerkers ⚙️, today I have a cool announcement to make. In the past two years I was working on the Phodex Framework, which is a set of helpful tools, utilities and systems, created to unleash Leadwerk's true power and potential, to be able to create the games I had in mind. Now, that I have released my first Leadwerks game Bladequest: The First Chapter and the Phodex Framework has proven its worth in practice, I thought it is time to share some parts of the framework, especially those I think everyone could make use of. I step by step will release different components of the framework, some of them are free, some of them paid. The best is, the first component, the very powerful and flexible Event System is already available for free. If used correctly, it can massively improve the structure of your code and also offers you the opportunity, to build your own powerful systems upon it. For the future I also plan to produce some video tutorials to explain everything more detailed. For now you can read the documentation, that comes with each component. The following components are planned to be released in the future:     🔹 Advanced Animation Manager     🔹 Advanced Debugging Functions     🔹 Easy Raycaster System     🔹 Helper Functions     🔹 Input Manager     🔹 Object Orientation for LUA     🔹 Save & Load System Together, those systems will massively improve your Leadwerks experience. I can't imagine working without them anymore and am sure you will feel them same once you use them. As always stay cool and have a nice day Markus from Phodex Games

Phodex Games

Phodex Games

Trademark for "Turbo Game Engine"

About six months ago I filed a trademark for the term "Turbo Game Engine". Today I heard back from the US Trademarks Office. I sent the requested reply. So it looks like we will get a trademark for the name "Turbo Game Engine" which means no one else can legally use it. 👍 This was the whole point of me announcing the name and putting it up for sale as a commercial product six months ago.

Josh

Josh

×