1、為什么要使用紋理集?
紋理集是將多張小圖合成一張大圖,使用紋理集有以下優(yōu)點(diǎn):
1、減少內(nèi)存占用,減少磁盤占用;
2、減少磁盤讀取次數(shù),一次性讀取一張大圖比多次讀取多張小圖速度更快;
3、減少OpenGL繪制次數(shù);
可以參照這里的講解https://blog.csdn.net/tonny_guan/article/details/26232685
2、SpriteKit自帶的紋理集工具SKTextureAtlas
SKTextureAtlas的用法比較簡單,接口也很簡單。將紋理圖片放入文件夾,并將文件夾命名為xxx.atlas,然后導(dǎo)入工程,項(xiàng)目中就可以使用方法:
+ (instancetype)atlasNamed:(NSString *)name;
創(chuàng)建紋理集,之后使用方法
- (SKTexture *)textureNamed:(NSString *)name;
獲取紋理集中的紋理。
3、TexturePacker
實(shí)際上大多數(shù)的游戲開發(fā)者都會(huì)用TexturePacker工具來打包紋理集,TexturePacker可以將一系列的小圖整合成一張大的.png圖片,外加一個(gè)描述文件,描述文件可以是.plist格式或者其他格式。描述文件的作用是描述每張小圖在大圖中的位置,旋轉(zhuǎn)等信息。TexturePacker支持市面上流行的Unity,Cocos等游戲引擎,在導(dǎo)出的時(shí)候可以選擇導(dǎo)出不同的游戲引擎。
遺憾的是SKTextureAtlas不支持TexturePacker導(dǎo)出的格式,而.atlas的文件夾的形式也不適合圖片從服務(wù)器端加載的情況。因此我想自己寫一個(gè)工具,支持讀取TexturePacker導(dǎo)出的支持Cocos格式的.plist文件,如果需要支持其他引擎,可以分析相應(yīng)的描述文件做相應(yīng)的調(diào)整。
4、TexturePacker導(dǎo)出的Cocos格式的.plist描述文件分析
.plist文件是一個(gè)字典,主要分為兩部分:
1、metadata:主要包含格式,png圖片大小,png圖片的名稱等信息

2、frames:這一部分就是原來的各個(gè)小圖片在合成到一張大圖片中之后的控制信息,這是一個(gè)數(shù)組。每個(gè)數(shù)組的數(shù)據(jù)如下:
aliases:不懂,不重要
spriteOffset:TexturePacker在合圖的時(shí)候可以選擇修剪模式(自動(dòng)裁減掉原圖的透明部分),表示裁剪之后的圖中心相對(duì)于原圖中心的偏移量
spriteSize:合圖之后的大小,這個(gè)值會(huì)小于或等于原圖大小
spriteSourceSize:原圖的大小
textureRect:這張圖在整張大圖里面的位置(要注意這個(gè)坐標(biāo)是以左上角為原點(diǎn)的)
textureRotated:是否順時(shí)針旋轉(zhuǎn)90度,合圖的時(shí)候可以勾選rotated,有的小圖有可能會(huì)被旋轉(zhuǎn)以最大限度利用空余部分
更詳細(xì)的介紹可以參考https://www.codeandweb.com/blog/2016/01/29/cocos2d-plist-format-explained

