Jump to content

Offroad Racing Game Template

Josh

1,306 views

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.

 

blogentry-1-0-45081600-1481656131_thumb.jpg

 

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.

  • Like 1


6 Comments


Recommended Comments

Why not do something like making it so the default Leadwerks templates are open source so the community can contribute to them? Of course, changes would still require your approval.

Share this comment


Link to comment

Why not do something like making it so the default Leadwerks templates are open source so the community can contribute to them? Of course, changes would still require your approval.

 

 

I assume since this is a template it'll show up like the fps template when we make a new project so we can modify all we want. If someone wants to make it a more formal community project they can do that then.

Share this comment


Link to comment

This is a great idea! Other contemporary game engines boast their vehicle implementation and I'm glad to see Leadwerks will too. I think it'd be great to have a vehicle construction tutorial tie in for this as well.

 

As for the music I'd recommend the Dubstep or Rock since that's more inline with the chaos of off-road racing. Trancey-shmancy music is more street racer IMO.

Share this comment


Link to comment

I think people are expressing an interest in participating because I just put out a complete and precise description of a game. When you write your ideas down it will bring issues to the forefront that you had not thought of. It is okay to make changes, but if you actually write things down it forces you to make decisions. I had not thought of the time trial option until I wrote this blog. I did not think about whether headlights should start turned on during the night until I wrote this all down.

 

This is why a game design document is good, not because it is a final unalterable specification, but because it organizes your ideas and draws a limit on what you're not going to bother with.

Share this comment


Link to comment
Quote

 

This single-player game will pit you against seven computer-controlled components. You win by coming in the top three places.

 

 

 

That's interesting, Josh did you think at that time about how to build the car AI opponents ?

There are so much games having a great car AI. With the Spline tools it is possible to simulate something but without ??? 

