0
0

More than 1 year has passed since last update.

アニメーションを前提にしたオブジェクトを考える(Ⅱ. ボタンの設計)

Posted at

この記事はⅠの続きとなります。

目標

今回は以下のようなボタン動作が可能な所までを設計したいと思います。さらに,レイヤーの機能を一部分作成したいと思います。
gif1.gif

ボタン以外は次回になります。

ボタンの設計

まずは,UIと言えばのボタンを作っていこうと思います。まずは,Object2Dを継承することによってボタンのためのクラスを設計します。
必要な情報は前回の

  • 有効状態
  • 位置
  • サイズ
  • マウスの接触状態

に加えて,

  • 内側の色
  • 外側の色
  • 描画色の有効状態
  • 線幅

をメンバ変数として追加します。
また,必要な機能として

  • 色の設定
  • 色の取得
  • Update
  • Draw
  • Collide

が挙げられるので,まずはヘッダファイルを提示したいと思います。

ヘッダファイル

// ButtonObject.hpp
#pragma once
#include <GL/glut.h>

#include <string>

#include "Object2D.hpp"
namespace Obj {
class Button : public Object2D {
 public:
  Button(Vector3D _pos, Vector3D _size, bool _enabledFill = true, bool _enabledOutline = false)
      : Object2D(_pos, _size),
        enabledFill(_enabledFill),
        enabledOutline(_enabledOutline),
        outlineWidth(0) {}

  ~Button() {}

  // 色(内側) 有効化/無効化
  bool SetEnabledFill(bool _enabled) {
    enabledFill = _enabled;
    return true;
  }
  // 色取得
  bool GetEnabledFill() { return enabledFill; }

  // 色(外側) 有効化/無効化
  bool SetEnabledOutline(bool _enabled) {
    enabledOutline = _enabled;
    return true;
  }
  // 色取得
  bool GetEnabledOutline() { return enabledOutline; }

  // 色情報登録等
  bool SetInnerColor(Color255 _innerColor, bool _defaultFill = true) {
    innerColor = _innerColor;
    hoveredInnerColor = _innerColor;
    clickedInnerColor = _innerColor;
    selectedInnerColor = _innerColor;
    if (_defaultFill) {
      innerAnimation.current = innerColor;
      innerAlphaAnimation.current = (float)innerColor.a;
    }
    disabledInnerColor =
        Color255(innerColor.r, innerColor.g, innerColor.b, innerColor.a / 5.);
    return true;
  }
  // 色情報登録等
  bool SetInnerColor(Color255 _innerColor, Color255 _hoveredColor,
                     Color255 _clickedColor, Color255 _selectedColor,
                     bool _defaultFill = true) {
    innerColor = _innerColor;
    hoveredInnerColor = _hoveredColor;
    clickedInnerColor = _clickedColor;
    selectedInnerColor = _selectedColor;
    if (_defaultFill) {
      innerAnimation.current = innerColor;
      innerAlphaAnimation.current = (float)innerColor.a;
    }
    disabledInnerColor =
        Color255(innerColor.r, innerColor.g, innerColor.b, innerColor.a / 5.);
    return true;
  }
  // アウトラインを表示する際はtrueになっているかをチェック
  bool SetOuterColor(Color255 _outerColor, float _outlineWidth, bool _defaultFill = true) {
    outerColor = _outerColor;
    hoveredOuterColor = _outerColor;
    clickedOuterColor = _outerColor;
    selectedOuterColor = _outerColor;
    outlineWidth = _outlineWidth;
    if (_defaultFill) {
      outerAnimation.current = outerColor;
      outerAlphaAnimation.current = (float)outerColor.a;
    }
    disabledOuterColor =
        Color255(outerColor.r, outerColor.g, outerColor.b, outerColor.a / 5.);
    return true;
  }
  // アウトラインを表示する際はtrueになっているかをチェック
  bool SetOuterColor(Color255 _outerColor, Color255 _hoveredColor,
                     Color255 _clickedColor, Color255 _selectedColor,
                     float _outlineWidth, bool _defaultFill = true) {
    outerColor = _outerColor;
    hoveredOuterColor = _hoveredColor;
    clickedOuterColor = _clickedColor;
    selectedOuterColor = _selectedColor;
    outlineWidth = _outlineWidth;
    if (_defaultFill) {
      outerAnimation.current = outerColor;
      outerAlphaAnimation.current = (float)outerColor.a;
    }
    disabledOuterColor =
        Color255(outerColor.r, outerColor.g, outerColor.b, outerColor.a / 5.);
    return true;
  }

