Overview
The main reason for this is pasting visual elements to the background so that they persist as they do in reality, eg. blood splats, corpses… of course it doesn’t have to all be violence related; it could be just generic build up of clutter or mess, such as paint, water, rubbish, etc. anything which you can concieve which involves “pasting” sprites onto a virutal “canvas” . But, a common theme in video games is destroying other creatures and things, so that’s the example we’re using here.
The bottom line is we will be able to visually represent the accumulation of an unlimited amount of sprites and the only cost is the fixed amount of VRAM that the canvas uses, as opposed to a continual build up of dynamic sprites which are completely dormant and don’t really need to be taking up the resources they are, since we just want a visual build up of clutter.
Also, this tutorial takes extra steps to work with a “pixel art” style game, which will save you headaches if this is your game, and if it’s not, you can leave it out and will still have learned more on the way.
Here’s a quick clip of what we are trying to achieve.

It flashes at the end because I’m toggling the canvas on and off with a key press, to prove the blood is on its own layer.
Template
Use the project of Part 1 of Mastering Render Targets, as a base. This gives us these important steps;
- Altering the render script to create a canvas to render to:
- Creating a “canvas” render target in
init() - In
update(), Rendering “predicates” to the render target (the sprites you want to “paste”) - then using the “canvas” as the texture for a quad, which will be rendered to the default render target (the final output)
- Creating a “canvas” render target in
- Using a quad mesh for rendering the canvas:
- Adding a quad to your collection
- Giving the quad a material that will be used to reference the canvas texture
- Setting up the vertex and fragment shader for the material
- A custom render script, and the game project pointing to the new renderer.
After running Part 1 as-is and having a look around, I recommend re-creating Part 1 yourself in a blank project to expose any missing gaps in your knowledge, before then attempting to re-create this setup in your game where you’ll use it, which can introduce new variables that you may have to address, eg. the camera component, see below.
NOTE: Where the tutorial has used the term “window”, I’ve gone with the name “canvas” in my examples.
Tips
Camera: The tutorial doesn’t use a camera component. As soon as you add one, the quad will disappear. This is because the default Near / Far properties of the camera are not compatible with the setup - they are set to 0.1 and 1000 respectively, when they should be set to -1 and 1.
Model component: The quad is added using the “Model” component, not the “Mesh” component, very easy to confuse, especially since they both use the same icon.
Clear colour: When trying to get the right position for the quad (see below) I found it was useful to set the clear colour to red, this would be (1,0,0,1) - may be common knowledge for many, perhaps it’s the retro programmer in me, but I found it helpful to have the reminder that the values are normalised 0 to 1 (not 0 to 255) and the 4th value, alpha, has to be full, or nothing shows.
Render script version: The render script that the tutorial uses seems to be older, there’s a lot different about the structure. So don’t copy that entire render script - copy the latest one from your builtin’s folder from a new blank project (or your current game you’re implementing the feature in) and implement the changes instructed by the tutorial. They should still work - just watch out for uninitialised variables. One difference I noticed is that the “frustum=frustum” part of the render.draw(self.paste_pred, {frustum = frustum}) is not needed in the newer version of the render script, but leaving it in doesn’t cause any issues.
Canvas alignment
For most post processing effects, it makes sense to always have the quad just fill the entire screen, which is what the Post Processing example does. But we want something that looks identical to the existing game resolution and is in the same world space as the rest of the game, so that the sprites appear to stay in place after being pasted). So we need to work with a fixed sized quad and fixed size render target.
The following assumes your game is a pixel art game with a “native” resolution of 480x270, which is 1/4 of 1920x1080. So my camera component has a zoom factor of 4.0.
Just regarding the camera position - I don’t know if this is common, but I like my 2D coordinates to have 0,0 be at the bottom left corner of the view. So my camera’s game object has it’s position centered in that 480x270 area, at 240,135.
Therefore, the quad’s game object is also set to that position, so it’s centered in the view.
Also, the model component doesn’t have a size, so you set the size by setting the scale on its game object to 480,270.
At this point, in the editor, the quad should be black and rendering over the whole 480x270 game area, and during runtime (if your render script is still using the example code) it should be showing the game contents - which will look identical to it not being there - so as a test, just set its size to maybe half the view, eg. 240,135, and you’ll see a smaller copy of the game window inside itself.
Note: If the game is running at the full 1920x1080 resolution, everything is aligned, but if you resize the window, the canvas output is off - right size just wrong position. This can probably be solved by making sure the quad is repositioned to compensate, you could do this on a window resize event in a game object script. At the time of writing I haven’t got around to dealing with this yet, but it shouldn’t be too hard. I just think the solution will be in game object scripts, not the render script, since we want to keep everything in the game’s own world space to keep everything aligned, and not mess with any view matrices like in the Post Processing example.
Canvas behaviour
We’re modifying the render script to get the behaviour we want - a canvas that doesn’t erase its contents until we say so, rather than wiping clean every frame like normal. Also, we want to only render one thing to the canvas, not the whole game output, so we can see the effect of not clearing the canvas and stamping one sprite onto it.
First, just comment out the section that clears the render target, simple enough;
Now go to your player game object, and set the sprite to use a new material - just copy the default sprite material from the builtins folder, and add a new tag: “paste”
And back in the render script, instead of rendering everything with the “tile” tag (which is both sprites and tiled backgrounds), you’ll just render everything with the “paste” tag. The original tutorial sets up stuff related to this so you can use it for reference and alter it;
-- in init()
self.paste_pred = render.predicate({"paste"})
-- in update(), after render.set_render_target(self.canvas)
render.draw(self.paste_pred)
Now when running the game, you should hopefully see something like this;

