小嶋秀樹 | 研究室
日本語 | English
iPhone アプリ開発道場(入門編)

openFrameworks を利用して,iPhone アプリを開発します.従来の iPhone アプリ開発では,Objective-C から iOS 独自の API を利用する方法を習得する必要がありましたが,openFrameworks を利用することで,音処理・画像生成・画像処理などを,一般的なデスクトップ(Mac OS や Windows など)のアプリケーションと同じ方法で,C++ を使って開発することができます.ここでは,まず,Mac 上での開発環境(Xcode と openFrameworks)を準備していきましょう.

なお,openFrameworks を使って Windows あるいは Mac のデスクトップで動作するアプリを開発したい場合は「iPhone 道場(Windows/Mac 編)」で準備をしてから,このページの「簡単なアプリを作ってみる」に進んでください.

【準備1:Xcode のインストール】

すでに Xcode と iOS SDK の最新版を入手してある人は,ここをスキップして「準備2」に進んでください.最新版かどうかをチェックするには,http://developer.apple.com/ から iOS Dev Center にログインし,Downloads の欄にあるバージョン番号と,Xcode を起動して Xcode > About Xcode ... で表示されるバージョン番号を比較してください.

なお,Mac OS X のインストールディスクに入っている Xcode では iPhone アプリ開発はできません.新しくダウンロードしてください.

まず,http://developer.apple.com/ にアクセスし,iOS Dev Center に Apple ID を登録 (Register) してください.無料です.すでに iTunes などで Apple ID を取得済みかもしれませんが,名前などをすべて英字にして,新しく Apple ID を取り直すことをおすすめします.

取得した Apple ID で iOS Dev Center にログイン (Log in) し,Downloads の欄から,Xcode and iOS SDK の最新版をダウンロードし,インストールしてください.無料です.ファイルサイズが大きい(数 GB ある)ので,大学内のネットワークではダウンロードに失敗する場合があります.自宅などの安定したネットワーク環境で作業するとよいでしょう.Xcode は /Developer/Applications/Xcode.app にインストールされます.

現在のところ,Xcode and iOS SDK は Snow Leopard (Mac OS X 10.6) のみに対応しているようです.近いうちに Lion (Mac OS X 10.7) にも対応すると思われますが,Leopard (Mac OS X 10.5) やそれ以前の Mac OS X には対応していないようです.

【準備2:iPhone 開発環境のインストール】

iPhone, iPad, iPod touch の実機(デバイス)を使わず,iOS シミュレータ上でのみアプリケーションを動作させる場合は,ここをスキップして「準備3」に進んでください.

なお,iOS シミュレータでは,加速度センサや GPS など,iPhone の一部機能を利用することができません.

宮城大学の学生・教職員であれば,iOS Developer University Program (Miyagi University) に参加(無料)することで,実機でのアプリケーション実行が可能になります.参加を希望する方は,自分の Apple ID と利用する実機の Device ID (Identifier) をメールで小嶋までお送りください.折り返し iOS Dev Center から招待メールをお送りします.Device ID は,実機を USB 接続した状態で Xcode を起動し,Window > Organizer を開いて,左側のメニューから利用する実機を選択します.右側の Identifier の欄に表示された文字列(長さ 40字)が Device ID です.

招待メールを受け取ってからの手続きは,およそつぎのようになります.詳しくは,道場やゼミをとおして解説していきます.(以下「参加者」はあなたを,「管理者」は小嶋を意味します.)

【準備3:openFrameworks のインストール】

まず,http://www.openframeworks.cc/ にアクセスし,download ページから iPhone 007 をダウンロードします.展開されたフォルダ(of_preRelease_v007_iphone)をアクセスしやすい場所(ホーム直下など)に移動し,扱いやすい名前(ofiOS など)に変更しておいてください.(以下,このフォルダを「ofiOS ホーム」と呼ぶことにします.)

これでインストールは終了です.動作テストをしてみましょう.

これ以外にも iPhoneExamples フォルダの中には,いろいろなアプリケーション例が入っています.いくつか試してみてください.