  // 色取得
  Color255* GetColor(ColorType type) {
    switch (type) {
      case ColorType::INNER:
        return &innerColor;
      case ColorType::HOVERED:
        return &hoveredInnerColor;
      case ColorType::CLICKED:
        return &clickedInnerColor;
      case ColorType::SELECTED:
        return &selectedInnerColor;
      case ColorType::OUTER_INNER:
        return &outerColor;
      case ColorType::OUTER_HOVERED:
        return &hoveredOuterColor;
      case ColorType::OUTER_CLICKED:
        return &clickedOuterColor;
      case ColorType::OUTER_SELECTED:
        return &selectedOuterColor;
      default:
        return nullptr;
    }
  }

  // 更新描画
  void Collide() override;
  void Update() override;
  void Draw() override;

 private:
  void CollideMouse() override;

 private:
  Color255 innerColor;
  Color255 hoveredInnerColor;
  Color255 clickedInnerColor;
  Color255 selectedInnerColor;
  Color255 outerColor;
  Color255 hoveredOuterColor;
  Color255 clickedOuterColor;
  Color255 selectedOuterColor;

  Color255 disabledInnerColor;
  Color255 disabledOuterColor;

  float outlineWidth;

  bool enabledFill;
  bool enabledOutline;
};

}  // namespace Obj

プログラムの冒頭に,

#include <GL/glut.h>

があると思います。もし他の描画ライブラリを用いる場合はこの部分が変更となります。

Button(Vector3D _pos, Vector3D _size, bool _enabledFill = true, bool _enabledOutline = false)

この部分はコンストラクタとなっており,ボタンの宣言がされた場合は最初に呼ばれることとなります。また,このクラスはObject2Dの継承となっているため,_pos_sizeは親クラスへと渡されることになります。また,この後に宣言されるメンバ変数の初期化も行っており,_enabledFill _enabledOutline の代入もここで行われます。

描画有効/無効化

bool SetEnabledFill(bool _enabled)
bool GetEnabledFill()
bool SetEnabledOutline(bool _enabled)
bool GetEnabledOutline()

これらは内側または外側の描画を有効/無効化するためのゲッタ・セッタです。これがFalseの場合には描画がされることがないようにします。

描画色設定

bool SetInnerColor(Color255 _innerColor, bool _defaultFill = true)
bool SetInnerColor(Color255 _innerColor, Color255 _hoveredColor, Color255 _clickedColor, Color255 _selectedColor, bool _defaultFill = true)
bool SetOuterColor(Color255 _outerColor, float _outlineWidth, bool _defaultFill = true)
bool SetOuterColor(Color255 _outerColor, Color255 _hoveredColor, Color255 _clickedColor, Color255 _selectedColor, float _outlineWidth, bool _defaultFill = true)

これらはColor255で宣言された色を,特定の状態の色として格納するようにします。こうすることでHitSelectedで色の区別を行うことが可能になり,視覚情報としてボタンが動いていると認識ができるようになると思います。今回のボタンでは以下の状態が挙げられるとします。

  • 通常
  • マウスヒット
  • マウスによる押し込み中
  • 選択中
  • 無効

無効状態の色は通常の色のアルファ値を1/5にしたものとします。

色取得

Color255* GetColor(ColorType type)

この関数により描画色を取得することで,アニメーションの対象物として間接的な変化を実現することが可能となります。

CollideUpdateDraw

void Collide() override
void Update() override
void Draw() override

元々Object2Dで宣言されていた仮想関数をこのようにオーバーライドすることで,今回のボタンは実際に更新,描画がされていきます。

ソースファイル

// ButtonObject.cpp
#include "ButtonObject.hpp"

void Obj::Button::Collide() { CollideMouseAsBox(); }

