Game Engine, C 1, P 2, Project Setup

Initial Setup

In case you came here for CMake or Git, scroll further down.

In the previous post we went through the project hierarchy, used applications and tools. Let's recap a few things. Keep in mind that info inside the parenthesis is not part of the name, but an explanation.
  • Build
  • Vivid-Docs
  • Vivid-Project
    • Libs (for external libraries)
    • VividCore (for the engine itself)
      • inc (for public includes)
        • vivid_core
      • inc_private (for private includes)
        • vivid_core
      • src (for source files)
        • vivid_core
    • VividTests  (for unit testing)
    • VividGame (a game using VividEngine)
On my GitHub you'll most likely find a few more folders, they aren't part of the project and are there for other purposes (such as docs for GitHub Pages). I suggest you create a similar directory hierarchy, so you can manage your project in better detail. I don't suggest that it is the perfect project hierarchy, but for me it gets the job done pretty well.

Tools setup

Now we have to fill all these folders with useful information. In order to fill all those code folders, we need actual code. In order to fill the build folder, we need to have something to build and the means to build it. Based on the above, we can only fill the Vivid-Docs folder for now. My docs folder includes a Draw.IO "Project Structure" file, an OpenOffice SpreadSheet "ToDo" file, an OpenOffice Writer "Notes" file and an OpenOffice SpreadSheet "Dependencies" file.

Advice

Time to give you a few pieces of advice, I guess.

My notes are structured based on their sub-project (UI, Rendering, VividGame, VividTests, etc.), preceding with a table of contents, auto-generated by my rich text editor of choice.

My dependencies file has a table with only the columns I need:
  • My sub-project
  • Library / Tool name
  • Authors
  • Link
  • Whether it works on Desktop, Android, iOS, etc.
  • License
  • Notes
This should be mostly enough to use the file effectively, but more columns may be added during project development.

My project structure is a mix of an UML Package Diagram and Class Diagram. Here I more or less copy the folder structure of my project (which should in theory show, that my project is well-structured), adding other dependent projects and libraries as I go.

My ToDo file has a table with a few columns indicating the tasks I am solving:
  • My sub-project
  • Source code file, library or internet-source
  • Info about the task
  • Date started, finished or dropped
  • Notes
Based on this table, I can make queries, like finding unfinished tasks, finding dropped tasks between certain dates, finding finished tasks belonging to some sub-project. I have also added coloring with conditional formatting. Sounds convenient, right?

Kanban vs Tables

You may have noticed, that I recommend using both a kanban board and a ToDo list. Why so?
The answer is simple: based on my experience, projects with many tiny tasks can flood the kanban board pretty quickly, so everything has to be either moved to a description field of a bigger task, or archived every few days. I think that the first option is much better than the second, especially with modern kanban board tools, but I find tables convenient to use in a constantly changing project, where I don't have any concrete plans.

Kanban

Now to the kanban board itself. As mentioned before, I will be using Trello. It has a pre-made Agile Sprint Board, which covers all requirements that we may have for a board of such type. I, personally, prefer the variant I saw in some YouTube game development vlog.
This structure works a bit better for a toy game engine project, but you are free to choose the one, that you like.

Git

The time has come for us to enable version control in our project folder. One of the best tools for that is Git. In this post we will do a basic git setup for our game engine, then make a commit.

Git init

To begin with, go to the root folder of your project and type git init. This will initialize a git repository for that folder, allowing us to use version control inside it.

You can look for uncommitted files inside the project with git status. This may help you in the future, in case you need to know, if you modified something.

Git commit

A commit is a record of what files you have changed since the last time you made a commit. Essentially, you make changes to your repo (for example, adding a file or modifying one) and then tell git to put those files into a commit. Commits make up the essence of your project and allow you to go back to the state of a project at any point.

How will Git know, what files to commit? You have to specify them yourself. This can be done by using git add <filename>. Keep in mind, that this command supports both file lists and file globs, so git add *.cpp will also work.

To reiterate, the file has not yet been added to a commit, but it's about to be.

Now it is time to create a commit. This can be done with git commit -m <message>. Of course, as with other git commands, there are a lot of other useful parameters, that can be specified, but we will dismiss them for now.

Git branch

After creating some commits, you might want to implement a few features separately, without modifying the main chunk of the project. This can be done using branches. Branches are used to move between different states of the project, so you can have a specific "feature_development" branch and a "master" branch. When all changes are completed and there are no critical bugs found, branches can be merged back together. Let's look at the specific commands on how to do that.

To move to another (or new) branch, use git checkout -b <branch_name>. This command creates a new branch if it does not exist, otherwise resets an existing one, then checks you out on it, essentially moving you to it and switching your staging environment for commits.

