小嶋秀樹 | 研究室
日本語 | English
openFrameworks iOS 編(1)

openFrameworks を利用して,iPhone / iPad / iPod touch のアプリを開発します.従来は,Objective-C から iOS 独自の API を利用する方法を習得する必要がありましたが,openFrameworks を利用することで,グラフィクス処理・音処理などを,一般的なデスクトップ(Mac OS や Windows など)のアプリケーションと同じ方法で,C++ を使って iOS アプリを開発することができます.

iOS 用の openFrameworks を利用するには,Mac + Xcode が必要となります.ここでは,既に,Mac 上での開発環境が準備できているものとして,iOS アプリ開発を解説していきます.必要に応じて,「openFrameworks の ios setup guide」を参照してください.利用する openFrameworks は iOS 用のものです.Windows や Mac のデスクトップ用のものとは異なりますので,of_v0.8.4_ios_release を別途ダウンロードしてください.今後,文中に出てくる OF_PATH は,openFrameworks のルートフォルダ(of_v0.8.4_ios_release)へのパスとします.

【openFrameworksLib のビルド】

まず,openFrameworksLib をビルドする必要があります.「openFrameworksLib のコンパイル」を参考に,openFrameworksLib をビルドしてください.Debug 環境で,Device と Simulator の2構成について,それぞれ Build > Build してください.

開発環境によっては,ビルドエラーが出る場合があります.その場合は,下記説明に従って,対処してください.

【サンプルプログラムで動作チェック】

準備ができたら,サンプルプログラムをビルドし,iOS Simulator(Mac 上で動作する iPhone/iPad シミュレータ)で動作を確認してみましょう.

これ以外にも examples > ios フォルダの中には,いろいろなアプリケーション例が入っています.いくつか試してみてください.ただし,シミュレータ環境では,加速度センサやカメラを使ったアプリは動作させることができません.

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

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

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

新しく開発するアプリケーションは,OF_PATH > apps > myApps > emptyExample のフォルダ全体を複製したものに変更を加えることで開発していきます.ここ(道場)では,OF_PATH > apps > myApps の中に,emptyExample のフォルダを複製し,たとえば dojoExample1 という名前に変更します.つぎに,dojoExample1 のフォルダに入り,その中にある emptyExample.xcodeproj をダブルクリックして Xcode を起動します.

Xcode ウィンドウの左側に,emptyExample という青いアイコンがあります.それをクリックすると,ウィンドウの中央と右側に,いろいろな設定画面が出ます.この中で,一番右上にある Identity and Type の Name 欄を,たとえば dojoExample1 に変更してください.Enter を押すとダイアログが開きますので,Rename を押してください.つぎに出てくるダイアログ(Disable/Enable の選択)はどちらを選んでもよいですが,ここでは Disable にしておきます.

ここまでできたら,Xcode ウィンドウの左上の青いアイコンが dojoExample1 に変わったことを確認して,その左にある小さな三角アイコンをクリックし,その内容を展開表示してください.展開された項目から src フォルダをさらに展開し,その中にある ofApp.mm を1回だけクリックしてください.これがアプリ本体のインプリメンテーションファイル(C++ で言うところの .cpp ファイル)です.ofApp.h はそのヘッダファイルとなります.

ためしに,Xcode ウィンドウ左上から,emptyExample と適切なシミュレータを選択して,Build and Run(右三角の再生ボタン)を押してみてください.ビルドの後,iOS Simulator 上で dojoExample1 が起動し,灰色一色に塗られたスクリーンが表示されるはずです.これが dojoExample1 アプリです.

【プログラムの構造】

Xcode ウィンドウの左側に,このプロジェクトに含まれるすべてファイルがグループごとに見えています.もし見えていなかったら,まずその上側にある「フォルダ型」のアイコンをクリックして,「右三角ボタン」をクリックしてグループを展開してください.たとえば src を展開すると,右上側の副ウィンドウには,main.mm, ofApp.h, ofApp.mm の3つのファイルのみが表示されます.なお,.mm は Mac OS X における C++(正確には Objective-C++)ファイルの拡張子です.Windows や Unix では .cpp が広く使われています.

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

ofApp.h
#pragma once
#include "ofMain.h"
#include "ofxiOS.h"
#include "ofxiOSExtras.h"

class ofApp : public ofxiOSApp{
    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);
};
ofApp.mm
#include "ofApp.h"
void ofApp::setup(){}
void ofApp::update(){}
void ofApp::draw(){}
void ofApp::exit(){}

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

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

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

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

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

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

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