void Obj::Button::Update() {
  UpdateEnforcedMouseCollision();

  // アニメーション記述をする場合,ここに記述
  if (mouseHit) {
    SetAnimationColorPoint(&innerAnimation, innerAnimation.current, hoveredInnerColor);
    SetAnimationColorPoint(&outerAnimation, outerAnimation.current, hoveredOuterColor);
    SetAnimationPoint(&innerAlphaAnimation, (float)innerAlphaAnimation.current, (float)hoveredInnerColor.a);
    SetAnimationPoint(&outerAlphaAnimation, (float)outerAlphaAnimation.current, (float)hoveredOuterColor.a);
  } else {
    // 通常状態
    SetAnimationColorPoint(&innerAnimation, innerAnimation.current, innerColor);
    SetAnimationColorPoint(&outerAnimation, outerAnimation.current, outerColor);
    SetAnimationPoint(&innerAlphaAnimation, (float)innerAlphaAnimation.current, (float)innerColor.a);
    SetAnimationPoint(&outerAlphaAnimation, (float)outerAlphaAnimation.current, (float)outerColor.a);
  }

  if (mouseSelected) {
    SetAnimationColorPoint(&innerAnimation, innerAnimation.current, selectedInnerColor);
    SetAnimationColorPoint(&outerAnimation, outerAnimation.current, selectedOuterColor);
    SetAnimationPoint(&innerAlphaAnimation, (float)innerAlphaAnimation.current, (float)selectedInnerColor.a);
    SetAnimationPoint(&outerAlphaAnimation, (float)outerAlphaAnimation.current, (float)selectedOuterColor.a);
  }

  if (mouseClicked) {
    SetAnimationColorPoint(&innerAnimation, innerAnimation.current, clickedInnerColor);
    SetAnimationColorPoint(&outerAnimation, outerAnimation.current, clickedOuterColor);
    SetAnimationPoint(&innerAlphaAnimation, (float)innerAlphaAnimation.current, (float)clickedInnerColor.a);
    SetAnimationPoint(&outerAlphaAnimation, (float)outerAlphaAnimation.current, (float)clickedOuterColor.a);
  }

  if (!enabled) {
    SetAnimationColorPoint(&innerAnimation, innerAnimation.current, disabledInnerColor);
    SetAnimationColorPoint(&outerAnimation, outerAnimation.current, disabledOuterColor);
    SetAnimationPoint(&innerAlphaAnimation, (float)innerAlphaAnimation.current, (float)disabledInnerColor.a);
    SetAnimationPoint(&outerAlphaAnimation, (float)outerAlphaAnimation.current, (float)disabledOuterColor.a);
  }

  UpdateAnimations();
}

void Obj::Button::Draw() {
  glEnable(GL_BLEND);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_LIGHTING);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  // 外枠の描画
  if (enabledOutline) {
    glColor4f(outerAnimation.current.fr, outerAnimation.current.fg,
              outerAnimation.current.fb, outerAlphaAnimation.current);
    // 1本目
    glBegin(GL_QUADS);
    glVertex2f(pos.x, pos.y);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y + outlineWidth);
    glVertex2f(pos.x, pos.y + outlineWidth);
    glEnd();
    // 2本目
    glBegin(GL_QUADS);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y);
    glVertex2f(pos.x + size.x, pos.y);
    glVertex2f(pos.x + size.x, pos.y + size.y - outlineWidth);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y + size.y - outlineWidth);
    glEnd();
    // 3本目
    glBegin(GL_QUADS);
    glVertex2f(pos.x + size.x, pos.y + size.y - outlineWidth);
    glVertex2f(pos.x + size.x, pos.y + size.y);
    glVertex2f(pos.x + outlineWidth, pos.y + size.y);
    glVertex2f(pos.x + outlineWidth, pos.y + size.y - outlineWidth);
    glEnd();
    // 4本目
    glBegin(GL_QUADS);
    glVertex2f(pos.x + outlineWidth, pos.y + size.y);
    glVertex2f(pos.x, pos.y + size.y);
    glVertex2f(pos.x, pos.y + outlineWidth);
    glVertex2f(pos.x + outlineWidth, pos.y + outlineWidth);
    glEnd();
  }

  // 内部の描画
  if (enabledFill) {
    glBegin(GL_QUADS);
    glColor4f(innerAnimation.current.fr, innerAnimation.current.fg,
              innerAnimation.current.fb, innerAlphaAnimation.current);
    glVertex2f(pos.x + outlineWidth, pos.y + outlineWidth);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y + outlineWidth);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y + size.y - outlineWidth);
    glVertex2f(pos.x + outlineWidth, pos.y + size.y - outlineWidth);
    glEnd();
  }

  glDisable(GL_BLEND);
}

void Obj::Button::CollideMouse() {}

Collide

void Obj::Button::Collide() { CollideMouseAsBox(); }

接触判定において,今回のボタン設計ではページ上部のような四角形を想定するとします。そのため,Object2Dクラスのメソッド,CollideMouseAsBox関数を使うことで目的が果たせるためこのように記述します。

Update

void Obj::Button::Update()

この関数内では主に特定のオブジェクトに対するアニメーションの設定が行われています。4つの状態を満たす際にアニメーションの目標値を設定し,特定の時間内に色が遷移するように施します。

    SetAnimationColorPoint(&innerAnimation, innerAnimation.current, hoveredInnerColor);
    SetAnimationColorPoint(&outerAnimation, outerAnimation.current, hoveredOuterColor);
    SetAnimationPoint(&innerAlphaAnimation, (float)innerAlphaAnimation.current, (float)hoveredInnerColor.a);
    SetAnimationPoint(&outerAlphaAnimation, (float)outerAlphaAnimation.current, (float)hoveredOuterColor.a);