You can see all your branches by using git branch. This can help you make sure, that the branch was created successfully.

When you add files or make commits, they will go to the "branch_name" branch and won't affect the "master" branch. To switch back, use git checkout master.

Git merge

The last thing I want to mention is merging your changes back together. Let's say, you have two branches: "branch1" ("master") and "branch2" ("feature_development"). You want to merge "branch2" into "branch1".

Start by checking out to the first branch (git checkout branch1). If you have an external repository and you use git push to push changes to it, then use git fetch and git pull to get the latest version of "branch1". Lastly, execute git merge branch2 to finish the merging process. This is it. The source branch for the merge ("branch1") can now be deleted with git branch -d branch2.

If you run into a conflict (same files were modified in both branches and git does not know how to merge them properly), you have to modify the source branch file to include changes from the destination branch, then commit these changes and perform a 3-way merge. However, this is is out of the scope of this post, so you can read up on this somewhere on the web.

 .gitignore

Oh my, I forgot to talk about file ignoring with git. This is a powerful feature, allowing you to exclude certain files or folders (again using file glob) from making their way into version control. For example, you are creating an big executable and don't want to store that big file every time you re-compile it. Besides, since source code is version controlled, you can always re-compile that executable, when you need it. Then you can simply ignore that file or all executables in git, saving you time and free disk space.

All of the above can be achieved using a ".gitignore" file. You simply write files, folders or file globs in it. Git will look for this file and ignore everything that matches the contents of this file. My ".gitignore" contains info about CMake, C-like and Visual Studio files. There can be only one ".gitignore" file per folder and it needs to be called this way and no other. For a toy game engine you can copy-paste text from multiple files and call it a day. These files can be found here.

Git submodule

This concludes our small git tutorial. Now, for sure, you can git on with your project development. If you want to add projects from other people using git, you can use the git submodule feature, which will be covered after CMake.

CMake

CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice.

I will try not to go into much detail on CMake, because this post is already too long for my liking and for its scope, so in case you want to learn more, feel free to search the web or read the docs.

Let's start by opening the "Vivid-Project" folder and creating a "CMakeLists.txt" file there. Then create a similar file inside the core, tests and game folder.

Root

Now it is time to fill all these text files. Let's start by editing the root one.
cmake_minimum_required (VERSION 3.8)
project (VividProject  LANGUAGES C CXX VERSION 0.0.1 DESCRIPTION "The Vivid Engine Project")

SET (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin )
SET (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin )
SET (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin )

set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)

list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/Libs/modules")
set(GLM_ROOT_DIR "Libs/glm")
find_package(GLM REQUIRED)

include_directories(VividCore/inc)
add_subdirectory(Libs/glfw)
add_subdirectory(VividCore)
add_subdirectory(VividTests)
add_subdirectory(VividGame)
Our first line tells CMake, which minimum version of it we want to use. As different people use different versions of CMake, we have to make sure that they will be able to perform the build. For that they need to have the same feature set as us.

Then we set a few CMake variables. They thell CMake, where to store the built files. For now, we simply put everything inside the "bin" folder. Library and archive output is the output of shared and static libraries (.dll, .so and .lib, .a). Runtime output is for binaries and executables.

To add sub-directories with our sub-projects we have to use the add_subdirectory command. This command works similarly to the C++ #include preprocessor directive. It searches for a "CMakeLists.txt" file in the specified directory and executes it. After that it returns control to the calling file.

Other 3 set commands are used together with the library called "GLFW". They are used to disable docs, tests and examples when building the app, however the time has not come for us to talk about them.

VividCore