たとえば advancedGraphics を iPhone(あるいは iPad, iPod touch)の実機で動かすには,上記「準備2」を済ませてから iPhone を USB で接続し,Xcode の Project ウィンドウの左上にあるメニューボタンを押し,Device を選択した状態で,Build and Run を押してください.iPhone に advancedGraphics アプリケーションが登録され,自動的に実行されます.USB 接続を外しても,advancedGraphics アプリケーションは iPhone に残り,いつでも実行することができます.不要になったら iPhone 上で削除してください.

簡単なアプリを作ってみる

それでは簡単な(しかしオリジナルな)アプリケーションを openFrameworks を使って開発してみましょう.複雑なアプリケーションを開発する場合も基本的に同じですので,その手順をしっかり把握してください.

【emptyExample のコピーと名称変更】

新しく開発するアプリケーションは,ofiOS ホーム > apps > iPhoneExamples > emptyExample のフォルダ全体を複製したものに変更を加えることで開発していきます.ここ(新道場)では,ofiOS ホーム > apps > dojo というフォルダを作り,ここに emptyExample のフォルダをコピー & ペーストします.新しく得られたフォルダの名前を,たとえば dojoExample1 に変更します.また,そのフォルダの中にある Xcode のプロジェクトファイル emptyExample.xcodeproj の名前も,たとえば dojoExample1.xcodeproj に変更します.

つぎに,dojoExample.xcodeproj をダブルクリックして Xcode を起動します.Project ウィンドウの左側にある Groups & Files の Targets を展開(三角をクリック)し,emptyExample というターゲット名を dojoExample1 に変更します.

ためしに,Simulator / dojoExample1 - iPhone Simulator 4.3(Project ウィンドウ左上のメニュー)を選択した状態で,Build and Run(金槌アイコン)してみてください.ビルドの後,iOS Simulator 上で dojoExample1 が起動し,灰色一色に塗られたスクリーンが表示されるはずです.

【プログラムの構造】

Xcode の Project ウィンドウの左側にある Groups & Files で一番上にある dojoExample1 を選択してください.すると右上側の副ウィンドウに,このプロジェクトに含まれるすべてのファイルが表示されます.一方,Groups & Files で dojoExample1 の下位にあるフォルダ状のものは,これらのファイルを論理的に分類したものです.たとえば src を選択すると,右上側の副ウィンドウには,main.mm, testApp.h, testApp.mm の3つのファイルのみが表示されます.なお,.mm は Mac OS X における C++(正確には Objective-C++)ファイルの拡張子です.Windows や Unix では .cpp が広く使われています.

これら3つのファイルの中で,testApp.htestApp.mm のみが,当座のあいだ,私たちが直接扱うファイルです.これらのファイル名を選択すると,右下側の副ウィンドウでその内容を表示・編集できます.あるいはダブルクリックすることで,別ウィンドウでその内容を表示・編集できます.これら2つのファイル(オリジナルから一部の空行やコメントを削除してあります)を見てみてください.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
public:
    void setup();
    void update();
    void draw();
    void exit();
    void touchDown(ofTouchEventArgs &touch);
    void touchMoved(ofTouchEventArgs &touch);
    void touchUp(ofTouchEventArgs &touch);
    void touchDoubleTap(ofTouchEventArgs &touch);
    void touchCancelled(ofTouchEventArgs &touch);
    void lostFocus();
    void gotFocus();
    void gotMemoryWarning();
    void deviceOrientationChanged(int newOrientation);
};
testApp.mm
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(127,127,127);
}

void testApp::update(){}
void testApp::draw(){}
void testApp::exit(){}

void testApp::touchDown(ofTouchEventArgs &touch){}
void testApp::touchMoved(ofTouchEventArgs &touch){}
void testApp::touchUp(ofTouchEventArgs &touch){}
void testApp::touchDoubleTap(ofTouchEventArgs &touch){}
void testApp::touchCancelled(ofTouchEventArgs &touch){}

