- Home /
How to get "pixel" color for Sprite (U4.3)?
Task: need to get sprites's color after click on it.
In 3D it was done using RaycastHit -> textureCoord -> texture.GetPixel (like this http://answers.unity3d.com/questions/545519/best-way-to-click-on-a-object-behind-other.html, but reinvented own wheel)))
But in unity sprites (U4.3) RaycastHit2D has no textureCoord at all. Is there any was to get coordinates or something else that can help to get "pixel's" color?
We had to end up using 3d colliders for our 2d game, you may have to do that too. It seems unity forgot a ton of stuff for 2d
RaycastHit2D.point is a Vector2 that represents where the collision occured, and Texture2D has the function GetPixel(). Not sure how you'd pull them together, but it seems like the right direction
Answer by Anxo · Jul 17, 2014 at 09:23 PM
Concept: Grab a sprite, grab the texture of the sprite, scan the texture.
But first, change your import settings to Advanced and enable read and write.
Execution:
private Texture2D myTexture;
private Sprite mySprite;
void Start(){
mySprite = gameObject.GetComponent<SpriteRenderer>().sprite;
myTexture = mySprite.texture;
Color MyPixel = myTexture.GetPixel(x,y);
}
@Anxo - good start, but you only get them part of the way there. First, only part of the sprite texture may be mapped into the sprite. See Sprite.rect. In addition, you still need to figure out how to map a mouse/hit position in world coordinates to the pixel you need to grab.
Answer by guavaman · Apr 29, 2015 at 10:35 PM
This will get a pixel's color from a Sprite's texture without using colliders. This is based on casting a ray from a mouse position to get the pixel's color. Note that this gets the texture's color, not the color as processed by the material's shaders. It also requires that your texture have the Read/Write option enabled in the inspector settings or it will throw an error. It works in 2D orthographic and 3D perspective.
(This was adapted from a sprite selector I just wrote that uses the same technique to select the sprite by clicking on any opaque pixel in the sprite, ignoring transparent pixels.)
Note: This will not work on PVRTC texture compression because texture.GetPixel is not supported.
public bool GetSpritePixelColorUnderMousePointer(SpriteRenderer spriteRenderer, out Color color) {
color = new Color();
Camera cam = Camera.main;
Vector2 mousePos = Input.mousePosition;
Vector2 viewportPos = cam.ScreenToViewportPoint(mousePos);
if(viewportPos.x < 0.0f || viewportPos.x > 1.0f || viewportPos.y < 0.0f || viewportPos.y > 1.0f) return false; // out of viewport bounds
// Cast a ray from viewport point into world
Ray ray = cam.ViewportPointToRay(viewportPos);
// Check for intersection with sprite and get the color
return IntersectsSprite(spriteRenderer, ray, out color);
}
private bool IntersectsSprite(SpriteRenderer spriteRenderer, Ray ray, out Color color) {
color = new Color();
if(spriteRenderer == null) return false;
Sprite sprite = spriteRenderer.sprite;
if(sprite == null) return false;
Texture2D texture = sprite.texture;
if(texture == null) return false;
// Check atlas packing mode
if(sprite.packed && sprite.packingMode == SpritePackingMode.Tight) {
// Cannot use textureRect on tightly packed sprites
Debug.LogError("SpritePackingMode.Tight atlas packing is not supported!");
// TODO: support tightly packed sprites
return false;
}
// Craete a plane so it has the same orientation as the sprite transform
Plane plane = new Plane(transform.forward, transform.position);
// Intersect the ray and the plane
float rayIntersectDist; // the distance from the ray origin to the intersection point
if(!plane.Raycast(ray, out rayIntersectDist)) return false; // no intersection
// Convert world position to sprite position
// worldToLocalMatrix.MultiplyPoint3x4 returns a value from based on the texture dimensions (+/- half texDimension / pixelsPerUnit) )
// 0, 0 corresponds to the center of the TEXTURE ITSELF, not the center of the trimmed sprite textureRect
Vector3 spritePos = spriteRenderer.worldToLocalMatrix.MultiplyPoint3x4(ray.origin + (ray.direction * rayIntersectDist));
Rect textureRect = sprite.textureRect;
float pixelsPerUnit = sprite.pixelsPerUnit;
float halfRealTexWidth = texture.width * 0.5f; // use the real texture width here because center is based on this -- probably won't work right for atlases
float halfRealTexHeight = texture.height * 0.5f;
// Convert to pixel position, offsetting so 0,0 is in lower left instead of center
int texPosX = (int)(spritePos.x * pixelsPerUnit + halfRealTexWidth);
int texPosY = (int)(spritePos.y * pixelsPerUnit + halfRealTexHeight);
// Check if pixel is within texture
if(texPosX < 0 || texPosX < textureRect.x || texPosX >= Mathf.FloorToInt(textureRect.xMax)) return false; // out of bounds
if(texPosY < 0 || texPosY < textureRect.y || texPosY >= Mathf.FloorToInt(textureRect.yMax)) return false; // out of bounds
// Get pixel color
color = texture.GetPixel(texPosX, texPosY);
return true;
}
Hey, thanks for this awesome piece of code. I used this to check if the mouse is over a sprite by checking if alpha value > a certain threshold.
One thing I noticed was that this implementation didn't work when using sprite-sheets (Sprite Type "$$anonymous$$ultiple" in the Texture Settings). Luckily there is an easy fix:
Rect textureRect = sprite.textureRect;
float pixelsPerUnit = sprite.pixelsPerUnit;
float halfRealTexWidth = sprite.rect.width * 0.5f;
float halfRealTexHeight = sprite.rect.height * 0.5f;
int texPosX = (int)(sprite.rect.x + (spritePos.x * pixelsPerUnit + halfRealTexWidth));
int texPosY = (int)(sprite.rect.y + (spritePos.y * pixelsPerUnit + halfRealTexHeight));
// Check if pixel is within texture
if(texPosX < 0 || texPosX < textureRect.x || texPosX >= $$anonymous$$athf.FloorToInt(textureRect.x$$anonymous$$ax)) return false;
if(texPosY < 0 || texPosY < textureRect.y || texPosY >= $$anonymous$$athf.FloorToInt(textureRect.y$$anonymous$$ax)) return false;
Thanks for this code.
If someone finds this answer, like I did, and needs to adapt to use with sprite atlas with custom pivots, just replace these lines:
int texPosX = (int)(spritePos.x * pixelsPerUnit + sprite.rect.x + sprite.pivot.x);
int texPosY = (int)(spritePos.y * pixelsPerUnit + sprite.rect.y + sprite.pivot.y);
// Check if pixel is within texture
if (texPosX < 0 || texPosX < sprite.rect.x || texPosX >= $$anonymous$$athf.FloorToInt(sprite.rect.x$$anonymous$$ax)) return false;
if (texPosY < 0 || texPosY < sprite.rect.y || texPosY >= $$anonymous$$athf.FloorToInt(sprite.rect.y$$anonymous$$ax)) return false;
This code is brilliant and I while I understand it I don't know how to implement or 'call' it.
Can anyone explain what I would pass in for the SpriteRenderer and Color params?
The method finds the color at the desired pixel and returns the value through an 'out' param, but since I don't know any color information yet, how do I pass anything in?
Also why is the methods return type set to a bool?
Would really appreciate any help or maybe I'm missing something on how 'out' works.
Thanks.
Your answer
Follow this Question
Related Questions
Incorrect Sprite Color on Android build 0 Answers
Change dynamicaly a PNG used in a motion 1 Answer
Sprite color changes after import 1 Answer
renderer.enabled doesn't work but coloring does 1 Answer
White edge around my sprites 3 Answers