• 2D
  • Liero-clone: How to do destructive environments without breaking CPU?

I decided to try game programming by way of a Liero-clone. As you can see in the video, and in the source on GitHub, the game features worms tunneling their way through dirt - not unlike the Lemmings of old.

I've managed to get small 2d Area nodes to destruct when bullets enter their body, but, alas, once I try and fill the screen with them the game breaks. I can make only very small chunks, so it would appear this is not a viable way of going about it.

How would you go about filling the screen with destructable dirt like in the video?

Here is how I would do it, but I have no idea how you would do collision detection with this method:

You make a black and white image that is the same size as the level, where one color is for solid parts of the level (the light brown dirt in the video above. Let's assume you chose white) and the other color is for the non-solid parts of the level (the dark brown dirt in the video above. Let's assume you chose black). This image will be a bit mask that will be used to figure out which parts of the level is solid dirt, and which parts are the background/non-solid-dirt.

Then you have two images/sprites, one for the solid dirt and another for the background/non-solid-dirt. On the solid dirt you add a shader that uses the black and white bit mask, only rendering pixels that are in the white section. I made a shader that does something similar to this in Godot 2. It could probably be adjusted to work with Godot 3, and to use a inputted texture instead of the screen texture.

Then when you want to destroy part of the ground, you add some black pixels to the bit mask at the location where the destruction occurred.

You could use a Viewport texture for the black and white bit mask if you wanted, which may make performance a little better (since drawing to a image is slow). By using the _draw function provided in Node2D, you should be able to get fairly fast performance adding black and white sections to the bit mask.

Then, whenever you update the black and white bitmask, you update the shader, and then it should look like part of the terrain was destroyed, since the shader will exclude areas that are black from the final image.

The only problem with using a shader for environment destruction like this is collision detection. I have no idea how to go about adding collision using this method. Maybe in Godot 3.1 when the mesh sprites come in you could auto generate a collider from the bit mask using the mesh sprite? In theory you could use the bit mask for collision detection and then get pixel perfect collision detection, but I have no idea on how to go about doing that.


I took a quick look through the source on Github, and at first glance it looks like this is how they are doing it. They are not using a bit mask though, instead it looks like they are just removing pixels from a copy of the level image in memory, but the idea is more or less the same. When something hits the environment, they remove a section of the pixels in the level image at the collision position, then they update the collision (I assume), and the game goes on.

I'm still not sure how to handle the collision detection though...

Thank you so much for that. I'll have to learn what shaders are and how they work. I can have the bullets report their position so long as I can make the shader turn off a bit at a particular position. Should be easy to make some explosion-like destruction as well this way.

As for player-collisions, is there anything stopping med from using a polygon collider with the same shader? Wouldn't that give players the ability to roam free wherever there is no dirt?

@kvebbs said: As for player-collisions, is there anything stopping me from using a polygon collider with the same shader? Wouldn’t that give players the ability to roam free wherever there is no dirt?

I rarely use the 2D side of Godot, so I’m not 100% sure what is possible with the 2D nodes. I guess it really depends on how the polygon collider is generated, and if you can pass the texture/shader data to it. In theory it is possible, and worse case you could (in theory) write code that takes the shader texture, converts the texture into polygons, and then assigns the polygons to a pollygon collider. As I said before though, I really don’t know, sorry!

While not Godot related, this code on StackExchange about XNA may help with writing a system that uses pixel perfect collision detection, which you may be able to use in Godot if you use the shader texture for the pixel perfect collision detection. (I have no idea if it will work, nor if it will be helpful)

13 days later

I am making some progress here so I thought I'd share. I'm down to 2px destructible terrain right now, and although I am not sure I am hopeful. I tried the above, but failed at math-and-programming to create polygons based on the bitmask. I found inspiration in another solution only based on blocks which subdivide into four smaller blocks. Here are the basics of what I have and plan to implement now:

  1. The screen is filled with blocks of 64px size. These are StaticBody2Ds with a Sprite and CollisionBody2d.
  2. When instanced (or shot, or whatever), blocks check their immediate surroundings for other blocks using intersect_point() 1px outside themselves.
  3. Blocks with < 4 neighbors subdivide until they either have 4 neighbors or cannot subdivide further due to minimum size. Blocks in the latter category constitute the edge of the terrain. Ensuring edge-blocks are the smallest possible size is key not only to give the feel of per-pixel destruction, but to ensure players don't have to shoot blocks several times to destroy parts of it.
  4. A nice full-screen image is shown by defining each block's Rect2 and fetching that from a level-image.

Current issues ...

It currently runs nicely at some-hundred fps uncapped, but as a level is shot up I should try an minimize the number of blocks. Subdivisions create a lot of new blocks that are not on the edge of the terrain so it would be nice to have non-edge blocks combine back to larger blocks. Maybe layer smallest size for edge, almost smallest right behind and as large as possible behind them again.

It can be hard to destroy the terrain of a given area. If I draw a circle over N boxes which require 3-4 subdivisions to queue_free(), those wholly within the circle can be removed easily enough. Those not wholly within must subdivide until they either are or aren't, which takes multiple hits. I think a solution may lie in combination with the above point. By introducing an order in what type of block exists at or near the edge, destroying them gets easier.

If I get there, I will find some way of sharing the project with you.

I've attached a picture with a player-analogue in a spawn-point-ish place.

Cheers,

Looks great @kvebbs!

That is a interesting way of doing destructible terrain, I would not have thought of using a method like that! I’ll have to remember it for later, as it sounds like it could be quite useful.

I wish you the best of luck figuring out the issues! It sounds complicated :smile: