[RELEASED] PixelSurface: Efficient Destructible 2D Terrain

Our PixelSurface asset has just been released in the Asset Store!

PixelSurface creates a 2D pixel environment. You have complete control over how this surface is divided into tiles for efficient processing, as well as how big the pixels appear on screen. It supports full 32-bit color, with transparency, and standard drawing methods including line, rectangle, ellipse, and flood fill (“paint bucket”).

PixelSurface also supports “live” (dynamic) pixels, which move around on top of the static background. These are perfect for special effects like flying debris, fire, falling snow or rain, and much more.

Now you can easily create retro-style games that rely on a destructible terrain, in the style of Lemmings, Worms, or Dig Dug, or modern smash hits like Sandbox. Let your imagination go wild — what could you create with this new capability?

Read the fine manual, or try the web player or WebGL demo! Or go directly to the Asset Store page.

And if you have any questions or comments, please post them here. I’ve been an active member of the community for years and if I don’t respond quickly, it probably means I am either lost in the desert, or trapped under something heavy. (Please send help.)

2 Likes

Hey all! I’ve verified that PixelSurface works perfectly under the latest version of Unity (5.3.0f4), and that it works in WebGL. In fact I’ve added a WebGL demo, so you can try it yourself!

Happy pixeling!
— Joe

1 Like

Incidentally, I’ve started a thread in the Design forum brainstorming various ways that PixelSurface could be used to make a unique game. This might be useful to PixelSurface users, or people just looking for some fresh ideas!

1 Like

Fascinating. I will most likely buy this and tear it apart just to figure out how you got it running so smooth!

I must know, do you use texture2d’s and Apply() to update changes? Maybe a rendertexture method?

How about dynamic stuff like bombs, or the pixels thrown from an explosion? Are they sprites? Are they drawn to a canvas and updated frame by frame?

What is the max size of the 2d area? Over the texture size limits on some platforms (like say, over 4096x4096)?

1 Like

It is Texture2D’s and Apply, but there are a host of optimizations to make it faster. For example, the surface is divided into subregions, so we only need to Apply in ones that have any changes; and even where there are changes, we batch up all the changes within a frame, and Apply only once.

As for dynamic stuff, you can either draw Sprites on top of (or behind) your SpriteSurface, or you can draw directly into the SpriteSurface. The demo shows both approaches: the moving pixels (snow, explosion debris, fire) are all “live pixels” which draw directly into the SpriteSurface, whereas the falling bomb is a sprite drawn on top.

That is almost the same approach I’ve taken, in “Pixel Destruction”, which I spent about 2 years developing (its been shelved for now), and was planning on working on in the future, but perhaps your system could provide me with some optimizations I hadn’t figured out yet!

I’ll let you know if I have any problems, thanks for creating the asset!

1 Like

Had a problem occur to me that you might have an idea for - how in the world do you handle pathfinding in this scenario? A* seems like the obvious choice given a grid of points to work with, however, using a 4096 x 4096 level as an example, this means 16.7 million points to check against. And being that the level can change, those points need updated for whether or not they are continuing to be “open” for characters to pass through. I instant messaged to data of datarealms a bit about this topic, and how they handled that in Cortex Command. He described it to me as a lower resolution grid, which is nowhere near as detailed as the graphical representation, that’s updated using tricks like diagonally testing all the individual pixels for “openness” then assuming the large pathfinding node is open. This avoids updating by the thousands (for instance if you checked against cells of textures, perhaps 128x128 sections) which are open and which aren’t, and just checks a diagonal section of a given area, and considering it open “enough”.

What are your thoughts on that? Is there a simple way to do ai pathfinding, more complex that just “run right till you hit obstacle, attempt jumping it, if that fails, run the other way”? And at the same time not so performance hungry as checking 16.7 million pixels for whether or not they are open after modifying the level?

Edit: Also, Data of CC also mentioned they did a percentage of openness, like they cast lines at different angles in a “node”, a pathfinding square area, and if certain percentages were open, it’s open enough for ai to pass through…

Edit2: for the record, trying to test against “cells”, the split texture 2d’s of the level, that have been updated in a frame for every single pixel in that cell, then taking a percent of that result for whether or not it is open, is too expensive for mobile performance, and frankly won’t work well on pc or consoles either.

