LoginSignup
1
1

More than 1 year has passed since last update.

DXライブラリでゲームの基礎を作ろう3 位置角度編

Posted at

大昔からの与太話

前回、いつも使ってるのに何であんなこと書いたんだろう…それはさておき与太話。

const参照、詳細を話すと長くなりますがとても便利です。

普通の値渡し(int aとか)だと無駄にコピーがとられることになります。
そこでC言語ジャンキーならコピーを回避するためにポインタを使うかもしれません。しかしC++はその一歩上を行きました。参照です。

参照はポインタのように&や*でポインタと実体との変換を行う必要はありませんし、ポインタのように実体の変更を参照元に反映できます。
変更が反映されないように参照したい…という場合でもconstを付ければOK。

関数の引数はconst参照をバンバン使っていきましょう!

はじめに

今回は3Dの座標(VECTOR,MATRIX)について解説します。表現の幅を広げる大事な箇所ですね。

3.3Dの位置回転を制御しよう

3Dで作成したオブジェクトの位置回転を制御する方法を追加します。

VECTORとは

3D空間では、XYZの三つの軸をもとにその軸方向にどれだけ離れているかを示すことで位置を表すことができます。
DXライブラリで設定されているVECTORはXYZのfloatデータを持った構造体となります。

VECTORの演算

さて、空間座標などの名目で3次元の演算を高校で習うかもしれませんので、基本的な内容をDXライブラリに沿っておさらいしましょう。

VGet

VECTORの中身を指定してVECTORを返します。

VAdd

VECTORどうしの足し算をします。イメージとしては元のベクトルの先に足したベクトルが付くような感じですね。

VSub

VECTORどうしの引き算をします。

VDot

VECTORどうしの内積をします。cosを取得するので、正負を見ることで元のベクトルに対し前方(90度の範囲)を向いているかいないかがわかります。

VCross

VECTORどうしの外積をします。sinを取得するので、(ちょっと工夫すると)正負を見ることで元のベクトルに対し左右どちらを向いているかの判別に使えます。

VScale

VECTORのスケーリングをします。

VSize

VECTORのサイズを取得します。スカラー。

VNorm

VECTORのサイズを1にします。サイズがもともと0のときは…?

…これめんどくさくない?

そうですね、足し算をするのに

vect1 = VAdd(vect1,vect2);

みたいなことをするより

vect1 += vect2;

とできる方がいいですよね。わかりやすいし数学っぽいです。

オペレーター3分クッキング

そこで使うのがoperater!c++の便利な機能です。
こちらはclassの関数として追加することで、演算子を用いた処理を簡単に追加することができます。

例として

class intel {
    int data = 0;//hansei
public:
    int operator+(const intel& tgt) {//+を使った際にする処理を指定
        return this->data + tgt.data;
    }
};
const intel add(const intel& a, const intel& b) {
    return a + b;
}

こんな感じで、例では+を用いた場合の演算を決めています。

ではこれを用いたVECTORのclassを作りましょう。完成品はこちら!

…実は前回使用したDXLib_vec.hppの中にVECTOR_refとしてあったんですね。
詳細はVECTOR_refクラスを実際に見てください。

operator==

xyz値がすべて等しいかを返します。float同士で比較するため原点などでない限り意味はほぼないと思われます。

operator!=

==の逆です。

operator+、operator+=

足し算

operator-、operator-=

引き算

operator*、operator*=

n倍のスケーリングをします

operator/、operator/=

1/nのスケーリングをします

cross

外積をします

dot

内積をします

Norm

サイズを1に

clear

中身を原点にします。

size

サイズ取得

get()

VECTORを返します。DXライブラリ本体に渡す際に使います。

x()、y()、z()

xyzそれぞれの値を見ます

vget

VGetのようにVECTOR_refを返します

前回指定したエフェクトの位置を変更してみよう

前回のエフェクトは位置を固定していましたが、今回は見回せるようにしてみましょう。ついでにVECTORで示していた部分をVECTOR_refに直します。

