Choose color/palette swap?

Hi pals!
I’ve been wandering throughout a lot of links and webpages all talking almost about what I’d like to do, but I couldn’t understand none of them.
As usual, not only I admit my knowledge of the argument itself is pretty lacking, but also the inability to have a talk with someone who can actually explain it to me in a more intelligible way, you know, it’s not really helpful.

So what’s this all about?
I’m about to color some drawings with which I plan to make a character in a 2d videogame.
I’ll import them in Unity as one big atlas.
Correct me if I’m wrong, when I do this the original atlas is considered to be a texture, while what I trim from it is a sprite, right?
Well then, what I’d like to do is to let players choose from a 255 RGB ramp the color they’d like to represent with a certain portion of the sprite.

In other words… when I’m in Photoshop and want to change an image’s colors I just press Command+U and a wonderful window pops up complete with a set of HSL/HSV ramps.
To prevent all colors to change I could do many things, usually I just keep different colors on different layers and that’s it.

Let’s pretend I already know how to make a 255 RGB ramp and display it, then what should I do to pick a color on my original texture and change it to another one? Maybe one that I’ve just selected with, say, my mouse from that ramp? This transformation will apply to all the sprites derived from that texture? Why should I need to color my texture in grayscale… I mean, different colors are just different, what’s the point in having them depicted with a certain shade of gray in the first place? Does what I’m asking make sense to you?

Plus, is this thing also known as palette swapping?
I know those are a bunch of topics and I’m really sorry about it, but I have to understand this thing as soon as I can and usually the faster way I know of is asking to you guys.

Thanks and sorry for my english!

What the hell does that code do?!..

  1. Firstly we must somehow fetch the sprite that was clicked on. To do this I use a raycast from the camera to the mouse position. This requires your sprite to have a collider! (it can be a trigger). If there isn’t a collider on your sprite gameObject the raycast will return nothing or whatever is behind your sprite.
  2. Once we have the gameObject of your sprite we must get the actual spritRender component of it using GetComponent()
  3. now that we have found a GO with a spriteRenderer that we want to recolour we must find which pixel was clicked, this is the difficult part! (or at least I don’t know an easier way unless you know the shader is not effected by lighting).
  4. If the sprite is known to never be rotated then most of the code in GetPixelColor is obsolete, though if you have sprites (like arms, legs) that are being rotated then GetPixelColor is able to work out how far from the bottom corner of the sprite the hit point is in Cartesian coordinates that are relative to the sprites axis (this assumes that the box collider fits the sprite exactly, which is the default when you add a new box collider).
  5. Once we have the vector from the bottom corner of the collider to the hit point, relative to the sprite. We can use this information combined with the size of the piece of texture used by the sprite, to find the exact coordinate of the pixel clicked in the main texture atlas.
  6. Once we have the pixel we wish to recolour we need to find others of the same or similar colour which is done in ‘RecolourTexture’
  7. RecolourTexture takes several arguments, the ones you may wish to change which are given yourself are ‘buffer’ which is an integer which allows all colours that are a ‘distance’ of buffer away from your selected colour to also be coloured (where 255 will colour everything). And ‘wholeAtlas’ which you should give a value of true or false.
  8. If wholeAtlas is set to true, the recolour will be applied to the entire texture atlas, if it is set to false then only the section of the texture used by the clicked sprite will be affected.
  9. The last variable you will wish to change is ‘texColor’ which is the colour that you are swapping in, in place of the selected colour. As your question says, ‘Let’s pretend I already know how to make a 255 RGB ramp and display it’ you would use your result from that colour ramp and pass those values to ‘texColor’, as it is, you should be able to change the colour from the instpector.



The Code




private var screenTexture : Texture2D;
private var mousePos : Vector2;
private var screenColor : Color;
var texColor : Color;
private var colorPreview : boolean = false;
var buffer : int = 20;

function Start(){
	screenTexture = new Texture2D(1, 1);
}