Right, I probably wouldn’t do A* on a pixel level, though you could, and if the path is reasonably direct, it wouldn’t actually check most of those points. But if it had to backtrack, then yeah, that would be painful.

So yeah, to make it efficient you need to lump open pixels together into a network of regions. A lower-resolution grid is a decent (and certainly the simplest) way to do that. Here are some other variations.

But people often reach for a search algorithm (typically A*) when a more problem-specific solution could work better. For example, if you have only a couple of destinations you commonly path towards, then you should make an maintain a distance map to those destinations. And in this case you actually could use a PixelSurface for your data structure, which would be great for visualizing it for debugging purposes — just have the amount of (say) green reflect your distance, in steps, to the goal. (Of course a big array of byte or short would be more efficient in this case.)

This sort of distance map is very easy to update gradually: when anything changes, you simply throw the changed pixel locations onto a “to-do” list, and then on each step, you pull a pixel location off the to-do list, figure its distance is one more than the shortest distance stored for any of its neighbors. If that answer is different from the previous distance you had for that location, then you throw its neighbors onto the to-do list. So you can just crank through N (100 or whatever) to-do items per frame, and the distances will very quickly sort themselves out.

Now to use this is trivial: from any position on the map, to move towards the destination, you simply move to the neighbor with the shortest distance.

This can even work when destinations are changing, as long as you can allow your agents to ponder for a few seconds before choosing a path. This might be fine in an RTS, for example. It can actually look quite believable when you occasionally tell your units to go to point X, and they immediately start moving in the direction of X, but then a few seconds later realize that that path is blocked, and change directions to go a different way.

1 Like

Thanks for the thorough answer! I hope I can get it all sorted out before long, especially how to consider 2D gravity and jumping into the pathfinding, that should be just days of fun :X

You make some good points and I will see what I can figure out. Still haven’t made the purchase for your asset, but I should get some free time soon to grab a copy and check it out!

1 Like

Thanks, and if you do, please write a review! A few people have bought it, but nobody has taken the time to review it yet, and I think some people get nervous about any asset with no reviews.

Good luck with your project!

I certainly will, I understand the work that goes into this and honestly think you should be charging more than 20 bucks! At least at surface value :stuck_out_tongue:

Hope it meets my expectations, but judging from the webgl demo, seems impressive.

Well, if it fails to meet your expectations in any way, just send me a message — I’ll be eager to make it right!

Is it possible to “wakeup” pixels above the explosion, so that they would fall down?
(as in the old Scorched Earth game, explosion would cause terrain above it to fall down)

Sure! The current code already does something a bit like that: it checks all the pixels in within the explosion radius, and then with some percentage chance, either erases them or converts them into live pixels.

So, what you could do instead (or in addition) is, check the pixels above this area, and throw any non-empty pixels onto some list. Then on each frame, process the pixels on this list: convert them to live pixels (perhaps with only some probability), and check the pixels above those — throw them on the work list too.

So in this way, the explosion would cause pixels to convert (bottom-up) to dust, which then falls down. And dividing it over multiple frames like this both makes it more visually interesting, and avoids any sudden drop in the framerate. By fiddling with the percentage chance of being “shaken loose,” you could make it so that small bridges or whatever collapse completely, but thick caverns merely drop some roof but then tend to stop. (And to smooth it out, you should probably check not just the pixels directly overhead, but ones to the left and right too, with a lower probability.)

I know this is a bit vague, but I have to run soon — if it’s not enough to get you going, just let me know and I’ll try to whip up a demo for you later this weekend!

2 Likes

Purchased for testing!

current idea is here (image below) - if you have time to make demo that would certainly help… but no rush :slight_smile:

Other random ideas/feedback after quick test:

  • Having very simple random terrain generation might be nice to have (having randomize method/button to generate levels, either just rolling hills like Scorched Earth, or more complicated like Worms / Liero) *Although it seems to be simple to do, just create any texture and feed to the pixsurf…
  • Water fluid pixels (then could create small lakes on the terrain, and when the side explodes, water pours away, probably works for lava also)
  • DropSand demo script could have option for amount (more fun to pour lots of sand :slight_smile: )
1 Like

For that picture above I figure you could just have it take the edges of the explosion, “cast” lines upwards, and flood fill until empty pixels…

Just an idea I guess, wouldn’t know where to start implementation ;p

I haven’t purchased yet, should get a chance this week.

