2010年4月30日 星期五

Bordered UILabel

當在遊戲中顯示較細小的文字時,如果使用了「反鋸齒補償(Anti-alias)」 的話,文字可能會變得虛影,不夠實在;要是關閉 Anti-alias 的話,文字又會太過生硬。這時便可能用到「陰影」或「包邊」來突顯文字的立體感。「陰影」是 API 內已有的東西,做起來沒有難度。可是「包邊」卻沒有 API,需要自行製作。然而,Apple 的編程架構實在太妙,我們可以利用 drawRect 來實現。
@interface UIBorderedLabel : UILabel  {
UIColor *borderColor;
CGFloat borderWeight;
}

@property (nonatomic, retain) UIColor *borderColor;
@property (nonatomic, readwrite) CGFloat borderWeight;

@end
@implementation UIBorderedLabel

@synthesize borderColor;
@synthesize borderWeight;

- (id)init {
self = [super init];
if (self == nil) {return self;}

borderColor = [[UIColor blackColor] retain];
borderWeight = 2.0f;

return self;
}

- (void)dealloc {
[borderColor release];
[super dealloc];
}

- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();

// Draw border
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextSetFillColorWithColor(context, borderColor.CGColor);
CGContextSetLineWidth(context, borderWeight);
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
[self.text drawInRect:self.bounds withFont:self.font lineBreakMode:self.lineBreakMode alignment:self.textAlignment];

// Draw text
CGContextSetLineWidth(context, 0.0f);
CGContextSetFillColorWithColor(context, self.textColor.CGColor);
[self.text drawInRect:self.bounds withFont:self.font lineBreakMode:self.lineBreakMode alignment:self.textAlignment];
}

@end
透過 borderColor 來設定「包邊」的顏色,以 borderWeight 來設定「包邊」的厚度。例子如下:
UILabel *label = [[UIOutlineLabel alloc] initWithFrame:CGRectMake(0, 0, 320, 30)];
label.font = [UIFont fontWithName:@"Hiragino Kaku Gothic ProN" size:16.0f];
label.textColor = [UIColor greenColor];
label.borderColor = [UIColor colorWithRed:0.9f green:0.2f blue:0.6f alpha:1.0f];
label.borderWeight = 3.0f;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.text = @"UIBorderedLabel";
[self label];
[label release];

2010年4月29日 星期四

MallocStackLoggingNoCompact

明天是《帝國》推出封測版的日子!辛苦了七個月,終於開始封測!往後還有大量系統需要移植。

為了明天的封測,今日作最後的測試,最新版本竟然在真機上無法執行,彈出一大堆「malloc: unable to create stack log directory」之餘,還在第一個畫面便彈出 EXC_BAD_ACCESS! 我立即冒了一個冷汗。經過 Google 的教育,原來是因為打開了 MallocStackLoggingNoCompact 所導致。只要在 Executables -> Get Info -> Arguments 內取消 MallocStackLoggingNoCompact 的打勾就行。嚇得我!

《帝國》封測大募集

好消息:真正萬人同時在線中文手機網遊,《帝國online》iPhone國際中文版將於本周六(5.1)中午12:00進行封閉測試!

有興趣參與這盛事的玩家和行家,請發電郵至 kin@ckxpress.com 報名,註明終端(iPhone/iPod/iPad、硬件和OS版本)、有否越獄和UDID。(如不介意公開私隱,亦可直接回覆這裏報名,提供以上資料和電郵地址)

名額只限50名,周五中午12:00截止報名!

WWDC10

WWDC10 來了!今次的海報很正!期待 iPhone 4 的公佈!

OpenVanilla

今天嘗試了一個新的免費軟件,名為 OpenVanilla。它是一個輸入法軟件。能以繁體中文的輸入法,以簡體中文字輸出,方便我跟國內同事溝通之用。這是一個很好的軟件!

2010年4月28日 星期三

為 UITextView 加入 placeholder

承接昨天所說,品檢同事的另一個要求就是替 UITextView 也加入 placeholder。這亦是 API 中沒有的東西。今次的處理比改變 UITextField 中 placeholder 顏色來得複雜,所以要用到「繼承 (Subclass)」來處理:
@interface UITextView2 : UITextView  {
NSString *placeholder;
UIColor *placeholderColor;
}