void testApp::lostFocus(){}
void testApp::gotFocus(){}
void testApp::gotMemoryWarning(){}
void testApp::deviceOrientationChanged(int newOrientation){}

メインプログラム main.mm では,これら2つのファイルによって定義される testApp クラスからインスタンスを生成し,それを「実行」します.testApp オブジェクトの「実行」とは,まず最初に1回だけ testApp::setup() が呼び出され,その後は testApp::update() testApp::draw() が交互に呼び出されるというものです.その繰り返しの中で,たとえばスクリーンへのタッチなどのイベントが発生すると,testApp::touchDown() のような対応するメソッド が自動的に呼び出されます.

なお,testApp::exit() はアプリケーションが終了するとき(ユーザがホームボタンを押したときなど)に呼び出されるメソッドです.また,testApp::lostFocus(), testApp::gotFocus() は,電話や SMS が届くなどしてアプリケーションが一時中断する場合に,その直前・直後に呼び出されます.testApp::gotMemoryWarning() はメモリ不足になりかけた場合に呼び出されるメソッドで,たとえばメモリ上の画像ファイルを解放するなどの応急処置を行なうようにします.testApp::deviceOrientationChanged() は,iPhone の向きの変化を加速度センサが感知したとき,その方向(4方向)をアプリケーションに知らせてくれるメソッドです.

【簡単なアプリ:第1段階】

それでは実際にプログラミングをしてみましょう.背景を白にして,赤丸を画面の中央に描いてみます.ここでは背景色の設定を testApp::setup() に,赤丸の描画を testApp::draw() に仕込むために,testApp.mm をつぎのように書き換えます.testApp.h に変更はありません.実行すると,下図のような「日の丸」が表示されるはずです.

testApp.mm
dojoExample1 window
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    //  white background
    ofBackground(255, 255, 255);
}

void testApp::update() {}

void testApp::draw() {
    //  draw a red circle
    ofSetColor(255, 0, 0);
    ofCircle(160, 240, 96);
}

(... 以下変更なし ...)

ofBackground(r, g, b) は背景の色を変更するための関数です.RGB それぞれの成分値を 0〜255 の整数で与えます.同様に,ofSetColor(r, g, b) は描画色(前景色)を設定するための関数です.ofCircle(x, y, r) は,中心を (x, y) とし半径を r とする円を描き,塗りつぶします.単位はピクセル(横 320,縦 480)で,左上端が (0, 0) となります.なお,塗りつぶさない場合は ofNoFill() を先に呼び出します.それ以降は ofSetLineWidth(w) で指定した太さの線で輪郭のみが描かれます.デフォルトの塗りつぶすモードに戻すには ofFill() を呼び出します.

発展課題: 赤丸の輪郭がややギザギザしています.これは円を正多角形(デフォルトでは正20角形)で近似しているためです.この分割数は ofSetCircleResolution() で設定できます.testApp::setup() に ofSetCircleResolution(32) を入れて,その効果を確かめてください.

【簡単なアプリ:第2段階】

上のアプリケーションを改造して,アニメーションのように赤丸を動かしてみましょう.testApp::update() で赤丸の位置(中心座標)を計算するようにします.testApp::draw() ではその位置に赤丸を描画するようにします.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
private:
    double rad;
    float posX, posY;
public:
    void setup();
    void update();
    void draw();
    void exit();
    (... 変更なし ...)
};
testApp.mm
dojoExample1 window 2
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    ofSetFrameRate(30);
}

void testApp::update() {
    rad += 0.05;
    posX = 160 + 100 * cos(rad);
    posY = 240 + 100 * sin(rad);
}

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 96);
}

(... 以下変更なし ...)

ofSetFrameRate(30) で毎秒 30 回のペースで testApp::update() と testApp::draw() のループが実行されるように設定します.このペースで,毎回,背景色でクリアされた画面に,testApp::update() と testApp::draw() によって描画要素を画面に配置しています.

testApp::update() では,角度 rad を 0.05 ずつ増やし,そこから中心座標 (posX, posY) を計算しています.これらの変数 (rad, posX, posY) は,基本的には testApp.h にある testApp クラスのインスタンス変数として用意するのがよいでしょう.testApp.mm のみからアクセスするなら private: に,外部からもアクセスできるようにするなら public: に置きます.

