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

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

问题描述

在我的游戏中,我有数以千计的平铺"节点组成游戏地图(想想 simcity),我想知道为每个节点设置纹理和动画的帧速率/内存效率最高的路线是什么?有一些独特的瓷砖类型",每个都有自己的纹理图集/动画,因此确保尽可能重复使用纹理是关键.

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.

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

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?)

或者,每个图块类型都是某个子类.每个 SKSpriteNode 瓦片处理自己的精灵图集加载会更好吗?[tileInstance texturise];(sprite kit 是如何处理这个问题的?这种方法是否会导致相同的纹理图集被加载到内存中,以用于某个瓦片类型的每个实例?)

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.

推荐答案

内存 首先:不会有任何明显的差异.您必须加载 tile 的纹理,纹理将至少占 Map+Tiles 内存的 99%,仅此而已.

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.

帧率/批处理:这都是关于正确批处理的.Sprite Kit 通过按添加到 children 数组的顺序渲染节点的子节点来处理它们的批处理.只要下一个子节点使用与前一个相同的纹理,它们都会被批处理到一个绘制调用中.可能你能做的最糟糕的事情就是添加一个精灵、一个标签、一个精灵、一个标签等等.您需要尽可能多地使用相同的纹理以连续的顺序添加精灵.

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 使用情况:这是您最能获胜的地方.通常,开发人员会尝试对他们的地图集进行分类,这是错误的做法.与其为每个图块(及其动画)创建一个图集,不如创建尽可能少的纹理图集,每个图集包含尽可能多的图块.在所有 iOS 7 设备上,纹理图集可以是 2048x2048,除了 iPhone 4 和 iPad 1 之外,所有其他设备都可以使用最高 4096x4096 像素的纹理.

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.

至于子类化图块,我强烈支持避免子类化节点类.特别是如果对它们进行子类化的主要原因只是改变它们正在使用/动画的纹理.sprite 已经是纹理的容器,因此您也可以更改 sprite 纹理并从外部对其进行动画处理.

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.

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

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"];

然后,此控制器对象执行您的图块所需的任何自定义代码.它可以为瓷砖和其他任何东西设置动画.唯一重要的一点是将对拥有节点(此处为 sprite)的引用设为弱引用:

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.

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

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

  • 如果设计得当,可以与任何或多个节点一起使用.如果有一天您想要一个标签、效果、形状节点图块怎么办?
  • 您不需要为每个图块创建一个子类.一些图块可能是简单的静态精灵.因此,请使用简单的静态 SKSpriteNode.
  • 它允许您根据需要启动/停止或添加/删除各个方面.即使是在您最初不希望拥有或需要某个方面的瓷砖上.
  • 组件允许您构建您将经常需要的功能库,甚至可能在其他项目中.
  • 组件有助于构建更好的架构.一个经典的 OOP 设计错误是拥有 Player 和 Enemy 类,然后意识到两者都需要能够射箭和装备盔甲.因此,您将代码移动到根 GameObject 类,使代码可用于所有子类.使用组件,您只需将设备和射击组件添加到需要它的对象中.
  • 基于组件的设计的最大好处是,您可以将各个方面与其他方面分开开发,因此可以根据需要重复使用和添加它们.您几乎可以自然而然地编写出更好的代码,因为您以不同的心态处理事情.
  • 根据我自己的经验,一旦你将游戏模块化成组件,你会发现更少的错误,而且它们更容易解决,因为你不必查看或考虑其他组件的代码 - 除非被组件使用,但即使那么当一个组件触发另一个组件时,您有一个明确的边界,即当另一个组件接管时传递的值是否仍然正确?如果不是,则错误必须在第一个组件中.
  • 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.

这是一个对基于组件的设计的很好的介绍.混合方法当然是要走的路.以下是更多关于基于组件的设计的资源,但我强烈建议不要偏离正轨和正如公认答案的作者"所建议的那样,研究 FRP - FRP 是一个有趣的概念,但在游戏开发中还没有实际应用.

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天全站免登陆