ゲーム制作フレームワーク不要! iOS ネイティブのノベルゲームアプリの作り方
iOS でノベルゲームを作る場合、ベストな方法が分からなかったので、とりあえず愚直な実装をしてみることにしました。
ゲーム制作フレームワークや、イケてるグラフィック効果など使っていません。言語は通常の Objective-C です。
今回作成したサンプルはこちらからダウンロード可能です。
仕様的なもの
- 縦画面
- 画面をタップするとゲームスタート
- 画面下部に台詞などのテキストを表示する
- 画面全体に背景画像を表示する
- タップすると次のテキスト・画像を表示する
- 最後までいくと最初にもどる
画面に表示される一連のオブジェクトをまとめたモデルを作る
今回はサンプルなので、表示するテキストと、背景画像のみをプロパティに持ちます。
実際のゲームでは立ち絵などのプロパティも必要になってくるでしょう。たぶん。
// NVMessage.h #import <Foundation/Foundation.h> @interface NVMessage : NSObject @property (copy, nonatomic) NSString *text; @property (copy, nonatomic) NSString *bgImageName; @end // NVMessage.m #import "NVMessage.h" @implementation NVMessage @end
アプリの起動
今回は Interface Builder を使用せず、コードでビューを作っていきます。
そのため AppDelegate には次のように記述します。
// NVAppDelegate.h #import <UIKit/UIKit.h> @interface NVAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @end // NVAppDelegate.m #import "NVAppDelegate.h" #import "NVViewController.h" @implementation NVAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.rootViewController = [[NVViewController alloc] init]; [self.window makeKeyAndVisible]; // Override point for customization after application launch. return YES; } @end
画面を表示する
テキスト・背景を表示する画面を作ります。
特に難しいことはしていないのですが、テキストを実際のゲームっぽく一文字ずつ順に表示する処理を実装したかったので、
その部分が少し複雑になっています。
画面処理の流れは、ビュー生成 (loadView) => ストーリー生成 (generateStory) => ユーザー反応待ち (buttonPressed:) => 次のメッセージ表示 という感じです。
以下、NVViewController のソースコードです。
// NVViewController.h #import <UIKit/UIKit.h> @interface NVViewController : UIViewController @end // NVViewController.m #import <QuartzCore/QuartzCore.h> #import "NVViewController.h" #import "NVMessage.h" @interface NVViewController () @property (strong, nonatomic) UITextView *messageView; @property (strong, nonatomic) UIImageView *bgImageView; @property (assign) BOOL messageTextAnimating; @property (strong, nonatomic) NSArray *messages; @property (assign) NSInteger messageIndex; @end @implementation NVViewController - (void)loadView { [super loadView]; self.view.backgroundColor = [UIColor blackColor]; UIImageView *bgImageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; [self.view addSubview:bgImageView]; self.bgImageView = bgImageView; // メッセージ表示枠を作る CGFloat messageHeight = 100; CGFloat messageMargin = 10; CGRect messageFrame = CGRectMake(messageMargin, self.view.frame.size.height - messageHeight - messageMargin, self.view.frame.size.width - (messageMargin * 2), messageHeight); // メッセージ表示の背景 UIView *messageBgView = [[UIView alloc] initWithFrame:messageFrame]; messageBgView.layer.borderWidth = 1.0f; messageBgView.layer.borderColor = [UIColor whiteColor].CGColor; messageBgView.backgroundColor = [UIColor blackColor]; messageBgView.alpha = 0.7f; [self.view addSubview:messageBgView]; // メッセージテキスト表示ビュー UITextView *messageView = [[UITextView alloc] initWithFrame:messageFrame]; messageView.editable = NO; messageView.userInteractionEnabled = NO; messageView.textColor = [UIColor whiteColor]; messageView.backgroundColor = [UIColor clearColor]; messageView.font = [UIFont boldSystemFontOfSize:14.0f]; [self.view addSubview:messageView]; self.messageView = messageView; // メッセージのタップ可能領域 UIButton *button = [[UIButton alloc] initWithFrame:messageFrame]; [button addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside]; button.backgroundColor = [UIColor clearColor]; [self.view addSubview:button]; } - (void)buttonPressed:(id)sender { if (self.messageTextAnimating) { // メッセージを表示しかけのときはアニメーションを停止 self.messageTextAnimating = NO; return; } if ([self.messages count] <= self.messageIndex) { // 最初に戻る self.messageIndex = 0; self.bgImageView.image = nil; } if ([self.messages count] > self.messageIndex) { NVMessage *m = self.messages[self.messageIndex++]; // 背景画像名が設定してある場合 if ([m.bgImageName length] > 0) { [UIView animateWithDuration:0.1 animations:^{ // 前の画像をフェードアウト self.bgImageView.alpha = 0.5f; } completion:^(BOOL finished) { // 新しい画像をフェードイン [UIView animateWithDuration:0.2 animations:^{ self.bgImageView.image = [UIImage imageNamed:m.bgImageName]; self.bgImageView.alpha = 1.0f; }]; }]; } else { self.bgImageView.image = nil; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ self.messageTextAnimating = YES; for (NSInteger i = 0; i <= [m.text length]; i++) { if (!self.messageTextAnimating) { // アニメーションが停止されてたら全テキストを表示する dispatch_async(dispatch_get_main_queue(), ^{ self.messageView.text = m.text; }); break; } // インデックス位置までのテキストを表示する dispatch_async(dispatch_get_main_queue(), ^{ self.messageView.text = [m.text substringToIndex:i]; }); // 次の文字を表示するまでの待ち時間 [NSThread sleepForTimeInterval:0.05]; } self.messageTextAnimating = NO; }); } } - (NSArray *)generateStory { NVMessage *m1 = [[NVMessage alloc] init]; m1.text = @"私「こんにちは」"; m1.bgImageName = @"bg_001.jpg"; NVMessage *m2 = [[NVMessage alloc] init]; m2.text = @"彼「さようなら」"; m2.bgImageName = @"bg_002.jpg"; NVMessage *m3 = [[NVMessage alloc] init]; m3.text = @"私「お待ちください。まだ話は終わっておりません。」"; m3.bgImageName = @"bg_001.jpg"; return @[m1, m2, m3]; } - (void)viewDidLoad { [super viewDidLoad]; self.messages = [self generateStory]; self.messageIndex = 0; self.messageTextAnimating = NO; } @end
完成!!
立ち絵や選択肢の実装も無く、ゲームと言ってはいけないような気もしますがとりあえず完成です!
実際のところ iOS のノベルゲーム開発ってどうやるんでしょうか
この分野は余り詳しくないのですが、通常だとゲーム制作フレームワークなどを使うのでしょうか? 吉里吉里系や NScripter 系のエンジンを使った方がさくっと作れるんでしょうかね・・・。
詳しい方、おすすめのフレームワークなどありましたら是非教えてください!!
参考リンク
- 同人ゲーム制作支援net > コラム > 制作ツール選び (詳しくまとまっていて、iOS/Android 対応がわかりやすかったです)
- 写真素材 足成【フリーフォト、無料写真素材サイト】 (背景画像)
- http://stackoverflow.com/questions/11686642/character-by-character-animation-for-uilable (文字を1文字ずつ表示する実装の参考)