Game Engineering 2 Assignment 6

Objective:

1. Learn how to iterate a table in Lua.

2. Learn how to run an application from a C++ program.

3. Learn how to use precompiled shader.

Iterate Table in Lua

The most common way to iterate a table is to use pairs() or ipairs():

for key, value in pairs(table) do
XXX
end

for key, value in ipairs(table) do
XXX
end

The difference between pairs() and ipairs() is pairs() iterates through all the keys in a table and ipairs() only iterates through numeric keys. So if your table is like this:

table = { value1, value2, value3, … }

Then there is no difference between using pairs() and ipairs(). However if your table is like this and each key is a string:

table = {

key1 = value1,

key2 = value2,

key3 = value3,

}

In this case you should use pairs() because your key is not continuous number.

I use Lua table to store all the files that needs to be built by my asset builder.  Here is the Lua file I used:

assetlist

For each kind of asset type, there are 3 elements:

  • builder: indicate which asset builder to use
  • extensions: the extension of the source asset file and the target extension
  • assets: list names of all the asset files, if the asset builder requires additional argument then list the argument here as well

Run Program in C++

Different kind of asset file will be built by different asset builder. In my program, each asset builder is a “.exe” program, so I need to run these “.exe” program in my code.

The way I do is to use a Win32 function named CreateProcess():

BOOL WINAPI CreateProcess(
  _In_opt_     LPCTSTR lpApplicationName,
  _Inout_opt_  LPTSTR lpCommandLine,
  _In_opt_     LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_     LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_         BOOL bInheritHandles,
  _In_         DWORD dwCreationFlags,
  _In_opt_     LPVOID lpEnvironment,
  _In_opt_     LPCTSTR lpCurrentDirectory,
  _In_         LPSTARTUPINFO lpStartupInfo,
  _Out_        LPPROCESS_INFORMATION lpProcessInformation
);

The 1st parameter of this function is the complete path to the “.exe” file; the 2nd parameter is the argument that passed to that “.exe”.

However, if the path to the “.exe” file contains space, then the correct way to call CreateProcess() function is to pass NULL as the 1st parameter and pass the whole command (path to “.exe” plus arguments) as the 2nd parameter.

Use Precompiled Shader

Previously I compiled my shader when my program loads a shader file (.hlsl file). Now my asset builder compiled all my shader file into binary files and I need to read these binary files whenever my program loads a shader.

The create shader function in Direct3D9 is like this:

HRESULT CreateVertexShader(
  [in]           const DWORD *pFunction,
  [out, retval]  IDirect3DVertexShader9 **ppShader
);

The 1st parameter is a const DWORD*. So now what I need to do is to read all the content of the binary file into a buffer, and then pass this buffer as the 1st parameter.

Problem Encountered

When I tried to call CreateProcess() function I got an error said that windows cannot find the path. The reason is the path contains space and I passed the path as 1st parameter.

Time Estimates

  • Reading – 2 hours
  • Coding – 5 hours
  • Write up – 2 hours
  • Total – 9 hours

Download Executable

Game Engineering 2 Assignment 5

Objective:

1. Learn the transformation between different coordinate spaces.

2. Learn how to debug shader program.

Multiple Coordinate Spaces

The first thing to notice is there are 2 types of Cartesian coordinate systems: left-handed and right-handed.

lh_rh

DirectX uses left-handed and OpenGL uses right-handed. What does this mean is it determines which face is the front face. Normally graphic card will not draw back faces because you will not see them. In a left-handed system a clockwise order of vertices defines a front face.

When we want to draw an object on the screen, each vertex of that object needs to go through 3 processes:

1) Model Space (Local Space) to World Space transformation

2) World Space to View Space (Camera Space) transformation

3) View Space to Projection Space (Screen Space) transformation

World Transform

Usually we store the world position, rotation and scale of an object into respective 3d vector, but graphic card does coordinate transformation through matrices, so what we need to do is to create a matrix from our position, rotation and scale vectors.

The basic idea is to form a matrix from position, rotation and scale respectively, and then multiply these matrices together to form the final matrix.

