The Visual Update | denizk0461

As promised, I've been working on a few visual things! There's not that much to show yet, but I have a model or two to show, plus a lot more to talk about in terms of where I want to take the game.

Before this, though, here's some progress in the coding department (that's where I'm best at):

Simplifying Node Chains

Behind-the-scenes, I've been steadily working at overhauling and refactoring some of my code. I wrote the majority of my game's scripts 6 to 12 months ago, and some of it hasn't aged that well. Complicated code structures and wonky node paths such as get_node("/root/Main/LevelParent/Level/Camera/AimRayCastContainer/AimRayCast3D") aren't things I want to maintain, so I'm trying my best to implement more best-practices – or at least better-practices.

One challenge I faced was referencing a node that has no clear direct relationship to the one I'm calling it from. Picture this node chain:

Say, for example, that I'm writing a script in Automatic – that's the node that handles projectile spawning and their direction in automatic weapons, among other things – and I need to reference AimRayCast3D to determine in which direction the projectile needs to be fired. Reasonable use case, seen as the RayCast3D is attached to the Camera3D to point exactly where the player is facing – except there's no real path I can follow to get to that node. Their closest relative is Level, which is the root node of the level, and no less than eight nodes away from Automatic! It's possible to retrieve the node in this way, starting from Automatic, but it's not pretty...

var _ray_cast = get_node("../../../../../../../../Camera/AimRayCastContainer/AimRayCast3D")

Imagine if any of these nodes has their name changed – or worse, if the order of nodes is changed. It could spell disaster. This method grabs the node relative to Automatic, which means it relies on this specific count of nodes as well as the names of the nodes, starting at Camera. We could switch this around:

var _ray_cast = get_node("/root/Main/LevelParent/Level/Camera/AimRayCastContainer/AimRayCast3D")

...but now we're reliant on the order starting from the root node (from the top), plus we now have to be wary of even more node names, so this is no good either. For quick testing, sure, but not to be implemented in a production release – hopefully. How do we fix this? I thought of using a singleton script that holds references to the most important nodes (level parent, player, camera) to retrieve nodes relative to them:

var _ray_cast = GlobalReferences.camera.get_node("AimRayCastContainer/AimRayCast3D")

This is a lot more streamlined, but... it doesn't work. You need to initialise the camera variable at some point after the level has loaded, but before the variable is used. If you use the variable in any _ready() call, then you're hosed, because you must initialise the variables in the level's _ready() and you cannot be certain that the level has its _ready() function called in time for the other node to retrieve a proper reference in camera instead of null.

Instead, we can use the function get_first_node_in_group() on the SceneTree to retrieve any node! We just have to make sure that the node has a group name that is unique to it within the SceneTree – in the case of a level camera, that's a given. Having multiple nodes with this group name is also possible by using get_nodes_in_group() and then filtering the returned array by, for example, checking its class name.

var _ray_cast = get_tree().get_first_node_in_group("level_camera").get_node("AimRayCastContainer/AimRayCast3D")

Almost there! We can pretty this up by adding two more things:

  • Turn "level_camera" into a constant. I did this by creating a global class called Groups and adding:
    const LEVEL_CAMERA: String = "level_camera"
    This simplifies changing group names, though keep in mind that this constant cannot be used inside the node's group assignment, meaning that, if you do change the name, you will have to change it in at least two places (the node and the constant).
  • Remove the get_node("AimRayCastContainer/AimRayCast3D") call and replace it with a variable call. Inside the camera's script, we can add a variable referencing AimRayCast3D quite easily because it's a child of the camera. Add to the camera script:
    @onready var aim_ray_cast: RayCast3D = $AimRayCastContainer/AimRayCast3D
    And then use camera.aim_ray_cast to retrieve it. Remember to add @onready because the node cannot be retrieved before _ready() is called.

In the end, we have the following statement:

var _ray_cast = get_tree().get_first_node_in_group(Groups.LEVEL_CAMERA).aim_ray_cast

We can use this anywhere in our game to retrieve the node, as long as it exists within the scene tree!

ButtMuncher7's Approach to Projectiles

A while back, ButtMuncher7 on YouTube uploaded a video on projectiles in video games. They use the Godot engine as well, so anything they implement, I can implement as well. The issue they faced was that projectiles often fly through walls a bit before colliding, since they may move past an object's surface between frames. I finally got around to implementing their solution, which worked quite well: you add a raycast to the projectile pointing from its current location to its last location, see if collides with anything, and if it does, you move the projectile to that position. It's quite a neat solution!

Lighten the Mood

I changed the sky to be brighter and more friendly in the Unity test level. This doesn't serve the game in its final form in any way, it's just a measure I took to give myself the impression that a lot has changed, since the game has visually changed.

Interestingly though, this did give me ideas. This sky shader has a setting for cloud fuzziness, which, when turned down, gives the clouds a more toon-like aesthetic (pictured here). This is great, because this is actually an aesthetic direction I had recently decided to pursue with Project N5.

Create New World

I discovered this insanely cool planet generator by Deep-Fold on itch.io. It's a pixel planet generator, strictly speaking, but since its output is just determined by generated noise (I think), the resolution can be increased much further to create sharp toon-like planets. Here's one I generated that I quite liked: