SpriteKit在哪里可以加载数千个精灵的纹理地图集 [英] SpriteKit where to load texture atlases for thousands of sprites

查看:193
本文介绍了SpriteKit在哪里可以加载数千个精灵的纹理地图集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的游戏中,我有成千上万的瓦片节点组成一个游戏地图(想simcity),我想知道什么最高帧率/内存高效的路线纹理和动画的每个节点将是?有一些独特的瓦片类型,每一个都有自己的纹理图集/动画,所以确保纹理被重复使用时,可能是关键。



所有我的 tile 节点是单个 map 节点的子节点,如果地图节点处理识别瓦片类型并加载必要的地图集,动画(例如通过从plist加载纹理和地图集名称)



或者,每个tile类型是一个特定的子类。对于每个SKSpriteNode tile来说,处理自己的sprite atlas loading例如: [tileInstance texturise]; (sprite kit如何处理这个方法,这个方法会导致相同的纹理地图集被加载到内存中的某个tile类型的每个实例) / p>

我一直在编写文档来深入解释地图集和纹理重用,但我不知道这样的场景的典型过程是什么。

解决方案

内存第一:不会有任何明显的区别。您必须加载图块的纹理,纹理将占据至少99%的Map + Tile的内存。



纹理重用:纹理被重复使用/自动缓存。使用相同纹理的两个sprite将引用相同的纹理,而不是每个都有自己的纹理副本。



Framerate / Batching:所有关于批量处理。 Sprite Kit通过按照将它们添加到children数组的顺序渲染来处理对节点的子项进行批处理。只要下一个子节点使用与前一个相同的纹理,它们都将被分组到一个绘制调用中。可能最糟糕的事情是你可以添加一个精灵,一个标签,一个精灵,一个标签等等。



Atlas使用:这里是您可以赢得的地方最多。通常开发人员尝试对他们的地图集进行分类,这是错误的方式去。而不是为每个tile(及其动画)创建一个地图集,您将需要创建尽可能少的纹理地图集,每个包含尽可能多的图块。在所有iOS 7设备上,纹理地图集可以是2048x2048,除了iPhone 4和iPad 1之外,所有其他设备都可以使用高达4096x4096像素的纹理。



例外这个规则,说,如果你有这么大量的纹理,你不能可能将它们一次加载到所有设备上的内存。在这种情况下,使用你最好的判断找到一个很好的妥协内存使用vs批处理效率。例如,一个解决方案可能是为每个唯一场景或者风景创建一个或两个纹理地图集,即使这意味着在另一个场景的其他纹理地图中复制一些图块。



至于子类化,您可以将它们放在共享地图集中。瓷砖,我是一个强大的支持者,以避免子类化节点类。特别是如果将它们子类化的主要原因只是改变他们使用/动画的纹理。一个sprite已经是一个纹理的容器,所以你也可以改变sprite纹理并从外部动画。



向节点添加数据或附加代码您可以通过创建您自己的NSMutableDictionary并添加您需要的任何对象来仔细检查其userData属性。典型的基于组件的方法如下:

  SKSpriteNode * sprite = [SKSpriteNode spriteWithWhatever。 。]; 
[self addChild:sprite];

//创建控制器对象
sprite.userData = [NSMutableDictionary dictionary];
MyTileController * controller = [MyTileController controllerWithSprite:sprite];
[sprite.userData setObject:forKey:@controller];

此控制器对象然后执行您的磁贴所需的任何自定义代码。它可以是动画的瓷砖和其他。唯一重要的一点是引用拥有节点(这里:sprite)一个弱引用:

  @interface MySpriteController 
@property(weak)sprite; //弱是重要的,避免保留周期!
@end

因为sprite保留字典。字典保留控制器。如果控制器保留子画面,则子画面不能释放,因为仍然会有一个保留引用 - 因此它将继续保留保留保留子画面的控制器的字典。



使用基于组件的方法的优点(也受 Kobold Kit 支持并实施):




  • 如果设计得当,可以使用任何或多个节点。如果有一天你想要一个标签,效果,形状节点图块?

  • 你不需要每个图块的子类。一些瓦片可以是简单的静态sprites。所以使用简单的静态SKSpriteNode。

  • 它可以根据需要启动/停止或添加/删除单个方面。即使是在你最初期望不具备或需要某个方面的图块上。

  • 组件允许你构建一个经常需要的功能,甚至可能在其他项目。

  • 组件有助于更好的架构。经典的OOP设计错误是有玩家和敌人类,然后实现两个需要能够射箭和装备装甲。因此,您可以将代码移动到根GameObject类,使代码可用于所有子类。

  • 基于组件的设计的最大好处是,您可以开始单独开发其他方面的各个方面,因此可以根据需要重复使用和添加。

  • 从我自己的经验来看,一旦你将游戏模块化为组件,你会得到更少的错误,更容易解决,因为你不必看看或考虑其他组件的代码 - 除非由组件使用,但即使当一个组件触发另一个你有一个清晰的边界,即传递的值仍然正确,当其他组件接管?



这是一个良好介绍基于组件的设计。混合方法肯定是一种方式。以下是更多基于组件设计的资源,但我强烈建议您不要偏离路径, FRP是一个有趣的概念,但在游戏开发中还没有真正的应用程序。