上から内側の色,外側の色,内側のアルファ値,外側のアルファ値と4つの操作が含まれています。

Updateの最後には

UpdateAnimations();

が記述されており,色やその他のアニメーションとして,このオブジェクト内で設定された変数が一気に更新されます。

Draw

ここで描画ライブラリの出番が来ます。(今回はOpenGL(glut))
決められた座標に対して内側,外側の描画がTrueの時に描画をしていくだけの機能です。他のライブラリを使う時は座標に気をつけながら移植をするのみで問題ありません。

    // 1本目
    glBegin(GL_QUADS);
    glVertex2f(pos.x, pos.y);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y + outlineWidth);
    glVertex2f(pos.x, pos.y + outlineWidth);
    glEnd();
    // 2本目
    glBegin(GL_QUADS);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y);
    glVertex2f(pos.x + size.x, pos.y);
    glVertex2f(pos.x + size.x, pos.y + size.y - outlineWidth);
    glVertex2f(pos.x + size.x - outlineWidth, pos.y + size.y - outlineWidth);
    glEnd();
    // 3本目
    glBegin(GL_QUADS);
    glVertex2f(pos.x + size.x, pos.y + size.y - outlineWidth);
    glVertex2f(pos.x + size.x, pos.y + size.y);
    glVertex2f(pos.x + outlineWidth, pos.y + size.y);
    glVertex2f(pos.x + outlineWidth, pos.y + size.y - outlineWidth);
    glEnd();
    // 4本目
    glBegin(GL_QUADS);
    glVertex2f(pos.x + outlineWidth, pos.y + size.y);
    glVertex2f(pos.x, pos.y + size.y);
    glVertex2f(pos.x, pos.y + outlineWidth);
    glVertex2f(pos.x + outlineWidth, pos.y + outlineWidth);
    glEnd();

外側の描画は上のようになります。縁の部分にのみ描画をしていくことで,アルファ値を考慮した描画が可能になると思います。

表示例

これにより,以下のようにボタンが表示されます。
・通常時
image.png
・選択時
image.png

後はこちらをインスタンス化すれば実際に利用できるものになります。

レイヤー機能

ここで,複数のオブジェクトを次回の記事以降含めて作っていくわけですが,2Dオブジェクトは本来プログラムで記述された順に表示されていきます。しかし,順序を入れ替えたい!という可能性はもちろんありますし,表示するときに1個1個描画命令を書くのもあれだという感覚もあります。そこでレイヤー機能を取り入れてみます。

// ObjectLayer.hpp
#pragma once
#include <vector>

#include "Object2D.hpp"
#include "ObjectData.hpp"
namespace Obj {
class ObjectLayer {
 public:
  ObjectLayer() {}
  ~ObjectLayer() {}

  void AddObject(Object2D* _object);
  void DeleteObject(Object2D* _object);

  void Clear();

  void SetTopLayer(Object2D* _object);
  void SetButtomLayer(Object2D* _object);

  void MoveLayer(Object2D* _object, int _distance);

  int Total() { return (int)layer.size(); }

  void Collide();
  void Update();
  void Draw();

 private:
  std::vector<ObjectFunc> layer;
};
}  // namespace Obj

作る機能は

  • 特定のオブジェクトを一番上/下にする。
  • レイヤーにオブジェクトを追加する。
  • レイヤーからオブジェクトを削除する。

です。(MoveLayerは現在実装していません。)

// ObjectLayer.cpp
#include "ObjectLayer.hpp"

void Obj::ObjectLayer::AddObject(Object2D* _object) {
  for (auto& item : layer) {
    if (item.object == _object) return;
  }
  layer.push_back(ObjectFunc(_object));
}

void Obj::ObjectLayer::DeleteObject(Object2D* _object) {
  for (auto it = layer.begin(); it != layer.end(); it++) {
    if (it->object == _object) {
      layer.erase(it);
      break;
    }
  }
}

void Obj::ObjectLayer::Clear() { layer.clear(); }

void Obj::ObjectLayer::SetTopLayer(Object2D* _object) {
  int i = 0;
  for (auto& item : layer) {
    if (item.object == _object) break;
    i++;
  }
  if (i == (int)layer.size()) return;

  for (int k = i; k < (int)layer.size() - 1; k++) {
    std::iter_swap(layer.begin() + k, layer.begin() + k + 1);
  }
}

