How do you set a texture to tile in Sprite Kit
Asked Answered
E

7

7

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

Effeminacy answered 13/11, 2013 at 20:19 Comment(0)
O
10

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.

Orsini answered 13/11, 2013 at 21:22 Comment(1)
Shader based implementation that uses just 1 node https://mcmap.net/q/1175943/-sprite-kit-and-colorwithpatternimageBrooke
G
1

Apply ciaffinetile filter when creating texture.

Ganges answered 13/11, 2013 at 20:58 Comment(0)
S
1
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

Strictly answered 26/1, 2014 at 19:2 Comment(2)
A belated thank you for this post - can you tell me where unit coordinate space is defined in Apple documentation?Banket
@rswayz, Added description and link in answer above.Strictly
D
1

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;
}
Diagnostic answered 28/7, 2017 at 10:59 Comment(0)
B
0

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);
}
Banket answered 12/2, 2017 at 17:3 Comment(0)
R
0

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.

Roentgenoscope answered 11/1, 2019 at 11:6 Comment(0)
S
0

The better and simpler way is certainly via SKShader. A shader as simple as this does:

// SpriteTiled.fsh

//uniform vec2 u_offset;
//uniform vec2 u_tiling;

void main() {
    vec2 uv = v_tex_coord.xy + u_offset;
    vec2 phase = fract(uv / u_tiling);
    vec4 current_color = texture2D(u_texture, phase);

    gl_FragColor = current_color;
}

Then set the shader property of your SKSpriteNode with:

shader = SKShader(
    named: "SpriteTiled", 
    uniforms: [
        // e.g., 0 has no effect and 1 will offset the texture by its whole extent
        SKUniform(name: "u_offset", vectorFloat2: vector_float2(0, 0)),
        // e.g., 0.5 will repeat the texture twice, 1 produces no effect, and 1 < "zooms in" on the texture
        SKUniform(name: "u_tiling", vectorFloat2: vector_float2(0.5, 0.5))
    ]
)

if your source texture isn't square, then simply multiply the first component of the value passed to u_tiling by the aspect ratio of your texture.

Seychelles answered 28/8, 2021 at 9:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.