How to make a computer-controlled car follow a road ?

 

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 2
      Previously I talked about the technical details of hardware tessellation and what it took to make it truly useful. In this article I will talk about some of the implications of this feature and the more advanced ramifications of baking tessellation into Turbo Game Engine as a first-class feature in the 
      Although hardware tessellation has been around for a few years, we don't see it used in games that often. There are two big problems that need to be overcome.
      We need a way to prevent cracks from appearing along edges. We need to display a consistent density of triangles on the screen. Too many polygons is a big problem. I think these issues are the reason you don't really see much use of tessellation in games, even today. However, I think my research this week has created new technology that will allow us to make use of tessellation as an every-day feature in our new Vulkan renderer.
      Per-Vertex Displacement Scale
      Because tessellation displaces vertices, any discrepancy in the distance or direction of the displacement, or any difference in the way neighboring polygons are subdivided, will result in cracks appearing in the mesh.

      To prevent unwanted cracks in mesh geometry I added a per-vertex displacement scale value. I packed this value into the w component of the vertex position, which was not being used. When the displacement strength is set to zero along the edges the cracks disappear:

      Segmented Primitives
      With the ability to control displacement on a per-vertex level, I set about implementing more advanced model primitives. The basic idea is to split up faces so that the edge vertices can have their displacement scale set to zero to eliminate cracks. I started with a segmented plane. This is a patch of triangles with a user-defined size and resolution. The outer-most vertices have a displacement value of 0 and the inner vertices have a displacement of 1. When tessellation is applied to the plane the effect fades out as it reaches the edges of the primitive:

      I then used this formula to create a more advanced box primitive. Along the seam where the edges of each face meet, the displacement smoothly fades out to prevent cracks from appearing.

      The same idea was applied to make segmented cylinders and cones, with displacement disabled along the seams.


      Finally, a new QuadSphere primitive was created using the box formula, and then normalizing each vertex position. This warps the vertices into a round shape, creating a sphere without the texture warping that spherical mapping creates.

      It's amazing how tessellation and displacement can make these simple shapes look amazing. Here is the full list of available commands:
      shared_ptr<Model> CreateBox(shared_ptr<World> world, const float width = 1.0); shared_ptr<Model> CreateBox(shared_ptr<World> world, const float width, const float height, const float depth, const int xsegs = 1, const int ysegs = 1); shared_ptr<Model> CreateSphere(shared_ptr<World> world, const float radius = 0.5, const int segments = 16); shared_ptr<Model> CreateCone(shared_ptr<World> world, const float radius = 0.5, const float height = 1.0, const int segments = 16, const int heightsegs = 1, const int capsegs = 1); shared_ptr<Model> CreateCylinder(shared_ptr<World> world, const float radius = 0.5, const float height=1.0, const int sides = 16, const int heightsegs = 1, const int capsegs = 1); shared_ptr<Model> CreatePlane(shared_ptr<World> world, cnst float width=1, const float height=1, const int xsegs = 1, const int ysegs = 1); shared_ptr<Model> CreateQuadSphere(shared_ptr<World> world, const float radius = 0.5, const int segments = 8); Edge Normals
      I experimented a bit with edges and got some interesting results. If you round the corner by setting the vertex normal to point diagonally, a rounded edge appears.

      If you extend the displacement scale beyond 1.0 you can get a harder extended edge.

      This is something I will experiment with more. I think CSG brush smooth groups could be used to make some really nice level geometry.
      Screen-space Tessellation LOD
      I created an LOD calculation formula that attempts to segment polygons into a target size in screen space. This provides a more uniform distribution of tessellated polygons, regardless of the original geometry. Below are two cylinders created with different segmentation settings, with tessellation disabled:

      And now here are the same meshes with tessellation applied. Although the less-segmented cylinder has more stretched triangles, they both are made up of triangles about the same size.

      Because the calculation works with screen-space coordinates, objects will automatically adjust resolution with distance. Here are two identical cylinders at different distances.

      You can see they have roughly the same distribution of polygons, which is what we want. The same amount of detail will be used to show off displaced edges at any distance.

      We can even set a threshold for the minimum vertex displacement in screen space and use that to eliminate tessellation inside an object and only display extra triangles along the edges.

      This allows you to simply set a target polygon size in screen space without adjusting any per-mesh properties. This method could have prevented the problems Crysis 2 had with polygon density. This also solves the problem that prevented me from using tessellation for terrain. The per-mesh tessellation settings I worked on a couple days ago will be removed since it is not needed.
      Parallax Mapping Fallback
      Finally, I added a simple parallax mapping fallback that gets used when tessellation is disabled. This makes an inexpensive option for low-end machines that still conveys displacement.

      Next I am going to try processing some models that were not designed for tessellation and see if I can use tessellation to add geometric detail to low-poly models without any cracks or artifacts.
    • By Josh in Josh's Dev Blog 0
      For finer control over what 2D elements appear on what camera, I have implemented a system of "Sprite Layers". Here's how it works:
      A sprite layer is created in a world. Sprites are created in a layer. Layers are attached to a camera (in the same world). The reason the sprite layer is linked to the world is because the render tweening operates on a per-world basis, and it works with the sprite system just like the entity system. In fact, the rendering thread uses the same RenderNode class for both.
      I have basic GUI functionality working now. A GUI can be created directly on a window and use the OS drawing commands, or it can be created on a sprite layer and rendered with 3D graphics. The first method is how I plan to make the new editor user interface, while the second is quite flexible. The most common usage will be to create a sprite layer, attach it to the main camera, and add a GUI to appear in-game. However, you can just as easily attach a sprite layer to a camera that has a texture render target, and make the GUI appear in-game on a panel in 3D. Because of these different usages, you must manually insert events like mouse movements into the GUI in order for it to process them:
      while true do local event = GetEvent() if event.id == EVENT_NONE then break end if event.id == EVENT_MOUSE_DOWN or event.id == EVENT_MOUSE_MOVE or event.id == EVENT_MOUSE_UP or event.id == EVENT_KEY_DOWN or event.id == EVENT_KEY_UP then gui:ProcessEvent(event) end end You could also input your own events from the mouse position to create interactive surfaces, like in games like DOOM and Soma. Or you can render the GUI to a texture and interact with it by feeding in input from VR controllers.

      Because the new 2D drawing system uses persistent objects instead of drawing commands the code to display elements has changed quite a lot. Here is my current button script. I implemented a system of abstract GUI "rectangles" the script can create and modify. If the GUI is attached to a sprite layer these get translated into sprites, and if it is attached directly to a window they get translated into system drawing commands. Note that the AddTextRect doesn't even allow you to access the widget text directly because the widget text is stored in a wstring, which supports Unicode characters but is not supported by Lua.
      --Default values widget.pushed=false widget.hovered=false widget.textindent=4 widget.checkboxsize=14 widget.checkboxindent=5 widget.radius=3 widget.textcolor = Vec4(1,1,1,1) widget.bordercolor = Vec4(0,0,0,0) widget.hoverbordercolor = Vec4(51/255,151/255,1) widget.backgroundcolor = Vec4(0.2,0.2,0.2,1) function widget:MouseEnter(x,y) self.hovered = true self:Redraw() end function widget:MouseLeave(x,y) self.hovered = false self:Redraw() end function widget:MouseDown(button,x,y) if button == MOUSE_LEFT then self.pushed=true self:Redraw() end end function widget:MouseUp(button,x,y) if button == MOUSE_LEFT then self.pushed = false if self.hovered then EmitEvent(EVENT_WIDGET_ACTION,self) end self:Redraw() end end function widget:OK() EmitEvent(EVENT_WIDGET_ACTION,self) end function widget:KeyDown(keycode) if keycode == KEY_ENTER then EmitEvent(EVENT_WIDGET_ACTION,self) self:Redraw() end end function widget:Start() --Background self:AddRect(self.position, self.size, self.backgroundcolor, false, self.radius) --Border if self.hovered == true then self:AddRect(self.position, self.size, self.hoverbordercolor, true, self.radius) else self:AddRect(self.position, self.size, self.bordercolor, true, self.radius) end --Text if self.pushed == true then self:AddTextRect(self.position + iVec2(1,1), self.size, self.textcolor, TEXT_CENTER + TEXT_MIDDLE) else self:AddTextRect(self.position, self.size, self.textcolor, TEXT_CENTER + TEXT_MIDDLE) end end function widget:Draw() --Update position and size self.primitives[1].position = self.position self.primitives[1].size = self.size self.primitives[2].position = self.position self.primitives[2].size = self.size self.primitives[3].size = self.size --Update the border color based on the current hover state if self.hovered == true then self.primitives[2].color = self.hoverbordercolor else self.primitives[2].color = self.bordercolor end --Offset the text when button is pressed if self.pushed == true then self.primitives[3].position = self.position + iVec2(1,1) else self.primitives[3].position = self.position end end This is arguably harder to use than the Leadwerks 4 system, but it gives you advanced capabilities and better performance that the previous design did not allow.
    • By Roland in ZERO 5
      For this Zero project I have a need to call C++ classes from LUA. I have gone the same way as Josh has and used 'tolua++' for this. This works like this
       
      1 - Create PKG
      For each class you need to export you have to write a corresponding 'pkg' file which is a version of the C++ header file suitable for the 'tolua++' program.
       
      2 - Use 'tolua++'
      Then send the 'pkg' file to 'tolua++' which then will generate a source file with the LUA-export version of the class and a header file which defines the function to call in order to export the class.
       
      3 - Add & Compile
      The two generated files should be included in your C++ project and you have to call the function defined in the header at some time after LUA has been initialized by Leadwerks. After compilation and linking you should be able to use the C++ class in you LUA scripts
       
      Here is a simple example:
       
      C++ header file: CppTest.h

      #pragma once #include <string> class CppTest { int _value; std::string _string; public: CppTest(); virtual ~CppTest(); int get_value() const; void set_value( int value ); std::string get_string() const; void set_string( const std::string& value ); };
       
      C++ source file: CppTest.cpp

      #include "CppTest.h" CppTest::CppTest() { _value = 0; } CppTest::~CppTest() { } int CppTest::get_value() const { return _value; } void CppTest::set_value( int value ) { _value = value; } std::string CppTest::get_string() const { return _string; } void CppTest::set_string( const std::string& value ) { _string = value; }
       
      PKG file: CppTest.pkg

      $#include <string> $#include "CppTest.h" class CppTest : public Temp { CppTest(); virtual ~CppTest(); int get_value() const; void set_value( int value ); std::string get_string() const; void set_string( const std::string& value ); };
       
      After you written the CppTest.pkg file you have to compile it using 'tolua++' like this. Note that I use the same filename for the outputs but with an '_' added. That way its easy to keep track of things
       

      tolua++ -o CppTest_.cpp -n CppTest -H CppTest_.h CppTest.pkg

       
      Now tolua should have generated two files.
       
      Generated CppTest_.h

      /* ** Lua binding: CppTest ** Generated automatically by tolua++-1.0.92 on 08/18/16 11:36:39. */ /* Exported function */ TOLUA_API int tolua_CppTest_open (lua_State* tolua_S);
       
      Generated CppTest_.cpp (shortned down)

      /* ** Lua binding: CppTest ** Generated automatically by tolua++-1.0.92 on 08/18/16 11:36:39. */ #ifndef __cplusplus #include "stdlib.h" #endif #include "string.h" #include "tolua++.h" /* Exported function */ TOLUA_API int tolua_CppTest_open (lua_State* tolua_S); #include <string> #include "CppTest.h" /* function to release collected object via destructor */ #ifdef __cplusplus static int tolua_collect_CppTest (lua_State* tolua_S) { CppTest* self = (CppTest*) tolua_tousertype(tolua_S,1,0); delete self; return 0; } #endif --- snip --- snip --- snip --- --- snip --- snip --- snip --- --- snip --- snip --- snip --- /* Open function */ TOLUA_API int tolua_CppTest_open (lua_State* tolua_S) { tolua_open(tolua_S); tolua_reg_types(tolua_S); tolua_module(tolua_S,NULL,0); tolua_beginmodule(tolua_S,NULL); #ifdef __cplusplus tolua_cclass(tolua_S,"CppTest","CppTest","Temp",tolua_collect_CppTest); #else tolua_cclass(tolua_S,"CppTest","CppTest","Temp",NULL); #endif tolua_beginmodule(tolua_S,"CppTest"); tolua_function(tolua_S,"new",tolua_CppTest_CppTest_new00); tolua_function(tolua_S,"new_local",tolua_CppTest_CppTest_new00_local); tolua_function(tolua_S,".call",tolua_CppTest_CppTest_new00_local); tolua_function(tolua_S,"delete",tolua_CppTest_CppTest_delete00); tolua_function(tolua_S,"get_value",tolua_CppTest_CppTest_get_value00); tolua_function(tolua_S,"set_value",tolua_CppTest_CppTest_set_value00); tolua_function(tolua_S,"get_string",tolua_CppTest_CppTest_get_string00); tolua_function(tolua_S,"set_string",tolua_CppTest_CppTest_set_string00); tolua_endmodule(tolua_S); tolua_endmodule(tolua_S); return 1; } #if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM >= 501 TOLUA_API int luaopen_CppTest (lua_State* tolua_S) { return tolua_CppTest_open(tolua_S); }; #endif
      You should include both of those in your project. You also have to call tolua_CppTest_open somewhere after Leadwerks has initialized LUA. I do it here in my App.cpp. Remember to #include "CppTest_.h" at the top of App.cpp.
       
      App.cpp (shortned down)

      #include "App.h" #include "CppTest_.h" using namespace Leadwerks; App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {} App::~App() { delete world; delete window; } bool App::Start() { int stacksize = Interpreter::GetStackSize(); //Get the global error handler function int errorfunctionindex = 0; #ifdef DEBUG Interpreter::GetGlobal("LuaErrorHandler"); errorfunctionindex = Interpreter::GetStackSize(); #endif //Create new table and assign it to the global variable "App" Interpreter::NewTable(); Interpreter::SetGlobal("App"); std::string scriptpath = "Scripts/Main.lua"; if (FileSystem::GetFileType("Scripts/App.Lua") == 1) scriptpath = "Scripts/App.Lua"; // ADDED to initialize the CppTest LUA implemenation tolua_CppTest_open(Interpreter::L); //Invoke the start script if (!Interpreter::ExecuteFile(scriptpath)) { System::Print("Error: Failed to execute script \"" + scriptpath + "\"."); return false; } --- snip --- snip --- snip --- --- snip --- snip --- snip --- --- snip --- snip --- snip ---
      LuaParser
      I read somewhere that Josh has a parser that automates this a bit by parsing lines in the header file that is commented with //lua and generates pkg-files. What a good idea. As I think programming is better that watching lousy programs on TV, I made my own version of this and called it "LuaParser". I have attached this program for those who like to use it. Here is what it does
       
      1. Parses all C++ header files (*.h) in the folder and subfolders for lines commented with //lua
      2. For such files it creates a pkg file suitable for tolua++ compilation
      3. It complies the generated pkg files into _.h and _.cpp files to be included into you project
       
      Here is same example from above declared for LuaParsing
       
      C++ header file: CppTest.h prepared for LuaParser
      #pragma once #include <string>//lua class CppTest { int _value; std::string _string; public: CppTest();//lua virtual ~CppTest();//lua int get_value() const;//lua void set_value( int value );//lua std::string get_string() const;//lua void set_string( const std::string& value );//lua };
       
      Extract the LuaParser.zip into you Source folder and open a command prompt there. The just type LuaParser and hit Enter. A number of files ending with '_' in the name will be generated. Include them in your project and call the function in each of the '_.h' files as mentioned.
       
      Windows version
      LuaParserWin-1.5.zip
       
      Linux version
      LuaParserLinux-1.0.tar.gz - support discontinued
       
      History
      1.0 Initial version
      1.1 Comment header in PKG files was inside class declaration instead of at top of file
      1.2 Didn't handle class inheritance
      1.3 - 1.5 Various minor fixes
       
      You can read more about tolua++ here
      tolua++ - Reference Manual
×
×
  • Create New...