void Obj::ObjectLayer::SetButtomLayer(Object2D* _object) {
  int i = 0;
  for (auto& item : layer) {
    if (item.object == _object) break;
    i++;
  }
  if (i == (int)layer.size()) return;

  for (int k = i; k > 0; k--) {
    std::iter_swap(layer.begin() + k, layer.begin() + k - 1);
  }
}

void Obj::ObjectLayer::MoveLayer(Object2D* _object, int _distance) {}

void Obj::ObjectLayer::Collide() {
  for (auto& item : layer) {
    item.object->Collide();
  }
}

void Obj::ObjectLayer::Update() {
  for (auto& item : layer) {
    item.object->Update();
  }
}

void Obj::ObjectLayer::Draw() {
  for (auto& item : layer) {
    item.object->Draw();
  }
}

ここで,

void Obj::ObjectLayer::SetTopLayer(Object2D* _object)
void Obj::ObjectLayer::SetButtomLayer(Object2D* _object)

は特定のオブジェクトが見つかった場合にレイヤーの端にたどり着くまでswapで交換を行い,目的の動作を果たすようにしています。

レイヤーへオブジェクトを追加する際は,

void Obj::ObjectLayer::AddObject(Object2D* _object)

で可能となります。ここで,抽象クラスを作っている恩恵を受けることが出来ます。画像やテキストを表示する時もObject2Dを継承して作ることになっていくので,テンプレートなどを作らずに1つの関数で好きなように放り込むことが可能になります。

また登録時のObjectFuncは拡張性を持たせるようにObject2Dを含んだ構造体にしているだけです。

// ObjectData.hpp
#pragma once

#include "Object2D.hpp"

namespace Obj {
struct ObjectFunc {
  Object2D* object;

  ObjectFunc(Object2D* _object) : object(_object) {}
};
}  // namespace Obj

実際に表示してみる(OpenGL,glut)

ボタンのクラスを作っただけで,表示する機能は無いので一度実装しようと思います。
ボタンが押されると他のページに移動するイメージもあると思うので,シーン管理の簡易版を実装しつつ表示していきます。

以下はOpenGLの視点や光源の処理も記述しています。他のライブラリでは特定の部分を必要な形に書き換える必要があります。

カメラ

透視投影と平行投影が切り替えられるような機能を実装します。3Dを表示する可能性を考慮した実装となります。

// Camera.hpp
#pragma once
#include <GL/glut.h>

#include "Vector3D.hpp"

namespace Scene {
class Camera {
 public:
  Camera() {}
  ~Camera() {}

  inline static void SetActive(bool _isActive = true) { isActive = _isActive; }

  inline static void SetAsPerspective(float _wph, float _fov, float _near, float _far,
                               Obj::Vector3D _pos = Obj::Vector3D(),
                               Obj::Vector3D _lookAt = Obj::Vector3D(),
                               Obj::Vector3D _vec = Obj::Vector3D()) {
    wph = _wph;
    fov = _fov;
    near = _near;
    far = _far;
    r = _pos;
    lookAt = _lookAt;
    vec = _vec;
    isPerspective = true;
    UpdateCamera();
  }
  inline static void SetAsOrtho(Obj::Vector3D _from, Obj::Vector3D _to) {
    from = _from;
    to = _to;
    isPerspective = false;
  }
  // for perspective
  inline static void SetCameraPos(Obj::Vector3D _pos) {
    r = _pos;
    UpdateCamera();
  }
  // for perspective
  inline static void SetCameraLookAt(Obj::Vector3D _lookAt) {
    lookAt = _lookAt;
    UpdateCamera();
  }
  // for perspective
  inline static void SetCameraVec(Obj::Vector3D _vec) {
    vec = _vec;
    UpdateCamera();
  }

  inline static void SetPerspectiveMode(bool _isPerspective) {
    isPerspective = _isPerspective;
  }

  inline static void UpdateCamera() {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (isPerspective) {
      gluPerspective(fov, wph, near, far);
      gluLookAt(r.x, r.y, r.z, lookAt.x, lookAt.y, lookAt.z, vec.x, vec.y,
                vec.z);
    } else {
      glOrtho(from.x, to.x, from.y, to.y, from.z, to.z);
    }
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }

 private:
  static bool isActive;

  static bool isPerspective;
  // perspective
  static float fov;
  static float wph;
  static float near, far;
  static Obj::Vector3D r;
  static Obj::Vector3D lookAt;
  static Obj::Vector3D vec;
  // ortho
  static Obj::Vector3D from, to;
};

}  // namespace Scene
// Camera.cpp
#include "Camera.hpp"

bool Scene::Camera::isActive = true;
bool Scene::Camera::isPerspective = true;