//エフェクト
class EffectS {
public:
    bool flug = false;                  /*エフェクトの表示フラグ*/
    size_t id = 0;                      /*エフェクトのID*/
    Effekseer3DPlayingHandle handle;    /*エフェクトのハンドル*/
    VECTOR_ref pos;                         /*エフェクトの場所*/
    VECTOR_ref nor;                         /*エフェクトのY軸の向き*/
    float scale = 1.f;                  /*エフェクトのスケール*/
    /*エフェクトの場所を指定し開始フラグを立てる*/
    void set(const VECTOR_ref& pos_, const VECTOR_ref& 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_ref& campos, const VECTOR_ref& camvec, const VECTOR_ref& camup, const float& fov, const float& near_, const float& far_) {
        SetDraw_Screen(true);
        SetCameraNearFar(near_, far_);
        SetupCamera_Perspective(fov);
        SetCameraPositionAndTargetAndUpVec(campos.get(), camvec.get(), camup.get());
    }
    //
};

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());//エフェクトのセットアップ
    //エフェクトの位置
    VECTOR_ref effect_pos = VECTOR_ref::vget(0.f, 0.f, 0.f);
    //カメラの注視点
    VECTOR_ref camera_target_pos = effect_pos;
    //カメラの位置
    VECTOR_ref camera_pos = effect_pos;
    //演算用の値
    float x = 0.f;
    float z = 0.f;
    float rad = 0.f;
    float size = 10.f;
    //メインループ
    while (ProcessMessage() == 0) {
        /*演算*/
        {
            //スペースを押しているときエフェクトを設定する
            if (CheckHitKey(KEY_INPUT_SPACE) != 0) {
                effcs[0].set(effect_pos, VGet(0.f, 1.f, 0.f), 0.1f);
            }
            if (CheckHitKey(KEY_INPUT_A) != 0) {
                rad += DX_PI_F / 180 * 6 * 60 / GetFPS();
            }
            if (CheckHitKey(KEY_INPUT_D) != 0) {
                rad -= DX_PI_F / 180 * 6 * 60 / GetFPS();
            }
        }
        //エフェクトの更新
        {
            for (size_t index = 0;index < effHndle.size();++index) {
                effcs[index].put(effHndle[index]);
            }
        }
        /*描画*/
        {
            //3D(主描画)
            {
                x = cos(rad) * size;
                z = sin(rad) * size;
                camera_target_pos = effect_pos;//注視点はエフェクトの位置
                camera_pos = camera_target_pos + VECTOR_ref::vget(x, 0.f, z);
            }
            draw->SetDraw_Screen(camera_pos, camera_target_pos, 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;// ソフトの終了 
}

ADキーを押すことでエフェクトの周りをまわるようにカメラが動きます。
まだ恩恵は大きくないですが、反射などを実装するときに涙が出るほど楽になると思います。
では続いてMATRIXの説明に移りましょう。

MATRIXとは

MATRIXとは、4x4の行列を取り扱う構造体です。4元数などとも言われますね。
行列については「数や記号や式などを縦と横に矩形状に配列したものである。」(Wikipediaママ)ということで各自で調べてもらうとして、
MATRIXを使うことで角度や位置をすべて確保し、制限(ジンバルロックなど)にとらわれず自在に回転させることすらできるようになります。
自分は位置は別途VECTOR_refで持ち、回転の演算ではMATRIXを使っています。

こいつもオペレーターを使おう

はい、DXLib_vecの中にMATRIX_refがあります。そちらが完成品です。

operator+、operator+=

行列どうしの足し算です。

operator*、operator*=

行列どうしの掛け算です。行列の場合掛ける順序で結果が変化する点に注意しましょう

Scale

スケーリングをします

Inverse

逆行列を出します。元の行列 * 元の行列.Inverse()=単位行列となります。

clear

単位行列にリセットします

get

MATRIXを返します。

Axis1、Axis2

指定したxyzベクトルを持ったMATRIX_refを返します。位置を指定することもできます

Axis1_YZ

AXis1はYZだけあれば後は外積でX軸が作れるため、Xの入力を省いたものです。

RotX、RotY、RotZ

それぞれの軸で回転するMATRIX_refを出します

RotAxis

任意の軸で回転するMATRIX_refを出します

RotVec2

あるベクトルからあるベクトルへ向くためのMATRIX_refを出します

GetScale

指定したVECTOR_refぶんの拡大成分を持つMATRIX_refを出します

Mtrans

指定したVECTOR_refぶんの移動成分を持つMATRIX_refを出します

Vtrans

VECTOR_refをMATRIX_refで動かしたものを出力します

pos

MATRIX_refの持つ位置成分のVECTOR_refを出します

xvec、yvec、zvec

位置成分を無視したそれぞれの軸のVECTOR_refを出します。

演算してみよう

さて、先ほどはこんなことをしてカメラの位置を求めていましたね。

x = cos(rad) * size;
z = sin(rad) * size;
camera_target_pos = effect_pos;//注視点はエフェクトの位置
camera_pos = camera_target_pos + VECTOR_ref::vget(x, 0.f, z);

わざわざcos、sinで演算するのもいいのですが、MATRIX(_ref)でこの通り

camera_target_pos = effect_pos;//注視点はエフェクトの位置
camera_pos = camera_target_pos + (MATRIX_ref::RotY(rad).zvec() * size);

RotYはY軸回転を行う行列演算です。これでradぶんの回転を取得ます。
zvec()を用いると、z軸向きのサイズ1のベクトルにMATRIXの持つ回転を付与したベクトルを取得できます。
それにVECTOR_refのスケーリング(operator*に付与しています)を用いると完成です。

今回の全体像

今回書いたコードの全体です。

#include "DxLib.h"
#include"EffekseerForDXLib.h"
#include<memory>
#include <array>
#include <vector>
#include <cmath>

#include"DXLib_ref/DXLib_vec.hpp"
#include"DXLib_ref/EffekseerEffectHandle.hpp"

//エフェクト
class EffectS {
public:
    bool flug = false;                  /*エフェクトの表示フラグ*/
    size_t id = 0;                      /*エフェクトのID*/
    Effekseer3DPlayingHandle handle;    /*エフェクトのハンドル*/
    VECTOR_ref pos;                         /*エフェクトの場所*/
    VECTOR_ref nor;                         /*エフェクトのY軸の向き*/
    float scale = 1.f;                  /*エフェクトのスケール*/
    /*エフェクトの場所を指定し開始フラグを立てる*/
    void set(const VECTOR_ref& pos_, const VECTOR_ref& 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_ref& campos, const VECTOR_ref& camvec, const VECTOR_ref& camup, const float& fov, const float& near_, const float& far_) {
        SetDraw_Screen(true);
        SetCameraNearFar(near_, far_);
        SetupCamera_Perspective(fov);
        SetCameraPositionAndTargetAndUpVec(campos.get(), camvec.get(), camup.get());
    }
    //
};

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());//エフェクトのセットアップ
    //エフェクトの位置
    VECTOR_ref effect_pos = VECTOR_ref::vget(0.f, 0.f, 0.f);
    //カメラの注視点
    VECTOR_ref camera_target_pos = effect_pos;
    //カメラの位置
    VECTOR_ref camera_pos = effect_pos;
    //演算用の値
    float rad = 0.f;
    float size = 10.f;
    //メインループ
    while (ProcessMessage() == 0) {
        /*演算*/
        {
            //スペースを押しているときエフェクトを設定する
            if (CheckHitKey(KEY_INPUT_SPACE) != 0) {
                effcs[0].set(effect_pos, VGet(0.f, 1.f, 0.f), 0.1f);
            }
            if (CheckHitKey(KEY_INPUT_A) != 0) {
                rad += DX_PI_F / 180 * 6 * 60 / GetFPS();
            }
            if (CheckHitKey(KEY_INPUT_D) != 0) {
                rad -= DX_PI_F / 180 * 6 * 60 / GetFPS();
            }
        }
        //エフェクトの更新
        {
            for (size_t index = 0;index < effHndle.size();++index) {
                effcs[index].put(effHndle[index]);
            }
        }
        /*描画*/
        {
            //3D(主描画)
            {
                camera_target_pos = effect_pos;//注視点はエフェクトの位置
                camera_pos = camera_target_pos + (MATRIX_ref::RotY(rad).zvec() * size);
            }
            draw->SetDraw_Screen(camera_pos, camera_target_pos, 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;
        }
    }
    return 0;// ソフトの終了 
}

まとめ

3Dでの位置角度の制御ができるようになりました。カメラの角度を簡単に指定できるようになりましたね。
次回はモデルの表示とTPS、FPSな動作をするカメラを実装してみましょう。

おまけ

後々解説します。

解説をすっ飛ばしたリスト

・3Dのカメラ設定
SetCameraPositionAndTargetAndUpVecなどを用いてカメラ座標、注視点、頭の向き、視野角、ニアファーを指定します。
GraphHandleの解説の際に正式に実装します。
・MV1
3Dモデルを管理するクラスです。アニメーションも紐づけできるように設計しています。

できたら解説したいリスト

・forの速度比較
イテレーターを移動していくのがかなり遅かったので比較してみようと思います
・逆運動学
モデルの腕制御などで活用しました。難産でしたが意外と仕組みは簡単です。

1
1
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
1
1