PS I second the amount idea, would like to stress test it doing dynamic pixels. Perhaps share the largest sized levels you’ve been able to run on mobile?

I don’t feel like it’s a competition lol, but using my own per pixel destruction demo I went up to 4096x4096, or 8192x2048 and so on… could push it further, but performance starts to suffer on older devices (galaxy exhibit) any higher than that.

PSS oh BTW @mgear I did a test of noise based “rolling hill” 2d level generation, if I can dig it up I’ll post it here, no guessing if I got it saved somewhere sensible. If not I’ll try and recreate it, as it was kinda nifty for these kinda games.

Thanks guys, those are good ideas!

On the gravity thing, I wouldn’t do a flood fill all the way up above the explosion (until you hit empty space) and convert them all at once. Instead, just convert the pixels directly above the explosion… and then when they fall, check the pixels above them… and repeat. I’ll see if I can make a demo later today.

I don’t think terrain generation is within the scope of the asset, but it would be handy for the demo, so I’ll do some of that too and make the code available! (But @mgear , you’re right, you can take any old texture — as long as it has “Read/Write Enabled” — and just feed it to PixelSurface via the DrawTexture method.)

And I think liquids (water, lava) would be a pretty simple behavior for LivePixels… but again I’ll put my code where my mouth is, and try to whip something up today.

OK, here’s a little example of random terrain generation. I was originally thinking “caves,” but what came out of it is more like “platforms,” and it’s so cool that I just ran with it. :slight_smile:

Here’s the code:

using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
using PixSurf;

public class RandomCaves : MonoBehaviour {
    public Vector2 noiseOrigin;
    public Vector2 noiseScale = new Vector2(8, 10);
    public float threshold = 0.5f;
    public Color clearColor = Color.clear;
    public Color rockColor = Color.gray;
    public Color grassColor = Color.green;
   
    void Start() {
        GenerateCaves();
        GrowGrass();
    }

    public void GenerateCaves() {
        PixelSurface surf = GetComponent<PixelSurface>();
        surf.Reset();
        Vector2 surfSize = new Vector2(surf.totalWidth, surf.totalHeight);
        for (int y=0; y<surf.totalHeight; y++) {
            for (int x=0; x<surf.totalWidth; x++) {
                float sampleX = noiseOrigin.x + x * noiseScale.x / surfSize.x;
                float sampleY = noiseOrigin.y + y * noiseScale.y / surfSize.y;
                float sample = Mathf.PerlinNoise(sampleX, sampleY);
                Color c = (sample < threshold ? clearColor : rockColor);
                surf.SetPixel(x, y, c);
            }
        }
        Debug.Log("Set " + surf.totalWidth +"x" + surf.totalHeight);
    }
   
    public void GrowGrass() {
        PixelSurface surf = GetComponent<PixelSurface>();
        for (int x=0; x<surf.totalWidth; x++) {
            int grassDepth = 0;
            for (int y=surf.totalHeight-1; y>=0; y--) {
                if (surf.GetPixel(x, y) == clearColor) {
                    grassDepth = Random.Range(1,3) + Random.Range(1,3);
                } else if (grassDepth > 0) {
                    surf.SetPixel(x, y, grassColor);
                    grassDepth--;
                }
            }
        }
    }
}

To try it out for yourself:

  • Make a new scene.
  • Create a new empty GameObject, add a PixelSurface component, and configure it as shown in the image above.
  • Attach the above RandomCaves script.
  • Run.

There is of course a lot that could be done to extend this script and make it better, such as picking slightly different shades based on the gradient of the noise function, adding random rocks within the dirt, making the grass stick up a bit on top, etc.

And of course we’re only doing one sample of the noise function here; typically you can get more interesting & detailed results by sampling it multiple times, at different scales, and just adding them together. But I rather liked the look of the smooth platforms & blobs produced by this simple method.

3 Likes

Very cool! Much more in depth than the random hilly thing I was jabbering about, thanks a lot joe!

1 Like

I worked a bit this evening trying to make a LivePixel subclass that acts like a fluid. I don’t have it quite right yet, though — it’s harder than I had first thought! My fluid particles tend to want to overlap, whereas a real fluid should be incompressible, so the particles never overlap. But making the pixels behave that way is harder than it sounds.

I’ll mull this over for the next few days, and get back to you when I have some good news!