In D3DX there is a function called D3DXMatrixTransformation that can help us do this job.

View Transform

This transformation is actually the inverse of the camera-to-world transformation.

In D3DX there is a function called D3DXMatrixLookAtLH can help us do this job.

Projection Transform

This transformation has nothing to do with the camera itself. What matters is the field of view, aspect ratio, near and far view-plane of the camera, or what we can say is the lens of the camera.

In D3DX there is a function called D3DXMatrixPerspectiveFovLH can help us do this job.

 

My Program

Here is a screenshot of my program:

app

Control of the Camera:

W, S, A, D: move the camera in XY plane.

Q, E: yaw the camera

Z, C: pitch the camera

Control of the Cube:

Up, Down, Left, Right: move the cube in XZ plane

 

Debug Shader Program

In DirecX 11 we can debug shader code in Visual Studio, but to debug a DrectX 9 shader program we need to use PIX.

Here is a screenshot of debugging shader program in PIX:

PIX

You can run your shader program step by step just like you debug your C++ code in visual studio.

 

Problem Encountered

In this assignment I encountered a problem that my debug build can work properly but my release build just show nothing. It turned out that I put my ID3DXConstantTable::SetMatrix in an assert() function so in release build this function is never called. Unfortunately Visual Studio is smart enough that my entire render function is skipped in the release build. The lesson I learned is never put a function in assert(), instead I should use a variable to store the return value and put that variable in the assert();

 

Time Estimates

  • Reading – 4 hours
  • Coding – 12 hours
  • Write up – 2 hours
  • Total – 18 hours

Download Executable

Game Engineering 2 Assignment 4

Objective:

1. Learn how to use constant table in Direct3D 9.

2. Change material file to include shader constant.

3. Learn how to call executable program from Lua script.

Direct3D Constant Table

A shader program can have global variables just like a normal C/C++ program. These variables are contained in the constant table and can be accessed from C++ program with the ID3DXConstantTable interface.

There are 2 ways to get the ID3DXConstantTable interface of a shader:

1. Using D3DXCompileShaderFromFile

HRESULT D3DXCompileShaderFromFile(
  _In_   LPCTSTR pSrcFile,
  _In_   const D3DXMACRO *pDefines,
  _In_   LPD3DXINCLUDE pInclude,
  _In_   LPCSTR pFunctionName,
  _In_   LPCSTR pProfile,
  _In_   DWORD Flags,
  _Out_  LPD3DXBUFFER *ppShader,
  _Out_  LPD3DXBUFFER *ppErrorMsgs,
  _Out_  LPD3DXCONSTANTTABLE *ppConstantTable
);

We use this function to compile a shader and the last parameter of this function is a pointer to a ID3DXConstantTable pointer.

2. Using D3DXGetShaderConstantTable

HRESULT D3DXGetShaderConstantTable(
  _In_   const DWORD *pFunction,
  _Out_  LPD3DXCONSTANTTABLE * ppConstantTable
);

After we compiled a shader, we can call D3DXGetShaderConstantTable to get its constant table. The 1st parameter of this function can be reinterpret_cast<DWORD*>(compiledShader->GetBufferPointer()).

To access a constant, we first need to get its handle. This can be done by calling ID3DXConstantTable::GetConstantByName. To set the value of the constant, we use ID3DXConstantTable::SetXXX, here XXX can be Bool, Float, Int, Matrix or Vector. The only difference is ID3DXConstantTable::SetDefaults, it will set the constant to its default value which is declared in the shader file.

New material format

In this assignment I modified the material file so it now looks like this:

material

 

Constants are stored in a table called “constants”, each constant contains a key, which is the name of the constant and its value, which might be another table. This way my constant table and shader table has the same architecture and I can read them using similar code.

This is the result of applying this material:

run1

This is the result after I change the g_colorModifier to { 0.5, 0.5, 0.0 }

run2

You can use 4 arrow keys (Up, Down, Left, Right) to move the rectangle.

Execute program from Lua

To run a .exe program from a Lua script is very simple. All you need to do is this line:

