A Day In The Life

とあるプログラマの備忘録

ストーリーメーカー 創作のための物語論

結構前に読んだストーリー作りのための本です。

ストーリーを作るためのテクニックについて書かれています。著者はただ闇雲に物語を書くのではなく、セオリーに則ってストーリーを組み立てると未経験者でもそれなりのストーリーを作ることが出来ると主張しています。
この本に載っているストーリー作りのテクニックの1つが以下の図です。

ゲーム制作でもストーリー作りはかなり重要なのでこういった本があるととてもたすかります。

プロの力が身につく iPhone/iPadアプリケーション開発の教科書が発売されました

1月17日に「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」の改訂版が発売されました。改訂内容は以下の通りです。

よろしくお願いします。

本書の特徴

昨年の12月から Swift に対応した本が次から次へと発売されています。多くの本が400ページを超える大型本です。400ページを超える本はとても丁寧に細かく沢山のことが説明されているもののすべてを読んで理解するのに時間がかかります。
本書はページ数を350ページ程度に抑えコンパクトに読める内容になっています。これだけの短いページ数で Swift の構文から UI 構築、データ永続化、課金まで説明している本は他にないと自負しています。iOS アプリ開発を効率よく学びたい方はぜひとも本書を手に取って読んでみてください。また Swift の構文説明は Objective-C と比較しながら解説しているのでこれまで Objective-C を使って開発されていた方にも読みやすい内容となっています。

Isometric MapのZオーダーの指定方法

Cocos2d-xでIsometric Mapを使ったゲームのサンプルを作成していたところz-order(zorder, zindex, z-index)の指定で結構ハマったのでメモです。
Isometric Mapというのはよくある2.5D的な1マスが菱形になっているマップのことです。こんな感じのやつです。
Isometric Map
Isometric Mapを使った有名なゲームだとクラッシュオブクランなんかがあります。
ゲームで使用するマップデータの作成を無料のTiled Map Editorというツールを使ってCocos2d-xと連携させて使っていました。
TiledMap Editorを使うとわりと簡単にマップを作成することはできるものの、マップの上にキャラだったり建物だったりを配置するとZオーダーの問題が出てきます。
そこで以下の図のようにマップ座標のxとyを足した値をZオーダーに設定するとうまくいきました(図は40マス×40マスのマップの例です)。
Isometric MapのZオーダー
プログラムにすると以下のような感じになります。

bool HelloWorldScene::init()
{
  //////////////////////////////
  // 1. super init first
  if ( !Layer::init()) {
    return false;
  }
  auto tiledMap = TMXTiledMap::create("tiledmap.tmx");
  this->addChild(tiledMap, 1, "tiledMap");  
  auto layer = tiledMap->getLayer("layer_name");
  Size size = tiledMap->getMapSize();
  // マップのサイズ分まわす
  for (int i = 0; i < size.width; i++) {
    for (int j = 0; j < size.height; j++) {
      Vec2 pos = Vec2(i, j);
      // タイルオブジェクトを取得
      auto tile = layer->getTileAt(pos);
      // タイルが存在する場合
      if (tile) {
        // GIDを取得
        auto gid = layer->getTileGIDAt(pos);
        // GIDごとに建物を配置
        if (gid == 1) {
          auto sprite = Sprite::create("building.png");
          // タイルの真ん中の位置にスプライトをセット
          Vec2 midPoint = tile->getPosition() + (tile->getContentSize() / 2);
          sprite->setPosition(midPoint);
          // Zオーダーを指定してスプライトをタイルマップに追加する
          tiledMap->addChild(sprite, i + j);
        }
      }
    }
  }
}

上記は動かないスプライトの例ですが、スプライトを動かす場合はポジションからマップ上の座標を計算してZオーダーを設定してやると上手くいきます。
通常の座標からマップ上に座標に変換する方法は以下のサイトが参考になります。

特定のスプライトすべて削除する

最近 Cocos2d-x で2Dゲームを作ってます。
スプライトに名前つけて追加したあと、その名前のスプライトを検索して削除する時にハマったのでメモです(Cocos2d-x のバージョンは3.3です)。
まずはダメなコードから

