雨雪霏霏 – iPhone博客

24 Jun, 2010

雨雪霏霏 – iPhone博客 正式推出移动版!

Posted by: 雨雪霏霏 In: iPhone

请大家在手机上看雨雪霏霏的iPhone博客,效果很不一样,很赞哦!标题、日期、评论数目、分类,均一目了然。点进每一篇文章后,文章内容格式均很棒。在此感谢WPTouch,是它使得这一切成为可能。

201006241644.jpg

22 Jun, 2010

NSLog:既是天使也是魔鬼

Posted by: 雨雪霏霏 In: iPhone

相信所有的iPhone开发者都曾经或者将会用到NSLog这个函数。NSLog的强大之处在于,它能在代码运行过程中显示变量值以及程序实际走向,能帮助我们发现大量错误和潜在风险。我们希望尽可能多的用到NSLog,希望它能无处不在,使得错误无法存在——简单的说NSLog是一个天使。

比如下面这段代码就利用NSLog,在程序运行时判断设备种类并显示出来

NSString *deviceType = [UIDevice currentDevice].model;
NSLog(@”device type: %@.”, deviceType);

使用NSLog的一个风险是:它的运行会占用时间和设备资源。当我们用Simulator时,NSLog的资源占用并不引人注意,风险也不会显示出来。但是如果你写的是一个即时战略游戏,而你在每一个action中都加入了NSLog——那么NSLog将成为一个魔鬼。灾难的具体表现常常是:你在Simulator中运行游戏畅通无阻,但到了真机上,会发现很“卡”,不论是拖动一个单位还是缩放一个场景,FPS也降到了各位数。

简单而粗暴的解决方案是:在一个游戏release前,将所有的NSLog注释掉。简单有效,但副作用是:下次你要调试时,又得将NSLog一个个取消注释。

我找到了一个最为有效的解决方案:你以release模式编译的程序不会用NSLog输出,而你以debug模式编译的程序将执行NSLog的全部功能。

#ifndef __OPTIMIZE__
# define NSLog(…) NSLog(__VA_ARGS__)
#else
# define NSLog(…) {}
#endif

代码来源

这个代码的魔术在于:release模式通常会定义 __OPTIMIZE__,当然debug模式不会。将这段代码放在你的头文件当中,你就可以放心的使用NSLog了!

加州时间2010年6月21日上午10:15,iOS 4正式版可以下载了。我正在通过iTunes升级我的iPhone 3G,很是期待!大家也可以连接上你们的iPhone开始Multi-task之旅了!

iOS 4 is ready for downloading at 10:15AM PDT, Jun 21, 2010!

18 Jun, 2010

Apple发布iPhone销售查询软件:iTunes Connect Mobile

Posted by: 雨雪霏霏 In: iPhone

大家可以用iPhone下载使用。可以查看每一个软件、每一个地区长达26周的销售记录,有数据和图标显示,比较方便。之前也有其它第三方销售查询软件,但不放心把帐号密码交给第三方的,Apple官方的软件是一个很好的选择。

10 May, 2010

iPhone游戏该如何保存

Posted by: 雨雪霏霏 In: iPhone

对于任何做iPhone游戏的开发者,如何保存游戏是一个必需解决的问题。这里转一篇非常赞的文章,里面简单明了地介绍了解决办法。我懒得翻译了,如果你更懒以至于不愿意读英文说明的话,可以直接看代码,挺清晰的。

原文链接:http://deadpanic.com/howtosave



How to save your game (or any object) on the iPhone

Warning to some, bonus to others: this post is less markety and more code-heavy. It also reveals some info about the architecture of “Dead Panic.”

Newcomers to iPhoneDevSDK often ask how to convert a string to bytes for writing to a file and other questions about data persistence that belie their real question: how do I save an object and restore it at a later time? NSUserDefaults is fine for storing some strings or bools, but what about an entire custom object? This is called “archiving” and the Apple docs are here.

In short, as long as an object conforms to the NSCoding protocol you can save it to a file just by calling archiveRootObject:toFile:. Most built-in objects already conform to the protocol; you can save an entire NSArray of NSStrings, NSNumbers, and NSDictiories with that one line of code. If you want to save your own custom objects, though, you must make them conform to the NSCoder protocol by adding two methods – encodeWithCoder and initWithCoder.

How to save an object

In “Dead Panic” I instantly save the game whenever they press the home button, and restore their saved game whenever the game is launched again. To do that I need to save some information about which map they’re on, the player’s progress, and the player and enemy positions and health.

Here is my code for saving the map to a coder:

//Map.m

//encode the map data

- (void) encodeWithCoder: (NSCoder *)coder

{


//code the level name

[coder encodeObject: currentLevelName forKey:@"currentLevelName" ];

// code the length of the event list (progress through the level)

NSNumber *listCount = [NSNumber numberWithInt: [eventList count]];

[coder encodeObject: listCount forKey:@"eventList.count" ];

// code the list of players

[coder encodeObject: charList forKey:@"charList" ];

  

}