local result, terminationType, exitCode = os.execute( commandLine )

The command line is the path to your .exe plus the arguments you want to pass to the .exe.

Problem Encountered

When I read the Lua table, I found if the table contains key and value pairs, then when I iterate through the table ,the order that each item is read is not fixed. In my material file I have a table called “shaders” which has 2 key and value pairs. At first I thought the 1st item that I read must be the path of vertex shader, but I get errors when compiling the vertex shader. However, this error seems appear randomly. After some debug I found sometimes the 1st item I got was the path of fragment shader. It turns out that if your table contains keys, then Lua calculates the order by using the hash value of the key and every time Lua may get different hash values.

Time Estimates

  • Reading – 4 hours
  • Coding – 4 hours
  • Write up – 2 hours
  • Total – 10 hours

Download Executable

Game Engineering 2 Assignment 3

Objective:

1. Learn how to use index buffer.

2. Learn how to create your own asset file format.

Direct3D Index Buffer

The only things graphic cards can draw on the screen are points, lines and triangles. This means when we want to draw a rectangle we are actually drawing 2 triangles.

rect

 

To draw 1st triangle, we need vertex V1, V2 and V3; to draw the 2nd triangle we need vertex V2, V3 and V4. In this case vertex V2 and V3 are used twice, if we can find a way to reuse these 2 vertices we can reduce the size of our vertex buffer. The trick is to use index buffer. In the index buffer we store the indices of vertices, which are integer offsets into the vertex buffer.

To use index buffer, we first need to create index buffer using IDirect3DDevice9::CreateIndexBuffer. Then we lock the buffer to fill it with data and unlock it  to let the graphic card use it, just like the way we use a vertex buffer. When we draw our rectangle, we use IDiect3DDevice9::SetIndices to set which index buffer to use. Then we draw with IDirect3DDevice9::DrawIndexedPrimitive.

app_screenshot

 

In my program the bottom-left vertex has an index of 0, the upper-left vertex has an index of 1, the bottom-right vertex has an index of 2 and the upper-right vertex has an index of 3.

 

Pix

 

In PIX we can see the order the vertices I used to draw the 1st triangle is 0, 1, 2 and the 2nd triangle used 2, 1, 3.

Create material asset

In this assignment we need to define a new asset to represent a material using Lua. Here is my material file:

material

Basically it is a Lua table with 2 entries: each one has a key which defines the shader type and a value which is the path of that shader file.

Load this file is just like load any Lua scripts, except this file only return a single value which is the table.

loadtable

Here is the code I load the entry of my table. I first push the key (“vertexShader” or “fragmentShader”) onto the stack. Then I get the value using lua_gettable. Because I pushed the key, so now the key has an index of -1 and the table has an index of -2. Now I can use lua_tostring to get the value.

Problem Encountered

When I debug my program my program reports that it cannot find the material file. The reason is by default when we press F5 in Visual Studio, it runs what $(TargetPath) points to and using $(ProjectDir) as the working directory. However in my program my .exe file and final asset files are not at $(ProjectDir). To solve this problem I need to config the Debugging page as follows:

debug

Time Estimates

  • Reading – 6 hours
  • Coding – 3 hours
  • Write up – 2 hours
  • Total – 11 hours

Download Executable

Game Engineering 2 Assignment 2

Objective:

1. Learn how to pass extra information to shaders.

2. Learn how to use PIX tool to analyse a Direct3D application.

3. Learn how to use Lua script in a C++ project.

Direct3D 9 Graphics Pipeline

IC412590

To use shaders in our program, we need to do the following steps:

1) Define vertex data layout by filling in D3DVERTEXELEMENT9 structure.

vertex

The above picture shows that each vertex includes 2 types of information: position and color. This information needs to match the data we pass into our vertex shader. Below is our vertex shader and you can see it has 2 input parameters: i_position and i_color.

vs

2) Calling IDirect3DDevice9::CreateVertexDeclaration to create a vertex shader declaration. The vertex shader declaration is saved in a IDirect3DVertexDeclaration9 struct.

