7
votes

I am making a sprite kit game and it is obviously more efficient to have one big SKSpriteNode with a tiled texture, than having multiple SKSpriteNodes with the tile texture. My Problem is that when I try to make a 5x5 tile using

SKTexture* tex = [SKTexture textureWithRect:CGRectMake(0,0,5,5) inTexture:[SKTexture textureWithImageNamed:@"tile.png"]];
SKSpriteNode* node = [SKSpriteNode spriteNodeWithTexture:tex size:CGSizeMake(100,100)];

The image is re sized appropriately, but it is clamped and not tiled. In openGL terms I am getting a GL_CLAMP_TO_EDGE where I want a GL_REPEAT. Is there anyway I could achieve the tiling effect of a texture in a single SKSpriteNode, without creating a very large image.

Here is an image of my problem: enter image description here

6

6 Answers

10
votes

Afaik, there is no way to create sprite with tiled texture. But what you can do, is render lot's of sprites in a single drawing pass.

From Apple's documentation (Sprite Kit Best Practices -> Drawing your content):

If all of the children of a node use the same blend mode and texture atlas, then Sprite Kit can usually draw these sprites in a single drawing pass. On the other hand, if the children are organized so that the drawing mode changes for each new sprite, then Sprite Kit might perform as one drawing pass per sprite, which is quite inefficient.

1
votes

Apply ciaffinetile filter when creating texture.

1
votes
SKTexture* tex = [SKTexture textureWithRect:CGRectMake(0,0,5,5) inTexture:[SKTexture textureWithImageNamed:@"tile.png"]];

The textureWithRect:inTexture: creates a subtexture, and the rectangle argument is in unit coordinate space, which means all values are 0-1

Behavior of CGRectMake(0,0,5,5) is non defined, but seems to be stretching the texture by a factor of 5.


Edit: Unit Coordinate Space

You can think of the unit coordinates as specifying a percentage of the total possible value. Every coordinate in the unit coordinate space has a range of 0.0 to 1.0. For example, along the x-axis, the left edge is at the coordinate 0.0 and the right edge is at the coordinate 1.0. Along the y-axis, the orientation of unit coordinate values changes depending on the platform

Core Animation Programming Guide, Layers Use Two Types of Coordinate Systems

1
votes

I found the question not perfectly answered. I solved it this way.

You know you can do this in UIButton or UIImageView by resizableImageWithCapInsets ,so ,Here is my solution :

-(SKTexture *)textureWithImage:(UIImage *)image tiledForSize:(CGSize)size withCapInsets:(UIEdgeInsets)capInsets{
//get resizable image
image =  [image resizableImageWithCapInsets:capInsets];
//redraw image to target size
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f);
[image drawInRect:CGRectMake(0, 0, size.width-1, size.height-1)];
[[UIColor clearColor] setFill];
//get target image
UIImage *ResultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// get texture
SKTexture * texture = [SKTexture textureWithImage:ResultImage];
return texture;
}
0
votes

In order to help me convert coordinate systems, I use the following function - that way I can use the absolute coordinates like you did above with (5,5):

//Pass the absolute sizes of your sub-texture (subRect) and main-texture (mainRect)
CGRect unitCoordinateGivenSubRectInRect (CGRect subRect, CGRect mainRect)
{
if (mainRect.size.height == 0 || mainRect.size.width == 0) {
    return CGRectZero;
}

float x = subRect.origin.x / mainRect.size.width;
float y = subRect.origin.y / mainRect.size.height;

float sizeX = subRect.size.width / mainRect.size.width;
float sizeY = subRect.size.height / mainRect.size.height;

return CGRectMake(x, y, sizeX, sizeY);
}
0
votes

I used 薛永伟’s answer, and translated it to Swift in an extension:

extension SKTexture {

  static func x_repeatableTexture(named name: String, toSize size: CGSize) -> SKTexture {
    let image = UIImage(named: name)
    UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
    image?.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
    guard let resultImage = UIGraphicsGetImageFromCurrentImageContext() else {
      fatalError("Image could not be drawn.")
    }
    UIGraphicsEndImageContext()
    return SKTexture(image: resultImage)
  }

}

You can use it by doing:

let texture = SKTexture.x_repeatableTexture(named: textureName, toSize: size)

This solution allows to use slices done in the assets editor, which is really nice.