bool HalloWorldScene::init()
{
  // nameという名前で大量にスプライトを追加する
  for (int i = 0; i < 100; i++) {
    auto sprite = Sprite::create("hoge.png");
    this->addChild(sprite, 0, "hoge");
  }

  // タッチイベント
  auto listener = EventListenerTouchOneByOne::create();
  listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
  auto dip = this->getEventDispatcher();
  dip->addEventListenerWithSceneGraphPriority(listener, this);
  dip->setPriority(listener, 0);
}

bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
{
  // だめ。たまに消えないスプライトがある
  for (auto node : this->getChildren()) {
    if (node->getName() == "hoge") {
      node->removeFromParent();
    }
  }
  // 以下もダメ
  this->enumerateChildren("hoge", [](Node *node) -> bool {
    node->removeFromParent();
    return false;
  });
  return true;
}

以下がうまく行ったコードです(initの処理は同じなので省略してます)。

bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
{
  // nameという名前のスプライトを検索する
  this->enumerateChildren("hoge", [](Node *node) -> bool {
    // スプライトを削除するアクション
    auto action = RemoveSelf::create();
    node->runAction(action);
    return false;
   });  
  return true;
}

RemoveSelf アクションは結構使いどころが多くて重宝してます。その他にも特定のアクションを実行されてからスプライトを削除する時なんかに使ってます。例えばこんな感じです。

bool HelloWorld::onTouchBegan(Touch* touch, Event* event)
{
  auto sprite = Sprite::create("hoge.png");
  this->addChild(sprite, 0, "hoge");
  auto moveTo = MoveTo::create(1, Vec2(100, 100));
  auto seq = Sequence::create(moveTo, RemoveSelf::create(), NULL);
  sprite->runAction(seq);
}

パーティクルを作成するときなんかに使えます。

C++のコード

このブログでC++のコードを書いたことがなかったんですね(話題にしたことはあったんですが)。記念すべき第1回目となりました。C++も11になってずいぶん使いやすくなったと思います。

SpriteKitではじめる2Dゲームプログラミング

先日こちらの記事にも書きましたが「Hedgehog Drive」というゲームをリリースしました。このアプリのゲーム部分は SpriteKit を使って実装しました。初めて SpriteKit を使ったので備忘録的にこのフレームワークの使い方をまとめてみました。

2D ゲームで使用される基本用語

SpriteKit の説明の前に 2D ゲーム開発でよく使われる用語とその説明をします。SpriteKit でも以下で説明する用語が使われていますので覚えておくと良いと思います。

  • ゲームループ
    ゲームではキャラクタや背景などが常に動いています。このような常に変化する状態を実現するために、ゲームの状態をプログラムで処理する必要があります。これを処理するための仕組みがゲームループ(またはメインループ)です。ゲームループの処理は常に一定時間ごとに呼び出されます
  • FPS
    Frames Per Second の略。1秒間に何回フレームが処理されるかを表す単位のことです。ゲールループはFPSごとに呼び出されます。
  • シーン(Scene)
    ゲームの場面のことです。RPG のマップ、町の中、バトル、ムービーなどゲームの見せ方が変わる単位です。ストーリーボードで使われるシーンと考え方は同じです
  • トランジション
    シーンの切り替え時のアニメーションのこと
  • スプライト(Sprite)
    シーンの上で動き回るオブジェクトや障害物のことです。スーパーマリオのマリオやクリボー、土管やハテナボックスがスプライトです
  • パーティクル(Particle)
    iOS ではエミッター(Emitter)とも言われています。シューティングゲームで敵を撃破したときの爆発とか、飛行機やロケットのエンジンから噴射される炎なんかを指します。スプライトが爆発する時の演出に使ったりスプライトの動きをよりリアルに見せる為に使うことが多いです
  • アクション(Action)
    スプライトの動き、アニメーションのこと
  • テクスチャ(Texture)
    スプライトの見た目や質感を表現するための模様やパターン、画像のこと

シーンとトランジションの関係を図にすると以下のようになります。
シーンとトランジション
シーンとスプライト、アクションの関係を図にすると以下のようになります。
スプライトとアクション

SpriteKit クラス構成