3) We told the hardware to use this vertex shader declaration by calling IDirect3DDevice9::SetVertexDeclaration method.

4) We now need a place to save our vertex data. We create this place by calling IDirect3DDevice9::CreateVertexBuffer method.

5) To fill in the actual data, we first need to use IDirect3DVertexBuffer9::Lock to obtain a pointer to the vertex buffer memory. After we set the data, we must call IDirect3DVertexBuffer9::Unlock to let the hardware know that it can use this vertex buffer.

6) Before we can use a shader file, we need to compile it first. This is done by calling D3DXCompileShaderFromFile function. After that we can call IDirect3DDevice9::CreateVertexShader to create our vertex shader.

7) Creating a pixel shader (fragment shader) is almost the same as creating a vertex shader, except you call IDirect3DDevice9::CreatPixelShader to create a pixel shader.

8) Now we have a vertex buffer, a vertex shader and a pixel shader. We need to tell the hardware to use them when we want to draw something on the screen. We call IDirect3DDevice9::SetVertexShader and IDirect3DDevice9::SetPixelShader respectively to use our vertex shader and pixel shader. We call IDirect3DDevice9::SetStreamSource to use our vertex buffer. When we call a draw function such as IDirect3DDevice9::DrawPrimitive the hardware will use the data from our vertex buffer and apply our vertex shader and pixel shader.

 Using PIX Tool to analyse my program

My program draws a colorful triangle on the screen.

Screenshot

Here is a screenshot from a PIX capture of my program.

PIX

In the “Events” tab on the left you can see all the functions that are called when drawing the screen and the time to execute those functions.

In the “Mesh” tab on the right you can see the mesh that got drawn on the screen. This is like what we will see in a 3D modelling software such as 3DS Max and Maya when we choose the “Wireframe” mode. Below you can see the data of each vertex that comprises the mesh.

Using Lua Script in C++

There are 2 common ways to use a Lua script in a C++ program:

  • Call Lua functions from a C++ program
  • Call C++ functions from a Lua script

1) Call Lua functions from C++ code

To call a Lua function it first need to be pushed onto the stack. This can be done by calling lua_getglobal function.

If this function require arguments, we need to push the arguments onto the stack by calling lua_pushXXX (XXX means the data type).

For example, to push a string argument, we call lua_pushstring(lua_State*, const char*).

Then we call lua_call to execute the Lua function.

If the Lua function has return values, the return values are also pushed onto the stack. We get the return values by calling lua_toXXX (XXX means the data type).

For example, to get a return value of type lua_Number, we call lua_tonumber(lua_State*, int index); (the last return value has a index of -1, the first return value has a index of -n )

For each return value we get, we need to call lua_pop to pop the return value off the stack.

2) Call C++ functions from Lua script

Each C++ function needs to be a function like this: int Func(lua_State*) , the return value is the number of values this function returns.

To let the Lua script recognize the C++ functions, we need to register these functions by calling lua_register.

Pass arguments is the same way as we call Lua functions from C++ code. The only difference is now we need to push the return values onto the stack as well.

If there is a error in the function, we call: return luaL_error(lua_State*, const char*) to display the error message.

 Time Estimates

  • Reading – 4 hours
  • Coding – 2 hours
  • Write up – 2 hours
  • Total – 8 hours

 Download executable

Game Engineering 2 Assignment 1

Objective:

To create a Visual Studio Solution with separate projects for game and tools that can be reused in the future.

Visual Studio Solution:

The lesson I learned from this assignment is how to organize my code properly and how to use property sheet to make this task easier.

The main concept is to separate your source code, intermediate objects and final output objects.  By defining new macros in property sheet, you can set your output directory and intermediate directory to any where you want and use this setting for all your projects. Another trick is to use command in “Custom Build Step”. You can copy your output objects to a specific directory after the build step.

Architecture Details:

I used one class Application as a win32 window wrapper class and one class Renderer as the graphic wrapper class. I am aiming for both to be cross platform.

My Application class has the following public functions:

  • Init(): bool – Initializes the window
  • Update(float):void – Where you put the game logic code
  • Render(float):void – Call renderer to display objects
  • ShutDown():void – Release all the resources before application quits