void ofApp::setup() {
    ofxAccelerometer.setup();
    //  white background
    ofBackground(255, 255, 255);
}

void ofApp::update() {}

void ofApp::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() で設定できます.ofApp::setup() に ofSetCircleResolution(32) を入れて,その効果を確かめてください.

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

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

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

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

void ofApp::setup() {
    ofxAccelerometer.setup();
    ofBackground(255, 255, 255);
    ofSetFrameRate(30);
}

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

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

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

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

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

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

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

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

タッチスクリーンは iPhone や iPad を特徴づける入力デバイス(画面も含めれば入出力デバイス)です.その基本的な使い方をマスターしていきましょう.新しく dojoExample2 をつくってください.

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

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

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

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

void ofApp::setup() {
    ofxAccelerometer.setup();
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
}

void ofApp::update(){}

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

void ofApp::exit(){}

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

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

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

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

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

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

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

void ofApp::setup() {
    ofxAccelerometer.setup();
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    goalX = posX;
    goalY = posY;
}

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

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

void ofApp::exit(){}

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

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

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

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

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

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

void ofApp::setup() {
    ofxAccelerometer.setup();
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
}

void ofApp::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 ofApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void ofApp::exit(){}

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

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

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

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

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

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

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

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

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

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

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

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

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

void ofApp::setup() {
    ofxAccelerometer.setup();
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
    isTouching = FALSE;
}

void ofApp::update() {
    if (isTouching) {
        float difX = finX - posX,
              difY = finY - posY,
              len2 = difX * difX + difY * difY;
        if (len2 > 0) {
            velX += - 10.0 * difX / len2;
            velY += - 10.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 ofApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void ofApp::exit(){}

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

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

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

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

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

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

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

#define FINMAX 5

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

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

void ofApp::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 ofApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void ofApp::exit(){}

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

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

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

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

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

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

void ofApp::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 を特徴づける入力でバイスです.ここでは,その基本的な使い方をマスターしていきましょう.なお,加速度センサは iOS Simulator 上では使えません.実機で動かしてください.

【簡単な加速度アプリ】

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

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

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

void ofApp::setup() {
    ofxAccelerometer.setup();
    ofBackground(255, 255, 255);
    posX = 160;
    posY = 240;
    velX = 0;
    velY = 0;
}

void ofApp::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 ofApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void ofApp::exit(){}

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

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

void ofApp::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) を取り出すこともできます.

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

void ofApp::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;
}

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

void ofApp::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" ボタンを押します.これで,プロジェクトに音ファイルが登録されました.

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

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

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

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

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

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

void ofApp::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 ofApp::draw() {
    ofSetColor(255, 0, 0);
    ofCircle(posX, posY, 48);
}

void ofApp::exit(){}

void ofApp::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 ofApp::touchMoved(ofTouchEventArgs &touch){}

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

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

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

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

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

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

void ofApp::setup() {
    ofxAccelerometer.setup();
    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 ofApp::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);
    }
}

(... 以下変更なし ...)
【ofSoundStream の利用】

of 応用編 (2))」の「音メディアのあ使い方」に記述した ofSoundStream による音波形の入出力も,同じようにプログラミングできます.ただし,プライバシー保護が強化された iOS 10 以降でマイクを利用するには,ofxiOS-info.plist に1項目加えることが必要です.これを加えないとアプリは強制終了します.

この項目を追加するには,まず,Xcode 上で左端に Project Navigator を表示(View -> Show Project Navigator)して,ofxiOS-info.pilst を選択してください.中央に plist の内容が表示されます.Key 欄に "Privacy - Microphone ...” みたいな項目が無ければ,最上段 "Information Property List" 右側の「+」ボタンを押して,新しい行をつくり,そこに Key としてプルダウンメニューからこれを選んでください.Type は String で,Value は適当な文字列(たとえば「あなたの声を録音します」)でよいです.これで,最初に iPhone で動かそうとしたときに,マイクへのアクセス許可を求めるダイアログが iPhone 上に出るので「OK」を押します.それ以降は聞いてこないはずです.

さらに先へ

標準的な openFrameworks の API 解説は http://www.openframeworks.cc/documentation/ にあります.API 名やメソッド名をクリックすると,より詳しい情報が表示されますので,プログラミングの参考にしてください.