itmecho

« back to blog

C, Raylib, and Nix in Neovim


This is a quick guide on how to get up and running with raylib in C using a nix flake to build the project.

Prerequisites

This isn’t a guide on how to get the nix package manager set up on you system and will assume you have it installed and working. If not, you can find instructions on the official nix website.

Creating the project

The first thing we’ll need is a project! We can start off by creating a directory and initialising a git repo and a nix flake configuration.

mkdir -p ~/src/my-game
cd ~/src/my-game
git init
nix flake init

This will give us the following default flake configuration (at the time of writing):

{
  description = "A very basic flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs = { self, nixpkgs }: {

    packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;

    packages.x86_64-linux.default = self.packages.x86_64-linux.hello;

  };
}

Setting up the nix flake configuration

Some quick tidy ups of the default configuration will give us a nicer starting point. First, set the description, then change the nixpkgs.url to use the latest stable version:

{
  description = "Basic raylib game";

  inputs = {
    nixpkgs.url = "nixpkgs/nixos-25.11"; # latest as of writing this article
  };
  
  ...
}

Now we can clean up the outputs section. We’ll remove the default packages and replace them with our own which will be in charge of building the game.

{
  # ...
  outputs = { self, nixpkgs }:
    let
      # systems we want to support
      systems = [ "aarch64-darwin" "x86_64-linux" ];
    in
    {
      # Loop over each system and define the package for it.
      packages = nixpkgs.lib.genAttrs systems (system:
        let
          pkgs = import nixpkgs { inherit system; };
        in
        rec {
          mygame = pkgs.stdenv.mkDerivation {
            pname = "mygame";
            version = "1.0";

            # The location of the source code
            src = ./src;

            # Use clang as the compiler
            nativeBuildInputs = [ pkgs.clang ];
            # Define raylib as a build input
            buildInputs = [ pkgs.raylib ];

            # Provide the command to build the game, padding -lraylib to ensure everything gets
            # linked correctly.
            buildPhase = ''
              clang -Wall -lraylib ./main.c -o mygame
            '';

            # Copy the built game into the bin directory for the output package.
            installPhase = ''
              mkdir -p $out/bin
              cp mygame $out/bin/
            '';
          };
        });
}

Lastly, we can tell nix which package to use as the default. As we only have one package per system, we will be using that!

{
  # ...
  outputs = { self, nixpkgs }: let
    systems = [ "aarch64-darwin" "x86_64-linux" ];
    in
    {
      packages = # ...

      defaultPackage = {
        aarch64-darwin = self.packages.aarch64-darwin.mygame;
        x86_64-linux = self.packages.x86_64-linux.mygame;
      };
    };

Great! Now all we’re missing is some source code to build.

Testing the build

Let’s start by creating a basic C program to test the build is working correctly. In src/main.c, add the following code:

#include <stdio.h>

int main() {
  printf("Hello there\n");
  return 0;
}

Now we have some source code to build, we need to add it to git so that nix can build it. Then we can ask nix to build the package for our current system.

git add .
nix build .

If all went well, you should now have the the build game in result/bin/mygame. If you run it, you should see the message printed to the terminal!

$ ./result/bin/mygame
Hello there

Setting up a basic raylib application

Now that we know our build is working, we can move on to writing some raylib code! We’ll just go with the basic “blue screen” to test that raylib is building and working correctly.

#include <stdio.h>
#include "raylib.h"

int main(void) {
  InitWindow(800, 450, "mygame");

  SetTargetFPS(60);

  while (!WindowShouldClose()) {
    BeginDrawing();
    ClearBackground(BLUE);
    EndDrawing();
  }

  CloseWindow();
}

Here, we’re initializing a window with the title “mygame”, setting the target FPS to 60, and running the game loop. All we’re doing in the loop is clearing the background using the BLUE colour. If we build and run it again, we should get a 800x450 window with a blue background!

Basic raylib window with a blue background.

Great, now we’ve got a basic raylib application being built as a nix flake! Now all that remains is getting set up in Neovim to make working on the code nice and easy.

Setting up Neovim

Making the libraries available

To get neovim working with autocompletion for raylib, we’ll be making use of nix dev shells. This is a shell that has access to a set of packages that may or may not be available on your system. We can define a dev shell for each system but adding the following to our flake.nix file:

  outputs = { self, nixpkgs }: let
    systems = [ "aarch64-darwin" "x86_64-linux" ];
    in 
    {
      # ...

      devShell = nixpkgs.lib.genAttrs systems (system:
        let
          pkgs = import nixpkgs { inherit system; };
        in
          pkgs.mkShell {
            nativeBuildInputs = [ pkgs.clang ];
            buildInputs = [ pkgs.raylib ];
            shellHook = ''
              echo "-I${pkgs.raylib}/include" > compile_flags.txt
            '';
          });

      # ...

    };

Here we are defining a dev shell that has both clang and raylib packages available. We also define a shellHook which runs on entering the shell. This creates a compile_flags.txt file which configures flags that are given to the compiler. More importantly, it tells clangd, the LSP we will be using, where to find the raylib headers.

Now we can enter the dev shell by running the following command:

nix develop
# Or to drop straight into your normal shell environment (in my case, fish)
nix develop --command fish

Configuring the LSP

Now that we’re in a dev shell, we can configure neovim to use the clangd LSP server for our project. I don’t want to assume much about your neovim set up so we’ll do this via the built in option. For the nvim config, we just need to configure and the enable the clangd server. To do this, add the following snippet somewhere in your neovim lua config:

vim.lsp.config["clangd"] = {
  -- Command to run for the language server
  cmd = { "clangd" },
  -- which file types to run the server for
  filetypes = { "c" },
  -- How to find the root of the project
  root_markers = { ".git", "compile_flags.txt" },
}
-- Enable the language server
vim.lsp.enable("clangd")

If you just have an init.lua with the above code, you can run nvim and check the configuration has worked by running :checkhealth lsp. You should see the following output:

vim.lsp: Enabled Configurations ~
- clangd:
  - cmd: { "clangd" }
  - filetypes: c
  - root_markers: { ".git", "compile_flags.txt" }

If you have clangd available in you system (outside of the nix dev shell), it should automatically start up when you open the src/main.c file and, if you haven’t generated the compile_flags.txt file, diplay errors because it can’t find raylib.h. To display a floating window with the first error, run :lua vim.diagnostic.goto_next().

Screenshot of neovim with errors indicating that functions from raylib.h are not available.

Now all you need to do is run nix develop and then open the src/main.c file again and you should see that the errors are gone! You can also trigger omnicompletion by pressing <C-x><C-o>, and see hover documentation by pressing K with the cursor on a function.

Screenshot demonstrating hover documentation for raylib functions in neovim. Screenshot demonstrating omnicompletion for raylib functions in neovim.

Conclusion

So now you should be all set to start working on a raylib project in neovim using nix to handle all the dependencies! From here, you can add other C libraries to your project as required and be sure that anyone else working on your game will have an easy time getting started as the development environment is all set up for them.

Hopefully this has been helpful in getting you started and you will continue to experiment with raylib and build some games!