@property (nonatomic, retain) NSString *placeholder;
@property (nonatomic, retain) UIColor *placeholderColor;

@end
@implementation UITextView2

@synthesize placeholder;
@synthesize placeholderColor;

- (id)initWithFrame:(CGRect)rect {
if (self = [super initWithFrame:rect]) {
[self setPlaceholder:@""];
[self setPlaceholderColor:[UIColor lightGrayColor]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}
return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}

- (void)drawRect:(CGRect)rect {
UILabel *label;

if ([[self placeholder] length] > 0) {
label = [[UILabel alloc] initWithFrame:CGRectMake(8, 8, self.bounds.size.width-16, 0)];
label.lineBreakMode = UILineBreakModeWordWrap;
label.numberOfLines = 0;
label.font = self.font;
label.backgroundColor = [UIColor clearColor];
label.textColor = self.placeholderColor;
label.text = self.placeholder;
label.alpha = 0;
label.tag = 999;
[self addSubview:label];
[label sizeToFit];
[self sendSubviewToBack:label];
[label release];
}

if ([[self text] length] == 0 && [[self placeholder] length] > 0) {
[[self viewWithTag:999] setAlpha:1];
}

[super drawRect:rect];
}

- (void)textChanged:(NSNotification *)notification {
if ([[self placeholder] length] == 0) {return;}

if ([[self text] length] == 0) {[[self viewWithTag:999] setAlpha:1];}
else {[[self viewWithTag:999] setAlpha:0];}
}

@end

2010年4月27日 星期二

修改 UITextField 中 placeholder 的顏色

公司的 MMORPG 遊戲已經進入了品檢階段,相關人員提出了很多的要求,其中一樣是把 UITextField 中 placeholder 的顏色改變。這是 Apple API 中沒有的功能。要達到這個目的,可以使用以下方法:
[textField setValue:[UIColor darkGrayColor] forKeyPath:@"_placeholderLabel.textColor"];
由於這個方法有點偏門,不知道 Apple 在審批時能否通過。

2010年4月26日 星期一

檢查剩餘電量

雖然檢查剩餘電量好像沒有多大用途,不過對我來說則會把它變成遊戲的一部份。例如:當電力不足時,玩家角色的效能會降低;而敵人的效能會上升、快沒電時會較易拿到寶物...等。檢查剩餘電量的方法如下:
 UIDevice *device = [UIDevice currentDevice];
device.batteryMonitoringEnabled = YES;

// Add battery related callback
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(batteryChanged:) name:@"UIDeviceBatteryLevelDidChangeNotification" object:device];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(batteryChanged:) name:@"UIDeviceBatteryStateDidChangeNotification" object:device];
- (void)batteryChanged:(NSNotification *)notification  {
UIDevice *device = [UIDevice currentDevice];
batteryLevel = device.batteryLevel;
batteryState = device.batteryState;
}
由此可見,我們可以檢測到 BatteryLevel 及 BatteryState。BatteryLevel 是真正的電量水平,每 5% 為一個單位。BatteryState 則是電源狀態,分為:充電中、沒接駁、滿電。因應這幾種狀態,套用遊戲內便能做出富有新鮮感的設定。

2010年4月24日 星期六

把自製格式的物件儲存到檔案

在開發 iPhone 軟件的過程,很多時都會建立自定義的物件;以動作遊戲為例,習慣上都會角色的 HP, MP, SP, Level...等數據放入一個名為 Player 的類入面。而遊戲內亦會由一個 NSMutableArray 或 NSMutableDictionary 儲存著當刻表演中的 Player(「表演」這個詞是從 Macromedia Director 中借來。她稱畫面為「舞台」。我十分認同,亦覺得十分貼切)。假如遊戲因為來電要立即關閉,最好的方法就是把所有 Player 物件儲存起來。

