小嶋秀樹 | 研究室
日本語 | English

スクリーンショットの一部が古くなっています.注意してください.

openFrameworks 入門編(1)

openFrameworks を利用して,Windows や Mac のデスクトップ上で動作するアプリを開発します.グラフィクスの操作や音の入出力,マウスやキーボード入力の扱いなど,インタラクティブなアプリケーションを制作するための基礎技術をマスターしていきましょう.ここでは記述を簡潔にするために Windows 上でのアプリ開発を想定して説明していきますが,Mac でも同じようにアプリ開発ができます.

openFrameworks の始めかた

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

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

新しく開発するアプリケーションは,OF_PATH(of_v0.8.0_vs_release フォルダへのパス)> apps > myApps > emptyExample のフォルダ全体を複製したものに変更を加えることで開発していきます.

ここ(道場)では,OF_PATH > apps > dojoApps というフォルダを作り,ここに emptyExample のフォルダをコピー & ペーストします.新しく得られたフォルダの名前を,たとえば dojoExample1 に変更します.

ここ(道場)では,作業を簡単にするため,プロジェクトフォルダの中にあるファイルの名前は,emptyExample.* のままで進めます.変更しないでください.(各プロジェクトは dojoApps の下に名前をつけて整理してください.プロジェクト名が日本文字やスペースを含んでいると正常に機能しないことがありますので,気をつけてください.)

【空っぽのプロジェクトをビルド・実行してみる】

コピー & ペーストしたプロジェクト dojoExample1 をビルドして実行してみましょう.VC++ (Visual Studio Express 2012) を起動し,スタートページの「プロジェクトを開く」またはメニューから「ファイル > 開く > プロジェクト/ソリューション」を選び,OF_PATH > apps > dojoApps > dojoExample1 > emptyExample.(vcxproj) を開きます.

プロジェクトを開いたら,VC++ ウィンドウ左端にあるソリューションエクスプローラー上で,emptyExample フォルダの▶印をクリックして,その中身を表示します.さらに src フォルダの中身を表示し,その中にある ofApp.cpp などの個別ファイルをダブルクリックしてください.右上側にエディタが展開し,ファイルの内容を確認したり編集したりすることができます.(ソリューションエクスプローラーが見当たらなくなったときはメニューの「表示 > その他ウィンドウ > ソリューションエクスプローラー」から呼び出してください.)

プロジェクトをビルド(コンパイル)し,実行するには,緑矢印のボタンをクリックします.ここ(道場)では,常に debug 構成でビルドすることにしましょう.

エラーがあれば,下部の「出力」ウィンドウにエラーメッセージが表示されます.エラーがなければ,自動的に実行され,以下のようなウィンドウ(何もない一面の灰色)が表示されるはずです.終了するには,この灰色ウィンドウで esc を打ちます.

Mac + Xcode の場合: Mac 上の Xcode から openFrameworks を利用する場合も,まず dojoApps フォルダを作成し,そこに myApps/emptyExample をコピーすることから始めます.

コピーした emptyExample は,本文での例と同じように,dojoExample1 のように名前を変更します.つぎに,その中にある emptyExample.xcodeproj をダブルクリックして Xcode を起動します.

Xcode ウィンドウの左端カラムから「emptyExample」を選択し,右端カラムに現れた Identity 欄を "emptyExample" から "dojoExample1" に書き換えます.スナップショットを作るかどうか聞いてきますので,ハードディスク容量に余裕があれば作ってください.作らなくてもおおむね問題ありません.これで準備完了です.あとは "Run" ボタンを押してビルド+実行し,灰色ウィンドウが出ることを確かめてください.終了させるには,esc を押します.

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

空っぽの dojoExample1 プロジェクトに C++ で機能を書き加えていくことで,簡単かつインタラクティブなアプリを作成していきます.

【プログラムの構造】

VC++ のウィンドウの左側にあるソリューションエクスプローラー(もし見当たらなければメニューの「表示 > その他ウィンドウ > ソリューションエクスプローラー」)で,emptyExample > src を展開し,ofApp.h と ofApp.cpp をダブルクリックし,右側のエディタ領域に表示してください.(下部にある「出力」ウィンドウは閉じてしまって構いません.)

