yoppa.org


メディア芸術演習 VI - メディア・アート II

openFrameworks – プログラムの制御構造

サンプルファイルのダウンロード

授業で解説したプログラムは下記からダウンロードしてください。

openFrameworksを使用した作品の紹介

openFrameworksを使用するとどんなことができるのか、自身の作品制作に向けてのイメージを膨らませるために、まずはいろいろな作品の実例を紹介します。

インスタレーション

パブリッシング

パフォーマンス

モバイル

プログラムの制御構造

制御構造は、プログラミング言語において、文や命令を実行する順番を意味しています。プログラミングでアルゴリズムを生成したり、図形やアニメーションを描いたり、特定の動作(イベント)に対する反応を定義したりといった様々な命令を実行する際に、プログラミングの制御構造を正しく理解することが理解の際の重要なポイントとなります。

逆にいうと、今日学ぶプログラミングの基本となる4つの制御構造さえ理解すれば、プログラミングに関しての半分以上は理解できたと考えることも可能でしょう。また、今回は基本となる制御構造をopenFramweworksを実例として(つまりC++の文法にのっとって)学んでいきますが、この基本の制御構造自体は、ほとんどのプログラミング言語で共通するものです。

プログラムの制御構造をしっかり理解することで、他の言語での開発をする際にも、すぐに知識を応用することが可能となるでしょう。

プログラミング、4つの制御構造

今日学ぶ制御構造は以下の4つのみです。しかし、この基本となる4つの制御構造で、プログラミングのほとんどの構造を表現することが可能となります。

  1. 継続 – 順番に実行する
  2. 条件分岐 – 「もし〜だったら…せよ」
  3. ループ – くりかえし
  4. 関数 – 特定の処理をまとめる、サブルーチン

以下では、openFrameworksを使用してプログラムの制御構造をビジュアルで理解することを試みます。実例を通して、制御構造についてしっかりと理解しましょう。

制御構造(1) – 継続

  • openFrameworksの(つまりC++の)プログラムは、基本的に上から下に実行される
  • 実行される順番が重要な意味を持つこともある

フローチャートで表現すると…

継続のサンプル 1. 順番に図形を描く

図形の重なり順に注意!!

testApp.cpp

[sourcecode language=”cpp”]
#include "testApp.h"

void testApp::setup(){
ofBackground(0, 0, 0);
ofSetCircleResolution(64); //円の解像度
}

void testApp::update(){

}

void testApp::draw(){
//長方形
ofSetColor(127, 127, 127);
ofRect(300, 300, 250, 250);

//円1
ofSetColor(255, 63, 31);
ofCircle(500, 300, 120);

//円2
ofSetColor(31, 63, 255);
ofCircle(600, 400, 80);
}
/* 《後略》 */
[/sourcecode]

継続のサンプル 2. 増殖する円

testApp.cpp

[sourcecode language=”cpp”]
#include "testApp.h"

void testApp::setup(){
ofBackground(0, 0, 0);
ofSetBackgroundAuto(false); //背景を更新しない
ofSetCircleResolution(64); //円の解像度
}

void testApp::update(){

}

void testApp::draw(){
//円を描く点をランダムに設定
ofPoint pos;
pos.set(ofRandom(0, ofGetWidth()), ofRandom(0, ofGetHeight()));

//円の半径もランダムに
float radius = ofRandom(1, 10);

//色もランダムに
ofSetColor(ofRandom(0, 255), ofRandom(0, 255), ofRandom(0, 255));

//設定した場所と半径で、円を描く
ofCircle(pos.x, pos.y, radius);
}
/* 《後略》 */
[/sourcecode]

継続のサンプル 3. アニメーション

アニメーションもdraw()による継続機能を使用している。

  • drawはofSetFramerate()で指定した間隔で、update() と draw() を行う
  • 後述するループとの差異について注意
  • 座標の値を継続して変更することによるアニメーションを作成してみる

testApp.cpp

[sourcecode language=”cpp”]
#include "testApp.h"

//円の座標を記録するための、クラス変数
ofPoint pos;

void testApp::setup(){
ofBackground(0, 0, 0);
ofSetFrameRate(60);
ofSetCircleResolution(64); //円の解像度

//座標を初期化
pos.x = 0;
pos.y = 0;
}

