Piczle Colors was in need of a material that dynamically built a texture for the puzzle “solution reveal”, considering there was an option that players could unlock to play randomly generated puzzles. What this post is not about is how I achieved this in Piczle Colors because, lordy…that was…special. I dread to even think about it now, ugh. But with all good game development one continues to learn and refine, and this post is about how I would create a dynamic texture of a blocky puzzle, like Piczle Colors or Piczle Cross, in an easy and neat way if I were to create a similar set of circumstances today.
We’ll be using UnrealEngine 4’s “Draw Canvas to Render Target” node, but first we need to make a few preparations. First let’s create a render target and a material.
We need this RenderTarget to create our material with and you can either create is as your final texture or, in our case, it’s just a placeholder as we create our own dynamically in BluePrints later.
This RenderTarget gets linked up in the Material. It’s very basic, and the material domain depends on how you’ll be using the final resulting texture. Let’s say we’re going to use it in a Widget, so here we’ll set the domain to “UserInterface” for this example, but it could be anything. Finally we turn the RenderTarget into a parameter, in this case called “target”. Finally we save this material and create a Material Instance from it.
Next we create a widget and open up the Event Graph. For this example let’s create a black and white preview of one of the earlier puzzles in Piczle Cross Adventure.
Instead of using the RenderTarget we created before we’re creating one dynamically here. We’re setting the width and height here to the size of the puzzle we’re going to draw. Just for good measure we set the filter to “Nearest (neighbour)” to make sure the pixels are super sharp and not blurred.
Just to keep things organised, let’s make a variable of the TextureRenderTarget2D object reference, and assign the canvas render target 2D we just created to it. This way whenever we need to do anything to that canvas we can refer to this variable instead of having to pull strings off of this node all the time. This will be handy later if you just want to adjust a few pixels in a separate function or event.
Now we clear the render target for good measure (it is newly created so should be empty but you may want to reuse this event/function multiple times). Better safe than sorry! Then we create a Dynamic Material Instance from the material instance we just created, set the “target” parameter to the canvas we just created, and set it as a brush material for an image we create in the Widget, which I called “preview image” and is 200 pixels square.
This is the basic set of nodes we’ll use to draw on the texture. First we set a “Begin Draw Canvas to Render Target” node, and from that canvas node pull a string to a “Draw Texture” node. There are several nodes you can use to draw onto a canvas, including lines and squares, but it’s only this node that seems to give us the clean, pixel-perfect drawing we want for a Piczle Cross preview texture. The screen position here states “0,0” so this node alone will draw a black pixel (Render color) at 0,0 (the very top left) of our canvas. Once you’re finished drawing you have to add an “End Draw Canvas to Render Target” connected to the context output of your original “Begin Draw Canvas to Render Target” node, and hey presto! We just drew a black pixel on a white canvas!
I added a little grey background to the widget to better show the effect here.
Now all we need to do is check each element of this 5 by 5 puzzle array and draw a black pixel accordingly.
In this example I just hard-scripted the 5 by 5 size of the grid, but obviously you’d make that depend on the same variables that dictate the size of the render target earlier. And in this example, because we start with a white canvas, I am only drawing black pixels where the puzzle array contains a 1 and ignoring where it contains a 0. And testing this by running it in the editor gives us:
Now obviously you don’t want to redraw a whole puzzle from scratch every time. Say you’re using it as a live preview of what the player is puzzling together, you will only want to draw where changes have occurred, drawing a white pixel where the player has removed a block, for example, adding a black pixel where they drew one and ignoring all the array indexes that have remained unchanged from the previous time you’ve checked.
Draw Canvas to Render Target is a very useful node for creating things like Piczle Cross style puzzle previews, coloured Piczle Colors style puzzle solutions, drawing the location of players or enemies on mini-maps on your HUD, etc. You can even create little drawing programs with this by painting specific textures onto another texture where the player specifies, add stickers or decals to textures (instead of using UE4’s dedicated decal system). The possibilities, perhaps not endless, are numerous indeed!