float Scene::Camera::fov = 0;
float Scene::Camera::wph = 0;
float Scene::Camera::far = 0;
float Scene::Camera::near= 0;
Obj::Vector3D Scene::Camera::r = Obj::Vector3D();
Obj::Vector3D Scene::Camera::lookAt = Obj::Vector3D();
Obj::Vector3D Scene::Camera::vec = Obj::Vector3D();
Obj::Vector3D Scene::Camera::from = Obj::Vector3D();
Obj::Vector3D Scene::Camera::to = Obj::Vector3D();

ソースファイルはstatic変数の初期化するためにあるものです。

シーンの抽象クラス

オブジェクトと同様にシーンでも抽象クラスを作り,派生クラスで中身を構築していきます。glutのコールバック関数が呼ばれた際に値がそのまま渡されるようにします。

// SceneBase.hpp
#pragma once
#include "ApplicationPreference.hpp"
#include "Camera.hpp"
#include "ObjectLayer.hpp"

namespace Scene {
class SceneBase {
 public:
  SceneBase() {}
  ~SceneBase() {}

  virtual void Update() = 0;
  virtual void Draw() = 0;
  virtual void KeyboardProc(unsigned char key, int x, int y) = 0;
  virtual void MouseProc(int button, int state, int x, int y) = 0;
  virtual void MotionProc(int x, int y) = 0;
  virtual void PassiveMotionProc(int x, int y) = 0;
  virtual void SpecialFuncProc(int key, int x, int y) = 0;

 protected:
  Obj::ObjectLayer layer2D;

  void SetOrthoCameraWindow();
  // カメラ系関数は含まれていない
  void Set3DDrawMode();
  // カメラ系関数は含まれていない
  void Set2DDrawMode();

  bool mustEscape = false;
};
}  // namespace Scene
// SceneBase.cpp
#include "SceneBase.hpp"

void Scene::SceneBase::SetOrthoCameraWindow() {
  Camera::SetAsOrtho(Obj::Vector3D(0, 0, -.1f),
                     Obj::Vector3D(ApplicationPreference::windowSize.x,
                            ApplicationPreference::windowSize.y, .1f));
}

void Scene::SceneBase::Set3DDrawMode() {
  glEnable(GL_DEPTH_TEST); // 描画ライブラリ関数
  glEnable(GL_LIGHTING); // 描画ライブラリ関数
}
void Scene::SceneBase::Set2DDrawMode() {
  glDisable(GL_DEPTH_TEST); // 描画ライブラリ関数
  glDisable(GL_LIGHTING); // 描画ライブラリ関数
}

シーン派生クラスの作成

SampleSceneというクラスを作ります。

// SampleScene.hpp
#pragma once
#include <GL/glut.h>

#include "Color255.hpp"
#include "SceneBase.hpp"

#include "ButtonObject.hpp"

namespace Scene {
class SampleScene : public SceneBase {
 public:
  SampleScene();
  ~SampleScene() {
    delete sampleButton;
  }

  void Update() override;
  void Draw() override;
  void KeyboardProc(unsigned char key, int x, int y) override {}
  void MouseProc(int button, int state, int x, int y) override {}
  void MotionProc(int x, int y) override {}
  void PassiveMotionProc(int x, int y) override {}
  void SpecialFuncProc(int key, int x, int y) override {}

 private:

  Obj::Button* sampleButton;

};

}  // namespace Scene

ここで

Obj::Button* sampleButton;

を定義しているようにレイヤーへ追加するためにポインターで作成しました。

// SampleScene.cpp
#include "SampleScene.hpp"

Scene::SampleScene::SampleScene() {
  Camera::SetActive(true);
  SceneBase::SetOrthoCameraWindow();
  Camera::UpdateCamera();

  Obj::Vector3D menuButtonSize(500, 75);
  Obj::Color255 buttonInnerColor("#40C8C0");
  Obj::Color255 buttonOutlineColor(35, 57, 40);
  float buttonOutlineWidth = 4.f;

  sampleButton = new Obj::Button(
      Obj::Vector3D(30 + 75, 30 + 25),
      menuButtonSize, true, true);
  sampleButton->SetInnerColor(buttonInnerColor, buttonInnerColor * 0.8,
                               buttonInnerColor * 0.5, buttonInnerColor * 0.9);
  sampleButton->SetOuterColor(buttonOutlineColor, buttonOutlineWidth);
  sampleButton->SetInnerAnimation(.2f);

  layer2D.AddObject(sampleButton);
}

void Scene::SampleScene::Update() {
  layer2D.Collide();

  layer2D.Update();
}