void testApp::update(){
//座標の更新
pos.x += 0.3;
pos.y += 0.2;
}

void testApp::draw(){
//設定した座標に、円を描く
ofSetColor(31, 127, 255);
ofCircle(pos.x, pos.y, 40);
}
/* 《後略》 */
[/sourcecode]

制御構造(2) – 条件分岐

条件分岐

パターン1:「もしXXならば、***しなさい」→ if文
パターン2:「もしXXならば、***しなさい、そうでなければ—しなさい」→ if-else文

if文の基本文法

[sourcecode language=”cpp”]
if (条件式) {
真文
}
[/sourcecode]

フローチャートで表現すると…


if文 (左)、if-else文 (右)

条件分岐のサンプル

座標のアニメーションを改造。画面の端にくると、反対側の端から出現する。

[sourcecode language=”cpp”]
#include "testApp.h"

//円の座標を記録するための、クラス変数
ofPoint pos;

void testApp::setup(){
ofBackground(0, 0, 0);
ofSetFrameRate(60);
ofSetCircleResolution(64); //円の解像度

//座標を初期化
pos.x = 0;
pos.y = 0;
}

void testApp::update(){
//座標の更新
pos.x += 3;
pos.y += 2;

//画面の端にきたら、反対がわから出るように条件分岐を設定
//左、右、上、下それぞれの場合で条件分けする

//左
if (pos.x < 0) {
pos.x = ofGetWidth();
}
//右
if (pos.x > ofGetWidth()) {
pos.x = 0;
}
//上
if (pos.y < 0) {
pos.y = ofGetHeight();
}
//下
if (pos.y > ofGetHeight()) {
pos.y = 0;
}
}

void testApp::draw(){
//設定した座標に、円を描く
ofSetColor(31, 127, 255);
ofCircle(pos.x, pos.y, 40);
}
/* 《後略》 */
[/sourcecode]

条件分岐 – クイズ

物体が画面の端に来たときに、反対側から出てくるのではなく、バウンドするようにするにはどうすれば良いか?

  • 条件分岐の部分を変更する
  • 2D空間を運動する物体の、バウンドを表現するには、どのような演算をすれば良いのか?

制御構造(3) – 繰り返し (ループ)

くりかえしを実現するには、for文を用いる

for文の基本文法

[sourcecode language=”cpp”]
for(初期化; ループの継続条件; カウンタ変数の更新) {

}
[/sourcecode]

フローチャートで表現すると…

ループ(繰り返し)を使用したプログラムで、大量の変数を格納する際には、配列(Array)を使用すると便利

  • 配列 = 変数のロッカーのようなもの、
  • 同じ型の変数(箱)が、コンピュータのメモリに順番に生成される
  • それぞれの要素には、区別するための連番の数字(添字)が付与されている

繰り返しと配列を使用したサンプル

繰り返しと配列を使用して、一度にたくさんの図形を動かしてみる。

testApp.cpp

[sourcecode language=”cpp”]
#include "testApp.h"
#define NUM 100 //定数NUMを100と定義する

//円の座標を記録するための、クラス変数をNUM個の配列で定義
ofPoint pos[NUM];
//円のスピードを記録するための、クラス変数NUM個を定義
ofPoint speed[NUM];

void testApp::setup(){
ofBackground(0, 0, 0);
ofSetFrameRate(60);
ofSetCircleResolution(64); //円の解像度

//for文で繰り返すことで、全ての配列の座標を初期化
for (int i = 0; i < NUM; i++) {
//画面の中でランダムな位置に初期化
pos[i].set(ofRandom(0, ofGetWidth()), ofRandom(0, ofGetHeight()));
//スピードを初期化
speed[i].set(ofRandom(-4, 4), ofRandom(-4, 4));
}
}

void testApp::update(){
//座標を、for文の繰り返しで全て更新
for (int i = 0; i < NUM; i++) {
//座標更新
pos[i] += speed[i];

//画面の端にきた場合,バウンドする
//左右
if (pos[i].x < 0 || pos[i].x > ofGetWidth()) {
speed[i].x *= -1;
}
//上下
if (pos[i].y < 0 || pos[i].y > ofGetHeight()) {
speed[i].y *= -1;
}
}
}

