An FPS playground for creating game prototypes in C using ECS. The ECS architecture is based on Goodluck. Prior attempt: breakoutc, OpenGLESApp1.
FirstPerson uses vcpkg to manage dependencies. When you first clone it, you'll need to run the bootstrap script once. It's called bootstrap-vcpkg.bat
on Windows and bootstrap-vcpkg.sh
on macOS and Linux.
-
(If you haven't already) Install
vcpkg
anywhere on your disk andcd
into it.- Run
.\bootstrap-vcpkg.bat
to buildvcpkg
. - Run
.\vcpkg.exe integrate install
to inform Visual Studio of the location ofvcpkg
.
- Run
-
Install the dependencies:
$ .\vcpkg.exe install glew:x64-windows sdl2:x64-windows sdl2-image:x64-windows
-
Open the repo in Visual Studio 2019 using the Open a local folder option.
-
Build
FirstPerson.exe
. -
Run
out\Windows\x64-Debug\FirstPerson.exe
orout\Windows\x64-Release\FirstPerson.exe
.
-
(If you haven't already) Install
vcpkg
in/opt/vcpkg
andcd
into it.-
Run
.\bootstrap-vcpkg.sh
to buildvcpkg
. -
If you choose a different location:
- Pass
-DCMAKE_TOOLCHAIN_FILE=[path to vcpkg]/scripts/buildsystems/vcpkg.cmake
to thecmake ..
command below. - (Visual Studio Code) Set the toolchain path in
settings.json
.
- Pass
-
-
Install the dependencies:
$ ./vcpkg install sdl2 sdl2-image cgltf # For macOS $ ./vcpkg install sdl2 sdl2-image glew cgltf # For Linux
-
If the dependencies fail to build, you might need to install some extra headers. On Ubuntu:
$ sudo apt install libgl-dev libxext-dev
-
-
cd
into the repo and runcmake
as usual:$ mkdir build $ cd build $ cmake .. -DCMAKE_BUILD_TYPE=Debug # Or: Release, MinSizeRel, RelWithDebInfo $ make
-
Run it!
$ ./build/FirstPerson
Entity 0 is a special "null" entity. When used as Transform.parent
, it signifies that the transform doesn't have a parent.
For simplicity, Transform.children
is a static array and MAX_CHILDREN
is 2. The value of the parent field and the values of the children array are entity indices rather than pointers to actual Transforms.
Forward declarations are used for most structs when they're used as opaque pointers. This helps avoid creating extra dependencies between header files. For the same reason, I avoided typedefs, except for: entity
, signature
, component data structs and math data types.
struct world
stores the component data of the current scene. It persists until the app is killed. struct client
is similar to the Game
class from Goodluck in TypeScript, and it stores data and references related to the GL context, the viewport and the input. Importantly, client is destroyed every time the app is sent to backgournd, and recreated every time it resumes. All shaders and vertex buffers are recreated too.
The math library is based on gl-matrix used in Goodluck. The vector and matrix data types are typedef'ed arrays. I might try structs at some point in the future. No effort to use SIMD has been made.
The code is currently written with a single thread of execution in mind. Specifically, I used a few static variables in some systems to store data between frames. In the future, I'd like to consider introducing storage for system-wide data.
I've used clang-format
to help maintain the consistency in the code base. I've tried something new, too: there's no limit on line width. I'm starting to think that deciding where to put line breaks is an integral part of the process of writing code, and that it shouldn't be automated. It's a hypothesis which I'm currently testing, and I might change my opinion on it in the future.