openFrameworksで映像作品を作る際のシーン管理について

  
  
##始めに
openFrameworksでインタラクティブなプロジェクションマッピングを製作する機会がありました.
その時用いた設計についてのメモです.
→[サンプルプロジェクト](https://github.com/SakuragiYoshimasa/SceneManagerSample)

## ベースクラス
今回は以下のSceneElementとSceneManagerの二つの抽象クラスをベースクラスにして開発を行いました.
以下SceneElement、SceneManagerを継承したクラスをそれぞれElement、Managerと表記します.

managerのsetup()にて必要なelementを追加してinit()します.
映像作品ということでFPSを一定で維持出来ないことを避けるために、基本的に全てのシーンのメモリの確保やofVboMeshの大量の頂点の初期化、ファイルの読み込みなどの重たい処理を起動時に先にまとめてやってしまうためにinit()関数を作りました。(今回は最初に見せる画面が待機画面だったので、起動に時間がかかっても問題がなかったため)

また今回はVJっぽい映像作品だったので同じelementを何回も切り替えて使い回ししたかったので切り替え時に背景やBlendMode,DepthTestなどの設定が次のElementに影響を与えないためにstart()でElementの描画設定を行い、stop()で描画設定をクリアできるようにしました.
ManagerにnextElement()とchangeElement()を導入することで切り替えを順番にしたい場合と、不規則な場合の両方に対応できるようにしました.
入力に対する挙動に関しては今回はkeyReleasedとmouseReleasedのみでよかったので２種しか実装しませんでしたが使って頂ける場合は必要な分だけ付け加えると良いと思います.

```cpp:SceneElement.cpp
#include "ofMain.h"

class SceneElement {
public:
    virtual void init(){};
    virtual void start(){};
    virtual void stop(){};
    virtual void update(){};
    virtual void draw()=0;
    virtual void end(){};
    
    virtual void keyReleased(int key){};
    virtual void mouseReleased(int x, int y, int button){};
};
```

```cpp:SceneManager.cpp
#include "SceneElement.cpp"
#include "ofMain.h"

class SceneManager{
protected:
    int elementIndex = 0;
    vector<SceneElement *> elements;

public:
    virtual void setup()=0; //add SceneElement in this
    
    void init(){
        for(int i = 0; i < elements.size(); i++){
            elements[i]->init();
        }
    }
    void update(){
        elements.at(elementIndex)->update();
    };
    
    void draw(){
        ofSetWindowTitle("FPS:" + ofToString(ofGetFrameRate()));
        elements.at(elementIndex)->draw();
    };
    
    void end(){
        for(int i = 0; i < elements.size(); i++){
            elements[i]->end();
        }
    }
    
    void changeElement(int index){
        if(index >= elements.size()) return;
        elements[elementIndex]->stop();
        elementIndex = index;
        elements[elementIndex]->start();
    }

    bool nextElement(){
        elements[elementIndex]->stop();
        elementIndex++;
        if(elementIndex >= elements.size()){
            elementIndex--;
            return false;
        }else{
            elements[elementIndex]->start();
            return true;
        }
    };
    
    virtual void keyReleased(int key){
        elements.at(elementIndex)->keyReleased(key);
    };
    virtual void mouseReleased(int x, int y, int button){
        elements.at(elementIndex)->mouseReleased(x, y, button);
    };
};
```

##使用例
最後にキー操作によって背景が変わる簡単なサンプルも掲載しておきます.
<img width="250" alt="2.png" src="https://qiita-image-store.s3.amazonaws.com/0/68570/c4d5d8e1-15ba-8c31-5369-4aaed1fee77f.png">
⇩
<img width="250" alt="3.png" src="https://qiita-image-store.s3.amazonaws.com/0/68570/1e654dd9-454c-5bff-09b4-4265e278bb59.png">

サンプルではnextElement()で次のElementがない場合に新しいManagerをインスタンス化して切り替えていますが、本来はここではあらかじめインスタンス化しておいたManagerに切り替えて使ってました.

```cpp:ofApp.cpp
#include "SampleSceneManager.h"

SampleSceneManager manager;

void ofApp::setup(){
    manager.setup();
}

void ofApp::update(){
    manager.update();
}

void ofApp::draw(){
    manager.draw();
}
void ofApp::keyReleased(int key){
    if(key == ' '){
        if(!manager.nextElement()){
            //Create new manager if has no more elements
            manager = *new SampleSceneManager();
            manager.setup();
        }
    }
    
    manager.keyReleased(key);
}

void ofApp::mousePressed(int x, int y, int button){
    manager.mouseReleased(x, y, button);
}
```

```cpp:SampleSceneElement
//SampleSceneElement.h

#include "SceneElement.h"
class SampleSceneElement : public SceneElement {
public:
    void draw() override;
    SampleSceneElement();
    
private:
    ofColor backgroundColor;
};

//SampleSceneElement.cpp

SampleSceneElement::SampleSceneElement(){
    this->backgroundColor = ofColor::fromHsb(ofRandom(255), 255, 255);
}

void SampleSceneElement::draw(){
    ofBackground(backgroundColor);
}

```

```cpp:SampleSceneManager

//SampleSceneManager.h
#include "SceneManager.cpp"
#include "SampleSceneElement.h"

class SampleSceneManager : public SceneManager {
public:
    void setup() override;
    void keyReleased(int key) override;
};

//SampleSceneManager.cpp
void SampleSceneManager::setup(){
    for(int i = 0; i < 5; i++){
        this->elements.push_back(new SampleSceneElement());
    }
    init();
}

void SampleSceneManager::keyReleased(int key){
    switch (key) {
        case OF_KEY_RETURN:
            changeElement(ofRandom(5));
            break;
        default:
            break;
    }
}
```

##終わりに
約１ヶ月間のプロジェクトで用いましたが最初に作ってから大きな変更はなかった上に、特に遷移での不便は感じず、シーン毎に管理出来るので開発効率も良かったです.
サンプルでは遷移がランダムもしくは順番のみですがキーボードに割り当てたり出来るので色々出来ます.
これからopenFrameworksで映像作品を作ってみようという方はよければ参考にしてください.
ご意見等お待ちしております.


       
2015.10.26