void Scene::SampleScene::Draw() {
  SceneBase::Set2DDrawMode();
  Camera::SetPerspectiveMode(false);
  Camera::UpdateCamera();

  layer2D.Draw();  // 2D描画
}

以下でボタンを宣言しています。

  sampleButton = new Obj::Button(
      Obj::Vector3D(30 + 75, 30 + 25),
      menuButtonSize, true, true);
  sampleButton->SetInnerColor(buttonInnerColor, buttonInnerColor * 0.8,
                               buttonInnerColor * 0.5, buttonInnerColor * 0.9);
  sampleButton->SetOuterColor(buttonOutlineColor, buttonOutlineWidth);
  sampleButton->SetInnerAnimation(.2f);

内側の色を#40C8C0とし,外枠の色をRGB(35,57,40)としています。そして,最後に

layer2D.AddObject(sampleButton);

とすることで,UpdateDrawでの演算対象となり実行してくれます。

OpenGL関連の設定

//GLBuilder.hpp
#pragma once
#include <GL/gl.h>
#include <GL/glut.h>

#include "ApplicationPreference.hpp"

namespace GLSYS {
class GLBuilder {
 public:
  static void GLSetup();

  static void DrawInitialize();
};
}  // namespace GLSYS
// GLBuilder.cpp
#include "GLBuilder.hpp"

void GLSYS::GLBuilder::GLSetup() {
  glClearColor(250.f/255.f, 250.f/255.f, 250.f/255.f, 0.0);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-ApplicationPreference::windowSize.x / 2.,
          ApplicationPreference::windowSize.x / 2.,
          -ApplicationPreference::windowSize.y / 2.,
          ApplicationPreference::windowSize.y / 2., -0.1, 0.1);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glEnable(GL_DEPTH_TEST);
}

void GLSYS::GLBuilder::DrawInitialize() {
  glClear(GL_COLOR_BUFFER_BIT);
  glPixelZoom(1, 1);
  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
  // ここからは3Dを想定した初期化
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
}

初期化処理

// ApplicationBuilder.hpp
#pragma once
#include "MouseInput.hpp"
#include "Time.hpp"

namespace GLSYS {
class ApplicationBuilder {
 public:
  static void Initialize();
  static void Update();
};
}  // namespace GLSYS
#include "ApplicationBuilder.hpp"

void GLSYS::ApplicationBuilder::Initialize() {
  Obj::Time::Initialize();
}

void GLSYS::ApplicationBuilder::Update() {
  Obj::Time::Update();
  Input::MouseInput::Update();
}

ここで,前回の初期化処理が必要であった

Obj::Time::Initialize();

がここで行われています。そのため,後ほどmain関数で呼び出す必要があります。
また,

void GLSYS::ApplicationBuilder::Update() {
  Obj::Time::Update();
  Input::MouseInput::Update();
}

にて,マウスの状態更新と時間の経過を測定することで目的のアニメーションが可能になります。
そのため,後のコールバック関数のアイドルコールバックにてGLSYS::ApplicationBuilder::Update()を呼ぶ必要があります。

シーン管理

次に現在のシーンは何が表示されてどのように表示するかを管理するSceneManagerを作成します。
現在のシーンのポインタを格納し,次のシーンが呼ばれたら現在のポインタを解放するという単純なものを作ります。

// SceneManager.hpp
#pragma once
#include "SceneBase.hpp"

namespace Scene {
class SceneManager {
 public:
  static SceneBase* GetCurrentScene() { return currentScene; }
  static void ChangeScene(SceneBase* _scene);

 private:
  // write current scene here.
  static SceneBase* currentScene;
};
}  // namespace Scene

とし,currentSceneに現在のシーンを格納します。

// SceneManager.cpp
#include "SceneManager.hpp"

#include "SceneBase.hpp"

Scene::SceneBase* Scene::SceneManager::currentScene = nullptr;

void Scene::SceneManager::ChangeScene(SceneBase* _scene) {
  if (currentScene != nullptr) delete (currentScene);
  currentScene = _scene;
}

初期化時はnullptrにしておきます。

コールバック関数

glutで設定するコールバック関数をここで宣言します。

// Callback.cpp
#include <GL/glut.h>

#include "ApplicationBuilder.hpp"
#include "GLBuilder.hpp"
#include "MouseInput.hpp"
#include "SceneManager.hpp"

void CloseFunc() {}

void DisplayProc() {
  GLSYS::GLBuilder::DrawInitialize();

  glPushMatrix();
  Scene::SceneManager::GetCurrentScene()->Draw();
  glPopMatrix();

  glutSwapBuffers();
}