testApp::draw() では,testApp::update() によって計算された座標 (posX, posY) に,半径 96 の円盤を描画しています.アプリケーションを実行すると,(160, 240) を中心とする半径 100 の円周上を,赤い円盤(の中心)が反時計回りに動くはずです.

発展課題: testApp::update() をつぎのように書き換えて動作させてみてください.赤丸が複雑に(リサージュ図形に沿って)動くようになります.

void testApp::update() {
    rad += 0.01;
    posX = 160 + 100 * sin(rad * 5);
    posY = 240 + 100 * cos(rad * 6);
}
タッチを使ったアプリを作ってみる

タッチスクリーンは iPhone や iPad を特徴づける入力デバイス(画面も含めれば入出力デバイス)です.その基本的な使い方をマスターしていきましょう.なお,Windows/Mac をターゲットとする場合は,touchDown() などのメソッドを mousePressed() などに置き換えてください.

【簡単なタッチアプリ:第1段階】

まずはタッチした位置に小さめの赤丸を描画するアプリケーションをつくります.ここでは,タッチスクリーンに指を置いたときに呼び出される testApp::touchDown() と testApp::touchMoved() の2つのメソッドを使います.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
private:
    float posX, posY;
public:
    void setup();
    void update();
    void draw();
    void exit();
    (... 変更なし ...)
};
testApp.mm
dojoExample1 window 2
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
}

void testApp::update(){}

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void testApp::exit(){}

void testApp::touchDown(ofTouchEventArgs &touch) {
    posX = touch.x;
    posY = touch.y;
}

void testApp::touchMoved(ofTouchEventArgs &touch) {
    posX = touch.x;
    posY = touch.y;
}

(... 以下変更なし ...)

プログラムは簡単です.タッチに関するイベントによって呼び出されるメソッドには,引数として ofTouchEventArgs という構造体へのリファレンス touch が与えられます.この構造体にはタッチに関する情報が格納されています.ここでは,タッチ座標 (x, y) を読み出し,インスタンス変数 posX, posY にコピーしています.この座標 (goalX, goalY) に testApp::draw() が半径 48 の赤丸を描画します.

発展課題: タッチした位置に赤丸がスムーズに近づいていくように改造してみましょう.ここでは,タッチ位置をゴール (goalX, goalY) とし,赤丸の現在位置 (posX, posY) との差 (difX, difY) に正定数(たとえば 0.05)をかけることで,移動速度を決めています.この移動速度を現在位置に足し込むことで,赤丸をスムーズに動かしています.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
private:
    float posX, posY;
    float goalX, goalY;
public:
    (... 変更なし ...)
};
testApp.mm
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    goalX = posX;
    goalY = posY;
}

void testApp::update() {
    float difX = goalX - posX,
          difY = goalY - posY;
    posX += difX * 0.05;
    posY += difY * 0.05;
}

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void testApp::exit(){}

void testApp::touchDown(ofTouchEventArgs &touch) {
    goalX = touch.x;
    goalY = touch.y;
}

void testApp::touchMoved(ofTouchEventArgs &touch) {
    goalX = touch.x;
    goalY = touch.y;
}

(... 以下変更なし ...)
【簡単なタッチアプリ:第2段階】

赤丸に動き(速度)を与えて指を離すと,その速度をある程度維持して動いていくようなアプリケーションをつくります.スクリーンの端で反射するようにします.赤玉1つのビリヤードの雰囲気です.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
private:
    float posX, posY;
    float velX, velY;
public:
    (... 変更なし ...)
};
testApp.mm
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
}

void testApp::update() {
    posX += velX;
    posY += velY;
    if (posX <   0) { posX =   0; velX *= -0.9; }
    if (posX > 319) { posX = 319; velX *= -0.9; }
    if (posY <   0) { posY =   0; velY *= -0.9; }
    if (posY > 479) { posY = 479; velY *= -0.9; }
    velX *= 0.99;
    velY *= 0.99;
}

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void testApp::exit(){}

