Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

[Solved] Problems creating a sprite at runtime

Discussion in '2D' started by tedsoft_games, Aug 18, 2015.

  1. tedsoft_games

    tedsoft_games

    Joined:
    May 21, 2014
    Posts:
    2
    So, I'm trying to create a sprite by taking a rectangle from a texture atlas and cropping it inside of a world space rectangle. So, the parameters are:

    1. the area (in pixel coordinates) of the requested sprite in the sprite atlas
    2. the position (in world coordinates) of the sprite center
    3. the area (in world coordinates) inside which it must be cropped (only the part of the sprite inside that rectangle must be shown)

    Now, this is the code that I've used. For all intents and purposes, this code is correct, as in, the final result (the FinalRect variable) has been thoroughly tested (both on paper and actually debugging) and is correct. The problem is that what is actually shown on screen isn't. While the crop rectangle is absolutely correct, for some reason the sprite is not cropped correctly. Some symptoms:

    1. while running the game, on the scene the sprite renderer's outline (when selecting its object) is absolutely correct, showing (again) that the crop rectangle is correct.

    Note the object outline.

    2. when increasing the resolution (as in, canvas size) of the atlas (which shouldn't impact anything, since it's just adding aditional space for new sprites) the results changed; before (512x256px atlas) the sprites were showing other areas of the image (say, corners or edges of more sprites), now (512x512px atlas) it's showing empty areas of the atlas.

    And, yes, I've checked countless times that the resulting clipping rectangle is correct. It is definitely inside the original source rectangle. Proof:


    The variable is SourceRect and the result is FinalRect.

    And yes, the pivot is correctly placed on the original position. Assuming it should usually be between (0, 0) and (1, 1) it is correctly placed.

    This is the code:

    Code (CSharp):
    1. public class SplatController : MonoBehaviour {
    2.         public Texture2D SplatTexture;
    3.  
    4.         /// <summary>
    5.         /// The area of the texture to use. In pixel, y-down coordinates.
    6.         /// </summary>
    7.         public Rect SourceRect;
    8.  
    9.         /// <summary>
    10.         /// The area inside which to keep the splat. In world, y-up coordinates.
    11.         /// </summary>
    12.         public Rect ClipRect;
    13.  
    14.         /// <summary>
    15.         /// The center of the splat
    16.         /// </summary>
    17.         public Vector2 Position;
    18.  
    19.         /// <summary>
    20.         /// The resulting clipping rectangle. (public to be seen in the inspector)
    21.         /// </summary>
    22.         public Rect FinalRect;
    23.  
    24.         public void Start() {
    25.             transform.position = new Vector3(Position.x, Position.y, -2);
    26.  
    27.             Vector2 texSizePixels = SourceRect.size;
    28.  
    29.             Vector2 texSizeWorld = texSizePixels / GameController.PixelsPerUnit;
    30.             Rect fullSpriteRectWorld = new Rect(
    31.                 Position.x - texSizeWorld.x / 2,
    32.                 Position.y - texSizeWorld.y / 2,
    33.                 texSizeWorld.x,
    34.                 texSizeWorld.y);
    35.  
    36.             Rect clippedSpriteRectWorld = RectIntersect(ClipRect, fullSpriteRectWorld);
    37.             Rect normalizedSpriteRectWorld = clippedSpriteRectWorld;
    38.             normalizedSpriteRectWorld.position -= Position - texSizeWorld / 2;
    39.  
    40.             normalizedSpriteRectWorld = RectIntersect(normalizedSpriteRectWorld,
    41.                 new Rect(0, 0, texSizeWorld.x, texSizeWorld.y));
    42.  
    43.             Rect spriteRectPixels = RectMultiply(normalizedSpriteRectWorld, GameController.PixelsPerUnit);
    44.  
    45.             if (!RectPositive(spriteRectPixels)) {
    46.                 Destroy(gameObject);
    47.                 return;
    48.             }
    49.  
    50.             Rect spriteUnitSpaceClipRect = RectDivide(spriteRectPixels, texSizePixels);
    51.  
    52.             Vector2 clipOriginSpriteUnitPivot = -spriteUnitSpaceClipRect.position + new Vector2(0.5f, 0.5f);
    53.             Vector2 pivot = Vector2ComponentDivide(clipOriginSpriteUnitPivot, spriteUnitSpaceClipRect.size);
    54.          
    55.             //FinalRect is y-down
    56.             FinalRect = new Rect(
    57.                 spriteRectPixels.x,
    58.                 texSizePixels.y - spriteRectPixels.y - spriteRectPixels.height,
    59.                 spriteRectPixels.width,
    60.                 spriteRectPixels.height);
    61.             FinalRect.position += SourceRect.position;
    62.  
    63.             Sprite result = null;
    64.             try {
    65.                 result = Sprite.Create(SplatTexture, FinalRect, pivot);
    66.                 Debug.Log($"Added sprite: ClipRect: {FinalRect}; Pivot: {pivot}; (SpriteRect: {spriteRectPixels})");
    67.             }
    68.             catch (Exception ex) {
    69.                 Debug.Log($"Could not instantiate sprite: ClipRect: {FinalRect}; Pivot: {pivot}; Ex: {ex}");
    70.             }
    71.             GetComponent<SpriteRenderer>().sprite = result;
    72.  
    73.             //Destroy(this);
    74.  
    75.         }
    76.  
    77. //-----------------------------------------------
    78. //Helper functions; 99% guaranteed to be correct. Don't worry about these.
    79. //-----------------------------------------------
    80.  
    81.         static Rect RectMultiply(Rect rect, float factor) {
    82.             return new Rect(
    83.                 rect.x * factor,
    84.                 rect.y * factor,
    85.                 rect.width * factor,
    86.                 rect.height * factor
    87.                 );
    88.         }
    89.  
    90.         static Rect RectDivide(Rect rect, Vector2 factor) {
    91.             return new Rect(
    92.                 rect.x / factor.x,
    93.                 rect.y / factor.y,
    94.                 rect.width / factor.x,
    95.                 rect.height / factor.y
    96.                 );
    97.         }
    98.  
    99.  
    100.         static Rect RectIntersect(Rect r1, Rect r2) {
    101.             float xMin = r1.xMin.Max(r2.xMin);
    102.             float yMin = r1.yMin.Max(r2.yMin);
    103.             float xMax = r1.xMax.Min(r2.xMax);
    104.             float yMax = r1.yMax.Min(r2.yMax);
    105.  
    106.             return new Rect(xMin, yMin, xMax - xMin, yMax - yMin);
    107.         }
    108.  
    109.         static bool RectPositive(Rect rect) {
    110.             return rect.width > 0 && rect.height > 0;
    111.         }
    112.  
    113.         static Vector2 Vector2ComponentDivide(Vector2 v1, Vector2 v2) {
    114.             return new Vector2(v1.x / v2.x, v1.y / v2.y);
    115.         }
    116.  
    117.         static Vector2 Vector2Inverse(Vector2 vector) {
    118.             return new Vector2(1 / vector.x, 1 / vector.y);
    119.         }
    120.     }
    So, can anyone help me? Why would Unity not respect the clipping rectangle I'm requesting?
     
    Last edited: Aug 18, 2015
  2. tedsoft_games

    tedsoft_games

    Joined:
    May 21, 2014
    Posts:
    2
    So, I've found my problem: For some unknown reason, the clipping rectangle (used in the Sprite.Create() method) is y-up (like in math class), as opposed to the standard for textures which is y-down (like in every image editor). Whatever. So, I've fixed my problem and my code:

    Code (CSharp):
    1. public class SplatController : MonoBehaviour {
    2.         public Texture2D SplatTexture;
    3.  
    4.         /// <summary>
    5.         /// The area of the texture to use. In pixel, y-down coordinates.
    6.         /// </summary>
    7.         public Rect SourceRect;
    8.  
    9.         /// <summary>
    10.         /// The area inside which to keep the splat. In world, y-up coordinates.
    11.         /// </summary>
    12.         public Rect ClipRect;
    13.  
    14.         /// <summary>
    15.         /// The center of the splat
    16.         /// </summary>
    17.         public Vector2 Position;
    18.  
    19.         public void Start() {
    20.             transform.position = new Vector3(Position.x, Position.y, -2);
    21.  
    22.             Vector2 texSizePixels = SourceRect.size;
    23.  
    24.             Vector2 texSizeWorld = texSizePixels / GameController.PixelsPerUnit;
    25.             Rect fullSpriteRectWorld = new Rect(
    26.                 Position.x - texSizeWorld.x / 2,
    27.                 Position.y - texSizeWorld.y / 2,
    28.                 texSizeWorld.x,
    29.                 texSizeWorld.y);
    30.  
    31.             Rect clippedSpriteRectWorld = RectIntersect(ClipRect, fullSpriteRectWorld);
    32.             Rect normalizedSpriteRectWorld = clippedSpriteRectWorld;
    33.             normalizedSpriteRectWorld.position -= Position - texSizeWorld / 2;
    34.  
    35.             normalizedSpriteRectWorld = RectIntersect(normalizedSpriteRectWorld,
    36.                 new Rect(0, 0, texSizeWorld.x, texSizeWorld.y));
    37.  
    38.             Rect spriteRectPixels = RectMultiply(normalizedSpriteRectWorld, GameController.PixelsPerUnit);
    39.  
    40.             if (!RectPositive(spriteRectPixels)) {
    41.                 Destroy(gameObject);
    42.                 return;
    43.             }
    44.  
    45.             Rect spriteUnitSpaceClipRect = RectDivide(spriteRectPixels, texSizePixels);
    46.  
    47.             Vector2 clipOriginSpriteUnitPivot = -spriteUnitSpaceClipRect.position + new Vector2(0.5f, 0.5f);
    48.             var pivot = Vector2ComponentDivide(clipOriginSpriteUnitPivot, spriteUnitSpaceClipRect.size);
    49.            
    50.             //FinalRect is y-up, but SourceRect is y-down
    51.             Rect finalRect = new Rect(
    52.                 spriteRectPixels.x + SourceRect.x,
    53.                 spriteRectPixels.y + (SplatTexture.height - SourceRect.y - SourceRect.height),
    54.                 spriteRectPixels.width,
    55.                 spriteRectPixels.height
    56.                 );
    57.  
    58.             GetComponent<SpriteRenderer>().sprite = Sprite.Create(SplatTexture, finalRect, pivot);
    59.  
    60.             //Destroy(this);
    61.  
    62.         }
    63.  
    64.         static Rect RectMultiply(Rect rect, float factor) {
    65.             return new Rect(
    66.                 rect.x * factor,
    67.                 rect.y * factor,
    68.                 rect.width * factor,
    69.                 rect.height * factor
    70.                 );
    71.         }
    72.  
    73.         static Rect RectDivide(Rect rect, Vector2 factor) {
    74.             return new Rect(
    75.                 rect.x / factor.x,
    76.                 rect.y / factor.y,
    77.                 rect.width / factor.x,
    78.                 rect.height / factor.y
    79.                 );
    80.         }
    81.  
    82.  
    83.         static Rect RectIntersect(Rect r1, Rect r2) {
    84.             float xMin = r1.xMin.Max(r2.xMin);
    85.             float yMin = r1.yMin.Max(r2.yMin);
    86.             float xMax = r1.xMax.Min(r2.xMax);
    87.             float yMax = r1.yMax.Min(r2.yMax);
    88.  
    89.             return new Rect(xMin, yMin, xMax - xMin, yMax - yMin);
    90.         }
    91.  
    92.         static bool RectPositive(Rect rect) {
    93.             return rect.width > 0 && rect.height > 0;
    94.         }
    95.  
    96.         static Vector2 Vector2ComponentDivide(Vector2 v1, Vector2 v2) {
    97.             return new Vector2(v1.x / v2.x, v1.y / v2.y);
    98.         }
    99.  
    100.         static Vector2 Vector2Inverse(Vector2 vector) {
    101.             return new Vector2(1 / vector.x, 1 / vector.y);
    102.         }
    103.     }
     
  3. HungryAdi

    HungryAdi

    Joined:
    Jan 20, 2018
    Posts:
    2
    I am finding a very similar problem! This is a long shot but could you (or anybody) explain how you converted the y-down system to y-up?

    Thanks
     
  4. Beastman632

    Beastman632

    Joined:
    Dec 12, 2016
    Posts:
    8
    Thanks for this. I've been struggling with this for hours now. Don't know why Unity would swap their image positioning around.

    To swap the coordinates
    newRectY = textureHeight - oldRectY - oldRectHeight