このページでは、openFrameworks上でどのようにオブジェクト指向プログラミングを行うかについて話をします。
この実習は、基本的にProcessingでのオブジェクト指向プログラミングのレジュメと同じ例題を利用しています。ですが、ProcessingとopenFrameworksは基本的なアルゴリズムは同じなのですが、書き方が結構違うので気をつけましょう。
オブジェクト指向プログラミングについては、Processingのページで大まかに説明していますので、そちらを参照してください。このレジュメは、オブジェクト指向についてある程度理解している人を対象にしています。
Processingでオブジェクト指向-1(クラス、メソッド)
Processingでオブジェクト指向-2(クラスの継承)
注意: このサンプルは2020年6月現在でのXcode最新版であるXcode11.5とopenFrameworks最新版であるof_v0.11.0_osx_releaseを使用しています。新しく作成するヘッダファイルの拡張子が.hppになっている場合がありますので、気をつけてください。
が、従来通りの拡張子(.h)でも動くと思います。
例題
それでは、まずはクラスを作ってみましょう。
以下は、横方向に設定されたスピードで移動する円を生成するクラスです。
まずは、クラスファイルを作成します。
Xcodeで、「Fileメニュー > New > File」を選びます。
以下のダイアログが出るので、左サイドバーの「OS X > Source」を選んで右側のファイルは「C++ File」を選びます。そして「Next」ボタンを押します。
以下のダイアログが出るので、Nameには「Circle」と入れます。この欄はクラス名になります。その際、「Alse create a header file」のチェックを入れておきます。そして「Next」ボタンを押します。
保存場所を確認してくるので、プロジェクトの「src」フォルダに保存しましょう。特に何も指定しないとsrcフォルダに保存されます。
プロジェクトの左サイドバーに「Crcle.cpp」と「Circle.hpp」が出てきます。これでクラスファイルの追加は完了です。
まず、Circle.hppにメソッドを登録します。
Circle.hpp
#ifndef Circle_hpp
#define Circle_hpp
#include "ofMain.h"
class Circle {
public:
void init(float _y, int _eSize, float _speed); //初期化メソッド
void update(); //更新
void display(); //描画
float xPos, yPos, eSize, speed;
private:
};
#endif
そして、Circle.cppに具体的にメソッドを書いていきます。
Circle.cpp
#include "Circle.hpp"
void Circle::init (float _y, int _eSize, float _speed) {
xPos = 0.0; //x座標の初期値は0.0
yPos = _y; //y座標の初期値はオブジェクトを宣言した際の引数になる
eSize = _eSize;
speed = _speed; //speedの初期値もオブジェクト宣言の際の引数
}
//円の位置を更新するメソッド
void Circle::update() {
xPos += speed; //speed分のx座標を移動
if (xPos > ofGetWidth()) { //円のx座標がウィンドウの幅を超えたら、
xPos = 0; //位置を0に戻す
}
}
void Circle::display(){
ofDrawCircle(xPos, yPos, eSize); //円を描く
}
ofApp.hでは、オブジェクトのための変数(メンバ変数)を宣言します。
ofApp.h
#pragma once
#include "ofMain.h"
#include "Circle.hpp"
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 mouseEntered(int x, int y);
void mouseExited(int x, int y);
void windowResized(int w, int h);
void dragEvent(ofDragInfo dragInfo);
void gotMessage(ofMessage msg);
Circle c1, c2; //ColorCircleクラスのオブジェクト(インスタンス)を宣言
};
最後に、ofApp.cpp内でクラスを利用してオブジェクトを生成します。
ofApp.cpp
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::setup(){
ofBackground(255, 255, 255);
ofSetCircleResolution(64);
c1.init(100, 10, 1.0); //オブジェクトを作成
c2.init(300, 40, 3.5);
}
//--------------------------------------------------------------
void ofApp::update(){
//c1オブジェクトのクラスであるCircleクラスのupdate(), display()メソッドを実行
c1.update();
c2.update();
}
//--------------------------------------------------------------
void ofApp::draw(){
ofSetColor(0, 0, 0);
c1.display();
c2.display();
}
openFrameworksの場合は、記述するファイルがかなり増えてしまうのが煩雑ですが、基本的にヘッダファイル(.hpp、.hファイル)にはオブジェクトで使う変数(メンバ変数)とメソッドを登録します。
どうでしょうか?c3、c4と、いくつもオブジェクトを増やしてみて下さい。
オブジェクトの配列化
さて、このように性格の違うオブジェクトを作成することに成功したら、もっと数を増やしたくなるというのが次の段階でしょう。
この場合には、配列を使います。
手順は今までのコードと殆ど変わらないのですが、配列のそれぞれの箱にオブジェクトを入れるのだと理解してください。
次の練習問題をやってみましょう。
同じクラスファイルを使うので、変更するのはofApp.hとofApp.cppのみです。
ofApp.hでは、オブジェクトのための変数(メンバ変数)を宣言します。
ofApp.h
#pragma once
#define NUM 40
#include "ofMain.h"
#include "Circle.hpp"
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 mouseEntered(int x, int y);
void mouseExited(int x, int y);
void windowResized(int w, int h);
void dragEvent(ofDragInfo dragInfo);
void gotMessage(ofMessage msg);
Circle circles[NUM]; //Circleクラスのオブジェクト(インスタンス)を宣言
};
ofApp.cpp内でクラスを利用してオブジェクトを生成します。
ofApp.cpp
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::setup(){
ofBackground(255, 255, 255);
ofSetCircleResolution(64);
//オブジェクトを作成
for (int i = 0; i < NUM; i ++) circles[i].init(i*10, 5, i/10.0);
}
//--------------------------------------------------------------
void ofApp::update(){
//c1オブジェクトのクラスであるCircleクラスのupdate(), display()メソッドを実行
for (int i = 0; i < NUM; i ++) circles[i].update();
}
//--------------------------------------------------------------
void ofApp::draw(){
ofSetColor(0, 0, 0);
//c1オブジェクトのクラスであるCircleクラスのupdate(), display()メソッドを実行
for (int i = 0; i < NUM; i ++) circles[i].display();
}
練習問題1
上のサンプルをもとにして、Y座標も変化する円の集合を作ってください。
Circle.hpp
#ifndef Circle_hpp
#define Circle_hpp
#include
#include "ofMain.h"
class Circle {
public:
//初期化用のメソッド
void init (float _x, float _y, int _eSize, float _xSpeed, float _ySpeed);
void update(); //更新
void display(); //描画
float xPos, yPos, xSpeed, ySpeed;
int eSize;
private:
};
#endif
そして、Circle.cppに具体的にメソッドを書いていきます。
Circle.cpp
#include "Circle.hpp"
void Circle::init (float _x, float _y, int _eSize, float _xSpeed, float _ySpeed) { //初期化用のメソッド
xPos = _x; //x, y座標の初期値はオブジェクトを宣言した際の引数になる
yPos = _y;
eSize = _eSize;
xSpeed = _xSpeed; //xSpeed, ySpeedの初期値もオブジェクト宣言の際の引数
ySpeed = _ySpeed;
}
//円の位置を更新するメソッド
void Circle::update() {
xPos += xSpeed; //xSpeed分のx座標を移動
yPos += ySpeed; //ySpeed分のy座標を移動
if (xPos > ofGetWidth()) { //円のx座標がウィンドウの幅を超えたら、
xPos = 0; //位置を0に戻す
}else if (yPos > ofGetHeight()) { //円のy座標がウィンドウの幅を超えたら、
yPos = 0; //位置を0に戻す
}
}
void Circle::display(){
ofDrawCircle(xPos, yPos, eSize); //円を描く
}
ofApp.hでは、オブジェクトのための変数(メンバ変数)を宣言します。
ofApp.h
#pragma once
#define NUM 40
#include "ofMain.h"
#include "Circle.hpp"
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 mouseEntered(int x, int y);
void mouseExited(int x, int y);
void windowResized(int w, int h);
void dragEvent(ofDragInfo dragInfo);
void gotMessage(ofMessage msg);
Circle circles[NUM]; //ColorCircleクラスのオブジェクト(インスタンス)を宣言
};
最後に、ofApp.cpp内でクラスを利用してオブジェクトを生成します。
ofApp.cpp
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::setup(){
ofBackground(255, 255, 255);
ofSetCircleResolution(64);
//オブジェクトを作成
for(int i = 0; i < NUM; i ++) circles[i].init(ofRandom((float)ofGetWidth()),
ofRandom((float)ofGetHeight()),
5,
ofRandom(1.0, 10.0),
ofRandom(1.0, 10.0));
}
//--------------------------------------------------------------
void ofApp::update(){
//c1オブジェクトのクラスであるCircleクラスのupdate()メソッドを実行
for (int i = 0; i < NUM; i ++) circles[i].update();
}
//--------------------------------------------------------------
void ofApp::draw(){
ofSetColor(0, 0, 0);
//c1オブジェクトのクラスであるCircleクラスのdisplay()メソッドを実行
for (int i = 0; i < NUM; i ++) circles[i].display();
}
練習問題2
次はもう少し複雑になります。ドラッグして投げると、円が投げた方向とスピードを記憶していて、そのまま画面内をバウンスし続けます。
上のクラスは、それぞれ生成された円が、マウスの投げるスピードによってその円自体のスピードを決定できるというものです。
練習問題としてはかなり難しいかもしれませんが、Circleクラスの中を記述して、プログラムが動くように変更して下さい。
Circle.hpp
#ifndef Circle_hpp
#define Circle_hpp
#include
#include "ofMain.h"
class Circle {
public:
//初期化用のメソッド
void init();
void update(); //更新
void display(); //描画
float x; //円のx座標
float y; //円のy座標
float radius; //円の直径
float xSpeed; //x軸方向のスピード
float ySpeed; //y軸方向のスピード
private:
};
#endif
Circle.cpp
#include "Circle.hpp"
//初期化用のメソッド(コンストラクタ)
void Circle::init() {
xSpeed = 0.0; //スピードの初期値は0.0
ySpeed = 0.0;
x = ofGetWidth()/2; //x,yは画面の中心に設定
y = ofGetHeight()/2;
radius = 20.0; //円のサイズを設定
}
//円の描画更新用メソッド
void Circle::update() {
// speedで設定された値を足す
x += xSpeed;
y += ySpeed;
if (x > ofGetWidth() || x < 0 ) xSpeed = -xSpeed;
if (y > ofGetHeight() || y < 0 ) ySpeed = -ySpeed;
}
void Circle::display(){
ofDrawCircle(x, y, radius); //円を描く
}
ofApp.h
#pragma once
#define NUM 40
#include "ofMain.h"
#include "Circle.hpp"
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 mouseEntered(int x, int y);
void mouseExited(int x, int y);
void windowResized(int w, int h);
void dragEvent(ofDragInfo dragInfo);
void gotMessage(ofMessage msg);
int count; //マウスクリックによって生まれる円の番号
Circle circles[NUM]; //ColorCircleクラスのオブジェクト(インスタンス)を宣言
};
マウスのインタラクションがあるので、mouseDragged()とmouseReleasesd()を書き換える必要があります。
ofApp.cpp
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::setup(){
ofBackground(255, 255, 255);
ofSetCircleResolution(64);
count = 0;
//オブジェクトを作成
for (int i = 0; i < NUM; i ++) circles[i].init();
}
//--------------------------------------------------------------
void ofApp::update(){
//c1オブジェクトのクラスであるCircleクラスのupdate()メソッドを実行
for (int i = 0; i < NUM; i ++) circles[i].update();
}
//--------------------------------------------------------------
void ofApp::draw(){
ofSetColor(0, 0, 0);
//c1オブジェクトのクラスであるCircleクラスのdisplay()メソッドを実行
for (int i = 0; i < NUM; i ++) circles[i].display();
}
//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
//ドラッグ中は円はマウスについてくる
circles[count].x = ofGetMouseX();
circles[count].y = ofGetMouseY();
//スピードを計測
circles[count].xSpeed = ofGetMouseX() - ofGetPreviousMouseX();
circles[count].ySpeed = ofGetMouseY() - ofGetPreviousMouseY();
}
//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
//テスト用にスピードを出力
printf("xspeed= %f, yspeed= %f\n", circles[count].xSpeed, circles[count].ySpeed);
count ++; //順番に更新
if (count >= NUM) count = 0;
}