My renderer class uses singleton pattern and now it has the following public functions:

  • Initialize(HWND):bool – Initialize Direct3D
  • Render():void – Do the drawing job
  • ShutDown():bool – Shutdown Direct3D

Problem Encountered:

My intention for the Application class is it should hide all the platform-specific information which include the message process function. In win32 this message process function is defined as a callback function, which means it should be a global function or a static member function. Obviously each window should have its own message process function, so my application class will have a protected member function that processes all the window messages. Since a global function cannot access a protected member function, the only option is to use a static member function. However I didn’t know how to get the instance of my application class in this static member function. I got the answer from internet. the key is the CreateWindow function, which is defined as:

HWND WINAPI CreateWindow(
  _In_opt_  LPCTSTR lpClassName,
  _In_opt_  LPCTSTR lpWindowName,
  _In_      DWORD dwStyle,
  _In_      int x,
  _In_      int y,
  _In_      int nWidth,
  _In_      int nHeight,
  _In_opt_  HWND hWndParent,
  _In_opt_  HMENU hMenu,
  _In_opt_  HINSTANCE hInstance,
  _In_opt_  LPVOID lpParam
);

The last parameter of this function is LPVOID lpParam. Normally we don’t care about this parameter and pass NULL. To achieve what I want, I need to pass (void*) this instead of NULL. When the first time we create the window, a WM_NCCREATE message is sent and we can capture this message in our static function. Then we can get the pointer to the window from lParam->lpCreateParams.

Time Estimates

It’s hard to measure the actual time because I spent most of the time on reading code of open source game engines and searching answers on the internet.

  • Reading – 8 hours
  • First working build – 6 hours
  • Code refactoring – 6 hours
  • Write up – 2 hours
  • Total – 22 hours

 Download executable

 

 

My Starcraft 2 map for level design class

This is the map I designed for my level design class.

sc2

It is co-op map that 4 players team up to fight the boss. Players need to kill the boss in 5 minutes.

I added 2 attributes for player’s unit: strength and agility. Strength increases unit’s max health and health regeneration rate. Agility increase unit’s movement speed and attack power.

Beside the boss, there will be enemy zerglings spawn all over the map periodically. Every time a player falls, he will revive after 10 seconds, but the enemy will become stronger.

GDC

For game developers, Game Developer Conference is the biggest event every year. This year is my first time to attend this event and it is an incredible experience.

I got an Expo pass so that means I can only visit Expo floor and career fair. Here are some lessons I learned from GDC.

1. The Expo is all about technology that used to build games.

This year Sony revealed their VR device. When I was at their booth, I found what they try to promote is their indie developer program. They’re willing to let developers join their program and release games on PS4. Epic games and Crytek were selling their engines at a lower price than Unity. I spent some time at Unity’s booth and they were showing Unity 5. There were also a lot of small companies tried to sell their motion controller and middleware. I also met a Chinese company that giving out their engine’s source code.

2. The Career Fair is not the best place to find an intern

There were not many companies as I thought that hiring people here and they were looking for people who can work full-time. These years I heard a lot of news about game company laying off their staff and many middle size game companies were closed. It is harder to get a job these days.

3. Game Developers Choice Award ceremony is a worthwhile event

This might be the most excited event during these 3 days and everyone can attend. You could see those big names that normally you could only see on Youtube and websites. This year’s biggest winner is Lucas Pope, the creator of Papers, Please. He is a very nice guy and I talked with him the other day and he answered all my questions about his game.

Mannequin Game

After the industry panel, we decided to terminate the Agoraphobia project. The reason is we could not find some unique features to add into the game and make the game outstanding.

Now every one joined mannequin team. Mannequin is a first person horror game featured mannequins. This game will use Oculus Rift and has procedural generated levels. The industry people liked Oculus Rift and they thought we should make this game.

The game itself is a traditional horror game that you are stuck in a mall and you need to find a way out by getting some keys. The mannequins will attack you but they will only do that and move when you’re not looking at them.

mannequin