1. Star Child
  2. News
  3. Star Child Dev Log #20

Star Child Dev Log #20

Jay Ingle - lead developer, designer, and artist:

This week, we are going to take a look at how I create an enemy for Star Child, from start to finish. Minus the graphics; I prefer to prototype and get it working first, then refine the visuals. This week I will talk about the nodes that I need, and how each are set up. Next week, I will talk about the code.

Here is our finished product, with placeholder graphics:



Here is what we can see in the editor. Note our node structure on the left side.



You may also notice that not all of my nodes are named very well, but that is not a big issue, since we will be using well-named variables that reference each node, in our code, as you can see here:



Our root node is an Area2D, which I like to use for enemies whenever they do not need to engage with the physics engine. Area2Ds can be moved around directly with code, or using an AnimationPlayer. We will be doing both.

We are using a Sprite2D node for the graphics:



A simple spritesheet contains each frame. You can see under the Animation heading on the right that this spritesheet contains 8 horizontal columns, and 1 vertical row. These frames will be accessed and animated by the AnimationPlayer.

I have created a Marker2D node, which marks the spot where our lava bombs will be spawning from:



Here is a look at our AnimationPlayer node:



We have two animations: 1 for Walking, and 1 for Firing. Our Walk animation autostarts when the scene is loaded, and our Fire animation starts whenever our cooldown timer finishes. This is what happens during the Fire animation:

First, you don't see it here, and I will talk about it more next week, but the enemy stops moving. I stop the enemy, then the animation is played, which means the enemy sits there for 1 second before beginning the actual firing animation. This telegraphs the attack and lets the player get out of the way.

Second, the attack animation begins, and halfway thru the attack frames, fire() is called. This spawns the lava bomb, and the details of how we spawn that bomb will be discussed next week.

Finally, at the end of the animation track, we call start_walking() again.

A few other nodes of note:

I am using four raycasts to detect walls and floors, and tell the enemy when it is time to turn around. For previous enemies I created, I just used two, and had them switch sides when the enemy turned around. I thought using four would be simpler, but to be honest, this way introduced another possible bug that I did not like. I resolved it, but I think in the future I will be doing it the previous way.

FireCooldown timer controls how long before the enemy fires again.

TurnAroundTimer fixes a possible bug where the enemy gets stuck turning around over and over. This might be solving a problem with the two-rays system that I'm not using here, but it has become somewhat of a standard thing I do for this type of enemy.

TimerDeath starts whenever the enemy is destroyed by the player. It waits a couple seconds then removes the enemy from the world. We cannot immediately remove the enemy, because then you wouldn't see the graphical effects that occur immediately upon/after death. More on that next week.

We also have a CollisionShape2D node, a ParticlesExplosion node, and a few Audio players.

And that is our basic node structure for this enemy, tune in next week and we will dig into the code and see exactly how everything works!


Janne - the other guy:

One of the things I had to change was a bit of the internals of the automap system. Previously I built it with the plan that we'd be building one large connected map, with individual levels identified by coordinates with A-Z for columns and 1-99 for rows. The plan changed and instead we wanted to support multiple zones with their own coordinate system.

What I decided on was that I didn't want the automap rendering part to know about any of this, so the data structures I had in place already for rendering shouldn't change and I would just change the values as necessary.

The initialization logic became a bit more complicated as I needed to initialize the structures for each zone, and determine the coordinate and zone from the file path, but in the end it didn't take very long to change. I already called a function in the automap data manager when switching levels, and now that function also checks if the zone is changed and switches the data structures given to the rendering layer to the correct zone.

Save structure also changed to support saving known coordinates for all different zones, but that was relatively simple as well since we're clearly in a pre-release state and I didn't have to care about supporting the data in the old save files, so instead I'm just throwing away the automap state in the old format.