function RecolourTexture(point : Vector2, sprite : Sprite, buffer : int, wholeAtlas : boolean){
	var texture = sprite.texture;
	var spriteRect = sprite.rect;
	var selectedColor = texture.GetPixel(point.x, point.y);
	var correctedBuffer = (parseFloat(buffer)/255.0);
	var colorDiff : float;
	var pixels : Color[];
	
	if(wholeAtlas){
		pixels = texture.GetPixels(0, 0, texture.width, texture.height);
		for(pix in pixels){
			colorDiff = Vector4(
				pix.r-selectedColor.r,
				pix.g-selectedColor.g,
				pix.b-selectedColor.b,
				pix.a-selectedColor.a).sqrMagnitude;
			if(colorDiff <= 4*(correctedBuffer*correctedBuffer)){
				pix = texColor;
			}
		}
		texture.SetPixels(0, 0, texture.width, texture.height, pixels);
		texture.Apply();
	}else{
		pixels = texture.GetPixels(spriteRect.x, spriteRect.y, spriteRect.width, spriteRect.height);
		for(pix in pixels){
			colorDiff = Vector4(
				pix.r-selectedColor.r,
				pix.g-selectedColor.g,
				pix.b-selectedColor.b,
				pix.a-selectedColor.a).sqrMagnitude;
			if(colorDiff <= 4*(correctedBuffer*correctedBuffer)){
				pix = texColor;
			}
		}
		texture.SetPixels(spriteRect.x, spriteRect.y, spriteRect.width, spriteRect.height, pixels);
		texture.Apply();
	}
}

function GetPixelColor(hit : RaycastHit, spriteRenderer : SpriteRenderer){
	var point = hit.point;
	var col = hit.collider;
	var go = col.gameObject;
	var up = go.transform.up;
	var right = go.transform.right;
	var forward = go.transform.forward;
	var hitVector = point - go.transform.position;
	var projectedVector = Vector3(
		Vector3.Project(hitVector, right).magnitude,
		Vector3.Project(hitVector, up).magnitude,
		Vector3.Project(hitVector, forward).magnitude);
	var scalerVector = Vector3(
		Vector3.Dot(right, Vector3.Project(hitVector, right))
			/Mathf.Abs(Vector3.Dot(right, Vector3.Project(hitVector, right))),
		Vector3.Dot(up, Vector3.Project(hitVector, up))
			/Mathf.Abs(Vector3.Dot(up, Vector3.Project(hitVector, up))),
		Vector3.Dot(forward, Vector3.Project(hitVector, forward))
			/Mathf.Abs(Vector3.Dot(forward, Vector3.Project(hitVector, forward))));
	var realVector = Vector3.Scale(projectedVector, scalerVector)
		+(0.5*Vector3.Scale(col.size, go.transform.localScale));
		
	var sprite : Sprite = spriteRenderer.sprite;
	var spriteRect = sprite.rect;
	var pixelPos = Vector2(
		spriteRect.x + Mathf.Clamp(Mathf.Floor(realVector.x*spriteRect.width), 0, spriteRect.width),
		spriteRect.y + Mathf.Clamp(Mathf.Floor(realVector.y*spriteRect.height), 0, spriteRect.height));
	
	RecolourTexture(pixelPos, sprite, buffer, false);
}

function Update(){
	mousePos = Vector2(Mathf.Clamp(Input.mousePosition.x, 0, Screen.width-1), Mathf.Clamp(Input.mousePosition.y, 0, Screen.height-1));
	if(Input.GetButtonDown("Fire1")){
		colorPreview = true;
	}
	
	if(Input.GetButton("Fire1")){
		//colorPreview = false;
		
		var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
		var hit : RaycastHit;
		if (Physics.Raycast (ray, hit)) {
			var col = hit.collider;
			var spriteRenderer = col.gameObject.GetComponent.<SpriteRenderer>();
			if(spriteRenderer != null){
				GetPixelColor(hit, spriteRenderer);
			}
		}
	}
	
}

function OnPostRender(){
	screenTexture = ScreenColor(mousePos, screenTexture);
}

function ScreenColor(mousePos : Vector2, tex2 : Texture2D){
	var tex = new Texture2D(1, 1);
	tex.ReadPixels(new Rect(mousePos.x, mousePos.y, 1, 1), 0, 0);
	var col = tex.GetPixel(0, 0);
	col.a = 1;
	screenColor = col;
	tex.SetPixel(0, 0, col);
	tex.Apply();
	return tex;
}

function OnGUI(){
	if(colorPreview){
		GUI.Box(Rect(mousePos.x-12,Screen.height-mousePos.y-12,14,14), "");
		GUI.DrawTexture(Rect(mousePos.x-10,Screen.height-mousePos.y-10,10,10), screenTexture);
	}
}



Don’t expect many people to write code like this for you, I was simply interested enough to want to find a solution myself :slight_smile:

Hope that helps!

Scribe