これら2のファイル(ofApp.hofApp.cpp)が,当座のあいだ,私たちが直接扱うファイルです.ソリューションエクスプローラー上でこれらファイル名をダブルクリックすることで,右側のエディタ領域に呼び出すことができます.エディタ領域では,タブを選択することで,個々のファイルを参照したり編集したりすることができます.編集結果をセーブするには,ctrl-S を打てばよいでしょう.

これら2つのファイル(オリジナルから一部の空行やコメントを削除してあります)を見てみてください.(C++ では // から行末までがコメントです.また C スタイルの /* ... */ もコメントとして使えます.)

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
public:
    void setup();
    void update();
    void draw();
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y);
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup () {}
void ofApp::update () {}
void ofApp::draw () {}
void ofApp::keyPressed (int key) {}
void ofApp::keyReleased (int key) {}
void ofApp::mouseMoved (int x, int y) {}
void ofApp::mouseDragged (int x, int y, int button){}
void ofApp::mousePressed (int x, int y, int button) {}
void ofApp::mouseReleased (int x, int y, int button){}
void ofApp::windowResized (int w, int h){}
void ofApp::dragEvent (ofDragInfo dragInfo) {}
void ofApp::gotMessage (ofMessage msg) {}

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

メソッド名から推察できるように,ofApp::setup() はアプリケーションの初期化(変数の初期設定など)に使います.ofApp::update() は状態更新(座標計算などにより変数の値を更新するなど)を担当し,ofApp::draw() は画面・インタフェースの表示を担当します.(デフォルトでは,ofApp::draw() が呼び出される直前に画面が自動クリアされます.)

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

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

ofApp.cpp
dojoExample1 window
#include "ofApp.h"
void ofApp::setup () {
    //  600x400, 30 frames/sec
    ofSetWindowShape(600, 400);
    ofSetFrameRate(30);
    //  white background
    ofSetBackgroundColor(255, 255, 255);
}
void ofApp::update () {
}
void ofApp::draw () {
    //  red circle (filled)
    ofSetColor(215, 19, 69);
    ofDrawCircle(300, 200, 120);
}
(... 以下変更なし ...)

ofSetWindowShape(w, h) でウィンドウの大きさを設定します.単位は画面上のピクセルです.ofSetFrameRate(n) で update/draw の繰り返し周期を毎秒 n 回に設定します.ただし,update/draw の処理に時間がかかる場合は,指定した周期になるとは限りません.また,ofSetBackgroundColor(r, g, b) は背景色を変更するための関数です.RGB それぞれの成分値を 0〜255 の整数で与えます.ofApp::draw() が呼び出される直前に,この背景色で画面がクリアされます.

ofSetColor(r, g, b) は描画色(前景色)を設定するための関数です.ofDrawCircle(x, y, r) は,中心を (x, y) とし半径を r とする円を描き,塗りつぶします.単位はピクセルで,左上端が (0, 0) となります.なお,塗りつぶさない場合は ofNoFill() を先に呼び出します.それ以降は ofSetLineWidth(w) で指定した太さの線で輪郭のみが描かれます.デフォルトの塗りつぶすモードに戻すには ofFill() を呼び出します.

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

発展課題2: ofApp::draw() の中で ofNoFill() を加えることで,それ以降の描画(ofDrawCircle() など)は輪郭線のみとなります.逆に,ofFill() を実行すれば,それ以降は塗りつぶしとなります.デフォルトは塗りつぶしです.

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

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

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
private:
    float rad;
    float posX, posY;
public:
    void setup();
    void update();
    void draw();
    (... 変更なし ...)
};

ofApp.h のクラス定義の中に,private: 部分を書き加え,そこに内部変数を宣言することができます.ここでは,回転角度を表わす rad という実数変数や,赤丸の中心座標を表わす posX, posY という実数変数を加えました.