本來 NSMutableArray 及 NSMutableDictionary 都有 writeToFile: 功能:
NSString *fullPath = [GameViewController getFileWithPath:PLAYER_DATA_FILE];
[playerArray writeToFile:fullPath atomically:NO];
但可惜不支援自定義的物件。不過,我們可以使用 NSKeyedArchiver 及 NSKeyedUnarchiver。方法是為自定義的物件建立:
- (id)initWithCoder:(NSCoder *)decoder;
- (void)encodeWithCoder:(NSCoder *)encoder;
這兩個分別是 initWithCoder: 還原數據及 encodeWithCoder: 儲存數據。示範如下:
- (id)initWithCoder:(NSCoder *)decoder  {
self = [super init];

level = [decoder decodeIntForKey:PLAYER_LEVEL];
hp = [decoder decodeIntForKey:PLAYER_HP];
mp = [decoder decodeIntForKey:PLAYER_MP];

return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder  {
[encoder encodeInt:level forKey:PLAYER_LEVEL];
[encoder encodeInt:hp forKey:PLAYER_HP];
[encoder encodeInt:mp forKey:PLAYER_MP];
}
有了這兩個函數之後,只要調用 archiveRootObject: 來儲存,以 unarchiveObjectWithFile: 來讀取。
BOOL result = [NSKeyedArchiver archiveRootObject:playerDictionary toFile:fullPath];
playerDictionary = [[NSKeyedUnarchiver unarchiveObjectWithFile:fullPath] retain];

2010年4月23日 星期五

UIButton 小秘技

很多時候我都會建立數個 UIButton 並放在畫面;而且很多時候的 UIButton 都是同一種類(例如:「大」、「中」、「小」三個按鈕),大家的茅頭都是指著同一個功能。例如:
- (void)doButtonAction:(id)sender;
那麼在凾數內要如何分辨是由哪個按鈕所觸發?答案就是使用 UIView 的 tag 數值。當建立 UIButton 時,順道給每個 UIButton 一個 tag 編號,這樣便能在 doButtonAction 時分辨出是由哪個按鈕所觸發。
- (void)doButtonAction:(id)sender  {
UIButton *button = (UIButton *)sender;
switch (button.tag) {
case BUTTON_LARGE: break;
case BUTTON_NORMAL: break;
case BUTTON_SMALL: break;
}
}

2010年4月22日 星期四

NSString 去空白化

很多時候都需要 Trim 一條 NSString,方式如下:
newString = [[oldString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] retain];

2010年4月15日 星期四

BlueTooth 接收器

今次在淘寶網買了兩隻無線 PS2 手掣外,還買了一塊藍芽接收器,價值 RMB$88。不過暫時沒有時間進行測試。
◆采用CSR主流蓝牙芯片,蓝牙V2.0协议标准
◆串口模块上底板带有RS232接口和TTL接口,任选一种接口使用,使用3.3至5V电源。串口对用户而言是透明的
◆蓝牙芯片采用向前纠错编码,通信效率更高,自动跳频,抗干扰能力强。
◆蓝牙开发板供电:采用RICOH R1114N-3.3V 超低压降IC
◆工作电压:3.6V to 5.0V (非常适合3.6V供电的锂电池)
◆工作电流:40mA 休眠电流:小于1mA
◆带蓝牙模块工作开关控制脚,H(1)=开模块,L(0)=关模块
◆波特率为2400 To 1382400可用AT指令和SPI下载板设置
◆用于GPS导航系统,水电煤气抄表系统,工业现场采控系统。
◆可以与蓝牙笔记本电脑、电脑加蓝牙适配器、PDA等设备进行无缝连接。
◆蓝牙开发板尺寸:30.8mm x 21.8mm x 1.6mm

2010年4月14日 星期三

Wireless PS2 Controller

之前在 ATmega-128 上完成了 PS2 手掣連線程序,於是我便到處尋找無線的 PS2 手掣。可是在香港找了很地方都沒有,始終 PS2 機乎被淘汰了,所以週邊設備也較難找得到。我在 Yahoo 找到了一款「蜘蛛俠」的版本,價值 HK$98,不過實在太難看而沒有買。上星期終於在淘寶網找到,而且十分便宜,只須 RMB$48。

前天終於到手,可惜在接上 ATmega-128 之後卻無法辨認出來,而在 PS2 上則沒有問題。究竟要如何連接呢?

2010年4月13日 星期二

iPhone 模擬器的「顏文字」輸入法

步驟如下:
1) 打開 Finder 並到 /Users/username/Library/Application\ Support/iPhone\ Simulator/User/Library/Preferences
2) 利用 BBEdit 或 Xcode 打開 com.apple.Preferences.plist
3) 輸入下圖中藍底色的文字及數值

4) 開啟 iPhone 模擬器
5) 選擇「設定」》「一般」》「國際設定」》「鍵盤」》「日文」
6) 打開「表情符號」
7) 完成

