OpenSiv3DでBoidsモデルを実装する＞＜


# tl;dr

- Boidsモデルという「群れ」を表現するモデルがあるよ！
    - とてもシンプルな数式＞＜
- OpenSiv3Dだと簡単に実装できるよ！
    - 様々なデータ構造やメソッドが用意されている
    - しかも実行が軽い＞＜

![](https://i.gyazo.com/0d54663640bbdddfc973d9f0e48cd1fd.gif)

# はじめに

学校の「スマートインフォメディアシステム特論」という授業で、Boidsモデルの実装があり、あまりProcessingが好きではないので、自分が好きなOpenSiv3Dで実装しました＞＜

Processingは、「関数型」で線を書くにも、図形を書くにも「特定の関数名」を大量に覚える必要があるのですが、Siv3Dはとても使いやすいライブラリがクラスベースで作成されているため、頭に優しく、また最新のC++の記法やライブラリが使えて心地よく書けます＞＜

今回は、BoidsモデルをSiv3Dで実装しました＞＜

# Boidsモデル

[スイミーを再現したい！ ～群れアルゴリズム Boids～](https://qiita.com/odanny/items/e0c0a00e13c2b4839cec)
こちらの記事で、とても丁寧にBoidsモデルが説明されています。

かいつまんで説明すると

- Boidsモデルは、魚や鳥の群れの複雑な動きを、各個体のシンプルな３つのルールのみで再現したものである。
    - 分離(Separation)
        - 各個体が近すぎないようにする
    - 整列(Alignment)
        - 周りの個体の進む方向に合わせる
    - 結合(Cohesion)
        - 他の仲間の中心位置に向かう
- 各個体は「視界」を持っており、見える範囲の他の個体の動きや位置のみを参考にする

というもので、シンプルなルールのみで複雑な動きが形成されるのが自然界の面白いところですね＞＜！

Boidsモデルは、粒子群最適化のもとになったモデルであり、群知能などの説明は（[群知能とは何ぞや？](https://qiita.com/ganariya/items/1c98b7c45957893933a6)）で説明しています。

# ソースコード

```c++

# include <Siv3D.hpp> // OpenSiv3D v0.4.3

using namespace std;

constexpr int NUM_OF_BIRDS = 50;
constexpr int NUM_OF_BLOCKS = 1;


class Block{
    
public:
    RectF rect;
    
    Block(){
        int sx = Random(Window::ClientWidth())/2;
        int sy = Random(Window::ClientWidth())/ 2;
        int dx = 200 + Random(20);
        int dy = 10 + Random(10);
        rect = RectF(sx, sy, dx, dy);
    }
    
    void draw()const{
        rect.draw();
    }
};


class Enemy{
public:
    
    Vec2 pos;
    Vec2 velocity;
    
    double can_see;
    
    Enemy(){
        pos = RandomVec2(Window::ClientSize().x, Window::ClientSize().y);;
        velocity = RandomVec2(5, 5) - Vec2(2.5, 2.5);
        can_see = 100;
    }
    
    void draw() const {
        Circle(pos, 20).draw(Palette::Red);
    }
    
    void update() {
        velocity += RandomVec2(1, 1) - Vec2(0.5, 0.5);
        pos += velocity;
        clamp();
    }
    
    void clamp(){
        if(pos.x < 0){
            long long x = (long long)(pos.x);
            x += 1000*Window::ClientWidth();
            x %= Window::ClientWidth();
            pos.x = x;
        }
        if(pos.x > Window::ClientSize().x){
            long long x = (long long)(pos.x);
            x += 1000*Window::ClientWidth();
            x %= Window::ClientWidth();
            pos.x = x;
        }
        if(pos.y < 0){
            long long y = (long long)(pos.y);
            y += 1000*Window::ClientHeight();
            y %= Window::ClientHeight();
            pos.y = y;;
        }
        if(pos.y > Window::ClientSize().y){
            long long y = (long long)(pos.y);
            y += 1000*Window::ClientHeight();
            y %= Window::ClientHeight();
            pos.y = y;;
        }
        if(velocity.length() >= 5){
            velocity = velocity.normalized() * 5;
        }
    }
};


class Boid{
    
private:
    Vec2 pos;
    Vec2 velocity;
    double can_see;
    
public:
    
    Boid(){
        pos = RandomVec2(Window::ClientSize().x, Window::ClientSize().y);;
        velocity = RandomVec2(5, 5) - Vec2(2.5, 2.5);
        can_see = 98;
    }
    
    Boid(double x, double y){
        pos.x = x;
        pos.y = y;
    }
    
    Vec2 separation(const Array<Boid> &boids){
        Vec2 vec;
        for(const auto &boid : boids){
            if((pos - boid.pos).length() <= can_see){
                Vec2 diff = pos - boid.pos;
                if(diff == Vec2(0, 0)) continue;
                vec += diff / diff.length();
            }
            
        }
        return vec / boids.size();
    }
    
    Vec2 alignment(const Array<Boid> &boids){
        Vec2 vec;
        for(const auto &boid: boids){
            if((pos - boid.pos).length() <= can_see){
                vec += boid.velocity;
            }
        }
        vec /= boids.size();
        return vec - velocity;
    }
    
    Vec2 cohesion(const Array<Boid> &boids){
        Vec2 other;
        for(const auto &boid: boids){
            if((pos - boid.pos).length() <= can_see){
                other += boid.pos - pos;
            }
        }
        other /= boids.size();
        return other;
    }
    
    Vec2 food_by_mouse(){
        if(MouseL.pressed()){
            auto mouse_pos = Cursor::PosF();
            auto diff = mouse_pos - pos;
            if(diff.length() > 0) diff /= diff.length();
            return diff;
        }
        return Vec2();
    }
    
    Vec2 avoid_blocks(const Array<Block> &blocks){
        Vec2 vec;
        for(const auto &block: blocks){
            auto center = block.rect.center();
            Vec2 diff = pos - center;
            if(diff.length() > 0) diff /= diff.length();
            vec += diff;
        }
        if(blocks.size() > 0)
        vec /= blocks.size();
        return vec;
    }
    
    Vec2 avoid_enemies(const Array<Enemy> &enemies){
        Vec2 vec;
        for(const auto &enemy: enemies){
            Vec2 diff = pos - enemy.pos;
            if(diff.length() > 0) diff /= diff.length();
            vec += diff;
        }
        if(enemies.size())
        vec /= enemies.size();
        return vec;
    }
    
    void update(Array<Boid> &boids, const Array<Block> &blocks, const Array<Enemy> &enemies){
        auto sep = separation(boids);
        auto ali = alignment(boids);
        auto cohe = cohesion(boids);
        auto mouse_food = food_by_mouse();
        auto avoid_b = avoid_blocks(blocks);
        auto avoid_e = avoid_enemies(enemies);
        Vec2 ac =  0.64 * sep + 0.000280 * ali + cohe * 0.0100 + mouse_food * 0.01 + avoid_b * 0.00008 + avoid_e * 0.02;
        velocity += 8 * ac;
        pos += velocity;
        clamp();
    }
    
    void clamp(){
        if(pos.x < 0){
            long long x = (long long)(pos.x);
            x += 1000*Window::ClientWidth();
            x %= Window::ClientWidth();
            pos.x = x;
        }
        if(pos.x > Window::ClientSize().x){
            long long x = (long long)(pos.x);
            x += 1000*Window::ClientWidth();
            x %= Window::ClientWidth();
            pos.x = x;
        }
        if(pos.y < 0){
            long long y = (long long)(pos.y);
            y += 1000*Window::ClientHeight();
            y %= Window::ClientHeight();
            pos.y = y;;
        }
        if(pos.y > Window::ClientSize().y){
            long long y = (long long)(pos.y);
            y += 1000*Window::ClientHeight();
            y %= Window::ClientHeight();
            pos.y = y;;
        }
        if(velocity.length() >= 6){
             velocity = velocity.normalized() * 6;
         }
    }
    
    void draw() const {
        Circle(pos, 5).draw();
    }
    
};

void Main(){
    
    Array<Boid> boids;
    for(int i = 0;i<NUM_OF_BIRDS;i++){
        boids << Boid();
    }
    
    Array<Block> blocks;
    for(int i = 0;i<NUM_OF_BLOCKS ;i++){
        blocks << Block();
    }
    
    Array<Enemy> enemies;
    for(int i = 0;i<1;i++) enemies << Enemy();
    
    while(System::Update()){
        for(auto &boid: boids) boid.update(boids, blocks , enemies);
        for(auto &enemy: enemies) enemy.update();
        for(const auto &boid: boids) boid.draw();
        for(const auto &block: blocks) block.draw();
        for(const auto &enemy: enemies) enemy.draw();
        
        if(MouseL.pressed()){
            Circle(Cursor::Pos(), 20).draw(Palette::Gray);
        }
    }
    
}

```

ソースコード･･･長いですね＞＜
一部ずつ見ていきます＞＜

# Boidクラス

```c++
class Boid{
    
private:
    Vec2 pos;
    Vec2 velocity;
    double can_see;
    
public:
    
    Boid(){
        pos = RandomVec2(Window::ClientSize().x, Window::ClientSize().y);;
        velocity = RandomVec2(5, 5) - Vec2(2.5, 2.5);
        can_see = 98;
    }
    
    Boid(double x, double y){
        pos.x = x;
        pos.y = y;
    }
    
    Vec2 separation(const Array<Boid> &boids){
        Vec2 vec;
        for(const auto &boid : boids){
            if((pos - boid.pos).length() <= can_see){
                Vec2 diff = pos - boid.pos;
                if(diff == Vec2(0, 0)) continue;
                vec += diff / diff.length();
            }
            
        }
        return vec / boids.size();
    }
    
    Vec2 alignment(const Array<Boid> &boids){
        Vec2 vec;
        for(const auto &boid: boids){
            if((pos - boid.pos).length() <= can_see){
                vec += boid.velocity;
            }
        }
        vec /= boids.size();
        return vec - velocity;
    }
    
    Vec2 cohesion(const Array<Boid> &boids){
        Vec2 other;
        for(const auto &boid: boids){
            if((pos - boid.pos).length() <= can_see){
                other += boid.pos - pos;
            }
        }
        other /= boids.size();
        return other;
    }
    
    Vec2 food_by_mouse(){
        if(MouseL.pressed()){
            auto mouse_pos = Cursor::PosF();
            auto diff = mouse_pos - pos;
            if(diff.length() > 0) diff /= diff.length();
            return diff;
        }
        return Vec2();
    }
    
    Vec2 avoid_blocks(const Array<Block> &blocks){
        Vec2 vec;
        for(const auto &block: blocks){
            auto center = block.rect.center();
            Vec2 diff = pos - center;
            if(diff.length() > 0) diff /= diff.length();
            vec += diff;
        }
        if(blocks.size() > 0)
        vec /= blocks.size();
        return vec;
    }
    
    Vec2 avoid_enemies(const Array<Enemy> &enemies){
        Vec2 vec;
        for(const auto &enemy: enemies){
            Vec2 diff = pos - enemy.pos;
            if(diff.length() > 0) diff /= diff.length();
            vec += diff;
        }
        if(enemies.size())
        vec /= enemies.size();
        return vec;
    }
    
    void update(Array<Boid> &boids, const Array<Block> &blocks, const Array<Enemy> &enemies){
        auto sep = separation(boids);
        auto ali = alignment(boids);
        auto cohe = cohesion(boids);
        auto mouse_food = food_by_mouse();
        auto avoid_b = avoid_blocks(blocks);
        auto avoid_e = avoid_enemies(enemies);
        Vec2 ac =  0.64 * sep + 0.000280 * ali + cohe * 0.0100 + mouse_food * 0.01 + avoid_b * 0.00008 + avoid_e * 0.02;
        velocity += 8 * ac;
        pos += velocity;
        clamp();
    }
    
    void clamp(){
        if(pos.x < 0){
            long long x = (long long)(pos.x);
            x += 1000*Window::ClientWidth();
            x %= Window::ClientWidth();
            pos.x = x;
        }
        if(pos.x > Window::ClientSize().x){
            long long x = (long long)(pos.x);
            x += 1000*Window::ClientWidth();
            x %= Window::ClientWidth();
            pos.x = x;
        }
        if(pos.y < 0){
            long long y = (long long)(pos.y);
            y += 1000*Window::ClientHeight();
            y %= Window::ClientHeight();
            pos.y = y;;
        }
        if(pos.y > Window::ClientSize().y){
            long long y = (long long)(pos.y);
            y += 1000*Window::ClientHeight();
            y %= Window::ClientHeight();
            pos.y = y;;
        }
        if(velocity.length() >= 6){
             velocity = velocity.normalized() * 6;
         }
    }
    
    void draw() const {
        Circle(pos, 5).draw();
    }
    
};
```

一番肝心なBoid（鳥や魚）のクラスですね＞＜
本当はもっと綺麗に設計すべきなのですが、普段競技プログラミングしかしていないので、設計が下手くそです･･･（すいません）

`separation`メソッドでは、他のBoid（仲間）を引数にとり、もし見える範囲ならば仲間たちから離れるように、移動する速度のベクトルを計算します。


`alignment`メソッドでは、見える範囲の仲間たちの速度の合計を求めることで、自分がどの速度を目指すべきかを計算し、その速度に近づくように、次の移動するベクトルを計算します。

`cohesion`メソッドでは、見える範囲の仲間たちの位置の合計を求めて、中央の位置に向かうようなベクトルを計算します。

そして、`update`メソッドで、これらのベクトルを求めて、うまいこと係数をかけて加速度`ac`を計算し、あとはそれを`velocity`に足して、その`velocity`を`pos`に足すことで、各個体の次の位置を求めます。


この単純なルールによって、魚の群れは複雑な動きを作れるなんですごいですね＞＜


clampは、範囲外に出ていったら反対側から出るように、また速度が早すぎるときはある一定の速度以下になるようにしています。

その他のEnemyやBlockは、Boidが避けるためのもので、なくても大丈夫です＞＜
よりリアルな環境に近くなります。


# 最後に

OpenSiv3DでBoidsモデルを実装してみました。
最終課題は自由に作っていいらしいので、面白いものを作りたいと思います＞＜（これまでの授業をすべて盛り込んだUndertaleっぽいゲーム作りたい･･･＞＜）