最近知った与太話
class MyClass
{
private:
int a = 0;
public:
void get(MyClass& tgt) {
this->a = tgt.a;
}
};
VS2017でリリースビルドしたときこれが通っちゃったんです。
変数aはプライベートな筈なのに...クラスでならちゃんとコンパイルエラーが出ます。
理屈はわかりません。
→単なる勘違いでした、どんな場合でもコンパイルエラーは出ないです
はじめに
今回は3Dの座標(VECTOR)に関してすっとばします。次回MATRIXと併せて解説したい所存です。
エフェクシアを導入して使えるようにする過程だけを書きます。
2.ゲームループの作成とエフェクトの用意
今回はエフェクシアを導入し、エフェクトのハンドル及びエフェクトの管理を行うクラスを作ります。
その前に、エフェクトの再生には演算、描画の更新を行うループ(以降ゲームループと表記)が必要です。そちらも作っていきましょう
ゲームループを作ろう
前回までのWinMainがこちらです。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//コピーしてはいけない場合unique_ptrを使おう
auto draw = std::make_unique<DXDraw>();
{
DrawPixel(320, 240, GetColor(255, 255, 255)); // 点を打つ
WaitKey(); // キー入力待ち
}
return 0;// ソフトの終了
}
これではWaitKeyの部分で処理が停止してしまっています。
3Dをヌルヌル動かしたいのに毎回毎回停止させるのはダメです。そこでゲームループを作ります。
たいていのゲームでは画面を毎秒60回(60FPS)更新します。それに合わせ、サンプルをもとに下のように改良しました。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//コピーしてはいけない場合unique_ptrを使おう
auto draw = std::make_unique<DXDraw>();
//メインループ
while (ProcessMessage() == 0) {
/*演算*/
{
}
/*描画*/
{
//3D(主描画)
{
}
//2D(UI:ユーザーインターフェース)
{
DrawPixel(320, 240, GetColor(255, 255, 255)); // 点を打つ
}
}
ScreenFlip();
//ESCキーを押すとゲームループから抜けます
if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) {
break;
}
}
return 0;// ソフトの終了
}
**ProcessMessage()**はOSとアプリケーションとの間であれこれやり取りをする関数です。やりとりが正常に行われている限りDXLIBを使用し続けられます。
**ScreenFlip()**は画像を最終的に画面に表示する役割を担います。垂直同期までやってくれている重要な関数です。
垂直同期とは、ディスプレイの更新レートとアプリケーションの描画のタイミングを合わせることでちらつき(ティアリング)を防ぐ為のものです。
前回の設定項目に合った** SetWaitVSyncFlag(TRUE)**関数で操作することによりオンオフを決めることができます。
両方ともループ中に1回だけ呼ぶ必要があります。大事な項目なので忘れずに!
描画自体は変わりませんが、内部は大きく進歩しました。
つい先日知ったのですが、Unityでのゲームループの概略図が載っています。わかりやすいですね。
エフェクシアを呼ぼう
次にエフェクシアを入れましょう。DLはこちらから行います。
付属しているDXLIB本体は古い場合がありますので、DXライブラリ置き場の最新版を使用してください。
まずはDXDrawの改良から。EffekseerForDXLib.hをDxLib.hのあとにインクルードします。
# include "DxLib.h"
# include"EffekseerForDXLib.h"
DXDrawのコンストラクタ、デストラクタにも加筆しましょう。Effekseerを含んだコメントのある行が追加個所です。
//コンストラクタ
DXDraw() {
SetOutApplicationLogValidFlag(FALSE); /*log*/
SetMainWindowText("game title"); /*タイトル*/
ChangeWindowMode(TRUE); /*窓表示*/
SetUseDirect3DVersion(DX_DIRECT3D_11); /*directX ver*/
SetGraphMode(640, 480, 32); /*解像度*/
SetUseDirectInputFlag(TRUE); /*DirectInput使用*/
SetDirectInputMouseMode(TRUE); /*DirectInputマウス使用*/
SetWindowSizeChangeEnableFlag(FALSE, FALSE); /*ウインドウサイズを手動変更不可、ウインドウサイズに合わせて拡大もしないようにする*/
SetUsePixelLighting(TRUE); /*ピクセルライティングの使用*/
SetFullSceneAntiAliasingMode(4, 2); /*アンチエイリアス*/
SetEnableXAudioFlag(TRUE); /*XAudioを用いるか*/
Set3DSoundOneMetre(1.0f); /*3Dオーディオの基準距離指定*/
SetWaitVSyncFlag(TRUE); /*垂直同期*/
DxLib_Init(); /*DXライブラリ初期化処理*/
Effekseer_Init(8000); /*Effekseerの初期化*/
SetSysCommandOffFlag(TRUE); /*タスクスイッチを有効にするかどうかを設定する*/
SetChangeScreenModeGraphicsSystemResetFlag(FALSE); /*Effekseer用*/
Effekseer_SetGraphicsDeviceLostCallbackFunctions(); /*Effekseer用*/
SetAlwaysRunFlag(TRUE); /*background*/
SetUseZBuffer3D(TRUE); /*zbufuse*/
SetWriteZBuffer3D(TRUE); /*zbufwrite*/
MV1SetLoadModelPhysicsWorldGravity(-9.8f); /*重力*/
SetWindowSize(640, 480); /*表示するウィンドウサイズ*/
}
//デストラクタ
~DXDraw() {
Effkseer_End(); /*effkseer終了*/
DxLib_End(); /*DXライブラリ使用の終了処理*/
}
これでエフェクシア自体の読み込み、破棄は完了しました。
エフェクトを再生しよう
エフェクシアも大体の仕組みは似ていて、intで取ったハンドルをもとに操作していく形となります。
さて、エフェクシアのハンドルについてです。一応intでいいんですが、intだと他リソースハンドルとの分別が付かないという問題があります。
そこで自分が以前 @yumetodo さんにお世話になった際に頂いたリソース管理クラスを使います。
3Dモデル、画像、サウンドに関してもございますが、必要になった際に使用します。一番手前に出してあるものをご利用ください
・DXLib_vec.hpp
・EffekseerEffectHandle.hpp
これを「プロジェクトに追加すべきファイル_VC用」ディレクトリかメインのソースがある場所に置いてインクルードしてください。
次にエフェクトハンドルを利用してエフェクト単体の情報をまとめる小さいクラスを作ります。
# include "DXLib_vec.hpp"
# include "EffekseerEffectHandle.hpp"
# include <memory>
# include <array>
# include <vector>
# include <cmath>
//エフェクト
class EffectS {
public:
bool flug = false; /*エフェクトの表示フラグ*/
size_t id = 0; /*エフェクトのID*/
Effekseer3DPlayingHandle handle; /*エフェクトのハンドル*/
VECTOR pos; /*エフェクトの場所*/
VECTOR nor; /*エフェクトのY軸の向き*/
float scale = 1.f; /*エフェクトのスケール*/
/*エフェクトの場所を指定し開始フラグを立てる*/
void set(const VECTOR& pos_, const VECTOR& nor_, float scale_ = 1.f) {
this->flug = true;
this->pos = pos_;
this->nor = nor_;
this->scale = scale_;
}
/*エフェクトの開始フラグが立っていればエフェクトを再生*/
void put(const EffekseerEffectHandle& handle_) {
if (this->flug) {
//再生チェック
if (this->handle.IsPlaying()) {
this->handle.Stop();
}
//再生
this->handle = handle_.Play3D();
//場所を指定
this->handle.SetPos(this->pos);
//向きを指定
this->handle.SetRotation(atan2(this->nor.y, std::hypotf(this->nor.x, this->nor.z)), atan2(-this->nor.x, -this->nor.z), 0);
//大きさを指定
this->handle.SetScale(this->scale);
//開始フラグを切る
this->flug = false;
}
}
};
場所、向き、サイズを固定して生成フラグが立った際にエフェクトを生成できます。
ではエフェクトリソースを読み込みましょう。今回はメインのソースと同じディレクトリにefkファイルを置いています。
エフェクシアのサンプルエフェクトなどを用意してください。(自分は0.efkを用意しています)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//コピーしてはいけない場合unique_ptrを使おう
auto draw = std::make_unique<DXDraw>();
//
std::vector<EffekseerEffectHandle> effHndle;/*エフェクトリソース*/
std::vector<EffectS> effcs; /*エフェクト管理*/
//事前用意
/*エフェクトリソースの読み込み*/
effHndle.resize(effHndle.size() + 1);
effHndle.back() = EffekseerEffectHandle::load("0.efk");
/*メインループで使用する際の管理クラスの用意*/
effcs.resize(effHndle.size());
//ゲームループ
while (ProcessMessage() == 0) {
/*演算*/
{
//スペースを押しているときエフェクトを設定する
if (CheckHitKey(KEY_INPUT_SPACE) != 0) {
effcs[0].set(VGet(0.f, 0.f, 0.f), VGet(0.f, 1.f, 0.f), 0.1f);
}
}
//エフェクトの更新
{
for (size_t index = 0;index < effHndle.size();++index) {
effcs[index].put(effHndle[index]);
}
}
/*描画*/
{
//3D(主描画)
Effekseer_Sync3DSetting();
{
UpdateEffekseer3D();
//draw3D
DrawEffekseer3D();
}
//2D(UI:ユーザーインターフェース)
{
DrawPixel(320, 240, GetColor(255, 255, 255)); // 点を打つ
}
}
ScreenFlip();
//
if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) {
break;
}
}
return 0;// ソフトの終了
}
スペースを押すとエフェクトが再生されるプログラムです。
メインループで追加したものは以下のものです。
**Effekseer_Sync3DSetting()**はエフェクシア内のカメラ設定とDXライブラリのカメラ設定を合わせます(後述)
**UpdateEffekseer3D()**はエフェクシアで扱うエフェクトの演算をします。
**DrawEffekseer3D()**はエフェクシアで扱うエフェクトを全部描画します。
カメラを設定しよう
ようやくエフェクト再生ができると思いきや…スペースを押しても画面に変わりはありません。
なぜかと言えばカメラを設定していないからです。エフェクトとは違う方向を向いてしまっているのです。
カメラを設定するには以下の関数を使います。
SetDrawScreen(DX_SCREEN_BACK) 描画する画面を指定します。
**SetCameraNearFar(near_, far_)**は描画する範囲を決めます。near_~far_の距離の中のものが描画できます。
**SetupCamera_Perspective(fov)**は視野角を決めます。ラジアンで指定します(今回は45度)
**SetCameraPositionAndTargetAndUpVec(campos, camvec, camup)**はカメラの場所、カメラの注視点、カメラのY軸の向きを指定します。
注視点をVGet(0.f,0.f,0.f)としているため、同じ場所に生成したエフェクトを見る形になります。
ここら辺も後々解説します。
そしてこれらをまとめた関数を一時的にDXDrawに持ってもらいましょう。
//裏画面のセッティング
void SetDraw_Screen(const bool& clear = true) {
SetDrawScreen(DX_SCREEN_BACK);
if (clear) {
ClearDrawScreen();
}
}
//裏画面のカメラ情報付きセッティング
void SetDraw_Screen(const VECTOR& campos, const VECTOR& camvec, const VECTOR& camup, const float& fov, const float& near_, const float& far_) {
SetDraw_Screen(true);
SetCameraNearFar(near_, far_);
SetupCamera_Perspective(fov);
SetCameraPositionAndTargetAndUpVec(campos, camvec, camup);
}
//
描画部分も改善します。
while (ProcessMessage() == 0) {
/*演算*/
{
//スペースを押しているときエフェクトを設定する
if (CheckHitKey(KEY_INPUT_SPACE) != 0) {
effcs[0].set(VGet(0.f, 0.f, 0.f), VGet(0.f, 1.f, 0.f), 0.1f);
}
}
//エフェクトの更新
{
for (size_t index = 0;index < effHndle.size();++index) {
effcs[index].put(effHndle[index]);
}
}
/*描画*/
{
//3D(主描画)
draw->SetDraw_Screen(VGet(0, 0, 10), VGet(0, 0, 0), VGet(0, 1, 0), DX_PI_F / 4, 0.5f, 20.f);
Effekseer_Sync3DSetting();
{
UpdateEffekseer3D();
//draw3D
DrawEffekseer3D();
}
//2D(UI:ユーザーインターフェース)
{
DrawPixel(320, 240, GetColor(255, 255, 255)); // 点を打つ
}
}
ScreenFlip();
//
if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) {
break;
}
}
こうすることで画面の中心にエフェクトが見えるようになりました。
今回の全体像
今回書いたコードの全体です。
# include "DxLib.h"
# include "EffekseerForDXLib.h"
# include <memory>
# include <array>
# include <vector>
# include <cmath>
# include "DXLib_vec.hpp"
# include "EffekseerEffectHandle.hpp"
//エフェクト
class EffectS {
public:
bool flug = false; /*エフェクトの表示フラグ*/
size_t id = 0; /*エフェクトのID*/
Effekseer3DPlayingHandle handle; /*エフェクトのハンドル*/
VECTOR pos; /*エフェクトの場所*/
VECTOR nor; /*エフェクトのY軸の向き*/
float scale = 1.f; /*エフェクトのスケール*/
/*エフェクトの場所を指定し開始フラグを立てる*/
void set(const VECTOR& pos_, const VECTOR& nor_, float scale_ = 1.f) {
this->flug = true;
this->pos = pos_;
this->nor = nor_;
this->scale = scale_;
}
/*エフェクトの開始フラグが立っていればエフェクトを再生*/
void put(const EffekseerEffectHandle& handle_) {
if (this->flug) {
//再生チェック
if (this->handle.IsPlaying()) {
this->handle.Stop();
}
//再生
this->handle = handle_.Play3D();
//場所を指定
this->handle.SetPos(this->pos);
//向きを指定
this->handle.SetRotation(atan2(this->nor.y, std::hypotf(this->nor.x, this->nor.z)), atan2(-this->nor.x, -this->nor.z), 0);
//大きさを指定
this->handle.SetScale(this->scale);
//開始フラグを切る
this->flug = false;
}
}
};
//いろいろまとめるクラス
class DXDraw {
public:
//コンストラクタ
DXDraw() {
SetOutApplicationLogValidFlag(FALSE); /*log*/
SetMainWindowText("game title"); /*タイトル*/
ChangeWindowMode(TRUE); /*窓表示*/
SetUseDirect3DVersion(DX_DIRECT3D_11); /*directX ver*/
SetGraphMode(640, 480, 32); /*解像度*/
SetUseDirectInputFlag(TRUE); /*DirectInput使用*/
SetDirectInputMouseMode(TRUE); /*DirectInputマウス使用*/
SetWindowSizeChangeEnableFlag(FALSE, FALSE); /*ウインドウサイズを手動変更不可、ウインドウサイズに合わせて拡大もしないようにする*/
SetUsePixelLighting(TRUE); /*ピクセルライティングの使用*/
SetFullSceneAntiAliasingMode(4, 2); /*アンチエイリアス*/
SetEnableXAudioFlag(TRUE); /*XAudioを用いるか*/
Set3DSoundOneMetre(1.0f); /*3Dオーディオの基準距離指定*/
SetWaitVSyncFlag(TRUE); /*垂直同期*/
DxLib_Init(); /*DXライブラリ初期化処理*/
Effekseer_Init(8000); /*Effekseerの初期化*/
SetSysCommandOffFlag(TRUE); /*タスクスイッチを有効にするかどうかを設定する*/
SetChangeScreenModeGraphicsSystemResetFlag(FALSE); /*Effekseer用*/
Effekseer_SetGraphicsDeviceLostCallbackFunctions(); /*Effekseer用*/
SetAlwaysRunFlag(TRUE); /*background*/
SetUseZBuffer3D(TRUE); /*zbufuse*/
SetWriteZBuffer3D(TRUE); /*zbufwrite*/
MV1SetLoadModelPhysicsWorldGravity(-9.8f); /*重力*/
SetWindowSize(640, 480); /*表示するウィンドウサイズ*/
}
//デストラクタ
~DXDraw() {
Effkseer_End(); /*effkseer終了*/
DxLib_End(); /*DXライブラリ使用の終了処理*/
}
//裏画面のセッティング
void SetDraw_Screen(const bool& clear = true) {
SetDrawScreen(DX_SCREEN_BACK);
if (clear) {
ClearDrawScreen();
}
}
//裏画面のカメラ情報付きセッティング
void SetDraw_Screen(const VECTOR& campos, const VECTOR& camvec, const VECTOR& camup, const float& fov, const float& near_, const float& far_) {
SetDraw_Screen(true);
SetCameraNearFar(near_, far_);
SetupCamera_Perspective(fov);
SetCameraPositionAndTargetAndUpVec(campos, camvec, camup);
}
//
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//コピーしてはいけない場合unique_ptrを使おう
auto draw = std::make_unique<DXDraw>();
//
std::vector<EffekseerEffectHandle> effHndle; /*エフェクトリソース*/
std::vector<EffectS> effcs;//メインループで使うエフェクト
//事前用意
//エフェクトファイルを探す
effHndle.resize(effHndle.size() + 1);
effHndle.back() = EffekseerEffectHandle::load("data/effect/0.efk");
effcs.resize(effHndle.size());//エフェクトのセットアップ
//メインループ
while (ProcessMessage() == 0) {
/*演算*/
{
//スペースを押しているときエフェクトを設定する
if (CheckHitKey(KEY_INPUT_SPACE) != 0) {
effcs[0].set(VGet(0.f, 0.f, 0.f), VGet(0.f, 1.f, 0.f), 0.1f);
}
}
//エフェクトの更新
{
for (size_t index = 0;index < effHndle.size();++index) {
effcs[index].put(effHndle[index]);
}
}
/*描画*/
{
//3D(主描画)
draw->SetDraw_Screen(VGet(0, 0, 10), VGet(0, 0, 0), VGet(0, 1, 0), DX_PI_F / 4, 0.5f, 20.f);
Effekseer_Sync3DSetting();
{
UpdateEffekseer3D();
//draw3D
DrawEffekseer3D();
}
//2D(UI:ユーザーインターフェース)
//draw->SetDraw_Screen(false);
{
DrawPixel(320, 240, GetColor(255, 255, 255)); // 点を打つ
}
}
ScreenFlip();
//
if (CheckHitKey(KEY_INPUT_ESCAPE) != 0) {
break;
}
}
return 0;// ソフトの終了
}
まとめ
駆け足でエフェクトの整備を行いました。次回は今回飛ばした座標に関してと3Dモデルの表示を行います。
おまけ
後々解説します。
解説をすっ飛ばしたリスト
・VECTOR_ref
VECTORにオペレーターを付けて計算しやすくしたクラス。位置、加速度などの演算に使います。
・MATRIX_ref
MATRIXにオペレーターを付けて計算しやすくしたクラス。回転演算に使います
・3Dのカメラ設定
SetCameraPositionAndTargetAndUpVecなどを用いてカメラ座標、注視点、頭の向き、視野角、ニアファーを指定します。
GraphHandleの開設の際に正式に実装します。
できたら解説したいリスト
・const参照
普通に値を見るよりは無駄がないのでよく使っています。
・forの速度比較
イテレーターを移動していくのがかなり遅かったので比較してみようと思います