1. Star Child
  2. News

Star Child News

Star Child Dev Log #25

Jay Ingle - lead developer, designer, and artist:

Hello, this week I will show you a deer. This is a deer that will run back and forth, pausing occasionally. If it detects the player, it will rear up, form a ball of energy between its antlers, and then fire a laser beam at you. I want to make this a small deer, a bit unassuming, with quite a powerful attack. I want the animations to be good, but to not be too natural. Not aiming for organic perfection, or too much cartoonish gesturing. The vibe should be 8 or 16-bit low frame count, and a bit awkward, slightly unusual.



This is Pyxel Edit which I use for my basic pixel art, tilesets, and animations. I use Aseprite when I need to use more powerful features, but Pyxel Edit works great for most simple things.

Step 1 for creating deer animations is to look at a deer running. I found a 9-frame deer running animation sheet. I chose 4 keyframes out of these, the most important frames during the run, needed to fully express the motion. I drew the deer himself, I always start with two-tone as you see here. A medium base color, and a darker color to indicate the limbs that are further away from your view. After a good bit of fiddling with it, here is our result. I am content with the basic animation flow.



Our deer needs a sort of idle animation, this will occur whenever the deer stops running for a moment, and perhaps also as a cooldown after firing his laser. Gives him a little personality, a little variation, and gives the player some timing.



And finally, attacking. Deer rears up and fires a laser at you. Deers don't rear much, like this anyway. This is no ordinary deer.



Stay tuned, next time I will show the finished graphics, and soon after, the deer in-game, in-action, in-all its glory.

Star Child Dev Log #24

Jay Ingle - lead developer, designer, and artist:

The last few devlogs were spent talking about the scene construction and coding of an enemy. This week, we can finally show the actual graphics, rather than just placeholder art.

Here is our lava_bomber, whose name does not seem to fit so well anymore, but alas:



So you see particles circling the prongs. This is actually a trick I thought up. I was originally going to make particles coming from the left, and then some coming from the right, giving the illusion of a sort of bar of electricity. I got the particles going one way, then I thought about making them appear to circle. There is no circling. I have one particle effect on top, the bigger particles, going one way. Then I added another particle generator a bit lower down, with smaller particles, going the other way, and voila, it looks like they are circling! It's not a magic trick; it's an illusion.

I also put some graphical work into our lava_spitter, as you can see here:



I am trying to make some of my enemies, or environmental hazards, into robotic creatures. This is for standard consistency, but also the grey colors can be used in any environment. I'm not completely happy with the trail of particles that follows the projectile, but I have some ideas on how to make it better. It should probably appear to be more attached to the projectile, rather than falling behind it, similar to what you see above with the bomb that the lava_bomber drops. The enemy itself could use a bit of animation as well, perhaps circling particles similar to the lava_bomber, or some movement of its prongs.

In other graphical news, I have put some effort into making our Laser weapon look cool:



The original idea was a weapon that felt similar to the laser shotgun from Helldivers. It has morphed into a sorta of lightning whip/energy dagger/Half-Life inspired beam weapon. It is a quick animation, so I only have room for a couple frames, but I think I got a decent progression there. It looks to my eyes like it is somewhat extending as it is being fired, and the particles really sell the energy effect. I liked how it kinda looked like a dagger, so I adjusted the weapon base itself to look more like a sword hilt. Almost an Arabian flair. We will couple this with a weighty buzzing zap sound effect, and I think it will feel great to destroy enemies with!

Thanks for reading, and come back next week for more!

Star Child Dev Log #23

Jay Ingle - lead developer, designer, and artist:

Hello, this will be the final in our series examining the ingredients that make up our Lava Bomber enemy. See the past few weeks for all the rest.

This is our completed enemy, with placeholder graphics:



Today we will be taking a look at the lava bomb itself, that the bomber drops on your head. In the last devlog, we looked at how the lava bomb is its own scene, and how we instantiate it at the proper time, in the proper place. You can see that code here:



But for now, let us take a look at the node structure of our lava bomb scene:



We have our root node, which is a RigidBody2D (extended class: RigidProjectileClass, but we will talk about that later). We have an unused collision shape simply because Godot will complain if we don't have one directly related to our RigidBody2D. This has no layers selected, so it will not interact with anything else. We have our AnimatedSprite. We have an Area2D that we use for detecting the player's hitbox, as well as detecting when it hits a wall/floor. If we were to use the RigidBody2D to detect the player's hitbox, then the RigidBody2D would push the player, in Godot's physics engine. We only want the projectile to damage the player, not influence their position (beyond a bit of knockback). But we still want to use a RigidBody2D node, since this gives us access to the physics engine, for other reasons. A standard death timer, which we explained the functionality of last time. An VisibleOnScreenNotifier node, which will allow us to detect when the projectile has gone off-screen, so we can remove it from the game accordingly. And finally, a particle explosion when it hits something.

