Do we have any way of repeating an image across an area, like a SKSpriteNode? SKColor colorWithPatternImage
doesn\'t work unfortunately.
Edit:
I
Yes, it is possible to implement that with a call to CGContextDrawTiledImage(), but that wastes a lot of memory for medium and large size nodes. A significantly improved approach is provided at spritekit_repeat_shader. This blog post provides example GLSL code and BSD licensed source is provided.
I found that the above linked sprite kit shader did not work with Xcode 10, so I rolled my own. Here is the shader code:
void main(void) {
vec2 offset = sprite_size - fmod(node_size, sprite_size) / 2;
vec2 pixel = v_tex_coord * node_size + offset;
vec2 target = fmod(pixel, sprite_size) / sprite_size;
vec4 px = texture2D(u_texture, target);
gl_FragColor = px;
}
Note that the offset
variable is only required if you want the pattern centralised - you can omit it, and its addition in the following line, if you want your tile pattern to start with the tile texture's bottom left corner.
Also note that you will need to manually add the node_size
and sprite_size
variables to the shader (and update them if they change) as neither of these have standard representations any more.
// The sprite node's texture will be used as a single tile
let node = SKSpriteNode(imageNamed: "TestTile")
let tileShader = SKShader(fileNamed: "TileShader.fsh")
// The shader needs to know the tile size and the node's final size.
tileShader.attributes = [
SKAttribute(name: "sprite_size", type: .vectorFloat2),
SKAttribute(name: "node_size", type: .vectorFloat2)
]
// At this point, the node's size is equal to its texture's size.
// We can therefore use it as the sprite size in the shader.
let spriteSize = vector_float2(
Float(node.size.width),
Float(node.size.height)
)
// Replace this with the desired size of the node.
// We will set this as the size of the node later.
let size = CGSize(x: 512, y: 256)
let nodeSize = vector_float2(
Float(size.width),
Float(size.height)
)
newBackground.setValue(
SKAttributeValue(vectorFloat2: spriteSize),
forAttribute: "sprite_size"
)
newBackground.setValue(
SKAttributeValue(vectorFloat2: nodeSize),
forAttribute: "node_size"
)
node.shader = tileShader
node.size = size
iOS working code:
CGRect textureSize = CGRectMake(0, 0, 488, 650);
CGImageRef backgroundCGImage = [UIImage imageNamed:@"background.png"].CGImage;
UIGraphicsBeginImageContext(self.level.worldSize); // use WithOptions to set scale for retina display
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawTiledImage(context, textureSize, backgroundCGImage);
UIImage *tiledBackground = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
SKTexture *backgroundTexture = [SKTexture textureWithCGImage:tiledBackground.CGImage];
SKSpriteNode *backgroundNode = [SKSpriteNode spriteNodeWithTexture:backgroundTexture];
[self addChild:backgroundNode];