Jump to content

3 Ways Leadwerks Game Engine 5 Makes Game Programming Easier

Josh

2,103 views

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.



4 Comments


Recommended Comments

Looks great 

This one could be even more simple

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

instead of 

for (auto it = list.begin(); it!= list.end(); it++)
{
    auto entity = *it;
    entity->SetPosition(1,2,3);
}

 

Share this comment


Link to comment
21 minutes ago, Roland said:

Looks great 

This one could be even more simple


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

 

Is that a Visual Studio only feature?

Share this comment


Link to comment
27 minutes ago, Josh said:

Is that a Visual Studio only feature?

Ranged based for loops were added to the C++ standard in c++11, not a MSVC only thing. I've not seen the exact syntax that Roland used but this is how I do it in MSVC 2015  and GCC.

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

( auto&& ensures you get a reference and no copies are made)

http://en.cppreference.com/w/cpp/language/range-for

https://stackoverflow.com/questions/26991393/what-does-auto-e-do-in-range-based-for-loops

Share this comment


Link to comment
2 hours ago, thehankinator said:

Ranged based for loops were added to the C++ standard in c++11, not a MSVC only thing. I've not seen the exact syntax that Roland used but this is how I do it in MSVC 2015  and GCC.


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

( auto&& ensures you get a reference and no copies are made)

http://en.cppreference.com/w/cpp/language/range-for

https://stackoverflow.com/questions/26991393/what-does-auto-e-do-in-range-based-for-loops

Yes that is even better. Will change my loops to that