This is the core of our engine. In the future I hope for this to become the engine project itself, but for now I may create temporary project for different engine features. As this is the engine, not the game or an editor, this needs to be compiled as a library. We have to take this into account. Moreover, we will have some implementation details that we don't want to be accessible by the users of the engine. This is why we have the "inc_private" folder. Let's take a look at the code:
file(GLOB_RECURSE SRC_FILES src/*.cpp)
file(GLOB_RECURSE INC_FILES inc/*.h)
file(GLOB_RECURSE INP_FILES inc_private/*.h)

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)

add_library(VividCore ${SRC_FILES}) # ${INC_FILES} ${INP_FILES}
set_target_properties(VividCore PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(VividCore PUBLIC inc)
target_include_directories(VividCore PRIVATE inc_private)
target_include_directories(VividCore PUBLIC ${GLM_INCLUDE_DIR})

# All users of this library will need at least C++17
target_compile_features(VividCore PUBLIC cxx_std_17)
As you can see, we start by performing a file glob to get all source and include files. As mentioned above, we grab our private include files separately to add some security to the table (or prevent users from breaking everything).

Then we set the export symbols feature (flag) to on. This is, in my opinion, a drawback of windows compiler. All symbols for export have to be explicitly specified. It can be really tedious if you have a lot of symbols to export or use lots of templates. This flag tells CMake to internally flag all supported symbols and export them, saving the user lots of trouble dealing with them. This only applies to dynamic (shared) libraries and should do nothing when building static libraries.

After that we add the library compile command itself, giving it a name and all of the source files. Keep in mind that CMake executes after parsing all of the commands, so the library isn't being compiled yet.

Next we tell CMake that we are using C / C++ during linking. This is not mandatory and (if I remember correctly) was added as an example.

After that we include or headers, first public, then private. You can spot an additional include command for the "GLM" library. it is similar to the other ones, but uses the "GLM_INCLUDE_DIR" variable.

Lastly, we add a directive for CMake to add the language version flag during compilation. For now we will be using C++ 17.

VividGame

Our game example, that uses our engine, is very similar and simple.
file(GLOB_RECURSE SRC_FILES src/*.cpp)
file(GLOB_RECURSE INC_FILES inc/*.h)

add_executable(VividGame ${SRC_FILES})
set_target_properties(VividCore PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(VividGame PRIVATE glfw)
target_link_libraries(VividGame PRIVATE VividCore)

target_compile_features(VividGame PUBLIC cxx_std_17)
We add an executable of the game, then link our library and the "GLFW" library. When linking libraries, their public includes, settings, etc. are merged.

VividTests

Again, our tests project is very similar to the previous ones.
file(GLOB_RECURSE SRC_FILES src/*.cpp)
file(GLOB_RECURSE INC_FILES inc/*.h)
file(GLOB_RECURSE INP_FILES inc_private/*.h)

add_executable(VividTests ${SRC_FILES})
set_target_properties(VividCore PROPERTIES LINKER_LANGUAGE CXX)

target_link_libraries(VividTests PRIVATE VividCore)
target_include_directories(VividTests PRIVATE inc)

target_compile_features(VividTests PUBLIC cxx_std_17)
As before, we glob the files, link the libraries and produce an executable. Remember, that all of our generated files will be stored in the "bin" folder.

Libs and Git submodules

It is time for us to add a few libraries, that we will be using.

Catch2

Let's start with the simplest one - "Catch2". It is a library for unit testing on C++ and a very versatile one at that. Although it can be included inside our root project using CMake, it resides inside a single header file and can be easily downloaded and put into the "VividTests" includes folder. I don't think I'll have a need to update it in the future, because it seems to work totally fine with everything I throw at it, so let's leave it at that.

GLFW, GLM and ImGUI

We should discuss first, what these libraries do. "GLFW" is a window and event context handler library. It can manage window resizing, multiple screens, button and joystick input and so on. "GLM" is a vector and matrix math library. It was modelled after the GLSL math and can be transfered to the GPU with a few lines of code. "ImGUI" is a highly-portable immediate-mode GUI library. It has an incredibly low entry point, which makes it perfect for beginners. It also has a lot of examples and a few tutorials. These reasons made it perfect for my needs. We will use these libraries in the following posts.

The only thing left is to actually import these libraries from GitHub or other sources. While "GLFW" provides a simple CMake file that lets you link it in two lines of code (add_subdirectory and target_link_libraries), "GLM" does not, which makes everything a bit more difficult. However, I managed to find an old finder script, that globs all the header files, inside a deprecated "GLM" repository on GitHub. That is why you can find a "modules" folder inside my "Libs" folder on this engines repo. It contains all finder and other scripts, related to our engine.

The command to import these GitHub repositories is pretty simple. It allows us to update them, when needed, and to save disk space during commits and pushes, as they are ignored by Git. Of course, it is run from the root directory.
git submodule add https://github.com/glfw/glfw.git Vivid-Project/Libs/glfw
git submodule add https://github.com/g-truc/glm.git Vivid-Project/Libs/glm
git submodule add https://github.com/ocornut/imgui.git Vivid-Project/Libs/imgui

Conclusion

This article was not easy to write because of the dramatic ammount of text, but I am proud of myself. Although many people may find most of the info here redundant and plain stupid, after a few years styding the subject and teaching others I found out, that explaining things as simply as possible gives others their best results.

I hope you found something useful within this post. Rest assured, that next posts will lean to the engine design and fevelopment and away from this chatting about tools, libraries, etc. Next post will focus on memory tracking inside the engine. Go there?

Comments