In my game I have thousands of "tile" nodes which make up a game map (think simcity), I am wondering what the most frame-rate/memory efficient route for texturing and animating each node would be? There a a handful of unique tile "types" which each have their own texture atlas / animations, so making sure textures are being reused when possible is key.

All my tile nodes are children of a single map node, should the map node handle recognising a tile type and loading the necessary atlas & animations (e.g. by loading texture & atlas names from a plist?)

Alternatively, each tile type is a certain subclass. Would it be better for each SKSpriteNode tile to handle their own sprite atlas loading e.g. [tileInstance texturise]; (how does sprite kit handle this? would this method result in the same texture atlas being loaded into memory for each instance of a certain tile type?)

I have been scrounging the docs for a deeper explanation of atlases and texture reusage but I don't know what the typical procedure is for a scenario like this. Any help would be appreciated, thanks.

解决方案

Memory first: there won't be any noticeable difference. You have to load the tile's textures, textures will account for at least 99% of the memory of the Map+Tiles and that's that.

Texture reuse: textures are being reused/cached automatically. Two sprites using the same texture will reference the same texture rather than each having its own copy of the texture.

Framerate/Batching: this is all about batching properly. Sprite Kit approaches batching children of a node by rendering them in the order they are added to the children array. As long as the next child node uses the same texture as the previous one, they will all be batched into one draw call. Possibly the worst thing you could do is to add a sprite, a label, a sprite, a label and so on. You'll want to add as many sprites using the same texture in consecutive order as is possible.

Atlas Usage: here's where you can win the most. Commonly developers try to categorize their atlases, which is the wrong way to go about it. Instead of creating one atlas per tile (and its animations), you'll want to create as few texture atlases as possible, each containing as many tiles as possible. On all iOS 7 devices a texture atlas can be 2048x2048 and with the exception of iPhone 4 and iPad 1 all other devices can use textures with up to 4096x4096 pixels.

There are exceptions to this rule, say if you have such a large amount of textures that you can't possibly load them all at once into memory on all devices. In that case use your best judgement to find a good compromise on memory usage vs batching efficiency. For example one solution might be to create one or two texture atlases per each unique scene or rather "scenery" even if that means duplicating some tiles in other texture atlases for another scene. If you have tiles that almost always appear in any scenery it would make sense to put those in a "shared" atlas.

As for subclassing tiles, I'm a strong proponent to avoid subclassing node classes. Especially if the main reason to subclass them is to merely change which texture they are using/animating. A sprite already is a container of a texture, so you can as well change the sprite texture and animate it from the outside.

To add data or additional code to a node you can peruse its userData property by creating your own NSMutableDictionary and adding any object you need to it. A typical component-based approach would go like this:

SKSpriteNode* sprite = [SKSpriteNode spriteWithWhatever..];
[self addChild:sprite];

// create the controller object
sprite.userData = [NSMutableDictionary dictionary];
MyTileController* controller = [MyTileController controllerWithSprite:sprite];
[sprite.userData setObject: forKey:@"controller"];

This controller object then performs any custom code needed for your tiles. It could be animating the tile and whatever else. The only important bit is to make the reference to the owning node (here: sprite) a weak reference:

@interface MySpriteController
@property (weak) sprite; // weak is important to avoid retain cycle!
@end

Because the sprite retains the dictionary. The dictionary retains the controller. If the controller would retain the sprite, the sprite couldn't deallocate because there would still be a retaining reference to it - hence it will continue to retain the dictionary which retains the controller which retains the sprite.

The advantages of using a component-based approach (also favored by and implemented in Kobold Kit):

  • If properly engineered, works with any or multiple nodes. If what if some day you want a label, effect, shape node tile?
  • You don't need a subclass for every tile. Some tiles may be simple static sprites. So use simple static SKSpriteNode for those.
  • It lets you start/stop or add/remove individual aspects as needed. Even on tiles you didn't initially expect to have or need a certain aspect.
  • Components allow you to build a repertoire of functionality you're going to need often and possibly even in other projects.
  • Components make for better architecture. A classical OOP design mistake is to have Player and Enemy classes, then realize both need to be able to shoot arrows and equip armor. So you move the code to the root GameObject class, making the code available to all subclasses. With components you simply have an equipment and a shooting component add to those objects that need it.
  • The great benefit of component-based design is that you start developing individual aspects separately from other things, so they can be reused and added as needed. You'll almost naturally write better code because you approach things with a different mindset.
  • And from my own experience, once you modularize a game into components you get far fewer bugs and they're easier to solve because you don't have to look at or consider other component's code - unless used by a component but even then when one component triggers another you have a clear boundary, ie is the passed value still correct when the other component takes over? If not, the bug must be in the first component.

This is a good introduction on component-based design. The hybrid approach is certainly the way to go. Here are more resources on component based design but I strongly advice against straying from the path and looking into FRP as the "accepted answer's author" suggests - FRP is an interesting concept but has no real world application (yet) in game development.

这篇关于SpriteKit在哪里可以加载数千个精灵的纹理地图集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