void testApp::touchDown(ofTouchEventArgs &touch) {
    posX = touch.x;
    posY = touch.y;
    velX = velY = 0;
}

void testApp::touchMoved(ofTouchEventArgs &touch) {
    posX = touch.x;
    posY = touch.y;
}

void testApp::touchUp(ofTouchEventArgs &touch) {
    velX = touch.x - posX;
    velY = touch.y - posY;
}

(... 以下変更なし ...)

赤丸の速度を決めるのは testApp::touchUp() です.指が離れた瞬間の座標 (touch.x, touch.y) から,その直前に testApp::touchMoved() あるいは testApp::touchDown() によって記録された指の位置=赤丸の位置 (posX, posY) を引くことで,速度ベクトル (velX, velY) を求めています.

実際に赤丸の位置を更新しているのは testApp::update() です.まず赤丸の現在位置 (posX, posY) に速度 (velX, velY) を加え,赤玉の位置を更新します.これがウィンドウの境界を越えてしまった場合,その境界位置で速度を反転(−0.9 倍)させています.また,繰り返しのステップごとに速度を 1% だけ減らしているため,赤丸はやがて止まります.

発展課題: iOS Simulator では思ったように赤丸に速度を与えることができないかもしれません.そこで,まず赤丸にタッチし,速度を与えたい方向とは 180° 逆向きにドラッグしてから指を離すことで,パチンコのように赤丸を弾くように改造します.タッチ関連のメソッドを以下のように書き換えてみてください.

void testApp::touchDown(ofTouchEventArgs &touch) {
    posX = touch.x;
    posY = touch.y;
    velX = velY = 0;
}

void testApp::touchMoved(ofTouchEventArgs &touch) {
}

void testApp::touchUp(ofTouchEventArgs &touch) {
    velX = posX - touch.x;
    velY = posY - touch.y;
}
【簡単なタッチアプリ:第3段階】

つぎは,指でタッチした場所から赤丸が逃げていくようなアプリケーションをつくります.赤丸は指が嫌いで,遠ざかろうとします.赤丸を追いかけて,角に追い込むなど,いじめてみてください.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
private:
    float posX, posY;
    float velX, velY;
    float finX, finY;
    int isTouching;
public:
    (... 変更なし ...)
};
testApp.mm
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
    isTouching = FALSE;
}

void testApp::update() {
    if (isTouching) {
        float difX = finX - posX,
              difY = finY - posY,
              len2 = difX * difX + difY * difY;
        if (len2 > 0) {
            velX += - 5.0 * difX / len2;
            velY += - 5.0 * difY / len2;
        }
    }
    posX += velX;
    posY += velY;
    if (posX <   0) { posX =   0; velX *= -0.9; }
    if (posX > 319) { posX = 319; velX *= -0.9; }
    if (posY <   0) { posY =   0; velY *= -0.9; }
    if (posY > 479) { posY = 479; velY *= -0.9; }
    velX *= 0.99;
    velY *= 0.99;
}

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void testApp::exit(){}

void testApp::touchDown(ofTouchEventArgs &touch) {
    finX = touch.x;
    finY = touch.y;
    isTouching = TRUE;
}

void testApp::touchMoved(ofTouchEventArgs &touch) {
    finX = touch.x;
    finY = touch.y;
}

void testApp::touchUp(ofTouchEventArgs &touch) {
    isTouching = FALSE;
}

(... 以下変更なし ...)

指でタッチした座標を (finX, finY) に記録し,タッチしているか否かを isTouching に記録しています.これらの情報から,testApp::update() では,タッチしている間だけ,タッチの位置と赤丸の現在位置との差 (difX, difY) から「反発力」を計算し,それを赤丸の速度 (velX, velY) に加えています.タッチしていなければ「反発力」は加えられません.その後の処理は従来どおりです.