Unlike last time, when we talked about NOT having multiple classes for enemies based on their root node, we do have a class for each type of projectile. This is because each type of projectile essentially acts very similarly, and it is easier to keep these things in mind. Whereas, enemies can have all kinds of unusual behavior, and it is harder to keep track of classes in this case (entirely due to Godot's way of handling classes).

Here is our entire lava bomb script:



The power variable at the top controls how strongly the projectile is going to be pushed by the physics engine, upon spawning it. The _ready() function is automatically called when the lava bomb enters the scene. Because we are extended from the RigidBody2D node, Godot gives us an apply_impulse() function that we use to push the projectile in the direction we want, with the amount of power we want. So, lava bomb is spawned in by the lava bomber, then the impulse is applied, pushing the bomb downward, at which point the physics engine takes over, gravity is applied every frame, etc.

We talked about body_entered signals last time, this is much the same. We do not have to create the explode_and_die() or damage_player() functions because they are already in our RigidProjectileClass script.

Well that is about it for the Lava Bomber enemy, and the Lava Bomb it spawns. Perhaps next time I can show you the graphics that will actually be in the game.

If you have any tips, advice, or ideas about any of the last several devlogs, please do post them. I am well aware that I am not the most experienced programmer, and I often stumble around blindly, learning the quirks of the Godot engine. But even if my methods are not the best, I make em work! Nothing more important than that.

Star Child Dev Log #22

Jay Ingle - lead developer, designer, and artist:

My devlog is late this week. Because I completely forgot I needed to make one! How do you forget to do a thing that has been due every week for several months? Let me tell you. If you are a solo game developer, there are 1 million things you need to do to finish your project. It is not an easy task to remember what you are supposed to be doing, when you get lost in so many things that need to be done.

Anyway! The last couple weeks we have been looking at an enemy I have created, going deep into the node structure and coding. Let's continue.

Here is our enemy and its behavior, with placeholder graphics:



First, we can take a look at some exported variables:



@export just means that we can adjust these values directly in the editor, per enemy, rather than having to go into the code itself. Knockback strength and damage_dealt are self-explanatory, other than the fact that this damage_dealt is how much damage the player takes when running into the enemy, not the damage from the actual lava bomb the enemy is dropping. first_delay lets you set how long the bomber initially takes before firing, from the moment the scene is loaded, per enemy. If we did not have this value, every bomber would fire their first shot at the same time. This can look unnatural if there are several on the screen.



The above code is several signals that are built into Godot's nodes. the first _on_body_entered(body) performs its code whenever a body enters the hitbox of the enemy, such as the player, which is a CharacterBody2D. What can be detected by this hitbox is specified by collision layers. I have specified that nothing except the player, or the player's attacks, can interact with the enemy's hitbox. So we can check if the player is the body that has entered, and if so, damage the player. Otherwise, it must be an attack from the player, and thus run the appropriate code, i.e. setting dead to true and running explode_and_die(). Some of the player's attacks are actually bodies (such as RigidBody2D), and some are areas (Area2D), which is why we need the _on_area_entered(area) signal at the bottom as well. Godot also has convenient groups we can add things to, and check things against, and I have one called "attacks" that all player attacks are in.

The middle signal is called when the death timer times out. The death timer is called when the enemy is killed, which allows time for death effects and such to play out, before the node is completely removed from the scene. queue_free() = remove enemy completely right now.



Above we can see how we damage the player. First of all, this should be in an enemy class script. And it is! But here is the problem: Godot classes are tied to node types. They can only extend a specific node type. Let's say I have one enemy that has a root node of RigidBody2D, and another enemy, such as this one, that has a root node of Area2D. Well, these enemies cannot be part of the same class. I have tried making the root node the same for all enemies, but Godot doesn't like it when you try to do physics on a node that is a child of something that does not have physics interaction. As in, I tried to make every enemy root node Node2D, and have a child that is the RigidBody2D for physics. This does not work, as far as I know. So make every enemy a RigidBody2D? Then I would have to write specific code for every enemy that is a RigidBody2D that does not need to interact with physics, just to stop it from interacting with physics. Another idea is to have 2 enemy classes. One for one that uses physics, and one for enemies that don't. But then I would have 2 of the same exact script. And I would have to remember to update both anytime I wanted to update one of them. This is not good. So, for now, I have an enemy class script for enemies that interact with the physics engine, and enemies that are of the Area2D type, they have all of their own code. I do not like any of these answers, but this way is the one I'm least likely to mess up, over the course of the project.

And second of all, this should not even be in an enemy class, damage_player() should be in the player script. This caused problems that I cannot even remember. So here we are, let's not work on fixing something that works already. Do it better next time. Explaining the details of this function isn't important for talking about this enemy, so let's move on.

_on_fire_cooldown_timeout() is called when it is time to fire a bomb. As long as the enemy is not dead, wouldn't want a dead enemy to be firing at you. After that is a solution to a problem. Currently, if the enemy reaches the edge of the ceiling, he turns around. What if he reaches the edge, and it also just so happens to be the time to start firing a shot? Well he might be turning around the entire time he is firing a shot. And if so, where the merry-go-round stops is anyone's guess, he might just walk off into thin air. So he will only fire a shot if he is not too close to the edge. Now, the tradeoff for this fix is he can skip a cycle of firing, doubling the time between shots. This is rare, and the player will surely not even notice.

The last line: animations.play("fire") is important of course. It not only plays the visual animation, but it also calls the fire() function, and the start_walking() function.



Our fire() function is a standard method of instantiating a scene in Godot, which means I am adding a preloaded scene into this scene (lava_bomb variable holding this preloaded scene). I do this to add the lava bomb into the world, at the right time. I have also added a Marker2D node in the enemy scene, which gives us an easy way to say where exactly to spawn the bomb.

At the end of the "fire" animation track, it calls start_walking() and the cycle continues.

At the bottom we see the explode_and_die() function that was referenced previously. It starts the death timer, disables the hitbox collision, makes the enemy invisible, plays the death audio sound, and shows you some particle effects. If we just did a queue_free() to delete the enemy the moment it was hit by an attack, it would instantly be gone, and you would not hear the audio, or see the effects. So we gotta give it a couple seconds to finish its work, and then delete when death timer completes.

We are nearing the end of this story, but we have at least one more week to talk about this guy. Specifically, we need to talk about the lava_bomb that is being added to the scene when the enemy fires. This is a completely unique scene that we can take a look at. So tune in next week!

Star Child Dev Log #21

Jay Ingle - lead developer, designer, and artist:

Last week we took a look at creating a new enemy, setting up our nodes. This week we look at the code itself. If you want to understand more about creating this enemy, make sure you have read last week's devlog. Here is our finished product, with placeholder graphics:



We setup some variables here:



We need to preload the projectile that the enemy fires, so we can instance it later. We have walk speed, a control variable for when the enemy is walking, and one for when dead. I also setup an enum for left and right, since i prefer DirectionType.LEFT rather than the magic "-1", even though i know that -1 means left in Godot coordinates, this is more readable and less error-prone. Dir.LEFT would probably be an improvement, but I have not standardized what DIR means to me yet, it could mean a few things. So when it doubt, spell it out.

Our ready function, which is called whenever the enemy is first loaded into the scene, sets our first fire_cooldown wait_time to our exported first_delay variable (which we will see later), and starts the cooldown.

Main process and switching direction:



The process function is a built-in function that is called by the engine every frame. Since this Area2D root node enemy will only be moving left and right, we can use Godot's built-in global_transform for walking back and forth.

Make sure control variable walking is true, make sure dead is false, and away we go. We make absolutely sure to multiple by delta, because if we don't, a higher frame-rate would mean that the enemy would move faster!

Next we check the raycasts that are checking for the absence of floor.. ceiling actually.. cause it's upside down sorta..... There is no ceiling in Godot, floor is based on gravity direction.. but I digress. If there is no more ceiling to traverse in front of you, turn around. Then, if there is a wall in front of you, turn around.

We have a turn_around_timer that only lets us turn around once, until the delay is finished. Imagine the enemy reaching a wall, turning around, but it isn't far enough away from the wall on the next frame, so it detects the wall again, so it turns around again, and becomes stuck in a loop of turning around forever. The first time I encountered this issue, it wasn't happening for me, but it was for Janne. Reason? My monitor was 60Hz and his was 120Hz. The enemy had time to get away from the wall on my slow refresh rate, but was too quick on Janne's!

I also set the turn_around_timer to autostart, because the enemy was turning around on it's own at the very start of the scene. I finally figured out that the enemy was loading in before the ceiling. So it detected no ceiling next to it, so it turned around. Must be careful of raycasts that are set to detect something that might be loading in later than expected.

This devlog is longer than expected so... tune in next week and we will talk more about how this enemy works under the hood!