1. Star Child
  2. News

Star Child News

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!

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.

Star Child Dev Log #19

Jay Ingle - lead developer, designer, and artist:

Godot has a neat AnimationPlayer node. It can do just about anything you can imagine. It can even call functions located in other places, so the power is nearly unlimited.

When I first began using Godot, and I needed something to happen at a certain time, I would use a Timer node. Then I would set up an AnimatedSprite2D node for the animations. Let's say I want a hitbox to be enabled at a certain time during an animation. Well we can start the Timer and the animation at the same time, and then when the Timer finishes, we enable the hitbox. Then when the animation finishes, we can start a different timer that will restart the animation.. and restart the other Timer... and hopefully you can see how this quickly becomes complicated, even for something simple. The other problem is that the animation is not exactly tied to when the hitbox is enabled. It SHOULD be synchronized, and if I have set my Timers correctly, and animations are a certain length, then things will be synchronized, but will very easily break. Any change to any component will destroy the timing. AnimationPlayer nodes remove all the guesswork, and tie things together real nice.



Above, we have added sprite frames from a Sprite2D node, and we call functions enable_hitbox() and disable_hitbox() at specific times. We have a nice visual where we can see which frame is shown at the time of those function calls. We could easily set this to loop, but at the end of that timeline I am calling start_timer(), which is how long until we restart this animation again, because I want a little more control of how long the flame is inactive.



Here we can see that you can even keyframe aspects of a particle system. We can change colors, increase or decrease amount of particles, and many other things.

If you are new to Godot, and you are using AnimatedSprite nodes, I would advise switching everything to Sprite node plus AnimationPlayer instead. Especially if you are not too familiar with animations and keyframes in general. AnimatedSprite nodes are very simple, very basic, and do their job well, but AnimationPlayer nodes are much more powerful, and will save you a lot of time and effort. Let them do the heavy lifting!

Star Child Dev Log #18

Jay Ingle - lead developer, designer, and artist:

Let's say you want to make a video game in the Metroidvania style. You have a large amount of reference material, but how do you make your game unique? One way is to use The But Technique. Here's how it works.

In the original Metroid, there is a seahorse-lookin thing that rises up out of lava, and shoots at you. Like this:



So far, all we have done is copy what Metroid did. Now let's apply The But Technique. We have a seahorse-lookin thing that rises up out of the lava and shoots at you.. BUT... but what? But whatever you want! How about... a seahorse-lookin thing that rises up out of the lava and shoots at you, BUT the projectile is massive.



Now we have something much more interesting, and even unexpected. This was just the first thing I thought of. But ideas are easy when you just let them roll out of your brain:

A seahorse-lookin thing that rises up out of the lava and shoots at you..

BUT... the projectile is massive.
BUT... the seahorse rises all the way out of the lava then shoots in all directions, then explodes.
BUT... the projectile is an enemy that hits the ground and starts hunting for the player.
BUT... the projectile is a planet.

Apply The But Technique by brainstorming a list of a bunch of random alterations to the original idea, unfiltered and straight out of your own personal unique creativity. Then pick the best ones and try them out!

Now I don't remember the first time I heard about this technique, or even what it was called then, but shoutout to Dev Worm on Youtube for reminding me of it (Dev Worm has good tutorials for Godot beginners).

Star Child Dev Log #17

Jay Ingle - lead developer, designer, and artist:

Sometimes I wonder what this devlog should be about. Is this a bullet-point list of what I have spent my work hours doing this week? Do I really want to tell you everything? There needs to be surprises!

Sometimes I feel like talking about broader issues in independent game development. How do you learn to code? What tools do you use?

Sometimes I want to talk about more personal things. Motivation, inspiration, work ethic.

I did a lot of things this week. I spent time working on automatically adjusting particle effect colors for new environments. I created some lava for you to fall into. I created an enemy that rises up out of the lava and spits at you. I also added the ability to switch the music to the next song by hitting a hotkey, for me and the playtesters as well. I also installed a Godot plugin, called G.U.I.D.E., which handles input. Remapping keybinds in Godot can be a massive pain, and G.U.I.D.E. does a great job at providing a much simpler framework. It also has full documentation, as well as several video tutorials on Youtube, created by the developer. Highly recommended.

This week, I studied a lot. If you are looking for the best overall pixel art course that I have found online, check out Pixel Art Master Course on Udemy. It's very thorough and helpful. I spent time reading a game design book called Level Up! by Scott Rogers. It is very good at breaking down all of the details of game design. I read Beginning Game Development with Godot (2021). I did not learn much. I am not THAT much of a beginner. I also read some of a French comic book called "The Obscure Cities" and the mysteries in the stories are very much inspiring my subversive creativity.

Here are some tools I use for game development. The Godot game engine. Very nice, open-source, constantly being updated, and learning resources are abundant. Pyxel Edit. This is the best tool for creating tilesets, period. It is not as fully-featured as Aseprite, lacking powerful features such as custom brushes, but for simple pixel art, tilesets, and animation, it is extremely useful.

Now how is my personal game development journey going, for me? Well, I am motivated. This is my dream job, since I was a little kid, and I have a huge drive to create worlds, craft stories, and provide unique experiences to players. I want to create games that are infused with my own personal creativity.

Once upon a time, I watched Twin Peaks season 3. I thought it was the single most uniquely creative visual project I have ever seen. Everything single moment of the show was pulled from the depths of David Lynch's creative mind. And I was ashamed. Why am I not putting ALL of my creativity into everything that I am doing with my life? I remember this moment, and I continue to work to release the unique vision that I have inside.