発展課題: 2本以上の指を使ったマルチタッチも試してみましょう.赤丸を集団でイジメます.マルチタッチ情報は,testA::touchDown() などのメソッドが複数回(それぞれ異なる id で)呼び出されることで,アプリケーション側に伝達されます.なお,Windows/Mac をターゲットとする場合,マルチタッチ機能を使えません.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

#define FINMAX 5

class testApp : public ofxiPhoneApp {
private:
    float posX, posY;
    float velX, velY;
    float finX[FINMAX], finY[FINMAX];
    int isTouching[FINMAX];
public:
    (... 変更なし ...)
};
testApp.mm
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
    for (int i = 0; i < FINMAX; i++) isTouching[i] = FALSE;
}

void testApp::update() {
    for (int i = 0; i < FINMAX; i++) {
        if (isTouching[i]) {
            float difX = finX[i] - posX,
                  difY = finY[i] - posY,
                  len2 = difX * difX + difY * difY;
            if (len2 > 0) {
                velX += - 5.0 * difX / len2;
                velY += - 5.0 * difY / len2;
            }
        }
    }
    posX += velX;
    posY += velY;
    if (posX <   0) { posX =   0; velX *= -0.9; }
    if (posX > 319) { posX = 319; velX *= -0.9; }
    if (posY <   0) { posY =   0; velY *= -0.9; }
    if (posY > 479) { posY = 479; velY *= -0.9; }
    velX *= 0.99;
    velY *= 0.99;
}

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void testApp::exit(){}

void testApp::touchDown(ofTouchEventArgs &touch) {
    if (touch.id < FINMAX) {
        isTouching[touch.id] = TRUE;
        finX[touch.id] = touch.x;
        finY[touch.id] = touch.y;
    }
}

void testApp::touchMoved(ofTouchEventArgs &touch) {
    if (touch.id < FINMAX) {
        finX[touch.id] = touch.x;
        finY[touch.id] = touch.y;
    }
}

void testApp::touchUp(ofTouchEventArgs &touch) {
    isTouching[touch.id] = FALSE;
}

(... 以下変更なし ...)

タッチ関連のメソッドの引数となっている touch には id というメンバがあり,ここにタッチの ID が非負整数で入ります.現状の iPhone は5カ所までのマルチタッチを読み取ることができるので,touch.id は 0〜4 の範囲をとります.

さらに発展課題: 上のマルチタッチの例で,testApp::draw() を以下のように書き換えて,マルチタッチ認識の動作を確認してください.

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
    ofSetColor(200, 200, 200);
    for (int i = 0; i < FINMAX; i++)
        if (isTouching[i]) ofCircle(finX[i], finY[i], 48);
}

6本目の指がタッチすると,それまで追跡していたすべての指は自動的にタッチアップしてしまうようです.これが仕様なのかバグなのかはわかりません.

加速度センサを使ったアプリを作ってみる

加速度センサ(動きや傾きを読み取るセンサ)も,iPhone や iPad を特徴づける入力でバイスです.ここでは,その基本的な使い方をマスターしていきましょう.なお,加速度センサは Windows/Mac をターゲットとする場合は利用できません.また,iPhone をターゲットとする場合でも iOS Simulator 上では使えません.実機で動かしてください.

【簡単な加速度アプリ】

既に testApp::setup() の中で ofxAccelerometer.setup() が呼び出されるように設定されていますので,加速度センサを利用するにあたり,とくに何の設定も必要ありません.センサ値の読み出しメソッドによって,いつでも加速度を得ることができます.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
private:
    float posX, posY;
    float velX, velY;
public:
    (... 変更なし ...)
};
testApp.mm
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
}

void testApp::update() {
    velX += ofxAccelerometer.getForce().x;
    velY -= ofxAccelerometer.getForce().y;
    posX += velX;
    posY += velY;
    if (posX <   0) { posX =   0; velX *= -0.9; }
    if (posX > 319) { posX = 319; velX *= -0.9; }
    if (posY <   0) { posY =   0; velY *= -0.9; }
    if (posY > 479) { posY = 479; velY *= -0.9; }
    velX *= 0.99;
    velY *= 0.99;
}

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void testApp::exit(){}