Share this comment


Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Blog Entries

    • By Josh in Josh's Dev Blog 3
      Previously, we saw how the new renderer can combine multiple cameras and even multiple worlds in a single render to combine 3D and 2D graphics. During the process of implementing Z-sorting for multiple layers of transparency, I found that Vulkan does in fact respect rasterization order. That is, objects are in fact drawn in the same order you provide draw calls to a command buffer.
      Furthermore, individual primitives (polygons) are also rendered in the order they are stored in the indice buffer:
      Now if you were making a 2D game with 1000 zombie sprites onscreen you would undoubtedly want to use 3D-in-2D rendering with an orthographic camera. Batching and depth discard would give you much faster performance when the number of objects goes up. However, the 2D aspect of most games is relatively simple, with only a dozen or so 2D sprites making up the user interface. Given that 2D graphics are not normally going to be much of a bottleneck, and that the biggest performance savings we have achieved was in making text a static object, I decided to rework the 2D rendering system into something that was a little simpler to use.
      Sprites are no longer a 3D entity, but are a new type of pure 2D object. They act in a similar way as entities with position, rotation, and scale commands, but they only use 2D coordinates:
      //Create a sprite auto sprite = CreateSprite(world,100,100); //Make blue sprite->SetColor(0,0,1); //Position in upper-left corner of screen sprite->SetPosition(10,10) Sprites have a handle you can set. By default this is in the upper-left corner of the sprite, but you can change it to recenter them. Sprites can also be rotated around the Z axis:
      //Center the handle sprite->SetHandle(0.5,0.5); //Rotation around center sprite->SetRotation(45); SVG vector images are great for 2D drawing and GUIs because they can scale for different display resolutions. We support these as well, with an optional scale value the image can be rasterized at.
      auto sprite = LoadSprite(world, "tiger.svg", 0, 2.0);
      Text is now just another type of sprite:
      auto text = CreateSprite(world, font, L"Hello, how are you today?\nI am fine.", 72, TEXT_LEFT); These sprites are all displayed within the same world as the 3D rendering, so unlike what I previously wrote about...
      You do not have to create extra cameras or worlds just to draw 2D graphics. (If you are doing something advanced then the multi-camera method I previously described is a good option, but you have to have very demanding needs for it to make a difference.) Regular old screen coordinates you are used to will be used (coordinate [0,0] is top-left). By default sprites will be drawn in the order they are created. However, I definitely see a need for additional control here and I am open to ideas. Should there be a sprite order value, a MoveToFront() method, or a system of different layers? I'm not sure yet.
      I'm also not sure how per-camera sprites will be controlled. At this time sprites are stored in a per-world list, but we will want some 2D elements to only appear on some cameras. I am not sure yet how this will be controlled.
      I am going to try to get an update out soon with these features so you can try them out yourself.
    • By Josh in Josh's Dev Blog 11
      Previously I described how multiple cameras can be combined in the new renderer to create an unlimited depth buffer. That discussion lead into multi-world rendering and 2D drawing. Surprisingly, there is a lot of overlap in these features, and it makes sense to solve all of it at one time.
      Old 2D rendering systems are designed around the idea of storing a hierarchy of state changes. The renderer would crawl through the hierarchy and perform commands as it went along, rendering all 2D elements in the order they should appear. It made sense for the design of the first graphics cards, but this style of rendering is really inefficient on modern graphics hardware. Today's hardware works best with batches of objects, using the depth buffer to handle which object appears on top. We don't sort 3D objects back-to-front because it would be monstrously inefficient, so why should 2D graphics be any different?
      We can get much better results if we use the same fast rendering techniques we use for 3D graphics and apply it to 2D shapes. After all, the only difference between 3D and 2D rendering is the shape of the camera projection matrix. For this reason, Turbo Engine will use 2D-in-3D rendering for all 2D drawing. You can render a pure 2D scene by setting the camera projection mode to orthographic, or you can create a second orthographic camera and render it on top of your 3D scene. This has two big implications:
      Performance will be incredibly fast. I predict 100,000 uniquely textured sprites will render pretty much instantaneously. In fact anyone making a 2D PC game who is having trouble with performance will be interested in using Turbo Engine. Advanced 3D effects will be possible that we aren't used to seeing in 2D. For example, lighting works with 2D rendering with no problems, as you can see below. Mixing of 3D and 2D elements will be possible to make inventory systems and other UI items. Particles and other objects can be incorporated into the 2D display.
      The big difference you will need to adjust to is there are no 2D drawing commands. Instead you have persistent objects that use the same system as the 3D rendering.
      Sprites
      The primary 2D element you will work with is the Sprite entity, which works the same as the 3D sprites in Leadwerks 4. Instead of drawing rectangles in the order you want them to appear, you will use the Z position of each entity and let the depth buffer take care of the rest, just like we do with 3D rendering. I also am adding support for animation frames and other features, and these can be used with 2D or 3D rendering.

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

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

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

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

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

      Because the text is just a single textured mesh, it will render very fast. This is a big improvement over the DrawText() command in Leadwerks 4, which performs one draw call for each character.
      The font loading command no longer accepts a size. You load the font once and a new image will be rasterized for each text size the engine requests internally:
      auto font = LoadFont("arial.ttf"); auto text = CreateText(foreground, font, "Hello, how are you today?", 18); Combining 2D and 3D
      By using two separate worlds we can control which items the 3D camera draws and which item 2D camera draws: (The foreground camera will be rendered on top of the perspective camera, since it is created after it.) We need to use a second camera so that 2D elements are rendered in a second pass with a fresh new depth buffer.
      //Create main world and camera auto world = CreateWorld(); auto camera = CreateCamera(world); auto scene = LoadScene(world,"start.map"); //Create world for 2D rendering auto foreground = CreateWorld() auto fgcam = CreateCamera(foreground); fgcam->SetProjection(PROJECTION_ORTHOGRAPHIC); fgcam->SetClearMode(CLEAR_DEPTH); fgcam->SetRange(-1,1); auto UI = LoadScene(foreground,"UI.map"); //Combine rendering world->Combine(foreground); while (true) { world->Update(); world->Render(framebuffer); } Overall, this will take more work to set up and get started with than the simple 2D drawing in Leadwerks 4, but the performance and additional control you get are well worth it. This whole approach makes so much sense to me, and I think it will lead to some really cool possibilities.
      As I have explained elsewhere, performance has replaced ease of use as my primary design goal. I like the results I get with this approach because I feel the design decisions are less subjective.
    • By Josh in Josh's Dev Blog 6
      I'm putting together ideas for a racing game template to add to Leadwerks. We already support vehicles. The challenge is to put together that looks and feels slick and professional, like a real game people want to play. The finished demo will be submitted to Greenlight, GameJolt, IndieDB, itch.io, etc.
       
      Gameplay
      First, I wanted to think about what style of racing I want this to be. I don't want street racing because it's kind of boring, and the level design is more involved. I don't want spintires-style technical offroading because it's too specialized. I want some fun medium-paced 4x4 racing like in
      , but modern. 


       
      This single-player game will pit you against seven computer-controlled components. You win by coming in the top three places. A time-trial option will allow you to compare your scores to other players via Steam leaderboards.
       
      The HUD will display a speedometer, your place in the race, current lap, and total and current lap time.
       
      Cars will be 4x4 trucks, identical except with a different texture.
       
      The player can turn headlights on and off, honk their horn, and drive. The transmission will always be automatic.
       
      Pressing the C key will alternate between views, including 3rd person, 3rd person further away, first-person (in-car), and a free third person camera that doesn't rotate with the car.
       
      Checkpoints will be placed throughout the level, with a sound when you pass through.
       
      After the race is complete, a replay will be performed from data recorded during the race, and scores will be shown on the screen.
       
      Environment
      I want the environment to be scrubby arid desert with big dramatic crags in the background.
       

       
      Roads will be painted on with a dirt texture, and decals will be used to add tire tracks sporadically. Decals will fade out at a fairly close distance, as I plan on having lots of them in the map.
       
      The game will allow you to set the time of day and weather. I have not decided if the weather and time of day will change as the race progresses. Time of day includes night, morning, afternoon, and evening.
       
      Weather can also be set, with options for sunny, rainy, and snowy. Snow will use a post-processing effect to add snow on all upwards-facing surfaces. Tire grip will be reduced in snowy and rainy conditions.
       
      The vehicles will throw up a cloud of dirt, mud, water, or other material, based on the primary texture of the terrain where they are contacting. Dirt, water, raindrops, ice, snow, and other effects will hit the camera and remain for a moment before fading.
       
      Screen-space reflection will be showcased heavily on the vehicle bodies.
       
      One song will play for the menu and one for the race. The song will sound something like this at 0:44 because it sounds modern:


       
      Or maybe this:

       
      Scope Limits
      The game is single-player only.
      I'm not going to bother with changes to the terrain or vehicles leaving tread marks.
      There will be no arms visible when the camera is inside the car.
      The environment will be static. There will be no destruction of the environment, and no moving objects or physically interactive items except for the cars.
      I am not going to implement an overhead map.
      I am not going to implement vehicle damage.
      Other than finishing the game GUI, I do not want to implement any new features in Leadwerks to complete this.
      The game will not attempt to be realistic or follow any real-world racing events.
      The race will not portray an audience or people standing around.
      No weapons.

×
×
  • Create New...