Celtican

Ludum Dare 50

Ghost Images in Against the Dying of the Light

Campfire.gif

Light snow crunches under your boots. The cold snap happened so suddenly, it's a miracle you had winter gear in the back of the car.

Hi! I'm @Celtican. I'm the programmer that helped make Against the Dying of the Light, a story-driven adventure survival game. The above text is the first line of dialogue the player sees when playing the game, but I'd like to talk about the dialogue another time. Mind if I talk about a peculiar but very basic visual effect in our game? Thanks!

You can call it different things, "Ghost Image," "Shadow Trail," "Bleeding Image," etc., but I'm talking about the blue trail behind the player when they walk. Look:

In Game Walk.gif

I came up with the idea pretty early in development, before any assets were made. I don't make any non-technical art (I leave that to @Gardenovena), so I tend to think about how I can make things interesting with code alone. Usually through tweening, shaders, other other such effects. In this case, I created the ghost image effect with the intention to emphasize lethargy. Unfortunately this kind of effect is also associated with super speed, but the effect was artistically interesting enough that we kept it in the final build.

You might be able to tell how the effect was made already. The character itself is animated with Dragonbones, a skeleton animation software. @Gardenovena only used Dragonbones a few times before LD50, this walk animation was the first animation she ever completed. We took her animation and exported it at 8 FPS (we thought it would look nicer that way), and then every frame I create a copy of that frame in the player's position. The newly created "GhostImage" is then colored navy blue (the primary color of the game), and fades out over the course of 0.25 seconds.

You can see it more clearly when I ~~destroy~~ hide the world:

Editor Walk.gif

By the way, I only noticed when making this gif, but apparently the legs are missing a frame of animation. No one (at least not us) noticed while playing because the legs are often hidden by grass, and you usually aren't looking at his legs in the first place. Oops!

To programmatically create the GhostImages, I made a CreateGhostImage() function that fires every time the frame changes. I don't detect a frame change, I leave that to Godot (I'm using Godot by the way) ala signals. I setup a signal (basically an event) that fires on the "frame_changed" event, and it connects directly to the CreateGhostImage() function. The CreateGhostImage() function itself is relatively small:

```cs // Player.cs

// various relevant variables private String _lastAnimation = "stand"; private int _lastFrame = 0; private float _lastScaleX = 1;

public void CreateGhostImage() { if (Animator.Animation == "walk" && (Animator.Frame == 0 || Animator.Frame == 4)) { // every time the character takes a step walkSound.RandomPlay(); // Really, this code should be in another function. // But, you know Ludum Dare, we love spaghetti code here } // Since CreateGhostImage() is called after Animator.Frame is set to // a new value, we need to fetch the old value. I opted to do this // simply by keeping the previous frame information in memory Texture frame = Animator.Frames.GetFrame(lastAnimation, _lastFrame); _lastAnimation = Animator.Animation; _lastFrame = Animator.Frame;

// I hate loading the resource every few frames. I was probably so much
// in crunch mode that I didn't even realize I did this.
Node2D ghostImage = GD.Load<PackedScene>("res://scenes/GhostImage.tscn").Instance<Node2D>();

// GhostImage doesn't have any code, so its behavior is handeled
// all in Player.cs. This includes its position, scale, and tweening.
// In production code, I would probably put this code in a static
// GhostImage.Instantiate() or similar.
ghostImage.Position = Position;
Sprite s = ghostImage.GetNode<Sprite>("Sprite");
s.Texture = frame;
s.Scale = new Vector2(s.Scale.x * _lastScaleX, s.Scale.y);
// This is used when I flip the image when the player transitions from
// looking left to looking right, or visa versa (I didn't make a gif
// that shows this effect, but it's pretty basic)
_lastScaleX = AnimatorHolder.Scale.x;

// I specifically want to put the GhostImage behind the Player in update order.
// If I did not do this, the GhostImage would render above the Player while
// the player was moving left and right (because their order would not change when
// their Y is the same)
GetParent().AddChild(ghostImage);
GetParent().MoveChild(ghostImage, 0);

// Lastly some tweening. Just to make it fade out nicely. I'm tweening
// the GhostImage's color from its default (#482d3375) to invisible
// linearly over 0.25 seconds. Godot has a useful Tween node class
Tween tween = ghostImage.GetNode<Tween>("Tween");
tween.InterpolateProperty(s, "modulate", s.Modulate, new Color(s.Modulate.r, s.Modulate.g, s.Modulate.b, 0), 0.25f);
tween.Start();

// And... it's done. Honestly not too much code for a nice effect.

} ```

