Skip to content

Materials and Shaders

Most sprites never need a material — the engine draws them correctly out of the box. Materials are how you get the other looks: a glow, a dissolve, an outline, a screen-wide underwater wobble, an enemy flashing white when hit.

Placeholder: the Shader Editor, with material source on the left, a live preview, and the generated parameter inspector

Two files, and which one you edit

This trips people up once and then never again:

FileWhat it isWhere you edit it
.matThe shader — the actual program that runs per pixelThe Shader Editor panel
.jmaterialA material — one shader plus a set of values for its parametersThe Properties panel

One .mat called Glow, and three .jmaterial files using it: a blue glow, a red glow, a faint white glow. Change the shader once and all three follow.

The .jmaterial is what you assign to a renderer. Sprite Renderer, Tilemap and Mesh Renderer each have a material slot in the Properties panel; drag a .jmaterial onto it.

Writing a shader

Double-click a .mat in the Content Browser to open the Shader Editor. Its toolbar has everything you need:

ButtonWhat it does
Save (Ctrl+S)Saves and compiles — errors appear in the gutter next to the offending line and in the Log panel
CompileCompiles without saving
Open in Joystick CodeHands the file to the full editor, for anything longer than a quick tweak
View GeneratedShows what your material actually compiled down to
Cheat SheetThe built-in reference for what you can write

There's a Templates dropdown with commented starter shaders — start from one of those rather than a blank file. Below the source is a live preview you can point at a quad, a sphere, a cube or the entity you have selected, with a few lighting environments and an animation toggle for time-based effects.

Compilation happens in-process on a worker thread and typically takes tens of milliseconds, so the loop is genuinely edit-save-look.

Sprites need a sprite-compatible shader

A shader written for meshes won't work on a Sprite Renderer. The engine checks this both when the material compiles and at the moment you drop it onto a renderer, so you get told rather than getting an invisible sprite.

Parameters

Anything your shader exposes as a parameter shows up as a field in the material's inspector — the engine reads the shader and builds the UI, so you never maintain a separate list.

You can annotate a parameter in the shader source to change how it's presented: a colour swatch instead of three number boxes, a slider with a sensible range, a checkbox, a tooltip. Parameters can be numbers, vectors, colours, booleans or textures; a texture parameter also carries its own filtering and wrap settings.

A material can optionally override a small amount of render state — double-sided, depth writing, cull mode.

Changing materials from C#

csharp
public class Enemy : GameEntity
{
    private Material m_Flash;

    protected override void OnCreate()
    {
        // Shared: every entity using this .jmaterial sees the change.
        Material shared = Material.LoadShared("Materials/HitFlash.jmaterial");

        // A clone only this entity uses.
        m_Flash = shared.InstantiateMaterial();
    }

    public void OnHit()
    {
        m_Flash.SetFloat("flashAmount", 1.0f);
    }

    protected override void OnDestroy()
    {
        m_Flash.Dispose();
    }
}
MethodSets
SetFloat / GetFloatA single number
SetVector2 / SetVector3 / SetVector4Vectors
SetColor / GetColorA colour, as a Vector4 of r, g, b, a
SetBool / GetBoolA toggle
SetTextureA texture, by asset path
HasParameterWhether the shader actually declares that name

InstantiateMaterial() gives you something to dispose

LoadShared hands back the shared material — changing a parameter on it changes it for every entity using that file. InstantiateMaterial() gives you a private copy, and you own its lifetime: dispose it in OnDestroy. This is deliberately a verb rather than something that happens by accident, because leaked clones are one of the easiest performance bugs to create and one of the hardest to find.

Setting a parameter the shader doesn't declare doesn't throw — it logs a warning naming both the parameter and the material. Check the Log panel if a change appears to do nothing.

Tinting without a material

For a plain colour tint you don't need a material at all:

csharp
GetComponent<SpriteRendererComponent>().Color = new Vector4(1, 0.4f, 0.4f, 1);
GetComponent<UIImage>().Color = new Vector4(1, 1, 1, 0.5f);   // fade a UI image

And to swap the image itself:

csharp
GetComponent<SpriteRendererComponent>().TexturePath = "Textures/PlayerHurt.png";
GetComponent<UIImage>().Source = "Textures/AlertPanel.png";

Both take effect on the next frame's draw.

Shader graphs

If you'd rather build an effect out of connected nodes than write shader source, a .jshadergraph compiles down to a .mat — you get a real material out the other end, usable everywhere a hand-written one is.

The graph canvas isn't drawable yet in 0.1.0. Opening a .jshadergraph shows a summary of the graph rather than the diagram, and the visual editing surface is still being built. See Node graphs for exactly what works today across all of Joystick's node-based editors.

Keeping it fast

  • Use the default material where you can. No custom shader means no custom shader cost.
  • Sprites sharing a material and a texture batch into one draw call. Every distinct material is a batch break, so a per-enemy clone of the same effect costs more than one shared material with the same values.
  • The shader runs per pixel. A full-screen effect runs it millions of times a frame. Test on the weakest device you intend to ship to, not on your desktop.
  • Watch for per-frame material changes — swapping materials every frame defeats batching.

Performance covers how to measure any of this.

See also

  • Node graphs — what the shader graph and visual script editors can and can't do today
  • Particle effects — blend modes and additive rendering
  • Performance — draw calls, batching, and profiling