ofApp.cpp
dojoExample1 window
#include "ofApp.h"
void ofApp::setup () {
    //  600x400, 30 frames/sec
    ofSetWindowShape(600, 400);
    ofSetFrameRate(30);
    ofSetCircleResolution(64);
    //  white background
    ofSetBackgroundColor(255, 255, 255);
    //  init instance var
    rad = 0.0;
}
void ofApp::update () {
    //  circular motion
    rad += 0.05;
    posX = 300 + 150 * cos(rad);
    posY = 200 + 150 * sin(rad);
}
void ofApp::draw () {
    //  red circle (filled)
    ofSetColor(215, 19, 69);
    ofDrawCircle(posX, posY, 120);
}
(... 以下変更なし ...)

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.cpp のみからアクセスするだけなら private: の下に,外部からもアクセスできるようにするなら public: の下に置きます.

ofApp::draw() では,ofApp::update() によって計算された座標 (posX, posY) に,半径 120 の赤丸を描画しています.赤丸の中心座標が時計回りに動くはずです.

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

void ofApp::update () {
    rad += 0.01;
    posX = 300 + 150 * sin(rad * 5);
    posY = 200 + 150 * cos(rad * 6);
}

発展課題2: リサージュ図形が見えるように,赤丸を小さくして,かつ軌跡を残す(ofApp::draw() の直前に画面クリアしない)ようにしてみます.つぎのように変更してください.(まずは 5:6 という東西日本の「調和」を感じとってください.また,他の周期比(2:3 など)も試してみてください.また,丸の色や大きさを変更したり,rad の増加ステップを調整するなどして,美しいパターンを描いてください.フレームレートも変えてみましょう.)

dojoExample1 window
#include "ofApp.h"
void ofApp::setup () {
    //  600x400, 30 frames/sec
    ofSetWindowShape(600, 400);
    ofSetFrameRate(30);
    ofSetBackgroundAuto(false);
    //  white background
    ofSetBackgroundColor(255, 255, 255);
    //  init instance var
    rad = 0.0;
}
void ofApp::update () {
    (... 変更なし ...)
}
void ofApp::draw () {
    //  red circle (filled)
    ofSetColor(215, 19, 69);
    ofDrawCircle(posX, posY, 20);
}
(... 以下変更なし ...)

バグ情報: 一部の Windows 環境では,ofSetBackgroundAuto(false) を指定すると,画面がうまく表示できなくなることがあるようです.その場合は,この発展課題をスキップしてください.

マウスを使ったインタラクションの制作(1)

マウスのクリックやドラッグといった操作を読み取り,それに応答するアプリを作ります.まずは dojoExample1 をコピーして dojoExample2 を作りましょう.その他の必要な設定は,前章「openFrameworks の始めかた」のとおりです.

【簡単なマウスアプリ:第1段】

マウスクリックした位置に小さめの赤丸を描画するアプリケーションをつくります.ユーザによるマウス操作は,イベントを発生させます.イベントを受け取った ofApp では,対応するメソッドが自動的に実行されます.ここでは,マウスのボタンを押し下げたときに呼び出される ofApp::mousePressed() メソッドに,赤丸を描画するという応答を仕込みます.

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
private:
    float posX, posY;
public:
    void setup();
    void update();
    void draw();
    (... 変更なし ...)
};
ofApp.cpp
dojoExample1 window
#include "ofApp.h"
void ofApp::setup () {
    //  600x400, 30 frames/sec
    ofSetWindowShape(600, 400);
    ofSetFrameRate(30);
    //  white background
    ofSetBackgroundColor(255, 255, 255);
    //  init instance var
    posX = 300;  posY = 200;
}
void ofApp::update () {
}
void ofApp::draw () {
    //  red circle (filled)
    ofSetColor(215, 19, 69);
    ofDrawCircle(posX, posY, 20);
}
(... 途中変更なし ...)
void ofApp::mousePressed (int x, int y, int button) {
    posX = x;  posY = y;
}
(... 以下変更なし ...)

プログラムは簡単です.マウスクリックによるイベントによって呼び出されるメソッド ofApp::mousePressed() には,引数としてマウスポインタの座標 (x, y) とボタン状態 button(左:0, 中:1, 右:2)が伝えられます.ここでは,座標 (x, y) を内部変数 posX, posY に代入しています.ofApp::draw() では,この座標に半径 20 の赤丸を描画しています.