SpriteKit ではシーン、スプライト、パーティクルがツリーで構成されています。それぞれシーンが SKScene クラス、スプライトが SKSpriteNode クラス、パーティクルが SKEmitterNode クラスとなっています。またツリーを構成する要素のことをノードと呼んでいます。ノードにあたるクラスが SKNode クラスで、SKScene、SKSpriteNode、SKEmitterNode クラスは SKNode クラスを継承しています。
また SpriteKit では UIView オブジェクト上にゲーム画面を配置できるように SKView というクラスを提供しています。SKView オブジェクト上にシーンオブジェクトを配置します。
以下は SpriteKit から提供されている主なクラスの関係を図にしたものです。
SpriteKitクラス図

ゲームプログラム作成の流れ

ゲームプログラム作成の流れは大まかに以下の順番で行います。

  • シーンの配置と表示
  • スプライトの配置
  • スプライトに動きや音をつける

シーンの配置と表示

シーンをビューに配置して表示するには SKView と SKScene クラスを使います。SKView はビューとシーンの橋渡しのためのクラスです。シーンの配置と表示は以下のように UIViewController の viewDidAppear: メソッドで行います。

@implementation MyViewController {

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
    
  // SKViewオブジェクトの生成と追加
  SKView *skView = [[SKView alloc] initWithFrame:self.view.frame];
  skView.showsFPS = YES; // FPSの表示(デバッグ用設定)
  skView.showsNodeCount = YES; // 配置されているノードの数を表示(デバッグ用設定)
  [self.view addSubview:skView];
    
  // SKSceneオブジェクトの生成と配置
  SKScene *scene = [MyScene sceneWithSize:skView.bounds.size];
  scene.scaleMode = SKSceneScaleModeAspectFill;
  scene.userInteractionEnabled = YES;
    
  // シーンの表示
  [skView presentScene:scene];
}

@end

はじめに SKView オブジェクトを生成してビューに追加します。その後、SKScene オブジェクトを生成して SKView オブジェクトの presentScene: メソッドを使って表示します。なお SKView オブジェクトの生成と追加はストーリーボード上で行うことも出来ます。

トランジションを使ったシーンの切り替え

SKTransition クラスを使うとシーンの切り替えにちょっとした演出を加えることが出来ます。
以下は UIViewController のタッチイベントでシーンを切り替えるプログラムの例です。

@implementation MyViewController

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  // シーンオブジェクトの生成
  SKScene *scene = [[SKScene alloc] initWithSize:self.stageView.frame.size];
    
  // トランジションオブジェクトの生成(ドアオープン)
 SKTransition *transition = [SKTransition doorsOpenHorizontalWithDuration:2];
    
  // トランジションをつかったシーンの切り替え
  [self.stageView presentScene:scene transition:transition];
}

@end

トランジションはここで紹介したドアオープン以外にもいろいろあります。

SKScene クラスの拡張

シーンはゲームの場面ごとに分かれていて、各場面ごとに各種イベント処理やスプライトの配置(後ほど説明します)をする必要があります。従って各場面ごとに SKScene クラスを継承してシーンを作成する必要があります。

@interface MyScene : SKScene

@end
タッチイベントの受け取り

SKScene オブジェクトは以下のようにタッチ系イベントを受け取ることが出来ます。

@implementation MyScene {

// Touch Began イベント
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // シーン上の位置を取得する
  UITouch *touch = [touches anyObject];
  CGPoint location = [touch locationInNode:self]; 
  NSLog(@"%@", NSStringFromCGPoint(location));
}
// Touch Moved イベント
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}
// Touch Ended イベント
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}

@end

基本 UIView オブジェクトでタッチイベントを受け取る方法と同じです。UITouch オブジェクトの locationInNode: メソッドを使うとシーン上のどの位置がタッチされたかがわかります。

ゲームループイベントの受け取り

また SKScene オブジェクトでは以下のようにゲームループイベントを受け取ることが出来ます。