(the flashing is because I set a key to toggle the visibility of the canvas object so we can prove this canvas layer is seperate from everything else)
Once you’re here, you’re home free, the rest is just neatening up.
Finishing up
When your blood sprites are about to be removed - eg. maybe when they finish an animation, or a timer expires, or they stop moving, etc. you can switch the material to the “paste” version of the sprite material right before you destroy them, and they should be caught up in your render.draw(self.paste_pred) draw call before the end of the frame, since the engine only actually destroys game objects at the end of the current frame, after a render pass. Which is great because it keeps this transition from sprite-to-paste nice and simple.
In my case, I have a “bounce” function that causes the sprite to lose a bit of energy each time it bounces, which will eventually bring the sprite to a standstill once the movement is smaller than a certain threshold - then it’s deleted. This is the perfect place to swap materials;
local function energy_loss(self)
self.vel.x = self.vel.x * self.bounce_loss.x
self.vel.y = self.vel.y * self.bounce_loss.y
if math.abs(self.vel.x) < self.min_move.x then
self.vel.x = 0
end
if math.abs(self.vel.y) < self.min_move.y then
self.vel.y = 0
end
if self.vel.x==0 and self.vel.y==0 then
go.set("#sprite", "material", self.paste_material) --<-- the material swap
go.delete()
end
end
And of course, at some point we will have to clear this canvas, eg. between levels. So we can send a message to the renderer, where it will actually run that clear code we commented out earlier.
--somewhere in your game scripts;
msg.post("@render:", "clear canvas")
However, we can’t just clear the canvas in the on_message() function of the render script, since render commands only work during the update function. So we will set a boolean instead, and re-instate the clear command in the update function, and conditionally clear it based on this boolean;
-- in render script init():
self.clear_canvas = false
-- in render script on_message():
if message_id == hash("clear canvas") then
self.clear_canvas = true
end
-- in render script update():
if self.clear_canvas then
render.clear({
[render.BUFFER_COLOR_BIT] = self.clear_color,
[render.BUFFER_DEPTH_BIT] = 1,
[render.BUFFER_STENCIL_BIT] = 0
})
self.clear_canvas = false
end
Taking it further
My current use case is for a single screen game, with no scrolling. If the camera were to move, we would see how blood splats wouldn’t appear beyond the bounds of the canvas, as it’s placed in the collection like a regular level asset, and will be left behind.
How you would handle this depends on the complexity of your game. Eg. my next game will be a game with classic “Super Mario” style scrolling, which only scrolls to the right, and never left. I was planning on having 2 canvases which would “leap frog” over each other as the camera scrolls to the right, clearing the canvas that just jumped ahead so it can start fresh.
If you plan to have bi-directional scrolling, or even multi directional scrolling, you’ll need something more involved. You also may wish to have the blood fade out over time so as not to leave hard edges when the player eventually backtracks to a place where a canvas has already been removed. One possible way could be to “clear” every frame, with a colour that has a very low alpha every frame, with additive blending, so that it effectively gradually eats away at the alpha value of all the existing pixels until they vanish entirely.