発展課題: マウスをドラッグしたときも,この赤丸が追従するように改造してみましょう.利用するメソッドは ofApp::mouseDragged() です.マウスが(ボタンを押された状態で)動いたとき,このメソッドが呼び出されます.

void ofApp::mouseDragged (int x, int y, int button) {
    posX = x;  posY = y;
}

これだけでアプリの応答は質的に大きく変わります.追従性がイマイチの場合は,ofApp::setup() の中の ofSetFrameRate() の設定を(たとえば毎秒 60 回に)変更してみてください.

【簡単なマウスアプリ:第2段】

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

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
private:
    float posX, posY;
    float goalX, goalY;
public:
    (... 変更なし ...)
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup () {
    //  600x400, 30 frames/sec
    ofSetWindowShape(600, 400);
    ofSetFrameRate(30);
    //  white background
    ofSetBackgroundColor(255, 255, 255);
    //  init instance var
    posX = 300;  posY = 200;
    goalX = posX;  goalY = posY;
}
void ofApp::update () {
    //  mouse movement
    float difX, difY;
    difX = goalX - posX,
    difY = goalY - posY;
    //  update circle position
    posX += difX * 0.05;
    posY += difY * 0.05;
}
void ofApp::draw () {
    ofSetColor(215, 19, 69);
    ofDrawCircle(posX, posY, 20);
}
void ofApp::mouseDragged (int x, int y, int button) {
    goalX = x;  goalY = y;
}
void ofApp::mousePressed (int x, int y, int button) {
    goalX = x;  goalY = y;
}
(... 以下変更なし ...)

発展課題1: ofApp::update() の中にある定数 0.05 はマウス位置への追従性を決めるパラメータです.値を大きくすれば追従性が良くなります.1.0 を越えるとプルンとした感じになります.

発展課題2: リアルなプルン感をつくりだすにはどうすればよいでしょうか.マウスと赤丸の間に「バネ」が存在すると仮定し,それら位置の差分(距離)に比例した力が赤丸にかかり,その力によって速度が変わるようにプログラムします.2つの定数(バネ定数 0.1 と摩擦逆定数 0.9)を調節して,好みのプルン感をつくってください.こじま先生の好みは 0.2 / 0.8 くらいでしょうか.

