Devlog - Intro and Entities
Wednesday, March 31, 2021
According to the git log, I have a project I've been working on off and on since April 24th, 2020 that I haven't talked about on my blog yet. I've been working on it more and more over the last month or two, so I'd like to start writing devlogs as I go along, making this the first one I write on it.
The project is a rail shooter of sorts, heavily inspired by Starfox and Panzer Dragoon. If you're unfamiliar, the general idea is the player is control of a ship or vehicle of some kind that is always moving forward at a steady speed, firing at enemies along the way and dodging various obstacles. The key differentiator is that I'm building it as a VR game, where rather you use your hands to fly the ship and aim your weapons. It all started with a little prototype I built over the course of a week or so, just prototyping some really simple stuff like moving a little ship towards your one hand and moving a blocky environment past you, giving it a sense of flight. I thought it was cool enough to continue work on it so I kept at it for a while. Then, like with most side projects, it'd get dropped for a few weeks, I'd come back and rewrite some things, then drop it again. All very protoype-y, neat but nothing to really share.
That repeated a few times, and then finally about two months ago I started getting much more into it. I've begun creating tickets for tasks to help keep myself from feeling overwhelmed or lost. I put a good bit of time into really working out the architecture of the project, so I can more rapidly prototype concepts and put more time into content. Part of that tasking and content is writing posts about progress, so here we are.
I'd also like to take a moment here to thank the Godot team for doing some fantastic work on this engine. It's been a lot of fun learning and tinkering with it - it's nice when the tools you use to make something are nice to work with themselves. Also huge thanks to Bastiaan Olij (and all the project contributors) for the OpenVR plugin that's making my VR work with Godot possible.
Part of all the work and rework I've been doing is trying to find a way to speed up prototyping so I can make things do things faster. The less roadblocks I have to trying out a new idea, the more encouraged I'll be to try out new ideas. A big part of this is the Entity scene I've established for any element in the game I want to interact with others. I've attempted other structures like this previously, but I don't think it was until the last couple of weeks it really clicked.
In Godot, you have what are referred to as Scenes. The best way to think of a Scene is as an object in an OOP framework. You have a base class (in Godot this is a Node) that you can then extend via inheritance to create a more specific object. For a long time, my thoughts on this were to take one of the provided Node types and extend it entirely with scripting, and then inherit that new scripted Node to create other objects. While it works, I lost one major benefit of Scenes - the ability to instance and inherit from them in the editor.
Every Scene in Godot (down to the editor itself) lives inside a SceneTree. Nodes are children of parent Nodes going all the way up (down?) to the root of the tree. While each Node is a class itself in a way, you can also treat a single Node and it's children as one whole object. This parent Node and set of children can be spun off into their own scene and then instanced in other scenes over and over again, in the same way you'd write a class and create instances of it in other languages/frameworks. Now, not only can you easily instance these Scenes in Godot, you can extend them on a per-instance basis by adding new child Nodes to them. It's the same as inheriting a class and providing new functionality, except here you can do it quickly and visually with other Nodes in the engine.
If you're familiar with OOP principles this all probably sounds pretty obvious, but for whatever reason this specific approach didn't click with me until very recently. This lead to the creation of an
Entity Scene, a base Scene that I can inherit from to create all sorts of interactive entities in the game that all behave nicely with one another. It's a
Spatial Node, Godot's base 3D Node, with a
CollisionArea Node and custom
Health Node as children.
CollisionArea is an extension of the
Area Node used to define the collision layer the entity exists on and what layers it's checking for collisions on. It also includes some data that is provided to other objects when they collide with it, like how much damage a colliding object should take (if any) on impact. This is useful for ships colliding with each other or running into buildings.
Health is a GDScript extending the base Node, and it manages the health of the object. You provide a maximum health value, a length of time the entity should be invincible for following taking damage, and a checkbox for flagging an entity as invincible. There's a
take_damage() method that is used to apply damage to the entity, and two signals (Godot's built-in implementation of the observer pattern) for announcing when it's taken damage and when it's been destroyed.
The basic player projectile a good simple example of the Entity inheritance/usage. The parent node handles logic for moving the projectile, spawning a small AudioStreamPlayer3D scene that plays a brief sound effect when spawned, and the lifespan of the projectile. The Entity Scene is a child, along with a .glb placeholder model representing the projectile. The Entity is configured so it can impact anything on the Enemy, EnemyProjectile, and Obstacle layers. On impact, it takes whatever impact damage the Entity it's hitting does and applies it's own impact damage to the other Entity. For the most part, this means anything it hits "kills" the projectile while simultaneously doing 1 damage to the Entity it hit. Enemies explode, invincible obstacles don't do anything.
Here's the player's ship Scene tree. The root
Spatial Node has a script managing movement of the ship and connecting the WeaponTargeting to the player's targeting hand. The Entity Scene is stock, with the
Health signals connected to the
HealthUI Scene, driving the progress bar representing the player's current health. ShipPlayer scene is just the .glb 3D model representing the ship.
WeaponTargeting handles the aiming of the player
Weapon, which is also a child of
WeaponTargeting. Similarly to the PlayerBasic projectile, it can take and apply impact damage when colliding with anything on the Enemy, EnemyProjectile, and Obstacle layers. The nice thing about this is you can crash the player ship into an enemy with 1 HP, killing the enemy on impact while suffering 1 damage.
This post was supposed to cover other stuff like the audio effects I've been tinkering with, or how the levels are managed, but that Entity stuff got so long I don't think I could fit any of it in here. If you liked this writeup shoot me a line via email or Twitter and let me know. I wouldn't mind writing similar posts about other components as I create them, but I also can keep future posts a little bit more high-level and brief.