5、WPTeatureAtlas和WPTexture
我寫的WPTeatureAtlas主要負(fù)責(zé)解析.plist文件,存儲(chǔ)解析出的紋理,提供便捷方法訪問紋理
@interface WPTextureAtlas : NSObject
@property(nonatomic, strong, readonly) NSString *plistFile;
@property(nonatomic, strong, readonly) NSString *imageFile;
@property(nonatomic, strong, readonly) SKTexture *totalTexture;
@property(nonatomic, strong, readonly) NSMutableDictionary<NSString *, WPTexture *> *texturesDict;
@property(nonatomic, strong, readonly) NSArray<WPTexture *> *sortTexturesArr;
- (instancetype)initWithPlistFile:(NSString *)plistFile
imageFile:(NSString *)imageFile;
- (WPTexture *)textureWithName:(NSString *)name;
@end
WPTexture繼承自SKTexture,并添加前面描述的.plist文件中的屬性
@interface WPTexture : SKTexture
// 裁剪后圖片的中心相對(duì)于裁剪前圖片的中心偏移量
@property(nonatomic, assign, readonly) CGPoint spriteOffset;
// 裁剪之后的大小
@property(nonatomic, assign, readonly) CGSize spriteSize;
// 裁剪之前的大小
@property(nonatomic, assign, readonly) CGSize spriteSourceSize;
// 是否順時(shí)針旋轉(zhuǎn)90度
@property(nonatomic, assign, readonly) BOOL textureRotated;
+ (instancetype)textureWithDict:(NSDictionary *)dict
totalTexture:(SKTexture *)totalTexture;
@end
下面是解析代碼,這里面主要用到了SKTexture的textureWithRect:inTexture:方法;這里對(duì)textureRect做了轉(zhuǎn)換,因?yàn)樵贠penGL里面紋理坐標(biāo)是左下角為原點(diǎn),寬和高都是1,textureWithRect:inTexture:方法里面的rect參數(shù)就是紋理坐標(biāo)下面的rect。這個(gè)方法返回的texture就是一整張大圖中的一部分,達(dá)到我們截取一部分紋理的目的。
+ (instancetype)textureWithDict:(NSDictionary *)dict
totalTexture:(SKTexture *)totalTexture
{
BOOL textureRotated = [dict[@"textureRotated"] boolValue];
CGRect rect = CGRectFromString(dict[@"textureRect"]);
// TexturePacker中獲取的rect是原點(diǎn)在左上角點(diǎn),但是SpriteKit、OpenGL默認(rèn)加載的紋理坐標(biāo)是原點(diǎn)在左下角點(diǎn)
CGFloat x, y, w, h;
if (textureRotated) { // 如果順時(shí)針旋轉(zhuǎn)了90度,那么width和heigth調(diào)換了
x = rect.origin.x / totalTexture.size.width;
y = (totalTexture.size.height - rect.origin.y - rect.size.width) / totalTexture.size.height;
w = rect.size.height / totalTexture.size.width;
h = rect.size.width / totalTexture.size.height;
} else {
x = rect.origin.x / totalTexture.size.width;
y = (totalTexture.size.height - rect.origin.y - rect.size.height) / totalTexture.size.height;
w = rect.size.width / totalTexture.size.width;
h = rect.size.height / totalTexture.size.height;
}
WPTexture *texture = [WPTexture textureWithRect:CGRectMake(x, y, w, h) inTexture:totalTexture];
texture.spriteOffset = CGPointFromString(dict[@"spriteOffset"]);
texture.spriteSize = CGSizeFromString(dict[@"spriteSize"]);
texture.spriteSourceSize = CGSizeFromString(dict[@"spriteSourceSize"]);
texture.textureRotated = textureRotated;
return texture;
}
6、支持紋理的裁剪和旋轉(zhuǎn)
TexturePacker在合成圖片的時(shí)候是支持裁剪和旋轉(zhuǎn)的,裁剪可以裁剪掉透明像素,而旋轉(zhuǎn)則可以更大限度利用空間。但是前面的SKTexture創(chuàng)建的紋理僅僅是截取大圖中的一部分,如果這個(gè)圖被裁剪,或者旋轉(zhuǎn)了,那么我們直接在SKSpriteNode中使用SKTexture會(huì)導(dǎo)致貼圖的位置或方向不正確,這個(gè)時(shí)候我們需要對(duì)SKSpriteNode做幾何變換才能達(dá)到正確的效果。我寫了個(gè)WPSpriteNode來處理這種情況:
@interface WPSpriteNode : SKSpriteNode
+ (instancetype)spriteNodeWithWPTexture:(WPTexture *)texture;
- (instancetype)initWithWPTexture:(WPTexture *)texture;
@end
@implementation WPSpriteNode
+ (instancetype)spriteNodeWithWPTexture:(WPTexture *)texture
{
return [[WPSpriteNode alloc] initWithWPTexture:texture];
}
- (instancetype)initWithWPTexture:(WPTexture *)texture
{
if (self = [super initWithTexture:texture]) {
if (texture.textureRotated) {
// 順時(shí)針轉(zhuǎn)了90度之后,原來的Y轉(zhuǎn)成與X軸同向,而原來的X變成與-Y同向
self.anchorPoint = CGPointMake(0.5 - texture.spriteOffset.y / texture.spriteSize.height, 0.5 + texture.spriteOffset.x / texture.spriteSize.width);
// 逆時(shí)針轉(zhuǎn)回90度
self.zRotation = M_PI_2;
} else {
// 根據(jù)偏移量將裁剪后的圖反向移回原圖的中心
self.anchorPoint = CGPointMake(0.5 - texture.spriteOffset.x / texture.spriteSize.width, 0.5 - texture.spriteOffset.y / texture.spriteSize.height);
}
}
return self;
}
@end