void testApp::touchDown(ofTouchEventArgs &touch){}

void testApp::touchMoved(ofTouchEventArgs &touch){}

void testApp::touchUp(ofTouchEventArgs &touch){}

(... 以下変更なし ...)
dojoExample1 window 3

加速度センサで捉えた加速度は,ofxAccelerometer.getForce() で取り出すことができます.得らる構造体のメンバ x, y, z は,それぞれ左右軸・上下軸・前後軸(いずれも iPhone を垂直に正立させたとき)の加速度(単位は g = 9.8m/s2)を表わしたものです.加速度が正となるのは,正立させた iPhone と向き合って左側・上側・手前側となります.(右図参照)

なお,ofxAccelerometer.getForce() で取り出せるのは,ローパスフィルタによって平滑化された加速度です.もし,より応答性の高い加速度センサが必要な場合は,ofxAccelerometer.getRawAcceleration() を使ってください.また,ofxAccelerometer.getOrientation() によって,X 軸・Y 軸まわりの傾き角度(単位 deg) を取り出すこともできます.

発展課題: ここまでの例では,赤丸の中心座標によって壁との衝突を検知していました.これを赤丸の輪郭で壁との衝突を検知するようにしてみます.testApp::update() を以下のように書き換えてください.

void testApp::update() {
    velX += ofxAccelerometer.getForce().x;
    velY -= ofxAccelerometer.getForce().y;
    posX += velX;
    posY += velY;
    if (posX <  48) { posX =  48; velX *= -0.9; }
    if (posX > 271) { posX = 271; velX *= -0.9; }
    if (posY <  48) { posY =  48; velY *= -0.9; }
    if (posY > 431) { posY = 431; velY *= -0.9; }
    velX *= 0.99;
    velY *= 0.99;
}

さらに発展課題: 赤丸にタッチしたときに,ランダムな方向に飛んでいくようにします.testApp::touchDown() を以下のように書き換えてください.

void testApp::touchDown(ofTouchEventArgs &touch) {
    if ((abs(touch.x - posX) < 48) && (abs(touch.y - posY) < 48) {
        float dir = ofRandom(0.0, 2.0 * PI);
        velX = 15.0 * cos(dir);
        velY = 15.0 * sin(dir);
    }
}

タッチ位置が赤丸の内部であるときに,ofRandom(min, max) によって 0〜2π の乱数を発生させ,それを方向とする長さ 15.0 のベクトルを新しい速度としています.

効果音を使ったアプリを作ってみる

上で制作した「加速度センサを使ったアプリ」に効果音を入れてみましょう.あらかじめ音ファイルとして与えておいた効果音を,赤丸が壁に当たったときに再生するというものです.

【CAFF 形式への変換】

ここでは,Mac OS X 付属の効果音から Frog.aiff(System > Library > Sounds > Frog.aiff)を使います.このファイルをデスクトップにコピーし,ターミナル(Applications > Utilities > Terminal.app)から以下のように打ち込みます.

/usr/bin/afconvert -f caff -d LEI16 ~/Desktop/Frog.aiff

これで iPhone に適した音形式に変換された音ファイル Frog.caf がデスクトップに作成されます.これをドラッグし,Xcode の Project ウィンドウの左側にある Groups & Files の data フォルダに入れます.すると以下のようなダイアログが表示されますので,上段にある "Copy ..." にチェックを入れて,下段の "Add" ボタンを押します.これで,プロジェクトに音ファイルが登録されました.

なお,Windows をターゲットとする場合,音データは bin/data フォルダの中に入れるだけでよいです.詳しくは,「Windows/Mac 編 / iPhone アプリ道場を参考にする場合の注意事項」を参照してください.

【簡単な効果音アプリ:第1段階】

音ファイルをロード・再生するには,ofSoundPlayer クラスのインスタンスを用意する必要があります.ここでは,sound という名前でインスタンスを生成しています.この sound に対して,testApp::setup() の中で,音ファイル Frog.caf をロードするために,sound.loadSound("Frog.caf") を呼び出しています.

この音ファイルを再生するのは,testApp::update() の中で,壁との衝突を判定している部分です.壁との衝突速度に応じて音量(0〜1)を sound.setVolume() によって調節しています.ofClamp(val, min, max) は,val の値が min〜max の範囲にある場合は val をそのまま返し,min を下回っていれば min を,max を上回っていれば max を返す関数です.その後,sound.play() によってロードしてあった効果音が設定した音量で再生されます.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
private:
    float posX, posY;
    float velX, velY;
    ofSoundPlayer sound;
public:
    (... 変更なし ...)
};
testApp.mm
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
    sound.loadSound("Frog.caf");
}

