-
Posted on
Writing GDScript with Neovim
Neovim is by far my favorite text editor. The clutter-free interface and keyboard-only navigation are what keep me productive in my daily programming. In an earlier post, I explained how I configure it into a minimalist development environment. Today, I will show you how to use it with Godot and GDScript.
Configure Godot
First, we need to tell Godot to use nvim as a text editor instead of the built-in one. Open Godot, and head to
Editor Settings > General > Text Editor > External
. There, you will need to tick the boxUse external editor
, indicate your Neovim installation path, and use--server /tmp/godothost --remote-send "<C-\><C-N>:n {file}<CR>{line}G{col}|"
as execution flags.While in the settings, head to
Network > Language Server
and note down the remote port Godot is using. By default, it should be 6005. We will need that value later.Connecting to Godot with vim-godot
Neovim will be able to access Godot features by using a plugin called vim-godot. We will need to edit the nvim configuration file to install plugins and configure Neovim. On Mac and Linux, it is located at
~/.config/nvim/init.vim
I use vim-plug to manage my plugins, so I can just add it to my configuration like this:
call plug#begin('~/.vim/plugged') " ... Plug 'habamax/vim-godot' " ... call plug#end()
Once the configuration file is modified and saved, use the
:PlugInstall
command to install it.You’ll also need to indicate Godot’s executable path. Add this line to your init.vim:
let g:godot_executable = '/Applications/Godot.app/Contents/MacOS/Godot'
For vim-godot to communicate with the Godot editor, it will need to listen to the
/tmp/godothost
file we configured in the editor previously. To do that, simply launch nvim with the flag--listen /tmp/godothost
.To save you some precious keypress, I suggest creating a new alias in your bashrc/zshrc like this:
alias gvim="nvim --listen /tmp/godothost"
Getting autocompletion with coc.nvim
Godot ships with a language server. It means the Godot editor can provide autocompletion, syntax highlighting, and advanced navigation to external editors like nvim.
While Neovim now has built-in support for the language server protocol, I’ve used the plugin
coc.nvim
to obtain these functionalities for years and see no reason to change. You can also install it with vim-plug by adding the following line to your plugin list:Plug 'neoclide/coc.nvim', {'branch':'release'}
Run
:PlugInstall
again to install it.You’ll need to indicate the Godot language server address and port using the command
:CocConfig
. It should open Coc’s configuration file, which is a JSON file normally located at~/.config/nvim/coc-settings.json
. In this file enter the following data, and make sure the port number matches the one located in your editor:{ "languageserver": { "godot": { "host": "127.0.0.1", "filetypes": ["gdscript"], "port": 6005 } } }
I recommend adding Coc’s example configuration to your init.vim file. You can find it on GitHub. It will provide you with a lot of useful shortcuts, such as using
gd
to go to a function definition andgr
to list its references.Debugging using nvim-dap
If you want to use the debugger from inside Neovim, you’ll need to install another plugin called nvim-dap. Add the following to your plugins list:
Plug 'mfussenegger/nvim-dap'
The plugin authors suggest configuring it using Lua, so let’s do that by adding the following in your init.vim:
lua <<EOF local dap = require("dap") dap.adapters.godot = { type = "server", host = "127.0.0.1", port = 6006, } dap.configurations.gdscript = { { type = "godot", request = "launch", name = "Launch scene", project = "${workspaceFolder}", launch_scene = true, }, } vim.api.nvim_create_user_command("Breakpoint", "lua require'dap'.toggle_breakpoint()", {}) vim.api.nvim_create_user_command("Continue", "lua require'dap'.continue()", {}) vim.api.nvim_create_user_command("StepOver", "lua require'dap'.step_over()", {}) vim.api.nvim_create_user_command("StepInto", "lua require'dap'.step_into()", {}) vim.api.nvim_create_user_command("REPL", "lua require'dap'.repl.open()", {}) EOF
This will connect to the language server (here on port 6005), and allow you to pilot the debugger using the following commands:
:Breakpoint
to create (or remove) a breakpoint:Continue
to launch the game or run until the next breakpoint:StepOver
to step over a line:StepInto
to step inside a function definition:REPL
to launch a REPL (useful if you want to examine values)
Conclusion
I hope you’ll have a great time developing Godot games with Neovim. If it helps you, you can check out my entire init.vim file on GitHub gist.
-
Posted on
Stuff I've been working on
It’s been around 2 years that I’ve had to stop with my long-term addiction to stable jobs. Quite a few people who read this blog are wondering what the hell exactly I’ve been doing since then so I’m going to update all of you on the various projects I’ve been working on.
Mikochi
Last year, I created Mikochi, a minimalist remote file browser written in Go and Preact. It has slowly been getting more and more users, and it’s now sitting at more than 200 GitHub stars and more than 6000 Docker pulls.
I personally use it almost every day and it fits my use case perfectly. It is basically feature-complete so I don’t do too much development on it.
I’ve actually been hoping users help me solve the few remaining GitHub issues. So far it happened twice, a good start I guess.
Itako
You may have seen a couple of posts on this blog regarding finance. It’s a subject I’ve been trying to learn more about for a while now. This led me to read some excellent books including Nassim Taleb’s Fooled by Randomness, Robert Shiller’s Irrational Exuberance, and Robert Carver’s Smart Portfolios.
Those books have pushed me toward a more systematic approach to investing, and I’ve built Itako to help me with that. I’ve not talked about it on this blog so far, but it’s a SaaS software that gives clear data visualizations of a stock portfolio performance, volatility, and diversification.
It’s currently in beta and usable for free. I’m quite happy that there are actually people using it and that it seems to work without any major issues. However, I think making it easier to use and adding a couple more features would be necessary to make it into a commercially viable product.
I try to work on it when I find the time, but for the next couple of months, I have to prioritize the next project.
Dice’n Goblins
I play RPGs too much and now I’m even working on making them. This project was actually not started by me but by Daphnée Portheault. In the past, we worked on a couple of game jams and produced Cosmic Delusion and Duat. Now we’re trying to make a real commercial game called Dice’n Goblins.
The game is about a Goblin who tries to escape from a dungeon that seems to grow endlessly. It’s inspired by classic dungeon crawlers like Etrian Odyssey and Lands of Lore. The twist is that you have to use dice to fight monsters. Equipping items you find in the dungeon gives you new dice and using skills allows you to change the dice values during combat (and make combos).
We managed to obtain a decent amount of traction on this project and now it’s being published by Rogue Duck Interactive. The full game should come out in Q1 2025, for PC, Mac, and Linux. You can already play the demo (and wishlist the game) on Steam. If you’re really enthusiastic about it, don’t hesitate to join the Discord community.
Technically it’s quite a big change for me to work on game dev since I can’t use that many of the reflexes I’ve built while working on infra subjects. But I’m getting more and more comfortable with using Godot and figuring out all the new game development related lingo. It’s also been an occasion to do a bit of work with non-code topics, like press relations.
Japanese
Something totally not relevant to tech. Since I’ve managed to reach a ‘goed genoeg’ level of Dutch, I’ve also started to learn more Japanese. I’ve almost reached the N4 level. (By almost I mean I’ve failed but it was close.)
I’ve managed to learn all the hiraganas, katakanas, basic vocabulary, and grammar. So now all I’ve left to do is a huge amount of immersion and grind more kanjis. This is tougher than I thought it would be but I guess it’s fun that I can pretend to be studying while playing Dragon Quest XI in Japanese.
-
Posted on
How to publish your Godot game on Mac
Since 2019, Apple has required all MacOS software to be signed and notarized. This is meant to prevent naive users from installing malware while running software from unknown sources. Since this process is convoluted, it stops many indie game developers from releasing their Godot games on Mac. To solve this, this article will attempt to document each and every step of the signing and notarization process.
Step 0: Get a Mac
While there tools exists to codesign/notarize Mac executables from other platforms, I think having access to a MacOS machine will remove quite a few headaches.
A Mac VM, or even a cloud machine, might do the job. I have not personally tested those alternatives, so if you do, please tell me if it works well.
Step 1: Get an Apple ID and the Developer App
You can create an Apple ID through Apple’s website. While the process should be straightforward, it seems like Apple has trust issues when it comes to email from protonmail.com or custom domains. Do not hesitate to contact support in case you encounter issues creating or logging into your Apple ID. They are quite responsive.
Once you have a working Apple ID, use it to log into the App Store on your Mac and install the Apple Developer application.
Step 2: Enroll in the Apple Developer Program
Next, open the Apple Developer app, log in, and ask to “Enroll” in the developer program.
This will require you to scan your ID, fill in data about you and your company, and most likely confirm those data with a support agent by phone. The process costs ~99$ and should take between 24 and 48 hours.
Step 3: Setup Xcode
Xcode will be used to codesign and notarize your app through Godot. You should install the app through the App Store like you did for the Apple Developer application.
Once the app is installed, we need to accept the license.
First, launch Xcode and close it. Then open a terminal and run the following commands:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer sudo xcodebuild -license accept
Step 4: Generate a certificate signing request
To obtain a code signing certificate, we need to generate a certificate request.
To do this, open Keychain, and from the top menu, select Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority. Fill in your email address and choose Request is: Saved to disk. Click on Continue and save the .certSigningRequest file somewhere.
Step 5: Obtain a code signing certificate
Now, head to the Apple Developer website. Log in and go to the certificate list. Click on the + button to create a new certificate. Check Developer ID Application when asked for the type of certificate and click on continue.
On the next screen, upload the certificate signing request we generated in step 4.
You’ll be prompted to download your certificate. Do it and add it to Keychain by double-clicking on the file.
You can check that your certificate was properly added by running the following command:
security find-identity -v -p codesigning
It should return (at least) one identity.
Step 6: Get an App Store Connect API Key
Back to the Apple Developer website, go to Users and Access, and open the Integrations tab. From this page, you should request access to the App Store Connect API. This access should normally be granted immediately.
From this page, create a new key by clicking on the + icon. Give your key a name you will remember and give it the Developer access. Click on Generate and the key will be created.
You will then be prompted to download your key. Do it and store the file safely, as you will only be able to download it once.
Step 7: Configure Godot
Open your Godot project and head to the project settings using the top menu (Project > Project Settings). From there search for VRAM Compression and check Import ETC2 ASTC.
Then make sure you have installed up-to-date export templates by going through the Editor > Manage Export Templates menu and clicking on Download and Install.
To export your project, head to the Project > Export. Click on Add and select macOS to create new presets. In the presets form on the left, you’ll have to fill in a unique Bundle Identifier in the Application section, this can be
com.yourcompany.yourgame
.In the Codesign section, select Xcode codesign and fill in your Apple Team ID and Identity. Those can be found using the
security find-identity -v -p codesigning
command: the first (~40 characters) part of the output is your identity, and the last (~10 characters, between parentheses) is your Team ID.In the Notarization section, select Xcode notarytool and fill in your API UUID (found on the appstoreconnect page), API Key (the file you saved in Step 6), and API Key ID (also found on the appstoreconnect page).
Click on Export Project… to start the export.
Step 8: Checking the notarization status
Godot will automatically send your exported file for notarization.
You can check the notarization progress by running:
xcrun notarytool history --key YOUR_AUTH_KEY_FILE.p8 --key-id YOUR_KEY_ID --issuer YOUR_ISSUER_ID
According to Apple, the process should rarely take more than 15 minutes. Empirically, this is sometimes very false and the process can give you enough time to grab a coffee, bake a cake, and water your plants.
Once the notarization appears completed (with the status Valid or Invalid), you can run this command to check the result (using the job ID found in the previous command output):
xcrun notarytool log --key YOUR_AUTH_KEY_FILE.p8 --key-id YOUR_KEY_ID --issuer YOUR_ISSUER_ID YOUR_JOB_ID
Step 9: Stapling your executable
To make sure that your executable can work offline, you are supposed to ‘staple’ the notarization to it. This is done by running the following command:
xcrun stapler staple MY_SOFTWARE.dmg
Extra: Exporting the game as .app
Godot can export your game as .dmg, .zip, or .app. For most users, it is more convenient to receive the game as .app, as those can be directly executed. However, the notarization process doesn’t support uploading .app files to Apple’s server.
I think the proper way to obtain a notarized .app file is to:
- Export the project .dmg from Godot with code signing and notarization
- Mount the .dmg and extract the .app located inside of it
- Staple the .app bundle
Extra: Code signing GodotSteam
GodotSteam is a Godot add-on that wraps the Steam API inside GDscript. If you use it, you might encounter issues during notarization, because it adds a bunch of .dylib and .framework files.
What I did to work around that was to codesign the framework folders:
codesign --deep --force --verify --verbose --sign "Developer ID Application: My Company" libgodotsteam.macos.template_release.framework codesign --deep --force --verify --verbose --sign "Developer ID Application: My Company" libgodotsteam.macos.template_debug.framework
I also checked the options Allow DyId environment variable and Disable Library Validation in the export settings (section Codesign > Entitlements).
FAQ: Is this really necessary if I’m just going to publish my game on Steam?
Actually, I’m not 100% sure, but I think it is only “recommended” and Steam can bypass the notarization. Steamworks does contain a checkbox asking if App Bundles Are Notarized, so I assume it might do something.
-
Posted on
Create a presskit in 10 minutes with Milou
Talking to the press is an inevitable part of marketing a game or software. To make the journalist’s job easier, it’s a good idea to put together a press kit. The press kit should contain all the information someone could want to write an article about your product, as well as downloadable, high-resolution assets.
Introducing Milou
Milou is a NodeJS software that generates press kits in the form of static websites. It aims at creating beautiful, fast, and responsive press kits, using only YAML configuration files.
I built it on top of presskit.html, which solved the same problem but isn’t actively maintained at the moment. Milou improves on its foundation by using a more modern CSS, YAML instead of XML, and up-to-date Javascript code.
Installation
First, you will need to have NodeJS installed:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash nvm install 22
Once Node is ready, you can use NPM to install Milou:
npm install -g milou
Running
milou -V
should display its version (currently 1.1.1).Let’s build a press kit
Let’s create a new project:
mkdir mypresskit cd mypresskit milou new
The root directory of your project will be used for your company. In this directory, the file
data.yml
should contain data about your company, such as your company name, location, website, etc… You can find an example of a fully completed companydata.yml
file on GitHub. To validate that your file is a valid YAML file, you can use an online validator.Your company directory should contain a sub-folder called
images
, you should put illustrations you want to appear in your press kit inside it. Any file namedheader.***
will be used as the page header,favicon.ico
will be used as the page favicon, and files prefixed by the wordlogo
will appear in a dedicated logo section of the page (eg.logo01.png
orlogo.jpg
). Other images you put in this folder will be included in your page, in the Images section.After completing your company page, we can create a product page. This will be done in a subfolder:
mkdir myproduct cd myproduct milou new -t product
Just like for a company, you should fill in the
data.yml
file with info about your product, like its title, features, and prices. You can find an example of a product file on GitHub. The product folder should also contain animages
subfolder. It works the same way as for the company.When your product is ready, go back to the company folder and build the press kit:
cd ../ milou build .
This will generate the HTML and CSS files for your online presskit in the directory
build
. You can then use any web server to access them. For example, this will make them accessible fromhttp://localhost:3000/
cd build npx serve
To put your press kit online, you can upload this folder to any static site host, like CloudFlare Pages, Netlify, or your own server running Nginx.
Conclusion
Milou is still quite new, and if you encounter issues while using it, don’t hesitate to open an issue. And if it works perfectly for you, leave a star on GitHub.
-
Posted on
How to solve it (with raycasting)
In 1945, mathematician George Pólya released the book “How to solve it”. It aims at helping math teachers guide their students into solving abstract problems by asking the right questions. It has since had a large influence on math education and computer science, to the point of being mentioned by Marvin Minksy as a book that everyone should know.
In this post, I will try to see how we can use Pólya’s methodology to solve a concrete software engineering problem: rendering 3D objects (using Golang).
Understanding the problem
Before starting to solve the problem, we must make sure that we completely understand the problem. The first question to ask ourselves is What is the data?
The data is a 3D object. The object is made out of triangles. Each triangle is made out of 3 vertices (and a normal vector). Those objects are stored in .STL files. I will parse them with the hschendel/stl lib.
The second question, which is probably the most important is What is the unknown?. Or in programming terms, What should the program output?
Our program should output an image. An image is a 2D matrix of pixels, each pixel representing a color. The most common way of representing color is the RGBA model, which stands for Red, Green, Blue, and Alpha. In Golang, images can be represented using the
image.Image
data structure from the standard library.The third question is What is the condition (linking the data to the output)?
The data gives us information about the space occupied by our 3D object. If the 3D object is in front of our pixel, this pixel should be in a different color. We will use the method known as “raycasting” which consists of sending a ray from each pixel, and checking what the ray hits.
Devise a plan
Now that we have understood our problem a little bit better, we should try to plan what our solution will look like. The most helpful question to come up with a solution is Do you know a related problem?
Raycasting constists of sending a “ray” for each pixel of our image. If this ray intersects with our 3D object, the pixel needs to be updated to a different color. Since our 3D object is made entirely out of triangle, a related problem would be Does a vector intersect with a triangle?
To solve this we can implement the Möller–Trumbore intersection algorithm. This algorithm transforms the above problem into two new questions Does the ray intersect with the triangle’s plane? and if yes, Does the ray-plane intersection lie outside the triangle?
This first question is simple to solve, the only way a vector doesn’t intersect with a plane is if the vector and plane are parallel. In that case, the dot product of the ray and the triangle’s normal vector would be zero, since the dot product of two perpendicular vectors is 0 and the normal vector is itself perpendicular to the triangle’s plane.
If the ray intersects with our triangle’s plane, then we can check if the intersection is inside the plane by switching to barycentric coordinates. Barycentric coordinates are a way to represent a point in a plane in relation to the vertices of the triangle. Each corner of the triangle will get the coordinates (0,0,1), (0,1,0) and (1,0,0). Any point outside of the triangle will get coordinates outside of the range [0,1].
Now that we know an algorithm that can solve our main issue, we can come up with the outline of our program:
func MTintersect(ray, triangle) bool { if isParallel(ray, triangle) { return false } u , v := projectBaryocentric(vec3, triangle) return u > 0 && u < 1 && v > 0 && u + v < 1 } func main () { solid := readSTL() image := newImage(width, height) for i := range width { for j := range height { image.Set(i, j, white) ray := castRay(i, j) for triangle := range solid.Triangles { ok := MTintersect(ray, triangle) if ok { image.set(i, j, blue) } } } } writePNG(image) }
Carrying out the plan
This is the easy part. We just write the code.
The main suggestion that Pólya makes, is to check that every step of the solution is correct. While programming, this can be achieved by writing unit tests to ensure the correctness of our code.
Looking back
Once we have something that seems to work it is tempting to just
git push
and call it a day. But there are a few more questions we should ask ourselves.First Can we check the result?
A good way to answer that is to test our program ourselves, either by manually going through a checklist or by writing an integration test that covers our problem.
Then we should ask ourselves Can we derive the result differently?
This question is not only a good way to learn about other ways to solve our problem (like Scanline rendering in our case) but also a good opportunity to check if maybe the code we wrote was not the most intuitive solution and could be refactored.
The last question is Can you use the result for another problem?
We can answer this question by checking if our code is written in a way that is reusable enough if we ever want to. For example, the raycaster above could be used as the first step into the implementation of a more sophisticated ray tracing algorithm, if we wanted to handle reflections and lightning.
Conclusion
If you want to check the source code for the raycaster I made before writing this article, it is on my GitHub.
You can find How to solve it by Pólya in any good library.
To learn more about computer graphics check out Ray Tracing in a weekend. And for the details of the Möller-Trumbore algorithm, this video is the one that made the most sense to me.
subscribe via RSS