2010年4月10日 星期六

UIButton 的其他設定

最近比較常使用到 UIButton,一般的需求已經不能滿足我,所以找到以下特別處理:

更改 UIButton 的字體大小
button.titleLabel.font = [UIFont fontWithName:@"Hiragino Kaku Gothic ProN" size:14.0f];

使 UIButton 支援多行模式
button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;

圖像按鈕
NSString *fullPath = [PacessLib getExistingFileWithPath:BUTTON_SUBMIT];
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, image.size.width, image.size.height);
[button setBackgroundImage:image forState:UIControlStateNormal];
[button addTarget:nil action:@selector(doSubmitButton:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
更新:2010/APR/26
按鈕文字置左
[button setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
更新:2010/APR/28
按鈕文字垂直置底
[button setContentVerticalAlignment:UIControlContentVerticalAlignmentBottom];

2010年4月7日 星期三

OpenGL 轉換成 UIImage 的方法

這是在網上找到的 OpenGL 轉換成 UIImage 的方法。昨天的問題是因為把 buffer2 釋放了,而在這裡是不需要的。
+ (UIImage *)openGL2UIImageX:(int)startX Y:(int)startY width:(int)width height:(int)height  {
NSInteger dataLength = width*height*4;

// Allocate array and read pixels into it.
GLubyte *buffer = (GLubyte *)malloc(dataLength);
glReadPixels(startX, startY, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

// GL renders "upside down" so swap top to bottom into new array.
// there's gotta be a better way, but this works.
GLubyte *buffer2 = (GLubyte *)malloc(dataLength);
for (int y=0; y< height; y++) {
int offsetY1 = (y*width)<<2;
int offsetY2 = ((height-1-y)*width)<<2;

for (int x=0; x<(width<<2); x++) {
buffer2[offsetY2+x] = buffer[offsetY1+x];
}
}
free(buffer);

// Make data provider with data.
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, dataLength, NULL);

// Prepare the ingredients
int bitsPerComponent = 8;
int bitsPerPixel = 32;
int bytesPerRow = 4*width;

CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;

// Make the cgimage
CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

// then make the uiimage from that
UIImage *uiImage = [UIImage imageWithCGImage:imageRef];

free(buffer2);
CGImageRelease(imageRef);
CGDataProviderRelease(provider);

return uiImage;
}

更正:buffer2 是需要釋放的。之前在真機上會死機,原來是因為沒有釋放 imageRef。

2010年4月6日 星期二

OpenGL to UIImage crash on device...

公司的 MMORPG 遊戲的開發進度已經進入尾聲,經過同事們的測試,有很多介面還需要作出改善。很多原本使用 OpenGL 的介面,為了方便開發,需要轉回 UIView 下顯示。本來這是有助加快開發的進程,可是我卻遇上一個兩難的局面。由放棄使用 UIView 的一剎那開始,所有的繪圖部份都是針對 OpenGL 來製作,人物紙娃娃便是其中之一;現在要回到 UIView 下顯示的話,一是把整個紙娃娃部份重寫 UIView 版本,在時間上不夠之餘,亦衍生了他日需要同時維護兩個版本的問題。二是把 OpenGL 所繪製出來的影像轉換為 UIImage,好處是一個紙娃娃版本便能服務 OpenGL 及 UIView。但需要花上較高的技術。

經過半天的鑽研,我成功地達成了第二個方法,亦在真機上試跑過沒有問題。當的滿心歡喜繼續開發了一段時間後,竟發現真機上似乎無法跑得成功。在這幾天假期,我花了很多時間除錯及改良,仍然找不到問題的徵結所在。時間已經迫在眉睫,希望能快點找到解決方法吧!

2010年4月5日 星期一

關閉字體的「反鋸齒補償」

在開發 iPhone Apps 的過程中,很多時會用上細小的字體。由於字體本身打開了「反鋸齒補償」,文字中較幼細的線條反而會變得難看。我一直都想使用「像素」式的字體,今天終於找到了方案:
BOOL allowsAntialiasing = NO;

CGContextSetShouldSmoothFonts(context, allowsAntialiasing);
CGContextSetShouldAntialias(context, allowsAntialiasing);
CGContextSetAllowsAntialiasing(context, allowsAntialiasing);