void testApp::update() {
    velX += ofxAccelerometer.getForce().x;
    velY -= ofxAccelerometer.getForce().y;
    posX += velX;
    posY += velY;
    if (posX <  48) {
        sound.setVolume(ofClamp(0.1 * abs(velX), 0, 1));
        sound.play();
        posX =  48; velX *= -0.9; 
    }
    if (posX > 271) {
        sound.setVolume(ofClamp(0.1 * abs(velX), 0, 1));
        sound.play();
        posX = 271; velX *= -0.9; 
    }
    if (posY <  48) {
        sound.setVolume(ofClamp(0.1 * abs(velY), 0, 1));
        sound.play();
        posY =  48; velY *= -0.9;
    }
    if (posY > 431) {
        sound.setVolume(ofClamp(0.1 * abs(velY), 0, 1));
        sound.play();
        posY = 431; velY *= -0.9;
    }
    velX *= 0.99;
    velY *= 0.99;
}

void testApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void testApp::exit(){}

void testApp::touchDown(ofTouchEventArgs &touch) {
    if ((abs(touch.x - posX) < 48) && (abs(touch.y - posY) < 48) {
        float dir = ofRandom(0.0, 2.0 * PI);
        velX = 15.0 * cos(dir);
        velY = 15.0 * sin(dir);
    }
}

void testApp::touchMoved(ofTouchEventArgs &touch){}

void testApp::touchUp(ofTouchEventArgs &touch){}

(... 以下変更なし ...)
【簡単な効果音アプリ:第2段階】

複数の音を鳴らすことも簡単です.壁との衝突音 (Frog.caf) に加えて,赤丸をタッチしたときに別の効果音 (Glass.caf) を鳴らすようにしてみます.上の例と同じように,Glass.aiff を Glass.caf に変換し,プロジェクトに取り込んでください.

複数の音を扱うには,必要な数だけ ofSoundPlayer のインスタンスを用意します.それぞれのインスタンスについて,音ファイルをロードしたり,音量設定・再生などを行なうことができます.

testApp.h
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
private:
    float posX, posY;
    float velX, velY;
    ofSoundPlayer sound, sound2;
public:
    (... 変更なし ...)
};
testApp.mm
#include "testApp.h"

void testApp::setup() {
    ofRegisterTouchEvents(this);
    ofxAccelerometer.setup();
    ofxiPhoneAlerts.addListener(this);
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
    sound.loadSound("Frog.caf");
    sound2.loadSound("Glass.caf");
    sound2.setVolume(0.5);
}

(... 変更ないので中略 ...)

void testApp::touchDown(ofTouchEventArgs &touch) {
    if ((abs(touch.x - posX) < 48) && (abs(touch.y - posY) < 48) {
        sound2.play();
        float dir = ofRandom(0.0, 2.0 * PI);
        velX = 15.0 * cos(dir);
        velY = 15.0 * sin(dir);
    }
}

(... 以下変更なし ...)
さらに先へ

標準的な openFrameworks の API 解説は http://www.openframeworks.cc/documentation/ にあります.薄いグレーで囲まれた API は,赤字で表示されたクラス(たとえば ofSoundPlayer)のインスタンスに対して呼び出せるメソッド(たとえば play() など)です.一方,白地に書かれている API は,そのまま使えます.API 名をクリックすると,より詳しい情報が表示されますので,プログラミングの参考にしてください.