void testApp::draw(){
//配列に格納された全ての座標に円を描く
for (int i = 0; i < NUM; i++) {
ofSetColor(31, 127, 255);
ofCircle(pos[i].x, pos[i].y, 20);
}
}
/* 《後略》 */
[/sourcecode]

繰り返し クイズ

同時にアニメーションする円の大きさ、色をランダムにしてみる

制御構造(4) – 関数 (サブルーチン)

  • 関数プログラム中で意味や内容がまとまっている作業をひとつの手続きとしたもの、サブルーチン
  • openFrameworks (C++) では「関数 (function)」という呼び方が一般的

引数と戻り値

  • 関数へ渡す値(パラメータ) – 引数 (ひきすう, argument)
  • 関数が返す値 – 戻り値(return value)

関数の、「引数」と「戻り値」のイメージ

関数を利用したサンプル

アニメーションの色がフェードする機能を、関数化する

関数を定義する

関数名:

  • ofFadeColor()

引数:

  • 引数1:フェードする色のR成分 (int)
  • 引数2:フェードする色のG成分 (int)
  • 引数3:フェードする色のB成分 (int)
  • 引数4:フェードの透明度 (int)

戻り値:

  • なし

testApp.h

[sourcecode language=”cpp”]
#ifndef _TEST_APP
#define _TEST_APP

#include "ofMain.h"

class testApp : 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 ofFadeColor(int r, int g, int b, int a);
};

#endif
[/sourcecode]

testApp.cpp

[sourcecode language=”cpp”]
#include "testApp.h"
#define NUM 100 //定数NUMを100と定義する

//円の座標を記録するための、クラス変数をNUM個の配列で定義
ofPoint pos[NUM];
//円のスピードを記録するための、クラス変数NUM個を定義
ofPoint speed[NUM];

void testApp::setup(){
ofBackground(0, 0, 0);
ofSetFrameRate(60);
ofSetCircleResolution(64); //円の解像度

//for文で繰り返すことで、全ての配列の座標を初期化
for (int i = 0; i < NUM; i++) {
//画面の中でランダムな位置に初期化
pos[i].set(ofRandom(0, ofGetWidth()), ofRandom(0, ofGetHeight()));
//スピードを初期化
speed[i].set(ofRandom(-4, 4), ofRandom(-4, 4));
}
}

void testApp::update(){
//座標を、for文の繰り返しで全て更新
for (int i = 0; i < NUM; i++) {
//座標更新
pos[i] += speed[i];

//画面の端にきた場合,バウンドする
//左右
if (pos[i].x < 0 || pos[i].x > ofGetWidth()) {
speed[i].x *= -1;
}
//上下
if (pos[i].y < 0 || pos[i].y > ofGetHeight()) {
speed[i].y *= -1;
}
}
}

void testApp::draw(){
//色をフェードさせる関数を呼び出し
ofFadeColor(0, 15, 63, 10);

//配列に格納された全ての座標に円を描く
for (int i = 0; i < NUM; i++) {
ofSetColor(31, 127, 255);
ofCircle(pos[i].x, pos[i].y, 20);
}
}

//画面をフェードさせる関数
void testApp::ofFadeColor(int r, int g, int b, int a) { //引数と戻り値はなし
ofSetBackgroundAuto(false); //背景を書き換えない
ofEnableAlphaBlending(); //透明度を使用
ofSetColor(r, g, b, a); //フェードする色と透明度を設定
ofSetRectMode(OF_RECTMODE_CORNER); //四角形の描画モードをCORNERに
ofRect(0, 0, ofGetWidth(), ofGetHeight()); //画面全体に四角形を描く
}
/* 《後略》 */
[/sourcecode]

課題:アルゴリズムについて考えてみる – 円を描くには?

  • ofCircle や ofEllipse を用いずに円を描いてみる
  • 円を描くアルゴリズム
    • 一番真っ当な方法 – 三角関数を用いる方法
  • その他の方法で円を描くことは可能か?
  • その方法を、openFrameworksで表現してみる
  • 今日解説したプログラム構造(継続、条件分岐、ループ、サブルーチン)を駆使して考えてみる