Project organization and architecture
Hey everybody and welcome back to another Gravity Ace devlog.
I invited everyone to ask me what they'd like to hear about this week and got a lot of interesting suggestions. It was a great exercise because I've been working in some of this stuff for so long I've gone kind of blind to what's cool and what's boring.
One of the most asked questions had to do with how I organize my project code and assets and patterns I use for passing data around between nodes. Architecture and design stuff. So I thought I'd just give you all a quick tour of Gravity Ace and talk you through some of it. I won't have time for eveything but let's just see how far we get.
Remember, every project is different. Just because this works for me on this project doesn't mean it'll work for you on yours. That said, there are lots of patterns that will be useful no matter what you're working on. Also, try not to judge me too harshly. I'm a professional software engineer but game development on a tight schedule can be… messy.
OK. So here's my project. I do most of my coding within Godot itself but I like to use Visual Studio Code as my git client. If you're not using source control… please start using source control. It will make your life easier.
I've got a few top level folders here for the project.
asset-source has raw files for things like audio and music production, fonts, trailer videos. Most of this is used to create supplementary material like this devlog and trailers… stuff that isn't actually in the game itself. Some of it does end up in the game, like the music, but most of it doesn't.
Next I've got a
build folder that contains my build scripts and the actual builds themselves. I've got a
post-commit script that runs whenever I make a commit to source control. It takes the text from that commit and posts it to Discord for me. I've got a little Discord library. And I've got a script called
push that pushes builds to itch.io and Steam for me. I got the idea for posting commits to Discord from my buddy Max who is developing Flock of Dogs. Go check it out.
game folder holds the actual Godot project itself. We'll talk about that more in a sec.
platforms has assets I use for all of the various storefronts and social media platforms where I have a presence. It's stuff like banners and icons and screenshots and all of that stuff that you need to set up a store page.
press folder is where I'm putting contact information, little blurbs I'm writing, pitches and summaries for reviewers, that sort of stuff.
scrapbook is just a dumping ground for every screenshot and animated GIF and video I produce. I'm in the habit of making a screenshot or recording a short video just about every time I test the game and that all ends up here.
Now let's switch over to Godot and take a peek at the
There are lots of different ways to organize your files. What works for me is keeping everything together. So, for example, if you look at the
fuel-station folder you'll see I've got all of the files related to the fuel station right there. I've got the scene file itself, the GD Script file, another scene file for the GUI that appears on the HUD when you're near a fuel station, I've got all of the different textures, and the sound effects. It's all self-contained in this folder. This way, if I need to find a file related to the fuel station I know exactly where to look. And I don't need to worry about filename collisions or trying to figure out which object some random
wav file belongs to.
We've got fuel pods… cargo boxes… all my explosions are in the
booms folder. Every object has a
materials folder as needed.
Then in the root folder I've got some important scenes like the splash screen and the main gameplay scene.
Now let's take a look at an actual script and I'll show you some of the coding patterns I'm using. This is an enemy turret. One thing I do is use exported variables for certain things. I'm doing this for two reasons. The traditional reason you do this is so that you can get access to the variables in the inspector. And they show up over there. And I use that for that purpose sometimes. But for this game I mostly use it for the editor.
When you're in the editor you can double click on any object and see a list of properties. Well, those properties are these exported variables. GDScript has some neat introspection features where you can actually query a script file and see all of the methods and variables. There's a script in the editor that looks for these variables and turns them into GUI controls. This basically replicates the functionality in the Godot inspector.
I use this same introspection feature when I save levels. When I save a level, I enumerate each node in the level and use introspection to get all of the exported variables and save their values. I also save the position and rotation of each node. The level save format itself uses Godot's built-in
Configfile class for serializing all of the data.
I also use signals and callbacks for everything. I'm trying to use the engine in the way it was intended and my gut feeling is that using signals and callbacks is going to give the best performance.
For example, if you want to test if something is near the player you could use the
_process() method and calculate the distance to the player each frame and then do something when you're within range. That'll work fine. But that makes your code run every frame and you're doing these calculations every frame.
A better way is to create an
Area2D node with the collision mask set to the player, add a circle collision shape with it's radius set to the distance you want to detect, and use the
_on_body_entered callback. Then you don't need any code in
_process() at all. The engine does all the calculations and just lets your code know when the player is in range.
Another pattern I use is based on the object-oriented nature of the scene tree. Every script is a class. And every node is like a self-contained object with it's own methods and data. So if I have some functionality that I might want to use in several object I'll create it as a node and save it as its own scene. Then I'll include that node in whatever scenes need it.
A good example are the icons that show up objects that can be attached to the tractor beam. Firstly each beamable object is in the
beamable group. I use that when I'm checking to see what can be beamed. Each beamable object has a
BeamTarget scene. It's an
Area2D. That scene takes care of drawing itself, playing its own animations, and detecting when the player is in range. So I can just parent it to any beamable object and I get these cool icons whenever the player is in range.
I could go on and on but there's too much to talk about and this video is going a little long. But DON'T GO YET! I've got some quick announcements!
We're having our SECOND Ambitious Indie Game Night in beautiful Long Beach California on February 17th. If you are in the area please come. There will be a bunch of indie games there for you to play and the beer at Ambitious Ales is fantastic. The first one was a BLAST and I met SO MANY COOL PEOPLE there from a LOT of different industries. You should DEFINITELY come. There's a link to the event in the description.
I'm also doing a beta test of Gravity Ace RIGHT NOW. If you're interested in testing the game and you can provide some feedback then please come join the Discord server and request a beta key. Go to GRAVITY ACE DOT COM for the link.
That's it for now. As always keep those questions coming and please help spread the word about Gravity Ace. Thanks for watching and see you next time!
Published February 2, 2020