ofApp.cpp
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp{
private:
    float posX, posY;
    float goalX, goalY;
    float velX, velY;
public:
(... 以下変更なし ...)
ofApp.cpp
#include "ofApp.h"
void ofApp::setup () {
    //  600x400, 30 frames/sec
    ofSetWindowShape(600, 400);
    ofSetFrameRate(60);
    //  white background
    ofSetBackgroundColor(255, 255, 255);
    //  init instance var
    posX = 300;  posY = 200;
    goalX = posX;  goalY = posY;
    velX = 0, velY = 0;
}
void ofApp::update () {
    //  mouse: dif -> acc
    float difX = goalX - posX, 
          difY = goalY - posY;
    //  acceleration: force -> vel
    velX += difX * 0.1;
    velY += difY * 0.1;
    //  friction
    velX *= 0.9;
    velY *= 0.9;
    //  motion: vel -> pos
    posX += velX;
    posY += velY;
}
void ofApp::draw () {
    ofSetColor(215, 19, 69);
    ofDrawCircle(posX, posY, 20);
}
(... 以下変更なし ...)

発展課題3: ofApp::mouseMoved() を有効にすれば,ボタンを押さなくてもマウスを動かすだけでプルンプルンさせることができます.

void ofApp::mouseMoved (int x, int y) {
    goalX = x;  goalY = y;
}
マウスを使ったインタラクションの制作(2)

引き続きマウス操作を使ったアプリを作ります.まずは dojoExample1 をコピーして dojoExample3 を作りましょう.その他の必要な設定は,前々章「openFrameworks の始めかた」のとおりです.

【簡単なマウスアプリ:第3段】

赤丸をマウスで「弾く」と,その勢いで赤丸がウィンドウの端で反射しながら動きまわるアプリをつくります.赤丸1つのビリヤードの雰囲気です.マウスボタンを押した位置に赤丸を固定し,ドラッグすることで仮想的なゴムを引き伸ばし,マウスボタンを離すとそのゴムで赤丸が弾かれるという仕組みを考えます.

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
private:
    float posX, posY;
    float velX, velY;
public:
    (... 変更なし ...)
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup () {
    //  600x400, 30 frames/sec
    ofSetWindowShape(600, 400);
    ofSetFrameRate(60);
    ofSetCircleResolution(64);
    //  white background
    ofSetBackgroundColor(255, 255, 255);
    //  init instance var
    posX = 300; posY = 200;
    velX = 0; velY = 0;
}
void ofApp::update () {
    posX += velX;
    posY += velY;
    //  bouncing againt the walls
    if (posX <  20) { posX =  20; velX *= -0.9; }
    if (posX > 579) { posX = 579; velX *= -0.9; }
    if (posY <  20) { posY =  20; velY *= -0.9; }
    if (posY > 379) { posY = 379; velY *= -0.9; }
    //  friction
    velX *= 0.99;
    velY *= 0.99;
}
void ofApp::draw() {
    ofSetColor(215, 19, 69);
    ofDrawCircle(posX, posY, 20);
}
void ofApp::mousePressed (int x, int y, int button) {
    //  place the ball
    posX = x;
    posY = y;
    velX = velY = 0;
}
void ofApp::mouseDragged (int x, int y, int button) {
    //  stretch the rubber band
}
void ofApp::mouseReleased (int x, int y, int button) {
    //  launch (set initial velocity)
    velX = posX - x;
    velY = posY - y;
}
(... 以下変更なし ...)

マウスボタンを押し下げると,その位置 (posX, posY) に赤丸が固定されます.そこからマウスをドラッグし,少しだけ離れた位置 (x, y) でボタンを離すと,赤丸に初速度 (velX, velY) = (posX - x, posY - y) が与えられます.

赤丸の動き(毎フレームごとの位置)を更新しているのは ofApp::update() です.まず赤丸の現在位置 (posX, posY) に速度 (velX, velY) を加え,赤丸の位置を更新します.これがウィンドウの境界を越えてしまった場合,その境界位置で速度を反転(−0.9 倍)させています.これは弾性反射をシミュレートしたものです.また,繰り返しのステップごとに速度を 1% だけ減らしているため,赤丸はやがて止まります.これは摩擦をシミュレートしたものです.

【簡単なマウスアプリ:第4段】

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

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
private:
    float posX, posY;
    float velX, velY;
    float mouseX, mouseY;
public:
    (... 変更なし ...)
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup () {
    //  600x400, 30 frames/sec
    ofSetWindowShape(600, 400);
    ofSetFrameRate(60);
    ofSetCircleResolution(64);
    //  white background
    ofSetBackgroundColor(255, 255, 255);
    //  init instance var
    posX = 300; posY = 200;
    velX = 0; velY = 0;
    mouseX = 300, mouseY = 200;
}
void ofApp::update () {
    //  escape from the mouse!
    float difX = mouseX - posX,
          difY = mouseY - posY, 
          dist = sqrt(difX * difX + difY * difY);
    if (dist > 5.0) {
        float accX = -5.0 / dist * (difX / dist), 
              accY = -5.0 / dist * (difY / dist);
        velX += accX;
        velY += accY;
    }
    //  update the position
    posX += velX;
    posY += velY;
    //  bouncing againt the walls
    if (posX <  20) { posX =  20; velX *= -0.9; }
    if (posX > 579) { posX = 579; velX *= -0.9; }
    if (posY <  20) { posY =  20; velY *= -0.9; }
    if (posY > 379) { posY = 379; velY *= -0.9; }
    //  friction
    velX *= 0.99;
    velY *= 0.99;
}
void ofApp::draw () {
    ofSetColor(215, 19, 69);
    ofDrawCircle(posX, posY, 20);
}
void ofApp::mouseMoved (int x, int y) {
    mouseX = x; mouseY = y;
}
void ofApp::mousePressed (int x, int y, int button) {
}
void ofApp::mouseDragged (int x, int y, int button) {
}
void ofApp::mouseReleased (int x, int y, int button) {
}
(... 以下変更なし ...)

このアプリでは,赤丸は「マウスとの距離に反比例した恐怖感」をもち,その恐怖感に比例した加速度でマウスから遠ざかろうとすると仮定しています.ofApp::update() では,マウス位置と赤丸位置の X, Y 成分ごとの差分 difX, difY を計算し,そこから計算した加速度を velX, velY に足し込んでいます.

発展課題: この意地悪なマウスが,ボタンが押されたときだけ「よいマウス」に変身し,赤丸がマウスに近づこうとするようにします.

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp{
private:
    float posX, posY;
    float velX, velY;
    float mouseX, mouseY;
    bool nasty;
public:
(... 以下変更なし ...)
ofApp.cpp
#include "ofApp.h"
void ofApp::setup() {
    (... 変更なし ...)
    nasty = true;
}
void ofApp::update() {
    //  escape from the mouse!
    float difX = mouseX - posX,
          difY = mouseY - posY, 
          dist = sqrt(difX * difX + difY * difY);
    if (dist > 5.0) {
        float accX = -5.0 / dist * (difX / dist), 
              accY = -5.0 / dist * (difY / dist);
        if (nasty) {
            velX += accX;  velY += accY;
        }
        else {
            velX -= accX;  velY -= accY;
        }
    }
    //  update the position
    (... 変更なし ...)
}
(... 変更なし ...)
void ofApp::mousePressed (int x, int y, int button) {
    mouseX = x; mouseY = y; nasty = false;
}
void ofApp::mouseDragged (int x, int y, int button) {
    mouseX = x; mouseY = y;
}
void ofApp::mouseReleased (int x, int y, int button) {
    mouseX = x; mouseY = y; nasty = true;
}
マウスアプリに効果音を加える

上で作成した「簡単なマウスアプリ:第4段」に効果音を入れてみましょう.あらかじめ音ファイルとして与えておいた効果音を,赤丸が壁に当たったときに再生するというものです.

【効果音を用意する】

ここでは,Windows 標準付属と思われる効果音ファイルを拝借します.ローカルディスク(C:) > Windows > Media > Windows ベル という wav ファイルをコピーし,それを OF_PATH > apps > dojoApps > dojoExample3 > bin の中に data というフォルダを新規作成し,そこにペーストし ding(.wav) というファイル名に変更します.

Mac + Xcode の場合: Mac OS X 付属の効果音から Frog.aiff(System > Library > Sounds > Frog.aiff)をコピーし,dojoExample3 > bin > data の中にペーストしてください.(以下,音ファイル名は適宜読み替えてください.)

【簡単な効果音アプリ】

音ファイルをロード・再生するには,ofSoundPlayer クラスを利用します.ここでは,sound という名前でインスタンスを生成しています.この sound に対して,ofApp::setup() の中で,音ファイル ding.wav をロードするために,sound.load("ding.wav") を呼び出しています.

この音ファイルを再生するのは,ofApp::update() の中で,壁との衝突したときに実行される部分にあります.すでにロードしておいた効果音を,sound.play() によって再生しています.

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
private:
    float posX, posY;
    float velX, velY;
    float mouseX, mouseY;
    bool nasty;
    ofSoundPlayer sound;
public:
    (... 変更なし ...)
};
ofApp.cpp
#include "ofApp.h"
void ofApp::setup () {
    (... 変更なし ...)
    //  load sound
    sound.load("Frog.caf");
}
void ofApp::update () {
    (... 変更なし ...)
    //  bouncing againt the walls
    if (posX <  20) { posX =  20; velX *= -0.9; sound.play(); }
    if (posX > 579) { posX = 579; velX *= -0.9; sound.play(); }
    if (posY <  20) { posY =  20; velY *= -0.9; sound.play(); }
    if (posY > 379) { posY = 379; velY *= -0.9; sound.play(); }
    //  friction
    velX *= 0.99;
    velY *= 0.99;
}
(... 以下変更なし ...)

発展課題1: というか,バグ取りです.赤丸が壁に接触しつづけている間,効果音が連続的に出てしまいます.おそらく音を出すには,ある程度の速度で壁に衝突した場合に限ったほうがよいでしょう.

void ofApp::update () {
    (... 変更なし ...)
    //  bouncing againt the walls
    if (posX <  20) {
        posX =  20;
        if (velX < -1.0) sound.play();
        velX *= -0.9;
    }
    if (posX > 579) {
        posX = 579;
        if (velX >  1.0) sound.play();
        velX *= -0.9;
    }
    if (posY <  20) {
        posY =  20;
        if (velY < -1.0) sound.play();
        velY *= -0.9;
    }
    if (posY > 379) {
        posY = 379;
        if (velY >  1.0) sound.play();
        velY *= -0.9;
    }
    //  friction
    velX *= 0.99;
    velY *= 0.99;
}
(... 以下変更なし ...)

発展課題2: さらに,衝突速度に応じて音量を変化させるとよいですね.音量の設定には,sound.setVolume() が使えます.0.0〜1.0 の実数値で音量を与えます.

void ofApp::update () {
    (... 変更なし ...)
    //  bouncing againt the walls
    if (posX <  20) {
        posX =  20;
        if (velX < -1.0) {
            sound.setVolume(ofClamp(0.1 * fabs(velX), 0.0, 1.0));
            sound.play();
        }
        velX *= -0.9;
    }
    if (posX > 579) {
        posX = 579;
        if (velX >  1.0)  {
            sound.setVolume(ofClamp(0.1 * fabs(velX), 0.0, 1.0));
            sound.play();
        }
        velX *= -0.9;
    }
    if (posY <  20) {
        posY =  20;
        if (velY < -1.0)  {
            sound.setVolume(ofClamp(0.1 * fabs(velY), 0.0, 1.0));
            sound.play();
        }
        velY *= -0.9;
    }
    if (posY > 379) {
        posY = 379;
        if (velY >  1.0)  {
            sound.setVolume(ofClamp(0.1 * fabs(velY), 0.0, 1.0));
            sound.play();
        }
        velY *= -0.9;
    }
    //  friction
    velX *= 0.99;
    velY *= 0.99;
}
(... 以下変更なし ...)

ここでは壁に垂直な成分の速度に 0.1 を乗じた値を音量として設定しています.なお ofClamp(val, min, max) は,val が min 未満ならば min を値とし,max 以上ならば max を値とし,それ以外の場合は val をそのまま値とする関数です.

発展課題3: ofApp::update() がややこしくなってきたので整理したいと思います.効果音を出す部分を抜き出せば,つぎのように簡潔に記述できます.

ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
private:
    float posX, posY;
    float velX, velY;
    float mouseX, mouseY;
    bool nasty;
    ofSoundPlayer sound;
    void playSound(float vol);
public:
    (... 変更なし ...)
};
ofApp.cpp
(... 変更なし ...)
void ofApp::playSound (float vel) {
    float fvel = fabs(vel);
    if (fvel > 1.0) {
        sound.setVolume(ofClamp(0.1 * fvel, 0.0, 1.0));
        sound.play();
    }
}
void ofApp::update () {
    (... 変更なし ...)
    //  bouncing againt the walls
    if (posX <  20) {
        posX =  20;
        playSound(velX);
        velX *= -0.9;
    }
    if (posX > 579) {
        posX = 579;
        playSound(velX);
        velX *= -0.9;
    }
    if (posY < 20) { 
        posY =  20;
        playSound(velY);
        velY *= -0.9; 
    }
    if (posY > 379) { 
        posY = 379;
        playSound(velY);
        velY *= -0.9; 
    }
(... 変更なし ...)
その先へ

お疲れさまです.このページの例題や発展課題をしっかり復習したら,つぎは「openFrameworks 入門(2)」に進んでください.より高度のグラフィクス処理などを解説します.

openFrameworks についての詳しい解説は「公式ドキュメント」のページにあります.英語ですが,まずはここを参照するとよいでしょう.日本語の「チュートリアル」もあります.適宜,参照してください.

自習のためのキーワード: