zoukankan      html  css  js  c++  java
  • Cocos2d开发系列(五)

     

    Learn IPhone and iPad Cocos2d Game Delevopment》第6章(原文中部分无关紧要的内容没有进行翻译)。

    一、 CCSpriteBatchNode

    在屏幕上贴图时,图形硬件需要经过准备、渲染、清除等步骤。每次贴图都会重复这个过程。如果图形硬件能事先知道有一组拥有相同纹理的Sprite需要渲染,则这个过程会被简化。比如,一组Sprite的准备和清除动作总共只需要执行一次。

    下图的例子使用了CCSpriteBacthNode。屏幕上同时有几百颗子弹飞过。如果一次只渲染一颗,那么帧率马上降到85%。使用CCSpriteBatchNode,可以避免这种情况:

     

    通常我们这样创建一个CCSprite:

    CCSprite* sprite=[CCSprite spriteWithFile:@”bullet.png”];

    [self addChild:sprite];

    而使用CCSpriteBatchNode 则需要修改为:

    CCSpriteBatchNode* batch=[CCSpriteBatchNode batchNodeWithFile:@”bullet.png”];

    [self addChild:batch];

    for(int i=0;i<100;i++){

    CCSprite* sprite=[CCSprite spriteWithFile:@”bullet.png”];

    [batch addChild:bullet];

    }

    注意,CCSpriteBatchNode需要一个图片文件名作为参数,哪怕它根本用不着这个图片(进行显示)。可以把它看做是一个Layer,你可以用它来加入一些CCSprite节点。由于它使用了一个图片文件作为构造参数,所以在后面加入的CCSprite中必须使用相同的文件作为构造参数,否则会导致如下错误:

    SpriteBatches[13879:207] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'CCSprite is not using the same texture id'

    当采用相同纹理的CCSpite越多,则采用CCSpriteBatchNode的好处越明显。

    但这有一个限制,所有的CCSprite节点都会位于同一个Z坐标(深度)上。如果子弹是“击穿”敌人并向后飞,你得使用两个Z轴不同的CCSpriteBatchNode。

    另外一个限制是,CCSpriteBatchNode和加入其中的CCSprite必须使用相同的贴图。这一点在使用Texture Atlas时尤其显得重要。一个Texture Atlas可以画多个不同的图片,并且这些图片使用同一个CCSpriteBatchNode,以提高渲染速度。

    Z轴的问题可以通过指定CCSpriteBatchNode中单个CCSprite的Z值来解决。如果你所有的图片都放到了一个Texture Atlas(纹理集),则你完全可以只使用一个CCSpriteBatchNode。

    CCSpriteBatchNode看成一个简单的CCLayer,它只接受使用相同图片的CCSprite,这样,你就知道怎么用它了。

    在下面代码中,隐藏有一个致命的陷阱:

    (id)init{

        If ((self = [super initWithFile:@"ship.png"])) {

    [self scheduleUpdate];

        }

        return self;

    }

    由于-(id)init方法是默认的初始化方法,它会被其他初始化方法比如initWithFile调用。 在-(id)init方法中调用了[super initWithFile…]方法,[super initWithFile…]会调用[super init], 该类覆盖了-(id)init方法,于是又会调用-(id)init方法,无限循环。

    解决办法是修改方法名,比如修改为-(id)initWithShipImage。

    这个教训告诉我们,在默认初始化方法-(id)init中,除了[super init]之外,永远不要调用其他东西(其他的初始化方法)。如果你必须在初始化方法中调用[super initWith…]方法,你应当把方法名命名为initWith…。

    二、示例代码

    1、ship类

    #import <Foundation/Foundation.h>

    #import "cocos2d.h"

     

    @interface Ship : CCSprite

    {

    }

     

    +( id ) ship;

     

    @end

    #import "Ship.h"

    #import "Bullet.h"

    #import "GameScene.h"

     

    @interface Ship (PrivateMethods)

    -( id ) initWithShipImage;

    @end

     

     

    @implementation Ship

     

    +( id ) ship

    {

    return [[[ self alloc ] initWithShipImage ] autorelease ];

    }

     

    -( id ) initWithShipImage

    {

    if (( self = [ super initWithFile : @"ship.png" ]))

    {

    [ self scheduleUpdate ];

    }

    return self ;

    }

     

    -( void ) dealloc

    {

    [ super dealloc ];

    }

     

    -( void ) update:( ccTime )delta

    {

    [[ GameScene sharedGameScene ] shootBulletFromShip : self ];

    }

     

    @end

     

    ship类很简单,除了update方法。该方法调用了GameScene的shootBulletFromShip方法外([GameScene shareGameScene]实际上只是获取GameScene 的单实例)。

    2、GameScene类

    #import <Foundation/Foundation.h>

    #import "cocos2d.h"

     

    #import "Ship.h"

     

    typedef enum

    {

    GameSceneNodeTagBullet = 1 ,

    GameSceneNodeTagBulletSpriteBatch ,

    } GameSceneNodeTags;

     

    @interface GameScene : CCLayer

    {

    int nextInactiveBullet ;

    }

     

    +( id ) scene;

    +( GameScene *) sharedGameScene;

     

    -( void ) shootBulletFromShip:( Ship *)ship;

     

    @property ( readonly ) CCSpriteBatchNode* bulletSpriteBatch;

     

    @end

    #import "GameScene.h"

    #import "Ship.h"

    #import "Bullet.h"

     

    @interface GameScene (PrivateMethods)

    -( void ) countBullets:( ccTime )delta;

    @end

     

    @implementation GameScene

     

    static GameScene* instanceOfGameScene;

    +( GameScene *) sharedGameScene

    {

    NSAssert ( instanceOfGameScene != nil , @"GameScene instance not yet initialized!" );

    return instanceOfGameScene ;

    }

     

    +( id ) scene

    {

    CCScene *scene = [ CCScene node ];

    GameScene *layer = [ GameScene node ];

    [scene addChild : layer];

    return scene;

    }

     

    -( id ) init

    {

    if (( self = [ super init ]))

    {

    instanceOfGameScene = self ;

    CGSize screenSize = [[ CCDirector sharedDirector ] winSize ];

    CCColorLayer * colorLayer = [ CCColorLayer layerWithColor : ccc4 ( 255 , 255 , 255 , 255 )];

    [ self addChild :colorLayer z :- 1 ];

    CCSprite * background = [ CCSprite spriteWithFile : @"background.png" ];

    background. position = CGPointMake (screenSize. width / 2 , screenSize. height / 2 );

    [ self addChild :background];

    Ship * ship = [ Ship ship ];

    ship. position = CGPointMake (ship. texture . contentSize . width / 2 , screenSize. height / 2 );

    [ self addChild :ship];

    CCSpriteBatchNode * batch = [ CCSpriteBatchNode batchNodeWithFile : @"bullet.png" ];

    [ self addChild :batch z : 1 tag : GameSceneNodeTagBulletSpriteBatch ];

     

    for ( int i = 0 ; i < 400 ; i++)

    {

    Bullet * bullet = [ Bullet bullet ];

    bullet. visible = NO ;

    [batch addChild :bullet];

    }

    [ self schedule : @selector ( countBullets :) interval : 3 ];

    }

    return self ;

    }

     

    -( void ) dealloc

    {

    instanceOfGameScene = nil ;

    // don't forget to call "super dealloc"

    [ super dealloc ];

    }

     

    -( void ) countBullets:( ccTime )delta

    {

    CCLOG ( @"Number of active Bullets: %i" , [ self . bulletSpriteBatch . children count ]);

    }

     

    -( CCSpriteBatchNode *) bulletSpriteBatch

    {

    CCNode * node = [ self getChildByTag : GameSceneNodeTagBulletSpriteBatch ];

    NSAssert ([node isKindOfClass :[ CCSpriteBatchNode class ]], @"not a CCSpriteBatchNode" );

    return ( CCSpriteBatchNode *)node;

    }

     

    -( void ) shootBulletFromShip:( Ship *)ship

    {

    CCArray * bullets = [ self . bulletSpriteBatch children ];

    CCNode * node = [bullets objectAtIndex : nextInactiveBullet ];

    NSAssert ([node isKindOfClass :[ Bullet class ]], @"not a bullet!" );

    Bullet * bullet = ( Bullet *)node;

    [bullet shootBulletFromShip :ship];

    nextInactiveBullet ++;

    if ( nextInactiveBullet >= [bullets count ])

    {

    nextInactiveBullet = 0 ;

    }

    }

     

    @end

    现在你应该看到了,在init方法中使用CCSpriteBatchNode加入了400颗子弹(被设置为不可见了)。然后在接下来的shootBulletFromShip方法(在ship的update方法中调用)中,依次调用每一颗子弹的shootBulletFromShip方法。

    3、Bullet类

    #import <Foundation/Foundation.h>

    #import "cocos2d.h"

     

    #import "Ship.h"

     

    @interface Bullet : CCSprite

    {

    CGPoint velocity ;

    float outsideScreen ;

    }

     

    @property ( readwrite , nonatomic ) CGPoint velocity;

     

    +( id ) bullet;

     

    -( void ) shootBulletFromShip:( Ship *)ship;

     

    @end

    #import "Bullet.h"

     

     

    @interface Bullet (PrivateMethods)

    -( id ) initWithBulletImage;

    @end

     

     

    @implementation Bullet

     

    @synthesize velocity;

     

    +( id ) bullet

    {

    return [[[ self alloc ] initWithBulletImage ] autorelease ];

    }

     

    -( id ) initWithBulletImage

    {

    if (( self = [ super initWithFile : @"bullet.png" ]))

    {

    }

    return self ;

    }

     

    -( void ) dealloc

    {

    [ super dealloc ];

    }

     

    // Re-Uses the bullet

    -( void ) shootBulletFromShip:( Ship *)ship

    {

    float spread = ( CCRANDOM_0_1 () - 0.5f ) * 0.5f ;

    velocity = CGPointMake ( 1 , spread);

    outsideScreen = [[ CCDirector sharedDirector ] winSize ]. width ;

    self . position = CGPointMake (ship. position . x + ship. contentSize . width * 0.5f , ship. position . y );

    self . visible = YES ;

    [ self scheduleUpdate ];

    }

     

    -( void ) update:( ccTime )delta

    {

    self . position = ccpAdd ( self . position , velocity );

    if ( self . position . x > outsideScreen )

    {

    self . visible = NO ;

    [ self unscheduleAllSelectors ];

    }

    }

    @end

    shootBulletFromShip方法实现了子弹的射击。Spread变量计算了一个扩散值,使从飞船中射出的子弹有1/2的机率会向上/下两边扩散。Velocity是一个每帧移动的位置偏移量。然后设置子弹的初始位置位于飞船右边。在把子弹可视状态设置为显示后,调度运行update方法(每帧调用一次)。

    update方法中,让子弹移动velocity的偏移量。这种方式,比CCMoveXX方法效率更高一些。而且这里用了一个技巧,当子弹飞出屏幕后,我们并没有立即将Bullet对象清除(为了节省资源),而是子弹设置为不可视缓存起来,方便再次使用以提高程序性能。出于这个原因,我们在GameScene类中设计了一个nextInactiveBullet变量,以此来记录已经使用掉(射出去)的子弹(设置为可视的子弹)。等所有子弹都射出去以后,nextInactiveBullet重置为0。

    三、增加角色动画

    以下代码为ship对象增加角色动画。Ship对象的角色动画是5张连续的帧图像,以表现飞船尾部不断喷射并变化的火焰。

    -( id ) initWithShipImage

    {

    if (( self = [ super initWithFile : @"ship.png" ]))

    {

    // 5张图片装入动画帧数组

    NSMutableArray * frames = [ NSMutableArray arrayWithCapacity : 5 ];

    for ( int i = 0 ; i < 5 ; i++)

    {

    NSString * file = [ NSString stringWithFormat : @"ship-anim%i.png" , i];

    // 使用贴图缓存构造2D贴图

    CCTexture2D * texture = [[ CCTextureCache sharedTextureCache ] addImage :file];

    CGSize texSize = texture. contentSize ;

    CGRect texRect = CGRectMake ( 0 , 0 , texSize. width , texSize. height );

    // 用2D贴图构造动画帧

    CCSpriteFrame * frame = [ CCSpriteFrame frameWithTexture :texture rect :texRect offset : CGPointZero ];

    // 把动画帧放入数组

    [frames addObject :frame];

    }

    // 用动画帧数组构造动画对象,帧率:0.08秒/帧,标识名:move

    CCAnimation * anim = [ CCAnimation animationWithName : @"move" delay : 0.08f frames :frames];

    // 如果你把anim存储到CCSprite,则可以通过名称move来访问CCAnimation

    //[self addAnimation:anim];

    // 构造Action:无限重复

    CCAnimate * animate = [ CCAnimate actionWithAnimation :anim];

    CCRepeatForever * repeat = [ CCRepeatForever actionWithAction :animate];

    [ self runAction :repeat];

    [ self scheduleUpdate ];

    }

    return self ;

    }

    为求简便,上面的代码我们也可以封装为一个新的类别Category。

    1、类别Animation Helper

    利用OC中的Category,我们可以扩展CCAnimation类。Category提供了一种不需要修改类的源代码即可为类增加新方法的途径(有点象AOP?),但它不能增加新的成员变量。下面的代码为CCAnimation增加了一个Category,名为Helper(新建Class,名为CCAnimationHelper.h):

    #import <Foundation/Foundation.h>

    #import "cocos2d.h"

     

    @interface CCAnimation (Helper)

     

    +( CCAnimation *) animationWithFile:( NSString *) name frameCount:( int )frameCount delay:( float ) delay ;

    +( CCAnimation *) animationWithFrame:( NSString *)frame frameCount:( int )frameCount delay:( float ) delay ;

     

    @end

    #import "CCAnimationHelper.h"

     

    @implementation CCAnimation (Helper)

     

    // 通过图片文件名创建CCAnimation对象 .

    +( CCAnimation *) animationWithFile:( NSString *) name frameCount:( int )frameCount delay:( float ) delay

    {

    // 把前面的代码移到这里来了

    NSMutableArray * frames = [ NSMutableArray arrayWithCapacity :frameCount];

    for ( int i = 0 ; i < frameCount; i++)

    {

    NSString * file = [ NSString stringWithFormat : @"%@%i.png" , name , i];

    CCTexture2D * texture = [[ CCTextureCache sharedTextureCache ] addImage :file];

     

    CGSize texSize = texture. contentSize ;

    CGRect texRect = CGRectMake ( 0 , 0 , texSize. width , texSize. height );

    CCSpriteFrame * frame = [ CCSpriteFrame frameWithTexture :texture rect :texRect offset : CGPointZero ];

    [ frames addObject :frame];

    }

    return [ CCAnimation animationWithName : name delay : delay frames : frames ];

    }

     

    // 通过 sprite frames 创建CCAnimation .

    +( CCAnimation *) animationWithFrame:( NSString *)frame frameCount:( int )frameCount delay:( float ) delay

    {

    // 未实现

    return nil ;

    }

    @end

    现住,在Ship类的初始化方法里,可以通过CCAnimation的类别Helper这样简单地创建动画对象了:

    -( id ) initWithShipImage

    {

    if (( self = [ super initWithFile : @"ship.png" ]))

    {

    // 使用类别Helper来创建动画对象

    CCAnimation * anim = [ CCAnimation animationWithFile : @"ship-anim" frameCount : 5 delay : 0.08f ];

    // 创建Action:无限循环播放动画

    CCAnimate * animate = [ CCAnimate actionWithAnimation :anim];

    CCRepeatForever * repeat = [ CCRepeatForever actionWithAction :animate];

    [ self runAction :repeat];

    [ self scheduleUpdate ];

    }

    return self ;

    }

    四、 Texture Atlas 贴图集(或译作纹理集)

    1、定义

    贴图集Texture Atlas仅仅是一张大的贴图。通过使用CCSpriteBatchNode,你可以一次性渲染所有的图片。使用Texture Atlas不但节约了内存也提升了性能。

    贴图的大小(宽和高)总是2的n次方——例如1024*128或256*512。由于这个规则,贴图尺寸有时候是大于图片的实际尺寸的。例如,图片大小140*600,当加载到内存时,贴图尺寸是256*1024。这显然是一种内存浪费,尤其是你有几个这样的单独的Texture时。

    因此有了Texture Atlas的概念。它是一张包含了多个图片的图片,并且它的尺寸已经是对齐的。所谓对齐,即是根据前面提到的那个规则,指它的长和宽都已经是2的n次方。贴图集每一个角色帧(sprite frame)都定义为贴图集中的一部分(一个矩形区域)。这些角色帧的CGrect则定义在单独的一个.plist文件里。这样cocos2d就可以从一张大的贴图集中单独渲染某个指定的角色帧。

    2、Zwoptex

    Zwoptex是一个2D贴图工具,付费版需要$24.95。有一个7天的试用版,下载地址http://zwoptexapp.com 。但Zwoptex提供了一个flash版,没有时间限制:http://zwoptexapp.com/flashwersion ,也基本够用(仅仅有一些限制,比如2048*2048贴图限制,角色旋转等)。

    如果你不想试用Zwoptex,那么有一个可以替换的工具是TexturePacker:

    http://texturepacker.com/

    这里以Zwoptex 0.3b7版本为例(这个是免费版)。打开Zwoptex,默认是一个叫做Untitled的空白画布。选择菜单:Sprite Sheet ——>Import Sprites,会弹出文件选择对话框,选择你需要的角色帧图片文件,点击import,于是所有的图片会导入到Zwoptex。

    选择菜单:Sprite Sheet——>Settings,会弹出布局窗口:

     

    你可以更改设置,包括画布大小,排序规则、行间距、列间距等,目的是用最小的贴图集容纳全部所需的图片。然后点击save去应用。

    注意,除非单独为3GS、iPad和iPhone4开发,否则不要使用2048*2048的画布尺寸,因为老的型号最大只支持1024*1024。

    改变画布大小时要当心,因为有时候图片会叠在一起——由于空间不足。

    建议不要手动更改图片(如移动、旋转),因为这个版本并不支持,它是自动布局的。

    Zwoptex会自动截掉图片中透明边沿,所以本来一样大小的图片在Zwoptex中会显得大小不一。

     

    不用担心,cocos2d会自动计算这些误差并正确显示(不用担心,这些数据都记载在.plist里)。

    点击File——>save菜单,编辑结果保存为.zss文件格式(Zwoptex格式)。

    点击Sprite Sheet——>Export——>Texture,编辑结果保存为.png格式。

    点击Sprite Sheet——>Export——>Cordinates,编辑结果保存为.plist格式。

    后两者,正是cocos2d所需要的。

    3、Cocos2d中使用贴图集

    首先,将Zwoptex生成的.png和.plist文件加入到项目的Resource组中。然后在代码中使用贴图集:

    -( id ) initWithShipImage

    {

    // CCSpriteFrameCache加载贴图集,用.plist文件而不是.png文件做参数

    CCSpriteFrameCache * frameCache = [ CCSpriteFrameCache sharedSpriteFrameCache ];

    [frameCache addSpriteFramesWithFile : @"ship-and-bullet.plist" ];

     

    // 从贴图集中加载名为ship.png的sprite,注意ship.png是.plist中定义的key,而不是真正意义的文件名

    if (( self = [ super initWithSpriteFrameName : @"ship.png" ]))

    {

    // 从贴图集中加载sprite帧,注意用.plist中的key值做参数而非文件名

    NSMutableArray* frames = [NSMutableArray arrayWithCapacity : 5 ];

    for ( int i = 0 ; i < 5 ; i ++)

    {

    NSString* file = [NSString stringWithFormat: @"ship-anim%i.png" , i ];

    CCSpriteFrame * frame = [frameCache spriteFrameByName :file];

    [frames addObject :frame];

    }

    CCAnimation * anim = [ CCAnimation animationWithName : @"move" delay : 0.08f frames :frames];

    CCAnimate * animate = [ CCAnimate actionWithAnimation :anim];

    CCRepeatForever * repeat = [ CCRepeatForever actionWithAction :animate];

    [ self runAction :repeat];

    [ self scheduleUpdate ];

    }

    return self ;

    }

    [CCSpriteFrameCache sharedSpriteFrameCache]是一个单例对象,其 addSpriteFramesWithFile 方法用于加载贴图集(需要以.plist文件名作为参数)。对于大文件贴图集(超过512*512),加载过程可能会花费数秒,应当在游戏开始前就加载。

    CCSprite的initWithSpriteFrameName方法可以从贴图集中获取贴图,并设置Sprite的显示图片,但它需要以贴图集(.plist)中的帧名(framename,实际上是<frames>中的一个<key>)为参数。

    当然,如果要从贴图集中得到一个帧,可以用CCSpriteFrameCache的spriteFrameByName方法。这同样需要用贴图集中的帧名为参数。

    如果你加载了多个贴图集,但只要名为ship.png的帧只有1个,那么cocos2d就可以找到正确贴图的。

     

    其他代码没有任何改变。但出现了一个奇怪的现象:飞船的位置莫名其妙地向屏幕中心靠近了一点,尽管不是很明显。这个问题很容易解决,之前Ship的初始化代码是这样的:

    Ship * ship = [ Ship ship ];

    ship. position = CGPointMake (ship. texture . contentSize . width / 2 , screenSize. height / 2 );

    [ self addChild :ship];

     

    这个地方需要改变:

    ship. position = CGPointMake (ship. contentSize . width / 2 , screenSize. height / 2 );

    问题解决了。导致这个现象的原因是,ship对象的texture的contentSize要比ship对象的contentSize大(Ship的Texture现在用的是贴图集——具体说就是 ship-and-bullet.png 这张图,尺寸为256*256,而原来的ship.png才128*64)。

    4、修改CCAnimation类别Helper

    现在是实现类别Helper中的animationWithFrame方法的时候了:

    +( CCAnimation *) animationWithFrame:(NSString*)frame frameCount:( int )frameCount delay:( float ) delay

    {

    // 构造一个frame数组

    NSMutableArray* frames = [NSMutableArray arrayWithCapacity :frameCount];

    // 通过CCSpriteFrameCache从贴图集中加载frame,并将frame加到数组中

    for ( int i = 0 ; i < frameCount; i ++)

    {

    NSString* file = [NSString stringWithFormat: @"%@%i.png" , frame, i ];

    CCSpriteFrameCache * frameCache = [ CCSpriteFrameCache sharedSpriteFrameCache ];

    CCSpriteFrame * frame = [frameCache spriteFrameByName :file];

    [ frames addObject :frame];

    }

    // frame数组构建animation对象并返回

    return [ CCAnimation animationWithName :frame delay : delay frames : frames ];

    }

    现在,可以修改ship类中initWithShipImage方法,把CCAnimation的初始化修改为:

    CCAnimation * anim = [ CCAnimation animationWithFrame : @"ship-anim" frameCount : 5 delay : 0.08f ];

     

    5、弱水三千,只取一瓢饮

    只要你愿意,你可以把所有游戏图片都加到一个贴图集里。用3个1024*1024的贴图集跟用20个更小的贴图集效率更高。

    对于程序员而言,应当把代码“分离”成不同的逻辑组件。于此不同,对于贴图集来说,我们的目标就是尽可能多地把图片放到一个贴图集里,尽可能降低内存空间的浪费。

    用一个贴图集放入玩家图片,用另外的贴图集放怪物A、B、C的图片——这好像更符合逻辑些,但这仅仅有助于你有大量的图片,而且每次你只是有选择地加载一部分图片的时候。

    当你的图片只需要3-4个1024*1024贴图集的时候,你应当把所有图片只放在这些贴图集里进行预加载。这需要12-16MB的内存。程序代码和其他资源如音频则不会占用这么多内存,你可以把这些贴图集都保留在内存,这样哪怕只有128MB RAM的老IOS设备也可以承受。

    如果超过这个内存,就应该采取特别的策略了。比如,可以把游戏图片进行分类,并且只在当前地图中加载必要的贴图集。这可以减少地图加载时的延时。

    因为cocos2d会自动缓存所有图片,需要一种卸载贴图的机制。绝大部分情况下你可以使用cocos2d提供的:

    [[CCSpriteFrameCache sharedSpriteFrameCache]removeUnusedSpriteFrames];

    [[CCTextureCache sharedTextureCache] removeUnusedTextures];

    显然应当在某些贴图不再使用的时候调用这些方法。比如转场景完成后。游戏正在进行时就不行了。注意,仅仅在新场景初始化结束后,前面的场景才会被 deallocated 。意即在一个 Scenen 的初始化方法中你不能调用 removeUnusedxxx 方法——除非你在两个 scene 转换中使用了第 5 章的 LoadingScene 类,这样你要扩展 LoadingScene 使它在加载新场景替时 remove 所有未使用的贴图。

    如果要绝对清除所有内存中的贴图以加载新的贴图,应当使用:

    [CCSpriteFrameCache purgeSharedSpriteFrameCache];

    [CCTextureCache purgeSharedTextureCache];

    6 、美工和艺术

    图片的编辑你可以使用免费软件 Searshore http://seashore.sourceforge.net

    对于动画和位图,你可以使用 Pixen http://opensword.org/Pixen ,这是一个简单易用和强大的动画编辑工具。

    如果你想自己制作音乐音效,可以使用 Mac 自带的 GarageBand 。插上一只麦克风开始记录一些声音,花几分钟编辑并在任何适当的地方使用这些声音。如果不想自己制作声效,也可以从网上搜索一些免费或便宜的音频,例如 www.soundsnap.com 或者类似网站。

    警告:对于使用源自互联网的音频和图片要尤其小心。你可以下载到一些“免费”的资源用于早期的游戏制作。但简单的“免费”并不意味着你可以再分发这些文件,或者用于商业用途,例如在苹果商店中销售。除非获得作者特别的授权或者这些资源附有一个正式的协议。

  • 相关阅读:
    Web开发用到的导航栏固定顶端,页脚固定低端
    Action Filter 与 内置的Filter实现(实例-防盗链)转
    MVC 通过继承HandleErrorAttribute,使用log4net或ELMAH组件记录异常等
    HTML5中custom data-*特性与asp.net mvc 3 表单验证
    jQuery验证控件jquery.validate.js使用说明+中文API (转)
    Android ADB server didn't ACK * failed to start daemon * 简单有效的解决方案 (转)
    Android定制控件-带图像的TextView
    使用Elmah记录日志
    laravel路由与控制器(资源路由restful)
    npm 常用命令
  • 原文地址:https://www.cnblogs.com/encounter/p/2188495.html
Copyright © 2011-2022 走看看