Additionally, I accidentally made the GhostImage slightly offset from the Player. This resulted in the GhostImage showing up when it is created even while the player is standing still. This doesn't happen normally because the "stand" animation's FPS is set to 0, but it does happen when I manually create a ghost image. I create a ghost image whenever the player chops a tree, and builds the bridge. It's pretty nice actually, there's a tiny bit of feedback even though we didn't have time to make the chop/interact animations. You can see this a little bit in the second gif on this post.

... Actually, we did make a chop animation. We made it in the last 20 minutes of the jam. That's correct, I was pressured to code this animation AND test it AND submit a build to itchio in less than 15 minutes. The crunch rush actually caused some major build bugs that prevented the game from running, needless to say the animation didn't make it. Which is a shame, really, because it demonstrated the ghost image effect much better than the standard walk animation. This is because the chop animation has much more movement, and so the trail is easier to see, even though the player is standing still while the animation is running. Here's the animation in the dev environment by the way:

Editor Axe.gif

This animation, made in 10 or so minutes, caused me so much hell. Particularly the delay for the chop sfx and tree disappearing was what made the crunch so difficult. Anyways, I'm still sad we couldn't put it in. Actually, that was the only asset that didn't make it in, which is impressive since our previous jams had many assets that didn't make it in the final build. But they were content-driven games, I suppose.

Anyways, this effect was super easy to add and was made probably in around 15 minutes or less. Most video game polish is done without any assets at all! I typically find that "game feel" is found through a combination of tweening, particles, shaders, and a bit of sfx. Of course, wonderful art really adds to the experience as well. This game would be nothing without @Gardenovena's pretty pictures.

I've been reading a book about game feel, I admit I haven't come anywhere close to finishing it though (I, uh, have more important things to be reading). Game feel is super important in video games, and is pretty easy to add (I tried to link some YouTube videos but the formatter bugged a bit. See "The Art of Screenshake" and "Juice it or lose it" on YouTube). I probably could have done more polish on my game, but I'm pretty content with the amount of polish it has.

I hope you learned something, and that this was insightful? Maybe? I've been writing a lot about the game in its comment section. Either way, feel free to link your own game so I can check it out and rate it. See you next time!

Rate Against the Dying of the Light here:

https://ldjam.com/events/ludum-dare/50/against-the-dying-of-the-light

Ludum Dare 53

Less Can Be More

RED.gif

Our game, packet.Breach(), is a tower defense with some interesting mechanics. Notably, most towers have the ability to modify bullets (packets) in some way.

When I began coding the game, it very very quickly became apparent that it would be very very easy to add new towers to the game. With an hour or two's work, I created at least a dozen towers, if not two dozens. The system was modular, and each tower simply required a small script to be created, or required reusing another script (maybe even multiple scripts) with different arguments. A small sample of some of the scripts for this:

Screenshot_26.png

Naturally, this gave us so much time to create new towers! Towers that rotate bullets, towers that make bullets home towards enemies, towers that make bullets explode, towers that slow enemies down, towers that increase the size of bullets, anything is possible! UNLIMITED POWER! This is a very very small section of the document that contained the list of tower ideas we came up with:

Screenshot_25.png

But at the end of the jam, we only have 9 towers available to our players. Why?

This game is about strategy. Other tower defense games appreciate ridiculous amounts of towers, but not this one. Each tower has a very specific role in the factory, each tower modifies bullets in very specific ways, creating a chain reaction that the player has to be able to understand and modify easily.

Take this gif for example:

Portal.gif

You can probably take a guess at what some of the things in that setup are doing. There are towers that rotate, towers that produce, and towers that duplicate. Easy enough to understand, right? But what about the red drum? What about the orange scorpion, or the blue medusa head? What do those mean?

If you play the game for a little bit, you'll figure out that the drum makes bullets explode, the scorpion increases damage, and the medusa multiplies damage.

Now imagine that you have five times that many towers. Suddenly, nothing makes sense. Because you weren't given enough time to understand. You were given three towers to choose from, and two of them you will never see again for a long time simply because there are that many towers.

Less can be more. In this game, less was much more than more.

It was a hard decision to make, to get rid of dozens of ideas and a few scripts, even hours of work. But it was worth it, from a game-design perspective. Now all of the towers in the game have a distinct purpose, specific use cases, and you can plan ahead because you know what's common and what's not. This game would have been so much worse if we didn't force ourselves to use less towers.

LDIcon.png

Play packet.Breach()!

And if you come up with any ideas for towers you think would be interesting to see in the full game, let us know!

Ludum Dare 54

*hacker voice* I'm in.

This is my 7th Ludum Dare in a row. Huzah! Hello to everyone, newbies and veterans alike! :tada:

Screenshot_1.png

Normally I don't go in to jams with a plan, but this time me and @gardenovena are planning on making a city builder game or a base builder game. We haven't planned more than that, we'll just try to fit the genre to the theme.

Good luck, everyone!

Ludum Dare 57

Depths :(

depths...