Notice I didn’t save anything about the structure of the map or the what it looks like – that’s all static data I can get elsewhere. When I load this object I’ll know the level name, and I can get all of the static data from there.

What about the positions of all of the players? Well, you can see that I’m encoding “charList” above, which is an NSArray of the players. That means encodeWithCoder will be called for each item in that array, so I leave it up to the player class to save the important data for each player. Here’s the code for saving a player:

//Player.m

//encode a player or monster character

- (void) encodeWithCoder: (NSCoder *)coder

{

//save type number

[coder encodeObject: [NSNumber numberWithInt:type] forKey:@”type” ];


//save x and y position

[coder encodeObject:

[NSNumber numberWithFloat:position.x] forKey:@”position.x” ];

[coder encodeObject:

[NSNumber numberWithFloat:position.y] forKey:@”position.y” ];


//save health

[coder encodeObject: [NSNumber numberWithInt: health] forKey:@”health” ];


}

You can see that there are many things I save, but there’s more data that I don’t save. I don’t save the maximum health of a character, or weapon range, or firing rate, or information about how he is drawn – that info is already in the default constructor, and there is no need to save it redundantly. Avoiding redundancy helps speed up the save process, and it should reduce bugs – a player loaded from a coder should behave the same as one created from scratch.

How to load an object

Of course we need to load the objects too; here is the code for that

//Map.m

//init a map from a coder

- (id) initWithCoder: (NSCoder *) coder

{

[self init];

  

// load the level name

self.currentLevelName = [coder decodeObjectForKey:@"currentLevelName"];

  

// load the current event number

int eventsRemaining = [[coder decodeObjectForKey:@"eventList.count"] intValue];

  

// load level based on level name

[self loadLevel: currentLevelName];


//skip events until we get to the current event number

int length = [eventList count];

NSRange deletionRange = NSMakeRange(0, length-eventsRemaining);

[eventList removeObjectsInRange:deletionRange];

  

//get the list of characters

NSArray *tempCharList = [coder decodeObjectForKey:@"charList"];

  

//add chars to map, set team counts

for (id newChar in tempCharList)

[self addChar: newChar];

  

return self;

}

There are some tricks to look at there. I call the default init for the class, then I load a level based on the current level name. This is the same loadLevel method that I use when starting a normal game – I try to keep the custom code for loaded games to a minimum. After I load the level I remove items from the event stack until it matches the length of the saved stack. I could have saved the event stack instead, but again, I’m trying not to duplicate any data.

I also restore the list of characters, but then I add them one at a time using another method of this object – that’s because I have some custom code that must be run for every object on the map. Rather than duplicate that code, I call the method the same way it would be called in regular gameplay.

//Player.m

//init a player or monster from a coder

- (id) initWithCoder: (NSCoder *) coder

{

type = [[coder decodeObjectForKey:@"type" ] intValue]; //load type number

  

//init based on type number

[self initWithType: type];


// load x and y position

position.x = [[coder decodeObjectForKey:@"position.x" ] floatValue];

position.y = [[coder decodeObjectForKey:@"position.y" ] floatValue];

  

health = [[coder decodeObjectForKey:@"health" ] intValue];


return self;

}

The only trick here is that I find out what type of player I’m dealing with first, and then init the correct type. Other than that both snippets follow the same pattern – init the object with the regular initializer (the same one I would use when starting a new game) and then apply the information from the saved game.

The final part you can’t see is the game information that I discard entirely – like any particles or damage decals on the screen, or currently playing sound effects. These are all short-term effects that don’t affect the gameplay, and the player won’t miss them when the game reloads. Any ongoing effects – like flames that can damage characters or poisonous clouds to be avoided- would have to be saved, though. You have to decide what is just a visual effect, and what is part of the simulation.

Starting the party

In case you’ve forgotten how all these methods get called, we kick it all off with these lines:

//load the map from disk; its array of players gets loaded too.

//This causes “intiWithCoder” to be called

myMap = [NSKeyedUnarchiver unarchiveObjectWithFile: filePath];


//take some action if myMap == nil (will happen if save file does not exist)


//save the map to disk; it contains an array of players, so they get saved too.

//This caused “encodeWithCoder” to be called

[NSKeyedArchiver archiveRootObject: myMap toFile:filePath];

That’s all there is to it – make your custom objects conform to NSCoder by adding the two methods, try to save only the data that is necessary, and try to use existing initializers and methods wherever possible. Then kick it all off with unarchiveObjectWithFile or archiveRootObject.

PS – be sure to add <NSCoding> to the list of protocols for your object in the .h file to eliminate any warnings.

About

About Google, about Apple, about phones and browsers, about science and technologies.

Subscribe
Page 2 of 912345...Last »