@implementation MyScene {

// ゲームループイベント
-(void)update:(CFTimeInterval)currentTime {
  // FPSごとに呼び出される
  NSLog(@"%g", currentTime);
}

@end

スプライトの移動やスプライト同士の衝突判定なんかをするときに使います。

スプライトの配置

シーンを表示しただけではなにも表示されませんので、シーンにスプライトを配置します。スプライトの配置には SKSprite クラスを使います。スプライトをシーン上に配置するとスプライトが画面に表示されます。スプライトの配置は以下のようにシーンのコンストラクタメソッドで行います。

@implementation MyScene

-(instancetype)initWithSize:(CGSize)size {
  if (self = [super initWithSize:size]) {
    // 画像名を指定してスプライトオブジェクトを生成する
    SKSprite *sprite = [[SKSprite alloc] initWithImageNamed:@"hedgehog"];
    // シーンの中央に設定
    sprite.position = CGPointMake(CGRectGetMidX(self.frame),
                                  CGRectGetMidY(self.frame));
    // スプライトに名前をつける
    sprite.name = @“hedgehog”;
    // スプライトを配置する
    [self addChild:sprite];
  }
}

@end

スプライトに名前をつけるとシーンオブジェクトの処理でスプライトを検索する時に役立ちます。

スプライトの検索

スプライトに対する処理は通常、シーンオブジェクトのタッチイベントかゲームループイベントで行います。各イベントでスプライトオブジェクトを参照する場合はシーン上のスプライトを検索する必要があります。
SKScne クラスの childNodeWithName: メソッドを使うとシーン上に配置されているスプライトを検索してオブジェクトを取得することが出来ます。

@implementation MyScene {

-(void)update:(CFTimeInterval)currentTime {
  // hedgehogという名前のスプライトを探してオブジェクトを取得する
  SKNode *sprite = [self childNodeWithName:@“hedgehog”];
}

@end

スプライトに動きや音をつける

SKAction クラスを使うとスプライトに簡単に動きや音をつけることが出来ます。スプライトをシーンに配置しただけではスプライトは動かずゲームらしくないのでスプライトにアクションをつけていきます。スプライトにアクションを実行させるには以下のようにシーンのイベントメソッドを実装します。

@implementation MyScene {

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // シーン上の位置を取得する
  UITouch *touch = [touches anyObject];
  CGPoint location = [touch locationInNode:self]; 
  // hedgehogスプライトの取得する
  SKNode *sprite = [self childNodeWithName:@“hedgehog”];
  // タッチした位置に移動するアクションの生成
  SKAction *move = [SKAction moveTo:location duration:1];
  // アクションを実行する
  [sprite runAction:move];
}

@end
複数のアクションを順番に実行する

シーケンスを使うと、複数のアニメーションを連続して実行することができます。シーケンスは SKAction クラスの sequence メソッドを使って作成することが出来ます。また作成したシーケンスオブジェクトは通常のアクションを実行するのと同じように runAction メソッドを使って実行することが出来ます。シーケンスを使ったアニメーションの実行では前のアクションが実行されるまで次のアクションは実行されません。
以下はスプライトの向きを変えてから移動するプログラムの例です。

@implementation MyScene {

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // シーン上の位置を取得する
  UITouch *touch = [touches anyObject];
  CGPoint location = [touch locationInNode:self];
  // hedgehogスプライトの取得する
  SKNode *sprite = [self childNodeWithName:@“hedgehog”];
  // タッチした位置に移動するアクションの生成
  SKAction *move = [SKAction moveTo:location duration:1];
  // 角度の計算
  CGFloat dx = location.x - self.position.x;
  CGFloat dy = location.y - self.position.y;
  CGFloat angle = atan2(dx, dy);
  // 角度を変えるアクションの生成
  SKAction *rotate = [SKAction rotateToAngle:-angle duration:1];
  // シーケンスの生成
  SKAction *seq = [SKAction sequence:@[rotate, move]];
  // アクションの実行(角度が変わってから移動する)
  [self runAction:seq];
}

@end
複数のアクションを同時に実行する

グループを使うと、複数のアニメーションを同時に実行することができます。グループは SKAction クラスの group メソッドを使って作成することが出来ます。また作成したグループオブジェクトは通常のアクションを実行するのと同じように runAction メソッドを使って実行することが出来ます。
以下はスプライトの向きを変えながら移動するプログラムの例です。

@implementation MyScene {

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // シーン上の位置を取得する
  UITouch *touch = [touches anyObject];
  CGPoint location = [touch locationInNode:self];
  // hedgehogスプライトの取得する
  SKNode *sprite = [self childNodeWithName:@“hedgehog”];
  // タッチした位置に移動するアクションの生成
  SKAction *move = [SKAction moveTo:location duration:1];
  // 角度の計算
  CGFloat dx = location.x - self.position.x;
  CGFloat dy = location.y - self.position.y;
  CGFloat angle = atan2(dx, dy);
  // 角度を変えるアクションの生成
  SKAction *rotate = [SKAction rotateToAngle:-angle duration:1];
  // グループの生成
  SKAction *group = [SKAction group:@[rotate, move]];
  // アクションの実行(角度が変わってから移動する)
  [self runAction:group];
}

@end
音の再生

アクションを使って音を再生することもできます。AVFundation フレームワークや AudioToolbox フレームワークを使って音を再生せるよりも簡単にできます。
以下はシーンでBGM音源を再生させるためのプログラム例です。

@implementation MyScene

-(instancetype)initWithSize:(CGSize)size {
  if (self = [super initWithSize:size]) {
    // 音声ファイルのパスを指定してアクションを生成
    SKAction *sound = [SKAction playSoundFileNamed:@“bgm.m4a" waitForCompletion:YES];
    // 音を再生する
    [self runAction:sound];
  }
}

@end

ゲーム開発初心者がはじめて2Dゲームを作ってみてわかったこと

こちらの記事にも書きましたが先日「Hedgehog Drive」という2Dのゲームをリリースしました。

アプリと違ってゲーム開発は初めてということもあり、苦労したところが多かったのでその辺りをまとめてみました。

どんなゲームをつくったか

指でハリネズミを操作して画面上のセルを消していくゲームです。
画面キャプチャ
ハリネズミが消したセルの数を競います。
セルをすべて消すとアイテムが出現します。最後に消したセルの色によって以下のアイテムが出現します。

  • 最後に消したセルの色が紫だったら「ブドウ」
  • 最後に消したセルの色が黄だったら「チーズ」
  • 最後に消したセルの色が赤だったら「ニンジン」

これらのアイテムを使ってゲームを有利に進めることが出来ます。
動画を見るとどんなゲームかわかりやすいと思うのでプレイ動画を貼付けておきます。

価格は無料で広告表示もありません。

開発のきっかけとプログラミング

ちょうど一年ほど前 iOS6 の時代に、ゲーム開発の勉強をするために Objective-C で UIView を使ったゲームフレームワークを作ったのがきっかけでした。

ゲームエンジン用のサンプルアプリとしてじゃんけんゲーム、タワーディフェンスゲーム、ハリネズミのセル消しゲームなどたしか7個ぐらい作りました。中でも特に気に入っていたセル消しゲームをちゃんとゲームとしてつくってみようと思い去年の7月に開発を開始しました。ゲームの名前はハリネズミが自由に動き回ることから「Hedgehog Drive」としました。
そうこうしてるうちに9月になり、iOS7がリリースされてアップル純正の Sprite Kit という素晴らしいゲームフレームワークの存在を知りました。自分の作ったゲームフレームワークはすぐに開発中止にして「Hegehog Drive」を Sprite Kit に移植することにしました*1
開発当初のハリネズミはこんな感じの絵でした。
初代ハリネズミ

開発でハマったところ

Hedgehog Driveで使った技術は以下の通りです。

  • UIKit
  • Core Data
  • Sprite Kit
  • Game Kit

一番ハマったのが UIKit の Autolayout でした。iPhone 4S発売から2年経過しているのでマルチデバイス対応するメリットがどこまであるのかはきちんと考えた方がよいかもしれません。iPhone 5以上をターゲットにしてAutolayoutを使わずに開発効率をあげるのもありな気がしました。
Autolayout でハマったときに書いた記事が以下にあります。

Sprite Kit を使った感想

必要最低限の機能しかない小さめのフレームワークのわりに、アクションを使ってサウンドを再生でるなどかゆいところに手が届く設計になっていてとても使いやすかったです。はじめて本格的なゲームプログラミングをしましたがあまりハマらずすんなりと使うことができました。

  • シーンの制御
  • スプライトの配置
  • アクションを使ってスプライトを動かす
  • サウンドの再生方法

上記がわかれば何となく作れます。Sprite Kit 関連で読んだ資料はアップル公式ドキュメント「Sprite Kit Programming Guide」です。これを読めばなんとかなります。

SpriteKitの基本的な使い方は以下の記事にまとめました。

どこまで Sprite Kit を使うのか

すべて Sprite Kit で作るのかストーリーボードを組み合わせるのかは結構迷いどころです。自分は画面遷移やスコアの表示なんかは普通にストーリーボードをつかって UIViewController + UIView で作成しました。ゲーム部分のみ Sprite Kit を使いました。Autolayout なんかを考えるとすべて Sprite Kit で開発するのは現実的ではないかもしれません。
ゲームを Sprite Kit で開発するにしてもゲーム以外の部分で UIView や UIViewController を使うのは普通のアプリ開発と変わらないです。ということで引き続き拙著「プロの力が身につく iPhone/iPadアプリケーション開発の教科書」をよろしくお願いします(Swiftに対応した改訂版が発売されました)。

Sprite Kit の説明はありませんが UIView や UIViewController、Core Data などアプリ開発の基礎を学ぶことが出来ます。

ゲームの骨格が決まったのでそれをどうアレンジするか

ゲーム性

ハリネズミを操作してセルを消すという単純なものをどうやって面白くするかはかなり悩みました。はじめ消えたセルをランダムに再生していましたがまったく面白みがありませんでした。セルを生き物のように増殖させたくてセルオートマトンをベースに創発のアプゴリズムを加えて実装しました。
セル増殖のアイデアを実装するときに役にたったのが以下の書籍です。


制限時間内にひたすらセルを消していくだけだと面白くないと感じたので、アイテムをつかってゲームを有利にすすめることが出来ないか考えました。
画面上のセルをすべて消すアイテムや時間を止めるアイテムなどいろいろ考えましたが最終的に以下の3つに決定しました。

  • 残り時間をプラスするブドウ
  • ハリネズミが巨大化するチーズ
  • ハリネズミのスピードがアップするニンジン

個人的にはハリネズミが巨大化するチーズがお気に入りでした。巨大化チーズはたまたまプログラム開発中に間違えてハリネズミのサイズを大きくしたことで生まれました。

ゲームのユーザーインターフェース

当時 iOS7 リリース前後ということもありフラットデザインという言葉が流行していました。フラットデザインの勉強をかねてゲームをフラットな感じの UI でまとめることにしました。ゲームの UI をフラットにして大丈夫かというのが不安でフラットデザインっぽいゲームをいろいろと探しました。そのなかでDotsというゲームにかなり感銘をうけ、セルの色や UI など参考にしました。
Hedgehog Drive の開発をしていたころ iOS7 のデザインについて勉強がてら書いた記事がこちらです。

iOS7 に合ったゲームにしたかったというのが一番つよかったです。

ゲームバランスや難易度をどうやって検証したか

ゲームの実装がひととおり終わったのが12月はじめごろでした(そのときはまだ巨大化チーズはありませんでした)。娘(当時3才、現在4才)にネズミさんのゲームがあるよってことを認識してもらうことからはじめて、ちょっとした変更を加えるだびに遊んでももらいました。子供の反応は素直で面白くなければすぐにホームボタンを押されました。ゲーム中にホームボタンを押される経験を何度か繰り返しましたが改善するにつけてちゃんと最後まで遊んでくれるようになりました。巨大化チーズを見せた時の反応が良かったのでそのまま採用しました。
娘がHedgehog Driveで遊んでいる様子が以下です。

開発当初は子供向けゲームということは意識していませんでしたが、娘に遊ばせているうちに子供でも遊べるようなゲームに仕上げることが出来ました。

イラスト

本職はプログラマなので絵はあまり得意ではありません。いままでアプリのアイコンはほとんど自分でなんとか描いてきたので今回も何とかなるだろうと考えていました。描いたイラストはハリネズミと3種類のアイテムだけです。
ハリネズミブドウチーズニンジン
すべてベクター画像ツールInkscape を使いました。

自分のような素人がイラスを描くコツは二つあります。

  • イラストは円と四角の組み合わせでなんとか描ける
    単純に組み合わせるだけじゃなく差分を使うとぐっと表現力が増します。三角は四角と四角の差分で描けます
  • サイズを何となく決めないで極力黄金比白銀比にする
    素人が感覚だけで絵が描けるわけがないので理論でカバーします

たったこれだけです。あとは自分の描きたいイラストを頭の中でイメージして Google 画像検索の中の膨大なのイラストを参考に円と四角を使って絵を描けばいけます。どうしてもうまくいかない時だけベジェをつかってみると良いとおもいます。Hedgehog Drive で描いたイラストのなかでブドウの弦の部分だけはベジェを使いました。

ハリネズミ画像の変遷

完全蛇足ですがハリネズミ画像は何度か描き直しました。変遷は以下のようになっています。
初代ハリネズミ2代目ハリネズミ3代目ハリネズミ4代目ハリネズミ5代目ハリネズミ

カラーマネジメント

セルの色をどうするかハリネズミの色をどうするかボタンのいろどうするか最後までかなり悩みました。バランスのとれた色合いとはどういうものかいくつかの書籍をよみいろんなサイトの色についての記事を読みましたが未だに上手く色を決めることができません。配色に関しては今後の課題です。1つわかったことはRGBで色を考えるよりもHSV(HSB)を使った方が色の根拠を決めやすいと言うことでした。
配色については現在も以下の書籍を読んで勉強しています。

サウンド

ゲームをつくるにあたりアイデア、プログラミング、デザイン、イラストここまでは正直自分の力でなんとかなりました。ただサウンドだけはどうしても上手くいかず最終的にフリーの効果音と有料の効果音素材を購入しました。
はじめはGrage Bandを使ってなんとか効果音を作ってみたのですが、どうしても自分の作成した音に納得がいかず効果音を自力で作成するのをあきらめました。サウンドがゲームの面白さに直結するので妥協できませんでした。こんな音にしたいというイメージがあってもそれをどうやって作れば良いのか全くわかりませんでした。結局、BGMのみ自作しました。
効果音を探すにあたり参考にしたのが以下のサイトです。

On-Jin は個人開発であれば無料で音をダウンロードして使うことが出来ます。Soundstock は個人開発関係なく商用のゲームで使用する場合は500円(税抜)で気に入った音を購入することができます。セルの破裂音を On-Jin でその他効果音を Soundstock で調達しました。気に入った音、自分のイメージに近い音を探すコツは根気よくいろんな音を探して聞きまくることです。かなり地味な作業ですが良い効果音に出会うとかなりうれしくなります。

開発期間

2013年7月はじめに開発をはじめて2014年3月7日にバージョン1.0.0の承認がおりました。開発開始からリリースまで8ヶ月かかりました。京都にいた(9月まで)ころは大阪までの通勤時間(往復1時間半)と仕事の昼休み、東京に転勤してからは単身赴任中(10月から)は昼休みと仕事終わりの時間、単身赴任後(11月から)は仕事の昼休み時間を利用して開発しました。

開発費用

Soundstock から購入した効果音7種類の費用3500円プラス消費税のみです。それ以外はかかりませんでした。

ダウンロード数

iOS ゲームはすぐに埋もれると聞いてはいたものの本当に厳しくほとんどダウンロードされていません。
無料ゲームであるにも関わらずダウンロード数は2014年3月29日時点で54です。しかもダウンロードの半分が自分の知り合いというかなり厳しい結果です。
前作「即興ピアノ」のときはなんのプロモーションをしなくても毎日30-50ぐらいのダウンロードがあって今でも平均20ぐらいのダウンロードがあります。ここまで悲惨とは思いませんでした。

最後に

最後まで読んでいただきありがとうございます。この記事を読んで少しでもHedgehog Driveに興味をもたれた方はダウンロードお願いします。無料です。広告表示もありません。

*1:iOS7のベータ版の調査をまじめにやっていればこんなことにならなかったわけで、当時はめちゃくちゃ凹みました

ハリネズミと遊ぼう!指でなぞるだけのシンプルな2Dゲームをリリースしました


先日、iOS向けの2Dゲーム「Hedgehog Drive」をリリースしました。
画面を指でなぞって丸いセルを消していくシンプルなゲームです。アプリは無料で広告表示もありません。

4才の娘がルールを理解して遊べるようにゲームのバランス調整をするのが大変でした。せっかくなので娘と実際にHedghog Driveで遊んでいる動画を撮影してみました。

Hedgehog Drive は UIKit と Sprite Kit を組み合わせて開発しました。
はじめのゲーム開発で苦労したことも多かったですが無事にリリースまでこぎ着けることができて良かったです。ぜひダウンロードして遊んでみてください。