void WindowResizeProc(int width, int height) {
  if (width != ApplicationPreference::windowSize.x ||
      height != ApplicationPreference::windowSize.y)
    glutReshapeWindow(ApplicationPreference::windowSize.x,
                      ApplicationPreference::windowSize.y);
}

void KeyboardProc(unsigned char key, int x, int y) {
  Scene::SceneManager::GetCurrentScene()->KeyboardProc(
      key, x, ApplicationPreference::windowSize.y - y);
  glutPostRedisplay();
}

void IdleProc() {
  GLSYS::ApplicationBuilder::Update();
  Scene::SceneManager::GetCurrentScene()->Update();
  glutPostRedisplay();
}

void MouseProc(int button, int state, int x, int y) {
  Scene::SceneManager::GetCurrentScene()->MouseProc(
      button, state, x, ApplicationPreference::windowSize.y - y);
  Input::MouseInput::UpdateMouseState(button, state);
  Input::MouseInput::SetMousePos(
      Obj::Vector3D(x, ApplicationPreference::windowSize.y - y));
  glutPostRedisplay();
}

void MotionProc(int x, int y) {
  Scene::SceneManager::GetCurrentScene()->MotionProc(
      x, ApplicationPreference::windowSize.y - y);
  Input::MouseInput::SetMousePos(
      Obj::Vector3D(x, ApplicationPreference::windowSize.y - y));
  glutPostRedisplay();
}

void PassiveMotionProc(int x, int y) {
  Scene::SceneManager::GetCurrentScene()->PassiveMotionProc(
      x, ApplicationPreference::windowSize.y - y);
  Input::MouseInput::SetMousePos(
      Obj::Vector3D(x, ApplicationPreference::windowSize.y - y));
  glutPostRedisplay();
}

特筆すべきはマウスに対して何らかのアクションがあった場合に常に更新をさせているというところです。glutで作成する都合上,マウスへ渡すy座標は反転しております。

アイドルコールバックはこのようになっています。

void IdleProc() {
  GLSYS::ApplicationBuilder::Update();
  Scene::SceneManager::GetCurrentScene()->Update();
  glutPostRedisplay();
}

時間・マウスを更新した後,オブジェクトのアニメーションの値を更新し,最後に描画するという形を取っています。このため,マウスを操作していない間も常にアニメーションが動くようになります。

main関数

ついにmain関数を実装します。最初に呼び出すシーンだけ宣言してSceneManagerChangeSceneで渡すようにします。これはシーンの派生先からでも同じことが可能です。

// main.cpp
#include <GL/glut.h>
#include <stdio.h>
// #include <GL/freeglut.h>

#include "GLBuilder.hpp"
#include "SceneManager.hpp"
#include "ApplicationBuilder.hpp"
#include "ApplicationPreference.hpp"
// 最初に呼び出すシーン
#include "SampleScene.hpp"

void DisplayProc(), KeyboardProc(unsigned char key, int x, int y), IdleProc(),
    MouseProc(int button, int state, int x, int y), MotionProc(int x, int y),
    PassiveMotionProc(int x, int y), WindowResizeProc(int width, int height), CloseFunc();

int main(int argc, char **argv) {
  glutInit(&argc, argv);
  glutInitWindowSize((int)ApplicationPreference::windowSize.x,
                     (int)ApplicationPreference::windowSize.y);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);

  glutCreateWindow(argv[0]);

  GLSYS::GLBuilder::GLSetup();
  GLSYS::ApplicationBuilder::Initialize();

  // 関数登録
  glutDisplayFunc(DisplayProc);
  glutReshapeFunc(WindowResizeProc);
  glutKeyboardFunc(KeyboardProc);
  glutIdleFunc(IdleProc);
  glutMouseFunc(MouseProc);
  glutMotionFunc(MotionProc);
  glutPassiveMotionFunc(MotionProc);
  // glutCloseFunc(CloseFunc); // freeglut

  Scene::SampleScene *titleScene = new Scene::SampleScene();
  Scene::SceneManager::ChangeScene(titleScene);

  glutMainLoop();

  return 0;
}

以下の部分

  Scene::SampleScene *titleScene = new Scene::SampleScene();
  Scene::SceneManager::ChangeScene(titleScene);

ここがシーンの宣言場所です。これ以降の遷移は派生先のシーンで行われることになります。

表示例

以上の宣言で実行画面はこのようになります。
image.png

これで最初に掲載したGIFのように動作します。

まとめ: Ⅱ

  • 特定のクラスから継承することでレイヤとして1つにまとめることができる。
  • ボタン以外を実装する際も四角形なら親クラスの関数を呼ぶことで判定を実現でき,簡略化することが可能である。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0