I\'m trying to animate my game using a sprite sheet. How would I go about cutting out each sprite from the sprite sheet and using the sprite in xcode? I\'m currently using o
In sprite kit you can cut part of a texture out using SKTexture(rect: inTexture:)
initializer. This is a helper class which manages an evenly spaced sprite sheet and can cut out a texture at a given row and column. It is used like So
let sheet=SpriteSheet(texture: SKTexture(imageNamed: "spritesheet"), rows: 1, columns: 11, spacing: 1, margin: 1)
let sprite=SKSpriteNode(texture: sheet.textureForColumn(0, row: 0))
Here is the full code
//
// SpriteSheet.swift
//
import SpriteKit
class SpriteSheet {
let texture: SKTexture
let rows: Int
let columns: Int
var margin: CGFloat=0
var spacing: CGFloat=0
var frameSize: CGSize {
return CGSize(width: (self.texture.size().width-(self.margin*2+self.spacing*CGFloat(self.columns-1)))/CGFloat(self.columns),
height: (self.texture.size().height-(self.margin*2+self.spacing*CGFloat(self.rows-1)))/CGFloat(self.rows))
}
init(texture: SKTexture, rows: Int, columns: Int, spacing: CGFloat, margin: CGFloat) {
self.texture=texture
self.rows=rows
self.columns=columns
self.spacing=spacing
self.margin=margin
}
convenience init(texture: SKTexture, rows: Int, columns: Int) {
self.init(texture: texture, rows: rows, columns: columns, spacing: 0, margin: 0)
}
func textureForColumn(column: Int, row: Int)->SKTexture? {
if !(0...self.rows ~= row && 0...self.columns ~= column) {
//location is out of bounds
return nil
}
var textureRect=CGRect(x: self.margin+CGFloat(column)*(self.frameSize.width+self.spacing)-self.spacing,
y: self.margin+CGFloat(row)*(self.frameSize.height+self.spacing)-self.spacing,
width: self.frameSize.width,
height: self.frameSize.height)
textureRect=CGRect(x: textureRect.origin.x/self.texture.size().width, y: textureRect.origin.y/self.texture.size().height,
width: textureRect.size.width/self.texture.size().width, height: textureRect.size.height/self.texture.size().height)
return SKTexture(rect: textureRect, inTexture: self.texture)
}
}
The margin property is the gap between the edge of the image and the sprites. The spacing is the gap between each sprite. The fameSize is the size each sprite will be. This image explains it:
Since the original question was tagged as Objective-C, here's a rough version of this SpriteSheet class in that language. Hope this is helpful:
#import "SpriteSheet.h"
@interface SpriteSheet ()
@property (nonatomic, strong) SKTexture *texture;
@property (nonatomic) NSInteger rows;
@property (nonatomic) NSInteger cols;
@property (nonatomic) CGFloat margin;
@property (nonatomic) CGFloat spacing;
@property (nonatomic, getter=frameSize) CGSize frameSize;
@end
@implementation SpriteSheet
- (instancetype)initWithTextureName:(NSString *)name rows:(NSInteger)rows cols:(NSInteger)cols margin:(CGFloat)margin spacing:(CGFloat)spacing {
SKTexture *texture = [SKTexture textureWithImageNamed:name];
return [self initWithTexture:texture rows:rows cols:cols margin:margin spacing:spacing];
}
- (instancetype)initWithTexture:(SKTexture *)texture rows:(NSInteger)rows cols:(NSInteger)cols {
return [self initWithTexture:texture rows:rows cols:cols margin:0 spacing:0];
}
- (instancetype)initWithTexture:(SKTexture *)texture rows:(NSInteger)rows cols:(NSInteger)cols margin:(CGFloat)margin spacing:(CGFloat)spacing {
if (self == [super init]) {
_texture = texture;
_rows = rows;
_cols = cols;
_margin = margin;
_spacing = spacing;
_frameSize = [self frameSize];
}
return self;
}
- (CGSize)frameSize {
CGSize newSize = CGSizeMake((self.texture.size.width - (self.margin * 2.0 + self.spacing * ((CGFloat)self.cols - 1.0))) / ((CGFloat)self.cols),
(self.texture.size.height - ((self.margin * 2.0) + (self.spacing * ((CGFloat)self.rows - 1.0))) / ((CGFloat)self.rows)));
return newSize;
}
- (SKTexture *)textureForColumn:(NSInteger)column andRow:(NSInteger)row {
if (column >= (self.cols) || row >= (self.rows)) {
NSLog(@"Asking for row or col greater than spritesheet");
return nil;
}
CGRect textureRect = CGRectMake(self.margin + (column * self.frameSize.width + self.spacing) - self.spacing,
self.margin + (row * self.frameSize.width + self.spacing) - self.spacing, // note using width here
self.frameSize.width,
self.frameSize.height);
textureRect = CGRectMake(textureRect.origin.x / self.texture.size.width, textureRect.origin.y / self.texture.size.height, textureRect.size.width / self.texture.size.width, textureRect.size.height/self.texture.size.height);
return [SKTexture textureWithRect:textureRect inTexture:self.texture];
}
I am adding this into a separate answer, because it contains two important things, that I found:
First :
var textureRect=CGRect(x: self.margin+CGFloat(column)*(self.frameSize.width+self.spacing)-self.spacing,
y: self.margin+CGFloat(row)*(self.frameSize.width+self.spacing)-self.spacing,
width: self.frameSize.width,
height: self.frameSize.height)
This code in Swift and its Objective-C counterpart both have a bug. The self.frameSize.width
is used for both row and column calculations. The height needs to be used for column.
Second :
The coordinate system for SKTexture
places its (0,0) origin at the bottom left corner of the image, this has to be considered when making your sprites or the code needs to be altered.
For example You can get all the frames of the sprite from top left corner to bottom right corner of the sprite like this:
var anim = [SKTexture]()
for row in (0..<self.rows).reversed() {
for column in 0..<self.columns {
if let frame = textureForColumn(column: column, row: row) {
anim.append(frame)
}
}
}
PS. I don't have the much experience with SpriteKit, so if there are any mistakes in my answer, please correct me.
I did a fix for getting sprites from top to bottom, left to right.
Also added a totalFrames so if you don't have a sprite with all frames, you can set the total frames so the array only contains the sprites, no blank sprites.
//
// SpriteSheet.swift
//
import SpriteKit
class SpriteSheet {
let texture: SKTexture
let rows: Int
let columns: Int
var margin: CGFloat=0
var spacing: CGFloat=0
var frameSize: CGSize {
return CGSize(width: (self.texture.size().width-(self.margin*2+self.spacing*CGFloat(self.columns-1)))/CGFloat(self.columns),
height: (self.texture.size().height-(self.margin*2+self.spacing*CGFloat(self.rows-1)))/CGFloat(self.rows))
}
init(texture: SKTexture, rows: Int, columns: Int, spacing: CGFloat, margin: CGFloat) {
self.texture=texture
self.rows=rows
self.columns=columns
self.spacing=spacing
self.margin=margin
}
convenience init(texture: SKTexture, rows: Int, columns: Int) {
self.init(texture: texture, rows: rows, columns: columns, spacing: 0, margin: 0)
}
func textureForColumn(column: Int, row: Int)->SKTexture? {
if !(0...self.rows ~= row && 0...self.columns ~= column) {
return nil
}
var textureRect = CGRect(x: self.margin + CGFloat(column) * (self.frameSize.width+self.spacing)-self.spacing, y: self.margin + CGFloat(self.rows - row - 1) * (self.frameSize.height+self.spacing)-self.spacing,
width: self.frameSize.width,
height: self.frameSize.height)
textureRect = CGRect(x: textureRect.origin.x/self.texture.size().width, y: textureRect.origin.y/self.texture.size().height,
width: textureRect.size.width/self.texture.size().width, height: textureRect.size.height/self.texture.size().height)
return SKTexture(rect: textureRect, in: self.texture)
}
}
To call, just add the animation like this:
fileprivate let framesOpening: [SKTexture] = {
let totalFrames = 28
let rows = 6
let columns = 5
let sheet = SpriteSheet(texture: SKTexture(imageNamed: "GETREADY"), rows: rows, columns: columns, spacing: 0, margin: 0)
var frames = [SKTexture]()
var count = 0
for row in (0..<rows) {
for column in (0..<columns){
if count < totalFrames {
if let texture = sheet.textureForColumn(column: column, row: row) {
frames.append(texture)
}
}
count+=1
}
}
return frames
}()
and call it
let sprite1 = SKSpriteNode(texture: framesOpening.first)
let openingAnimation: SKAction = SKAction.animate(with: framesOpening, timePerFrame: 0.2, resize: false, restore: true)
sprite1.position = CGPoint(x: frame.midX, y: frame.midY)
sprite1.zPosition = 2000
self.addChild(sprite1)
sprite1.run(SKAction.repeatForever(openingAnimation))