Recoloring Sprites in Unity

What are the Options?

  • Before running the game (ie. in Photoshop),
  • Before providing a texture to the shader,
  • Within the shader.
One possible implementation of this is to export the player-inputted colors into small gradient textures with pre-defined dark/light extrapolation, concatenate them, and provide it as a sampling texture for a sprite which is defined in partitioned grayscale.

Part 1. Palettes

[CreateAssetMenu(menuName = "Colors/Palette")]
public class Palette : ScriptableObject {
public enum Shade {
WHITE,
HIGHLIGHT,
LIGHT,
PURE,
DARK,
OUTLINE,
BLACK
}
public string colorName;
public Color highlight;
public Color light;
public Color pure;
public Color dark;
public Color outline;
private static readonly Color BLACK = Color.black;
private static readonly Color WHITE = Color.white;


public Color GetColor(Shade shade) {
if (shade == Shade.WHITE) {
return WHITE;
} else if (shade == Shade.HIGHLIGHT) {
return highlight;
} else if (shade == Shade.LIGHT) {
return light;
} else if (shade == Shade.PURE) {
return pure;
} else if (shade == Shade.DARK) {
return dark;
} else if (shade == Shade.OUTLINE) {
return outline;
}
return BLACK;
}
}

Part 2. ColorMap

public abstract class ColorMap : ScriptableObject {
protected virtual void PrepareColors() { }
protected const byte zero = 0;
public Sprite Recolor(Sprite baseSprite) {
PrepareColors();
Texture2D tex = Instantiate(baseSprite.texture);
NativeArray<Color32> pixels_n = tex.GetRawTextureData<Color32>();
int len = pixels_n.Length;
unsafe {
Color32* pixels = (Color32*)pixels_n.GetUnsafePtr();
Map(pixels, len);
}
tex.Apply();
Vector2 pivot = baseSprite.pivot;
pivot.x /= baseSprite.rect.width;
pivot.y /= baseSprite.rect.height;
var s = Sprite.Create(tex, baseSprite.rect, pivot, baseSprite.pixelsPerUnit);
return s;
}
protected abstract unsafe void Map(Color32* pixels, int len);
}
  • You need to call tex.Apply after editing a texture’s raw data,
  • You need to call GetRawTextureData and not GetPixels, which will work but also allocate half your bank account,
  • Sprite.pivot is in pixels, but when creating a new sprite you have to provide it as normalized to [(0,0), (1,1)],
  • In Sprite Import settings, make sure Read/Write Enabled is on, and you probably want the format to be RGBA 32,
  • You may need to modify other options like extrusion or mesh type in the Sprite constructor (I set my mesh type to FullRect for shader effects).
public abstract class ThreeColorGradientMap : ColorMap {
protected Color32 mBlack;
protected Color32 mGray;
protected Color32 mWhite;

protected override unsafe void Map(Color32* pixels, int len) {
for (int ii = 0; ii < len; ++ii) {
Color32 pixel = pixels[ii];
float value = pixel.r / 255f;
if (value > 0.5f) {
value = value * 2 - 1f;
//Lerp from gray-color to white-color
pixel.r = (byte)(mGray.r + value * (mWhite.r - mGray.r));
pixel.g = (byte)(mGray.g + value * (mWhite.g - mGray.g));
pixel.b = (byte)(mGray.b + value * (mWhite.b - mGray.b));
} else {
value = value * 2;
//Lerp from black-color to gray-color
pixel.r = (byte)(mBlack.r + value * (mGray.r - mBlack.r));
pixel.g = (byte)(mBlack.g + value * (mGray.g - mBlack.g));
pixel.b = (byte)(mBlack.b + value * (mGray.b - mBlack.b));
}
pixels[ii] = pixel;
}
}
}

[CreateAssetMenu(menuName = "Colors/ThreeColorGradient")]
public class PaletteThreeColorGradientMap : ThreeColorGradientMap {
public Palette mapToBlackBase;
public Palette.Shade mapToBlackShade;
public Palette mapToGrayBase;
public Palette.Shade mapToGrayShade;
public Palette mapToWhiteBase;
public Palette.Shade mapToWhiteShade;

protected override void PrepareColors() {
mBlack = mapToBlackBase.GetColor(mapToBlackShade);
mGray = mapToGrayBase.GetColor(mapToGrayShade);
mWhite = mapToWhiteBase.GetColor(mapToWhiteShade);
}
}

3. Actually Recoloring Things

public class RecolorMe : MonoBehaviour {
public ColorMap recolorMap;
private void Start() {
SpriteRenderer sr = GetComponent<SpriteRenderer>();
Sprite s = sr.sprite;
Sprite recolored_s = recolorMap.Recolor(s);
sr.sprite = recolored_s;
}
}

Conclusion

--

--

--

Software engineer, epic gamer, and Touhou fangame developer.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Salesforce Summer ’18 Sandbox Preview

Fast Inline Images With React and Webpack

https://youtu.be/5EdmHSTwmWY

Enterprise application architecture with redux (Time machine attached!)

How to CORRECTLY use let, var, const, and ‘use strict’ by Understanding Scopes

8 Addictive Games Built Only with HTML, CSS, and JavaScript 🎮✨

Title Image

Keycloak (Red Hat Single Sign On) Walkthrough Demo

How we built a website from scratch using React.js and AWS.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Bagoum

Bagoum

Software engineer, epic gamer, and Touhou fangame developer.

More from Medium

Space Shooter Game: What is a Game Object? Tranform? Component? A Script?!

Tracked Dolly Virtual Camera Explained in Unity’s Cinemachine

Height Maps And Displacement In Unity HDRP

Day 17 of Game Dev: Why Prototype Before Making a Game Pretty?