[C++]3Dタイル描画エンジン1

  1. 試しにボックス型のタイルをポリゴンを表示してみる
  2. 複数カメラと複数スクリーンで複数プレイヤ視点で3D空間を描けるようにする
  3. 文字タグ判定の代わりに使えるハッシュ(32bit)でカメラにhash32タグをつけて管理する
  4. ワールドを定義して、複数プレイヤと複数カメラをワールドに保持する
  5. シーンを定義して、ゲームシーンの中に複数ワールドを保持する
  6. MyTimerクラスでchronoから得た時刻でゲームの描画のFPSを管理する
  7. 共通のベース基底となるGameObjectを定義して、プレイヤなどはそれを継承する形を設計する
  8. Componentクラスを継承して、プレイヤなどに付け離し再利用可能なコンポーネント指向の機能を設計する
  9. 何もない空間をグリッド分割して隣接していない空間に所属するものとの当たり判定をスルーして高速化する
  10. タイル情報を.tsxや.jsonや.csvから読み取りタイルマップを表示する
  11. Map/MapSetting.csvの設定に合わせてタイルとの当たり判定をする
  12. Factory.cppにZako生成などの#includeを集約(依存性の逆転)させてMapでZakoなどの敵を出現スポーンさせる
  13. ItemクラスとItemActionクラスを作成し継承して弾やブロックなどのアイテムを取って使用できるようにする

試しにボックス型のタイルをポリゴンを表示してみる

(DXのプロジェクトはシューティングの別記事を参考に作っておいてください。

Resource.h と Resource.cppは次の記事を参考に作っておきます→画像や音声や3Dのファイルを読み込んで辞書で管理するクラス

Mapフォルダを作成して、地形画像ファイルmapchip.png2Dアクションゲームの別記事を参考にMap/mapchip.pngで保管されている前提で進めます)

main.cppを新規作成して、分割テクスチャ画像でタイルを正しく表示できるかテストします。

#include "DxLib.h"

#include "Resource.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMainからプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(960, 540, 32); // 画面サイズ960×540のカラービット数32ビットで起動
    DxLib::SetWindowSize(960, 540);// ウィンドウサイズ960×540(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("3Dタイルゲーム");//この行でエラーになったら【設定】マルチバイト文字セットが間違ってるかも
    //↑ここまでの設定は↓下のDXライブラリ初期化より先にやらないとDxLib_Init()中は画面がフルスクリーンになって終わってからウィンドウサイズが変更になり見苦しい

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    DxLib::ScreenFlip();

    // タイルマップを読み込む 縦8マス、横8マス、縦32ピクセル、横32ピクセル
    std::string mapChipPath{ "Map/mapchip.png" };
    Resource::MakeShared<Texture>(mapChipPath,8,8,32,32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする

    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        // 2Dタイル描画処理(位置x, y, 拡大率, 回転, 画像ID, TRUEなら透過有効)
        DxLib::DrawRotaGraphF(100, 200, 1.0f, 0, Resource::files[mapChipPath]->m_handles[2], TRUE);

        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}


MyDraw.hを新規作成して、3Dで分割テクスチャ画像を描画する関数を準備します。

#ifndef MYDRAW_H_
#define MYDRAW_H_

#include <assert.h> // 画像読込み失敗表示用
#include <cmath> // std::absを使う
#include <vector>

#include "DxLib.h"
#include "Resource.h"

// 3Dのタイルを描くための自作描画系処理のクラス
class MyDraw
{
public:

    enum class Plane
    {   // XYZ平面の指定
        X = 1,
        Y,
        Z
    };

    // ★デフォルトではDXライブラリ2D座標系に合わせて右手系 & Yの矢印を画面下方向に設定
    //static bool isLeftHanded;
    static VECTOR XYZ_ARROW;
    static bool IsReverseX_Texture;
    static bool IsReverseY_Texture;
    static bool IsReverseZ_Texture;

    // ★【Y矢印の方向と右手・左手系】の設定に合わせたVECTORを取得
    // このVGetを通せば座標系を統一できる
    // 入力される座標x,y,zは左上を(x,y,z) = (0,0,0)としてZの奥方向をプラス
    // Yをマイナス下方向としたDXライブラリの画面座標系を想定
    static VECTOR VGet(float x, float y, float z)
    {   // DXライブラリの3D描画座標系に変換
        if (XYZ_ARROW.x == 1 && XYZ_ARROW.y == 1 && XYZ_ARROW.z == 1)
        {   // DX デフォルト2D描画座標系
            return DxLib::VGet(x, y, z);
        }
        else if (XYZ_ARROW.x == 1 && XYZ_ARROW.y == -1 && XYZ_ARROW.z == 1)
        {   // DX 3D描画座標系
            return DxLib::VGet(x, -y, z);
        }

        VECTOR Result;
        if (XYZ_ARROW.x == 1) Result.x = x;
        else Result.x = -x;
        if (XYZ_ARROW.y == 1) Result.y = y;
        else Result.y = -y;
        if (XYZ_ARROW.z == 1) Result.z = z;
        else Result.z = -z;
        return Result;
    }

    inline static VECTOR_D VecAngle(Plane plane, double Angle)
    {
        VECTOR_D AngleVec = DxLib::VGetD(0.0F, 0.0F, 0.0F);
        // 平面に合わせて回転軸を選択
        if (plane == Plane::X) AngleVec.x = Angle;
        else if (plane == Plane::Y) AngleVec.y = Angle;
        else if (plane == Plane::Z) AngleVec.z = Angle;

        return AngleVec;
    }

    // 回転行列からXYZ軸周りの角度(単位:ラジアン)に戻す
    inline static VECTOR_D GetAngle(MATRIX_D rotMat)
    {
        double threshold = 0.001;
        VECTOR_D radAngle; // XYZ軸周りの角度(単位:ラジアン)
        //[参考:opencvは左手系なので行列位置は斜め鏡映し逆] https://qiita.com/q_tarou/items/46e5045068742dfb2fa6
        if (std::abs(rotMat.m[1][2] - 1.0) < threshold) { // rotMat[1][2] = sin(x) = 1の時
            radAngle.x = DX_PI / 2;
            radAngle.y = 0;
            radAngle.z = std::atan2(rotMat.m[0][1], rotMat.m[0][0]);
        }
        else if (std::abs(rotMat.m[1][2] + 1.0) < threshold) { // rotMat[1][2] = sin(x) = -1の時
            radAngle.x = -DX_PI / 2;
            radAngle.y = 0;
            radAngle.z = std::atan2(rotMat.m[0][1], rotMat.m[0][0]);
        }
        else {
            radAngle.x = std::asin(rotMat.m[1][2]);
            radAngle.y = std::atan2(-rotMat.m[0][2], rotMat.m[2][2]);
            radAngle.z = std::atan2(-rotMat.m[1][0], rotMat.m[1][1]);
        }
        return radAngle;
    }


    // UnityにならってfarClipPlane(z方向のどこまで遠いものを描くか)
    // https://qiita.com/shoridevel/items/5c4a249ff09244645c93
    //static int farClipPlane;// = 1000;

    // カメラの視点、注視点、アップベクトルを設定する( アップベクトルはY軸方向から導き出す )
    static int SetCameraPositionAndTarget_UpVecY(VECTOR Position, VECTOR Target)
    {
        // 右手・左手系とY軸矢印の方向に従い座標を変換
        VECTOR Position_COVERT_XYZ = VGet(Position.x, Position.y, Position.z);
        VECTOR Target_COVERT_XYZ = VGet(Target.x, Target.y, Target.z);
        return DxLib::SetCameraPositionAndTarget_UpVecY(Position_COVERT_XYZ, Target_COVERT_XYZ);
    }

    // 画像のビルボード描画(常に面がカメラ向き)( 座標指定が float 版 )
    static int DrawDivBillboardGraphF3D(Plane plane, float xf, float yf, float zf, double ExRate, VECTOR_D Angle, Texture& divTexture, int id, int TransFlag = TRUE, int ReverseXFlag = FALSE, int ReverseYFlag = FALSE, int ReverseZFlag = FALSE)
    {
        return MyDraw::DrawDivRotaGraphF3D(plane, xf, yf, zf, ExRate, Angle, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, ReverseZFlag, true); // ビルボード描画フラグをtrueに
    }

    // 画像の回転描画( 座標指定が float 版 )
    static int DrawDivRotaGraphF(float xf, float yf, double ExRate, double Angle, Texture& divTexture, int id, int TransFlag, int ReverseXFlag = FALSE, int ReverseYFlag = FALSE, int ReverseZFlag = FALSE)
    {   // Zの値を0.0Fとして
        return MyDraw::DrawDivRotaGraphF3D(Plane::Z, xf, yf, 0.0F, ExRate, Angle, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, ReverseZFlag);
    }

    // 画像の回転描画( 座標指定が float 版 )【注意!】現状Angle回転機能は未対応
    static int DrawDivRotaGraphF3D(Plane plane, float xf, float yf, float zf, double ExRate, double Angle, Texture& divTexture, int id, int TransFlag = TRUE, int ReverseXFlag = FALSE, int ReverseYFlag = FALSE, int ReverseZFlag = FALSE)
    {
        VECTOR_D AngleVec = VecAngle(plane, Angle);
        //                                              角度だけ↓VECTOR化
        return MyDraw::DrawDivRotaGraphF3D(plane, xf, yf, zf, ExRate, AngleVec, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, ReverseZFlag);
    }   // ↓すぐ下のAngleベクタ版関数に引き継ぐ

    // 画像の回転描画( 座標指定が float 版 )【注意!】現状Angle回転機能は未対応
    static int DrawDivRotaGraphF3D(Plane plane, float xf, float yf, float zf, double ExRate, VECTOR_D Angle, Texture& divTexture, int id, int TransFlag = TRUE, int ReverseXFlag = FALSE, int ReverseYFlag = FALSE, int ReverseZFlag = FALSE, bool isBillboard = false)
    {
        // 遠すぎるものは描かないで即return
        //if (zf > farClipPlane) return -1;

        // 右手・左手系とY軸矢印の方向に従い座標を変換
        VECTOR COVERT_XYZ = MyDraw::VGet(xf, yf, zf);
        float Xf = COVERT_XYZ.x, Yf = COVERT_XYZ.y, Zf = COVERT_XYZ.z;

        if (id < 0 && divTexture.m_handles.size() <= id) { assert("div画像id指定範囲外" == ""); return -1; }

        int imageWidth = divTexture.m_XSize;
        int imageHeight = divTexture.m_YSize;
        float u_X0 = 0.0F, v_Y0 = 0.0F; //点Vertex[0]のuとvの値
        float u_X3 = 1.0F, v_Y3 = 1.0F; //点Vertex[3]のuとvの値
        if (divTexture.m_XNum > 1 || divTexture.m_YNum > 1)
        {   // div分割画像の分割位置確定のため横uと縦vの値を計算
            int divX = id % divTexture.m_XNum;//分割X列番号
            int divY = id / divTexture.m_XNum;//分割Y行番号
            u_X0 = (float)divX / (float)(divTexture.m_XNum);
            v_Y0 = (float)divY / (float)(divTexture.m_YNum);
            u_X3 = (float)(divX + 1) / (float)(divTexture.m_XNum);
            v_Y3 = (float)(divY + 1) / (float)(divTexture.m_YNum);
        }

        // 【拡大縮小率ExRate】をかけて画像サイズを再計算
        imageWidth = (int)(imageWidth * ExRate);  // 拡大縮小した画像の幅
        imageHeight = (int)(imageHeight * ExRate);// 拡大縮小した画像の高さ
        float halfWidth = (float)(imageWidth / 2);//画像幅の1/2(先に計算し使いまわして処理削減)
        float halfHeight = (float)(imageHeight / 2);//画像高さの1/2(先に計算し使いまわして処理削減)
        //【テクスチャ反転処理】幅halfWidthと高さhalfHeightをマイナスにすることで反転
        // ★ReverseXFlagとIsReverseX_Texture両方反転は【反転の反転で=反転しない!】
        // 【反転の反転で=反転しない!】は★XOR【排他的論理和 ^ 演算子】
        // [排他的論理和 ^ 演算子]http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01047.html
        if ((IsReverseX_Texture ^ (ReverseXFlag == TRUE))
            && (plane == Plane::Y || plane == Plane::Z))
        {   // マイナス幅でテクスチャ反転
            halfWidth = -halfWidth;
        }
        if ((IsReverseY_Texture ^ (ReverseYFlag == TRUE))
            && (plane == Plane::X || plane == Plane::Z))
        {   // マイナス幅でテクスチャ反転
            halfHeight = -halfHeight;
        }
        if (IsReverseZ_Texture ^ (ReverseZFlag == TRUE))
        {   // マイナス幅でテクスチャ反転
            if (plane == Plane::X) halfWidth = -halfWidth;
            if (plane == Plane::Y) halfHeight = -halfHeight;
        }


        // [頂点VERTEXは四角形は6個必要] https://dixq.net/rp/57.html
        // ★CGの【最小単位は三角形】2Dはドットの集合で描くが【CGは三角形の集合で描く】
        VERTEX3D Vertex[6]; // ポリゴン2枚なので、頂点は6個(三角3点×2)
        // VERTEX3DSHADER Vertex[6]; // シェーダの頂点を使う場合

        // ★頂点情報のセット
        {   // Plane=X なら X平面に(Y,Z) = (yf,zf)中心に描画
            // Plane=Y なら Y平面に(X,Z) = (xf,zf)中心に描画
            // Plane=Z なら Z平面に(X,Y) = (xf,yf)中心に描画
            // 回転は以下リンク参照
            // https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=past&no=2749
            MATRIX TransformMatrix;
            // 回転( x, y, z軸回転の順に回転した後、座標移動行列により平行移動)
            // ★★MMultは行列の乗算【乗算した順にx軸回転→y軸回転→z軸回転→平行移動】
            //★【複数軸回転は頭が混乱するのでクォータニオンをつかうやり方もある】

            if (isBillboard)
            {
                VECTOR_D billboardAngle = (isBillboard) ? GetAngle(DxLib::GetCameraBillboardMatrixD()) : DxLib::VGetD(0, 0, 0);
                Angle.x += billboardAngle.x; Angle.y += billboardAngle.y; Angle.z += billboardAngle.z;
            }
            TransformMatrix = MGetRotX((float)Angle.x);
            TransformMatrix = MMult(TransformMatrix, MGetRotY((float)Angle.y));
            TransformMatrix = MMult(TransformMatrix, MGetRotZ((float)Angle.z));
            //★【座標移動行列により移動】
            TransformMatrix = MMult(TransformMatrix, MGetTranslate(VGet(Xf, Yf, Zf)));

            if (plane == Plane::X)
            {   //座標固定平面→→→→→→→→→↓X平面指定
                Vertex[0].pos = VTransform(VGet(0.0F, +halfHeight, -halfWidth), TransformMatrix);
                Vertex[1].pos = VTransform(VGet(0.0F, +halfHeight, +halfWidth), TransformMatrix);
                Vertex[2].pos = VTransform(VGet(0.0F, -halfHeight, -halfWidth), TransformMatrix);
                Vertex[3].pos = VTransform(VGet(0.0F, -halfHeight, +halfWidth), TransformMatrix);
            }
            else if (plane == Plane::Y)
            {   //座標固定平面→→→→→→→→→→→→→→→→↓Y平面指定
                Vertex[0].pos = VTransform(VGet(-halfWidth, 0.0F, +halfHeight), TransformMatrix);
                Vertex[1].pos = VTransform(VGet(+halfWidth, 0.0F, +halfHeight), TransformMatrix);
                Vertex[2].pos = VTransform(VGet(-halfWidth, 0.0F, -halfHeight), TransformMatrix);
                Vertex[3].pos = VTransform(VGet(+halfWidth, 0.0F, -halfHeight), TransformMatrix);
            }
            else // if (plane == Plane::Z)
            {   //座標固定平面→→→→→→→→→→→→→→→→→→→→→→↓Z平面指定
                Vertex[0].pos = VTransform(VGet(-halfWidth, +halfHeight, 0.0F), TransformMatrix);
                Vertex[1].pos = VTransform(VGet(+halfWidth, +halfHeight, 0.0F), TransformMatrix);
                Vertex[2].pos = VTransform(VGet(-halfWidth, -halfHeight, 0.0F), TransformMatrix);
                Vertex[3].pos = VTransform(VGet(+halfWidth, -halfHeight, 0.0F), TransformMatrix);
            }
            Vertex[4].pos = Vertex[2].pos; // 点2と点4は同じ位置
            Vertex[5].pos = Vertex[1].pos; // 点1と点5は同じ位置
            //↑点0 点1&5 点4&2 点3 の4点の四角形を描く

            // ★div分割画像を考慮に入れて【UV展開】切り出し位置を0.0~1.0で指定
            Vertex[0].u = u_X0; Vertex[0].v = v_Y0; Vertex[0].su = u_X0; Vertex[0].sv = v_Y0;
            Vertex[1].u = u_X3; Vertex[1].v = v_Y0; Vertex[1].su = u_X3; Vertex[1].sv = v_Y0;
            Vertex[2].u = u_X0; Vertex[2].v = v_Y3; Vertex[2].su = u_X0; Vertex[2].sv = v_Y3;
            Vertex[3].u = u_X3; Vertex[3].v = v_Y3; Vertex[3].su = u_X3; Vertex[3].sv = v_Y3;
            Vertex[4].u = u_X0; Vertex[4].v = v_Y3; Vertex[4].su = u_X0; Vertex[4].sv = v_Y3;
            Vertex[5].u = u_X3; Vertex[5].v = v_Y0; Vertex[5].su = u_X3; Vertex[5].sv = v_Y0;

            // 輝度(拡散光:Diffuseの明るさ)は全地点100% a(拡散光:Diffuseのアルファ不透明度) も最大値(255=不透明 0=透明)
            Vertex[0].dif = GetColorU8(255, 255, 255, 255);
            Vertex[1].dif = GetColorU8(255, 255, 255, 255);
            Vertex[2].dif = GetColorU8(255, 255, 255, 255);
            Vertex[3].dif = GetColorU8(255, 255, 255, 255);
            Vertex[4].dif = GetColorU8(255, 255, 255, 255);
            Vertex[5].dif = GetColorU8(255, 255, 255, 255);

            // 鏡面反射光(スペキュラ)は全地点30
            Vertex[0].spc = GetColorU8(0, 0, 0, 255);
            Vertex[1].spc = GetColorU8(0, 0, 0, 255);
            Vertex[2].spc = GetColorU8(0, 0, 0, 255);
            Vertex[3].spc = GetColorU8(0, 0, 0, 255);
            Vertex[4].spc = GetColorU8(0, 0, 0, 255);
            Vertex[5].spc = GetColorU8(0, 0, 0, 255);
           
            // 法線を設定
            Vertex[0].norm = VGet(0.0f, 0.0f, -1.0f); // Vertex[0].binorm = // シェーダーを利用するとき
            Vertex[1].norm = VGet(0.0f, 0.0f, -1.0f); // Vertex[1].binorm =
            Vertex[2].norm = VGet(0.0f, 0.0f, -1.0f); // Vertex[2].binorm =
            Vertex[3].norm = VGet(0.0f, 0.0f, -1.0f); // Vertex[3].binorm =
            Vertex[4].norm = VGet(0.0f, 0.0f, -1.0f); // Vertex[4].binorm =
            Vertex[5].norm = VGet(0.0f, 0.0f, -1.0f); // Vertex[5].binorm =

            // 0 1 2
            // 5 4 3
            //↑上の順の点で点0 点1&5 点4&2 点3 の4点の四角形を描く
        }

        //DxLib::DrawSphere3D(VGet(Xf, Yf, Zf),1, 4, GetColor(255, 255, 255),GetColor(255, 255, 255),FALSE); // テストとして四角形の中心に目印の球体を描く
       
       
        // ポリゴンを2枚描画
        DxLib::DrawPolygon3D(Vertex, 2, divTexture[id], TransFlag);

        //if (divTexture[id] != -1) //[シェーダーを利用する場合] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4204
        //    DxLib::SetUseTextureToShader(0, divTexture[id]); // 使用するテクスチャを0番にセット

        // シェーダーを利用して描く場合はこちら https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4204
        //DxLib::DrawPolygon3DToShader(Vertex, 2); // シェーダーにも対応できるようにToShader系関数を通して描く
    }


    // 画像ボックスの回転描画( 座標指定が float 版 )
    static void DrawDivBoxF3D(float xf, float yf, float zf, double ExRate, Texture& divTexture, int id, int TransFlag = TRUE, int ReverseXFlag = FALSE, int ReverseYFlag = FALSE, int ReverseZFlag = FALSE)
    {
        int imageWidth = (int)((double)divTexture.m_XSize * ExRate);
        int imageHeight = (int)((double)divTexture.m_YSize * ExRate);
        VECTOR_D Angle = VGetD(0,0,0);
        //サイコロの6面を描く
        MyDraw::DrawDivRotaGraphF3D(Plane::Y, xf, yf - imageHeight / 2, zf, ExRate, Angle, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, ReverseZFlag);
        MyDraw::DrawDivRotaGraphF3D(Plane::Y, xf, yf + imageHeight / 2, zf, ExRate, Angle, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, ReverseZFlag);
        // X方向の裏側は左右反転
        MyDraw::DrawDivRotaGraphF3D(Plane::X, xf - imageWidth / 2, yf, zf, ExRate, Angle, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, !ReverseZFlag);
        MyDraw::DrawDivRotaGraphF3D(Plane::X, xf + imageWidth / 2, yf, zf, ExRate, Angle, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, ReverseZFlag);
        MyDraw::DrawDivRotaGraphF3D(Plane::Z, xf, yf, zf - imageHeight / 2, ExRate, Angle, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, ReverseZFlag);
        // Z方向の裏側は左右反転
        MyDraw::DrawDivRotaGraphF3D(Plane::Z, xf, yf, zf + imageHeight / 2, ExRate, Angle, divTexture, id, TransFlag, !ReverseXFlag, ReverseYFlag, ReverseZFlag);
        return;
    }

    // 床の回転描画( 座標指定が float 版 )
    static int DrawDivRotaFloorF3D(Plane plane, float xf, float yf, float zf, double ExRate, double Angle, Texture& divTexture, int id, int TransFlag = TRUE, int ReverseXFlag = FALSE, int ReverseYFlag = FALSE, int ReverseZFlag = FALSE)
    {
        VECTOR_D AngleVec = VecAngle(plane, Angle);
        int imageHeight = (int)((double)divTexture.m_YSize * ExRate);
        //                                              角度だけ↓VECTOR化
        return MyDraw::DrawDivRotaGraphF3D(plane, xf, yf - imageHeight / 2, zf, ExRate, AngleVec, divTexture, id, TransFlag, ReverseXFlag, ReverseYFlag, ReverseZFlag);
    }

    // 四角形(枠線のみ)を描画する
    static int DrawLineBox(VECTOR startPos, VECTOR endPos, unsigned int color)
    {
        return DxLib::DrawCube3D(startPos, endPos, color, color, FALSE);
    }
};

#endif

MyDraw.cppを新規作成して、staticに関する定義をします。

#include "MyDraw.h"

//int MyDraw::farClipPlane = 1000; // 仮にZ=1000までの遠さのものを描く設定で

// ★デフォルトではDXライブラリ2D座標系に合わせて右手系&Yの矢印を画面下方向に設定
// http://www.f.waseda.jp/moriya/PUBLIC_HTML/education/classes/infomath6/applet/fractal/coord/
//bool MyDraw::isLeftHanded = false;
VECTOR MyDraw::XYZ_ARROW = DxLib::VGet(1, -1, 1);

bool MyDraw::IsReverseX_Texture = false;
bool MyDraw::IsReverseY_Texture = true;
bool MyDraw::IsReverseZ_Texture = false;


main.cppにプログラムを追加し、MyDrawクラスを通して、ポリゴンでサイコロ状のボックスを描いてみましょう。

#include "DxLib.h"

#include "Resource.h"
#include "MyDraw.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMainからプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(960, 540, 32); // 画面サイズ960×540のカラービット数32ビットで起動
    DxLib::SetWindowSize(960, 540);// ウィンドウサイズ960×540(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("3Dタイルゲーム");//この行でエラーになったら【設定】マルチバイト文字セットが間違ってるかも
    //↑ここまでの設定は↓下のDXライブラリ初期化より先にやらないとDxLib_Init()中は画面がフルスクリーンになって終わってからウィンドウサイズが変更になり見苦しい

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    DxLib::ScreenFlip();

    // タイルマップを読み込む 縦8マス、横8マス、縦32ピクセル、横32ピクセル
    std::string mapChipPath{ "Map/mapchip.png" };
    Resource::MakeShared<Texture>(mapChipPath,8,8,32,32)->Load(); // メソッドチェーン -> でMakeShared読込語にLoadもする


    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        // 光の明暗計算を無効に
        DxLib::SetUseLighting(FALSE);


        // 2Dタイル描画処理(位置x, y, 拡大率, 回転, 画像ID, TRUEなら透過有効)
        DxLib::DrawRotaGraphF(100, 200, 1.0f, 0, Resource::files[mapChipPath]->m_handles[2], TRUE);


        // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
        MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0,  *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);


        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょう?謎のバラバラの変な図形がでちゃいましたか?
よく眺めてみましょう。お互いがお互いを塗りつぶしあっている感じですね。
これは、カメラからの距離に応じて塗りつぶすかどうかを決める「Zバッファ」が効いてない状態です。
Zバッファが効いていない状態では、3Dの図形を描いた順に塗りつぶされます。

main.cppのプログラムを変更し、Zバッファを有効にして描いてみましょう。

#include "DxLib.h"

#include "Resource.h"
#include "MyDraw.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMainからプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(960, 540, 32); // 画面サイズ960×540のカラービット数32ビットで起動
    DxLib::SetWindowSize(960, 540);// ウィンドウサイズ960×540(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("3Dタイルゲーム");//この行でエラーになったら【設定】マルチバイト文字セットが間違ってるかも
    //↑ここまでの設定は↓下のDXライブラリ初期化より先にやらないとDxLib_Init()中は画面がフルスクリーンになって終わってからウィンドウサイズが変更になり見苦しい

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    DxLib::ScreenFlip();

    // タイルマップを読み込む 縦8マス、横8マス、縦32ピクセル、横32ピクセル
    std::string mapChipPath{ "Map/mapchip.png" };
    Resource::MakeShared<Texture>(mapChipPath,8,8,32,32)->Load(); // メソッドチェーン -> でMakeShared読込語にLoadもする

    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        // 光の明暗計算を無効に
        DxLib::SetUseLighting(FALSE);

        // Zバッファを有効にする [Zの深度]
        //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
        DxLib::SetUseZBuffer3D(TRUE);
        // Zバッファへの書き込みを有効にする
        DxLib::SetWriteZBuffer3D(TRUE);


        // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
        MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);

        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}


いかがですか?カメラからの距離に応じてタイルがサイコロ状に描画されましたか?

Zバッファが有効ならば、カメラから見た奥行き(=Z)と、すでにZバッファに塗られているドットの奥行きを比較して、手前のものが上に塗られていきます
逆にいうなら、岩の裏に隠れてカメラから見えないアイテムを、アイテムを描く際にZバッファを一時的に有効にしたり、無効にさせたりして、チカチカさせれば、
岩に隠れているアイテムを一番上に塗りつぶして、目立たせるような演出もできなくはないわけです(岩とかマップすべてがアイテムより先に描かれていればだが)




複数カメラと複数スクリーンで複数プレイヤ視点で3D空間を描けるようにする

Input.hとInput.cppを別記事から作成しておきます→Inputクラスを拡張してマウス、キーボード対応させる

Vector3.hとVector3.cppを別記事から作成しておきます→Vector3に色々な関数を用意して3D空間の当たり判定をする


さて、複数のプレイヤで遊べるゲームを作るためには、カメラが複数あって、スクリーンに複数の視点が描ける機能が必要ですよね。
そう、実は、カメラが複数あるということはそれを描く先であるスクリーンも複数だったり、切り替える場合はスクリーン1枚だったりと、
カメラとそれを描くスクリーンは関連づいたり切り替わったり「リンクしあう関係に」なるのですね。
そして、GPUでの3Dの描画過程でも3D上のローカル位置→ワールド位置→カメラからのビュー行列変換→スクリーン射影変換→クリッピング...と、
カメラの行列変換処理(3D)とスクリーン位置(2D)は、実は行列計算をはさんで表裏一体の近い関係なのです。

では、カメラとスクリーンの数だけ、変換行列がDXライブラリの内部に沢山あるのでしょうか?
実はそうではなくて、カメラのための変換行列は内部で1つだけ、で運用されています。
え、そうすると複数カメラ視点はどうやるの?と疑問と不安が出てきそうですが、
描く処理(=Drawコール)の直前に、その都度、カメラのための変換行列の設定値を変える処理を呼びだして、
カメラの行列のセッティングを変えたい→3Dを描きたい(=Drawコール)→カメラ行列設定を変えたい→.. 以下繰り返し
のような「コマンドをセットでまとめてGPUに送り付ける」という方式で、
画面スクリーン(画面に映らないスクリーンにも描ける)にGPUは処理の結果の数値(色など)を格納している、というわけで、
実はGPU側からすれば「カメラのセッティングを変えろ!」も「描け!(Drawコール)」もコマンドの1種として、
沢山の届いた一連のコマンドを「コマンドの順番に沿って たんたん と実行」してるにすぎないわけです。

では、複数のカメラの視点を描くにはどうするかというと、
①「描く先のスクリーンを変えろ!」→「カメラの位置などの設定を変えろ!」→...→「現在の行列設定でポリゴンを描け」...
②「描く先のスクリーンを変えろ!」→「カメラの位置などの設定を変えろ!」→...→「現在の行列設定でポリゴンを描け」...
上記の一連のコマンドを、複数あるカメラの数①、②..ぶん繰り返せば、スクリーン①②..には違う視点の映像が描かれていくわけです。

上記の世界観をイメージできると、ドローコールに関する話題もなんとなくイメージできるようになります。
そう、ほんとは、DxLib::DrawPolygonIndexed3DToShaderなどを呼ぶ回数を減らすために
「点のVertexをサイコロの面全部まとめて詰め込めば一気に6面(12ポリゴンぶん)」12コールのかわりに1回だけDrawコールを呼ぶ工夫など、
GPUの処理の負荷を下げる努力なども実はできて、アピールポイントにすることもできたりするわけです。
(点をまとめることはできるが、その点に張るテクスチャ指定は1枚=GPUの設計的に←だからUnityの木[同じ共通テクスチャの]が沢山ある森なんかは1回のドローコールにできるかも)
とりあえずは、基礎を勉強するために、最も愚直な形で1ポリゴンずつ3Dを描く形から始めているわけです。
一番愚直なスタンダードで愚かな基礎の経験から始めてこそ、それを工夫して変化させた「発展の発想ができる地力につながるわけなので」
逆に言うなら「便利に慣れすぎて原型に出会えない損失」のほうが危険な「ドローコールってなんですか状態(遊戯王っすか)」に陥るので。
(同じようなゲームを繰り替えし作るループでは地力は上がらない、次のゲームを作るためにどんな新たな知識を必要とし出会えたか?)
DXライブラリのDrawPolygonIndexed3DToShaderという関数を知っていることには、世間的には何の意味もないナンセンスだけども、
ポリゴンを描く「ドローコール」がGPUにどのように届くかの世界観を持っていることは、Unityにも他のゲームエンジンにも共通する意味のあること。
逆に言うならば、Unityの便利な関数を使いこなして同じようなゲームを作る作業ループも実はナンセンスを浪費してないか?を一旦振り返る必要がある。

MyDraw.hというファイルの名前をつけてあるのも、マイダイアリー(私の日記)みたいなニュアンスで、
あくまで個人の見解のプログラムというニュアンスで名付けてあります。個人の見解でおのおのが進化させる余地があるということです。

さて、では本題であるところの複数視点の描画に挑戦していきましょう。

Screen.hを新規作成して、デフォルトの画面全体の縦横のサイズと違うサイズの別スクリーンがStar()~End()関数で指定されたときにも、
視野(FOV=Field Of View:フィールドのビュー)すなわちスクリーンに描かれる3Dの範囲を StartFov()~EndFov()関数でDraw系関数を囲うことで、
一時的にカメラの射影変換行列(プロジェクション行列)の幅w 高さhの行列のパラメータを変更した状態で、GPUに描かせることができるようにしましょう。

#ifndef _SCREEN_H
#define _SCREEN_H

#include "DxLib.h"

#include "Input.h"

#include <assert.h> // アサート警告表示用

// 画面解像度
class Screen
{
public: //publicはC#と違いpublic:以下にまとめて書かれるスタイル
    static constexpr int Width = 1280; // 幅(Nintendoスイッチの縦横サイズ[いわゆるHD画質16:9]を採用)
    static constexpr int Height = 720; // 高さ

    // X,Yがウィンドウの中にあるか
    static bool IsInsideWindow(int screenX = Input::MouseX, int screenY = Input::MouseY) { return (0 <= screenX && screenX < Width) && (0 <= screenY && screenY < Height); }

    static int SubWidth, tmpSubWidth; // 現在指定中のサブのスクリーンの幅:SubWidth と 指定前のスクリーンの幅:tmpSubWidth
    static int SubHeight, tmpSubHeight; // 現在指定中のサブのスクリーンの高さ:SubHeight と 指定前のスクリーンの高さ:tmpSubHeight
    static int DrawHandle; // Start()~End()で指定中のハンドル番号
    static int tmpDrawHandle; // Endまでのあいだ一旦変更前のhandleを退避
    static int tmpResetSetting; // SetDrawScreen切り替え前に 設定を一旦tmpに退避
    static MATRIX_D tmpViewportMatrix; // StartFov前に もとのビューポート行列を一旦退避
    static MATRIX_D tmpProjectionMatrix; // StartFov前に もとの射影変換行列を一旦退避

    // 射影変換行列とビューポート行列をスクリーンの大きさに合わせて再設定
    static void StartFov(int handle, double fovAngle = 0)
    {   // [描画の縦横領域が変わっても画角維持] https://dxlib.xsrv.jp/cgi/patiobbs/patio.cgi?mode=view&no=4639

        tmpViewportMatrix = DxLib::GetCameraViewportMatrixD(); // もとのビューポート行列を一旦退避
        tmpProjectionMatrix = DxLib::GetCameraProjectionMatrixD(); // もとの射影変換行列を一旦退避
        int handleWidth, handleHeight; // 描画するスクリーンのサイズを得る
        DxLib::GetGraphSize(handle, &handleWidth, &handleHeight);

        // 射影行列を再設定
        MATRIX_D ProjectionMatrix;
        double fovRad = (fovAngle == 0) ? DxLib::GetCameraFovD() : DX_PI * fovAngle / 180.0; // スクリーンに描くカメラの画角の設定 0のときは既存のFOVの設定を適用
        auto dotAspect = DxLib::GetCameraDotAspectD(); // スクリーンに描くカメラのドットアスペクト比率
        DxLib::CreatePerspectiveFovMatrixD(&ProjectionMatrix, fovRad, DxLib::GetCameraNear(), DxLib::GetCameraFar(), (double)handleHeight / handleWidth * dotAspect);
        DxLib::SetTransformToProjectionD(&ProjectionMatrix); // 射影変換行列をスクリーン幅や高さやFOVの設定に合わせて一時的に再設定
#if 0 // [参考]デフォルトの射影変換行列のDXライブラリの内部計算
#define FOV                (60.0 * DX_PI / 180.0)
#define NEARZ            (0.0)
#define FARZ            (1000.0)
#define TAN_FOV_HALF    (0.52359877559829887307710723054658) // /*(0.57735026918962573)*/ tan( FOV * 0.5 )
        // (略)...
        w = GSYS.DrawSetting.DrawSizeX;
        h = GSYS.DrawSetting.DrawSizeY;
        D = (double)((h / 2) / TAN_FOV_HALF);
        // (略)...
        CreatePerspectiveFovMatrixD(&mat, FOV, D * 0.1, D + FARZ, -1.0f); // デフォルトの射影変換行列 呼び出し
        // (略)...

        // ↓ デフォルトの射影変換行列
        MATRIX_D mat; // ProjectionMatrix 射影行列
        double Sin = std::sin(fov / 2), Cos = std::cos(fov / 2); // tan(θ / 2) = Sin / Cos   θ = fovのとき
        if (-0.0001 < Sin && Sin < 0.0001) return -1;

        aspect = (double)MathScreenSizeY / (double)MathScreenSizeX;
        double w = aspect * (Cos / Sin); // = 高/幅 / tan( θ / 2 )
        double h = 1.0 * (Cos / Sin); // = 1 / tan( θ / 2 )
        mat->m[0][0] = w;   mat->m[0][1] = 0;   mat->m[0][2] = 0;                               mat->m[0][3] = 0;
        mat->m[1][0] = 0;   mat->m[1][1] = h;   mat->m[1][2] = 0;                               mat->m[1][3] = 0;
        mat->m[2][0] = 0;   mat->m[2][1] = 0;   mat->m[2][2] = zFar / (zFar - zNear);  mat->m[2][3] = 1.0;
        mat->m[3][0] = 0;   mat->m[3][1] = 0;   mat->m[3][2] = -zNear * zFar / (zFar - zNear);  mat->m[3][3] = 0.0;
#endif
        // ビューポート行列の再設定
        //[スクリーン上の3Dの消失点をスクリーンのサイズの1/2の真ん中の位置に設定] https://dxlib.xsrv.jp/function/dxfunc_3d_camera.html#R12N12
        DxLib::SetCameraScreenCenter((float)handleWidth / 2.0f, (float)handleHeight / 2.0f);
    }

    // 射影変換行列とビューポート行列をStartFovする前に戻す
    static void EndFov()
    {
        DxLib::SetTransformToProjectionD(&tmpViewportMatrix);
        DxLib::SetTransformToViewportD(&tmpProjectionMatrix);
    }

    // ★このStartとEndのあいだの描画処理は別スクリーンに描かれるようになる is3Dに
    static int Start(int handle = DX_SCREEN_BACK, bool is3D = false, double fovAngle = 0)
    {   // スクリーンのハンドルが 0以下 なら普通のスクリーンに描画する
        if (handle < 0) handle = DX_SCREEN_BACK;
        Screen::tmpResetSetting = DxLib::GetUseSetDrawScreenSettingReset(); // 設定を一旦tmpに退避
        DxLib::SetUseSetDrawScreenSettingReset(FALSE); // SetDrawScreen切り替え時に設定をリセットしない(FALSE)(これをやらないとカメラ視点などがリセットされるから)
        Screen::tmpDrawHandle = DxLib::GetDrawScreen(); // Endまでのあいだ一旦変更前のhandleを退避
        DxLib::GetGraphSize(tmpDrawHandle, &tmpSubWidth, &tmpSubHeight); // 現在のスクリーンのサイズを一旦退避
        // 描く先を別途用意した別スクリーン(指定されたhandle)に設定
        if (DxLib::SetDrawScreen(handle) == -1) DxLib::SetDrawScreen(handle = DX_SCREEN_BACK); // -1 失敗したら普通のDX_SCREEN_BACKのほうに描く
        Screen::DrawHandle = handle; // 現在選択中のスクリーンのハンドル番号を handleに 変更
        // 描画するスクリーンのサイズを得る
        if (DxLib::GetGraphSize(handle, &Screen::SubWidth, &Screen::SubHeight) == -1) return handle; // -1 失敗したらreturn
        if (is3D) // 3Dのときは
            Screen::StartFov(handle, fovAngle); // 射影変換行列とビューポート行列をスクリーンの大きさに合わせて再設定

        return handle;
    }

    // ★このStartとEndのあいだの描画処理は別スクリーンに描かれるようになる
    static void End(bool is3D = false)
    {   // ゲームを描く先をもとのデフォルトに戻しておく
        DxLib::SetDrawScreen(tmpDrawHandle = (tmpDrawHandle < 0) ? DX_SCREEN_BACK : tmpDrawHandle);
        Screen::DrawHandle = tmpDrawHandle; // 現在選択中のスクリーンのハンドル番号を handleに 変更
        int beforeSubWidth = Screen::SubWidth, beforeSubHeight = Screen::SubHeight; // Start()で変更した幅と高さを 一時退避
        if (DxLib::GetGraphSize(tmpDrawHandle, &Screen::SubWidth, &Screen::SubHeight) == -1) return; // -1 失敗したらreturn
        if (is3D) // 3Dのときは
            Screen::EndFov(); // 射影変換行列とビューポート行列をStartFovする前に戻す

        DxLib::SetUseSetDrawScreenSettingReset((tmpResetSetting == -1) ? TRUE : tmpResetSetting); // 設定をStart()前の元に戻す
    }

    // 指定した screenHandle のキャンバスをきれいにまっさらにクリアする
    static void ClearDrawScreen(int screenHandle)
    {
        Screen::Start(screenHandle);
        {
            DxLib::ClearDrawScreen(); // screenHandle のキャンバスをきれいにまっさらに
        }
        Screen::End();
    }

};// C#と違ってクラスの定義も;セミコロンで終わる



#endif


Screen.cppを新規作成して、Start()~End()やStartFov()~EndFov()の間にはさんでDrawコールするあいだ、
Start()で一時的にパラメータを変更する前のカメラの行列のパラメータを一旦保管するtmp~系変数をstatic定義します。

#include "Screen.h"


int Screen::SubWidth{ 0 }, Screen::tmpSubWidth{ 0 }; // 現在指定中のサブのスクリーンの幅:SubWidth と 指定前のスクリーンの幅:tmpSubWidth
int Screen::SubHeight{ 0 }, Screen::tmpSubHeight{ 0 }; // 現在指定中のサブのスクリーンの高さ:SubHeight と 指定前のスクリーンの高さ:tmpSubHeight

int Screen::DrawHandle{ DxLib::GetDrawScreen() }; // Start()~End()で指定中のハンドル番号
int Screen::tmpDrawHandle{ -1 }; // Endまでのあいだ一旦変更前のhandleを退避
int Screen::tmpResetSetting{ -1 }; // SetDrawScreen切り替え前に 設定を一旦tmpに退避
MATRIX_D Screen::tmpViewportMatrix; // StartFov前に もとのビューポート行列を一旦退避
MATRIX_D Screen::tmpProjectionMatrix; // StartFov前に もとの射影変換行列を一旦退避

Start()で一旦もとからのパラメータをtmp~の変数に退避しておいて、End()でそれを元のパラメータ設定に戻すという方式です。

さて、スクリーンのStart()関数にFOV視野の角度を渡さなければならぬということは、
当然、カメラ側から現在のFOV視野の角度のセッティングなどをDXライブラリ内部のカメラのビュー行列から得るための関数も必要です。
さらに、複数カメラがあるということは、カメラの位置も複数持てるように、カメラの初期化コンストラクタやカメラの位置の変数を準備する必要があります。

Camera.hを新規作成して、GetCameraSetting関数などで現在のFov視野角などの情報を得たり、個別にカメラをコンストラクタで初期化できるようにしましょう。

#ifndef CAMERA_H_
#define CAMERA_H_

#include <functional> // カメラのDraw関数に「引数として関数=ラムダ式」を渡してカメラのDraw関数の外側の呼び出し元から描画したい処理を渡すことができるようにする

#include "DxLib.h"
#include "Screen.h"
#include "MyDraw.h"
#include "Vector3.h"


class Camera
{
public:
    // カメラの位置(ワールド座標)
    union {
        Vector3 pos; // カメラの位置(ワールド座標)
        struct { float x, y, z; }; // unionしてx,y,zでもアクセスできるように
    };

    struct Rotate { double V, H, T; }; // カメラの回転角度定義 DXライブラリの内部行列ではfloatじゃなくてdouble型で保持されているので倍精度のdouble型にした
    Rotate rot{ 0.0, 0.0, 0.0 }; // カメラの3軸の回転角度
    double fovAngle = 0; // カメラをスクリーンに描くときの視野角(0~179度) 0のときはDxLib::GetCameraFovD()で得られる値が適用される

    float cameraLookAtHeight{ 0.0f }; // LookAtする対象を見下ろす高さ

    int screenHandle = DX_SCREEN_BACK; //カメラを描くスクリーンのハンドル番号


    // コンストラクタ
    Camera(Vector3 worldPos) : pos{ worldPos } {}

    // デストラクタ
    ~Camera() {}

    // カメラを通して描くスクリーンのハンドル番号を設定する
    void SetScreenHandle(int handle) { screenHandle = handle; }

    // カメラの位置をセットしなおす
    void SetPosition(Vector3 worldPos)
    {   // ワールド座標系として保存
        this->pos = worldPos;
    }

    // 指定されたワールド座標が画面の中心に来るように、カメラの位置を変更する
    void LookAt(Vector3 targetXYZ)
    {
        // カメラの位置と向きを設定
        //MyDraw::SetCameraPositionAndTarget_UpVecY(pos, targetXYZ + Vector3(0, cameraLookAtHeight,0));
        // 注視点はターゲットの座標から cameraLookAtHeight 分だけ高い位置
        DxLib::SetCameraPositionAndTarget_UpVecY(this->pos, targetXYZ + Vector3(0, this->cameraLookAtHeight, 0));
        this->rot.H = DxLib::GetCameraAngleHRotateD(); // カメラの水平方向の向きを取得
        this->rot.V = DxLib::GetCameraAngleVRotateD();
        this->rot.T = DxLib::GetCameraAngleTRotateD();
    }

    // 現在のDX内部のカメラのビュー行列などに入っているカメラの設定パラメータを得る
    static void GetCameraSettings(VECTOR_D* pPosition, double* pRotV = nullptr, double* pRotH = nullptr, double* pRotT = nullptr,
        double* pFov = nullptr, double* pNear = nullptr, double* pFar = nullptr)
    {
        if (pPosition != nullptr) *pPosition = DxLib::GetCameraPositionD(); // カメラ位置
        if (pRotV != nullptr) *pRotV = DxLib::GetCameraAngleVRotateD(); // カメラの角度V
        if (pRotH != nullptr) *pRotH = DxLib::GetCameraAngleHRotateD(); // カメラの角度H
        if (pRotT != nullptr) *pRotT = DxLib::GetCameraAngleTRotateD(); // カメラの角度T

        if (pFov != nullptr) *pFov = DxLib::GetCameraFovD(); // カメラの視野角
        if (pNear != nullptr) *pNear = DxLib::GetCameraNearD(); // カメラ一番近くの描き始めるレンジ
        if (pFar != nullptr) *pFar = DxLib::GetCameraFarD(); // カメラ一番遠くの描くレンジ

    }

    // カメラの視野角に変更があったら変えて射影変換行列を再計算する (現在と同じfovの数値の場合は再計算をしないで負荷を回避)
    static inline void SetupCamera_PerspectiveD(double fov) { if (DxLib::GetCameraFovD() != fov) DxLib::SetupCamera_PerspectiveD(fov); }
    // カメラの視野角に変更があったら変えて射影変換行列を再計算する (現在と同じfovの数値の場合は再計算をしないで負荷を回避)
    static inline void SetCameraNearFarD(double Near, double Far) { if (DxLib::GetCameraNearD() != Near && DxLib::GetCameraFarD() != Far) DxLib::SetCameraNearFarD(Near, Far); }

    // カメラの設定を変える
    static void SetCameraSettings(VECTOR_D* pPosition, double* pRotV, double* pRotH, double* pRotT,
        double* pFov = nullptr, double* pNear = nullptr, double* pFar = nullptr)
    {
        if (pPosition != nullptr && pRotV != nullptr && pRotH != nullptr && pRotT != nullptr)
            DxLib::SetCameraPositionAndAngleD(*pPosition, *pRotV, *pRotH, *pRotT);
        if (pFov != nullptr) Camera::SetupCamera_PerspectiveD(*pFov); // カメラの視野角に変化があったら射影変換行列を更新
        if (pNear != nullptr && pFar != nullptr) Camera::SetCameraNearFarD(*pNear, *pFar); // カメラ一番近くから遠くまで描くレンジ
    }

    // このワールドに存在するカメラすべてに対して funcで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
    inline void Draw(std::function<void()> drawFunc) noexcept
    {
        VECTOR_D beforeCamPos; // 設定変更前のカメラのパラメータを一旦、保管
        double beforeVRot, beforeHRot, beforeTRot, beforeNear, beforeFar, beforeFov;
        Camera::GetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);

        DxLib::SetCameraPositionAndAngle(this->pos, this->rot.V, this->rot.H, this->rot.T); // カメラの位置、回転を設定

        Screen::Start(this->screenHandle, true, this->fovAngle); // カメラに関連付けられたscreenHandleのスクリーンを描画先にする
        {
            drawFunc(); // 渡された描画処理を実行
        }
        Screen::End(true); // 描画先を元に戻す

        // カメラの設定をもとに戻しておく
        Camera::SetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);
    }

};

#endif

個々のカメラには、上記↑のDraw関数を準備して、その引数に std::function型のdrawfunc 経由で描きたい処理(=関数)を渡せるようにしてあります。
トリッキーに聞こえるかもしれませんが、関数の引数( )内に関数を渡すのです。
Draw関数に関数を渡せれば、Draw関数を呼び出す外側から色んなDraw系の処理を使う側が自由に渡せます。
std::function型のラムダ式ならばそれができます。プログラミングの自由度がめちゃめちゃ上がります。


では、実際にそのDraw関数にラムダ式[&]{ ~ }で描画処理を渡してみましょう。

main.cppを変更して、複数カメラ視点、複数カメラで3Dを描いてみましょう。

#include "DxLib.h"

#include "Screen.h"
#include "Camera.h"

#include "Resource.h"
#include "MyDraw.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMainからプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズ Width×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズ Width×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("3Dタイルゲーム");//この行でエラーになったら【設定】マルチバイト文字セットが間違ってるかも
    //↑ここまでの設定は↓下のDXライブラリ初期化より先にやらないとDxLib_Init()中は画面がフルスクリーンになって終わってからウィンドウサイズが変更になり見苦しい

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    DxLib::ScreenFlip();

    // タイルマップを読み込む 縦8マス、横8マス、縦32ピクセル、横32ピクセル
    std::string mapChipPath{ "Map/mapchip.png" };
    Resource::MakeShared<Texture>(mapChipPath,8,8,32,32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする

   
    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    int screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    int screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);


    // カメラを2つ生成して、2つのカメラ視点を2つのスクリーンにそれぞれ関連付ける
    std::vector<std::shared_ptr<Camera>> cameras; // 複数のカメラへのポインタを格納する配列
    cameras.emplace_back(std::make_shared<Camera>(Vector3{ 20,40,-50 })); // カメラ0
    cameras.emplace_back(std::make_shared<Camera>(Vector3{ 80,30,-50 })); // カメラ1

    cameras[0]->screenHandle = screenHandle0; // カメラ0 と スクリーン0 を関連付ける
    cameras[1]->screenHandle = screenHandle1; // カメラ1 と スクリーン1 を関連付ける

   
    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Screen::ClearDrawScreen(screenHandle0); // 一旦スクリーン0のキャンバスをきれいにまっさらに
        Screen::ClearDrawScreen(screenHandle1); // 一旦スクリーン1のキャンバスをきれいにまっさらに


        // 光の明暗計算を無効に
        DxLib::SetUseLighting(FALSE);

        // Zバッファを有効にする [Zの深度]
        //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
        DxLib::SetUseZBuffer3D(TRUE);
        // Zバッファへの書き込みを有効にする
        DxLib::SetWriteZBuffer3D(TRUE);

        // 複数のカメラを 範囲 for文 で回す
        for (auto&& camera : cameras)
        {   // ラムダ式[&](){ ~ }で{}の外側の変数すべてを & キャプチャで&参照として「Draw関数の内側へ引き連れて」処理できる
            camera->Draw([&]()
                {
                    // Draw関数() の 外側にある変数 mapChipPath も[&]効果で { } の内側で問題なくアクセスできている↓


                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 0);
                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 70, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 4);
                });
        }


        // スクリーン0の内容を画面に描く
        DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
        DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));
       
        // スクリーン1の内容を画面に描く
        DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
        DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));


        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}

いかがでしょう?複数のカメラ視点からの3Dのビューが、上下複数のスクリーンに描画されたでしょうか?
(見やすいようにタイルのブロックの数も3つに増やしておきました。)
ラムダ式は便利なのですが、あまり初心者向けのC++の本には出てきたのを見たことがありません。
私もC++とは関係ない別言語のJavascript経由で、関数の中に関数を渡せると便利だというニーズに気づきました。
今回のラムダ式はJavascriptでいうところの、無名関数として3Dブロックを描く処理をdrawfuncとしてDraw関数の引数に渡した形という雰囲気でしょうか。
ある言語においては基本的な文法が、別の言語では初心者向けではない、みたいな扱いになっていたりするのは機会の損失ですよね。
大事なのは「文法を実際に使いたくなるシチュエーションに出会えるかどうか」です。
その意味で今回のDrawへの 無名ラムダ式渡し はぴったりの実例だと思います。
ラムダ式がなかったら...色んな種類のDraw処理を書き分けるのが...どうするよ...難しくなります。

「それでは今回の文法ニーズを振り返っておきましょう。」
Start() ~ End() で囲い込むスタイルの書き方のニーズがある。→imguiのBegin()~End()囲いの雰囲気
関数の中に関数を渡すにはラムダ式が便利。(C++の入門書にはあまり出てこないので学校で勉強してても卒業まで出会えないことも多い)

今回はDraw関数にdrawfuncとして、描きたい処理を渡すことで、

void Draw関数 (    drawfunc    )
{
  Start() ←カメラやスクリーンの設定を変える(もとの設定を一旦保管しておく)

    drawfunc() ←描きたい処理をはさみこんだ真ん中で実行する

  End() ←カメラやスクリーンの設定を元に戻す
}

以上の処理を割と簡単に書くことができました。
ん、カメラもDrawStart() ~ DrawEnd() みたいに囲うスタイルにもできるんじゃないかって?
確かに、できますね。
要はセッティングの変数をDXライブラリ内部やCameraクラスやScreenクラスが蓄えることができれば、
Start() ~ End() 方式で設計することもできますね。
では、ラムダ式が真に威力を発揮するのはどういうときでしょうか?
たとえばStart() End()でさばけないような、データが複雑な木構造のときに、木の枝をたどって、
目的のデータにたどりついたら drawfunc を実行するとしたらどうでしょう?
沢山の関節があるCGデータやフォルダの中にフォルダがあるような木の構造をたどって、
CGの指だったらある処理を走らせたいとか、
フォルダをたどった先にあるデータが写真形式だったら写真アイコンを描く、
とか複雑な構造の場合はラムダ式を渡す形が向いているかもしれません。



文字タグ判定の代わりに使えるハッシュ(32bit)でカメラにhash32タグをつけて管理する

まず、最初にハッシュという言葉は色んな意味で使われている(ハッシュドビーフも)ので区別しなければいけない
英語圏で#はハッシュと読まれる。だからハッシュタグのハッシュは#の意味だ。
それとは全く異なる 情報科学の言葉としても「暗号学的ハッシュ関数」のようなハッシュという言葉がある。
今回のハッシュはこちらの情報科学のハッシュ関数に近い概念だ。
例えば、パスワードは入力された文字の羅列をそのままネットで送信すると、個人情報が「もれ放題」になる。
なのでパスワードの文字の羅列を数字などの羅列に変換するパスワード変換の関数を通してから、ネット経由でサーバーに送られる。
その変換に「法則性がなければ、ないほど」解読が難しい変換の関数として評価される。
ただし、忘れてはいけないのは、ある文字の羅列に対して、
変換した後にできる数字などの羅列が他のパスワードを入力した場合にできるものと同じ(かぶる)だと、
別の間違ったパスワードでログインできちゃうことになりうる。
なので、ある文字の羅列に対して、変換後にできる数字などの羅列は「ほぼかぶらない(42億分の1とか)」必要がある。
ハッシュ関数は、この「変換前のパターンに対して、変換後のパターンが※ほぼかぶらない」性質を持つ色んな数式的関数のことを言う。
逆にいうと「めっちゃかぶる手抜きのハッシュ関数」の計算ルールも考え出すこともできる
例えば、ある数字のパスワードを、あるキーとなる数字で割った余りを変換後のパスワードとするとすると
パスワードの数字が10だったとする。パスワードをキー:8で割るというハッシュ関数を考えるとすると
パスワード:10 が 変換後:2 になる。
そうすると9ではログインできないが10や18なら余りが2になってログインできるハッシュ関数ができあがる。
やばいじゃん!と思うかもしれないがハッシュ関数のルールとはそれが8分の1の確率なのか、42億分の1の確率なのかの違いに過ぎない
ハッシュの一致判定とは、ある入力を一定のかぶりにくいルールで変換して、その変換後の出力どうしが一致するか判定することだ。
そして、もう一つ着目してほしい点がある。
8で割ると10が2になって、2桁の10 が 1桁の2 の数字になって数字の桁数が減っている!
これは890とか3桁の数字にも当てはまる。
そう、どんな入力も一定の桁数内に収めてしまう性質をもったハッシュ関数がありうるのだ。
今回、導入するhash32はどんな文字の羅列でも32桁の0と1の2進数に収めてしまう性質をもったハッシュ関数が採用されている。
"Zako0"などの判別タグを、文字列のままデータとしてキープしておくと長い文字列のタグでデータはかさばるし、
それよりは、用途が「タグが一致するか」だけなら、文字列のままではなく、hash32型の32桁の0と1のビットとして保管しておいて、
32桁の0,1の数字どうしを判別
したほうが、
メモリの消費量(=32bitで一定)的にも、一致判定の速度的にも(文字列どうしの比較はめちゃ遅いので)効率的になる。

MyHash.hを新規作成して、文字列をハッシュのルールに基づいて32桁の0と1に変換してhash32型のタグどうしで一致判定できるようにしましょう。

#ifndef MYHASH_H_
#define MYHASH_H_

#include <string>
#include <stdint.h> // uint32_t 型 32ビットの unsined 数値型に変換する

#define CRC32_TABLE_SIZE (256) // CRC32テーブルの要素数

// 言語 C++ C# など や ビルドするコンパイラ VisualStudioやgccなどに関係なく同じハッシュを計算値できる
// [参考] https://norizn.hatenablog.com/entry/2020/10/18/145628

// 生成済みのCRC32テーブル このテーブルをベースとして使用してハッシュ値を計算する
static constexpr uint32_t s_crc32_table[CRC32_TABLE_SIZE] =
{
    0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
    0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
    0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
    0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD,
    0x4C11DB70, 0x48D0C6C7, 0x4593E01E, 0x4152FDA9,
    0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75,
    0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011,
    0x791D4014, 0x7DDC5DA3, 0x709F7B7A, 0x745E66CD,
    0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039,
    0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5,
    0xBE2B5B58, 0xBAEA46EF, 0xB7A96036, 0xB3687D81,
    0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D,
    0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49,
    0xC7361B4C, 0xC3F706FB, 0xCEB42022, 0xCA753D95,
    0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1,
    0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D,

    0x34867077, 0x30476DC0, 0x3D044B19, 0x39C556AE,
    0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072,
    0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16,
    0x018AEB13, 0x054BF6A4, 0x0808D07D, 0x0CC9CDCA,
    0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE,
    0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02,
    0x5E9F46BF, 0x5A5E5B08, 0x571D7DD1, 0x53DC6066,
    0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
    0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E,
    0xBFA1B04B, 0xBB60ADFC, 0xB6238B25, 0xB2E29692,
    0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6,
    0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A,
    0xE0B41DE7, 0xE4750050, 0xE9362689, 0xEDF73B3E,
    0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2,
    0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686,
    0xD5B88683, 0xD1799B34, 0xDC3ABDED, 0xD8FBA05A,

    0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637,
    0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB,
    0x4F040D56, 0x4BC510E1, 0x46863638, 0x42472B8F,
    0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53,
    0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47,
    0x36194D42, 0x32D850F5, 0x3F9B762C, 0x3B5A6B9B,
    0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF,
    0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623,
    0xF12F560E, 0xF5EE4BB9, 0xF8AD6D60, 0xFC6C70D7,
    0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B,
    0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F,
    0xC423CD6A, 0xC0E2D0DD, 0xCDA1F604, 0xC960EBB3,
    0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7,
    0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B,
    0x9B3660C6, 0x9FF77D71, 0x92B45BA8, 0x9675461F,
    0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3,

    0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640,
    0x4E8EE645, 0x4A4FFBF2, 0x470CDD2B, 0x43CDC09C,
    0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8,
    0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24,
    0x119B4BE9, 0x155A565E, 0x18197087, 0x1CD86D30,
    0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
    0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088,
    0x2497D08D, 0x2056CD3A, 0x2D15EBE3, 0x29D4F654,
    0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0,
    0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C,
    0xE3A1CBC1, 0xE760D676, 0xEA23F0AF, 0xEEE2ED18,
    0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4,
    0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0,
    0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C,
    0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668,
    0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4,
};

// 言語 C++ C# など や ビルドするコンパイラ VisualStudioやgccなどに関係なく変換後が同じになるハッシュ値を計算する
class hash32
#ifdef _DEBUG // [デバッグビルド時だけ _DEBUG が定義] デバッグのときだけ std::string を継承して文字列と同じ扱いにできる(メモリ量は無駄なのでReleaseビルドのときは継承させない)
    : protected std::string // [std::stringはデストラクタがvirtualではないのでprotected:継承] https://mahou-ptr.hatenablog.com/entry/2017/12/05/163120
#endif                      // [std::stringを継承するときの注意点] https://qiita.com/samakusa/items/d6f0bf48a3f1e72edae1
{
public:
#ifndef _DEBUG // Releaseビルドのときは コンストラクタに constexpr をつけて、ビルド中のうちに文字列からハッシュ値へ事前計算を走らせる
    constexpr
#endif
    inline hash32(const char* str) : m_hash{ hashcode(str, std::string(str).length()) }
#ifdef _DEBUG // デバッグ機能(デバッグの時だけ _DEBUG が定義)
     ,std::string(str) // Releaseビルドのときはstd::stringは継承されないのでメモリ量は32ビットになり、コンパクトになる
#endif
    {
        // ちょっと読みにくいけど ここがコンストラクタになってる
    }

#ifndef _DEBUG // Releaseビルドのときは コンストラクタに constexpr をつけて、ビルド中のうちに文字列からハッシュ値へ事前計算を走らせる
    constexpr
#endif
    inline hash32(const std::string & str) : m_hash{ hashcode(str.c_str(), str.length()) }
#ifdef _DEBUG // デバッグ機能(デバッグの時だけ _DEBUG が定義)
     ,std::string(str) // Releaseビルドのときはstd::stringは継承されないのでメモリ量は32ビットになり、コンパクトになる
#endif
    {
        // ちょっと読みにくいけど ここがコンストラクタになってる
    }

#ifdef _DEBUG // デバッグ機能(デバッグの時だけ _DEBUG が定義)
    ~hash32() { std::string().swap(*this); } // デバッグのときだけ念のため継承しているstd::stringを空のstd::string()とswapしておく(stringにはvirtualデストラクタがない)
#endif

    // 文字列からハッシュ値を計算して返す
    static constexpr uint32_t hashcode(const char* str, const size_t length)
    {   // CRC32 Hashの特徴: 計算が簡単で速い 広く利用されている ハッシュの衝突が発生する確率が低い(1/43億) ハードウェアでの実装が容易
        uint32_t hash = 0xffffffff; //[CRC32] https://norizn.hatenablog.com/entry/2020/10/18/145628
        for (size_t i = 0; i < length; ++i) //[CRC32] https://qiita.com/He3-toolbox/items/3c40ca4b744bad2c0e04
            hash = (hash << 8) ^ s_crc32_table[((hash >> 24) ^ str[i]) & 0xff];

        return hash;
    }

    // 一致演算子 hash32型 どうしを比較する std::unordered_mapなどのキーにするときにも比較は必須
    constexpr inline bool operator == (const hash32 & other) const { return this->m_hash == other.m_hash; }

    // 不一致演算子 hash32型 どうしを比較する std::unordered_mapなどのキーにするときにも比較は必須
    constexpr inline bool operator != (const hash32 & other) const { return this->m_hash != other.m_hash; }

    // 一致演算子 m_hashの値が一致するか判定
    constexpr inline bool operator == (const char* str) const { return m_hash == hashcode(str, std::string(str).length()); }

    // 不一致演算子
    constexpr inline bool operator != (const char* str) const { return m_hash != hashcode(str, std::string(str).length()); }

    // 一致演算子 m_hashの値が一致するか判定
    constexpr inline bool operator == (const std::string & str) const { return m_hash == hashcode(str.c_str(), str.length()); }

    // 一致演算子 m_hashの値が一致するか判定
    constexpr inline bool operator != (const std::string & str) const { return m_hash != hashcode(str.c_str(), str.length()); }

    // ハッシュ値を取得
    constexpr inline operator uint32_t() const { return m_hash; }

private:
    // 文字列から変換した ハッシュ値
    uint32_t m_hash{ 0 }; // 同じ文字列からは必ず同じハッシュ値に計算変換されるので、文字列を比較せずにすむ(文字列の比較は計算が重たいので回避できる)
};

// std::unordered_map で 自作クラスをキーにできるようにするには operator() と == 一致演算子が必須  https://qiita.com/izmktr/items/8e0fd1b6e37de59a9bd0
namespace std {
    template<>
    class hash<hash32> {
    public: //[std::unordered_mapのキー が uint32_t型のときの 速度やメモリ使用量比較] https://tech.preferred.jp/ja/blog/sparse-vector/
        size_t operator () (const hash32& hash) const { return std::hash<uint32_t>()(hash); }
    };
}

// "Zako0"_hash32 などのように 文字列 "~" の後ろに_hash32 をつけるとhash32型 -> uint32_tに変換される オペレータ""_hash32 を定義
constexpr uint32_t operator""_hash32(const char* str, std::size_t length) // [参考] https://cpprefjp.github.io/lang/cpp11/user_defined_literals.html
{
    return hash32::hashcode(str, length); // hash32型のハッシュルールで32ビットのuint型に変換して返す
}

#endif


さて、いろいろ難しいコードがでてきたが、最初のほうに出てきたのはCRC-32 で32桁への変換をするため のテーブルである。
詳細はwikiをみてもらえば、数学者であれば完全理解できると思うので、そのへんの解説は↑wikiに譲るとしよう。

今回作成したのはhash32クラスである。
このクラスには「仕掛け」がしてある。
デバッグ用ビルド ではデバック用に class hash32 : protected std::string の:継承の形でstd::string型を継承して文字タグを残しておき、
本番リリース用ビルドでは、#ifndef _DEBUG マクロ の効果で↑ 文字タグを継承せず、完全に純粋な32桁の0と1の32bitぶんのデータしか持たず、
リリース時には文字列タグのムダなメモリ使用を打ち消す工夫が「仕掛け」てある。

さらに、第2の「仕掛け」として、
== 一致判定 や != 不一致判定の 演算子オペレータoperator の先頭に
constexpr を付けてある。
constexprとは何かというと、
[再生ビルド]ボタンを押して、コンパイルをしている最中に計算できる数値はビルド中に事前計算しておいてくれるものだ。
例えば、if (tag == "Zako0") という判別するプログラムがあったとする。
tag が ただのstd::string型であれば、ゲームの実行中に tag が"Zako0" と一致するかの遅くてムダな計算が走る。
constexpr をつけたhash32の == オペレータならば[再生ビルド]ボタンを押してビルドをしている最中に"Zako0"が32桁のビットとして事前に計算されうるので
ゲームを起動して遊んでいる最中には、ムダな文字列のハッシュへの変換処理も、文字列どうしの比較一致判定処理も まったくいらなくなる。

hash32クラスの導入も簡単だ。
std::string tag; // 文字列タグ

hash32 tag; // hash32タグ

たったこれだけの変更で、もとの if (tag == "Zako0") の判定の部分は変えなくても高速な32桁の数値どうしの一致判定に変わる
必要なのは「hash32とは何なのか」を理解して「自分の言葉で説明できること」のみ。
(当然「自分の言葉で説明できない」場合は「ハッシュ判定のありがたみ」はわからないわけなのでその人にとっては使う意味もない)

さて、第3の「仕掛け」も理解しておこう。
今回導入したハッシュ関数の数式の変換ルールは
「C#やC++などの言語やVisualStudioやgccやclangなどのビルドするコンパイラ」が違っても同じハッシュ値になる
という性質をもっている。
C++にもデフォルトの std::hash でハッシュ値を計算する機能がある。
じゃあ、なぜデフォルトを使わないか?
今回導入したハッシュ関数[CRC32]ならば、ウィンドウズ上でセーブしたデータもスマホのiPhone上でセーブしたデータもハッシュ値に一貫性がある。
つまり、スマホでセーブしたデータをウィンドウズ上で続きをあそぶときにも、
"Zako0"のhash32タグが010100001010...32桁のビット値としてスマホでセーブされていれば、
ウィンドウズでゲームを再開して、セーブされた010100001010...32桁のタグの数値は、ちゃんと"Zako0"として一致判定できるということだ。
CRC32というルールで変換された、というルールさえ一致していれば、きちんと環境が違っても変換結果は一致する。
デフォルトの std::hash を使った場合はどうなるか?
std::hash はVisualStudio(ウインドウズ)やclang(スマホiPhone)のビルドする環境によって、違うハッシュルールでビルドされる可能性がある。(環境依存)
ゆえに、デフォルトのstd::hashでは変換後の数値をセーブして引き継ぎたいときにはまったくあてにならない。
どうせハッシュを使うなら「CRC32というルールで保存するぞ」と明確に変換ルールを決めて運用するほうが、機動力や互換性が高くなる

ゆえに、hash32は保存データを伴うプログラムを作る上での「基本インフラ」として組み込むニーズがある。
MyRandomクラスの乱数生成のメルセンヌツイスタと同じような「基本の情報科学的インフラ」として、持っておいて損はない。
では、実際にこのMyHash.hを使って、カメラにhash32でタグを振ってみよう。

main.cppを変更して、hash32でカメラをstd::vectorではなくstd::unordered_mapの辞書にhash32タグを振って管理してみましょう。

#include "DxLib.h"

#include "Screen.h"
#include "Camera.h"
#include "MyHash.h"
#include "Resource.h"
#include "MyDraw.h"

// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMainからプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズ Width×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズ Width×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("3Dタイルゲーム");//この行でエラーになったら【設定】マルチバイト文字セットが間違ってるかも
    //↑ここまでの設定は↓下のDXライブラリ初期化より先にやらないとDxLib_Init()中は画面がフルスクリーンになって終わってからウィンドウサイズが変更になり見苦しい

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    DxLib::ScreenFlip();

    // タイルマップを読み込む 縦8マス、横8マス、縦32ピクセル、横32ピクセル
    std::string mapChipPath{ "Map/mapchip.png" };
    Resource::MakeShared<Texture>(mapChipPath,8,8,32,32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする

    
    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    int screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    int screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);


    // カメラを2つ生成して、2つのカメラ視点を2つのスクリーンにそれぞれ関連付ける
    std::unordered_map<hash32, std::shared_ptr<Camera>> cameras; // 複数のカメラへのポインタを格納するタグを振った辞書配列
    cameras.emplace("カメラ0", std::make_shared<Camera>(Vector3{ 20,40,-50 })); // カメラ0
    cameras.emplace("カメラ1", std::make_shared<Camera>(Vector3{ 80,30,-50 })); // カメラ1

    cameras["カメラ0"]->screenHandle = screenHandle0; // カメラ0 と スクリーン0 を関連付ける
    cameras["カメラ1"]->screenHandle = screenHandle1; // カメラ1 と スクリーン1 を関連付ける
    

    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Screen::ClearDrawScreen(screenHandle0); // 一旦キャンバスをきれいにまっさらに
        Screen::ClearDrawScreen(screenHandle1); // 一旦キャンバスをきれいにまっさらに

        // 光の明暗計算を無効に
        DxLib::SetUseLighting(FALSE);

        // Zバッファを有効にする [Zの深度]
        //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
        DxLib::SetUseZBuffer3D(TRUE);
        // Zバッファへの書き込みを有効にする
        DxLib::SetWriteZBuffer3D(TRUE);

        // 複数のカメラを 範囲 for文 で回す
        for (auto&& camera : cameras)
        {   // ラムダ式[&](){ ~ }で{}の外側の変数すべてを & キャプチャで&参照として「Draw関数の内側へ引き連れて」処理できる
            camera.second->Draw([&]()
                {
                    // Draw関数() の 外側にある変数 mapChipPath も[&]効果で { } の内側で問題なくアクセスできている↓

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 0);
                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 70, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 4);

                    if (camera.first == "カメラ0") //←"カメラ0"はビルド時に事前にハッシュ値が計算され、文字列ではない数値とおきかわっている
                        DxLib::DrawString(0, 0, "こちらがカメラ0", GetColor(255, 0, 0));
                    else if (camera.first == "カメラ1")
                        DxLib::DrawString(0, 0, "こちらはカメラ1", GetColor(0, 255, 0));

                });
        }

        // スクリーン0の内容を画面に描く
        DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
        DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));
        
        // スクリーン1の内容を画面に描く
        DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
        DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));

        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょう?ちゃんと上の画面に赤色の文字で「こちらがカメラ0」、下の画面に緑色の文字で「こららはカメラ1、現場からお伝えします」と出てきましたでしょうか?

一応お伝えしておきますが、CRC32についてはwebで検索すればでてきますが、
hash32クラスはこの記事で作成したクラスなので当然、web検索してもでてきません。
std::stringを継承してあたかも文字列のように扱える設計も特にweb検索してもでてくるはずがないです。
つまり、使うユーザー側が 記事の内容や意図を理解 して、使わない限りは「文字列タグで一致判定しているのとほぼ変わらない」ということです。
std::のライブラリだって「使う側が特性を理解して使わない限りはプログラムの動作や速度をカスタマイズできない」のと同じです。
AIやChatGPTだってそうです。便利な時代になっても、どのみち求められているのは「使う側の基礎知識 = 地力」なのです。

数学すべてを理解する必要はないが「物事の本質や道理を把握するちから」は大事なのです。
プログラムや数学の専門家じゃない任天堂の宮本先生でも「原理と機能を把握するちから = 地力」は持っている。
ゲームが計算機をベースにしている以上、その原理や源流を脳みそからシャットアウトする人には、新しいものを生み出す機会を損失するリスクが伴うということです
誰だって「原理や機能を把握する地力のあるかしこい人」と仕事するほうが実力が発揮できる現場になる、のは あたりまえでしょう。
その能力はむしろ「ゲームをプランニングするプランナ側」に必要な能力です。プログラマの能力を最大限に引き出される現場で働きたいでしょう。
ゲームの学科などではプログラミングをあきらめた人がゲームのプランナを逃げで選択し、プログラミングを続ける人を逆に使う側になったりしがちですが、
ゲームのプランニングはプログラミングやCGやデザインなど確固たるバックグラウンドに持った地力のある人が仕切るほうが対話が現場で生まれ実力が引き出されると思います。

数学やプログラムより、むしろ大事なのは「むずかしいことを自分のことばで把握して説明できること = 脳力」(ものわかりのよさ、話のわかるひと)ではないでしょうか。
技術側は技術を自分のことばで説明する能力を持つ、それを活かす側はその話を理解できるよう基礎に興味を持ちきちんと時代ともに追いかける、それが理想的開発現場です。




ワールドを定義して、複数プレイヤと複数カメラをワールドに保持する

別記事でImageフォルダにImage/pikkoron.pngと新たに別に自作したImage/satan.pngを保存しておいてください→CharaMELで自作のキャラのタイル分割画像を作る


Unityなどゲームエンジンには「シーン」などの概念がある。
「シーン」の下に複数のカメラや複数のゲームオブジェクトがヒエラルキーとしてぶら下がっている。
シーンを閉じるとともにぶら下がっている複数のゲームオブジェクトを巻き込んで「道ずれにシーンごとリセット」される。
シーンをまたぐためには「別シーンをロードしても消えないDontDestoroyOnLoadを前提としたSingletonMonoBehaviorなど」がパターンとして使われる。
ゆえに、プレイヤが扉に入って、別ワールドに移動するような場合「シーン切り替え=ワールド移動」のようにイメージされていると思う。
じゃあ、例えばプレイヤ①がワールド①にいて、プレイヤ②がワールド①からワールド②に扉で移動するとしたケースでは、
ワールド①とワールド② を シーン①とシーン② のように設計しているとすると、シーン②をLoadするときにシーン①が消えてしまうではないか。
(ワールド①をDontDestoroyOnLoadにしたときには、シーン①とシーン②のステージのXYZ座標のエリアが「かぶらないように」しないと、2つのステージがごっちゃになった世界になっちゃう)
ゆえに、今回は「シーン」という概念と「ワールド」という概念を明確に区別する設計をしてみよう。
「シーン」の下に複数の「ワールド」がぶら下がるような設計をするためには、
複数のカメラやゲームオブジェクトの保持(と道連れリセット)は「ワールド」にて分担する形がいい。

今回は先行して「ワールド」のみの設計に集中する。
設計するにあたり、シューティングでGameManger(シングルトン)の担った役割をワールドが代わりに担うため、
色んな別クラスでWorld.hが #include "World.h" されてインクルードのリンクがワールドに集中して循環インクルード問題が起こることが事前に予想される。
ゆえにワールドは「個別の Player.hやCamera.h のインクルードはしない」設計が適切になる。
#includeするかわりに「前方宣言をして Playerというクラスや Cameraというクラスがありうる」という宣言だけをしてやる。

class Camera; // 前方宣言だけして#include はしなければポインタ型 Camera* の定義は許される
class Player;

class World
{
    Camera* camera; // 前方宣言をすればポインタは定義が許されるようになる
    Player* player;
};

ポインタの実体は「メモリ上の住所 = 番地番号」である。
だから64bit環境のマシンではポインタは64ビットの番地を持てるし、少し昔の32bit環境のマシンではポインタは32ビットの番地を持っているに過ぎない
ようは、void* 型のポインタも Player* 型のポインタも「実は同じ64bitの数字 = int型やshort型などと同類」に過ぎないわけです。
だから.hヘッダでPlayerのメンバの変数や関数を呼び出したりしない状況にして、ポインタだけならば、
実は #include しなくてもポインタ番号だけの定義にしてしまえば「インクルードするまでもない」のです。
Playerのメンバ変数の hp ヒットポイント などまでアクセスしたり、コンストラクタで初期化したいなら #include Playerまで必要だが、
Worldクラスの中に、Player* player; や Camera* camera; など「ポインタを羅列するだけなら前方宣言だけでいい」のである。
そして、この「ポインタならばインクルード不要」を応用することで「循環インクルード」を回避できるのである。

ワールドクラスの定義はプレイヤやカメラなどの「ポインタをキープ」することに特化する。
ようは「住所番地の掲示板」を主とした役割として設計するのだ。

第2の設計要件として必要なのが「キープ = 道連れ」の役割である。
ワールドを閉じたときにワールド内にカメラやプレイヤがいたら「ワールドもろとも一緒にメモリから消えてお掃除してほしい = バルス」
「キープ(道ずれ)」は共有ポインタの役目そのものである。
C#で暗に黙々とガベージコレクタがやってくれている役割を共有ポインタで代行する。

#include <memory> // 共有ポインタを使う
class Camera; // 前方宣言だけして#include はしなければポインタ型 Camera* の定義は許される
class Player;

class World
{
    std::shared_ptr<Camera> camera; // 前方宣言をすればポインタは定義が許されるようになる
    std::shared_ptr<Player> player;
};

これで、Worldクラスが削除されるとともに、cameraやplayerが別の個所に共有リンクが張られていない状態(共有カウンタが1←ワールドのみの)ならば、
worldが最後までキープしていた共有カウンタ1が、このworldクラスが閉じられるとともに、共有カウンタが0になり、道ずれにできる。
つまり、Worldクラスは共有ポインタの最後の命綱(いのちづな)の共有カウント 1 をキープする役割を上記コードで期待できるわけです。
具体的には、以下のコード例で、プレイヤは命綱をキープして消えずにワールドを移動できます。

world2->player = world1->player; // ワールド2でプレイヤへのリンクが張られたのでplayerポインタの共有カウンタが+1になる
world1->player = nullptr; // world1のplayerポインタがnullになるのでplayerの共有カウンタが-1される
// 結果、playerはワールド2のぶんは最低限の共有カウンタ1は確保されているので消えない(ワールド移動完了!!!!)

このように、ようはstd::shared_ptr型のリンクがnullptrじゃないリンクされた数だけ共有ポインタのplayer内部の共有カウンタが+1され、
全リンクがnullptrにならない限り、playerは消えるのを免れられるということ。

例えるならば、メンヘラ系VTuberがいいねが完全に0になったらやめる(引退するする詐欺)みたいな状況をイメージすると、ワールドが最後の推し 1 カウントを保持していて、
完全にどこからも気にされない状態(共有リンク切れ)ならばワールド消滅とともにさすがに消していいだろ。
という「この世界(ワールド)にいるうちは最後まで見捨てないよ設計」です。
なので、デッドロック(互いにどっかのクラス同士で共有リンク張りあい状態)のときは
「永遠にメンヘラが世界消滅後もメモリに残り続けるという罠」
もあったりするので気を付けましょう。

そして、もしPlayerのhpなどメンバ変数にアクセスしたかったり、
コンストラクタを呼んで初期化したいときはWorld.cppのほうで#include "Player.h"すれば、循環インクルードは回避できる構造を設計できる。

World.hの例
#include <memory> // 共有ポインタを使う
class Camera; // 前方宣言だけして#include はしなければポインタ型 Camera* の定義は許される
class Player;

class World
{
    std::shared_ptr<Camera> camera; // 前方宣言をすればポインタは定義が許されるようになる
    std::shared_ptr<Player> player;
    void MakePlayer(); // .hでは初期化を直で呼ばずに、間接的に初期化するための関数だけを定義
};

World.cppの例
#include "World.h";

#include "Player.h"; // .cppではstd::make_sharedで初期化コンストラクタを呼ぶためにインクルードは必要
#include "Camera.h";

void World::MakePlayer()
{
    player = std::make_shared<Player>(); // 初期化コンストラクタを呼ぶ
    player->hp = 10; // インクルードしてればメンバ変数にもアクセスできる
};

こういう風にして実際の#includeを.cppに逃がせば、少なくともPlayer.h と World.hどうしが循環インクルードすることはない構造を設計できる
まあ、普通はめんどくさがらずに「実際の処理は全部 .cpp に書くようにする」のが王道の世間一般のC++のプログラミングスタイルではある。
ただ、私はVisual Studioで「定義をここに表示」で.hヘッダの処理をワンクリックで追いかけて見たりできて便利なので
「コードを試行錯誤で書いている最中は.hで処理を書いて考えたり、必要に応じて.cppに処理を移したり、最後に丁寧に.cppに処理を移してきれいにする」
みたいな形で書き進めがちなので .h書き をおすすめしてるわけではない。
きれいな理想的なコーディングスタイルはFaceBookの元技術者の人の恐ろしいほど簡潔で「読める」コードをみて真似するのがよい。最終理想形である。
上記のプロジェクトでも、中身が1行の処理のようなinline展開できそうな簡単な関数の処理は.hに書かれていたりする。
また、ヘッダオンリーなライブラリなどの文化もあるので、スタイルは場合や人や開発現場によりけりである。



World.hを新規作成して、世界で保持する(道ずれにする)要素を共有ポインタで定義しましょう。

#ifndef WORLD_H_
#define WORLD_H_

#include <list>
#include <memory>
#include <unordered_map>
#include <functional> // std::functionのラムダ式 で 関数を Draw関数の引数にして、カメラすべてに対して描画処理を発動させる

#include "MyHash.h" // ワールドのタグをhash32型で管理する


// [前方宣言] 宣言だけして、#includeはしてないので、ポインタだけはこのファイル内でつかえる
// #includeはしてないので、hpなどの変数には#include "~.h"しないとアクセスできないので循環インクルード防止のため.cppでインクルードしてください
class GameScene;
class Player;
class Bullet;
class Enemy;
class Camera;

// プレイヤや敵や弾などの共有ポインタを管理し、EraseRemoveIfで消して参照カウンタが0になったらそれらを消滅させるクラス(C#のガベージコレクタの代わり)
class World
{
public:
    inline World(GameScene* pScene, const std::string& worldTag = "") : m_pScene{ pScene }, tag{ worldTag } {}
    inline ~World() {}
protected: // public:にすると他で外部で後からタグやシーンを書き換えられると辞書での管理などの前提がおかしくなるのでprotected:する
    hash32 tag{ "" }; // ワールド名のタグ
    GameScene* m_pScene{ nullptr }; // ワールドを所有するシーン
public:
    hash32 Tag() const { return tag; }
    GameScene* scene() { return m_pScene; }

    std::unordered_map<hash32, std::shared_ptr<Player>> players; // プレイヤのタグ辞書

    std::list<std::shared_ptr<Bullet>> bullets; // 弾のリスト

    std::list<std::shared_ptr<Enemy>> enemies; // 敵のリスト

    // 削除処理を共通テンプレート関数にする
    // [共通テンプレート関数]https://programming-place.net/ppp/contents/cpp/language/009.html#function_template
    template <typename TypeT, class T_if>
    void EraseRemoveIf(std::list<TypeT>& v, T_if if_condition)
    {   //            特定のタイプT↑  ↑配列v   ↑条件式if_condition
        v.erase(
            std::remove_if(v.begin(), v.end(), if_condition),
            v.end() //  ↓remove_ifの位置
        );//例.[生][生][死][死][死]← v.end()の位置
    };

    std::unordered_map<hash32, std::shared_ptr<Camera>> cameras; // このワールドに存在するカメラの辞書<カメラにつけたタグ, カメラの共有ポインタ>
    // 指定されたタグのカメラへの共有ポインタを得る カメラ辞書に存在しないタグでアクセスしたときにunordered_mapの特性で勝手に意図しないカメラができるのを防ぐ関数
    std::shared_ptr<Camera> camera(const std::string& cameraTag) { auto itr = cameras.find(cameraTag); return (itr != end(cameras)) ? itr->second : nullptr; }
    // このワールドに存在するカメラすべてに対して drawFuncで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
    void Draw(std::function<void()> drawFunc) noexcept;
    // カメラにタグをつけてワールドに追加 カメラにもワールドへのポインタリンクを張る
    inline bool AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera);
    // 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
    inline bool EraseCamera(const std::string& cameraTag);
    // カメラを nowWorld から nextWorld に移動させる
    static bool MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld);

};

#endif


World.cppを新規作成して、ポインタ以外の実体のインクルードが必要な処理を記述しましょう。

#include "World.h"

#include "Player.h"
#include "Camera.h"
#include "Screen.h"


// カメラにタグをつけてワールドに追加 カメラにもワールドへの生ポインタリンクを渡す
bool World::AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera)
{
    if (pCamera == nullptr) return false;
    if (cameras.count(cameraTag) > 0) return false; // すでに同じタグのカメラがワールドにあったら false
    if (pCamera->world() != nullptr) pCamera->world()->EraseCamera(cameraTag); // すでにカメラが別ワールドに配置されていたら別ワールドから削除
    cameras.emplace(cameraTag, pCamera); // ワールドにカメラを追加する
    pCamera->world(this); // カメラにもワールドへのポインタリンクを張る(カメラ側に渡すのは共有ポインタではない生ポインタだから共有デッドロックにはならない)
    return true;
}

// 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
bool World::EraseCamera(const std::string& cameraTag)
{
    auto itr = cameras.find(cameraTag); // 指定したタグのカメラを探す
    if (itr == end(cameras)) return false; // 指定したタグのカメラがワールドにない
    auto pCamera = itr->second;
    pCamera->world(nullptr); // カメラ側の worldへのリンクのポインタも nullptrに
    cameras.erase(cameraTag); // カメラを辞書から削除

    return true;
}

// カメラを nowWorld から nextWorld に移動させる
bool World::MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld)
{
    if (nowWorld == nullptr) return false;
    auto itr = nowWorld->cameras.find(cameraTag); // タグでカメラを探す
    if (itr == end(nowWorld->cameras)) return false; // タグに関連づいたカメラがなかった
    auto targetCamera = itr->second; // カメラの共有ポインタをキープ(次の行でeraseしてもカメラの共有カウンタは0にはならずこのリンクのおかげでカメラはメモリに残存)
    targetCamera->world(nullptr); // カメラ側の worldへのリンクのポインタを一旦 nullptrに
    nowWorld->cameras.erase(cameraTag); // nowWorld からは カメラを消す
    if (nextWorld == nullptr) return false;

    return nextWorld->AddCamera(cameraTag, targetCamera); // 次のワールドにカメラを追加する
}

// このワールドに存在するカメラすべてに対して funcで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
void World::Draw(std::function<void()> drawFunc) noexcept
{
    for (auto&& keyValue : cameras)    // このワールドにあるすべてのカメラぶんループ
    {
        VECTOR_D beforeCamPos; // 設定変更前のカメラのパラメータを一旦、保管
        double beforeVRot, beforeHRot, beforeTRot, beforeNear, beforeFar, beforeFov;
        Camera::GetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);

        auto& camera = keyValue.second;
        //camera->screenID
        DxLib::SetCameraPositionAndAngle(camera->pos, camera->rot.V, camera->rot.H, camera->rot.T); // 各カメラの位置、回転を設定

        Screen::Start(camera->screenHandle, true, camera->fovAngle); // カメラに関連付けられたスクリーンIDを描画先にする
        {
            drawFunc(); // 渡された描画処理を実行
        }
        Screen::End(true); // 描画先を元に戻す

        // カメラの設定をもとに戻しておく
        Camera::SetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);
    }
}


Camera.hを変更して、ワールドに所有される立場のカメラ側からも自分の今いるワールドの情報にアクセスできるために
World* 型の生ポインタを初期化と同時に渡しておけるようにしましょう。

#ifndef CAMERA_H_
#define CAMERA_H_

#include <functional> // カメラのDraw関数に「引数として関数=ラムダ式」を渡してカメラのDraw関数の外側の呼び出し元から描画したい処理を渡すことができるようにする

#include "DxLib.h"
#include "Screen.h"
#include "MyDraw.h"
#include "Vector3.h"
#include "World.h"


class Camera
{
public:
    // カメラの位置(ワールド座標)
    union {
        Vector3 pos; // カメラの位置(ワールド座標)
        struct { float x, y, z; }; // unionしてx,y,zでもアクセスできるように
    };

    struct Rotate { double V, H, T; }; // カメラの回転角度定義 DXライブラリの内部行列ではfloatじゃなくてdouble型で保持されているので倍精度のdouble型にした
    Rotate rot{ 0.0, 0.0, 0.0 }; // カメラの3軸の回転角度
    double fovAngle = 0; // カメラをスクリーンに描くときの視野角(0~179度) 0のときはDxLib::GetCameraFovD()で得られる値が適用される

    float cameraLookAtHeight{ 0.0f }; // LookAtする対象を見下ろす高さ

    int screenHandle = DX_SCREEN_BACK; //カメラを描くスクリーンのハンドル番号
protected:
    World* m_world; // カメラが存在するワールドへのポインタ
public:
    World* world() { return m_world; } //ワールドへのポインタを得る
    Camera& world(World* pWorld) { m_world = pWorld; return *this; } // ワールドへのポインタを再セットする



    // コンストラクタ
    Camera(World* pWorld, Vector3 worldPos) : m_world{ pWorld }, pos { worldPos } {}

    // デストラクタ
    ~Camera() {}

    // カメラを通して描くスクリーンのハンドル番号を設定する
    void SetScreenHandle(int handle) { screenHandle = handle; }

    // カメラの位置をセットしなおす
    void SetPosition(Vector3 worldPos)
    {   // ワールド座標系として保存
        this->pos = worldPos;
    }

    // 指定されたワールド座標が画面の中心に来るように、カメラの位置を変更する
    void LookAt(Vector3 targetXYZ)
    {
        // カメラの位置と向きを設定
        //MyDraw::SetCameraPositionAndTarget_UpVecY(pos, targetXYZ + Vector3(0, cameraLookAtHeight,0));
        // 注視点はターゲットの座標から cameraLookAtHeight 分だけ高い位置
        DxLib::SetCameraPositionAndTarget_UpVecY(this->pos, targetXYZ + Vector3(0, this->cameraLookAtHeight, 0));
        // カメラ回転後の角度を保存
        this->rot.H = DxLib::GetCameraAngleHRotateD(); // カメラの水平方向の向きを取得
        this->rot.V = DxLib::GetCameraAngleVRotateD();
        this->rot.T = DxLib::GetCameraAngleTRotateD();
    }

    // 現在のDX内部のカメラのビュー行列などに入っているカメラの設定パラメータを得る
    static void GetCameraSettings(VECTOR_D* pPosition, double* pRotV = nullptr, double* pRotH = nullptr, double* pRotT = nullptr,
        double* pFov = nullptr, double* pNear = nullptr, double* pFar = nullptr)
    {
        if (pPosition != nullptr) *pPosition = DxLib::GetCameraPositionD(); // カメラ位置
        if (pRotV != nullptr) *pRotV = DxLib::GetCameraAngleVRotateD(); // カメラの角度V
        if (pRotH != nullptr) *pRotH = DxLib::GetCameraAngleHRotateD(); // カメラの角度H
        if (pRotT != nullptr) *pRotT = DxLib::GetCameraAngleTRotateD(); // カメラの角度T

        if (pFov != nullptr) *pFov = DxLib::GetCameraFovD(); // カメラの視野角
        if (pNear != nullptr) *pNear = DxLib::GetCameraNearD(); // カメラ一番近くの描き始めるレンジ
        if (pFar != nullptr) *pFar = DxLib::GetCameraFarD(); // カメラ一番遠くの描くレンジ

    }

    // カメラの視野角に変更があったら変えて射影変換行列を再計算する (現在と同じfovの数値の場合は再計算をしないで負荷を回避)
    static inline void SetupCamera_PerspectiveD(double fov) { if (DxLib::GetCameraFovD() != fov) DxLib::SetupCamera_PerspectiveD(fov); }
    // カメラの視野角に変更があったら変えて射影変換行列を再計算する (現在と同じfovの数値の場合は再計算をしないで負荷を回避)
    static inline void SetCameraNearFarD(double Near, double Far) { if (DxLib::GetCameraNearD() != Near && DxLib::GetCameraFarD() != Far) DxLib::SetCameraNearFarD(Near, Far); }

    // カメラの設定を変える
    static void SetCameraSettings(VECTOR_D* pPosition, double* pRotV, double* pRotH, double* pRotT,
        double* pFov = nullptr, double* pNear = nullptr, double* pFar = nullptr)
    {
        if (pPosition != nullptr && pRotV != nullptr && pRotH != nullptr && pRotT != nullptr)
            DxLib::SetCameraPositionAndAngleD(*pPosition, *pRotV, *pRotH, *pRotT);
        if (pFov != nullptr) Camera::SetupCamera_PerspectiveD(*pFov); // カメラの視野角に変化があったら射影変換行列を更新
        if (pNear != nullptr && pFar != nullptr) Camera::SetCameraNearFarD(*pNear, *pFar); // カメラ一番近くから遠くまで描くレンジ
    }

    // このワールドに存在するカメラすべてに対して funcで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
    inline void Draw(std::function<void()> drawFunc) noexcept
    {
        VECTOR_D beforeCamPos; // 設定変更前のカメラのパラメータを一旦、保管
        double beforeVRot, beforeHRot, beforeTRot, beforeNear, beforeFar, beforeFov;
        Camera::GetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);

        DxLib::SetCameraPositionAndAngle(this->pos, this->rot.V, this->rot.H, this->rot.T); // カメラの位置、回転を設定

        Screen::Start(this->screenHandle, true, this->fovAngle); // カメラに関連付けられたscreenHandleのスクリーンを描画先にする
        {
            drawFunc(); // 渡された描画処理を実行
        }
        Screen::End(true); // 描画先を元に戻す

        // カメラの設定をもとに戻しておく
        Camera::SetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);
    }

};

#endif


MyMath.hを新規作成して、プレイヤが歩き回るさいの計算に必要な円周率などの定数を定義しましょう。

#ifndef MYMATH_H_
#define MYMATH_H_

// 数学関連クラス
class MyMath
{
public:
    // [static constexprを使うことでstaticな定数を.hヘッダで初期化] https://onihusube.hatenablog.com/entry/2019/06/30/230431
   
    static constexpr float Sqrt2 = 1.41421356237f; // ルート2
    static constexpr float PI = 3.14159265359f; // 円周率
    static constexpr float Deg2Rad = PI / 180.0f; // 度からラジアンに変換する定数
};

#endif


Player.hを新規作成して、ワールドを歩きまわるプレイヤをタイル分割画像でパラパラアニメ描画できるようにしましょう。

#ifndef PLAYER_H_
#define PLAYER_H_

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "MyHash.h" // hash32型でプレイヤが現在いるワールドのタグを管理する
#include "Vector3.h"

class World; // 前方宣言
class Camera; // 前方宣言

class Player
{
public:
    // コンストラクタ
    Player(World* world, Pad pad, Vector3 position)
        : pad{ pad }, m_world{ world }, position{ position }, velocity{ 0,0,0 }, force{0,0,0}
    {
        this->tag = "Player";
    }

    // 仮想デストラクタ
    virtual ~Player() {}

    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    };// unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

    hash32 tag{ "" }; // プレイヤにつけるタグ
protected:
    World* m_world; // 配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }

    Pad pad; // 操作するコントローラー番号
    hash32 worldTag{ "" }; // 現在いるワールドのタグ
    float MoveSpeedMax = 6; // 移動速度Max値
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // プレイヤの動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

    std::shared_ptr<Texture> image; // プレイヤの板ポリゴンで描くタイル分割画像への共有リンク

    Camera* camera{ nullptr }; // プレイヤを追随するカメラ
    void SetCamera(Camera* pCamera) { camera = pCamera; }

    // 入力を受けての処理
    virtual void HandleInput();

    // 更新処理
    virtual void Update();

    // 描画処理
    virtual void Draw();

};

#endif



Player.cppを新規作成して、ワールドを歩きまわる処理を記述にしましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}


// 更新処理
void Player::Update()
{
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    // 実際に位置を動かす

    // まず横に移動する
    x += vx;

    // 次に縦に動かす
    y += vy;

    // 次にZ奥行き方向に動かす
    z += vz;

    vy += vyForce; // 勢い(力・フォースを加算


    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

            });

    }
}



main.cppを変更して、Worldクラスに移したカメラとプレイヤをworld1経由で描画してみましょう。

#include "DxLib.h"

#include "Screen.h"
#include "Camera.h"
#include "MyHash.h"
#include "Resource.h"
#include "MyDraw.h"
#include "Player.h"
#include "World.h"


// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMainからプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズ Width×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズ Width×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("3Dタイルゲーム");//この行でエラーになったら【設定】マルチバイト文字セットが間違ってるかも
    //↑ここまでの設定は↓下のDXライブラリ初期化より先にやらないとDxLib_Init()中は画面がフルスクリーンになって終わってからウィンドウサイズが変更になり見苦しい

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    DxLib::ScreenFlip();

    // タイルマップを読み込む 縦8マス、横8マス、縦32ピクセル、横32ピクセル
    std::string mapChipPath{ "Map/mapchip.png" };
    Resource::MakeShared<Texture>(mapChipPath,8,8,32,32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする

    std::string playerImgPath1{ "Image/pikkoron.png" };
    Resource::MakeShared<Texture>(playerImgPath1, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
   
    std::string playerImgPath2{ "Image/satan.png" };
    Resource::MakeShared<Texture>(playerImgPath2, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする


    
    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    int screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    int screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);

    // ワールドを生成
    std::shared_ptr<World> world1 = std::make_shared<World>(nullptr, "ワールド1");


    // カメラを2つ生成して、2つのカメラ視点を2つのスクリーンにそれぞれ関連付ける
    std::unordered_map<hash32, std::shared_ptr<Camera>> cameras; // 複数のカメラへのポインタを格納するタグを振った辞書配列
    world1->cameras.emplace("カメラ0", std::make_shared<Camera>(world1.get(), Vector3{ 20,40,-50 })); // カメラ0
    world1->cameras.emplace("カメラ1", std::make_shared<Camera>(world1.get(), Vector3{ 80,30,-50 })); // カメラ1

    world1->cameras["カメラ0"]->screenHandle = screenHandle0; // カメラ0 と スクリーン0 を関連付ける
    world1->cameras["カメラ1"]->screenHandle = screenHandle1; // カメラ1 と スクリーン1 を関連付ける
   
    world1->players.emplace("プレイヤ0", std::make_shared<Player>(world1.get(), Pad::Key, Vector3{ 20,30,-50 }));
    world1->players.emplace("プレイヤ1", std::make_shared<Player>(world1.get(), Pad::Two, Vector3{ 80,30,0 }));

    world1->players["プレイヤ0"]->SetCamera(world1->cameras["カメラ0"].get()); // .get()で共有ポインタstd::shared_ptrから生の*ポインタへ変換
    world1->players["プレイヤ1"]->SetCamera(world1->cameras["カメラ1"].get());

    world1->players["プレイヤ0"]->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);
    world1->players["プレイヤ1"]->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);

    Input::Init(); // 入力状態を初期化

   

    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Screen::ClearDrawScreen(screenHandle0); // 一旦キャンバスをきれいにまっさらに
        Screen::ClearDrawScreen(screenHandle1); // 一旦キャンバスをきれいにまっさらに

        Input::Update(); // 入力状態を更新

        // 光の明暗計算を無効に
        DxLib::SetUseLighting(FALSE);

        // Zバッファを有効にする [Zの深度]
        //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
        DxLib::SetUseZBuffer3D(TRUE);
        // Zバッファへの書き込みを有効にする
        DxLib::SetWriteZBuffer3D(TRUE);

        // world1のプレイヤを更新する
        for (auto& pair : world1->players)
        {
            auto pPlayer = pair.second;
            pPlayer->Update();
        }

        // world1のプレイヤを描画する
        for (auto& pair : world1->players)
        {
            auto pPlayer = pair.second;
            pPlayer->Draw();
        }


        // 複数のカメラを 範囲 for文 で回す
        for (auto&& camera : world1->cameras)
        {   // ラムダ式[&](){ ~ }で{}の外側の変数すべてを & キャプチャで&参照として「Draw関数の内側へ引き連れて」処理できる
            camera.second->Draw([&]()
                {
                    // Draw関数() の 外側にある変数 mapChipPath も[&]効果で { } の内側で問題なくアクセスできている↓

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 0);
                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 70, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 4);

                    if (camera.first == "カメラ0") //←"カメラ0"はビルド時に事前にハッシュ値が計算され、文字列ではない数値とおきかわっている
                        DxLib::DrawString(0, 0, "こちらがカメラ0", GetColor(255, 0, 0));
                    else if (camera.first == "カメラ1")
                        DxLib::DrawString(0, 0, "こちらはカメラ1", GetColor(0, 255, 0));
                });
        }

        // スクリーン0の内容を画面に描く
        DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
        DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));
        
        // スクリーン1の内容を画面に描く
        DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
        DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));

        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょう?実行したら、キーボードの上下左右矢印キーで上画面のプレイヤ0を、WASDキーで下画面のプレイヤ1を操作することができたでしょうか?
worldを間にはさんだことで、cameraとplayerがworldのポインタを経由して互いのデータにアクセスできるため、
違うキャラのカメラと自分のキャラの位置など、トリッキーなデータどうしのアクセスもworldを通してできるようになっています。
また、違うワールドにいるときはカメラのforループでは違うワールドにあるカメラはfor文に引っかからないわけなので、
別の世界にいるキャラは当然、描かれない仕組みがワールドの配下にcamaraやplayerのポインタが管理されることによって実現できています。


シーンを定義して、ゲームシーンの中に複数ワールドを保持する

前提として乱数クラスMyRandomを別記事から作成しておきます乱数で散乱弾を作成する


つぎは、シーンを定義して、ワールドはGameSceneの中で複数保持できるように設計していきましょう。

Scene.hを新規作成して、様々なシーンの基底ベースとなるクラスを定義します(UpdateやDrawなどを純粋仮想関数としてoverride必須にしておく)。

#ifndef SCENE_H_
#define SCENE_H_

#include <string> // シーンの判別文字列tagに使う
#include "MyHash.h" // hash32型をタグ判別に使う

// シーンの基底クラス あいまいなabstract型タイプ(virtual ~() = 0;の純粋仮想関数を含む)
class Scene
{
public:
    std::string tag{ "" }; // シーンでロードするファイル名などに使う
    hash32 typeTag{ "" }; // シーンのタイプの判別に使う

    // コンストラクタ
    Scene(const std::string& sceneTag = "") : tag{ sceneTag }
    {
    }

    //仮想デストラクタ【忘れるとメモリがヤバいダメ絶対】
    virtual ~Scene()
    {
        // 特に配列とかなくても★string文字列も内部では実質配列だから!5文字分のメモリとかがすり減ってく..
        // 仮想デストラクタはstringなど暗黙に内部に配列を持つ要素のデストラクタを暗黙に呼んでくれている!
    }

    //★純粋仮想関数=0は継承したGameSceneなどの必須機能(継承したら絶対override必須縛り)
    // 必須縛りによってSceneを継承したものは【Update,Drawが実装されてるはずの確証があるので】
    // for文であいまいなSceneのまま【まとめてUpdate,Drawできる】のだ。

    // 初期化処理
    virtual void Initialize() = 0; //純粋仮想関数に(継承したらoverride必須)

    // 終了処理(大抵Initializeと同じくリセット処理を行うがInitと区別して終了時だけやりたいリセットもある)
    virtual void Finalize() = 0; //純粋仮想関数に(継承したらoverride必須)

    // 更新処理
    virtual void Update() = 0; //純粋仮想関数に(継承したらoverride必須)

    // 描画処理
    virtual void Draw() = 0; //純粋仮想関数に(継承したらoverride必須)
};

#endif


次に、シーンを管理するマネージャーが欲しいので、シングルトンのテンプレートクラスを準備します。

Singleton.hを新規作成して、シングルトンのテンプレートクラスを定義します。

#ifndef SINGLETON_H_
#define SINGLETON_H_

// テンプレートT型シングルトンSingleton<~> ~になんでも指定して唯一物にできる
// ★Singleton<TypeT>型は【必ず唯一で複製不可】(使い方:どこからでもアクセスし変数を共有するクラス向き)
// ★ゲーム内の【どこからアクセスしても統一性が保たれる】(唯一だから)「クラス名と実体が一致」
template<class TypeT>
class Singleton
{
public:
    // Instance()を通して【しか】T型クラスをつくれないうえに、
    // ★作ったものは【かならず唯一で複製不可】
    // それが【シングルトン型】!
    // ゲーム中で唯一で複製不可にしたい【ゲームを一律管理するクラス】などに最適!
    // https://teratail.com/questions/17416
    // http://rudora7.blog81.fc2.com/blog-entry-393.html
    // ②↓同じ関数の定義はプログラム全体で1つだけしか許されません。(static関数名は被らない)
    static inline TypeT& Instance()
    {   //①↓関数の中で定義されたstatic変数は、関数の定義毎に1つ領域が確保されます。
        static TypeT instance; //メモリ上にstatic変数確保(静かに常に待機するinstance)
        return instance; //①=(常駐)②=(唯一) ①と②両方満たすinstanceは【メモリ上に唯一】
    }

protected:
    Singleton() {} // 外部でのインスタンス作成は禁止(protected:内部公開、外部禁止)
    virtual ~Singleton() {} // 仮想デストラクタ(シングルトンを【継承するためには忘れちゃダメ】)

private:
    void operator=(const Singleton& obj) {} // 代入演算子禁止
    Singleton(const Singleton& obj) {} // コピーコンストラクタ禁止(コピーできない【唯一】)
    static TypeT* instance; // private:だから外部アクセスできない【GetInstance()経由しか無理】
};

// いつもcppで書いたstatic変数初期化も★【Tテンプレートクラスだからすべて.hヘッダにまとめて書いた】
template< class TypeT >
TypeT* Singleton< TypeT >::instance = 0; //cppでの定義を.h内に書いた(static変数はclass内で初期化できないから)

#endif


SceneManager.hを新規作成して、シングルトンを継承してどこからでもアクセスできるマネージャを定義します。

#ifndef SCNENEMANAGER_H_
#define SCNENEMANAGER_H_

#include <vector>
#include <memory>
#include <string>
#include <assert.h> // シーン読み込みの失敗表示用

#include "MyHash.h" // hash32型でタグを管理する
#include "Singleton.h"

class Scene; //クラス宣言だけで★インクルードしないのでこのマネージャファイルで循環は止まる

class SceneManager : public Singleton<SceneManager>//←<~>として継承すると唯一のシングルトン型タイプとなる
{
public:
    friend class Singleton<SceneManager>; // Singleton でのインスタンス作成は許可

    std::shared_ptr<Scene> prevScene{ nullptr }; // 一つ前のシーン
    std::shared_ptr<Scene> currentScene{ nullptr }; // 現在のシーン

    hash32 currentSceneType{ "" }; // 現在のシーン名を保管しておく
    std::string currentSceneTag{ "" }; // 現在のシーンのタグを保管しておく

    hash32 changingSceneType{ "" }; // 予約された次のシーンのクラス名(次に移動予定のシーン名を保管しておく)
    std::string changingSceneTag{ "" }; // 予約された次のシーンのタグ(次に移動予定のシーンのタグを保管しておく)

    // シーン名を比較、何の変化もなければfalseでシーン移動不必要の判定
    bool isChanging(const std::string& nextSceneType, const std::string& nextSceneTag = "");

    // 次のシーンの予約だけしてLoadScene()を引数なしで呼び出したタイミングで遷移する(ループの途中じゃないキリのいいタイミングを狙って遷移させる)
    void NextScene(const std::string& nextSceneName, const std::string& nextSceneTag = "");

    // シーンをチェンジし遷移する(前のシーンのリセット処理もする)
    void LoadScene(const std::string& sceneName = "", const std::string& nextSceneTag = "");

protected:
    SceneManager() {}; // 外部からのインスタンス作成は禁止
    virtual ~SceneManager() {}; //外部からのインスタンス破棄も禁止
};

#endif



SceneManager.cppを新規作成して、先行してGameSceneなどをロード・切り替えする処理を先に書いておきます。

#include "SceneManager.h"

#include "Scene.h"
#include "TitleScene.h"
#include "GameScene.h"
#include "GameOverScene.h"

bool SceneManager::isChanging(const std::string& nextSceneType, const std::string& nextSceneTag)
{
    if (currentSceneType != nextSceneType) //シーンのタイプ不一致
        return true; // シーンのタイプが変更
    if (currentSceneTag != nextSceneTag) //シーンのタグ不一致
        return true; // シーンのタグが変更

    return false; // シーンに変更無し
}

void SceneManager::NextScene(const std::string& nextSceneType, const std::string& nextSceneTag)
{
    // 遷移予定nextシーン名と現在のシーン名を比較、何の変化もなければfalseでシーン移動不必要の判定
    if (isChanging(nextSceneType) == false) return; //特に変更なしでシーン移動の必要なし

    changingSceneType = nextSceneType; // 次に移動予定のシーン名を保管しておく
}

void SceneManager::LoadScene(const std::string& nextSceneType, const std::string& nextSceneTag)
{
    hash32 loadSceneType = nextSceneType;
    std::string loadSceneTag = nextSceneTag;

    // nextSceneTypeの指定が空の場合にはNextScene()での事前のシーン変更予約があるかチェック
    if (nextSceneType == "" && changingSceneType != "")
    {
        loadSceneType = changingSceneType;
        loadSceneTag = changingSceneTag;
    }
    if (loadSceneType == "") return; // シーン指定がないときは終了

    // 現在のシーンの終了処理
    if (currentScene != nullptr)
        currentScene->Finalize(); // 終了処理を呼び出す

    if (loadSceneType == "TitleScene")
    {
        // 次のシーンの生成
        currentScene = std::make_shared<TitleScene>(loadSceneTag);
    }
    else if (loadSceneType == "GameScene")
    {
        // 次のシーンの生成
        currentScene = std::make_shared<GameScene>(loadSceneTag);
    }
    else if (loadSceneType == "GameOverScene")
    {
        // 次のシーンの生成
        currentScene = std::make_shared<GameOverScene>(loadSceneTag);
    }
    else
        assert("指定されたシーンの生成処理が見つからなかった→SceneManager.cppを見直しましょう" == "");

    currentSceneType = loadSceneType; // 現在のシーン名の更新
    currentSceneTag = loadSceneTag; // 現在のシーンのタグの更新

    changingSceneType = ""; // NextScenceのシーン予約名をリセット
    changingSceneTag = ""; // NextScenceのシーン予約タグをリセット

    // 次のシーンの初期化
    currentScene->Initialize(); // 初期化を呼び出す
}

今回はシューティングのときと違って、初期化のときにシーンのタグを渡せるように設計しました。
たとえば、同じ"GameScene"のシーンタイプでも、どのステージを遊ぶのかなどをタグを通して渡せれば、
ステージごとにシーンタイプを増やして別定義しなくてすむようになりますし、柔軟性が増します。

TitleScene.hを新規作成して、特に今回は処理を書きませんが実際に制作するときにはタイトル画面を実装するためのひな形だけ作っておきます。

#ifndef TITLESCENE_H_
#define TITLESCENE_H_

#include "Scene.h"

#include "DxLib.h"
#include "Input.h"

#include "SceneManager.h"

class TitleScene : public Scene
{
public:
    //★【仲介者パターン】ゲーム内のもの同士はマネージャを通して一元化してやり取り
    SceneManager& sm = SceneManager::Instance(); //唯一のシーンマネージャへの参照(&)を得る

    // コンストラクタ
    TitleScene(const std::string& sceneTag = "") : Scene(sceneTag)
    {
        this->typeTag = "TitleScene";
    }

    // 初期化処理
    void Initialize() override
    {

    }

    // 終了処理(大抵Initializeと同じリセットだがInitと区別して終了時だけやりたいリセットもある)
    void Finalize() override
    {

    }

    // 更新処理
    void Update() override
    {
        if (Input::GetButtonDown(Pad::All, PAD_INPUT_1))
        {
            sm.LoadScene("GameScene"); //シーン遷移
            return; // シーンをロードしたらUpdateを即終了しないとUpdateの他の処理が走っちゃう
        }
    }

    // 描画処理
    void Draw() override
    {
        DxLib::DrawString(0, 0, "TitleSceneです。ボタン押下でGameSceneへ。", GetColor(255, 255, 255));

    }
};

#endif



GameOverScene.hを新規作成して、特に今回は処理を書きませんが実際に制作するときにはゲームオーバー画面を実装するためのひな形だけ作っておきます。

#ifndef GAMEOVERSCENE_H_
#define GAMEOVERSCENE_H_

#include "Scene.h"

#include "DxLib.h"
#include "Input.h"

#include "SceneManager.h"

class GameOverScene : public Scene
{
public:
    //★【仲介者パターン】ゲーム内のもの同士はマネージャを通して一元化してやり取り
    SceneManager& sm = SceneManager::Instance(); //唯一のシーンマネージャへの参照(&)を得る

    // コンストラクタ
    GameOverScene(const std::string& sceneTag = "") : Scene(sceneTag)
    {
        this->typeTag = "GameOverScene";
    }

    // 初期化処理
    void Initialize() override
    {

    }

    // 終了処理(大抵Initializeと同じリセットだがInitと区別して終了時だけやりたいリセットもある)
    void Finalize() override
    {

    }

    // 更新処理
    void Update() override
    {
        if (Input::GetButtonDown(Pad::All, PAD_INPUT_1))
        {
            sm.LoadScene("TitleScene"); //シーン遷移
            return; // シーンをロードしたらUpdateを即終了しないとUpdateの他の処理が走っちゃう
        }
    }

    // 描画処理
    void Draw() override
    {
        DxLib::DrawString(0, 0, "GameOver....ボタン押下でPlaySceneへ", GetColor(255, 255, 255));
    }
};

#endif



さて、今回の本題のGameSceneを作成する前に、シーンに関するセッティングをCSVファイルから読みだせるようにすれば、
ゲームシーンに関する情報、例えば、ステージのマップのファイルの場所一覧、などをプランナがエクセルで管理でき、それをCSVに吐き出して、
そのセッティングに合わせて、ゲームのシーンの初期化と同時に、ワールドのマップ読出しに必要な情報をCSVファイルから柔軟に得ることができます。

DataCsv.hを新規作成して、CSV形式のファイルをロードできるようにします。

#ifndef DATACSV_H_
#define DATACSV_H_

#include <vector>
#include <string>

#include <fstream> // ファイル読み出しifstreamに必要
#include <string> //文字列に必要
#include <sstream> // 文字列ストリームに必要

#include <assert.h> // 読込み失敗表示用


//★CsvValue型をint型や文字列string型や小数float型として【カメレオン】のように使える。
// 【エクセルはマス目がカメレオン】整数も小数も文字列もありうる。
// それを解消して統一して扱える便利な★CsvValue型。
class CsvValue
{
public:
    enum class Type : unsigned char
    {
        Int,
        Float,
        String,
        NUM
    };
    Type type = Type::String;
protected: // 内部データを書き換え禁止:書き換えられるとintと文字列とのずれが起きうる
    // エクセルのCsvデータはマス目がint,float,stringの3種類ありうる
    union {  // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408
        int intData = 0; // 整数int型データを格納
        float floatData; // 小数float型データを格納
    };
    union {
        std::string stringData{ "" }; // 文字列string型データを格納
    };

public:
    CsvValue() = default;
    // 3種類のコンストラクタ
    CsvValue(float floatData) : type{ Type::Float }
    {
        this->floatData = floatData;
        this->stringData = std::to_string(floatData);
    }

    CsvValue(const std::string& stringData) : type{ Type::String }
    {
        this->stringData = stringData;
    }

    ~CsvValue() {}; // 仮想デストラクタ

    void operator=(const int& intValue) // 代入演算子
    {
        this->intData = intValue;
    }
    void copy(const CsvValue& value)
    {
        type = value.type;
        if (value.type == Type::Int) { this->intData = value.intData; this->stringData = std::to_string(value.intData); }
        else if (value.type == Type::Float) { this->floatData = value.floatData; this->stringData = std::to_string(value.floatData); }
        else if (value.type == Type::String) { this->stringData = value.stringData; }
    }
    void operator=(const CsvValue& value) { copy(value); } // 代入演算子
    CsvValue(const CsvValue& value) { copy(value); } // コピーコンストラクタ

    // 変換演算子によりCSVのセルをintとしてもstringとしても扱えるように
    // https://programming-place.net/ppp/contents/cpp/language/019.html#conversion_op
    // 3種類の読み取りオペレータ
    operator int()
    {
        if (type == Type::Float) return (int)floatData;
        else if (type == Type::String && intData == 0)
            intData = intFromString(); // デフォルトで文字列として読込んだデータをintに変換
       
        return intData;
    }

    inline operator float()
    {
        if (type == Type::Int) return (float)intData;
        else if (type == Type::String && intData == 0)
            floatData = floatFromString(); // デフォルトで文字列として読込んだデータをfloatに変換
        return floatData;
    }

    inline operator std::string() const
    {
        return stringData;
    }

    int intFromString() // 文字列データからint型への変換する関数
    {
        std::istringstream ss(stringData); //文字列ストリームの初期化
        int num; // 数字単体
        ss >> num; // 文字列ストリームから数字への変換
        return num;
    }
    float floatFromString() // 文字列データからfloat型への変換する関数
    {
        std::istringstream ss(stringData); //文字列ストリームの初期化
        float num; // 数字単体
        ss >> num; // 文字列ストリームから数字への変換
        return num;
    }

};

// CSVの1行を[]オペレータでアクセスした際1行のサイズが足りない場合に自動resizeする機能を[]オペレータに実装する
struct CsvLine : public std::vector<CsvValue>
{
    inline std::vector<CsvValue>& operator[](std::size_t index) {
        if (index >= size()) { resize(index + 1); }
        return (*this)[index]; // 読み書き
    }
};

// CSVファイルを読込み幅や高さとデータ本体を保持するデータ型
struct DataCsv
{
    enum class CsvType
    {
        IntMap,
        CsvValue
    };
    CsvType type;
    bool isInitialized = false; //[継承2重ロード対策]ベース基底でロードしたらtrueに
    // 読込んだデータファイルの情報
    std::string FilePath{ "" };
    std::vector<CsvLine> Data;// csvデータ

    // ★スムーズに[][]でアクセスできるように[]演算子を独自定義する
    // https://www.hiramine.com/programming/c_cpp/operator.html
    inline std::vector<CsvValue>& operator[](std::size_t index) {
        if (index >= Data.size())
            Data.resize(index + 1); // 範囲外アクセス時は自動リサイズ(使う人が大きな数字間違っていれるとやばいので注意)
        return Data[index]; // 読取り
    }

    std::size_t size()
    {   // size()関数の名前をvectorと被らせることで使う側はvectorインvectorのままのコードで使える
        return Data.size();
    }
    // 初期化コンストラクタでファイル名を指定して初期化と同時にファイル読込
    DataCsv(std::string FilePath = "", CsvType csvType = CsvType::IntMap) : type{csvType}, FilePath { FilePath }
    {// csvファイルの読込み★【初期化と同時なのでファイルとデータ型が一心同体で使いやすい】
        if (FilePath != "")
        {
            Load(FilePath, csvType); // ファイル読込み
        }
    };
    virtual ~DataCsv()
    {// 仮想デストラクタ
        clear();// 2次元配列データのお掃除
    };

    // データをクリアしてメモリを節約する
    virtual void clear()
    {   // [確実にメモリを空にするには] http://vivi.dyndns.org/tech/cpp/vector.html#shrink_to_fit
        std::vector<CsvLine>().swap(Data); // 空のテンポラリオブジェクトでリセット
        isInitialized = false; //ロード済みフラグをOFF
    }

    // csvファイルの読み込み
    virtual void Load(std::string filePath, CsvType csvType = CsvType::IntMap)
    {
        if (filePath == "" || isInitialized) return; //ファイル名がないもしくはロード済
        this->FilePath = filePath; // ファイル名を保管
        this->type = csvType;
        Data.clear(); //データを一旦クリア
        // 読み込むcsvファイルを開く(std::ifstreamのコンストラクタで開く)
        std::ifstream ifs_csv_file(filePath);
        std::string line; //1行単位の読込み文字列

        int readWidth = 0, readHeight = 0; //読込みデータの幅と高さ
        int maxWidth = 0; //今から読み込んだ時のMAXの幅
        while (std::getline(ifs_csv_file, line)) // ファイルを行ごとに読み込む
        {
            CsvLine valuelist; // 1行の数字リスト
            std::istringstream linestream(line); // 各行の文字列ストリーム
            std::string splitted; // カンマ分割文字列
            int widthCount = 0; //この行の幅をカウント
            if (this->type == CsvType::IntMap)
            {
                while (std::getline(linestream, splitted, { ',' }))
                {
                    std::istringstream ss; //文字列ストリームの初期化
                    ss = std::istringstream(splitted); //文字列ストリーム
                    int num; // 数字単体
                    ss >> num; // 文字列ストリームから数字への変換
                    valuelist.push_back(CsvValue(num)); // 数字をリスト(1行リスト)に追加
                    widthCount++; //この行の幅をカウントアップ
                }
            }
            else if (this->type == CsvType::CsvValue)
            {
                while (std::getline(linestream, splitted, { ',' }))
                {
                    if (splitted.size() > 1 && splitted[0] == '\"')
                        splitted = splitted.substr(1, splitted.size() - 2);
                    valuelist.emplace_back(splitted); // 数字を数字のリスト(1行リスト)に追加
                    widthCount++; //この行の幅をカウントアップ
                }
            }

            if (widthCount > maxWidth) maxWidth = widthCount; //暫定Max幅を更新

            // 1行分をvectorに追加
            if (valuelist.size() != 0) Data.emplace_back(valuelist);
            readHeight++; //マップの高さをカウントアップ
        }
        readWidth = maxWidth; //MAX幅の王者を確定

        assert(readWidth > 0); // マップ幅がおかしい
        assert(readHeight > 0); // マップ高さがおかしい

        // コンストラクタで初期化でロードの場合とLoad関数経由で読む経路があるから
        isInitialized = true; // 読込み初期化済みフラグをON
        return;
    }
};


#endif


Mapフォルダの中にGameSceneSetting.csvを新規作成して、下記内容のcsvのデータで、シーン1というタグでGameSceneを初期生成するとき、
ステージ1というタグのついたワールドにMap/stage1.jsonから読みだしたマップを読み出し生成する、という情報を記述し保存しておきましょう。(マップのjson読み出しはのちの章でやる予定です)

"//","GameSceneでシーン名を指定するとその行以降をそのシーンで使うworldsとしてロードする",,,,
"GameScene","シーン1",,,,
"ステージ1","Map/stage1.json",,,,



GameScene.hを新規作成して、ゲームを実際にUpdateしたりDrawしたりするためのシーンを定義しましょう(ワールドの定義もシーンの中にある)。

#ifndef GAMESCENE_H_
#define GAMESCENE_H_

#include <memory> // 共有ポインタ std::shared_ptrを使う
#include <unordered_map> // ワールドにタグをつけて複数のワールドを管理 複数あれば(扉→別マップへの移動→元のマップへ戻っても敵が消えずに残せる)

#include "Scene.h"

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "Screen.h"
#include "Camera.h"
#include "DataCsv.h"

#include "SceneManager.h"

struct GameSceneSetting // ゲームシーン情報をGameSceneSetting.csvなどから読みだす
{
    GameSceneSetting(const std::string& csvFileName = "")
    {
        if (csvFileName != "") Load(csvFileName);
    }
    ~GameSceneSetting() {}; //デストラクタ

    struct WorldTag_Path
    {
        WorldTag_Path() = default;
        inline WorldTag_Path(const std::string& tag, const std::string& mapFilePath) : tag{ tag }, mapFilePath{ mapFilePath } {}
        inline ~WorldTag_Path() {}
        std::string tag{ "" }; // ワールドにつけるtag
        std::string mapFilePath{ "" }; // マップのファイルパス
    };

    struct Info : public std::unordered_map<hash32, WorldTag_Path> // <ワールド名のタグ, ワールドのファイルパス>
    {
        Info() = default;
        inline Info(const std::string& gameSceneTag) : gameSceneTag{ gameSceneTag } {};
        ~Info() {};
        std::string gameSceneTag{ "" }; // シーン名のタグ
    };
    std::unordered_map<hash32, Info> info; //[辞書]シーン名のタグ → ワールド情報
    bool isInitialized = false; //[継承2重ロード対策]
    std::string FilePath{ "" }; // csvのファイルパス

    Info& operator[](const std::string& gameSceneTag) {
        assert(info.count(gameSceneTag) != 0 && "シーン情報が読み取れていません Map/GameSceneSettings.csv に情報があるか確認!");
        return info[gameSceneTag]; // 読み書き
    }

    void Load(const std::string& csvFileName = "")
    {
        if (isInitialized) return; // 2重ロード対策
        if (csvFileName != "") this->FilePath = csvFileName;
        if (this->FilePath == "") return;

        DataCsv Data; // マップ情報一覧のデータ
        Data.Load(csvFileName, DataCsv::CsvType::CsvValue);// CSVファイルからデータをロード
        std::string gameSceneTag; //ゲームシーン名
        for (int j = 0; j < Data.size(); ++j)
        {
            if ((std::string)Data[j][0] == "//") continue; // 一番左の列が//なら読み飛ばしのコメント行
            else if ((std::string)Data[j][0] == "GameScene")
            {
                gameSceneTag = Data[j][1]; //ゲームシーン名
                info.emplace(std::piecewise_construct, std::forward_as_tuple(gameSceneTag), std::forward_as_tuple(gameSceneTag));
                continue;
            }

            if (gameSceneTag == "") continue; //ステージファイル名が未確定
            else if ((std::string)Data[j][0] != "")
                info[gameSceneTag][(std::string)Data[j][0]] = std::move(WorldTag_Path(Data[j][0], Data[j][1])); //ワールド名 → ワールドのファイルのパスを読み取り
        }
        isInitialized = true;
    }
};


class GameScene : public Scene
{
public:
    static GameSceneSetting settings; // ゲームシーンの設定情報 デフォルトは{ "Map/GameSceneSetting.csv" } を読み取り

    //★【仲介者パターン】ゲーム内のもの同士はマネージャを通して一元化してやり取り
    SceneManager& sm = SceneManager::Instance(); //唯一のシーンマネージャへの参照(&)を得る

    int screenHandle0{ -1 }; // 上画面スクリーンのハンドル
    int screenHandle1{ -1 }; // 下画面スクリーンのハンドル

    // タイルマップを読み込む 縦8マス、横8マス、縦32ピクセル、横32ピクセル
    std::string mapChipPath{ "Map/mapchip.png" };
    std::string playerImgPath1{ "Image/pikkoron.png" };
    std::string playerImgPath2{ "Image/satan.png" };


protected:
    std::unordered_map<hash32, std::shared_ptr<World>> worlds; // 複数ワールドの共有ポインタ辞書
public:
    // ワールドをタグをつけて生成する
    void CreateWorld(const std::string& worldTag) { if (worlds.count(worldTag) == 0) { worlds.emplace(worldTag, std::make_shared<World>(this, worldTag)); } }
    // タグからワールドへのポインタリンクを得る
    World* world(const std::string& worldTag) { auto itr = worlds.find(worldTag); return (itr != end(worlds)) ? itr->second.get() : nullptr; }
    // 指定したゲームシーンのタグに関連づいたファイル名に対応したワールドを初期化する
    bool LoadWorlds(const std::string& gameSceneTag);

    // プレイヤはタイトル画面などでも初期選択できるようにあえてworld配下の管理ではなく
    // GameScene配下の管理でゲーム開始時から直接生成できるようにここに定義

    std::unordered_map<hash32, std::shared_ptr<Player>> players; // <タグ,プレイヤへの共有ポインタ>辞書[複数プレイヤ可能]
    void ChangeWorld(const std::string& worldTag, std::shared_ptr<Player> pPlayer); // プレイヤのいるワールドを移動する


    int score = 0; // プレイ中のゲームのスコア:短期的数値でシーン遷移ごとにリセットされる

    // コンストラクタ
    GameScene(const std::string& sceneTag = "") : Scene(sceneTag)
    {
        this->typeTag = "GameScene";
        if (this->tag != "") // this->tag には : Scene(sceneTag) 経由で sceneTag が設定されている
            LoadWorlds(this->tag); // GameSceneSetting.csv に設定したタグに対応したファイルをロード
    }

    // 初期化処理
    void Initialize() override;

    // 終了処理(大抵Initializeと同じリセットだがInitと区別して終了時だけやりたいリセットもある)
    void Finalize() override
    {
        // ここにプレイ終了時のリセット処理(ゲームクリアやゲームオーバーなど)
    }

    // 更新処理
    void Update() override;

    // 描画処理
    void Draw() override;
};

#endif


World.hを変更して、AddPlayerでシーンにプレイヤ追加する関数などを定義しましょう。

#ifndef WORLD_H_
#define WORLD_H_

#include <list>
#include <memory>
#include <unordered_map>
#include <functional> // std::functionのラムダ式 で 関数を Draw関数の引数にして、カメラすべてに対して描画処理を発動させる

#include "MyHash.h" // ワールドのタグをhash32型で管理する


// [前方宣言] 宣言だけして、#includeはしてないので、ポインタだけはこのファイル内でつかえる
// #includeはしてないので、hpなどの変数には#include "~.h"しないとアクセスできないので循環インクルード防止のため.cppでインクルードしてください
class GameScene;
class Player;
class Bullet;
class Enemy;
class Camera;

// プレイヤや敵や弾などの共有ポインタを管理し、EraseRemoveIfで消して参照カウンタが0になったらそれらを消滅させるクラス(C#のガベージコレクタの代わり)
class World
{
public:
    inline World(GameScene* pScene, const std::string& worldTag = "") : m_pScene{ pScene }, tag{ worldTag } {}
    inline ~World() {}
protected: // public:にすると他で外部で後からタグやシーンを書き換えられると辞書での管理などの前提がおかしくなるのでprotected:する
    hash32 tag{ "" }; // ワールド名のタグ
    GameScene* m_pScene{ nullptr }; // ワールドを所有するシーン
public:
    hash32 Tag() const { return tag; }
    GameScene* scene() { return m_pScene; }

    // ワールドを所有するシーンにプレイヤを追加する
    void AddPlayer(const std::string& playerTag, std::shared_ptr<Player> pPlayer);
    // ワールドにいるプレイヤを得る
    std::vector<std::weak_ptr<Player>> GetPlayers();


    std::unordered_map<hash32, std::shared_ptr<Player>> players; // プレイヤのタグ辞書

    std::list<std::shared_ptr<Bullet>> bullets; // 弾のリスト

    std::list<std::shared_ptr<Enemy>> enemies; // 敵のリスト

    // 削除処理を共通テンプレート関数にする
    // [共通テンプレート関数]https://programming-place.net/ppp/contents/cpp/language/009.html#function_template
    template <typename TypeT, class T_if>
    void EraseRemoveIf(std::list<TypeT>& v, T_if if_condition)
    {   //            特定のタイプT↑  ↑配列v   ↑条件式if_condition
        v.erase(
            std::remove_if(v.begin(), v.end(), if_condition),
            v.end() //  ↓remove_ifの位置
        );//例.[生][生][死][死][死]← v.end()の位置
    };

    std::unordered_map<hash32, std::shared_ptr<Camera>> cameras; // このワールドに存在するカメラの辞書<カメラにつけたタグ, カメラの共有ポインタ>
    // 指定されたタグのカメラへの共有ポインタを得る カメラ辞書に存在しないタグでアクセスしたときにunordered_mapの特性で勝手に意図しないカメラができるのを防ぐ関数
    std::shared_ptr<Camera> camera(const std::string& cameraTag) { auto itr = cameras.find(cameraTag); return (itr != end(cameras)) ? itr->second : nullptr; }
    // このワールドに存在するカメラすべてに対して drawFuncで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
    void Draw(std::function<void()> drawFunc) noexcept;
    // カメラにタグをつけてワールドに追加 カメラにもワールドへのポインタリンクを張る
    inline bool AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera);
    // 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
    inline bool EraseCamera(const std::string& cameraTag);
    // カメラを nowWorld から nextWorld に移動させる
    static bool MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld);

};

#endif


World.cppを変更して、シーンにプレイヤを追加するAddPlayer関数などを追加しましょう。

#include "World.h"

#include "Player.h"
#include "Camera.h"
#include "Screen.h"

#include "GameScene.h"

void World::AddPlayer(const std::string& playerTag, std::shared_ptr<Player> pPlayer)
{
    if (m_pScene != nullptr)
    {
        m_pScene->players.emplace(playerTag, pPlayer); // シーンにプレイヤを追加する
    }
}

// このワールドにいるプレイヤを得る
std::vector<std::weak_ptr<Player>> World::GetPlayers()
{
    std::vector<std::weak_ptr<Player>> players;
    for (auto&& pPlayer : players)
    {
        players.emplace_back(pPlayer);
    }
    return players;
}


// カメラにタグをつけてワールドに追加 カメラにもワールドへの生ポインタリンクを渡す
bool World::AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera)
{
    if (pCamera == nullptr) return false;
    if (cameras.count(cameraTag) > 0) return false; // すでに同じタグのカメラがワールドにあったら false
    if (pCamera->world() != nullptr) pCamera->world()->EraseCamera(cameraTag); // すでにカメラが別ワールドに配置されていたら別ワールドから削除
    cameras.emplace(cameraTag, pCamera); // ワールドにカメラを追加する
    pCamera->world(this); // カメラにもワールドへのポインタリンクを張る(カメラ側に渡すのは共有ポインタではない生ポインタだから共有デッドロックにはならない)
    return true;
}

// 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
bool World::EraseCamera(const std::string& cameraTag)
{
    auto itr = cameras.find(cameraTag); // 指定したタグのカメラを探す
    if (itr == end(cameras)) return false; // 指定したタグのカメラがワールドにない
    auto pCamera = itr->second;
    pCamera->world(nullptr); // カメラ側の worldへのリンクのポインタも nullptrに
    cameras.erase(cameraTag); // カメラを辞書から削除

    return true;
}

// カメラを nowWorld から nextWorld に移動させる
bool World::MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld)
{
    if (nowWorld == nullptr) return false;
    auto itr = nowWorld->cameras.find(cameraTag); // タグでカメラを探す
    if (itr == end(nowWorld->cameras)) return false; // タグに関連づいたカメラがなかった
    auto targetCamera = itr->second; // カメラの共有ポインタをキープ(次の行でeraseしてもカメラの共有カウンタは0にはならずこのリンクのおかげでカメラはメモリに残存)
    targetCamera->world(nullptr); // カメラ側の worldへのリンクのポインタを一旦 nullptrに
    nowWorld->cameras.erase(cameraTag); // nowWorld からは カメラを消す
    if (nextWorld == nullptr) return false;

    return nextWorld->AddCamera(cameraTag, targetCamera); // 次のワールドにカメラを追加する
}

// このワールドに存在するカメラすべてに対して funcで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
void World::Draw(std::function<void()> drawFunc) noexcept
{
    for (auto&& keyValue : cameras)    // このワールドにあるすべてのカメラぶんループ
    {
        VECTOR_D beforeCamPos; // 設定変更前のカメラのパラメータを一旦、保管
        double beforeVRot, beforeHRot, beforeTRot, beforeNear, beforeFar, beforeFov;
        Camera::GetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);

        auto& camera = keyValue.second;
        //camera->screenID
        DxLib::SetCameraPositionAndAngle(camera->pos, camera->rot.V, camera->rot.H, camera->rot.T); // 各カメラの位置、回転を設定

        Screen::Start(camera->screenHandle, true, camera->fovAngle); // カメラに関連付けられたスクリーンIDを描画先にする
        {
            drawFunc(); // 渡された描画処理を実行
        }
        Screen::End(true); // 描画先を元に戻す

        // カメラの設定をもとに戻しておく
        Camera::SetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);
    }
}



GameScene.cppを新規作成して、ゲームをUpdateしたりDrawしたりする処理を実装しましょう。

#include "GameScene.h"

#include "Player.h"

GameSceneSetting GameScene::settings{ "Map/GameSceneSetting.csv" }; // ゲームシーンの設定情報

// 指定したゲームシーンのタグに関連づいたファイル名に対応したワールドを初期化する
bool GameScene::LoadWorlds(const std::string& gameSceneTag)
{
    if (settings.info.count(gameSceneTag) == 0)
        return false;

    auto& info = settings[gameSceneTag];
    for (auto&& tag_mapPath : info)
    {
        CreateWorld(tag_mapPath.second.tag); // ワールドをタグをつけて生成
    }
    return true;
}

void GameScene::ChangeWorld(const std::string& worldTag, std::shared_ptr<Player> pPlayer)
{
    if (pPlayer == nullptr) return;
    if (worlds.count(worldTag) == 0) return; // 存在しないタグだった

    pPlayer->world(worlds[worldTag].get()); // ワールドへのリンクをすげ替える
}


void GameScene::Initialize()
{// Init処理

    // Zバッファを有効にする [Zの深度]
    //[参考]https://docs.google.com/presentation/d/1Z23t1yAS7uzPDVakgW_M02p20qt9DeTs4ku4IiMDLco/edit?usp=sharing
    //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
    DxLib::SetUseZBuffer3D(TRUE);
    // Zバッファへの書き込みを有効にする
    DxLib::SetWriteZBuffer3D(TRUE);
    // 光の明暗計算を無効に
    DxLib::SetUseLighting(FALSE);

    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);


    Resource::MakeShared<Texture>(mapChipPath, 8, 8, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath1, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath2, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする


    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    // プレイヤの生成
    if (pWorld != nullptr)
    {
        //pWorld->AddCamera("カメラ1", Camera::defaultCamera); // カメラ1というタグをデフォルトカメラにつけてワールドにデフォルトカメラを配置
        pWorld->AddCamera("カメラ1", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera1 = pWorld->camera("カメラ1"); // カメラ1 というタグのついたカメラへの共有ポインタを得る
        pCamera1->SetScreenHandle(screenHandle0); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer1 = std::make_shared<Player>(pWorld, Pad::Key, Vector3(90 + 256, 32, 95 + 256));
        pWorld->AddPlayer("プレイヤ1", pPlayer1); // シーンにプレイヤを追加する
        pPlayer1->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);

        pPlayer1->camera = pCamera1.get(); // カメラをプレイヤにリンクする


        pWorld->AddCamera("カメラ2", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera2 = pWorld->camera("カメラ2"); // プレイヤ2 というタグのついたカメラへの共有ポインタを得る
        pCamera2->SetScreenHandle(screenHandle1); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer2 = std::make_shared<Player>(pWorld, Pad::Two, Vector3(190, 32, 195));
        pWorld->AddPlayer("プレイヤ2", pPlayer2); // シーンにプレイヤを追加する
        pPlayer2->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);
       
        pPlayer2->camera = pCamera2.get(); // カメラをプレイヤにリンクする

    }

};


void GameScene::Update()
{// 更新処理

    Screen::ClearDrawScreen(screenHandle0); // 一旦キャンバスをきれいにまっさらに
    Screen::ClearDrawScreen(screenHandle1); // 一旦キャンバスをきれいにまっさらに

    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    for (const auto& player : players)
        player.second->Update(); // プレイヤの更新【忘れるとプレイヤが動かない】

}

void GameScene::Draw()
{// 描画処理
    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    for (const auto& player : players)
    {
        player.second->Draw(); // プレイヤの描画【忘れるとプレイヤ表示されない】

        // プレイヤのいるワールドのカメラのみを描けば、プレイヤのいないワールドは描かずに済む
        // 複数のカメラを 範囲 for文 で回す
        for (auto&& camera : player.second->world()->cameras)
        {   // ラムダ式[&](){ ~ }で{}の外側の変数すべてを & キャプチャで&参照として「Draw関数の内側へ引き連れて」処理できる
            camera.second->Draw([&]()
                {
                    // Draw関数() の 外側にある変数 mapChipPath も[&]効果で { } の内側で問題なくアクセスできている↓

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 0);
                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 70, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 8);

                    if (camera.first == "カメラ1")
                        DxLib::DrawString(0, 0, "こちらがプレイヤ1", GetColor(255, 0, 0));
                    else if (camera.first == "カメラ2")
                        DxLib::DrawString(0, 0, "こちらはプレイヤ2", GetColor(0, 255, 0));
                });
        }
    }

    // スクリーン0の内容を画面に描く
    DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
    DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));

    // スクリーン1の内容を画面に描く
    DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
    DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));

}

さて、このまま、main.cppでシーンを直接Update、Drawしていいものか?
なぜなら、タイトルシーンやゲームオーバー、ゲーム中のシーンで共通するような処理はあるでしょうから、
シーンとmain.cppの間にもう一つGameというクラスをはさんで、共通の処理はそちらにさせるようにしましょう。


Game.hを新規作成して、各シーンで共通する処理をUpdateしたりDrawしたりする処理を実装しましょう。

#ifndef GAME_H_
#define GAME_H_

#include "Screen.h"
#include "SceneManager.h"
#include "Scene.h"
#include "MyRandom.h"
#include "Input.h"

class Game
{
public:
    Game() {}; // 初期化コンストラクタ
    ~Game() {}; // 破棄処理デストラクタ

    //★【仲介者パターン】ゲーム内のもの同士はマネージャを通して一元化してやり取り
    SceneManager& sm = SceneManager::Instance(); //唯一のシーンマネージャへの参照(&)を得る

    void Init()
    {// Init処理

        // 各種Init中に、ユーザーにお待ちいただく間、不安を和らげるためのメッセージをScreenFlipで表示してからInit開始
        std::string loadingStr("ゲーム起動中...  必要なファイルをロードしています");
        int strSize = DxLib::GetFontSize() * loadingStr.size();
        DxLib::DrawString(Screen::Width / 2 - strSize / 2, Screen::Height / 2, loadingStr.c_str(), GetColor(255, 255, 255));
        DxLib::ScreenFlip();

        Input::Init(); // 入力機能の初期化
        MyRandom::Init(); // 乱数のシードを現時点の時刻に連動して初期化


        // ゲーム開始して最初のシーンに遷移
        sm.LoadScene("GameScene", "シーン1");
    };

    void Update() {// 更新処理

        Input::Update(); //【注意】これ忘れるとキー入力押してもキャラ動かない

        // シーンの更新処理
        sm.currentScene->Update();

        // NextScene関数で次のシーン移動予約があったらシーンを移動する
        sm.LoadScene(); // 引数無し呼び出しの場合、事前に NextScene関数で予約がない場合はスルーされる
    }

    void Draw()
    {// 描画処理

        // シーンの描画処理
        sm.currentScene->Draw();

    }
};

#endif


main.cppを変更して、Gameクラスを通じてシーンをInit、Update、Drawしましょう。

#include "DxLib.h"

#include "Game.h"

#include "Screen.h"
#include "Camera.h"
#include "MyHash.h"
#include "Resource.h"
#include "MyDraw.h"
#include "Player.h"
#include "World.h"


// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMainからプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズ Width×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズ Width×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("3Dタイルゲーム");//この行でエラーになったら【設定】マルチバイト文字セットが間違ってるかも
    //↑ここまでの設定は↓下のDXライブラリ初期化より先にやらないとDxLib_Init()中は画面がフルスクリーンになって終わってからウィンドウサイズが変更になり見苦しい

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    // 初期化処理
    Game game;
    game.Init(); // gameのInit準備


    DxLib::ScreenFlip();

    // タイルマップを読み込む 縦8マス、横8マス、縦32ピクセル、横32ピクセル
    std::string mapChipPath{ "Map/mapchip.png" };
    Resource::MakeShared<Texture>(mapChipPath,8,8,32,32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする

    std::string playerImgPath1{ "Image/pikkoron.png" };
    Resource::MakeShared<Texture>(playerImgPath1, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
   
    std::string playerImgPath2{ "Image/satan.png" };
    Resource::MakeShared<Texture>(playerImgPath2, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする

    
    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    int screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    int screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);

    // ワールドを生成
    std::shared_ptr<World> world1 = std::make_shared<World>(nullptr, "ワールド1");

    // カメラを2つ生成して、2つのカメラ視点を2つのスクリーンにそれぞれ関連付ける
    world1->cameras.emplace("カメラ0", std::make_shared<Camera>(world1.get(), Vector3{ 20,40,-50 })); // カメラ0
    world1->cameras.emplace("カメラ1", std::make_shared<Camera>(world1.get(), Vector3{ 80,30,-50 })); // カメラ1

    world1->cameras["カメラ0"]->screenHandle = screenHandle0; // カメラ0 と スクリーン0 を関連付ける
    world1->cameras["カメラ1"]->screenHandle = screenHandle1; // カメラ1 と スクリーン1 を関連付ける
   
    world1->players.emplace("プレイヤ0", std::make_shared<Player>(world1.get(), Pad::Key, Vector3{ 20,30,-50 }));
    world1->players.emplace("プレイヤ1", std::make_shared<Player>(world1.get(), Pad::Two, Vector3{ 80,30,0 }));

    world1->players["プレイヤ0"]->SetCamera(world1->cameras["カメラ0"].get()); // .get()で共有ポインタstd::shared_ptrから生の*ポインタへ変換
    world1->players["プレイヤ1"]->SetCamera(world1->cameras["カメラ1"].get());

    world1->players["プレイヤ0"]->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);
    world1->players["プレイヤ1"]->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);

    Input::Init(); // 入力状態を初期化

   

    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
       
        game.Update(); //ゲームの更新処理
       
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに
        game.Draw(); // パラパラ漫画の描画処理

       
        DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに

        Screen::ClearDrawScreen(screenHandle0); // 一旦キャンバスをきれいにまっさらに
        Screen::ClearDrawScreen(screenHandle1); // 一旦キャンバスをきれいにまっさらに

        Input::Update(); // 入力状態を更新

        // 光の明暗計算を無効に
        DxLib::SetUseLighting(FALSE);

        // Zバッファを有効にする [Zの深度]
        //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
        DxLib::SetUseZBuffer3D(TRUE);
        // Zバッファへの書き込みを有効にする
        DxLib::SetWriteZBuffer3D(TRUE);

        // world1のプレイヤを更新する
        for (auto& pair : world1->players)
        {
            auto pPlayer = pair.second;
            pPlayer->Update();
        }

        // world1のプレイヤを描画する
        for (auto& pair : world1->players)
        {
            auto pPlayer = pair.second;
            pPlayer->Draw();
        }

        // 複数のカメラを 範囲 for文 で回す
        for (auto&& camera : world1->cameras)
        {   // ラムダ式[&](){ ~ }で{}の外側の変数すべてを & キャプチャで&参照として「Draw関数の内側へ引き連れて」処理できる
            camera.second->Draw([&]()
                {
                    // Draw関数() の 外側にある変数 mapChipPath も[&]効果で { } の内側で問題なくアクセスできている↓

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 0);
                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 70, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 4);

                    if (camera.first == "カメラ0") //←"カメラ0"はビルド時に事前にハッシュ値が計算され、文字列ではない数値とおきかわっている
                        DxLib::DrawString(0, 0, "こちらがカメラ0", GetColor(255, 0, 0));
                    else if (camera.first == "カメラ1")
                        DxLib::DrawString(0, 0, "こちらはカメラ1", GetColor(0, 255, 0));
                });
        }

        // スクリーン0の内容を画面に描く
        DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
        DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));
        
        // スクリーン1の内容を画面に描く
        DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
        DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));


        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え
    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょう?無事、2つのプレイヤから見える世界が映し出され、操作できましたか?
Gameクラスに任せることで、main.cppはすっきり見通しがよくなりました。

MyTimerクラスでchronoから得た時刻でゲームの描画のFPSを管理する

さて、板ポリとはいえ3Dを描画するので、描画のフレームレート[単位 FPS:Frame Per Second]を管理したいところです。

MyTimer.hを新規作成して、chrono経由で現在時刻などを計測できる機能をシングルトンとして準備しましょう。

#ifndef MYTIMER_H_
#define MYTIMER_H_


#include <chrono> // 精度の高い時刻を得る(C++ならWindows,Macなど色んな環境で使える)
#include <thread> // 処理休止sleep_forするために必要
#include <string> // 文字列string型に必要
#include <sstream> // ostringstreamに必要 標準出力(ostringstreamで文字列に標準出力を)
#include <iomanip> // 時間の文字列出力put_time()に必要

#include "Singleton.h"

// マルチプラットフォーム対応のchrono使用の時刻取り扱いクラス(C++11対応なら移植容易)
class MyTimer : public Singleton<MyTimer>
{
public:
    friend class Singleton<MyTimer>; // Singleton でのインスタンス作成は許可

    // 最初にsystem_clockとsteady_clockの両方で現在時間を取得
    // ★system_clockは【WindowなどOSの時刻(ユーザーが変更したら巻き戻しもありうる)】
    // ★steady_clockは【↑OSに依存しない時刻(不可逆なので時間計測に向いている)】
    std::chrono::system_clock::time_point systemInitTime = std::chrono::system_clock::now();
    std::chrono::steady_clock::time_point steadyInitTime = std::chrono::steady_clock::now();
    std::chrono::steady_clock::time_point steadyNowTime = std::chrono::steady_clock::now();

    std::chrono::system_clock::time_point systemTimeFromStart = UpdateSystemTimeFromStart();

    // 時刻を保管する文字列【注意】UpdateSystemTimeFromStart()呼ばないうちは最新に更新されない
    std::string timeStringSystemClock = GetTimeString("%Y年%m月%d日%H時%M分%S秒");
    float timeScale = 1.0f;//[実験機能]main.cppで画面を描く速度と連動させてスローモーションや早送りも
    bool isTimeScaleMode = false; //[実験機能]スローモーション、早送りモードをON OFF


    // 起動時間の記録の初期化/リセット(Initし直せば再取得もできる)
    inline void Init()
    {
        // 最初にsystem_clockとsteady_clockの両方で現在時間を取得(再取得も)
        systemInitTime = std::chrono::system_clock::now();
        steadyInitTime = std::chrono::steady_clock::now();
    }

    // 現時刻をOSに左右されず求める(計算結果はsystemTimeFromStartに保管)
    inline std::chrono::system_clock::time_point UpdateSystemTimeFromStart()
    {
        steadyNowTime = std::chrono::steady_clock::now(); //現時刻を得る(OSの時刻じゃない方)
        // 開始時のsteady_clock::nowとの差をとって、開始時のsystem_clock::nowに足す
        // [参考] https://techblog.kayac.com/steady_clock
        systemTimeFromStart =
            systemInitTime + std::chrono::duration_cast<std::chrono::seconds>(steadyNowTime - steadyInitTime);
        return systemTimeFromStart;
    }

    // 現時刻を特定の文字列フォーマットで得る(得たものはsteadyNowTimeとtimeStringSystemClockに保管)
    // 出力は"%c"ならフォーマットは[Mon Nov 27 10:58:32 2017]
    // "%Y年%m月%d日%H時%M分%S秒"なら[2020年12月11日14時30分15秒](デフォルト)
    // [フォーマット例] https://cpprefjp.github.io/reference/chrono/local_time.html
    inline std::string& GetTimeString(const char* timeStringFormat = "%Y年%m月%d日%H時%M分%S秒")
    {
        systemTimeFromStart = UpdateSystemTimeFromStart();//現時刻をOS時刻に左右されず得る

        // 時刻タイプ型をsystem_clockのtime_point型からtime_t型に変換する。
        std::time_t timeFromStart = std::chrono::system_clock::to_time_t(systemTimeFromStart);
        // ローカルタイムを得る(ローカルタイムについては以下リンク参照)
        // https://programming.pc-note.net/c/time.html
        struct tm localTimeFromStart;
        localtime_s(&localTimeFromStart, &timeFromStart);
        //【ostringstream】は文字列ストリームに<<演算子などの方法でデータを流し込んでいく
        // https://programming-place.net/ppp/contents/cpp/library/029.html#write_to_string_stream
        std::ostringstream oss;
        //↓この一行で文字列ストリームに流し込み
        oss << std::put_time(&localTimeFromStart, timeStringFormat) << std::endl;

        timeStringSystemClock = oss.std::ostringstream::str(); //str()で文字列string化して保管
        return timeStringSystemClock; //現時刻を文字列として返す
    }


    // Initしてから現在までの経過時間を得る(単位Tick = 100ナノ秒 = 1/10000000秒)
    inline long GetElapsedTicks()
    {    // 時刻を更新
        UpdateSystemTimeFromStart();
        // Initしてから現在までの経過時間(ナノ秒)
        auto durationFromInit = std::chrono::duration_cast<std::chrono::nanoseconds>(steadyNowTime - steadyInitTime);
        // Tick数。1Tick = 100ナノ秒 = 1/10000000秒
        return (long)(durationFromInit.count() / 100);
    }

    // 指定ナノ秒 プログラムを休止
    inline void SleepForNanoSec(int waittime)
    {    // 指定ナノ秒 プログラムを休止
        std::this_thread::sleep_for(std::chrono::nanoseconds(waittime));
    }

    // 指定マイクロ秒 プログラムを休止
    inline void SleepForMicroSec(int waittime)
    {    // 指定ミリ秒 プログラムを休止
        std::this_thread::sleep_for(std::chrono::microseconds(waittime));
    }

    // 指定ミリ秒 プログラムを休止
    inline void SleepForMilliSec(int waittime)
    {    // 指定ミリ秒 プログラムを休止
        std::this_thread::sleep_for(std::chrono::milliseconds(waittime));
    }

    // 指定 秒 プログラムを休止
    inline void SleepForSec(int waittime)
    {    // 指定 秒 プログラムを休止
        std::this_thread::sleep_for(std::chrono::seconds(waittime));
    }

    // 指定 分 プログラムを休止
    inline void SleepForMinutes(int waittime)
    {    // 指定 分 プログラムを休止
        std::this_thread::sleep_for(std::chrono::minutes(waittime));
    }

protected:
    // 外部からのインスタンス作成は禁止
    MyTimer() {};
    //外部からのインスタンス破棄も禁止
    virtual ~MyTimer() {};
};

#endif



main.cppを変更して、MyTimerクラスでゲームの毎フレーム描画処理(GameDraw)を「スキップやウェイト待ち処理」などしてフレームレートを安定させましょう。

#include "DxLib.h"

#include "Game.h"
#include "MyTimer.h"
#include "Screen.h"

//[g_グローバル変数 と s_スタティックグローバル変数の違い] https://zenn.dev/melos/articles/1ddb67d024220f
// グローバル変数は「他のファイルからもアクセス可能な変数」になる
// staticグローバル変数は「他のファイルからはアクセスできないこのファイル限定呼び出し可能変数」になる

// FPS 60フレームを目標にして描画に時間がかかるときに描画をスキップして1秒あたりのフレームレートを安定させる

int g_targetFPS{ 60 }; // 目標のFPS(Frame Per Second, 1秒あたりのフレーム数)
static bool s_isEnableFrameSkip{ true }; // 高負荷時にフレームスキップするか(falseの場合は処理落ち(スロー))
double g_maxAllowSkipTime{ 0.2 }; // フレームスキップする最大間隔(秒)これ以上の間隔が空いた場合はスキップせずに処理落ちにする。
long g_intervalTicks = (long)(1.0 / g_targetFPS * 10000000); // フレーム間のTick数 1Tick = 100ナノ秒 = 1/10000000秒
int g_maxAllowSkipCount = (int)(g_targetFPS * g_maxAllowSkipTime); // フレームスキップを許す最大数

static long s_nextFrameTicks = g_intervalTicks; // 次のフレームの目標時刻
static long s_fpsTicks{ 0 }; // FPS計測のためのTicks
static int s_skipCount{ 0 }; // 何回連続でフレームスキップしたか
static int s_fpsFrameCount{ 0 }; // FPS計測のためのフレームカウント 60回数えるごとに、要した時間からFPSを算出する


static float s_currentFPS; // 現在のFPS (Frame per Second)

// ゲームを描く処理
static inline void GameDraw(Game& game)
{//↑staticグローバル関数は「他のファイルからはアクセスできないこのファイル限定呼び出し可能関数」になる
    DxLib::ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに
    game.Draw(); // パラパラ漫画の描画処理
#ifdef DEBUG_DRAW_FPS
    DxLib::DrawString(Screen::Width - 130, 30, (std::string("FPS:") + std::to_string(s_currentFPS)).c_str(), GetColor(255, 255, 255));
#endif
    DxLib::ScreenFlip(); // 裏で描いておいたパラパラ漫画を表面に入れ替えフリップ
    s_skipCount = 0; // フレームスキップのカウントをリセット
}



// 設定画面の【リンカー】→【システム】の【サブシステム】をWindows(SUBSYSTEM:WINDOWS)に設定するとWinMainからプログラムが開始する
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // [Windowsの機能]このプログラム実行の処理優先度を上げたいときに呼ぶ
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
   
    // ★垂直同期とはモニターへの描画が完了するまで処理全体を停止し、
    // 描画処理と更新処理の間隔を双方一定となるように処理の間隔を調整すること
    // https://qiita.com/Seiten_Minagawa/items/615312ebe5e688ffee7f
    // 画面リフレッシュレートと目標フレームレートが等しい場合は垂直同期を有効に、等しくない場合は垂直同期を無効にする
    DxLib::SetWaitVSyncFlag( (DxLib::GetRefreshRate() == g_targetFPS) ? TRUE : FALSE ); //垂直同期を切るか?
    //DxLib::SetWaitVSyncFlag(0); // 垂直同期を強制的に切りたい場合はコメントをはずす https://www.ay3s-room.com/entry/dxlib-framerate-controll

   
    // 画面モードの設定
    DxLib::SetGraphMode(Screen::Width, Screen::Height, 32); // 画面サイズ Width×Heightのカラービット数32ビットで起動
    DxLib::SetWindowSize(Screen::Width, Screen::Height);// ウィンドウサイズ Width×Height(こことSetGraphModeのサイズが異なると画像がゆがむ)
    DxLib::ChangeWindowMode(TRUE);//フルスクリーン表示かウィンドウ表示か
    DxLib::SetMouseDispFlag(TRUE);// ここをFALSEにするとマウスカーソル非表示
    DxLib::SetMainWindowText("3Dタイルゲーム");//この行でエラーになったら【設定】マルチバイト文字セットが間違ってるかも
    DxLib::SetAlwaysRunFlag(TRUE); // ウィンドウが非アクティブでも動作させるか?
    //↑ここまでの設定は↓下のDXライブラリ初期化より先にやらないとDxLib_Init()中は画面がフルスクリーンになって終わってからウィンドウサイズが変更になり見苦しい

    // DXライブラリの初期化
    if (DxLib_Init() < 0)
    {
        // DXの初期化にエラーが発生したらプログラム自体を終了(returnで)
        return -1;
    }

    // 独自時刻クラスの参照を得る(シングルトン型)
    MyTimer& timer = MyTimer::Instance();
    timer.Init(); // 時刻計測開始


    // 初期化処理
    Game game;
    game.Init(); // gameのInit準備
#ifdef _DEBUG // デバッグ用にgameのinit終了までの秒数を一瞬画面に出す(初期化ロード時間計測のため)
    long initTime = timer.GetElapsedTicks(); // デバッグ用にInitの経過時間を取得
    DxLib::DrawString(0, 0, std::to_string(initTime / 10000000.0).c_str(), GetColor(255, 255, 255));
#endif


    DxLib::ScreenFlip();
   
    timer.Init(); // 時刻計測開始時間リセット(game.Initにかかった時間は計測時間に含めないため)
   
    // アニメーション(パラパラ漫画)するにはWhile文
    while (DxLib::ProcessMessage() == 0)
    {// ProcessMessage() == 0になるのは×ボタン押したときなど
#ifdef DEBUG_DRAW_FPS // ←#defineされていたら FPSを画面右上に描画する
        // FPSの計測(FPS = 1秒あたりのパラパラ漫画枚数)
        ++s_fpsFrameCount; // フレーム数のカウントを +1
        if (s_fpsFrameCount >= 60) // 60フレームを超えたら
        {   // 経過時間 1Tick = 100ナノ秒 = 1/10000000秒 (elasped=経過)
            long elapsedTicks = timer.GetElapsedTicks() - s_fpsTicks;
            float elapsedSec = (float)elapsedTicks / 10000000; // 単位を秒(s:Second)に変換
            s_currentFPS = s_fpsFrameCount / elapsedSec;// 現在のFPS

            s_fpsFrameCount = 0; // 60枚数えたら0リセット
            // 60枚数えたら現在のFPS(パラパラ速度)を記録
            s_fpsTicks = timer.GetElapsedTicks();
        }
#endif

        game.Update(); //ゲームの更新処理

        game.Draw(); // パラパラ漫画の描画処理
       
        DxLib::ScreenFlip(); //隠れて裏側で描いておいた画像を表面に入れ替え

       
        //[実験機能]スローモーション、早送り(time.timeScaleが 2で2倍速 0.5で1/2スロー)
        if (timer.isTimeScaleMode && timer.timeScale > 0)
        {   // フレームレートを再計算し早送りやスローモーション
            g_targetFPS = (int)(60 * timer.timeScale); // 目標のFPS(Frame Per Second, 1秒あたりのフレーム数)
            g_maxAllowSkipTime = 0.2; // フレームスキップする最大間隔(秒)。これ以上の間隔が空いた場合はスキップせずに処理落ちにする。
            g_intervalTicks = (long)(1.0 / g_targetFPS * 10000000); // フレーム間のTick数。1Tick = 100ナノ秒 = 1/10000000秒
            g_maxAllowSkipCount = (int)(g_targetFPS * g_maxAllowSkipTime);
        }

        // ★垂直同期がDX設定で効いているならDXライブラリにWait処理を任せられる(フレームスキップだけ自分でやる)
        if (DxLib::GetWaitVSyncFlag() == TRUE && timer.isTimeScaleMode == false)
        {   // ★フレームスキップとは描画処理が重い時にパラパラ漫画を1枚飛ばすか?
            if (s_isEnableFrameSkip)
            {   // ★余った時刻 = 次のパラパラ予定時刻 - 現在の時刻
                long waitTicks = s_nextFrameTicks - timer.GetElapsedTicks();
                if (waitTicks < 0) // 目標時刻をオーバーしている
                {   //★あまりにも漫画のパラパラページ数を飛ばすとキャラがワープして見えるので限度をつけてる
                    if (s_skipCount < g_maxAllowSkipCount) // 連続フレームスキップ数が最大スキップ数を超えていなければ
                    {
                        ++s_skipCount; // フレームスキップした数を +1(描画処理を飛ばした数)
                    }
                    else
                    {   // 最大スキップ数を超えてるので、フレームスキップしないで描画
                        s_nextFrameTicks = timer.GetElapsedTicks();
                        GameDraw(game); // ★限度越えでスロー描画処理に切替え
                    }
                }
                else
                {   // 目標時刻OKなのでフレームスキップしないで描く
                    // ★DXライブラリが自動で液晶の60Hzとタイミング合わせて描画
                    GameDraw(game); // 描画処理
                }
                // 次のパラパラ予定時刻はIntervalTicks(単位:100ナノ秒)あと
                s_nextFrameTicks += g_intervalTicks;
            }
            else
            {   // ここのelseは何があってもフレームスキップしない設定の時に来る
                GameDraw(game); // ★スロー描画処理
            }
        }
        else
        {   // ★垂直同期がDX設定で【効いてない】ならWait処理を自力でやる(フレームスキップも自分でやる)
            // ↓こちらはフレームスキップ処理(waitTicks = 次のパラパラ予定時刻 - 現在の時刻)
            long waitTicks = s_nextFrameTicks - timer.GetElapsedTicks(); // 余った時間(待機が必要な時間)
            if (s_isEnableFrameSkip && waitTicks < 0)
            {   // あまりにも漫画のパラパラページ数を飛ばしすぎるとキャラがワープして見えるので限度をつけてる
                if (s_skipCount < g_maxAllowSkipCount)
                {
                    ++s_skipCount; // フレームスキップ(描画処理を飛ばす)
                }
                else // スキップ数の許容限度をオーバーしたら、FPSを落としてでもスローで無理してでも描く
                {   // 次のパラパラ予定時刻を現時刻に設定するので次も絶対遅れてwaitTicks < 0になる
                    s_nextFrameTicks = timer.GetElapsedTicks();
                    GameDraw(game); //遅れつつもスローモーションで描いてゆく設定の時はここ(当然Updateも遅れていく)
                }
            }
            else
            {   // ★自力でWait処理
                if (waitTicks > 20000) // あと2ミリ秒以上待つ必要がある
                {   // ★自作TimeクラスのSleepで一定時間プログラム休止
                    // https://marycore.jp/prog/objective-c/sleep-process/#sleep_for
                    // Sleepは指定した時間でピッタリ戻ってくるわけではないので、
                    // 余裕を持って、「待たなければならない時間-2ミリ秒」Sleepする
                    int waitMillsec = (int)(waitTicks / 10000) - 2;
                    timer.SleepForMilliSec(waitMillsec);
                }

                // 時間が来るまで何もしないループを回して待機する
                while (timer.GetElapsedTicks() < s_nextFrameTicks)
                {   // 所定の時間になるまで空ループ
                }

                GameDraw(game); // 所定の時間になったら描画処理
            }
            // 次のパラパラ予定時刻はIntervalTicks(単位:100ナノ秒)あと
            s_nextFrameTicks += g_intervalTicks;
        }

    }

    // キー入力待ちをする
    DxLib::WaitKey();
    // DXライブラリの後始末
    DxLib::DxLib_End();
    // ソフトの終了
    return 0;
}


いかがでしょう?フレームレートを安定させる処理をいれました。
といわれても実感があまり湧きませんね。
では、画面にFPSを表示させてみましょう。

main.cppを変更して、#define DEBUG_DRAW_FPSで FPSを画面右上に表示させるマクロを発動させましょう。

#include "DxLib.h"

#include "Game.h"
#include "MyTimer.h"
#include "Screen.h"


#define DEBUG_DRAW_FPS // ここをコメントアウトすると画面にFPSを文字で表示しない


//[g_グローバル変数 と s_スタティックグローバル変数の違い] https://zenn.dev/melos/articles/1ddb67d024220f
// グローバル変数は「他のファイルからもアクセス可能な変数」になる
// staticグローバル変数は「他のファイルからはアクセスできないこのファイル限定呼び出し可能変数」になる

// FPS 60フレームを目標にして描画に時間がかかるときに描画をスキップして1秒あたりのフレームレートを安定させる

int g_targetFPS{ 60 }; // 目標のFPS(Frame Per Second, 1秒あたりのフレーム数)
static bool s_isEnableFrameSkip{ true }; // 高負荷時にフレームスキップするか(falseの場合は処理落ち(スロー))
double g_maxAllowSkipTime{ 0.2 }; // フレームスキップする最大間隔(秒)これ以上の間隔が空いた場合はスキップせずに処理落ちにする。
long g_intervalTicks = (long)(1.0 / g_targetFPS * 10000000); // フレーム間のTick数 1Tick = 100ナノ秒 = 1/10000000秒
int g_maxAllowSkipCount = (int)(g_targetFPS * g_maxAllowSkipTime); // フレームスキップを許す最大数

(中略)..........................

// ゲームを描く処理
static inline void GameDraw(Game& game)
{//↑staticグローバル関数は「他のファイルからはアクセスできないこのファイル限定呼び出し可能関数」になる
    ClearDrawScreen();// 一旦キャンバスをきれいにまっさらに
    game.Draw(); // パラパラ漫画の描画処理
#ifdef DEBUG_DRAW_FPS // game.Drawを描いたあとに上のレイヤにぬりつぶす形でFPSを文字で画面右上に表示
    DxLib::DrawString(Screen::Width - 130, 30, (std::string("FPS:") + std::to_string(s_currentFPS)).c_str(), GetColor(255, 255, 255));
#endif
    ScreenFlip(); // 裏で描いておいたパラパラ漫画を表面に入れ替えフリップ
    s_skipCount = 0; // フレームスキップのカウントをリセット
}


(以下略).....


いかがでしょう?game.Draw()の「あとにFPSを描画」しているので、
ゲーム本体の描画のレイヤの上を、必ず塗りつぶして画面右上に現在のFPSを表示させることができます。
今は快適なFPSで描画できていますが、今後、ゲームの描画を複雑化させたときにFPSの状況をモニタリングする必要もでてくるかもしれません。
留意しなければいけないのが、Drawはスキップしていますが、Updateはスキップしていないということです。
今後、Update文にAIの経路の探索など重い処理を入れた場合はFPSは当然落ちます。
そのため、Unityなどでは Update文(毎フレーム) と FixedUpdate文(固定フレームレート毎) を分けているわけですね。
とりあえずこのゲームでは初心者にもとっつきやすい「固定フレームレート」前提の仕組みで制作していきます。
つまり、このゲームにおける Update文 = FixedUpdate文 の意味に読み替えて理解しておいたほうがいいです。


共通のベース基底となるGameObjectを定義して、プレイヤなどはそれを継承する形を設計する

前提としてクラスQuaternionを別記事から作成しておきますクオータニオンで安定して回転できるようにする


さて、次はゲームエンジンの基礎となる共通のベース基底となるGameObjectクラスを設計していきましょう。
UnityではGameObjectという名前ですし、Unreal EngineではActor(アクター)と呼ばれる概念です。
名前は好みの問題ですのでGameObjectという名前にしましょう。
さて、GameObjectの使われ方を考えておきましょう。
重大なのは、シーンの配下にあり「シーンと道ずれに消えてリセットされるべき」という要件です。
これは、最初から共有ポインタとしてstd::make_sharedされる前提で設計すべき、ということです。
そして、共有ポインタ前提で設計する際に厄介なのは GameObjectクラス内部の関数で this を使いたいときです。
this も「自分自身を指すポインタ」である以上、そのthisポインタが共有されたときにも、カウント +1 しなければ、つじつまが崩れてしまいます
ただの this では共有カウンタは +1 されないので、代わりに shared_from_this を使って「共有カウントを +1 したthis」を使う必要があります。
shared_from_this を使う資格は「std::enable_shared_from_this を継承したクラスに与えられる」のでGameObjectはstd::enable_shared_from_this を継承したクラスでないといけません
そして、さらなる「重大なる厄介さ」が待ち構えています。

「shared_from_thisは初期化コンストラクタで呼んではならない」という制約です。

この「厄介な制約」により、事実上「shared_from_thisを使いたいような処理は初期化コンストラクタではできない」ことを回避せねばならなくなってしまいました。
ゆえに、代わりにInit()関数とか、UnityでいうStart()やAwake()関数のようなものを初期化の代替としてコンストラクタの後追いで一緒に呼ぶ、という回避策が必要になります。
逆にいうと、GameObjectを継承するクラスは「使う側が自由にnew GameObject(~)やstd::make_sharedできてはいけない」設計が必要となってきます。
まあ、どのみちUnityでもシーンにGameObjectを生成するときにはInstatiate()関数を経由して生成していますよね。
直接生成を封じるためには「クラスの初期化コンストラクタをprotected:にして外部から直接コンストラクタ経由の初期化生成するのを封じる」対策が必要です。
上記までの事情を分かっているプログラマなら初期化コンストラクタとInit()関数をセットで呼んで初期生成するでしょうが、
事情を知らないプログラマは普通に初期化コンストラクタ経由で初期生成をしようとするでしょう。それをprotected:で邪魔するのです。
代わりにコンストラクタのコメントに// Create関数経由で初期生成してください とただし書きをしておけば「空気読んでくれ」は伝わります。
関数の名前はMake()関数でもいいし、Create()関数でもいいですが、わりとwebに出てくる動画などではcreate()関数にしていることが多い感はありますね。
なのでcreate()関数などが共有ポインタを返すような文脈で出てきているwebの動画などは「暗に上記事情の設計」をしている雰囲気がかもし出されている、と感じるべきです。

さてと、上記事情をGameObjectが 直接std::enable_shared_from_thisを継承する 形でも設計はできるのですが、
今後「GameObjectじゃないものも上記事情をかかえる場合もありそう」だと予測できますよね。
ですので、さらに「ベース基底となるObjectクラス(std::enable_shared_from_thisを継承)」を間に入れるほうがよさそうです。
GameObjectは上記のObjectクラスを継承して「間接的にstd::enable_shared_from_thisを孫として継承」する形にします。
ついでにObjectクラスにはUnityにもあるように
「通し番号インスタンスIDを初期化と同時にカウントアップして保持して == で同じインスタンスIDか判定」
「タグを振ってFindWithTagで辞書からタグでオブジェクトを検索できる機能」
なども組み入れてしまいましょう。

Object.hを新規作成して、継承するとshared_from_this_asでき、
Create関数経由で初期化とInitをセットでできて、インスタンスIDやタグを振って検索できるベース基底クラスを準備しましょう。

#ifndef OBJECT_H_
#define OBJECT_H_

#include <memory> // 共有ポインタを使う
#include <unordered_map>
#include "MyHash.h" // hash32でタグ→オブジェクトを辞書検索できるようにする


// Objectクラスのコンストラクタでは shared_from_this を使えないから
// Initをoverride継承して Initの中で shared_from_thisを必要とする初期化する際に渡すパラメタ
// このベース基底クラスを継承して定義する
struct InitParams
{
    InitParams() = default;

    // このクラスを継承したクラスで、初期化に必要なパラメタを渡す(初期化に必要なパラメタはそれぞれ違うので)

    virtual ~InitParams() {}
};


// GameObjectなどをこの Object型を継承して新規生成することで シリアルIDを自動で振り、shared_from_this_as<Player>()などで 共有状態のthisを得られるようにする
class Object : public std::enable_shared_from_this<Object>
{    // [std::shared_ptrでthisポインタをつかうためには]↑ https://www.kabuku.co.jp/developers/cpp_enable_shared_from_this

    // 通し番号(シリアルID)をstaticで管理してインスタンスが生成されるたびにシリアル番号を++して返す
    static unsigned int newID() { static unsigned int _serialID_ = 0; return _serialID_++; }
protected: // protected:にしてクラス外からの直接のアクセスを阻む
    const unsigned int m_InstanceID; // オブジェクトのインスタンスID (0 ~ 約42億までの番号)
    hash32 m_Tag{ "" }; // オブジェクトに振ったタグ デフォルトの空文字""の場合は検索可能な辞書に登録しない

    // コンストラクタはprotected:にしてあるので Create関数経由で初期化してください
    Object() : m_InstanceID{ Object::newID() } {  };
public:
    unsigned int InstanceID() { return m_InstanceID; };
    inline hash32 Tag() const { return m_Tag; }

    // 仮想デストラクタ (辞書にタグが登録されていたら、そのタグを辞書からも消しておく)
    virtual ~Object() { if (m_Tag != "") umTagObjects().erase(m_Tag); };

    //[protectedなコンストラクタでもstd::make_sharedで呼ぶテク] https://gintenlabo.hatenablog.com/entry/20131211/1386771626
    template<typename ObjectT>
    struct ProtectedCall : public ObjectT
    {   // ダミーとなる ProtectedCallクラスが public:だからそれを経由して間接的にコンストラクタがprotected:なクラスでもmake_sharedを呼ぶテク
        template<class... ArgsT> // std::make_sharedはprotectedな継承クラスは呼べないのでそれを回避するテクニック
        explicit ProtectedCall(ArgsT&&... args)
            : ObjectT(std::forward<ArgsT>(args)...) { } //std::forwardで複数引数を完全転送で引き渡し https://proc-cpuinfo.fixstars.com/2016/03/c-html/
    };


    // コンストラクタでは shared_from_this を使えないから、Initをoverride継承して Initの中で shared_from_thisを必要とする初期化をする
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr)
    {
        // overrideして shared_from_this_as が必要な パラメータの初期化を行ってください
    }

    // Objectを継承した型はこの create関数を経由して新規生成する
    template <typename DerivedT, typename OutTypeT = DerivedT, typename ...ArgsT>
    static auto Create(std::shared_ptr<InitParams> initParams, ArgsT&&...args)
    {
        auto pObject = std::static_pointer_cast<OutTypeT>(std::make_shared<ProtectedCall<DerivedT>>(std::forward<ArgsT>(args)...));
        pObject->Init(initParams); // コンストラクタでは shared_from_this_as できないものをmake_sharedが終わったタイミングで一緒に初期化
        return pObject;
    }

    // shared_from_this() を DerivedT型にキャストして返す [参考] https://qiita.com/terukazu/items/e4129105b12442e42651
    template <typename DerivedT>
    auto shared_from_this_as() noexcept {
        return std::dynamic_pointer_cast<DerivedT>(Object::shared_from_this());
    }

protected:
    // <振ったタグ, オブジェクトへの弱共有リンク> の辞書を関数内staticで保持して検索できるようにしてある
    static inline std::unordered_map<hash32, std::weak_ptr<Object>>& umTagObjects()
    {   // 関数内staticで タグから オブジェクトへの弱共有ポインタを保持
        static std::unordered_map<hash32, std::weak_ptr<Object>> _umTagObjects_;
        return _umTagObjects_;
    }
public:
    // オブジェクトにタグを振って検索できるようにしておく (空文字 "" のタグが指定されたら辞書から消す)
    template <typename DerivedT>
    inline auto SetTag(const std::string& tag)
    {
        if (m_Tag != "" && tag == "") // 空文字 "" のタグが指定されたら辞書から消す
        {
            umTagObjects().erase(m_Tag); // 既存のタグが辞書に登録されてたら辞書から消す
            m_Tag = ""; // タグを空文字へリセット
            return shared_from_this_as<DerivedT>(); // メソッドチェーンで->で連鎖アクセスできるように thisを返す
        }
        m_Tag = tag; // タグを設定
        umTagObjects().emplace(m_Tag, shared_from_this_as<DerivedT>()); // タグと弱共有リンクをペアで辞書に登録
        return shared_from_this_as<DerivedT>(); // メソッドチェーンで->で連鎖アクセスできるように thisを返す
    }
   
    // オブジェクトに振っておいたタグで検索する
    template <typename DerivedT>
    static std::weak_ptr<DerivedT> FindWithTag(const std::string& tag)
    {
        auto found = umTagObjects().find(tag); // 辞書からtagと一致するものを見つける
        if (found == end(umTagObjects())) // 結果がendのときは見つからなかった
            return std::weak_ptr<DerivedT>(); // 空の弱共有ポインタを返す(空の弱共有ポインタはlock()してもnullptrになる)
        if (found->second.lock() == nullptr)
        {   // 辞書には found が見つかったが lock()したら nullptr のときはすでにリンク先本体が消されているから辞書からもeraseで消す
            umTagObjects().erase(tag);
            return std::weak_ptr<DerivedT>(); // 空の弱ポインタを返す(lock()してもnullptrになる)
        }
        return std::dynamic_pointer_cast<DerivedT>(found); // DerivedT型(継承先の型タイプ)に変換してreturn する
    }

    // 比較演算子 == ではインスタンスIDを比較する
    inline bool operator == (const Object& other) const { return this->m_InstanceID == other.m_InstanceID; }
    // 不一致演算子 != ではインスタンスIDを比較する
    inline bool operator != (const Object& other) const { return !(*this == other); }
};


#endif


template <typename DerivedT>などテンプレートでかなり読みづらいとは思いますが、
ようはCreate関数の中でstd::make_sharedで初期化して、それとセットでInit関数も呼び出しているということです。
初期化コンストラクタでは例えば、World* worldやVector3 positionなど色々初期化で渡したい引数があるしクラスごとに引数の数も増えるとおもいますが、
Create関数のテンプレートの typename ...ArgsT←これのおかげで魔法のように引数がワールドやポジション以外に何個増えても
Create( world, position, ~その他いろんな引数が増えても...) のように呼び出しかたがテンプレで拡張可能になっています。

あとはこのObjectクラスを継承すれば、GameObjectはshared_from_this_as<Player>()などで 「共有カウンタを+1できるthis」 を使える準備が整います。

GameObject.hを新規作成して、PlayerやZakoなどの基底ベースとなるクラスを準備しましょう。

#ifndef GAMEOBJECT_H_
#define GAMEOBJECT_H_

#include <memory> // 共有ポインタの定義のため

#include "Vector3.h"
#include "Quaternion.h"

#include "Object.h" // インスタンスIDや検索タグを持ち、shared_from_this_as<継承先クラス名>などができる

class World; // 前方宣言

// ゲーム上に表示される物体の基底クラス。
// プレイヤーや敵、アイテムなどはこのクラスを継承して作る。
class GameObject : public Object
{
protected:
    // コンストラクタはprotected:にしてあるのでCreate関数経由で初期生成してください
    GameObject(World* world = nullptr, Vector3 position = { 0,0,0 }, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : Object(), m_world{ world }, position{ position }, rotation{ rotation }, velocity{ velocity }, force{ force }
    {

    }

public:
    // 仮想デストラクタ
    virtual ~GameObject() {}


    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    }; // unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    Quaternion rotation; // 回転クオータニオン

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

protected:
    World* m_world; // GameObjectが配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }
   
    bool isDead{ false }; // 死んだ(削除対象)フラグ
    bool isStatic{ false }; // 動かない(静的オブジェクトか?)
    bool isGround{ false }; // 地面についているか
    bool isFlying{ false }; // 空をとんでいるか

    hash32 typeTag{ "" }; // 小カテゴリ Zako0など個別のタイプ
    hash32 baseTag{ "" }; // 大カテゴリ Enemyなど大まかなベースジャンル


    // 更新処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Update() = 0; // 純粋仮想関数 = 0 にすると【絶対 Update()関数はoverrideしなきゃいけない義務を付与できる 】

    // 描画処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Draw() = 0; // 純粋仮想関数 = 0 にすると【絶対 Draw()関数はoverrideしなきゃいけない義務を継承先クラスに付与 】

};

#endif


とりあえずは位置positionなどは「親子構造をもたない形」で定義しました。
親子構造を持つtransformなどを考え出すと、xを一つ変えるだけでもそのxの変化を子供に伝搬させる必要が出てきたりして、
x = x + 1; したあとにいちいち UpdateChildX() などしないと子供に伝搬されず、忘れると「なぜだー状態」になり、
非常に頭が混乱して、初心者にはとっつきにくいプログラミングになってしまうので、シンプル化して他に注力するため今回は導入を見送ります。

Player.hを変更して、GameObjectを継承しましょう。

#ifndef PLAYER_H_
#define PLAYER_H_

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "GameObject.h"
#include "MyHash.h" // hash32型でプレイヤが現在いるワールドのタグを管理する
#include "Vector3.h"


class World; // 前方宣言
class Camera; // 前方宣言

class Player : public GameObject
{
protected:
    // コンストラクタはprotected:にしてあるので、Create関数経由で初期生成してください
    Player(World* world, Pad pad, Vector3 position, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : pad{ pad }, GameObject(world, position, rotation, velocity, force) m_world{ world }, position{ position }, velocity{ 0,0,0 }, force{0,0,0}
    {
        this->typeTag = "Player"; // タグは個別の"プレイヤ1"などに使い、タイプはtypeTagで判別する形にしてタイプのジャンルを区別
    }

public:
    // 仮想デストラクタ
    virtual ~Player() {}

    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    };// unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

    hash32 tag{ "" }; // プレイヤにつけるタグ
protected:
    World* m_world; // 配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }


    Pad pad; // 操作するコントローラー番号
    hash32 worldTag{ "" }; // 現在いるワールドのタグ
    float MoveSpeedMax = 6; // 移動速度Max値
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // プレイヤの動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

    std::shared_ptr<Texture> image; // プレイヤの板ポリゴンで描くタイル分割画像への共有リンク

    Camera* camera{ nullptr }; // プレイヤを追随するカメラ
    void SetCamera(Camera* pCamera) { camera = pCamera; }

    // 入力を受けての処理
    virtual void HandleInput();

    // 更新処理
    virtual void Update() override;

    // 描画処理
    virtual void Draw() override;

};

#endif



Player.cppを変更して、GameObjectを継承する記述にしましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}


// 更新処理
void Player::Update()
{
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    // 実際に位置を動かす

    // まず横に移動する
    x += vx;

    // 次に縦に動かす
    y += vy;

    // 次にZ奥行き方向に動かす
    z += vz;

    vy += vyForce; // 勢い(力・フォースを加算


    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

            });

    }
}



GameScene.cppを変更して、ObjectのCreate関数を呼び出す形でPlayerを新規生成する記述にしましょう。

#include "GameScene.h"

#include "Player.h"

(中略).................


void GameScene::Initialize()
{// Init処理

    (中略).................

    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    // プレイヤの生成
    if (pWorld != nullptr)
    {
        //pWorld->AddCamera("カメラ1", Camera::defaultCamera); // プレイヤ1というタグをカメラにつけてワールドにカメラを配置
        pWorld->AddCamera("カメラ1", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera1 = pWorld->camera("カメラ1"); // カメラ1 というタグのついたカメラへの共有ポインタを得る
        pCamera1->SetScreenHandle(screenHandle0); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer1 = Object::Create<Player>(nullptr, pWorld, Pad::Key, Vector3(90 + 256, 32, 95 + 256));
        pWorld->AddPlayer("プレイヤ1", pPlayer1); // シーンにプレイヤを追加する
        pPlayer1->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);

        pPlayer1->camera = pCamera1.get(); // カメラをプレイヤにリンクする


        pWorld->AddCamera("カメラ2", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera2 = pWorld->camera("カメラ2"); // カメラ2 というタグのついたカメラへの共有ポインタを得る
        pCamera2->SetScreenHandle(screenHandle1); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer2 = Object::Create<Player>(nullptr, pWorld, Pad::Two, Vector3(190, 32, 195));
        pWorld->AddPlayer("プレイヤ2", pPlayer2); // シーンにプレイヤを追加する
        pPlayer2->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);
       
        pPlayer2->camera = pCamera2.get(); // カメラをプレイヤにリンクする

    }

};


(以下、略)......


いかがでしょう?カメラも今後必要あらば、ObjectやGameObjectを継承させることもできなくはありません。
現状は、shared_from_this_as<Player>を使ってInitで初期化しなきゃいけないようなものはありません。
次は、上記のshared_from_this_asを使ったInit初期化処理が必要になるような「コンポーネントの仕組み」を導入してみましょう。

Componentクラスを継承して、プレイヤなどに付け離し再利用可能なコンポーネント指向の機能を設計する

Unityなどのゲームエンジンの便利さを担保している仕組みとして「コンポーネント」があります。
ベースとなるGameObjectに[AddComponentボタン]を押して機能を付けたり外したりできる仕組みです。
では、そもそもコンポーネントとは何なのかを考えてみる必要がありそうです。
コンポーネントはいろいろな機能を持ちますが、それらはしょせんは継承した先の追加の機能にすぎません。
ですからコンポーネントそのものとは何なのか?ということに対する1つの重要な答えは、

「そのコンポーネントの所有者(オーナー)のパラメータにアクセスでき、オーナーに付着することができる」

ゲームでいうところの装備みたいなイメージでしょうか?
装備すると、装備したオーナーのHPを毎ターン回復、するとするなら、オーナーのHPのパラメータにアクセスして、書き換えられなきゃいけません。
つまり、コンポーネントは初期化するときに「所有者オーナーの this ポインタを受け取り、そのthis経由でオーナーのパラメタを書き換える」
ことができれば、装備主にいろいろな影響を与えることができるわけです。
すなわち「コンストラクタでオーナーのthisを受け取り、オーナーに所有:共有ポインタで所持され、オーナーと道連れにリセットされる」という
shared_from_this_as と std::shared_ptrの組み合わせが、コンポーネントの設計の根幹であることが導かれます。

では、下記のシンプル化したComponentの仕組みを見てみましょう。

Componentの例
struct Component
{
   GameObject* m_owner{ nullptr }; // オーナーのパラメータへアクセスするためのポインタ
   Component(GameObject* owner) : m_owner{ owner } {}

   void Update()
   {
      owner->x += 1; // ポインタ経由でオーナーの位置を+1ずつずらす
   }
}

そうです。コンストラクタの初期化のときに宿主である所有者オーナーのポインタ ownerさえ受け取ってリンクを取っておけば、
毎回のUpdate()ごとに宿主オーナーのパラメタの x を +1 するなり、毒で宿主のHPを減らし続けるなり、
オーナーのパラメタに影響を及ぼすことができます。

オーナー側は下記のようになります。

Playerの例
struct Player
{
   std::shared_ptr<Component> m_component{ nullptr }; // コンポーネントへの共有カウンタ +1をキープして消えないように所持する
   Player() : m_component{ this } {}

   void Update()
   {
      m_component->Update(); // コンポーネントのUpdate経由でオーナーの位置を+1ずつずらす
   }
}

所有者のオーナー側はコンポーネントへの共有ポインタに最低限 +1 のカウンタをキープし続けることでコンポーネントが消えないようにキープします。
そして、Playerクラス側のUpdate関数内でm_component->Update(); のように所有するコンポーネントのUpdateを経由して自分のx位置のパラメタを変化させます。
これが一番シンプルな形のコンポーネントの仕組みです。

ただし、コンポーネント側のポインタは生ポインタの this ではなく共有ポインタのほうがよいでしょう。
一旦、生ポインタになってしまうと、例えば、コンポーネントを継承した当たり判定のコライダがあるとしましょう。
コライダ同士が衝突した際に、どれとどれがぶつかったかの衝突情報にはぶつかったGameObjectの共有ポインタが必要になります。
なぜ、生ポインタだとだめなの?と思うかもしれませんが、
衝突した情報を見ている時点ですでにそのぶつかったGameObjectが消えてしまっているとしたら、
生ポインタでは、住所は空の番号(=nullptr) ではないけど、すでにその住所にあった データ本体は メモリから削除済 ということが起こりえます。
例えるなら、スマホに遠い昔の知人の電話番号はまだ登録されているが、電話をかけてみると「この番号は現在使われておりません」となる状態です。
この状態の生ポインタは、VisualStudioの環境ではデバッグでアクセスすると0xccccccccc.. という16進数のcで埋まった番号になっています。
このすでにメモリから削除済の0xcccccc...状態になっているポインタを「ダングリングポインタ」と呼びます。
「ダングリング状態 = 0xcccccc....」で「ヌルポインタ状態 = 0x000000....」ですからダングリング状態は if (p == nullptr) では判定できません
ですから、ダングリング状態が予想される状況では、共有ポインタを使うことになります。
共有ポインタには std::shared_ptr だけではなく 弱共有ポインタ std::weak_ptr もあります。
弱共有ポインタ std::weak_ptr は 共有ポインタstd::shared_ptr と違って、リンクを張っただけでは 共有カウンタを +1 しません

弱共有ポインタは使うときだけ、
std::shared_ptr<Component> pComponent = weak_pComponent.lock(); のように
.lock() でロックすることによって、ふつうの共有ポインタ std::shared_ptr型に戻すことができます
当然、戻した共有ポインタ pComponent が使われなくなれば、lock()も外れて、再び共有カウンタは -1 されます。
では、もし 弱共有ポインタstd::weak_ptrの指す先がすでに「この番号は現在使われておりません状態」のときはどうなるのでしょうか?
この場合は生ポインタと違って if (pComponent == nullptr) の判定で引っ掛けることができます

このように生ポインタではなくstd::weak_ptrの弱共有ポインタなら、使いたいときだけ.lockして共有ポインタに戻したり、ダングリング判定もできます
なので、やはり大元「おおもと」のGameObjectは生ポインタの this では心もとないので shared_from_this_as で 共有ポインタ状態で初期生成できる必要があります。

大元が生ポインタ発祥であると、衝突など「大元がすでに現在使われておりません状態」のダングリング判定が必要な状況で詰んでしまうことになるのです。

では、下記のstd::weak_ptr化したComponentの例を見てみましょう。

Componentの例
struct Component
{
   std::weak_ptr<GameObject> m_owner{ nullptr }; // オーナーのパラメータへアクセスするためのポインタ
   Component(std::shared_ptr<GameObject> owner) : m_owner{ owner } {}

   void Update()
   {
      auto pPlayer = owner.lock(); // 共有ポインタに戻す
      if(pPlayer != nullptr) // ダングリング状態じゃないか?
          pPlayer-
>x += 1; // ポインタ経由でオーナーの位置を+1ずつずらす
   }
}

これでダングリング状態の心配もなくなりました。
なぜ m_owner は std::shared_ptrじゃなくわざわざstd::weak_ptrにする必要があるのか?って?
これは、Componentが「所有される側だから」です。
オーナーであるPlayerは「共有カウンタが 0」にならない限りはメモリから削除されません。
でも、もし「所有しているコンポーネント内部にstd::shared_ptrのリンクがあるとずっと +1 が残ってしまう」わけですね。
「プレイヤと道連れにプレイヤの所有するコンポーネントも消えてほしい」はずなのに逆に「コンポーネント側の +1」の呪いにかかって成仏できないわけです。
コンポーネント内部の m_owner が 弱共有ポインタ std::weak_ptr ならば 共有カウンタは +1 されないので、呪われる心配はありません。

この「所有する側と所有される側が互いに std::shared_ptr のリンクを持ち続ける 呪い」はデッドロックの実例として注意が必要です。
なので、一時的に使ってすぐにリンクを捨てる場合以外は、できる限りstd::weak_ptrで使うときだけ.lock()して使うほうがデッドロックの心配は減ります。
使う側は「std::shared_ptrをメンバ変数などとして長期保持する際は、デッドロックしないかを気にする必要」があります。
std::shared_ptrが共有だけでなく「+1のキープ」を意味することを意識した上で「むやみにクラスのメンバ変数としてstd::shared_ptr型を持たず」に
どこが共有状態の「最低限の+1のキープ」のおおもとかをプログラミングする人は明確に知っておかないと「メモリから成仏しない状態」に悩むことになるでしょう。

共有ポインタの使い手はつねに「いかに道連れにしてやろうか」を考えながらプログラミングする必要があるのです。

実はこれはC++だけじゃなくC#でも同じです。
暗黙のリンクがあちこちに張られている限り「メモリから成仏されない」=「メモリもれ」=「メモリリーク」が起こっている可能性があります。
「C#はポインタを意識しなくてすむ」=「すべてが暗黙の共有リンク状態」であるわけで、
実は「普通の共有ポインタ」と「弱共有ポインタ」を自分でちゃんと区別して使い分けられるC++のほうがまだ怖くない説、すらあるわけです。

「その変数のリンク先、ほんとにメモリから消えてくれてますか?」(怪談調)のことばを意識しながらプログラムしてくださいね。

ではPlayerに付着できるジャンプ上昇と重力落下のコンポーネントを設計していきましょう。
設計を始める前に、まずはコンポーネント設計無しのPlayerクラスへの直打ちでのジャンプと重力落下のコードを書いてみましょう。
ジャンプのコードはこちらの記事を参考に3D対応します→キャラをZキーでジャンプさせるプログラム

Player.hを変更して、ジャンプと重力落下の処理を直打ちで追加します。

#ifndef PLAYER_H_
#define PLAYER_H_

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "GameObject.h"

class World; // 前方宣言
class Camera; // 前方宣言

class Player : public GameObject
{
protected:
    // コンストラクタはprotected:にしてあるので、Create関数経由で初期生成してください
    Player(World* world, Pad pad, Vector3 position, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : pad{ pad }, GameObject(world, position, rotation, velocity, force)
    {
        this->typeTag = "Player"; // タグは個別の"プレイヤ1"などに使い、タイプはtypeTagで判別する形にしてタイプのジャンルを区別
    }

public:
    // 仮想デストラクタ
    virtual ~Player() {}

    Pad pad; // 操作するコントローラー番号
    hash32 worldTag{ "" }; // 現在いるワールドのタグ
    float MoveSpeedMax = 6; // 移動速度Max値
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // プレイヤの動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

    std::shared_ptr<Texture> image; // プレイヤの板ポリゴンで描くタイル分割画像への共有リンク

    Camera* camera{ nullptr }; // プレイヤを追随するカメラ
    void SetCamera(Camera* pCamera) { camera = pCamera; }

    float yJumpStart{ 0.0f };
    std::vector<float> vyJumpSpeed{ 13, 14, 15, 4 }; // ジャンプ開始の初期速度
    std::vector<float> vyForceJump{ 0.5f, 0.4f, 0.3f, 0.1f }; // ジャンプ上昇にかかる力の初期値
    std::vector<float> vyGravity{ 0.8f,0.8f,0.8f,0.8f }; // 重力の設定値
    std::vector<float> vyDownSpeedMax{ 16, 16, 16, 8 }; // 降下スピードのリミット


    // 入力を受けての処理
    virtual void HandleInput();

    // 更新処理
    virtual void Update() override;

    // 描画処理
    virtual void Draw() override;

};

#endif



Player.cppを変更して、GameObjectを継承する記述にしましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}


// 更新処理
void Player::Update()
{
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    if (isGround)
    {
        if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
            || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
        {
            vy = vyJumpSpeed[0]; // ジャンプ方向はY上方向
            vyForce = -vyForceJump[0];
            yJumpStart = y;
            isGround = false; // ジャンプ中はisGroundフラグがfalse
        }
    }
    else if (vy <= 0) // 下落開始(vyが0未満のときはまだジャンプ中)
    {
        vyForce = -vyGravity[0]; // ジャンプ上昇力が0になって以降に初めて重力をオンにする
    }
    else if (Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
        || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
    {
        if (y - yJumpStart >= 1) // ジャンプ位置から1ピクセルを超えないタイミングでボタンを離しても重力はかけない
            vyForce = -vyGravity[0]; // ジャンプ上昇中(vy未満のとき)にボタンを離したら早めに重力をかけ始める
    }


    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    // 実際に位置を動かす

    // まず横に移動する
    x += vx;

    // 次に縦に動かす
    y += vy;

    // 次にZ奥行き方向に動かす
    z += vz;

    vy += vyForce; // 勢い(力・フォースを加算

    if (y <= 0.0f) // 0.0fを地面として 0.0f以下になったら地面に着地したと判定する
    {
        y = 0.0f; // 地面に沿わせる
        vy = -0.01f; // 下方向への速度をほぼ 0 にする
        isGround = true;
    }

    if (vy <= -vyDownSpeedMax[0]) // 無限に落下速度が加速しないようにリミット(空気抵抗みたいなもの)
        vy = -vyDownSpeedMax[0];



    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

            });

    }
}


いかかでしょう?上のカメラの"プレイヤ1"はキーボードのZキー(コントローラ1はA)を押したらジャンプしましたか?
下のカメラの"プレイヤ2"はキーボードのQキー(コントローラ2はA)を押したらジャンプしましたか?
いちいち追加のコントローラをPCに用意しなくてもプレイヤ2もキーボードでデバッグできるようにしました。

まあ、これで全然OKじゃん、と思う人もいますでしょうが、コンポーネントで着脱可能にすれば「アイテムを取ったらジャンプできるようになる」
もしくは、プレイヤ以外の「敵にもジャンプや重力のコンポーネントを付着して再利用」することも可能になり、柔軟性があがるわけです。
それがわざわざ頑張って「コンポーネント化」する意義なわけですね。

ではここからはこの「ジャンプするコンポーネント」と「重力落下するコンポーネント」を作成していきましょう。

まずはベース基底となるComponentクラスを準備します。

Component.hを新規作成して、コンポーネントのベース基底となるクラスを作成しましょう。

#ifndef COMPONENT_H_
#define COMPONENT_H_

#include <unordered_map> // コンポーネントの辞書配列に使う
#include <typeindex> // intやComponentなどの「型」をsize_t型の IDの数値 へ typeid(int)などを使って変換する
#include "MyHash.h" // MyHashクラスで 言語 C++ C# など や ビルドするコンパイラ VisualStudioやgccなどに関係なく同じハッシュを計算
#include <string>
#include <memory> // 共有ポインタを使う

#include "Object.h" // コンポーネントもObjectクラスを継承してshared_from_thisできるようにする

class GameObject; //前方宣言(循環インクルード防止)

// 各コンポーネントのひな形
class Component : public Object
{
protected:
    std::weak_ptr<GameObject> m_pOwner; // このコンポーネントを所有するPlayerクラスなどへの弱共有ポインタ(弱なので所有権を主張しない)
public:
    // このコンポーネントを所有するプレイヤなどのオーナーへの共有ポインタを得る (オーナーがすでにメモリから削除済の場合は nullptr になる)
    std::shared_ptr<GameObject> owner() const { auto pOwner = m_pOwner.lock(); return (pOwner != nullptr) ? pOwner : nullptr; }
    std::type_index typeID = typeid(Component);
    hash32 typeTag{ "" }; // 小カテゴリ コンポーネントの名前 TilemapColliderなどColliderを継承したコンポーネント本体の名前
    hash32 baseTag{ "" }; // 大カテゴリ コンポーネントの種類(Collider)など https://docs.unity3d.com/ja/current/ScriptReference/Collider.html
    hash32 statusTag{ "" }; // 自由に使えばよいタグ
    bool isEnabled{ true }; // コンポーネントが有効かどうか?
protected:
    // 初期化コンストラクタ : このコンポーネントの所有者ownerを内部に持つ
    Component(std::weak_ptr<GameObject> pOwner) : m_pOwner{ pOwner }, Object()
    {};
public:
    // 下記関数をoverrideして実装して【コンポーネント志向】を実現する
    //virtual void Initialize(GameObject* obj = nullptr) {}; // Init的な処理 BGMの変更とか
    virtual void Update(GameObject* obj = nullptr) {}; // Update的な役
    virtual void Draw(GameObject* obj = nullptr) {}; // Draw的な役
    //virtual void Finalize(GameObject* obj = nullptr) {}; // Finalize的な役(ステートの終了時)

    virtual ~Component() {}; // 仮想デストラクタ(無いとstringなど配列を内包するもののデストラクタが暗黙で呼ばれずメモリリーク)
};

#endif

最初に解説したとおり、オーナーのGameObjectへのリンクは弱共有ポインタstd::weak_ptrで受け取っているのでオーナーが削除されるのを邪魔しません。
一方で、owner()関数を呼んだときには、.lock()でロックをかけてstd::weak_ptrをstd::shared_ptrに変換して 一時的に共有カウンタを+1 しています。
owner()関数によって、普段はオーナーが削除されるのを邪魔せず、虎視眈々とstd::weak_ptrをかかえて潜伏し「必要なときだけ 一時的に共有カウンタを+1」して共有ロック状態へ復帰もできます。
「弱い共有状態とは普段はリンク先が消えるのを邪魔せず、一時的に使ってる最中は消えないように +1 で削除を邪魔する」使い方ができるということです。

つぎにベース基底となるComponentクラスを継承して、派生(Derived)したジャンプや落下の物理系コンポーネントを作成しましょう。

PhysicsComponent.hを新規作成して、物理系のPhysicsComponentクラスを継承してジャンプや落下のコンポーネント作成しましょう。

#ifndef PHYSICS_COMPONENT_H_
#define PHYSICS_COMPONENT_H_

#include <vector>

#include "Component.h"
#include "GameObject.h"

// 物理系のコンポーネントはこのクラスを継承して実装する
class PhysicsComponent : public Component
{
protected:
    inline PhysicsComponent(std::shared_ptr<GameObject> pOwner) : Component(pOwner)
    {
        this->baseTag = "PhysicsComponent"; // 大まかなカテゴリーとしては物理系コンポーネント
    };
public:
    virtual ~PhysicsComponent() {};

    virtual void Update(GameObject* obj = nullptr) override
    {
        // なにか物理系のコンポーネントで共通して更新したいことがあればここに実装
    }

    virtual void Draw(GameObject* obj = nullptr) override
    {
        // なにか物理系のコンポーネントで共通して描画したいことがあればここに実装
    }
};

class GravityComponent : public PhysicsComponent
{
public:
    inline GravityComponent(std::shared_ptr<GameObject> pOwner, const std::vector<float>& vyGravity, const std::vector<float>& vyDownSpeedMax)
        : PhysicsComponent(pOwner), m_vyGravity{ vyGravity }, m_vyDownSpeedMax{ vyDownSpeedMax }
    {
        this->typeID = typeid(GravityComponent); //[環境によって差が出るので↓が無難か] https://qiita.com/nil_gawa/items/1fece3eee7ca1f88c71d
        this->typeTag = "GravityComponent"; // 個別のカテゴリーとしては重力落下コンポーネント
    };
    virtual ~GravityComponent() {}

    size_t m_idx{ 0 }; // 適用する設定配列の番号
    void SetIndex(size_t idx) { m_idx = idx; }
    std::vector<float> m_vyGravity; // 重力の設定値
    float gravity() { return (m_idx < m_vyGravity.size()) ? m_vyGravity[m_idx] : 0.0f; }
    std::vector<float> m_vyDownSpeedMax; // 降下スピードのリミット
    float downSpeedMax() { return (m_idx < m_vyDownSpeedMax.size()) ? m_vyDownSpeedMax[m_idx] : 0.0f; }

    virtual void Update(GameObject* obj = nullptr) override
    {
        if (!isEnabled) return; // コンポーネントが有効状態じゃないときは何もせずreturn
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタをロックして得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (pOwner->isGround == false && pOwner->vy <= 0) // 下落開始(vyが 0以上 のときはまだジャンプ中なので上昇を邪魔しないようにする)
        {
            pOwner->vyForce = -gravity(); // ジャンプ上昇力が0になって以降に初めて重力をオンにする(ジャンプの加速を邪魔しない工夫)
            pOwner->OnFalling(); // 下落中の処理のコールバック関数を呼び返す
        }

        if (pOwner->vy <= -downSpeedMax()) // 無限に落下速度が加速しないようにリミットを働かせる(空気抵抗みたいなもの)
            pOwner->vy = -downSpeedMax(); // downSpeedMax()以上の落下速度にならないようにする
    }
};

class JumpComponent : public PhysicsComponent
{
public:
    inline JumpComponent(std::shared_ptr<GameObject> pOwner, const std::vector<float>& vyJumpSpeed, const std::vector<float>& vyForceJump)
        : PhysicsComponent(pOwner), m_vyJumpSpeed{ vyJumpSpeed }, m_vyForceJump{ vyForceJump }
    {
        this->typeID = typeid(JumpComponent); //[環境によって差が出るので↓が無難か] https://qiita.com/nil_gawa/items/1fece3eee7ca1f88c71d
        this->typeTag = "JumpComponent"; // 個別のカテゴリーとしてはジャンプ上昇コンポーネント
    };
    virtual ~JumpComponent() {}

    size_t m_idx{ 0 }; // 適用する設定配列の番号
    void SetIndex(size_t idx) { m_idx = idx; }
    float yJumpStart{ 0 }; // ジャンプした瞬間のyの位置
    std::vector<float> m_vyJumpSpeed; // ジャンプの上昇スピードのリミット
    float jumpSpeed() { return (m_idx < m_vyJumpSpeed.size()) ? m_vyJumpSpeed[m_idx] : 0.0f; }
    std::vector<float> m_vyForceJump; // ジャンプ中の下方向の勢いの減速する力
    float forceJump() { return (m_idx < m_vyForceJump.size()) ? m_vyForceJump[m_idx] : 0.0f; }

    virtual void Update(GameObject* obj = nullptr) override
    {
        if (!isEnabled) return; // コンポーネントが有効状態じゃないときは何もせずreturn
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (pOwner->isGround == false && pOwner->vy > 0) // vyが0以上のときはまだジャンプ中
        {
            pOwner->OnJumping(); // 空中でジャンプの上昇中に呼ばれるコールバック関数を呼び返す
        }
    }

    virtual void JumpStart(GameObject* obj = nullptr)
    {
        if (!isEnabled) return; // コンポーネントが有効状態じゃないときは何もせずreturn
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (pOwner->isGround)
        {
            pOwner->vy = jumpSpeed(); // ジャンプ方向はY上方向
            pOwner->vyForce = -forceJump(); // ジャンプの勢いが弱まる力
            yJumpStart = pOwner->y;

            pOwner->OnStartJump(); // ジャンプスタート時の処理をコールバックで呼び返す
        }
    }

    virtual void JumpCancel(GameObject* obj = nullptr)
    {
        if (!isEnabled) return; // コンポーネントが有効状態じゃないときは何もせずreturn
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (!pOwner->isGround && pOwner->vy >= 0) // vyが0以上のときはまだジャンプ上昇中
        {
            if (pOwner->y - yJumpStart >= 1) // ジャンプ位置から1ピクセルを超えないタイミングでボタンを離しても重力はかけ始めない
            {
                pOwner->vyForce = -pOwner->gravity(); // ジャンプボタンを離したときには重力をかけ始める
            }
        }
    }
};

#endif


GameObject.hを変更して、OnStartJump()やOnFalling()やgravity()などジャンプ中や落下中に呼ばれるoverride元の関数を準備しましょう。

#ifndef GAMEOBJECT_H_
#define GAMEOBJECT_H_

#include <memory> // 共有ポインタの定義のため

#include "Vector3.h"
#include "Quaternion.h"

#include "Object.h" // インスタンスIDや検索タグを持ち、shared_from_this_as<継承先クラス名>などができる

class World; // 前方宣言
class Component; // 前方宣言

// ゲーム上に表示される物体の基底クラス。
// プレイヤーや敵、アイテムなどはこのクラスを継承して作る。
class GameObject : public Object
{
protected:
    // コンストラクタはprotected:にしてあるのでCreate関数経由で初期生成してください
    GameObject(World* world = nullptr, Vector3 position = { 0,0,0 }, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : Object(), m_world{ world }, position{ position }, rotation{ rotation }, velocity{ velocity }, force{ force }
    {

    }

public:
    // 仮想デストラクタ
    virtual ~GameObject() {}


    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    }; // unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    Quaternion rotation; // 回転クオータニオン

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

    // 重力を返す、重力を変えたいときはoverrideして処理を変えてください、重力を変えてない場合は 0.0f
    virtual inline float gravity() { return 0.0f; }


protected:
    World* m_world; // GameObjectが配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }
   
    bool isDead{ false }; // 死んだ(削除対象)フラグ
    bool isStatic{ false }; // 動かない(静的オブジェクトか?)
    bool isGround{ false }; // 地面についているか
    bool isFlying{ false }; // 空をとんでいるか

    hash32 typeTag{ "" }; // 小カテゴリ Zako0など個別のタイプ
    hash32 baseTag{ "" }; // 大カテゴリ Enemyなど大まかなベースジャンル

    // 更新処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Update() = 0; // 純粋仮想関数 = 0 にすると【絶対 Update()関数はoverrideしなきゃいけない義務を付与できる 】

    // 描画処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Draw() = 0; // 純粋仮想関数 = 0 にすると【絶対 Draw()関数はoverrideしなきゃいけない義務を継承先クラスに付与 】

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump()
    {
        // overrideしてジャンプした瞬間に音を鳴らしたりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中でジャンプの上昇中に呼ばれる関数
    virtual void OnJumping()
    {
        // overrideして空中でジャンプの上昇中にエフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中で落ちている最中に呼ばれる関数
    virtual void OnFalling()
    {
        // overrideして空中で落ちている最中に音を鳴らしたり、エフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };


};

#endif


あとはこれらのコンポーネントの処理を、先ほどPlayerに直で書いたジャンプと落下の処理と置き換えます。

Player.hを変更して、ジャンプと重力落下の初期化をコンポーネントへ置き換えます。

#ifndef PLAYER_H_
#define PLAYER_H_

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "GameObject.h"
#include "PhysicsComponent.h"

class World; // 前方宣言
class Camera; // 前方宣言

class Player : public GameObject
{
protected:
    // コンストラクタはprotected:にしてあるので、Create関数経由で初期生成してください
    Player(World* world, Pad pad, Vector3 position, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : pad{ pad }, GameObject(world, position, rotation, velocity, force)
    {
        this->typeTag = "Player"; // タグは個別の"プレイヤ1"などに使い、タイプはtypeTagで判別する形にしてタイプのジャンルを区別
    }

public:
    // 仮想デストラクタ
    virtual ~Player() {}

    Pad pad; // 操作するコントローラー番号
    hash32 worldTag{ "" }; // 現在いるワールドのタグ
    float MoveSpeedMax = 6; // 移動速度Max値
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // プレイヤの動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

    std::shared_ptr<Texture> image; // プレイヤの板ポリゴンで描くタイル分割画像への共有リンク

    Camera* camera{ nullptr }; // プレイヤを追随するカメラ
    void SetCamera(Camera* pCamera) { camera = pCamera; }

    // overrideして物理落下コンポーネントからの重力値を返す
    virtual float gravity() override { return (gravityPhysics != nullptr) ? gravityPhysics->gravity() : 0.0f; }
protected:
    std::shared_ptr<JumpComponent> jumpPhysics{ nullptr }; // ジャンプの物理処理コンポーネント
    std::shared_ptr<GravityComponent> gravityPhysics{ nullptr }; // 落下の重力の物理処理コンポーネント
public:
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        std::vector<float> vyJumpSpeed{ 13, 14, 15, 4 }; // ジャンプ開始の初期速度
        std::vector<float> vyForceJump{ 0.5f, 0.4f, 0.3f, 0.1f }; // ジャンプ上昇にかかる力の初期値
        std::vector<float> vyGravity{ 0.8f,0.8f,0.8f,0.8f }; // 重力の設定値
        std::vector<float> vyDownSpeedMax{ 16, 16, 16, 8 }; // 降下スピードのリミット
        // shared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        jumpPhysics = std::make_shared<JumpComponent>(shared_from_this_as<Player>(), vyJumpSpeed, vyForceJump);
        gravityPhysics = std::make_shared<GravityComponent>(shared_from_this_as<Player>(), vyGravity, vyDownSpeedMax);
    }

    float yJumpStart{ 0.0f };
    std::vector<float> vyJumpSpeed{ 13, 14, 15, 4 }; // ジャンプ開始の初期速度
    std::vector<float> vyForceJump{ 0.5f, 0.4f, 0.3f, 0.1f }; // ジャンプ上昇にかかる力の初期値
    std::vector<float> vyGravity{ 0.8f,0.8f,0.8f,0.8f }; // 重力の設定値
    std::vector<float> vyDownSpeedMax{ 16, 16, 16, 8 }; // 降下スピードのリミット


    // 入力を受けての処理
    virtual void HandleInput();

    // 更新処理
    virtual void Update() override;

    // 描画処理
    virtual void Draw() override;

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump() override;

};

#endif

着目すべきは、Init()関数やgravity()関数やOnStartJump()関数のoverrideです。
gravity()関数のoverrideによりプレイヤはGravityComponentから得た重力を採用することができます。
OnStartJump()関数のoverrideにより、ジャンプ開始時にisGroundのフラグをfalseにしたり、プレイヤごとの効果音を鳴らす処理もここでカスタムできます。
Init()関数のoverrideにより、Create関数の初期化と連動して、上昇する力や落下の設定値をコンポーネントに渡し、
もっとも重要なのは、プレイヤへのポインタshared_from_this_as<Player>()をコンポーネントに初期化と同時に渡せていることです。
これで、コンポーネント側はプレイヤ側の vy(縦方向の加速度) などに自由にアクセスできる構造を実現できるようになります。

では、あとはプレイヤ側のジャンプや落下の処理をコンポーネントの呼び出しへと置き換えましょう。

Player.cppを変更して、コンポーネントにジャンプや落下の処理を任せましょう

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}

// ジャンプ開始した直後に呼ばれる関数
void Player::OnStartJump()
{
    this->isGround = false; // ジャンプ中はisGroundフラグがfalse
}



// 更新処理
void Player::Update()
{
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    if (jumpPhysics != nullptr)
    {
        if (isGround)
        {
            if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
                || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
            {
                jumpPhysics->JumpStart(); // ジャンプのスタート処理を発動
            }
        }
        else if (vy > 0 && Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
            || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
        {
            jumpPhysics->JumpCancel(); // ジャンプのキャンセル処理を発動
        }
        jumpPhysics->Update(); // ジャンプのコンポーネントを更新する
    }
    if (gravityPhysics != nullptr)
        gravityPhysics->Update(); // 重力落下のコンポーネントを更新する


    if (isGround)
    {
        if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
            || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
        {
            vy = vyJumpSpeed[0]; // ジャンプ方向はY上方向
            vyForce = -vyForceJump[0];
            yJumpStart = y;
            isGround = false; // ジャンプ中はisGroundフラグがfalse
        }
    }
    else if (vy <= 0) // 下落開始(vyが0未満のときはまだジャンプ中)
    {
        vyForce = -vyGravity[0]; // ジャンプ上昇力が0になって以降に初めて重力をオンにする
    }
    else if (Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
        || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
    {
        if (y - yJumpStart >= 1) // ジャンプ位置から1ピクセルを超えないタイミングでボタンを離しても重力はかけない
            vyForce = -vyGravity[0]; // ジャンプ上昇中(vy未満のとき)にボタンを離したら早めに重力をかけ始める
    }


    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    // 実際に位置を動かす

    // まず横に移動する
    x += vx;

    // 次に縦に動かす
    y += vy;

    // 次にZ奥行き方向に動かす
    z += vz;

    vy += vyForce; // 勢い(力・フォースを加算

    if (y <= 0.0f) // 0.0fを地面として 0.0f以下になったら地面に着地したと判定する
    {
        y = 0.0f; // 地面に沿わせる
        vy = -0.01f; // 下方向への速度をほぼ 0 にする
        isGround = true;
    }

    if (vy <= -vyDownSpeedMax[0]) // 無限に落下速度が加速しないようにリミット(空気抵抗みたいなもの)
        vy = -vyDownSpeedMax[0];



    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

            });

    }
}


いかがでしょう?無事キーボードのZキーとQキーでキャラをジャンプさせることができましたか?
ここまでがゲームエンジンを形づくっているコンポーネント指向の仕組みの根幹であり、その仕組みを一通り自作したわけです。

え、Unityみたいに「かっこよくAddComponent<JumpComponent>(..~..);」しないと気分が上がらないですって?
お若いですね。。「かっこつけるということは、柔軟にテンプレで可変引数...ArgsTを受け入れる」ってことですからね。
それは、つまり「引数のタイプ型や引数の個数などが 自分で定義したJumpComponentのコンストラクタ と合わないとテンプレの謎エラーが出る」ってことです。
単なる自分の勘違いで初期化コンストラクタの 〇〇Component(Vector3 pos1, int param2, ...)のパラメータparam2に間違ってVector3型を渡しちゃったとしましょう。
そうすると当然、テンプレの謎エラーが出ます「エラー:オーバーロードされた関数ですべての引数の型が変換できませんでした」(ファイルはxutility←テンプレ関連ファイル)
つまり、普通の初期化コンストラクタで「渡す引数まちがえちゃったケアレスミスの場合」は 〇〇Component.h などのコンストラクタのファイルが案内されるけれども、
可変引数テンプレ...ArgsT に手を出すと、かっこよさと引き換えに「謎エラー案内」が出るわけですね。
ゆえに、可変引数テンプレ...ArgsTは 中級者向けの機能とはなるわけです。
が、しかし、すでにCreate関数で初期化に 可変引数テンプレ...ArgsT は使っておりますし、これなしにはやっていけませんので、
上記の「謎エラー案内」=「渡す引数まちがえちゃったケアレスミス」の場合が多いので、ちゃんと初期化で渡すパラメータをよく見て渡しましょう。

では、Unityみたいに「かっこよくAddComponent<JumpComponent>(..~..);」するテンプレ処理を追加しましょう。

GameObject.hを変更して、AddComponentやGetComponentsなどのテンプレ処理を準備しましょう。

#ifndef GAMEOBJECT_H_
#define GAMEOBJECT_H_

#include <memory> // 共有ポインタの定義のため

#include "Vector3.h"
#include "Quaternion.h"

#include "Object.h" // インスタンスIDや検索タグを持ち、shared_from_this_as<継承先クラス名>などができる

class World; // 前方宣言
class Component; // 前方宣言

// ゲーム上に表示される物体の基底クラス。
// プレイヤーや敵、アイテムなどはこのクラスを継承して作る。
class GameObject : public Object
{
protected:
    // コンストラクタはprotected:にしてあるのでCreate関数経由で初期生成してください
    GameObject(World* world = nullptr, Vector3 position = { 0,0,0 }, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : Object(), m_world{ world }, position{ position }, rotation{ rotation }, velocity{ velocity }, force{ force }
    {

    }

public:
    // 仮想デストラクタ
    virtual ~GameObject() {}


    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    }; // unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    Quaternion rotation; // 回転クオータニオン

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

    // 重力を返す、重力を変えたいときはoverrideして処理を変えてください、重力を変えてない場合は 0.0f
    virtual inline float gravity() { return 0.0f; }

protected:
    World* m_world; // GameObjectが配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }
   
    bool isDead{ false }; // 死んだ(削除対象)フラグ
    bool isStatic{ false }; // 動かない(静的オブジェクトか?)
    bool isGround{ false }; // 地面についているか
    bool isFlying{ false }; // 空をとんでいるか

    hash32 typeTag{ "" }; // 小カテゴリ Zako0など個別のタイプ
    hash32 baseTag{ "" }; // 大カテゴリ Enemyなど大まかなベースジャンル

protected:
    // コンポーネントの<ハッシュID, 共有所有ポインタ> の辞書でコンポーネントの共有カウンタ+1を保持する
    std::unordered_map<size_t, std::shared_ptr<Component>> components;
public:
    // コンポーネントを得る 例えば GetComponent<BoxCollider>でBoxColliderという名前のコンポーネントをcomponents辞書から得る
    template<class ComponentT>
    std::vector<std::weak_ptr<ComponentT>> GetComponents()
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        std::vector<std::weak_ptr<ComponentT>> returnList; // "ComponentT" 例."CircleCollider"など をキーとするコンポーネント一覧
        for (auto ite = range.first; ite != range.second; ++ite)
            returnList.emplace_back(ite->second); // 辞書で見つかったコンポーネントを返すリストに追加
        return returnList;
    }

    // コンポーネントを追加する .AddComponent<BoxCollider>(...)で追加できる
    template<class ComponentT, typename ...ArgsT>
    std::weak_ptr<ComponentT> AddComponent(ArgsT&&...args)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]

        // shared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        std::shared_ptr<ComponentT> newComponent = std::make_shared<ComponentT>(shared_from_this_as<GameObject>(), std::forward<ArgsT>(args)...);
       
        // https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        // std::unrodered_multimap<> と同型の値を取る std::pair<> を作って insert 関数でcomponents辞書に追加
        components.insert(std::pair<size_t, std::shared_ptr<ComponentT>>(hashID, newComponent));
        return newComponent;
    }

    // コンポーネントを削除する
    template<typename ComponentT>
    bool RemoveComponent(std::shared_ptr<ComponentT> removeComponent)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/

        //[イテレート中に探しながら消す] https://cpprefjp.github.io/reference/unordered_map/unordered_multimap/erase.html
        for (auto ite = range.first; ite != range.second;)
            if (ite->second == removeComponent)
            {
                components.erase(ite); // 削除された要素の次を指すイテレータが返される
                return true; // 見つかって消せた場合は true
            }
            else
                ++ite; // 次の要素へイテレータを進める

        return false; // 見つからずに消せなかった場合は false
    }


    // 更新処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Update() = 0; // 純粋仮想関数 = 0 にすると【絶対 Update()関数はoverrideしなきゃいけない義務を付与できる 】

    // 描画処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Draw() = 0; // 純粋仮想関数 = 0 にすると【絶対 Draw()関数はoverrideしなきゃいけない義務を継承先クラスに付与 】

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump()
    {
        // overrideしてジャンプした瞬間に音を鳴らしたりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中でジャンプの上昇中に呼ばれる関数
    virtual void OnJumping()
    {
        // overrideして空中でジャンプの上昇中にエフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中で落ちている最中に呼ばれる関数
    virtual void OnFalling()
    {
        // overrideして空中で落ちている最中に音を鳴らしたり、エフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

};

#endif


コンポーネントが最後まで消えないように「共有カウンタ+1をキープする役割」はcomponents辞書に移りました
ですので、Playerクラス側のstd::shared_ptrはstd::weak_ptrに変えておきましょう。+1のキープ元はなるべく1か所に限定しておきたいですから。

Player.hを変更して、UnityっぽくAddComponentでコンポーネントを追加し、プレイヤ側のstd::shared_ptrはstd::weak_ptrに変えておきましょう。

#ifndef PLAYER_H_
#define PLAYER_H_

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "GameObject.h"
#include "PhysicsComponent.h"

class World; // 前方宣言
class Camera; // 前方宣言

class Player : public GameObject
{
protected:
    // コンストラクタはprotected:にしてあるので、Create関数経由で初期生成してください
    Player(World* world, Pad pad, Vector3 position, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : pad{ pad }, GameObject(world, position, rotation, velocity, force)
    {
        this->typeTag = "Player"; // タグは個別の"プレイヤ1"などに使い、タイプはtypeTagで判別する形にしてタイプのジャンルを区別
    }

public:
    // 仮想デストラクタ
    virtual ~Player() {}

    Pad pad; // 操作するコントローラー番号
    hash32 worldTag{ "" }; // 現在いるワールドのタグ
    float MoveSpeedMax = 6; // 移動速度Max値
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // プレイヤの動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

    std::shared_ptr<Texture> image; // プレイヤの板ポリゴンで描くタイル分割画像への共有リンク

    Camera* camera{ nullptr }; // プレイヤを追随するカメラ
    void SetCamera(Camera* pCamera) { camera = pCamera; }

    // overrideして物理落下コンポーネントからの重力値を返す
    virtual float gravity() override { auto pGravityPhysics = gravityPhysics.lock(); return (pGravityPhysics != nullptr) ? pGravityPhysics->gravity() : 0.0f; }
protected:
    std::weak_ptr<JumpComponent> jumpPhysics{ nullptr }; // ジャンプの物理処理コンポーネント
    std::weak_ptr<GravityComponent> gravityPhysics{ nullptr }; // 落下の重力の物理処理コンポーネント
public:
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        std::vector<float> vyJumpSpeed{ 13, 14, 15, 4 }; // ジャンプ開始の初期速度
        std::vector<float> vyForceJump{ 0.5f, 0.4f, 0.3f, 0.1f }; // ジャンプ上昇にかかる力の初期値
        std::vector<float> vyGravity{ 0.8f,0.8f,0.8f,0.8f }; // 重力の設定値
        std::vector<float> vyDownSpeedMax{ 16, 16, 16, 8 }; // 降下スピードのリミット
        // AddComponent内部でshared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        jumpPhysics = AddComponent<JumpComponent>(vyJumpSpeed, vyForceJump);
        gravityPhysics = AddComponent<GravityComponent>(vyGravity, vyDownSpeedMax);
    }

    // 入力を受けての処理
    virtual void HandleInput();

    // 更新処理
    virtual void Update() override;

    // 描画処理
    virtual void Draw() override;

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump() override;
};

#endif


Player.cppを変更して、std::shared_ptrからstd::weak_ptrに変えたので.lock()でロックして、
コンポーネントを使っている間(Update関数()の {  ~ }のカッコが閉じるまで )はコンポーネントが消えないように +1 キープしながら使うようにしましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}

// ジャンプ開始した直後に呼ばれる関数
void Player::OnStartJump()
{
    this->isGround = false; // ジャンプ中はisGroundフラグがfalse
}


// 更新処理
void Player::Update()
{
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    auto pJumpPhysics = jumpPhysics.lock();
    if (pJumpPhysics != nullptr)
    {
        if (isGround)
        {
            if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
                || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
            {
                pJumpPhysics->JumpStart(); // ジャンプのスタート処理を発動
            }
        }
        else if (vy > 0 && Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
            || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
        {
            pJumpPhysics->JumpCancel(); // ジャンプのキャンセル処理を発動
        }
        pJumpPhysics->Update(); // ジャンプのコンポーネントを更新する
    }
    auto pGravityPhysics = gravityPhysics.lock();
    if (pGravityPhysics != nullptr)
        pGravityPhysics->Update(); // 重力落下のコンポーネントを更新する

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    // 実際に位置を動かす

    // まず横に移動する
    x += vx;

    // 次に縦に動かす
    y += vy;

    // 次にZ奥行き方向に動かす
    z += vz;

    vy += vyForce; // 勢い(力・フォースを加算

    if (y <= 0.0f) // 0.0fを地面として 0.0f以下になったら地面に着地したと判定する
    {
        y = 0.0f; // 地面に沿わせる
        vy = -0.01f; // 下方向への速度をほぼ 0 にする
        isGround = true;
    }

    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

            });

    }
}


いかがでしょう?結局、AddComponentはしましたが、Player側でジャンプや重力などのstd::weak_ptrを保持して、GetComponentsはわざわざしないようにしました。
GetComponentsは辞書を探す処理が走るので、無駄な計算時間がかかるので避けたかったのも理由の一つです。
UnityでもGetComponentsなどはなるべくStart()関数でするほうが、処理は高速になります。
どのみちPlayerなどのメンバ変数としてstd::weak_ptr<JumpComponent> jumpPhysics;などのコンポーネントへの弱リンクを保管するほうが、
辞書をいちいち探すコストはかからなくなるわけですので、同じようなコードのままになるのは必然なわけです。

では、AddComponentやGetComponentsなどの必要性はどこにあるかというと、
Unityではインスペクタの[Add Component]ボタンなど画面のUI経由でAddComponentできちゃいますから、
必ずしもInit経由からAddComponentされるものだけとは限らないわけです。
または、ジャンプできるようになるアイテムを取った瞬間にAddComponentされるケースもありえます。
そういう場合、つまりUIから多種多様なコンポーネントが来たり、アイテムの種類が多種多様な場合は、
メンバ変数のコンポーネントのタイプ型を事前に想定することができにくいわけですね。
そうなってくるとGetComponentの<~>の中で引き受けたいタイプをそのつど受け取るほうが良いケースもでてくるわけです。
まあでも、InitでAddComponentしたあと、即 isEnabled = false; で無効にしてジャンプ能力を眠らせておいて、
アイテムを取ったときだけisEnabled = true; で有効にしてGetComponentsの辞書サーチは走らせないほうが回避策としては優秀ですがね。
ということで、なるべくUpdateなど毎フレーム繰り返す文脈の中で毎度GetComponentsしないのが、プログラマとしては有能なわけです。
(まあ、それだとUI経由のコンポーネント追加は検知できないわけで、ゲームエンジンのUIの便利さは計算コストとのトレードオフ ではあるわけです)
想定可能なコンポーネントはInit()すなわち UnityではStart()関数であらかじめGetComponentsでメンバ変数に保持しておきましょうということですね。
Update関数の中でGetComponentsするならまだしも、for文のループの中でGetComponentsしている人はいませんか? 完全アウトです。
(UnityでFPSを60に保てる限度において、GetComponentは5万回だそうです。。)


以上、共有ポインタや可変引数テンプレやoverrideなど、もろもろの基礎の積み上げによりAddComponentにたどりつけました。
Unityで何気なくつかっているAddComponentのままでは、thisポインタ渡し は完全ブラックボックス化されていて、
コンポーネントとは「this渡しによる親の変数アクセス」が本質であるという重要な勉強がすっとばされてしまうので、
ほぼプログラミングの設計サイドにふれあう機会なく、便利さのぬるま湯につかってしまいます。
ここまでの勉強によって「ゲームエンジンそのものとは何なのか?」の理解に少しは近づけたでしょうか?


何もない空間をグリッド分割して隣接していない空間に所属するものとの当たり判定をスルーして高速化する

Plane4.hとCollision3D.hは別記事を参考に作っておきます→Vector3の記事の下のほうにPlane4.hとCollision3D.hがあります→Vector3に色々な関数を用意して3D空間の当たり判定をする


さあAddComponentもできてあとは広大なマップを冒険だ!と思った人もおられるでしょう。
しかし、まだ甘い、修業は続きます。
マップが広がってからでは実感できないことを、この「何もない精神と時の部屋」にいるうちにやってしまったほうがよいでしょう。

マップがなくても、空間は分割できるのです。
なんだか空間とか言い出し始めたら「無量空処に適応する魔虚羅」みたいな、わけわからん概念の世界に迷い込みそうですが、
ようは、XYZの位置を10ごとに区切れば
0~10のエリアにいる人と31~40のエリアにいる人のように離れたエリアにいる人どうしは当たり判定をスルーできるということです。
これは、別にマップがなくても空間は区切れます。
Unityなどゲームエンジンでは上位互換の「8分木」を使うことが多いですが、
今回は入門として、単なる上記のような10とか64とか128の倍数などで空間を区切るグリッド分割をとりいれてみましょう。


まずは所属する空間のマス目を表現するためのCellXYZクラスを準備します。(マス目はAIの経路探索などでも多用します)

CellXYZ.hを新規作成して、将棋などのようなマス目位置をハッシュ値変換できて辞書で管理できるクラスを準備しましょう。

#ifndef CELLXYZ_H_
#define CELLXYZ_H_

#include <unordered_map> // ハッシュ値計算 std::hashに必要

#include <cmath>
#include <algorithm> // std::maxに必要

// マス目のXとYとZを保持するデータ型
struct CellXYZ
{
    int X = 0, Y = 0, Z = 0;
    CellXYZ() = default;
    inline ~CellXYZ() {};
    inline CellXYZ(int X, int Y, int Z = 0) : X{ X }, Y{ Y }, Z{ Z } {};
    // 連想配列mapのキーで使うため < 比較演算子(連想配列並べ替えのため)をカスタム定義 http://programming-tips.info/use_struct_as_key_of_map/cpp/index.html
    inline bool operator < (const CellXYZ& other) const
    {
        if (this->Z < other.Z) return true; // Z優先並べ替え(std::map並び順ではZの大きいものが一番あとに[並び順]X行⇒Y列⇒Zのエクセル順)
        else if (this->Z > other.Z) return false;
        if (this->Y < other.Y) return true; // Zが同じ時はYを比較 Z Y の順に並べばCSVのように左上から列→行の順にならぶ
        else if (this->Y > other.Y) return false;
        if (this->X < other.X) return true; // Z,Yが同じ時はXを比較
        else if (this->X > other.X) return false;
        return false; // X と Y と Z がすべて等しい場合
    }
    // == 比較演算子カスタム定義 https://sygh.hatenadiary.jp/entry/2014/02/13/222356
    inline bool operator == (const CellXYZ& other) const {
        return this->X == other.X && this->Y == other.Y && this->Z == other.Z;
    }
    inline bool operator != (const CellXYZ& other) const {
        return !(this->operator==(other));
    }
    inline size_t GetHashCode() const // X,Y,Zの値からハッシュ値を計算 https://sygh.hatenadiary.jp/entry/2014/02/13/222356
    {
        return
            std::hash<decltype(X)>()(this->X) ^
            std::hash<decltype(Y)>()(this->Y) ^
            std::hash<decltype(Z)>()(this->Z);
    }

    struct Hash // ハッシュ値取得用の関数オブジェクト(std::unordered_map のキーで使うため) https://sygh.hatenadiary.jp/entry/2014/02/13/222356
    {
        size_t operator ()(const CellXYZ& cellXYZ) const
        {
            return cellXYZ.GetHashCode();
        }
    };

    inline CellXYZ up() const { return CellXYZ(X, Y - 1, Z); }; // 上方向
    inline CellXYZ down() const { return CellXYZ(X, Y + 1, Z); }; // 下方向
    inline CellXYZ left() const { return CellXYZ(X - 1, Y, Z); }; // 左方向
    inline CellXYZ right() const { return CellXYZ(X + 1, Y, Z); }; // 右方向
    inline CellXYZ upZ() const { return CellXYZ(X, Y, Z - 1); }; // 上方向(Z方向)
    inline CellXYZ downZ() const { return CellXYZ(X, Y, Z + 1); }; // 下方向(Z方向)

    // 方向の種類:2D(4方向,8方向) 3D(6方向,26方向)
    enum class DirType : unsigned char { Direction4, Direction8, Direction6, Direction26 };

    //[enum定義:4方向]上下左右(反時計回り順:上→左→下→右)
    enum class Direction4 : unsigned char { Up = 0, Left, Down, Right };
    //[enum定義:8方向] 反時計回り順:上→左上→左→左下→下→右下→右→右上
    enum class Direction8 : unsigned char { Up = 0, LeftUp, Left, LeftDown, Down, RightDown, Right, RightUp };
    //[enum定義:6方向] 反時計回り順:上→左→下→右→右上→3D上→3D下
    enum class Direction6 : unsigned char { Up = 0, Left, Down, Right, UpZ, DownZ };
    //[enum定義:26方向] 反時計回り順:上→左上→左→左下→下→右下→右→右上→3D下→..反時計回り..→3D上→..反時計回り
    enum class Direction26 : unsigned char {
        Up = 0, LeftUp, Left, LeftDown, Down, RightDown, Right, RightUp,
        DownZ, Up_DownZ, LeftUp_DownZ, Left_DownZ, LeftDown_DownZ, Down_DownZ, RightDown_DownZ, Right_DownZ, RightUp_DownZ,
        UpZ, Up_UpZ, LeftUp_UpZ, Left_UpZ, LeftDown_UpZ, Down_UpZ, RightDown_UpZ, Right_UpZ, RightUp_UpZ,
    };

    //4方向[2D] [constexprで実行前に固定値化して高速化]
    static constexpr const std::tuple<int, int, int> dir4[4] = { {0,-1,0},{-1,0,0},{0,+1,0},{+1,0,0} };

    // マス目の上下左右 4方向 をvector配列として返す ★配列順 と enum定義順 を合わせてある
    static inline std::vector<CellXYZ> directions4()
    {   // moveで右辺値の所有権をそのまま { }の外へreturnで渡す https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
        return std::move(std::vector<CellXYZ> {
            CellXYZ(0, -1), // 上(0時方向)
            CellXYZ(-1, 0), // 左(9時方向)
            CellXYZ(0, +1), // 下(6時方向)
            CellXYZ(+1, 0), // 右(3時方向)
        });
    }
    // マス目の上下左右 4方向 をvector配列として返す ★配列順 と enum定義順 を合わせてある
    inline std::vector<CellXYZ> getDirections4() const
    {   // moveで右辺値の所有権をそのまま { }の外へreturnで渡す https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
        return std::move(std::vector<CellXYZ> {
            CellXYZ(X, Y - 1, Z), // 上(0時方向)
            CellXYZ(X - 1, Y, Z), // 左(9時方向)
            CellXYZ(X, Y + 1, Z), // 下(6時方向)
            CellXYZ(X + 1, Y, Z) // 右(3時方向)
        });
    }

    //8方向[2D] [constexprで実行前に固定値化して高速化]
    static constexpr const std::tuple<int, int, int> dir8[8] = { {0,-1,0},{-1,-1,0},{-1,0,0},{-1,+1,0},{0,+1,0},{+1,+1,0},{+1,0,0},{+1,-1,0} };

    // マス目の上下左右斜め 8方向 をvector配列として返す ★配列順 と enum定義順 を合わせてある
    static inline std::vector<CellXYZ> directions8()
    {   // moveで右辺値の所有権をそのまま { }の外へreturnで渡す https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
        return std::move(std::vector<CellXYZ> {
            CellXYZ(0, -1), //  上(12時  方向)
            CellXYZ(-1, -1), //左上(10時半方向)
            CellXYZ(-1, 0), //  左( 9時  方向)
            CellXYZ(-1, +1), //左下( 7時半方向)
            CellXYZ(0, +1), //  下( 6時  方向)
            CellXYZ(+1, +1), //右下( 4時半方向)
            CellXYZ(+1, 0), //  右( 3時  方向)
            CellXYZ(+1, -1)  //右上( 1時半方向)
        });
    }
    // マス目の上下左右斜め 8方向 をvector配列として返す ★配列順 と enum定義順 を合わせてある
    inline std::vector<CellXYZ> getDirections8() const
    {   // moveで右辺値の所有権をそのまま { }の外へreturnで渡す https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
        return std::move(std::vector<CellXYZ> {
            CellXYZ(X, Y - 1, Z), //  上(12時  方向)
            CellXYZ(X - 1, Y - 1, Z), //左上(10時半方向)
            CellXYZ(X - 1, Y, Z), //  左( 9時  方向)
            CellXYZ(X - 1, Y + 1, Z), //左下( 7時半方向)
            CellXYZ(X, Y + 1, Z), //  下( 6時  方向)
            CellXYZ(X + 1, Y + 1, Z), //右下( 4時半方向)
            CellXYZ(X + 1, Y, Z), //  右( 3時  方向)
            CellXYZ(X + 1, Y - 1, Z)  //右上( 1時半方向)
        });
    }

    //6方向[3D] [constexprで実行前に固定値化して高速化]
    static constexpr const std::tuple<int, int, int> dir6[6] = { {0,-1,0},{-1,0,0},{0,+1,0},{+1,0,0},{0,0,-1},{0,0,+1} };

    // マス目の[2D]上下左右、[3D]上下 6方向 をvector配列として返す ★配列順 と enum定義順 を合わせてある
    static inline std::vector<CellXYZ> directions6()
    {   // moveで右辺値の所有権をそのまま { }の外へreturnで渡す https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
        return std::move(std::vector<CellXYZ> {
            CellXYZ(0, -1), //  上(12時  方向)
            CellXYZ(-1, 0), //  左( 9時  方向)
            CellXYZ(0, +1), //  下( 6時  方向)
            CellXYZ(+1, 0), //  右( 3時  方向)
            CellXYZ(0, 0, -1), // 下方向(Z方向)
            CellXYZ(0, 0, +1)  // 上方向(Z方向)
        });
    }

    // マス目の[2D]上下左右、[3D]上下 6方向 をvector配列として返す ★配列順 と enum定義順 を合わせてある
    inline std::vector<CellXYZ> getDirections6() const
    {   // moveで右辺値の所有権をそのまま { }の外へreturnで渡す https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
        return std::move(std::vector<CellXYZ> {
            CellXYZ(X, Y - 1, Z), //  上(12時  方向)
            CellXYZ(X - 1, Y, Z), //  左( 9時  方向)
            CellXYZ(X, Y + 1, Z), //  下( 6時  方向)
            CellXYZ(X + 1, Y, Z), //  右( 3時  方向)
            CellXYZ(X, Y, Z - 1), // 下方向(Z方向)
            CellXYZ(X, Y, Z + 1)  // 上方向(Z方向)
        });
    }

    //26方向[3D] [constexprで実行前に固定値化して高速化]
    static constexpr const std::tuple<int, int, int> dir26[26] = {
                 {0,-1, 0},{-1,-1, 0},{-1,0, 0},{-1,+1, 0},{0,+1, 0},{+1,+1, 0},{+1,0, 0},{+1,-1, 0},
        {0,0,-1},{0,-1,-1},{-1,-1,-1},{-1,0,-1},{-1,+1,-1},{0,+1,-1},{+1,+1,-1},{+1,0,-1},{+1,-1,-1},
        {0,0,+1},{0,-1,+1},{-1,-1,+1},{-1,0,+1},{-1,+1,+1},{0,+1,+1},{+1,+1,+1},{+1,0,+1},{+1,-1,+1},
    };

    // マス目の[2D]上下左右、[3D]上下 6方向 をvector配列として返す ★配列順 と enum定義順 を合わせてある
    static inline std::vector<CellXYZ> directions26()
    {   // moveで右辺値の所有権をそのまま { }の外へreturnで渡す https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
        return std::move(std::vector<CellXYZ> {
            CellXYZ(0, -1, 0), //  上(12時  方向)
            CellXYZ(-1, -1, 0), //左上(10時半方向)
            CellXYZ(-1, 0, 0), //  左( 9時  方向)
            CellXYZ(-1, +1, 0), //左下( 7時半方向)
            CellXYZ(0, +1, 0), //  下( 6時  方向)
            CellXYZ(+1, +1, 0), //右下( 4時半方向)
            CellXYZ(+1, 0, 0), //  右( 3時  方向)
            CellXYZ(+1, -1, 0), //右上( 1時半方向)

            CellXYZ(0, 0, -1), //[3D下]下方向(Z方向)
            CellXYZ(0, -1, -1), //[3D下]  上(12時  方向)
            CellXYZ(-1, -1, -1), //[3D下]左上(10時半方向)
            CellXYZ(-1, 0, -1), //[3D下]  左( 9時  方向)
            CellXYZ(-1, +1, -1), //[3D下]左下( 7時半方向)
            CellXYZ(0, +1, -1), //[3D下]  下( 6時  方向)
            CellXYZ(+1, +1, -1), //[3D下]右下( 4時半方向)
            CellXYZ(+1, 0, -1), //[3D下]  右( 3時  方向)
            CellXYZ(+1, -1, -1), //[3D下]右上( 1時半方向)

            CellXYZ(0, 0, +1), //[3D上]上方向(Z方向)
            CellXYZ(0, -1, +1), //[3D上]  上(12時  方向)
            CellXYZ(-1, -1, +1), //[3D上]左上(10時半方向)
            CellXYZ(-1, 0, +1), //[3D上]  左( 9時  方向)
            CellXYZ(-1, +1, +1), //[3D上]左下( 7時半方向)
            CellXYZ(0, +1, +1), //[3D上]  下( 6時  方向)
            CellXYZ(+1, +1, +1), //[3D上]右下( 4時半方向)
            CellXYZ(+1, 0, +1), //[3D上]  右( 3時  方向)
            CellXYZ(+1, -1, +1)  //[3D上]右上( 1時半方向)
        });
    }

    // マス目の[2D]上下左右、[3D]下反時計周り 上反時計回り 26方向 をvector配列として返す ★配列順 と enum定義順 を合わせてある
    inline std::vector<CellXYZ> getDirections26() const
    {   // moveで右辺値の所有権をそのまま { }の外へreturnで渡す https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
        return std::move(std::vector<CellXYZ> {
            CellXYZ(X, Y - 1, Z), //  上(12時  方向)
            CellXYZ(X - 1, Y - 1, Z), //左上(10時半方向)
            CellXYZ(X - 1, Y, Z), //  左( 9時  方向)
            CellXYZ(X - 1, Y + 1, Z), //左下( 7時半方向)
            CellXYZ(X, Y + 1, Z), //  下( 6時  方向)
            CellXYZ(X + 1, Y + 1, Z), //右下( 4時半方向)
            CellXYZ(X + 1, Y, Z), //  右( 3時  方向)
            CellXYZ(X + 1, Y - 1, Z), //右上( 1時半方向)

            CellXYZ(X, Y, Z - 1), //[3D下]下方向(Z方向)
            CellXYZ(X, Y - 1, Z - 1), //[3D下]  上(12時  方向)
            CellXYZ(X - 1, Y - 1, Z - 1), //[3D下]左上(10時半方向)
            CellXYZ(X - 1, Y, Z - 1), //[3D下]  左( 9時  方向)
            CellXYZ(X - 1, Y + 1, Z - 1), //[3D下]左下( 7時半方向)
            CellXYZ(X, Y + 1, Z - 1), //[3D下]  下( 6時  方向)
            CellXYZ(X + 1, Y + 1, Z - 1), //[3D下]右下( 4時半方向)
            CellXYZ(X + 1, Y, Z - 1), //[3D下]  右( 3時  方向)
            CellXYZ(X + 1, Y - 1, Z - 1), //[3D下]右上( 1時半方向)

            CellXYZ(X, Y, Z + 1), //[3D上]上方向(Z方向)
            CellXYZ(X, Y - 1, Z + 1), //[3D上]  上(12時  方向)
            CellXYZ(X - 1, Y - 1, Z + 1), //[3D上]左上(10時半方向)
            CellXYZ(X - 1, Y, Z + 1), //[3D上]  左( 9時  方向)
            CellXYZ(X - 1, Y + 1, Z + 1), //[3D上]左下( 7時半方向)
            CellXYZ(X, Y + 1, Z + 1), //[3D上]  下( 6時  方向)
            CellXYZ(X + 1, Y + 1, Z + 1), //[3D上]右下( 4時半方向)
            CellXYZ(X + 1, Y, Z + 1), //[3D上]  右( 3時  方向)
            CellXYZ(X + 1, Y - 1, Z + 1)  //[3D上]右上( 1時半方向)
        });
    }

    inline CellXYZ& operator += (const CellXYZ& other)
    {
        this->X = this->X + other.X;
        this->Y = this->Y + other.Y;
        this->Z = this->Z + other.Z;
        return *this;
    }

    inline CellXYZ operator -= (const CellXYZ& other)
    {
        this->X = this->X - other.X;
        this->Y = this->Y - other.Y;
        this->Z = this->Z - other.Z;
        return *this;
    }

    enum class DistType : unsigned char//[距離の種類] https://datachemeng.com/use_of_distance/
    {
        None = 0,
        Manhattan, // マンハッタン距離(都市のブロック単位の距離、京都の碁盤の目の距離)
        Euclidian, // ユークリッド距離(√直線距離:三平方の定理の距離)
        Euclidian2, // ユークリッド距離(2乗直線距離:ルート計算を省いて高速化)
        Chebyshev, // チェビシェフ距離(縦か横か高さの差のどれか一番大きい値)
    };

    // マス目距離を求める Aスターアルゴリズムのヒューリスティックコストなどに使われる
    inline float distance(const CellXYZ& cell, DistType type = DistType::Manhattan)
    {
        if (type == DistType::Manhattan)
            return (float)(std::abs(cell.X - X) + std::abs(cell.Y - Y) + std::abs(cell.Z - Z));
        else if (type == DistType::Euclidian || type == DistType::Euclidian2)
        {
            float dist2 = (float)((cell.X - X) * (cell.X - X) + (cell.Y - Y) * (cell.Y - Y) + (cell.Z - Z) * (cell.Z - Z));
            if (type == DistType::Euclidian2)
                return dist2; // 2乗のまま返す(√計算コスト省く)
            else
                return std::sqrt(dist2); // √距離
        }
        else // <windows.h>をインクルードするとmaxという名前の関数マクロが定義され、std::max()と衝突してしまうので(std::max)(a, b);のように関数名をカッコで囲んで使用 https://cpprefjp.github.io/reference/algorithm/max.html
            return (float)(std::max)({ std::abs(cell.X - X), std::abs(cell.Y - Y), std::abs(cell.Z - Z) }); // チェビシェフ距離 3変数のmax https://moxtsuan.hatenablog.com/entry/2014/12/01/235957
    }
};

inline CellXYZ operator + (const CellXYZ& left, const CellXYZ& right)
{
    return std::move(CellXYZ{ left.X + right.X, left.Y + right.Y, left.Z + right.Z });
}

inline CellXYZ operator - (const CellXYZ& left, const CellXYZ& right)
{
    return std::move(CellXYZ{ left.X - right.X, left.Y - right.Y, left.Z - right.Z });
}

// CellXYZに追加機能として原点(0,0,0)からの距離を事前計算する機能を追加
struct CellDistXYZ
{
    union {
        struct { int X, Y, Z; };
        CellXYZ XYZ;
    };
    int distpow2{ 0 }; // 原点(0,0,0)からの距離の2乗

    operator CellXYZ& () { return XYZ; } // CellXYZ型への変換オペレータ

    CellDistXYZ() = default;
    inline ~CellDistXYZ() {};
    inline CellDistXYZ(int X, int Y, int Z = 0) : X{ X }, Y{ Y }, Z{ Z }
    {
        distpow2 = X * X + Y * Y + Z * Z; // 原点(0,0,0)からの距離比較のため事前初期計算
    };

    // ★連想配列mapのキーで使うため < 比較演算子(連想配列並べ替えのため)をカスタム定義 http://programming-tips.info/use_struct_as_key_of_map/cpp/index.html
    inline bool operator < (const CellDistXYZ& other) const {
        //★原点からの距離distpow2順になるようにするとstd::mapにいれれば自動で原点からの距離順にならぶ(敵の近い順に使える)
        if (this->distpow2 < other.distpow2) return true;
        if (this->Z < other.Z) return true; // Z優先並べ替え(std::map並び順ではZの大きいものが一番あとに[並び順]X行⇒Y列⇒Zのエクセル順)
        else if (this->Z > other.Z) return false;
        if (this->Y < other.Y) return true; // Zが同じ時はYを比較 Z Y の順に並べばCSVのように左上から列→行の順にならぶ
        else if (this->Y > other.Y) return false;
        if (this->X < other.X) return true; // Z,Yが同じ時はXを比較
        else if (this->X > other.X) return false;
        return false; // X と Y と Z がすべて等しい場合
    }

    // 一致演算子 == std::unordered_map のキーで使うため == 比較演算子カスタム定義 https://sygh.hatenadiary.jp/entry/2014/02/13/222356
    inline bool operator == (const CellXYZ& other) const {
        return this->X == other.X && this->Y == other.Y && this->Z == other.Z;
    }
    // 不一致演算子 !=
    inline bool operator != (const CellXYZ& other) const {
        return !(this->operator==(other));
    }
    inline size_t GetHashCode() const { return XYZ.GetHashCode(); }

    struct Hash // ハッシュ値取得用の関数オブジェクト(std::unordered_map のキーで使うため) https://sygh.hatenadiary.jp/entry/2014/02/13/222356
    {
        inline size_t operator ()(const CellDistXYZ& cell) const { return cell.XYZ.GetHashCode(); }
    };

    inline CellDistXYZ& operator += (const CellXYZ& other)
    {
        this->XYZ += other;
        this->distpow2 = X * X + Y * Y + Z * Z; // 原点(0,0,0)からの距離比較のため
        return *this;
    }
    inline CellDistXYZ& operator += (const CellDistXYZ& other)
    {
        this->XYZ += other.XYZ;
        this->distpow2 = X * X + Y * Y + Z * Z; // 原点(0,0,0)からの距離比較のため
        return *this;
    }

    inline CellDistXYZ& operator -= (const CellXYZ& other)
    {
        this->XYZ -= other;
        this->distpow2 = X * X + Y * Y + Z * Z; // 原点(0,0,0)からの距離比較のため
        return *this;
    }
    inline CellDistXYZ& operator -= (const CellDistXYZ& other)
    {
        this->XYZ -= other.XYZ;
        this->distpow2 = X * X + Y * Y + Z * Z; // 原点(0,0,0)からの距離比較のため
        return *this;
    }

    // ムーブコンストラクタ distpow2を再計算せずにそのまま代入 https://cpprefjp.github.io/lang/cpp11/rvalue_ref_and_move_semantics.html
    inline CellDistXYZ(CellDistXYZ&& other) noexcept : XYZ{ other.XYZ }, distpow2{ other.distpow2 } {}
};


inline CellDistXYZ operator + (const CellDistXYZ& left, const CellDistXYZ& right)
{   // ムーブコンストラクタで { }内 の一時変数 を いちいち無駄にコピーせず { }外 へそのまま所有権ごと移動させるので { }内 のデータは消えずに { }外へ
    return std::move(CellDistXYZ{ left.X + right.X,left.Y + right.Y,left.Z + right.Z });
}

inline CellDistXYZ operator - (const CellDistXYZ& left, const CellDistXYZ& right)
{   // ムーブコンストラクタで { }内 の一時変数 を いちいち無駄にコピーせず { }外 へそのまま所有権ごと移動させるので { }内 のデータは消えずに { }外へ
    return std::move(CellDistXYZ{ left.X - right.X,left.Y - right.Y,left.Z - right.Z });
}

// 空間をグリッドでマス目として分割する
template<typename TypeT>
class CellGrid
{
public:
    int cellWidth, cellHeight, cellDepth; // マス目セルのサイズ(当たり判定の半径よりは大きくないとあかん)

    // CellXYZをキーにそのマス目にあるTypeTを得る辞書配列
    std::unordered_map<CellXYZ, TypeT, CellXYZ::Hash> cellData;

    inline CellGrid(int cellWidth = 64, int cellHeight = 64, int cellDepth = 64)
        : cellWidth{ cellWidth }, cellHeight{ cellHeight }, cellDepth{ cellDepth }
    {
        clear();
    }
    virtual ~CellGrid()
    {
        clear();
    }
    virtual int cellX(float worldX)
    {   // マイナス方向にも対応
        return (worldX >= 0) ? (int)worldX / cellWidth : (int)worldX / cellWidth - 1;
    }
    virtual int cellY(float worldY)
    {   // マイナス方向にも対応
        return (worldY >= 0) ? (int)worldY / cellHeight : (int)worldY / cellHeight - 1;
    }
    virtual int cellZ(float worldZ)
    {   // マイナス方向にも対応
        return (worldZ >= 0) ? (int)worldZ / cellDepth : (int)worldZ / cellDepth - 1;
    }
    virtual float centerX(int cellX)
    {   // マイナス方向にも対応
        return (cellX >= 0) ? (float)cellX * cellWidth + cellWidth / 2 : (float)(cellX + 1) * cellWidth - cellWidth / 2;
    }
    virtual float centerY(int cellY)
    {   // マイナス方向にも対応
        return (cellY >= 0) ? (float)cellY * cellHeight + cellHeight / 2 : (float)(cellY + 1) * cellHeight - cellHeight / 2;
    }
    virtual float centerZ(int cellZ)
    {   // マイナス方向にも対応
        return (cellZ >= 0) ? (float)cellZ * cellDepth + cellDepth / 2 : (float)(cellZ + 1) * cellDepth - cellDepth / 2;
    }
    inline TypeT top(float worldX, float worldY, float worldZ = 0) // マス目に入っている先頭の要素へのポインタを得る
    {
        return top(cellX(worldX), cellY(worldY), cellZ(worldZ));
    }
    inline TypeT top(int cellX, int cellY, int cellZ = 0) // マス目に入っている先頭の要素へのポインタを得る
    {
        return top(CellXYZ(cellX, cellY, cellZ));
    }
    inline TypeT top(const CellXYZ& searchXYZ) { // マス目に入っている先頭の要素を得る
        if (cellData.count(searchXYZ) == 0) {
            auto ret = cellData[searchXYZ]; //TypeTのデフォルトコンストラクタを走らせる
            cellData.erase(searchXYZ); // 即消す
            return ret;
        }
        return cellData[searchXYZ];
    }
    inline int cellID(int cellX, int cellY, int cellZ = 0) // マス目番号IDを返す(見つからない場合は-1が返る)
    {
        CellXYZ searchXYZ(cellX, cellY, cellZ);
        if (cellData.count(searchXYZ) == 0) return -1; // 見つからない場合は-1が返る
        return top(searchXYZ).posID;
    }
    inline int cellID(const CellXYZ& searchXYZ)  // マス目番号IDを返す(見つからない場合は-1が返る)
    {
        if (cellData.count(searchXYZ) == 0) return -1; // 見つからない場合は-1が返る
        return top(searchXYZ).posID;
    }
    inline TypeT& operator()(float worldX, float worldY, float worldZ = 0) { // マス目に入っている先頭の参照を得る
        return cellData[CellXYZ(cellX(worldX), cellY(worldY), cellZ(worldZ))];
    }
    inline TypeT& operator()(int cellX, int cellY, int cellZ = 0) { // マス目に入っている先頭の参照を得る
        return cellData[CellXYZ(cellX, cellY, cellZ)];
    }

    inline TypeT& operator[](const CellXYZ& searchXYZ) { // マス目に入っている先頭の参照を得る
        return cellData[searchXYZ];
    }

    struct LineZ {//★ []オペレータの数珠つなぎで[][][]3つ四角で3次元配列のようにアクセスできるように
        CellGrid* parent{ nullptr };
        int X, Y;
        inline LineZ(int X, int Y, CellGrid* parent) : X{ X }, Y{ Y }, parent{ parent } {};
        inline TypeT& operator[](int Z) { // マス目に入っている先頭の参照を得る
            return (*parent)[CellXYZ(X, Y, Z)];
        }
        inline operator TypeT& () { // [][]の2つ[]のときはZ無し辞書にアクセスして返す
            return (*parent)[CellXYZ(X, Y)];
        }
        inline TypeT& operator = (TypeT other) { // 代入 [X][Y] = ~;の時の挙動
            return (*parent)[CellXYZ(X, Y)] = other;
        }
    };
    struct LineY {//★ []オペレータの数珠つなぎで[][][]3つ四角で3次元配列のようにアクセスできるように
        CellGrid* parent{ nullptr };
        int X;
        inline LineY(int X, CellGrid* parent) : X{ X }, parent{ parent } {};
        inline LineZ operator[](int Y) { // []オペレータの数珠つなぎで[][][]3つ四角で3次元配列のようにアクセスできるように
            return LineZ(X, Y, parent);
        }
    };
    inline LineY operator[](int X) { // マス目に入っている要素へ[][][]3つ四角でアクセスできるように
        return LineY(X, this);
    }

    virtual void clear(CellXYZ* targetXYZ = nullptr)
    {
        if (targetXYZ == nullptr) {
            std::vector<CellXYZ> cells;
            for (auto&& cell : cellData)
                cells.emplace_back(cell.first);

            for (auto&& cell : cells)
                clear(&cell);

            cellData.clear(); //格子をクリア
            //std::unordered_map<CellXYZ, TypeT, CellXYZ::Hash>().swap(cellData); // 空のテンポラリオブジェクトでリセット https://stackoverflow.com/questions/42114044/how-to-release-unordered-map-memory/42115076
        }
        // 継承してTypeTクラスがリンクリスト構造ならoverrideして数珠つなぎのリストをクリアしたほうがいい(そのままだと先頭しかclearされないから)
        /*else if (targetXYZ != nullptr && cellData.count(*targetXYZ) != 0)
            cellData[*targetXYZ]->clearLinkList();*/
    };


    virtual void Update() {}; // Update仮想関数
    virtual void Draw() {}; // Draw仮想関数

    // マス目の上下左右4方向をvector配列として返す
    inline std::vector<CellXYZ> getDirections4(const CellXYZ& center, bool ifExists = false) // ifExists = trueにすると存在しているときだけget
    {
        std::vector<CellXYZ> directions;
        for (auto&& cell : center.getDirections4())
        {
            if (ifExists == true && cellData.count(cell) > 0)
                directions.emplace_back(cell);
        }
        return directions;
    }
    // マス目の上下左右斜め方向をvector配列として返す
    inline std::vector<CellXYZ> getDirections8(const CellXYZ& center, bool ifExists = false) // ifExists = trueにすると存在しているときだけget
    {
        std::vector<CellXYZ> directions;
        for (auto&& cell : center.getDirections8())
        {
            if (ifExists == true && cellData.count(cell))
                directions.emplace_back(cell);
        }
        return directions;
    }
    //[3D] マス目の上下左右 と 3D上下 方向をvector配列として返す
    inline std::vector<CellXYZ> getDirections6(const CellXYZ& center, bool ifExists = false) // ifExists = trueにすると存在しているときだけget
    {
        std::vector<CellXYZ> directions;
        for (auto&& cell : center.getDirections6())
        {
            if (ifExists == true && cellData.count(cell))
                directions.emplace_back(cell);
        }
        return directions;
    }
    //[3D] マス目の[2D]上下左右斜め 8方向 と [3D下]反時計回り9方向 と [3D上]反時計回り9方向 計26方向をvector配列として返す
    inline std::vector<CellXYZ> getDirections26(const CellXYZ& center, bool ifExists = false) // ifExists = trueにすると存在しているときだけget
    {
        std::vector<CellXYZ> directions;
        for (auto&& cell : center.getDirections26())
        {
            if (ifExists == true && cellData.count(cell))
                directions.emplace_back(cell);
        }
        return directions;
    }
};

#endif


なかなか長いですが、
ようは「何もないマス目の情報をもたないためにはマス目のハッシュ値をキーにして辞書で管理したい」ということです。
例えば、普通の辞書を考えてみてください。
辞書のア行にアイスという言葉は登録されていますが「あああああ」という言葉は登録されていませんよね。
あたりまえのように聞こえるかもしれませんが、それをマス目に入れ替えるとどうでしょう。
空間のほとんどにはキャラはおらず「マス目は空」ですよね。
なのにこれをもしstd::vector<std::vector<std::vector<Chara>>>型の3次元配列で管理しようとするとどうでしょう。
ほとんどの[2][3][6]の位置にだけキャラがいたとして、その他の[z][y][x]のマス目のほとんどは「キャラがいないのにデータとしてはメモリを食う」ことになります。
これは「あああ」「ああい」「ああう」「ああえ」など意味のないキーのために辞書のページを無駄づかいしているようなものです。
ハッシュ値をキーにした辞書なら、キャラのいないマス目はそもそも辞書にはのっていない = ページ無駄がない ということが実現できます。
上記のCellXYZでは Hash を定義して、
CellGridクラスのメンバのstd::unordered_map<CellXYZ, TypeT, CellXYZ::Hash> cellData;ではそのハッシュをキーとした辞書を持っています。
そして、CellGridクラスの下記の関数
    virtual int cellX(float worldX)
    {   // マイナス方向にも対応
        return (worldX >= 0) ? (int)worldX / cellWidth : (int)worldX / cellWidth - 1;
    }
上記のworldX / cellWidth の割り算で例えば cellWidthが10だとすると、x = 92.0の位置は 92 / 10 = 9番のマス目の位置に変換されます。
(マイナスのときは、x = -12.0の位置は -12 / 10 - 1 = -2番、x = -4.0の位置は -4 / 10 - 1 = -1番 の位置に変換されます←int型割り算の余り-4は切り捨て)
マス目は、ようするにfloat型のワールド位置をint型にキャストして、マス目のサイズで割って(余りは切り捨て)、int型のマス目番号に変換するというのがポイントですね。
逆にいうと、マス目のサイズ(縦と横と高さ)が確定しないと、ワールド位置をマス目の番号に変換することはできないということです。


では、次はCellGridに入れるオブジェクト、GridObjectを作成していきましょう。

GridObject.hを新規作成して、マス目にいれるオブジェクトのクラスを準備しましょう。

#ifndef GRID_OBJECT_H_
#define GRID_OBJECT_H_

#include "CellXYZ.h"
#include "GameObject.h"

class GridObject; // 前方宣言

// GridObjectに特化したGridをCellGridを継承して定義
class Grid : public CellGrid<GridObject*>
{
public:
    // 継承overrideしてGridObjectのリンクリスト構造を頭から最後まで全部クリアする
    virtual void clear(CellXYZ* targetXYZ = nullptr) override;

    Grid(int cellWidth = 64, int cellHeight = 64, int cellDepth = 64)
        : CellGrid(cellWidth, cellHeight, cellDepth)
    {
        clear();
    }
    virtual ~Grid()
    {
        clear();
    }
};


// GameProgramingPatternsの第20章:空間分割を参考に独自実装(コンポーネント化、マイナス空間も対応)
// https://www.amazon.co.jp/gp/offer-listing/4844338900/ref=tmm_pap_used_olp_0?ie=UTF8&condition=used

// グリッドに入れるオブジェクト、前と後のオブジェクトへのリンクを数珠繋ぎリンクリスト構造で持つことができる
class GridObject
{
public:
    inline GridObject(Grid* pGrid = nullptr) : m_pGrid{ pGrid } {}
    virtual ~GridObject() { erase(); }

    union {
        CellXYZ beforeXYZ{ 0,0,0 }; // 1フレーム前のマス目位置
        struct { int X, Y, Z; };
    };

    // 前と次への双方向のポインタ矢印を持つ数珠繋ぎリンクリスト構造
    GridObject* m_pPrev{ nullptr }; //前の点ノードに戻る矢印ポインタ
    GridObject* m_pNext{ nullptr }; //次の点ノードに進む矢印ポインタ

    Grid* m_pGrid{ nullptr }; // GridObject自身が入っているグリッドへのリンク
   
    // override して グリッドの隣接グリッド探索などの処理を実装する
    virtual void checkCell()
    {
        // override して グリッドの隣接グリッド探索などの処理を実装してください
    }

    // リンク構造のリセットとm_pGridへのリンクのクリア(ドミノ倒し的に全部リセット)
    virtual void clearLinkList()
    {
        GridObject* next = m_pNext; // eraseでpNextのリンクが消える前にリンクを一時退避
        erase();
        m_pGrid = nullptr;
        if (next != nullptr) next->clearLinkList(); // 最初に取っておいた次のリンク先も連鎖的にクリアさせる(ドミノ倒し的に全部リセット)
    }
    // グリッドにthisを追加 (m_pGridのnowXYZのマス目位置に追加)
    virtual void add(CellXYZ* nowXYZ)
    {
        if (nowXYZ == nullptr) return;
        if (m_pGrid == nullptr) return;

        //↓リンクリスト構造のリストを使って先頭に追加する
        m_pPrev = nullptr; //先頭だから自分より前はない
        m_pNext = m_pGrid->top(*nowXYZ); // 次→grid配列の先頭に元から入っていたもの
        (*m_pGrid)[*nowXYZ] = this; // grid配列の先頭をthisにすげかえる
        if (m_pNext != nullptr) // 先頭に元から入っていたものがnullptrじゃなかったら
            m_pNext->m_pPrev = this; // this←元からの先頭(先頭に割り込んだので元の先頭の人の前矢印をthisへリンク)
        //↑ここまでの処理で数珠つなぎのリンクリスト構造になる

        beforeXYZ = *nowXYZ; // 一つ前のx,y,zの位置を保存して次に備える
    }
    // グリッドからthisを削除
    void erase(CellXYZ* nowXYZ = nullptr)
    {
        // リンクリスト構造からのリンク削除処理
        if (m_pPrev != nullptr)
            m_pPrev->m_pNext = m_pNext; // 前要素とのリンク削除
        if (m_pNext != nullptr)
            m_pNext->m_pPrev = m_pPrev; // 次要素とのリンク削除

        // デバッグ表示
        //if (nowXYZ == nullptr) printfDx("(%d,%d,%d)\n", beforeXYZ.X, beforeXYZ.Y, beforeXYZ.Z);

        if (nowXYZ == nullptr) nowXYZ = &beforeXYZ;
        //printfDx("(%d,%d,%d)\n", fromXYZ->X, fromXYZ->Y, fromXYZ->Z); // デバッグ表示
        if (m_pGrid == nullptr) return;

        if (m_pGrid->top(*nowXYZ) == this) // このグリッド(this)がリンクリスト構造の一番最初(top)だった場合はtopの置き換えが必要
        {
            if (m_pNext != nullptr) // リンクリスト構造の数珠つなぎの後ろ側(m_pNext)があるときには
                (*m_pGrid)[*nowXYZ] = m_pNext; // gridのマス目配列の先頭を thisのかわりに pNextで上書きしてthisを上書きで消す
            else // 数珠つなぎの後ろがいないときは
                (*m_pGrid).cellData.erase(*nowXYZ); // マス目辞書配列の目次から削除(辞書の目次数が多いと重くなりやすいから)
        }
    }

    virtual void Draw(GameObject* obj = nullptr)
    {
#ifdef _DEBUG
        if (m_pGrid == nullptr) return;

        int cellX = beforeXYZ.X; int cellY = beforeXYZ.Y; int cellZ = beforeXYZ.Z;

        float leftX = (float)cellX * m_pGrid->cellWidth;
        float topY = (float)cellY * m_pGrid->cellHeight;
        float leftTopZ = (float)cellZ * m_pGrid->cellDepth;
        float rightX = (float)(cellX + 1) * m_pGrid->cellWidth;
        float bottomY = (float)(cellY + 1) * m_pGrid->cellHeight;
        float rightBottomZ = (float)(cellZ + 1) * m_pGrid->cellDepth;
        unsigned int color = GetColor(0, 255, 255); // グリッドを 水色 で描画

        // 四角Boxで GridObjectが存在するグリッド を描画
        DxLib::DrawCube3D(VGet(leftX, topY, leftTopZ),
            VGet(rightX, bottomY, rightBottomZ),
            color, color, FALSE);
#endif
    }
};

#endif

ここで、GridObjectの重要なデータ構造「リンクリスト構造」について触れなければなりません。
    // 前と次への双方向のポインタ矢印を持つ数珠繋ぎリンクリスト構造
    GridObject* m_pPrev{ nullptr }; //前の点ノードに戻る矢印ポインタ
    GridObject* m_pNext{ nullptr }; //次の点ノードに進む矢印ポインタ
リンクリストは上記のように2つ「数珠(じゅず)つなぎ用」ポインタを持ちます。
数珠つなぎをイメージするために「左手を前に右手を後ろに出してみてください」
自分の後ろに人がいるときには、自分の右手(m_pNext) と 後ろの人の左手(m_pPrev) とをつなぎます。
ポインタをつなぐとは、
自分(this)の右手(m_pNext) this->m_pNext = 後ろの人のデータへのポインタ;
後ろの人(m_pNext)の左手(m_pPrev) m_pNext->m_pPrev = this 自分;
上記のように、2行で書きます。2行必要なのは自分の手に相手をつなぐ1行だけでなく、相手側の手から自分へのポインタをつなぐ1行も必要だからです。
このように「リンクリスト構造とは自分の前と後ろのポインタの手をつなぐ構造」です。

リンクリスト構造はstd::vectorなどの配列を別途用意していないのに「前から後ろへの順番 = 数珠(じゅず)繋ぎ」を持てるという強力なテクニックです。
そして、驚くべきことにリンクリスト構造なら「先頭の人を1人だけ辞書に登録してるだけなのに」「先頭の人からたどれば沢山の人を登録したのと実質同じ」であるということです。
さらに、どんなに沢山データがあるときでも「列から離れるときには右手と左手を離せばよいだけ[計算速度O(1)] 」です。

つまり「登録する辞書は全くかさばらないし、データ1つずつのつけ離しも高速」なわけです。
これは空間をグリッドで区切ったマス目を辞書としたときに、キャラをマス目位置を目次とした辞書に頻繁に入れたり出したりするのに非常に適しています。
敵キャラが一つのマス目に集中したとしても、辞書には先頭の敵キャラへのポインタリンクが1つ入っているだけで済みます

上記の性質から、リンクリスト構造は「プログラマが自由に自作できるべき基礎データ構造」として知られています。
つまり、今回はGridObjectというクラス名で作成しましたが、必要な別件に出くわせば別のクラス名で別途、自分で自作できるようになるべきデータ構造です。

GridObject.cppを新規作成して、グリッドのマス目辞書から指定したターゲットとなるGridObjectのリンクリスト数珠つなぎをclearLinkList()でリセットできるようにしましょう。

#include "GridObject.h"

// 継承overrideしてGridObjectのリンクリスト構造を頭から最後まで全部クリアする
void Grid::clear(CellXYZ* targetXYZ)
{
    if (targetXYZ == nullptr) // ターゲットの指定がないときは辞書cellDataに入っているすべてをclearにかける
    {
        std::vector<CellXYZ> cells;
        for (auto&& cell : cellData) // 一旦辞書cellDataに入っているマス目をすべてcellsに移す
            cells.emplace_back(cell.first);

        for (auto& targetCell : cells) // 辞書のすべてのマス目をループ
            clear(&targetCell); // 辞書のすべてのマス目をtargetXYZにすえて、clearをひとつひとつ実行(辞書を全部クリア)

        cellData.clear(); //辞書をクリアして目次が一つもない状態にする
    }
    else if (targetXYZ != nullptr && cellData.count(*targetXYZ) != 0) // ターゲットが辞書の目次で見つかったら
        cellData[*targetXYZ]->clearLinkList(); // 辞書に入っているGridObjectのリンクリストの数珠つなぎをドミノ倒し的にクリア
}

グリッドをクリアするときは、中に入っているGridObject同士の数珠つなぎもclearLinkList()で解消する必要があるわけですね。
なぜなら、辞書と数珠つなぎは形式としては別物のデータなので、上記clear関数のように連動させない限り、
辞書にはないのに数珠つなぎだけ残っちゃう可能性が出てきてしまうので。


続いては、コンポーネントとGridObjectを継承してGridComponentクラスを準備します。

GridComponent.hを新規作成して、オーナーが今いるグリッドと隣接するグリッドのマス目辞書の先頭のGridObjectから数珠つなぎにチェックできるようにしましょう。

#ifndef GRIDCOMPONENT_H_
#define GRIDCOMPONENT_H_

#include "Component.h"

#include <list>
#include <memory> // 共有ポインタ std::shared_ptr を使用する
#include <unordered_map>
#include <string>
#include <assert.h> // OnCheck()を継承先でoverrideしていないことの警告用

#include "CellXYZ.h"
#include "GameObject.h"

#include "Camera.h"

#include "GridObject.h"
#include "Collider.h"


// GameProgramingPatternsの第20章:空間分割を参考に独自実装(コンポーネント化、マイナス空間も対応)
// https://www.amazon.co.jp/gp/offer-listing/4844338900/ref=tmm_pap_used_olp_0?ie=UTF8&condition=used

// 空間分割の機能をコンポーネントとして継承。[弾×敵]や[プレイヤ×敵]などを作り分けられる設計に
class GridComponent : public GridObject, public Component
{
public:
    GridComponent(std::shared_ptr<GameObject> pOwner, const std::string& typeTag, CellXYZ::DirType dirType = CellXYZ::DirType::Direction8)
        : Component(pOwner), dirType{ dirType },
        GridObject((pOwner->world() == nullptr) ? nullptr : pOwner->world()->makeGrid()) // グリッドをmakeGridで初期生成
    {
        this->typeTag = typeTag;
        this->baseTag = "GridComponent";
        this->beforeXYZ = CellXYZ(m_pGrid->cellX(pOwner->x), m_pGrid->cellY(pOwner->y), m_pGrid->cellZ(pOwner->z));
        add(); // 初期化と同時にgridに追加
    };
    virtual ~GridComponent() { erase(); }

    //GridManager& grids = GridManager::GetInstance();
    CellXYZ::DirType dirType; // 当たり判定でたどる周りのセルのタイプ: [2D]4方向,8方向,[3D]6方向,26方向

    // 継承overrideしてコライダがあるときはそのリストを返す
    virtual std::list<std::weak_ptr<Collider>>* colliders() { return nullptr; }

    // グリッドにthisを追加
    virtual void add(CellXYZ* nowXYZ = nullptr) override
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        CellXYZ nowCellXYZ(m_pGrid->cellX(pOwner->x), m_pGrid->cellY(pOwner->y), m_pGrid->cellZ(pOwner->z));
        if (nowXYZ == nullptr) nowXYZ = &nowCellXYZ;

        GridObject::add(nowXYZ); // 基底ベースクラスのaddを呼び出す
    }

    // 移動時のグリッドのマス目の隣への移動処理
    void moveCell(CellXYZ* nowXYZ = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) // オーナーがすでにメモリから解放済のときは
        {
            erase(); // オーナーがすでにメモリから解放済のときは リンクリストから削除して return
            return;
        }

        //移動先のセル(マス目)の指定 nowXYZ がnullptrで指定がなかったら現在オーナーのいる位置を nowXYZに指定する
        CellXYZ nowCellXYZ(m_pGrid->cellX(pOwner->x), m_pGrid->cellY(pOwner->y), m_pGrid->cellZ(pOwner->z));
        if (nowXYZ == nullptr) nowXYZ = &nowCellXYZ;

        if (beforeXYZ == *nowXYZ) return; // 移動しても同一のマス目なら即return

        erase(&beforeXYZ); // 前のマス目の位置でのリンクリストから一旦削除

        add(nowXYZ); // 今のマス目位置(nowXYZ)の先頭に thisを割り込みでaddで追加する
    }

    // 隣接するグリッドのレンジ内にいてcheck関数がよばれたときに実行したい処理をoverrideして実装する
    virtual void OnCheck(GridComponent* other)
    {
        assert("OnCheckを継承したクラスでoverrideして独自の当たり判定処理などを実装しないと意味ないよ(例.GridCollisionComponent.hを見てください)" == "");
    }

    // リンクリストの数珠つなぎを順にたどってOnCheck関数を呼ぶ
    inline void checkLinkList(GridObject* other)
    {
        while (other != nullptr) // while文で数珠つなぎのm_pNextを次々とたどって最後にたどり着くと次がなくてnullptrになる
        {
            if (this != other) // otherが自分自身のときは自分自身どうしのチェックはしない
                OnCheck((GridComponent*)other); // OnCheckを継承したクラスで書き分けることで[弾×敵]や[プレイヤ×敵]などを作り分けられる設計に
            other = other->m_pNext; // リンクリスト構造の次をwhileで次々とたどる
        }
    }
    // グリッドのマス目内と隣接するマス目内に格納されている要素(敵や弾)のチェック
    // 実際のチェック処理はOnCheck関数でやる。隣接するマス目しか調べないので高速
    void checkCell() override
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) // オーナーがすでにメモリから解放済のときは
        {
            erase(); // オーナーがすでにメモリから解放済のときは リンクリストから削除して return
            return;
        }

        // オーナーのいるグリッドのマス目辞書から先頭を取り出し、その同一グリッドにいるオブジェクトをチェックする
        auto center = (GridComponent*)m_pGrid->top(pOwner->x, pOwner->y, pOwner->z);
        if (center == nullptr) return;

        this->checkLinkList(center); // 中心centerの同一マスに他にもオブジェクトがある場合にそれらのオブジェクトをチェックする

        int cellX = m_pGrid->cellX(pOwner->x);
        int cellY = m_pGrid->cellY(pOwner->y);
        int cellZ = m_pGrid->cellZ(pOwner->z);

        size_t iSize = 4;
        auto* dir = &CellXYZ::dir4[0]; //[2D] 隣接 4方向グリッド調べる constexpr で高速化を狙う
        if (dirType == CellXYZ::DirType::Direction8) { iSize = 8; dir = &CellXYZ::dir8[0]; } //[2D] 隣接 8方向グリッド調べる
        else if (dirType == CellXYZ::DirType::Direction6) { iSize = 6; dir = &CellXYZ::dir6[0]; } //[3D] 隣接 6方向グリッド調べる
        else if (dirType == CellXYZ::DirType::Direction26) { iSize = 26; dir = &CellXYZ::dir26[0]; } //[3D] 隣接 26方向グリッド調べる

#define DIR_X( i )    std::get<0>(dir[i]) // std::get<0>(dir[i]) と書くより少し見やすく DIR_X(i) と書けるように
#define DIR_Y( i )    std::get<1>(dir[i])
#define DIR_Z( i )    std::get<2>(dir[i])
       
        for (size_t i = 0; i < iSize; ++i) //隣接するグリッドを先頭から数珠つなぎにチェックして調べる
            this->checkLinkList(m_pGrid->top(cellX + DIR_X(i), cellY + DIR_Z(i), cellZ + DIR_Y(i)));

#undef DIR_X
#undef DIR_Y
#undef DIR_Z

    }

    virtual void Update(GameObject* obj = nullptr) override
    {
        for (auto& colider : *colliders())
            if (auto pCollider = colider.lock())
                pCollider->UpdateIsCollision(); // 衝突情報を一旦リセット(m_isPrevCollisionとm_isCollisionなどをリセット)

        moveCell(); // 所属するマス目セルを更新する(現在のオーナーがいるマス目位置のグリッドに移動)
    };

    virtual void Draw(GameObject* obj = nullptr) override
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (pOwner->world() != nullptr) // コンポーネントを所有するオーナーのいるワールドのカメラすべてに描く
            pOwner->world()->Draw([&]()
                {   // 四角Boxで GameObjectが存在するグリッド を描画
                    GridObject::Draw(obj);
                });
    };
};

#endif

隣接するグリッド(設定:[2D]4方向、8方向、[3D]6方向、26方向)に入っているGridObjectのOnCheck関数しか呼ばれなくなるので、
広大なフィールドにぽつぽつと離れているキャラ同士の当たり判定などムダな判定はスキップして高速化が望めます。
例えば、フィールドにぼつぼつ離れて1万個キャラがいるとき、隣接グリッド以外をスキップしないと、総当たりだと1万×1万=1億回の判定になってしまいます

では次はOnCheck関数で呼ぶ当たり判定用のコライダーを準備しましょう。

Collider.hを新規作成して、Collision3D.hのカプセルや球などの当たり判定を呼んで衝突を判定しましょう。

#ifndef COLLIDER_H_
#define COLLIDER_H_

#include "Component.h"
#include <memory> // 共有ポインタ std::shared_ptr を使用する
#include <unordered_set> // std::unordered_setで衝突している他のコライダのIDを保管してかぶらないようにする

#include "GameObject.h"
#include "Collision3D.h"

class Collider; // 前方宣言

//[衝突情報:参考] https://docs.unity3d.com/jp/2018.4/ScriptReference/Collision.html
class Collision
{    // 衝突はGameObjectに所有されているComponent同士の衝突により生成される 衝突後、発生元のGameObjectはCollisionと関係なく消えてしまうかもしれない
protected:
    std::weak_ptr<GameObject> m_pGameObject; // 衝突を発生させたGameObjectへのリンクを保管する(GameObjectはすでに消えているかもしれないから弱リンクに)
public:
    Collision(std::weak_ptr<GameObject> pGameObject) : m_pGameObject{ pGameObject } {}
    class ContactPoint //[衝突点の情報] https://docs.unity3d.com/jp/2018.4/ScriptReference/ContactPoint.html
    {
    public:
        ContactPoint() = default;
        ContactPoint(Vector3 point_, Vector3 normal_, float separation_, std::weak_ptr<Collider> thisCollider_, std::weak_ptr<Collider> otherCollider_)
            : point{ point_ }, normal{ normal_ }, separation{ separation_ }, thisCollider{ thisCollider_ }, otherCollider{ otherCollider_ } {}
        virtual ~ContactPoint() {}
        Vector3 point{ 0.0f,0.0f,0.0f }; // 接触点
        Vector3 normal{ 0.0f,0.0f,0.0f }; // 接触点の法線
        float separation{ 0.0f }; // 接触点でのコライダー間の距離
        std::weak_ptr<Collider> thisCollider{ std::weak_ptr<Collider>() }; //[衝突元]接触したコライダー情報
        std::weak_ptr<Collider> otherCollider{ std::weak_ptr<Collider>() }; //[衝突先] 接触している他のコライダー情報
    };
    std::vector<ContactPoint> contacts; // 全ての接触点の配列
    // 基本的にthis側のコライダーのオーナーのGameObjectを返す
    std::weak_ptr<GameObject> gameObject() { return m_pGameObject; };
};


class Collider; // 前方宣言

// 関数の前方宣言
inline bool isCollisionColliders(std::shared_ptr<Collider> pThis, std::shared_ptr<Collider> pOther,
    std::shared_ptr<Collision>* ppThisResult = nullptr, std::shared_ptr<Collision>* ppOtherResult = nullptr);

class Collider : public Component
{
public:
    // コライダの当たり判定のタイプ
    enum class Type : unsigned char
    {
        //Point = 0, // 点
        Sphere, // 球
        Capsule, // カプセル
        //Box, // ボックス
        //Plane, // 平面
    };

    inline Collider(std::shared_ptr<GameObject> pOwner, Type type) : Component(pOwner), m_type{ type } {}
    virtual ~Collider() {}


    const Type m_type; // コライダのタイプ
    inline operator Type () { return m_type; }

protected:
    bool m_isPrevCollision = false; // 前回衝突したか?
    bool m_isCollision = false; // 今回衝突したか?
public:
    bool isTrigger = false; // コライダーがトリガーですり抜けるかどうか?
    bool enabled = true; // コライダーが有効か?
    unsigned int hitColor = DxLib::GetColor(255, 255, 255); // 描画するときの色
    std::vector<Collision::ContactPoint> m_contacts; // 全ての接触点の配列
    std::unordered_set<unsigned int> m_prevCollisionIDs; // 前のフレームで衝突した他のコライダのInstanceID
    std::unordered_set<unsigned int> m_collisionIDs; // 接触している他のコライダのInstanceID
    bool IsPrevCollision() { return m_isPrevCollision; }
    void IsPrevCollision(bool isPrevCollision) { m_isPrevCollision = isPrevCollision; return; }
    bool IsCollision() { return m_isCollision; }
    //void IsCollision(bool isCollision) { m_isCollision = isCollision; return; }
    //  コライダのワールド座標位置(ownerをたどったワールド座標値)
    inline Vector3 worldPos(Vector3 localPosition) const { return owner()->position + localPosition; }

    // コライダの含まれるワールド位置のx,y,z最小値を求める(8分木のエリア分別などに使うため)
    virtual Vector3 minXYZ() const = 0; // = 0で純粋仮想関数にして必ずoverride継承しないとエラーになるようにする

    // コライダの含まれるワールド位置のx,y,z最大値を求める(8分木のエリア分別などに使うため)
    virtual Vector3 maxXYZ() const = 0; // = 0で純粋仮想関数にして必ずoverride継承しないとエラーになるようにする

    // 継承override してカプセルや球やボックスなどのコライダの判定を実装する デフォルトは isCollisionColliders関数を呼び出す
    virtual inline bool isCollision(std::shared_ptr<Collider> pOther,
        std::shared_ptr<Collision>* ppThisResult = nullptr, std::shared_ptr<Collision>* ppOtherResult = nullptr)
    {
        if (m_collisionIDs.count(pOther->InstanceID()) > 0)
            return false; // すでに調べたコライダのIDのペアだったらfalse
        // 継承override してカプセルや球やボックスなどのコライダの判定を実装する
        return isCollisionColliders(shared_from_this_as<Collider>(), pOther, ppThisResult, ppOtherResult);
    }

    // 毎フレーム isPrev(=m_isCollision)とisCollision(=false)に更新してから当たり判定する
    virtual void UpdateIsCollision() // 前フレームで当たってなくて今当たっていればOnCollisionEnterに使える
    {
        std::vector<Collision::ContactPoint>().swap(m_contacts); // 衝突点情報の配列をクリア
        // 今のフレームで衝突しているIDを↓prevに移しておけば次のフレームでのOnCollisionEnterやOnCollisionExitを呼ぶタイミングの判定に使える
        m_collisionIDs.swap(m_prevCollisionIDs);
        std::unordered_set<unsigned int>().swap(m_collisionIDs); // 衝突していたID一覧は、一旦空()と入れ替えて今フレームの衝突検査に備える
        m_isPrevCollision = m_isCollision; // 前のフレームで衝突したかを保管
        m_isCollision = false; // 一旦、今のフレームでは衝突していない状態として設定して後で当たり判定して書換
    }

};

class SphereCollider : public Collider
{
public:
    SphereCollider(std::shared_ptr<GameObject> pOwner, Vector3 center, float radius, unsigned int hitColor_ = GetColor(255, 0, 0))
        : Collider(pOwner, Type::Sphere), m_center{ center }, m_radius{ radius }
    {
        this->hitColor = hitColor_;
    }
    virtual ~SphereCollider() {}

protected:
    Vector3 m_center{ 0.0f,0.0f,0.0f }; // コライダのローカル座標での中心位置
public:
    // 球体の半径
    float m_radius{ 0.0f };
    // 球体のローカル座標での中心座標
    inline Vector3 center() const { return m_center; }
    //  球体のワールド座標での中心位置(ownerをたどったワールド座標値)
    inline Vector3 worldCenter() const
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        return pOwner->position + center();
    }
    // 球体の含まれるワールド位置のx,y,z最小値を求める(8分木のエリア分別などに使うため)
    inline Vector3 minXYZ() const override
    {
        Vector3 center = worldCenter();
        return Vector3{ (m_radius < 0) ? center.x + m_radius : center.x - m_radius,
                        (m_radius < 0) ? center.y + m_radius : center.y - m_radius,
                        (m_radius < 0) ? center.z + m_radius : center.z - m_radius };
    }
    // 球体の含まれるワールド位置のx,y,z最大値を求める(8分木のエリア分別などに使うため)
    inline Vector3 maxXYZ() const override
    {
        Vector3 center = worldCenter();
        return Vector3{ (m_radius < 0) ? center.x - m_radius : center.x + m_radius,
                        (m_radius < 0) ? center.y - m_radius : center.y + m_radius,
                        (m_radius < 0) ? center.z - m_radius : center.z + m_radius };
    }

    // デバッグ用に球体コライダを描く
    virtual void Draw(GameObject* obj = nullptr) override
    {
#ifdef _DEBUG
        if (owner() == nullptr) return; // このコンポーネントを所有しているオーナーがすでにメモリから解放されていたら return
        Vector3 center = worldCenter();
        DxLib::DrawSphere3D(center, m_radius, 2, (m_isCollision) ? hitColor : GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);
        if (m_isCollision)
        {
            for (auto& contact : m_contacts) // 衝突点と法線を描く
            {
                DxLib::DrawSphere3D(contact.point, 1.0f, 3, hitColor, GetColor(255, 255, 255), TRUE);
                DxLib::DrawLine3D(contact.point, contact.point + contact.normal, GetColor(255, 255, 0));
            }
        }
#endif
    }
};

class CapsuleCollider : public Collider
{
public:
    CapsuleCollider(std::shared_ptr<GameObject> pOwner, Vector3 center, float radius, float height, Axis3D direction = Axis3D::Y, unsigned int hitColor_ = GetColor(255, 0, 0))
        : Collider(pOwner, Type::Capsule), m_center{ center }, m_radius{ radius }, m_height{ height }, m_direction{ direction }
    {
        this->hitColor = hitColor_;
    }
    virtual ~CapsuleCollider() {}

protected:
    Vector3 m_center{ 0.0f,0.0f,0.0f }; // コライダのローカル座標での中心位置
    float m_height{ 1.0f }; // コライダの高さ
    Axis3D m_direction{ Axis3D::Y };
public:
    // カプセルの半径
    float m_radius{ 0.0f };
    // カプセルのローカル座標での中心座標
    inline Vector3 center() const { return m_center; }
    //  ワールド座標での中心位置(ownerをたどったワールド座標値)
    inline Vector3 worldCenter() const
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        return pOwner->position + center();
    }
    // カプセルの高さ
    inline Vector3 height() const { return m_height; }
    // カプセルの向く方向軸(height高さの方向軸)
    inline Axis3D direction() const { return m_direction; }

    // カプセルの始点(pOwnerから見たローカル座標値)
    inline Vector3 start() const
    {
        return m_center - Vector3{ (m_direction == Axis3D::X) ? m_height / 2.0f : 0.0f,
                                   (m_direction == Axis3D::Y) ? m_height / 2.0f : 0.0f,
                                   (m_direction == Axis3D::Z) ? m_height / 2.0f : 0.0f };
    }

    //  カプセルの始点(ownerをたどったワールド座標値)
    inline Vector3 worldStart() const
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        return pOwner->position + start();
    }

    //  カプセルの終点(ownerをたどったワールド座標値)
    inline Vector3 worldEnd() const
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        return pOwner->position + end();
    }

    // カプセルの終点(pOwnerから見たローカル座標値)
    inline Vector3 end() const
    {
        return m_center + Vector3{ (m_direction == Axis3D::X) ? m_height / 2.0f : 0.0f,
                                   (m_direction == Axis3D::Y) ? m_height / 2.0f : 0.0f,
                                   (m_direction == Axis3D::Z) ? m_height / 2.0f : 0.0f };
    }

    // カプセルの含まれるワールド位置のx,y,z最小値を求める(8分木のエリア分別などに使うため)
    inline Vector3 minXYZ() const override
    {
        Vector3 start = worldStart();
        Vector3 end = worldEnd();
        return Vector3{ (start.x < end.x) ? start.x - m_radius : end.x - m_radius,
                        (start.y < end.y) ? start.y - m_radius : end.y - m_radius,
                        (start.z < end.z) ? start.z - m_radius : end.z - m_radius };
    }
    // カプセルの含まれるワールド位置のx,y,z最大値を求める(8分木のエリア分別などに使うため)
    inline Vector3 maxXYZ() const override
    {
        Vector3 start = worldStart();
        Vector3 end = worldEnd();
        return Vector3{ (start.x > end.x) ? start.x + m_radius : end.x + m_radius,
                        (start.y > end.y) ? start.y + m_radius : end.y + m_radius,
                        (start.z > end.z) ? start.z + m_radius : end.z + m_radius };
    }

    // デバッグ用にカプセルコライダを描く
    virtual void Draw(GameObject* obj = nullptr) override
    {
#ifdef _DEBUG
        if (owner() == nullptr) return; // このコンポーネントを所有しているオーナーがすでにメモリから解放されていたら return
        Vector3 start = worldStart();
        Vector3 end = worldEnd();
        DxLib::DrawCapsule3D(start, end, m_radius, 4, (m_isCollision) ? hitColor : GetColor(255, 255, 255), GetColor(255, 255, 255), FALSE);
        if (m_isCollision)
        {
            for (auto& contact : m_contacts) // 衝突点と法線を描く
            {
                DxLib::DrawSphere3D(contact.point, 0.1f, 3, hitColor, GetColor(255, 255, 255), TRUE);
                DxLib::DrawLine3D(contact.point, contact.point + contact.normal, GetColor(255, 255, 0));
            }
        }
#endif
    }
};

// カプセル同士が衝突しているかを判定する(ppThisResultとppOtherResult:衝突したら衝突情報が返される)
inline bool isCollisionCapsule_Capsule(std::shared_ptr<Collider> pThis0, std::shared_ptr<Collider> pOther1,
    std::shared_ptr<Collision>* ppThisResult = nullptr, std::shared_ptr<Collision>* ppOtherResult = nullptr)
{
    auto gameObject0 = pThis0->owner(), gameObject1 = pOther1->owner();
    if (gameObject0 == nullptr || gameObject1 == nullptr) return false;
    if (gameObject0->isStatic || gameObject1->isStatic) return false; // どちらも静的オブジェクト(isStatic)の場合は当たり判定しない

    float radius0 = 0, radius1 = 0;
    Vector3 start0, end0, start1, end1, result0, result1, center0, center1;
    // col0はthis
    if (*pThis0 == Collider::Type::Capsule)
    {
        auto col0 = std::static_pointer_cast<CapsuleCollider>(pThis0);
        start0 = col0->worldStart(), end0 = col0->worldEnd(), center0 = col0->worldCenter();
        radius0 = col0->m_radius;
    }
    else if (*pThis0 == Collider::Type::Sphere)
    {    // 球体のときはstartとendが両方、球の中心にすればカプセルの当たり判定を使いまわせる
        auto col0 = std::static_pointer_cast<SphereCollider>(pThis0);
        start0 = col0->worldCenter(), end0 = col0->worldCenter(), center0 = col0->worldCenter();
        radius0 = col0->m_radius;
    }
    // col1はother
    if (*pOther1 == Collider::Type::Capsule)
    {
        auto col1 = std::static_pointer_cast<CapsuleCollider>(pOther1);
        start1 = col1->worldStart(), end1 = col1->worldEnd(), center1 = col1->worldCenter();
        radius1 = col1->m_radius;
    }
    else if (*pOther1 == Collider::Type::Sphere)
    {    // 球体のときはstartとendが両方、球の中心にすればカプセルの当たり判定を使いまわせる
        auto col1 = std::static_pointer_cast<SphereCollider>(pOther1);
        start1 = col1->worldCenter(), end1 = col1->worldCenter(), center1 = col1->worldCenter();
        radius1 = col1->m_radius;
    }

    // Collision3D.hの当たり判定関数を使ってチェック
    bool isCollision = isCollisionCapsule_Capsule(start0, end0, radius0, start1, end1, radius1, &result0, &result1);
    if (!isCollision) return false;

    // collisionの 法線ベクトル:norm距離:mag を求める
    Vector3 v01 = result1 - result0; // 最短距離の2点 result0 から result1 へ向かうベクトル
    float mag01 = v01.magnitude(); // result0とresult1の距離
    Vector3 norm01 = v01.normalized(); // 正規化normalizeする
    if (mag01 == 0.0f) // カプセルの軸が交差しているときはresult0とresult1が同じ点になっちゃう
    {
        norm01 = (center1 - center0).normalized(); // 中心同士の差ベクトルを法線に
        if (center0 == center1) // 中心位置が完全一致するときは
            norm01 = Vector3{ 1.0f,0.0f,0.0f }; // 仕方ないからx軸方向に引き離す(2DでもつじつまあうようにX軸で)
    }

    pThis0->m_collisionIDs.emplace(pOther1->InstanceID()); // 接触している other のIDを this側 に登録して同じペアの判定を2度しないようにする
    pOther1->m_collisionIDs.emplace(pThis0->InstanceID()); // 接触している this のIDを other側 に登録して同じペアの判定を2度しないようにする

    // 衝突点 pThisContactとpOtherContact を生成
    Collision::ContactPoint thisContact{ result0, -norm01, mag01, pThis0, pOther1 };
    Collision::ContactPoint otherContact{ result1,  norm01, mag01, pOther1, pThis0 };
    pThis0->m_contacts.emplace_back(thisContact); // コライダ0のm_contactsにも情報登録
    pOther1->m_contacts.emplace_back(otherContact); // コライダ1のm_contactsにも情報登録

    if (ppThisResult != nullptr)
    {
        *ppThisResult = std::make_shared<Collision>(gameObject0); // this側の衝突点をpResultでreturnする
        (*ppThisResult)->contacts.emplace_back(thisContact); //[0] this側の衝突点の情報をcontacts配列に格納
        (*ppThisResult)->contacts.emplace_back(otherContact); //[1] other側の衝突点の情報をcontacts配列に格納
    }
    if (ppOtherResult != nullptr)
    {
        *ppOtherResult = std::make_shared<Collision>(gameObject1); // other側の衝突点をpResultでreturnする
        (*ppOtherResult)->contacts.emplace_back(otherContact); //[0] other側の衝突点の情報をcontacts配列に格納
        (*ppOtherResult)->contacts.emplace_back(thisContact); //[1] this側の衝突点の情報をcontacts配列に格納
    }

    return true;
}

// コライダー同士の当たり判定 当たったら衝突情報が ppThisResult,ppOtherResult がnullptrじゃなければ代入されて返却
inline bool isCollisionColliders(std::shared_ptr<Collider> pThis, std::shared_ptr<Collider> pOther,
    std::shared_ptr<Collision>* ppThisResult, std::shared_ptr<Collision>* ppOtherResult)
{
    // 現状はカプセルと球だけの当たり判定しか実装していない
    if ((*pThis == Collider::Type::Sphere || *pThis == Collider::Type::Capsule)
        && (*pOther == Collider::Type::Sphere || *pOther == Collider::Type::Capsule))
    {    // カプセルの当たり判定で球の当たり判定も兼用する
        return isCollisionCapsule_Capsule(pThis, pOther, ppThisResult, ppOtherResult);
    }

    // ここにカプセルと球以外の当たり判定も追加すればボックスなどとの当たり判定もできるようになる

    return false;
}

// 衝突したカプセル同士が当たらないようにコライダがついているGameObjectを押し戻してずらして当たらない位置に移動
inline bool resolveCollisionCapsule_Capsule(const Collision::ContactPoint& thisContact, const Collision::ContactPoint& otherContact)
{
    //[共有ポインタのキャスト] https://cpprefjp.github.io/reference/memory/shared_ptr/dynamic_pointer_cast.html
    auto thisCollider = std::dynamic_pointer_cast<CapsuleCollider>(thisContact.thisCollider.lock());
    auto otherCollider = std::dynamic_pointer_cast<CapsuleCollider>(otherContact.thisCollider.lock());
    auto thisGameObject = thisCollider->owner();
    auto otherGameObject = otherCollider->owner();
    if (!thisGameObject || !otherGameObject || !thisCollider || !otherCollider)
        return false; // どれかがnullptrのときはfalse
    if (thisGameObject->isStatic && otherGameObject->isStatic)
        return false; // どちらも静的オブジェクト(isStatic)の場合は当たり判定を押し戻ししない
    Vector3 p0 = thisContact.point;
    Vector3 p1 = otherContact.point;
    if (p0 == p1) // カプセル0とカプセル1の軸が交差しているとき
    {    // カプセルの中心位置を使って引き離す
        p0 = thisCollider->worldPos(thisCollider->center());
        p1 = otherCollider->worldPos(otherCollider->center());
    }
    // 押し戻しの量を計算
    float overlap = (thisCollider->m_radius + otherCollider->m_radius) - (p1 - p0).magnitude() + 0.00001f;
    // 押し戻し方向に移動
    Vector3 correction = thisContact.normal * (overlap / 2.0f); // 2で割って両方±動いて当たらないようにする
    if (otherGameObject->isStatic) // 片方がisStaticなら↓2倍動かしてもう片方はそのまま
        thisGameObject->position += correction * 2.0f;
    else if (thisGameObject->isStatic)
        otherGameObject->position += -correction * 2.0f;
    else
    {    // 両方isStaticじゃないときは * 2.0fせずに両方移動させる
        thisGameObject->position += correction;
        otherGameObject->position += -correction;
    }
    return true;
}


#endif


では次は同一グリッドにある他のコライダーと当たり判定するためのコンポーネントを準備しましょう。

GridCollidersComponent.hを新規作成して、m_colliders変数に複数のコライダをLinkCollider関数でリンクして、OnCheck関数をoverrideしてコライダ同士の当たり判定をcppで実装できるようにしましょう。

#ifndef GRID_COLLIDERS_COMPONENT_H_
#define GRID_COLLIDERS_COMPONENT_H_

#include "GridComponent.h"
#include <list>
#include <memory> // 共有ポインタ std::shared_ptr を使用する

class Collider; // 前方宣言

class GridCollidersComponent : public GridComponent
{
    friend Component;
    std::list<std::weak_ptr<Collider>> m_colliders; // GridComponentを所有するGameObjectが持つコライダーのリスト
public:
    GridCollidersComponent(std::shared_ptr<GameObject> pOwner, const std::string& typeTag, CellXYZ::DirType dirType = CellXYZ::DirType::Direction8)
        : GridComponent(pOwner, typeTag, dirType)
    {
    }

    // コライダのリスト(弱共有ポインタ)へのポインタを返す
    virtual std::list<std::weak_ptr<Collider>>* colliders() override { return &m_colliders; }

    // コライダの弱共有ポインタを グリッドで当たり判定する対象として リンクを リスト m_colliders に追加する
    virtual void LinkCollider(std::weak_ptr<Collider> pColliderLink) { m_colliders.emplace_back(pColliderLink); }

    // OnCheckをoverrideすることで[弾×敵]や[プレイヤ×敵]などの当たり判定処理をCollision3D.hの関数でする
    virtual void OnCheck(GridComponent* other) override;
};

#endif



ではcppファイルのOnCheck関数でCollision3D.hのisCollision関数で当たり判定をしてGameObjectのOnCollision関数に衝突結果を引き渡してコールバックしましょう。

GridCollidersComponent.cppを新規作成して、Collider.hのisCollision関数経由でCollision3D.hの各種当たり判定を衝突結果をGameObjectのOnCollision関数に渡しましょう。

#include "GridCollidersComponent.h"

#include "Collider.h"

void GridCollidersComponent::OnCheck(GridComponent* other)
{
    auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
    if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

    // コンポーネントのオーナーが死んでたらスキップする
    //if (pOwner->isDead) return;
    //if (other->owner()->isDead) return;

    for (auto& pCollider : this->m_colliders) // this側のコライダすべてをループ
    {
        auto collider = pCollider.lock(); // 弱共有ポインタをロックして共有ポインタに変換
        if (collider == nullptr) continue; // 共有ポインタが削除されすでに解放済の場合
        for (auto& pOtherCollider : *(other->colliders()))
        {
            auto otherCollider = pOtherCollider.lock(); // 弱共有ポインタをロックして共有ポインタに変換
            if (otherCollider == nullptr) continue; // 共有ポインタが削除されすでに解放済の場合

            std::shared_ptr<Collision> pThisResult{ nullptr }; // 衝突したら情報が入ってきてnullptrじゃなくなる
            std::shared_ptr<Collision> pOtherResult{ nullptr }; // 衝突したら情報が入ってきてnullptrじゃなくなる
            if (collider->isCollision(otherCollider, &pThisResult, &pOtherResult))
            {
                if (collider->m_prevCollisionIDs.count(otherCollider->InstanceID()) == 0)
                {   //[OnEnter]前のフレームでcolliderがotherと衝突してなくて、今フレームで衝突開始したらOnEnterを呼ぶ
                    collider->owner()->OnCollisionEnter(pThisResult); // collider側の衝突開始したときの関数をコールバックで呼び返す
                    otherCollider->owner()->OnCollisionEnter(pOtherResult); // other側の衝突開始したときの関数をコールバックで呼び返す
                }
               
                //[OnCollision] 衝突していたらコールバックを呼び続ける
                collider->owner()->OnCollision(pThisResult); // collider側の衝突したときの関数をコールバックで呼び返す
                otherCollider->owner()->OnCollision(pOtherResult); // other側の衝突したときの関数をコールバックで呼び返す
            }
            else // 衝突していなかったら
            {
                //[OnExit]前のフレームでcolliderがotherと衝突していて今フレームで衝突しなくなったらOnExitを呼ぶ
                if (collider->m_prevCollisionIDs.count(otherCollider->InstanceID()) > 0)
                    collider->owner()->OnCollisionExit(pThisResult); // collider側の衝突終了したときの関数をコールバックで呼び返す
            }
        }
    }
}


GameObject.hを変更して、OnCollisionなどの衝突判定のコールバック関数を準備しましょう。

#ifndef GAMEOBJECT_H_
#define GAMEOBJECT_H_

#include <memory> // 共有ポインタの定義のため

#include "Vector3.h"
#include "Quaternion.h"

#include "Object.h" // インスタンスIDや検索タグを持ち、shared_from_this_as<継承先クラス名>などができる

class World; // 前方宣言
class Component; // 前方宣言
class Collision; // 前方宣言

// ゲーム上に表示される物体の基底クラス。
// プレイヤーや敵、アイテムなどはこのクラスを継承して作る。
class GameObject : public Object
{
protected:
    // コンストラクタはprotected:にしてあるのでCreate関数経由で初期生成してください
    GameObject(World* world = nullptr, Vector3 position = { 0,0,0 }, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : Object(), m_world{ world }, position{ position }, rotation{ rotation }, velocity{ velocity }, force{ force }
    {

    }

public:
    // 仮想デストラクタ
    virtual ~GameObject() {}


    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    }; // unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    Quaternion rotation; // 回転クオータニオン

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

    // 重力を返す、重力を変えたいときはoverrideして処理を変えてください、重力を変えてない場合は 0.0f
    virtual inline float gravity() { return 0.0f; }

protected:
    World* m_world; // GameObjectが配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }
   
    bool isDead{ false }; // 死んだ(削除対象)フラグ
    bool isStatic{ false }; // 動かない(静的オブジェクトか?)
    bool isGround{ false }; // 地面についているか
    bool isJumping{ false }; // ジャンプ中か
    bool isFlying{ false }; // 空をとんでいるか

    float pushPower{ 5.0f }; // コライダで接触したときに押し返す力(相手側が強いと押される)
    float mass{ 1.0f }; // 質量(重さ)コライダで接触したときに重さでforceを割り算すると動きにくくなる


    hash32 typeTag{ "" }; // 小カテゴリ Zako0など個別のタイプ
    hash32 baseTag{ "" }; // 大カテゴリ Enemyなど大まかなベースジャンル

protected:
    // コンポーネントの<ハッシュID, 共有所有ポインタ> の辞書でコンポーネントの共有カウンタ+1を保持する
    std::unordered_map<size_t, std::shared_ptr<Component>> components;
public:
    // コンポーネントを得る 例えば GetComponent<BoxCollider>でBoxColliderという名前のコンポーネントをcomponents辞書から得る
    template<class ComponentT>
    std::vector<std::weak_ptr<ComponentT>> GetComponents()
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        std::vector<std::weak_ptr<ComponentT>> returnList; // "ComponentT" 例."CircleCollider"など をキーとするコンポーネント一覧
        for (auto ite = range.first; ite != range.second; ++ite)
            returnList.emplace_back(ite->second); // 辞書で見つかったコンポーネントを返すリストに追加
        return returnList;
    }

    // コンポーネントを追加する .AddComponent<BoxCollider>(...)で追加できる
    template<class ComponentT, typename ...ArgsT>
    std::weak_ptr<ComponentT> AddComponent(ArgsT&&...args)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]

        // shared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        std::shared_ptr<ComponentT> newComponent = std::make_shared<ComponentT>(shared_from_this_as<GameObject>(), std::forward<ArgsT>(args)...);
       
        // https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        // std::unrodered_multimap<> と同型の値を取る std::pair<> を作って insert 関数でcomponents辞書に追加
        components.insert(std::pair<size_t, std::shared_ptr<ComponentT>>(hashID, newComponent));
        return newComponent;
    }

    // コンポーネントを削除する
    template<typename ComponentT>
    bool RemoveComponent(std::shared_ptr<ComponentT> removeComponent)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/

        //[イテレート中に探しながら消す] https://cpprefjp.github.io/reference/unordered_map/unordered_multimap/erase.html
        for (auto ite = range.first; ite != range.second;)
            if (ite->second == removeComponent)
            {
                components.erase(ite); // 削除された要素の次を指すイテレータが返される
                return true; // 見つかって消せた場合は true
            }
            else
                ++ite; // 次の要素へイテレータを進める

        return false; // 見つからずに消せなかった場合は false
    }

    // 更新処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Update() = 0; // 純粋仮想関数 = 0 にすると【絶対 Update()関数はoverrideしなきゃいけない義務を付与できる 】

    // 描画処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Draw() = 0; // 純粋仮想関数 = 0 にすると【絶対 Draw()関数はoverrideしなきゃいけない義務を継承先クラスに付与 】

    // 衝突開始したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollisionEnter(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突開始したときの処理などをオブジェクトの種類ごとに実装する
    };

    // 衝突したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollision(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    };

    // 衝突終了したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollisionExit(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突終了したときの処理などをオブジェクトの種類ごとに実装する
    };


    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump()
    {
        // overrideしてジャンプした瞬間に音を鳴らしたりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中でジャンプの上昇中に呼ばれる関数
    virtual void OnJumping()
    {
        // overrideして空中でジャンプの上昇中にエフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中で落ちている最中に呼ばれる関数
    virtual void OnFalling()
    {
        // overrideして空中で落ちている最中に音を鳴らしたり、エフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

};

#endif



World.hを変更して、makeGridで世界にグリッド分割情報を持たせられるようにしましょう(デフォルト設定で128×128×128の大きさのグリッドにする)。

#ifndef WORLD_H_
#define WORLD_H_

#include <list>
#include <memory>
#include <unordered_map>
#include <functional> // std::functionのラムダ式 で 関数を Draw関数の引数にして、カメラすべてに対して描画処理を発動させる

#include "MyHash.h" // ワールドのタグをhash32型で管理する


// [前方宣言] 宣言だけして、#includeはしてないので、ポインタだけはこのファイル内でつかえる
// #includeはしてないので、hpなどの変数には#include "~.h"しないとアクセスできないので循環インクルード防止のため.cppでインクルードしてください
class GameScene;
class Player;
class Bullet;
class Enemy;
class Camera;
class Grid;

// プレイヤや敵や弾などの共有ポインタを管理し、EraseRemoveIfで消して参照カウンタが0になったらそれらを消滅させるクラス(C#のガベージコレクタの代わり)
class World
{
public:
    inline World(GameScene* pScene, const std::string& worldTag = "") : m_pScene{ pScene }, tag{ worldTag } {}
    inline ~World() {}
protected: // public:にすると他で外部で後からタグやシーンを書き換えられると辞書での管理などの前提がおかしくなるのでprotected:する
    hash32 tag{ "" }; // ワールド名のタグ
    GameScene* m_pScene{ nullptr }; // ワールドを所有するシーン
public:
    hash32 Tag() const { return tag; }
    GameScene* scene() { return m_pScene; }

    //std::unordered_map<hash32, std::shared_ptr<Player>> players; // プレイヤのタグ辞書
    // ワールドを所有するシーンにプレイヤを追加する
    void AddPlayer(const std::string& playerTag, std::shared_ptr<Player> pPlayer);
    // ワールドにいるプレイヤを得る
    std::vector<std::weak_ptr<Player>> GetPlayers();

protected:
    std::shared_ptr<Grid> m_pGrid{ nullptr }; // 当たり判定を高速にするためグリッドで空間分割
public:
    Grid* grid() { return m_pGrid.get(); } // グリッドを返す
    Grid* makeGrid(int cellWidth = 128, int cellHeight = 128, int cellDepth = 128); // グリッドを生成する


    std::unordered_map<hash32, std::shared_ptr<Player>> players; // プレイヤのタグ辞書

    std::list<std::shared_ptr<Bullet>> bullets; // 弾のリスト

    std::list<std::shared_ptr<Enemy>> enemies; // 敵のリスト

    // 削除処理を共通テンプレート関数にする
    // [共通テンプレート関数]https://programming-place.net/ppp/contents/cpp/language/009.html#function_template
    template <typename TypeT, class T_if>
    void EraseRemoveIf(std::list<TypeT>& v, T_if if_condition)
    {   //            特定のタイプT↑  ↑配列v   ↑条件式if_condition
        v.erase(
            std::remove_if(v.begin(), v.end(), if_condition),
            v.end() //  ↓remove_ifの位置
        );//例.[生][生][死][死][死]← v.end()の位置
    };

    std::unordered_map<hash32, std::shared_ptr<Camera>> cameras; // このワールドに存在するカメラの辞書<カメラにつけたタグ, カメラの共有ポインタ>
    // 指定されたタグのカメラへの共有ポインタを得る カメラ辞書に存在しないタグでアクセスしたときにunordered_mapの特性で勝手に意図しないカメラができるのを防ぐ関数
    std::shared_ptr<Camera> camera(const std::string& cameraTag) { auto itr = cameras.find(cameraTag); return (itr != end(cameras)) ? itr->second : nullptr; }
    // このワールドに存在するカメラすべてに対して drawFuncで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
    void Draw(std::function<void()> drawFunc) noexcept;
    // カメラにタグをつけてワールドに追加 カメラにもワールドへのポインタリンクを張る
    inline bool AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera);
    // 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
    inline bool EraseCamera(const std::string& cameraTag);
    // カメラを nowWorld から nextWorld に移動させる
    static bool MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld);

};

#endif


World.cppを変更して、ワールドごとにmakeGrid関数でGridをstd::make_sharedで生成する処理を記述しましょう。

#include "World.h"

#include "Player.h"
#include "Camera.h"
#include "Screen.h"

#include "GameScene.h"

#include "GridObject.h" // Gridをmake_sharedで生成するため

Grid* World::makeGrid(int cellWidth, int cellHeight, int cellDepth)
{
    if (this->m_pGrid == nullptr) // グリッドが未生成の場合は生成
        this->m_pGrid = std::make_shared<Grid>(cellWidth, cellHeight, cellDepth);
    return this->m_pGrid.get();
}


void World::AddPlayer(const std::string& playerTag, std::shared_ptr<Player> pPlayer)
{
    if (m_pScene != nullptr)
    {
        m_pScene->players.emplace(playerTag, pPlayer); // シーンにプレイヤを追加する
    }
}

// このワールドにいるプレイヤを得る
std::vector<std::weak_ptr<Player>> World::GetPlayers()
{
    std::vector<std::weak_ptr<Player>> players;
    for (auto&& pPlayer : players)
    {
        players.emplace_back(pPlayer);
    }
    return players;
}

// カメラにタグをつけてワールドに追加 カメラにもワールドへの生ポインタリンクを渡す
bool World::AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera)
{
    if (pCamera == nullptr) return false;
    if (cameras.count(cameraTag) > 0) return false; // すでに同じタグのカメラがワールドにあったら false
    if (pCamera->world() != nullptr) pCamera->world()->EraseCamera(cameraTag); // すでにカメラが別ワールドに配置されていたら別ワールドから削除
    cameras.emplace(cameraTag, pCamera); // ワールドにカメラを追加する
    pCamera->world(this); // カメラにもワールドへのポインタリンクを張る(カメラ側に渡すのは共有ポインタではない生ポインタだから共有デッドロックにはならない)
    return true;
}

// 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
bool World::EraseCamera(const std::string& cameraTag)
{
    auto itr = cameras.find(cameraTag); // 指定したタグのカメラを探す
    if (itr == end(cameras)) return false; // 指定したタグのカメラがワールドにない
    auto pCamera = itr->second;
    pCamera->world(nullptr); // カメラ側の worldへのリンクのポインタも nullptrに
    cameras.erase(cameraTag); // カメラを辞書から削除

    return true;
}

// カメラを nowWorld から nextWorld に移動させる
bool World::MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld)
{
    if (nowWorld == nullptr) return false;
    auto itr = nowWorld->cameras.find(cameraTag); // タグでカメラを探す
    if (itr == end(nowWorld->cameras)) return false; // タグに関連づいたカメラがなかった
    auto targetCamera = itr->second; // カメラの共有ポインタをキープ(次の行でeraseしてもカメラの共有カウンタは0にはならずこのリンクのおかげでカメラはメモリに残存)
    targetCamera->world(nullptr); // カメラ側の worldへのリンクのポインタを一旦 nullptrに
    nowWorld->cameras.erase(cameraTag); // nowWorld からは カメラを消す
    if (nextWorld == nullptr) return false;

    return nextWorld->AddCamera(cameraTag, targetCamera); // 次のワールドにカメラを追加する
}

// このワールドに存在するカメラすべてに対して funcで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
void World::Draw(std::function<void()> drawFunc) noexcept
{
    for (auto&& keyValue : cameras)    // このワールドにあるすべてのカメラぶんループ
    {
        VECTOR_D beforeCamPos; // 設定変更前のカメラのパラメータを一旦、保管
        double beforeVRot, beforeHRot, beforeTRot, beforeNear, beforeFar, beforeFov;
        Camera::GetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);

        auto& camera = keyValue.second;
        //camera->screenID
        DxLib::SetCameraPositionAndAngle(camera->pos, camera->rot.V, camera->rot.H, camera->rot.T); // 各カメラの位置、回転を設定

        Screen::Start(camera->screenHandle, true, camera->fovAngle); // カメラに関連付けられたスクリーンIDを描画先にする
        {
            drawFunc(); // 渡された描画処理を実行
        }
        Screen::End(true); // 描画先を元に戻す

        // カメラの設定をもとに戻しておく
        Camera::SetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);
    }
}


Player.hを変更して、球体のコライダとグリッドのコンポーネントと押し返す力の設定を追加しましょう。

#ifndef PLAYER_H_
#define PLAYER_H_

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "GameObject.h"
#include "PhysicsComponent.h"
#include "GridCollidersComponent.h"
#include "Collider.h"


class World; // 前方宣言
class Camera; // 前方宣言

class Player : public GameObject
{
protected:
    // コンストラクタはprotected:にしてあるので、Create関数経由で初期生成してください
    Player(World* world, Pad pad, Vector3 position, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : pad{ pad }, GameObject(world, position, rotation, velocity, force)
    {
        this->typeTag = "Player"; // タグは個別の"プレイヤ1"などに使い、タイプはtypeTagで判別する形にしてタイプのジャンルを区別
        this->baseTag = "Player";
        this->pushPower = 5.0f; // コライダで衝突したときに押し返す力

    }

public:
    // 仮想デストラクタ
    virtual ~Player() {}

    Pad pad; // 操作するコントローラー番号
    hash32 worldTag{ "" }; // 現在いるワールドのタグ
    float MoveSpeedMax = 6; // 移動速度Max値
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // プレイヤの動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

    std::shared_ptr<Texture> image; // プレイヤの板ポリゴンで描くタイル分割画像への共有リンク

    Camera* camera{ nullptr }; // プレイヤを追随するカメラ
    void SetCamera(Camera* pCamera) { camera = pCamera; }

    // overrideして物理落下コンポーネントからの重力値を返す
    virtual float gravity() override { auto pGravityPhysics = gravityPhysics.lock(); return (pGravityPhysics != nullptr) ? pGravityPhysics->gravity() : 0.0f; }
protected:
    std::weak_ptr<JumpComponent> jumpPhysics; // ジャンプの物理処理コンポーネント
    std::weak_ptr<GravityComponent> gravityPhysics; // 落下の重力の物理処理コンポーネント

    std::weak_ptr<GridCollidersComponent> gridCollision; // グリッドマス目分割コンポーネント
    std::weak_ptr<SphereCollider> sphereCollider; // 球コライダのコンポーネント


public:
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        std::vector<float> vyJumpSpeed{ 13, 14, 15, 4 }; // ジャンプ開始の初期速度
        std::vector<float> vyForceJump{ 0.5f, 0.4f, 0.3f, 0.1f }; // ジャンプ上昇にかかる力の初期値
        std::vector<float> vyGravity{ 0.8f,0.8f,0.8f,0.8f }; // 重力の設定値
        std::vector<float> vyDownSpeedMax{ 16, 16, 16, 8 }; // 降下スピードのリミット
        // AddComponent内部でshared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        jumpPhysics = AddComponent<JumpComponent>(vyJumpSpeed, vyForceJump);
        gravityPhysics = AddComponent<GravityComponent>(vyGravity, vyDownSpeedMax);
        // 半径16(直径32)の球体コライダ
        sphereCollider = AddComponent<SphereCollider>(Vector3(0, 0, 0), 16);
        gridCollision = AddComponent<GridCollidersComponent>("Player"); //とりあえずわかりやすいように"Player"というタグをつけておく(別に何でもいい)
        gridCollision.lock()->LinkCollider(sphereCollider); // コライダをリンクする

    }

    // 入力を受けての処理
    virtual void HandleInput();

    // 更新処理
    virtual void Update() override;

    // 描画処理
    virtual void Draw() override;

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump() override;

    // コライダの衝突した際に呼ばれるコールバック関数
    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override;

};

#endif


Player.cppを変更して、OnCollision関数のpCollisionから得た衝突した法線方向を使ってUpdate関数で力を加えましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}

// ジャンプ開始した直後に呼ばれる関数
void Player::OnStartJump()
{
    this->isGround = false; // ジャンプ中はisGroundフラグがfalse
}


// 更新処理
void Player::Update()
{
    vxForce *= 0.9f; // かかっている力の減衰率
    vzForce *= 0.9f; // かかっている力の減衰率

    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    auto pJumpPhysics = jumpPhysics.lock();
    if (pJumpPhysics != nullptr)
    {
        if (isGround)
        {
            if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
                || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
            {
                pJumpPhysics->JumpStart(); // ジャンプのスタート処理を発動
            }
        }
        else if (vy > 0 && Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
            || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
        {
            pJumpPhysics->JumpCancel(); // ジャンプのキャンセル処理を発動
        }
        pJumpPhysics->Update(); // ジャンプのコンポーネントを更新する
    }
    auto pGravityPhysics = gravityPhysics.lock();
    if (pGravityPhysics != nullptr)
        pGravityPhysics->Update(); // 重力落下のコンポーネントを更新する

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    vx += vxForce; // 力をかけて速度に加算する
    vz += vzForce; // 力をかけて速度に加算する


    // 実際に位置を動かす

    // まず横に移動する
    x += vx;

    // 次に縦に動かす
    y += vy;

    // 次にZ奥行き方向に動かす
    z += vz;

    vy += vyForce; // 勢い(力・フォースを加算

    if (y <= 0.0f) // 0.0fを地面として 0.0f以下になったら地面に着地したと判定する
    {
        y = 0.0f; // 地面に沿わせる
        vy = -0.01f; // 下方向への速度をほぼ 0 にする
        isGround = true;
    }

    if (auto pGridCollision = gridCollision.lock()) // lock()してnullptrじゃなければ
        pGridCollision->Update(); // 所属するマス目セルを更新する


    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

void Player::OnCollision(std::shared_ptr<Collision> pCollision)
{
    if (isDead) return; // すでにisDeadなら return
    auto otherCollider = pCollision->contacts[0].otherCollider.lock(); // コライダの弱共有ポインタをロック
    if (otherCollider == nullptr) return; // すでにコライダが消去済だった
    auto other = otherCollider->owner();

    if (other->isDead) return; // other側がisDeadだったら処理をスルー

    if (other->baseTag == "Enemy" || other->baseTag == "Player")
    {
        auto norm = pCollision->contacts[0].normal * other->pushPower; // pushPowerの力でotherに押し返される
        force += -norm; // 衝突法線方向の力を加える
    }
}


// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

#ifdef _DEBUG // デバッグのときだけ_DEBUGが定義され描かれる
                if (auto pSphereCollider = sphereCollider.lock()) pSphereCollider->Draw(); // コライダを描く
#endif

            });

    }
}


GameScene.cppを変更して、プレイヤのコライダと所属するグリッドをデバッグ表示する処理を実装しましょう。

#include "GameScene.h"

#include "Player.h"

GameSceneSetting GameScene::settings{ "Map/GameSceneSetting.csv" }; // ゲームシーンの設定情報

// 指定したゲームシーンのタグに関連づいたファイル名に対応したワールドを初期化する
bool GameScene::LoadWorlds(const std::string& gameSceneTag)
{
    if (settings.info.count(gameSceneTag) == 0)
        return false;

    auto& info = settings[gameSceneTag];
    for (auto&& tag_mapPath : info)
    {
        CreateWorld(tag_mapPath.second.tag); // ワールドをタグをつけて生成
    }
    return true;
}

void GameScene::ChangeWorld(const std::string& worldTag, std::shared_ptr<Player> pPlayer)
{
    if (pPlayer == nullptr) return;
    if (worlds.count(worldTag) == 0) return; // 存在しないタグだった

    pPlayer->world(worlds[worldTag].get()); // ワールドへのリンクをすげ替える
}


void GameScene::Initialize()
{// Init処理

    // Zバッファを有効にする [Zの深度]
    //[参考]https://docs.google.com/presentation/d/1Z23t1yAS7uzPDVakgW_M02p20qt9DeTs4ku4IiMDLco/edit?usp=sharing
    //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
    DxLib::SetUseZBuffer3D(TRUE);
    // Zバッファへの書き込みを有効にする
    DxLib::SetWriteZBuffer3D(TRUE);
    // 光の明暗計算を無効に
    DxLib::SetUseLighting(FALSE);

    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);


    Resource::MakeShared<Texture>(mapChipPath, 8, 8, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath1, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath2, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする


    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    // プレイヤの生成
    if (pWorld != nullptr)
    {
        //pWorld->AddCamera("カメラ1", Camera::defaultCamera); // カメラ1というタグをデフォルトカメラにつけてワールドにデフォルトカメラを配置
        pWorld->AddCamera("カメラ1", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera1 = pWorld->camera("カメラ1"); // カメラ1 というタグのついたカメラへの共有ポインタを得る
        pCamera1->SetScreenHandle(screenHandle0); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer1 = Object::Create<Player>(nullptr, pWorld, Pad::Key, Vector3(90 + 256, 32, 95 + 256));
        pWorld->AddPlayer("プレイヤ1", pPlayer1); // シーンにプレイヤを追加する
        pPlayer1->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);

        pPlayer1->camera = pCamera1.get(); // カメラをプレイヤにリンクする


        pWorld->AddCamera("カメラ2", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera2 = pWorld->camera("カメラ2"); // プレイヤ2 というタグのついたカメラへの共有ポインタを得る
        pCamera2->SetScreenHandle(screenHandle1); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer2 = Object::Create<Player>(nullptr, pWorld, Pad::Two, Vector3(190, 32, 195));
        pWorld->AddPlayer("プレイヤ2", pPlayer2); // シーンにプレイヤを追加する
        pPlayer2->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);
       
        pPlayer2->camera = pCamera2.get(); // カメラをプレイヤにリンクする

    }

};


void GameScene::Update()
{// 更新処理

    Screen::ClearDrawScreen(screenHandle0); // 一旦キャンバスをきれいにまっさらに
    Screen::ClearDrawScreen(screenHandle1); // 一旦キャンバスをきれいにまっさらに

    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    for (const auto& player : players)
        player.second->Update(); // プレイヤの更新【忘れるとプレイヤが動かない】

    // グリッドを更新して隣接するグリッドに含まれる敵や弾の当たり判定を行う
    for (const auto& world : worlds)
        if (world.second->grid() != nullptr)
            for (const auto& cell : world.second->grid()->cellData)
            {
                auto pGridObject = cell.second;
                while (pGridObject != nullptr)
                {   // while文で数珠つなぎのm_pNextを次々とたどって最後にたどり着くと次がなくてnullptrになる
                    pGridObject->checkCell();
                    pGridObject = pGridObject->m_pNext; // リンクリスト構造を次々とたどる
                }
            }


}

void GameScene::Draw()
{// 描画処理
    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    for (const auto& player : players)
    {
        player.second->Draw(); // プレイヤの描画【忘れるとプレイヤ表示されない】

        auto world = player.second->world(); // プレイヤのいるワールド

        // プレイヤのいるワールドのカメラのみを描けば、プレイヤのいないワールドは描かずに済む
        // 複数のカメラを 範囲 for文 で回す
        for (auto&& camera : player.second->world()->cameras)
        {   // ラムダ式[&](){ ~ }で{}の外側の変数すべてを & キャプチャで&参照として「Draw関数の内側へ引き連れて」処理できる
            camera.second->Draw([&]()
                {
                    // Draw関数() の 外側にある変数 mapChipPath も[&]効果で { } の内側で問題なくアクセスできている↓

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 0);
                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 70, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 8);

                    if (camera.first == "カメラ1")
                        DxLib::DrawString(0, 0, "こちらがプレイヤ1", GetColor(255, 0, 0));
                    else if (camera.first == "カメラ2")
                        DxLib::DrawString(0, 0, "こちらはプレイヤ2", GetColor(0, 255, 0));
                });
        }
#ifdef _DEBUG  // デバッグのときだけ_DEBUGが定義され描画される
        if (world->grid() != nullptr)
            for (const auto& cell : world->grid()->cellData)
                cell.second->Draw(); // グリッドの描画
#endif

    }

    // スクリーン0の内容を画面に描く
    DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
    DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));

    // スクリーン1の内容を画面に描く
    DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
    DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));

}


いかがでしょう?プレイヤ1とプレイヤ2の球体コライダが表示され、プレイヤ1を操作してプレイヤ2にぶつかると跳ね返りましたでしょうか?
水色のグリッドは128×128×128で大きめにとってあります。
なぜなら、グリッドを2つ飛び越えるような大きなコライダだと、
今のまわりの隣接するグリッドを探す形ではグリッドを飛び越える大きさになると機能しなくなる弱点があるからです。
具体的に、256の半径のコライダが[●]にいたら[ ( ][   ][●][   ][ ) ] ←コライダの半径 ( ● ) の ) は2つ先のグリッドに及ぶので、
ほんとは2つ先のグリッドにいるキャラのコライダとも当たり判定しなければなりませんが、
今のグリッドの仕組みでは隣1つまでのグリッドしか調べません
なので、ボスなど大きいキャラでも、128+α ぐらいのサイズが今のグリッドの仕組みが正しく機能する限界となります。
8分木などを使うと、このような制約はなくなるので、8分木のほうが上位互換ではあります。

大変でしたが、これで一通りのゲームエンジンとしての基本機能がそろってきました。
コライダは最小限、球体とカプセルのみ準備しました。球体の当たり判定はカプセルの芯を点として同じ計算を流用しています。
これで球とカプセルのみではありますが、ある程度のゲームを作れる土台は整ってきたのではないかと思います。

物理が苦手な人は「位置(position)」「速度(velocity)」「力(force)」の関係について確認が必要です。
毎秒5メートル進む人がいるとします。
1秒ごとに 位置position += 5メートル。これはわかりやすいですね。
では、力(force)とは何なのか。
自転車を全力でこいで力を入れると、毎秒スピード速度が上がっていきます。
毎秒 5の力 で自転車をこぐ人がいるとします。
1秒ごとに 速度velocity += 5。実は「力」とは速度に毎秒(毎フレーム)いくつ足し算されるかなのです。
物理の色んな公式がわからなくても、これなら少しわかるぞ。と思ってもらえばラッキーです。
何のことはない、ブランコをこいでる人がいて、もう一人が後ろから来るたびに押すと段々ブランコが高くまで上がるアレと同じです。
ブランコを押す瞬間に += 5してるから、速度が大きくなって、当然ブランコも上に上がるようになるわけです。
Unityの AddForce の「force」とは実はこの「力」なのです。




以上が、中二学派(厨二学派)向けの物理学です。

中二学派(厨二学派)の物理学では不安だという方に向けて、
念のためニュートン学派(欧米か)のほうの式も以下に解説しておきましょう。

運動の第2法則 : F = m × v × Δt [つまり Force(力) = m(重さ) × velocitiy(速度) × Δt(時間の変化) ]

先ほどの中二学派の式と対応関係を取るために、Δt(時間の変化) = 1(フレーム) に固定しましょう。
そうすると、 ΔF(力の変化) = m(重さ) × Δv(速度の変化) になりますよね。 今度は、両辺を m(重さ)で割りましょう
m(重さ)で割ると ΔF(力の変化) / m(重さ) = Δv(速度の変化)になりました。中二学派と合わせるために右辺と左辺をひっくり返しましょう
右辺⇔左辺すると Δv(速度の変化) = ΔF(力の変化) / m(重さ)になりました。これであとは m(重さ) = 1に固定してみましょう。
m(重さ) = 1なら Δv(速度の変化) = ΔF(力の変化)になりましたね。これで += 5 で中二学派の式と同じにできます。
ΔF(力の変化) = 5なら、Δv(速度の変化) += 5 になりました。
これで「中二のアイザック・ニュートン」(ニュータイプ)が誕生しましたね。この物理の式ならガンダムのモビルスーツも動かせそうです。

じゃあ、m(重さ) = 1に固定しなかった場合、つまり、マリオカートのドンキーコングやクッパみたいに重いキャラはどうなるのでしょうか?
m(重さ) = 2 にしたとき、
Δv(速度の変化) = ΔF(力の変化) / m(重さ) なので当然、m(重さ) = 2 なら
Δv(速度の変化) += 5 / 2になりますね。
つまり、5 / 2 = 2.5 ずつしか速度が加速しないので、当然あんまりふっとばないようになりますね。
ニュートン風にまじめに計算したい人は、変数 m(質量) を定義して、v(速度) += 5 / m(重さ) で計算すればいいですし、
中二風にシンプルに 計算したい人は、v(速度) += 2 とかにして F = 5 を 2 に変えてキャラをふっとばないようにしてもいいわけです。




さらについでに、ニュートンの上位互換のアインシュタインっぽいユダヤ物理学の式の解説にも踏み込んでおきましょう。
時は19世紀、パリ万国博覧会に渋沢栄一と徳川昭武公が送られ、ブルーウォーターとピカチュウをめぐって抗争が繰り広げられていた頃、
光は粒なのか?波なのか?」という「水は個体なのか?液体なのか?」「ネコは液体なのか?」という人類にとって重要な実験が行われ、
宇宙空間全体を満たす「エーテルの風」など吹いていないことが証明されてしまったため、
超高速で加速した状態で悟空がレーザーを宇宙空間に放った場合でも、
そのレーザー(光)が加速することはなく「光は完全固定速度」(定額料金制)であることを認めざる負えなくなってしまったため、
物理学者は従来の法則とつじつまを合わせるためには「光の代わりに物体のほうが収縮してないとつじつまがあわん」状態に追い込まれていた。

ローレンツ因子の式: L = L0 × √(1 - vの2乗 / cの2乗)
ローレンツ因子の式: L(縮んだ長さ) = L0(縮む前の長さ) × √(1 - 悟空の速度vの2乗 / 光速cの2乗) が苦肉の式として爆誕してしまった。

この式に沿ってしまうと、悟空が地球を1秒間に7.5周する光の速度に近づいたときに、
地上から見た悟空は進行方向の長さLがほぼ0になってバグった長さになるという式である。
光の1/2 つまり0.5倍の速度の場合は√(1 - 0.5×0.5 / 1×1) = √(1 - 0.25) = √( 0.75 ) = √( 75 / 100 ) = √( 3×5×5 / 100 ) = 5√3 / 10 = √3 / 2
つまり、L(縮んだ長さ) = L0 × √3 / 2倍 、√3 / 2 = 1.73 / 2 = 0.86倍に縮んでしまうという式になるということです。 われわれが普段使っているメートル自体、光の速度を基準に逆算して「1メートル=光が真空中を2億9979万2458分の1秒の間に進む距離」決められています
ああ、仕方ない、じゃあおとなしく縮むしかないか..とあきらめムードのなか、
しかし、ローレンツ収縮は実験結果と矛盾してしまった、、つまりわれわれは高速でも縮むことはないらしいのだ。
では、どうやってつじつまを合わせればいいのか、、
そこにアインシュタインは「悟空自身はゆがんでいるつもりはない、他人(他の速度の観測者)から見ると縮んで見えてるだけ」という「相対性でつじつまをあわせた」。
そうなのです、結局「他の人から見て超高速で動いている人は縮んで見えてる」というバグっぽい見え方はホントなのであった。
そして、中二学派、ニュートン学派の F = m × v × Δt にもつじつま合わせの影響はおよぶ。
「相対性」ではつじつま合わせとして、他人から見て「長さだけじゃなく、時間をゆがませてつじつま合わせるのもアリ」なのだ。
じゃあ時間をゆがませず、ニュートンのときのように「時間を1に固定」してしまうとどうなるか?を見てみよう。

相対性理論の力の式 : F(力) = m(重さ) × v(速度) / √(1 - vの2乗 / cの2乗)

さあ、上の式で「長さも固定=ゆがませない」→速度は長さ、どれだかの距離進むか に関係してるから、
v(速度)もゆがませられない
とすると..
もう、m(重さ)がいけにえとしてゆがんで √(1 - vの2乗 / cの2乗) で割り算されるしかないことになってしまう。
そうすると、光速に近づいている人の「自称の体重はそのままだが、他人から見た見かけ上の体重は∞になっていく」ということなのだろうか。
あ、ブラックホールって他人から見た見かけ上の体重が∞にみえるんじゃ、、という感覚?。
太陽は動いてるようには見えず v(速度固定) だけど、内部で超重いものが超高速に行ったり来たり震えることで、力→エネルギーを発しているという感覚だろうか。


「時間をいけにえにして時間がゆがむのを許すとどうなるか」
t(自分時間)の代わりに、τ(タウ)を絶対時間とすると、(絶対時間とはどんな人からも光速のように、共通の不変の時間)
速度の変化 Δv = Δx(位置の変化) / Δτ(絶対時間) →この式を t(自分時間) にするために、分子と分母でつじつまをあわせる
速度の変化 Δv = Δx(位置の変化) / Δt(分母) × Δt(分子) / Δτ(絶対時間) -①

Δt(分子) / Δτ(絶対時間) = 1 / √(1 - vの2乗 / cの2乗) -② ←時間の伸縮の式
①と②の式を 速度の変化 Δv = v(速度) ×Δtとして ニュートンの F(力) = m(重さ) × v(速度) × Δtに入れると

相対性理論の力の式 : F(力) = m(重さ) × Δx(位置の変化) / Δt(分母) × Δt(分子) / Δτ(絶対時間)
相対性理論の力の式 : F(力) = m(重さ) × v(速度) × 1 / √(1 - vの2乗 / cの2乗) -③

はい、「時間をいけにえ」にしたら、先ほどの「相対性理論の力の式」になりましたね。

そして、有名なE = mc2 [E(エネルギー) = m(重さ) × 光速c の2乗] の式も実は上の式からできあがります。

実は、前提として、E(エネルギー) = F(力) × (速度) です。
これは、雑巾で床を強く押しながら走ると、エネルギーを消耗するので実感できますよね(車を相撲力士が押して動かすのも同じ)。
なので、光速c を③の式の両辺に掛け算して速度も v(速度) = c(光速)にします。


F(力) × c(光速) = m(重さ) × c(速度) × c(光速) × 1 / √(1 - vの2乗 / cの2乗) -③
E(エネルギー) = m(重さ) × c(光速)の2乗 / √(1 - vの2乗 / cの2乗)

でてきましたね、これで v = 0 にして、分母を√1 = 1にすれば、E = mc2になります。
つまり、速度がゼロでも E(エネルギー) = m(体重) × c(光速)の2乗 のエネルギーを皆さんは持っているわけです。
そう、体重が光速の2乗倍のエネルギーに変えられるという「ライザップの減量の法則」が爆誕したわけですね。
つまり、脂肪を燃焼して体重を 1kg 減量するためには、光速の2乗倍のエネルギーぶんの努力が必要だということです。

ということは、本来、プレイヤが光るエネルギー弾を撃つためには、MP ではなく 体重を減らさなきゃ物理的にはおかしいわけですね。
その証拠にサイヤ人の悟空は大量のメシを喰ってますよね。
一方、幽遊白書は霊力というおかしい概念がでてきますので「幽遊白書の霊丸(れいがん)は物理的におかしい」という結論になります。
前提として、幽霊には体重がない、霊の質量 = 0 ゼロなわけですからエネルギーが発生するはずがないのです。

[エネルギーと時間スケールを実感するには]
植物の人生観からしたら、人間は超高速で動いて見えるわけで、
ノコギリでギコギコ木を切っている時間は、植物の時間感覚からすると超高速な振動(波の震え)に見えてるわけだし、
人間の時間のスケールから見ると電子レンジのマイクロ波の波もノコギリのように水分子を押したり引いたりしてお弁当を温めているのは見えない。
直感的に体重とその内部の超高速な分子レベルの震えの速度とエネルギー(太陽のように一見止まって見えるけど内部で重いものが超高速で震えている)は関係しているのです。

一応、これで基本的な物理の、
位置(x,y,z)、速度(vx,vy,vz)、力(F:vxForce,vyForce,vzForce)、エネルギー(E = F × [速度] = m×c2乗)
を押さえることができたかと思います。
位置がエネルギーまでつながる実感がない人は水力発電のダムをイメージしてください。
湯気が冬に山に雪として降り、重たい位置エネルギーに変わりダムの放水で発電で電気エネルギーの振動(震え)に変わります。
雪国で雪かきしている人は雪をスコップで持ち上げながら実感しているはずです。「何と無益な位置エネルギーの移動作業なのだ」と。
体重がエネルギーに変わる実感がない人は、体温計で自分の体温を測ってみましょう。
あなたの身体の体温の源はどこにあるのでしょう?体内で細胞などが分子レベルの化学反応で震えて熱エネルギーに変わっているのです。



【練習物理実験1】プレイヤ1とプレイヤ2でぶつかったときの force += 5 をいじって、衝突の物理現象をカスタマイズする。

GameScene.cppを変更して、プレイヤ1とプレイヤ2に質量 mass (重さ)を設定しましょう。

#include "GameScene.h"

#include "Player.h"

(中略).........

void GameScene::Initialize()
{// Init処理

    // Zバッファを有効にする [Zの深度]
    //[参考]https://docs.google.com/presentation/d/1Z23t1yAS7uzPDVakgW_M02p20qt9DeTs4ku4IiMDLco/edit?usp=sharing
    //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
    DxLib::SetUseZBuffer3D(TRUE);
    // Zバッファへの書き込みを有効にする
    DxLib::SetWriteZBuffer3D(TRUE);
    // 光の明暗計算を無効に
    DxLib::SetUseLighting(FALSE);

    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);


    Resource::MakeShared<Texture>(mapChipPath, 8, 8, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath1, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath2, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする


    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    // プレイヤの生成
    if (pWorld != nullptr)
    {
        //pWorld->AddCamera("カメラ1", Camera::defaultCamera); // カメラ1というタグをデフォルトカメラにつけてワールドにデフォルトカメラを配置
        pWorld->AddCamera("カメラ1", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera1 = pWorld->camera("カメラ1"); // カメラ1 というタグのついたカメラへの共有ポインタを得る
        pCamera1->SetScreenHandle(screenHandle0); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer1 = Object::Create<Player>(nullptr, pWorld, Pad::Key, Vector3(90 + 256, 32, 95 + 256));
        pWorld->AddPlayer("プレイヤ1", pPlayer1); // シーンにプレイヤを追加する
        pPlayer1->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);

        pPlayer1->camera = pCamera1.get(); // カメラをプレイヤにリンクする
        pPlayer1->mass = 100.0f; // 体重を100にして衝突されてもびくともしないようにする


        pWorld->AddCamera("カメラ2", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera2 = pWorld->camera("カメラ2"); // プレイヤ2 というタグのついたカメラへの共有ポインタを得る
        pCamera2->SetScreenHandle(screenHandle1); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer2 = Object::Create<Player>(nullptr, pWorld, Pad::Two, Vector3(190, 32, 195));
        pWorld->AddPlayer("プレイヤ2", pPlayer2); // シーンにプレイヤを追加する
        pPlayer2->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);
       
        pPlayer2->camera = pCamera2.get(); // カメラをプレイヤにリンクする
        pPlayer2->mass = 1.0f; // 体重を1にして普通にふっとぶようにする

    }

};

(以下略)....


Player.cppを変更して、力 force の計算式を体重 mass で割り算してぶつかったときのふっとび具合に差をつけてみましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

(中略)........................

void Player::OnCollision(std::shared_ptr<Collision> pCollision)
{
    if (isDead) return; // すでにisDeadなら return
    auto otherCollider = pCollision->contacts[0].otherCollider.lock(); // コライダの弱共有ポインタをロック
    if (otherCollider == nullptr) return; // すでにコライダが消去済だった
    auto other = otherCollider->owner();

    if (other->isDead) return; // other側がisDeadだったら処理をスルー

    if (other->baseTag == "Enemy" || other->baseTag == "Player")
    {
        auto norm = pCollision->contacts[0].normal* other->pushPower; // pushPowerの力でotherに押し返される
        force += -norm / mass; // 衝突法線方向の力を加える
    }
}

(以下略).............


いかがでしょう?操作してプレイヤ1とプレイヤ2をぶつけると、プレイヤ2を操作してプレイヤ1にぶつけてもほぼ動じないようになりましたか?
ちょっと forceの式を mass (体重) で割り算しただけなのに、キャラの重たさ(強さ)を表現できるようになりました。
難しい式を使わなくても、物理の仕組みの割り算と掛け算、つまり分母と分子がわかれば、ゲームのキャラの表現につながる実感がわきましたか?
デフォルトのpushPower = 5 と キャラごとの体重 mass の割り算だけの「なんちゃって中二物理学」でも十分にゲームとして遊べる衝突は表現可能なのです。




【練習物理実験2】超本格派の物理計算式「離散要素法(Discrete Element Method :DEM)」を使って本格的な物理衝突計算をしてみる。

Player.cppを変更して、力 force の計算式を「離散要素法:DEM」の計算式に変えてみましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

(中略)........................

void Player::OnCollision(std::shared_ptr<Collision> pCollision)
{
    if (isDead) return; // すでにisDeadなら return
    auto otherCollider = pCollision->contacts[0].otherCollider.lock(); // コライダの弱共有ポインタをロック
    if (otherCollider == nullptr) return; // すでにコライダが消去済だった
    auto other = otherCollider->owner();

    if (other->isDead) return; // other側がisDeadだったら処理をスルー

    if (other->baseTag == "Enemy" || other->baseTag == "Player")
    {
        //[離散要素法:DEM] https://qiita.com/konbraphat51/items/157e5803c514c60264d2
        constexpr float k = 1.0f; // バネ係数(めり込みに対するバネ反発の掛け率)
        constexpr float e = 0.1f; // はね返り係数(直前と比べどれくらいの速さで離れるか0.5だと半分の速さではね返る)
        float log_e = std::log(e); // はね返り係数の自然対数log
        constexpr float pi_2 = DX_PI_F * DX_PI_F; // πの2乗
        float eta = -2.0f * log_e * std::sqrt(k * mass / (log_e * log_e + pi_2)); // 粘性係数η(イータ)

        auto norm = pCollision->contacts[0].normal; // 衝突の法線 * other->pushPower; // pushPowerの力でotherに押し返される
        auto v_norm = dot(other->velocity - velocity, norm) * norm; // [相対速度v] 法線norm方向のv = (v1 - v0)・norm [ドット積]
        auto sep = pCollision->contacts[0].separation; // 衝突点どうしの距離(めりこみ距離)
        auto F = -k * sep * norm - eta * v_norm; // F (法線方向の力) = -k × sep - η × v (相対速度:自分から見た相手otherの速度)

        force += F / mass; // 衝突法線方向の力を加える
    }
}

(以下略).............


いかがでしょう?プレイヤ1とプレイヤ2を衝突させたら「助走つけて加速してぶつかるとよりふっとぶ」ようになりましたか?
たったこれだけのコードの追加で「工業のシュミーションにつかわれるプロ仕様の物理計算」ができるようになりました。
「離散要素法:DEM」自体を理解するのは物理学者ではないので必要はありません。
プログラマの仕事は物理の式のパラメータの掛け算や割り算のからみあい方を把握して動かして計算式に破綻がないようにすることで、
ゲームの作り手の仕事はパラメータをいじって はね返り係数 eを変えてみたり、バネ係数 kをいじったり「いじってもよい数字を調整」することです。




【パラメーター演習】質量 mass や バネ係数 k や はね返り係数 e をいじって、プレイヤどうしぶつかりながら実験し、パラメータを使いこなせるようになろう
いかがでしょう?実際に自分でパラメータを変えて実験しないことにはゲームを作りこむことはできません。


特に、反発係数 eを0~0.5~1.0まで変えてはね返り具合を実感してください。
auto F = -k * sep * norm - eta * v_norm; ← v_norm = dot(other->velocity - velocity, norm) * norm;
上記の式を見ればわかるのですが、
実は「ぶつかった相手のother側の影響は自分との速度の差のみ」になっています。
そう、どんなに相手の体重が重くても、ぶつかる瞬間の速度の差[相対速度]が大きくなければ、こちら側はふっとびません
となると現状のイメージは「ゴムのスーパーボール」のはね返りの感じがすると思います。
スーパーボールがはずむのはスーパーボール自身の表面がゴムで伸縮するからです。
ですから、スーパーボールはぶつかる側など気にせず「自分のゴムパラメータではね返っている感じ」が出ていると感じませんか?
ただ、相手のother側の影響がないとなると、ぶよぶよのお腹にスーパーボールを落としてもスーパーボールははね返ってしまいます
なので、本来は if (otherがおデブキャラ) e = はね返らない数値; のように
反発係数 e をif文で場合分けする必要がありますし、物理的解釈でも「反発係数e はぶつかったペアにより変わる」とされています。

次に、バネ係数 k の式は : F = -k * sep * norm ...のように「ぶつかる速度と無関係で、めり込み具合と法線に関係する」ようになっています。
実際のバネを思い浮かべてください「速度ではなく、バネをどれだけ縮めたか、で反発が決まる」イメージがしませんか?
ですので、eをはね返らない数値にして、代わりに バネ係数 k の数値の影響を大きくすれば、バネを表現できるはずです。
例えば、if (sep > 3) k = 大きな数字; にすれば、バネのめり込みが 3を超えると派手にはね返る、という風に
自分が指でバネを縮めてバウンドの手ごたえが出てくるところを確かめる、みたいな工夫もできるかもしれません。


さて、実は現状は縦方向 上下 y 方向の物理の反発処理は封印されています。
プレイヤ2でジャンプして、プレイヤ1をマリオのように踏みつけてみてください。
きっと、横方向にしかプレイヤははね返らないはずです。

あえて、縦方向は封印してあります。
なぜなら、縦方向は、パラシュートなどで感じるような「空気抵抗」の物理式が必要になりますし、
現状 y方向は、JumpComponentとGravityComponentに処理を任せているから一緒にするとややこしいからです。
PhysicsComponent.hを開いてみてください。

#ifndef PHYSICS_COMPONENT_H_
#define PHYSICS_COMPONENT_H_

(中略)..................

class GravityComponent : public PhysicsComponent
{
public:
    (中略)..................

    virtual void Update(GameObject* obj = nullptr) override
    {
        (中略)..................

        if (pOwner->isGround == false && pOwner->vy <= 0) // 下落開始(vyが 0以上 のときはまだジャンプ中なので上昇を邪魔しないようにする)
        {
            pOwner->vyForce = -gravity(); // ジャンプ上昇力が0になって以降に初めて重力をオンにする(ジャンプの加速を邪魔しない工夫)
            pOwner->OnFalling(); // 下落中の処理のコールバック関数を呼び返す
        }

        if (pOwner->vy <= -downSpeedMax()) // 無限に落下速度が加速しないようにリミットを働かせる(空気抵抗みたいなもの)
            pOwner->vy = -downSpeedMax(); // downSpeedMax()以上の落下速度にならないようにする
    }
};

見てください、致命的なのが、pOwner->vyForce = -gravity();の部分です。
せっかく先ほどの「離散要素法:DEM」でforce += F / mass; して加えたforceの力の数値が vyForce = -gravity();= イコールで上書きされて無効化されています。
ですので、現状の設計のままだと「離散要素法:DEM」のforce と 重力 gravityの vyForce の共存が難しい状況になっています。
この状況を打開する方法は1つ「コンポーネントごとに別管理の個別の vyForce を持てるようにすればいい」です。
別管理の個別の vyForce を持てるように設計しなおせば、DEMのほうで頑張った forceを上書きせずに済みます
個別にコンポーネントごとに分離してforceを管理して、
あとから Player.cppで vy += vyForce + vyJumpForce + vyGravityForce;のようにまとめて合算して速度に影響を与えればいいのです。

PhysicsComponent.hを変更して、変数m_vyForceと関数vyForce()を定義してコンポーネントごとに力を分離管理できるようにしましょう。

#ifndef PHYSICS_COMPONENT_H_
#define PHYSICS_COMPONENT_H_

#include <vector>

#include "Component.h"
#include "GameObject.h"

// 物理系のコンポーネントはこのクラスを継承して実装する
class PhysicsComponent : public Component
{
protected:
    inline PhysicsComponent(std::shared_ptr<GameObject> pOwner) : Component(pOwner)
    {
        this->baseTag = "PhysicsComponent"; // 大まかなカテゴリーとしては物理系コンポーネント
    };
public:
    virtual ~PhysicsComponent() {};

    virtual void Update(GameObject* obj = nullptr) override
    {
        // なにか物理系のコンポーネントで共通して更新したいことがあればここに実装
    }

    virtual void Draw(GameObject* obj = nullptr) override
    {
        // なにか物理系のコンポーネントで共通して描画したいことがあればここに実装
    }
};

class GravityComponent : public PhysicsComponent
{
public:
    inline GravityComponent(std::shared_ptr<GameObject> pOwner, const std::vector<float>& vyGravity, const std::vector<float>& vyDownSpeedMax)
        : PhysicsComponent(pOwner), m_vyGravity{ vyGravity }, m_vyDownSpeedMax{ vyDownSpeedMax }
    {
        this->typeID = typeid(GravityComponent); //[環境によって差が出るので↓が無難か] https://qiita.com/nil_gawa/items/1fece3eee7ca1f88c71d
        this->typeTag = "GravityComponent"; // 個別のカテゴリーとしては重力落下コンポーネント
    };
    virtual ~GravityComponent() {}
protected:
    float m_vyForce{ 0.0f }; // オーナーのvyForceとは独立した「このコンポーネントのy方向の力」
public:
    float vyForce() { return m_vyForce; }


    size_t m_idx{ 0 }; // 適用する設定配列の番号
    void SetIndex(size_t idx) { m_idx = idx; }
    std::vector<float> m_vyGravity; // 重力の設定値
    float gravity() { return (m_idx < m_vyGravity.size()) ? m_vyGravity[m_idx] : 0.0f; }
    std::vector<float> m_vyDownSpeedMax; // 降下スピードのリミット
    float downSpeedMax() { return (m_idx < m_vyDownSpeedMax.size()) ? m_vyDownSpeedMax[m_idx] : 0.0f; }

    virtual void Update(GameObject* obj = nullptr) override
    {
        if (!isEnabled) return; // コンポーネントが有効状態じゃないときは何もせずreturn
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタをロックして得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (pOwner->isGround == false && pOwner->vy <= 0) // 下落開始(vyが 0以上 のときはまだジャンプ中なので上昇を邪魔しないようにする)
         || pOwner->isJumping == false && pOwner->vy > 0)// 上方向にぶっ飛ばされたとき
        {   
            float vyMax_2 = downSpeedMax() * downSpeedMax(); // 最大スピードの2乗 を空気抵抗 kに使う
            //[空気抵抗の物理] https://hunikablog.com/2020/02/02/equation-of-motion-resistance-proportional-to-the-square-of-speed/
            // 空気抵抗係数 k https://www.natural-science.or.jp/article/20100829232938.php
            float k = pOwner->mass * gravity() / vyMax_2; // k = m * g / vmaxの2乗 にすれば vmaxが収束速度になる
            if (pOwner->vy <= 0) k = -k; // 下方向への空気抵抗は逆になる

            // ジャンプ上昇力が0になって以降に初めて重力をオンにする(ジャンプの加速を邪魔しない工夫)
            m_vyForce = -gravity() - k / pOwner->mass * pOwner->vy * pOwner->vy; // 空気抵抗は - k / m * vyの2乗
            if (pOwner->vy > 0) pOwner->isGround = false; // ふっ飛ばされた場合はisGroundがtrueなのにvy > 0の状態になったりするから

            pOwner->OnFalling(); // 下落中の処理のコールバック関数を呼び返す
        }

        if (pOwner->isGround)
            m_vyForce = 0.0f; // 着地したらm_vyForceを0にリセットしないと次回ジャンプ時にJump側と重力がダブっちゃう

           
        if (pOwner->vy <= -downSpeedMax()) // 無限に落下速度が加速しないようにリミットを働かせる(空気抵抗みたいなもの)
            pOwner->vy = -downSpeedMax(); // downSpeedMax()以上の落下速度にならないようにする

    }
};

class JumpComponent : public PhysicsComponent
{
public:
    inline JumpComponent(std::shared_ptr<GameObject> pOwner, const std::vector<float>& vyJumpSpeed, const std::vector<float>& vyForceJump)
        : PhysicsComponent(pOwner), m_vyJumpSpeed{ vyJumpSpeed }, m_vyForceJump{ vyForceJump }
    {
        this->typeID = typeid(JumpComponent); //[環境によって差が出るので↓が無難か] https://qiita.com/nil_gawa/items/1fece3eee7ca1f88c71d
        this->typeTag = "JumpComponent"; // 個別のカテゴリーとしてはジャンプ上昇コンポーネント
    };
    virtual ~JumpComponent() {}
protected:
    float m_vyForce{ 0.0f }; // オーナーのvyForceとは独立したこのコンポーネントのy方向の力
public:
    float vyForce() { return m_vyForce; }


    size_t m_idx{ 0 }; // 適用する設定配列の番号
    void SetIndex(size_t idx) { m_idx = idx; }
    float yJumpStart{ 0 }; // ジャンプした瞬間のyの位置
    std::vector<float> m_vyJumpSpeed; // ジャンプの上昇スピードのリミット
    float jumpSpeed() { return (m_idx < m_vyJumpSpeed.size()) ? m_vyJumpSpeed[m_idx] : 0.0f; }
    std::vector<float> m_vyForceJump; // ジャンプ中の下方向の勢いの減速する力
    float forceJump() { return (m_idx < m_vyForceJump.size()) ? m_vyForceJump[m_idx] : 0.0f; }

    virtual void Update(GameObject* obj = nullptr) override
    {
        if (!isEnabled) return; // コンポーネントが有効状態じゃないときは何もせずreturn
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (pOwner->isJumping && pOwner->vy <= jumpSpeed() // jumpSpeedを超える場合はふっとばされている状態
            &&
pOwner->isGround == false && pOwner->vy > 0) // vyが0以上のときはまだジャンプ中
        {
            pOwner->OnJumping(); // 空中でジャンプの上昇中に呼ばれるコールバック関数を呼び返す
            // ジャンプ上昇中にも空気抵抗を効かせるとき
            float vyMax_2 = jumpSpeed() * jumpSpeed(); // jumpSpeed()の2乗を収束速度にする
            float vyGravity = forceJump(); // ジャンプ中はforceJump()のゆるい重力でジャンプにふわっと感を出す
            float k = pOwner->mass * vyGravity / vyMax_2; // k = m * g / vmaxの2乗 にすれば vmaxが収束速度になる
            // 空気抵抗は - k / m * vyの2乗
            m_vyForce = -vyGravity - k / pOwner->mass * pOwner->vy * pOwner->vy;

        }
        else // vy < 0になってジャンプ中じゃなくなり、落下状況になった
        {
            m_vyForce = 0.0f;
            pOwner->isJumping = false; // ジャンプ状態フラグをfalseにする
        }

    }

    virtual void JumpStart(GameObject* obj = nullptr)
    {
        if (!isEnabled) return; // コンポーネントが有効状態じゃないときは何もせずreturn
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (pOwner->isGround)
        {
            pOwner->vy = jumpSpeed(); // ジャンプ方向はY上方向
            m_vyForce = -forceJump(); // ジャンプの勢いが弱まる力
            yJumpStart = pOwner->y;
            pOwner->isJumping = true; // isJumpingフラグがtrueになるのはJumpStart()関数経由が正規ルート
            pOwner->OnStartJump(); // ジャンプスタート時の処理をコールバックで呼び返す
        }
    }

    virtual void JumpCancel(GameObject* obj = nullptr)
    {
        if (!isEnabled) return; // コンポーネントが有効状態じゃないときは何もせずreturn
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        if (!pOwner->isGround && pOwner->vy >= 0) // vyが0以上のときはまだジャンプ上昇中
        {
            if (pOwner->y - yJumpStart >= 1) // ジャンプ位置から1ピクセルを超えないタイミングでボタンを離しても重力はかけ始めない
            {
                m_vyForce = -pOwner->gravity(); // ジャンプボタンを離したときには重力をかけ始める
                pOwner->isJumping = false; // falseにしてジャンプ状態フラグをオフにする
            }
        }
    }
};

#endif


物理の落下の数式を読み解くと 空気抵抗の係数 k = m(体重) * g(重力加速度) / v_max(落下の限界速度) にすれば、
少し時間が経つと「落下の速度は空気の抵抗と均衡収束して v_maxに落ち着く」ようにうまく意図的に設定できます。
空中の空気の抵抗は「チャリのように低速では速度の1乗倍になり、飛行機のように超高速では乱気流が発生し速度の2乗倍」に比例します。
float k = pOwner->mass * vyGravity / vyMax_2; // k = m * g / vmaxの2乗
m_vyForce = -gravity()- k / pOwner->mass * pOwner->vy * pOwner->vy; // 空気抵抗は - k / m * vyの2乗

大事な式です↑y方向だけでなく、x,z方向にも空気抵抗をいれることもできます。1乗倍にして特定の方向だけ抵抗を入れればチャリにのって風の抵抗を表現することもできます。

Player.cppを変更して、OnCollision関数のpCollisionから得た衝突した法線方向を使ってUpdate関数で力を加えましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

(中略).......................

// 更新処理
void Player::Update()
{
    vxForce *= 0.9f; // かかっている力の減衰率
    vyForce *= 0.9f; // かかっている力の減衰率
    vzForce *= 0.9f; // かかっている力の減衰率
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    auto pJumpPhysics = jumpPhysics.lock();
    if (pJumpPhysics != nullptr)
    {
        if (isGround)
        {
            if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
                || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
            {
                pJumpPhysics->JumpStart(); // ジャンプのスタート処理を発動
            }
        }
        else if (vy > 0 && Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
            || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
        {
            pJumpPhysics->JumpCancel(); // ジャンプのキャンセル処理を発動
        }
        pJumpPhysics->Update(); // ジャンプのコンポーネントを更新する
    }
    auto pGravityPhysics = gravityPhysics.lock();
    if (pGravityPhysics != nullptr)
        pGravityPhysics->Update(); // 重力落下のコンポーネントを更新する

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    float vyForceJump = (pJumpPhysics != nullptr) ? pJumpPhysics->vyForce() : 0.0f;
    float vyForceGravity = (pGravityPhysics != nullptr) ? pGravityPhysics->vyForce() : 0.0f;

   
    vx += vxForce; // 力をかけて速度に加算する
    vy += vyForce + vyForceJump + vyForceGravity; // 勢い(力・フォースを加算
    vz += vzForce; // 力をかけて速度に加算する

    // 実際に位置を動かす

    // まず横に移動する
    x += vx;

    // 次に縦に動かす
    y += vy;

    // 次にZ奥行き方向に動かす
    z += vz;

    vy += vyForce; // 勢い(力・フォースを加算

    if (y <= 0.0f) // 0.0fを地面として 0.0f以下になったら地面に着地したと判定する
    {
        y = 0.0f; // 地面に沿わせる
        vy = -0.01f; // 下方向への速度をほぼ 0 にする
        isGround = true;
    }

    if (auto pGridCollision = gridCollision.lock()) // lock()してnullptrじゃなければ
        pGridCollision->Update(); // 所属するマス目セルを更新する

    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

(中略).......................


いかがでしょう?体重の軽いプレイヤ2がジャンプして、プレイヤ1をマリオのように上から踏みつけると、反発で大ジャンプできたでしょうか?
このように、数式単位で意味をわかれば、Unityのデフォルトの物理現象以外に、自作の物理現象をAddForceでできる可能性もでてきます。
これが、現代ゲームプログラマの仕事「グラフィック、フレームワーク、AI、ダイナミクス..」などの一角「ダイナミクス=物理プログラミング」の仕事の実例です。


タイル情報を.tsxや.jsonや.csvから読み取りタイルマップを表示する

まず読み取るデータとしてタイルマップエディタで生成された下記データを「Mapフォルダに保管」してください。

マップに並べられる「タイル側の情報」を持つ.tsx形式のファイル→mapchip.tsx

マップにタイルやオブジェクトを並べた「マップ側のステージ情報」を持つ.json形式のファイル→stage1.json

マップに関して色々「エクセルで設定したい追加情報」を持つ.csv形式のファイル→MapSetting.csv


さてダウンロードしたファイルはすべて「Mapフォルダ内」に置いて、互いにリンク切れにならないように注意してください。

続いて、マップに出したいアイテムや敵の画像をダウンロードして「Imageフォルダに保管」してください。

敵(Zako0)の画像.png形式のファイル→zako0.png (GIMPの書き出し元データzako0.xcf)

敵(CharaMELで制作)の分割画像.png形式のファイル→skullman.png

アイテム(エネルギー弾)の画像.png形式のファイル→bullet.png (GIMPの書き出し元データbullet.xcf)

アイテム(お金:1円)の画像.png形式のファイル→yen1.png

アイテム(お金:10円)の画像.png形式のファイル→yen10.png


さてダウンロードしたファイルはすべて「Imageフォルダ内」に置いて、リンク切れにならないように注意してください。


さて、まずは.tsx形式について[右クリックでメモ帳やVisualStudioで開く]して中身に目を通してみましょう。
まず、最初の行に着目してください→ xml version="1.0" ... とありますね。
.tsx形式は「.xml形式をベース」としたファイル形式であることがわかります。
XML形式はIT企業でもよくつかわれるフォーマットで、基本は< ~ >の三角カッコ内にデータの詳細があり、
< ~ >データ中身</ ~ >のように三角カッコ同士でデータの中身を囲ってデータの構造を表現する形式です。

一般に流通するフォーマットの形式なので、自分で中身を見れば、tilewidth="32"...←つまりタイルの幅が32、理解できる範囲です。
C++であれば色々なXML形式を読み取るオープンソースのコードがありますが、
今回はシンプルな「tinyxml2 (GPLライセンス)」を使って読み取ってみましょう。
導入方法は tinyxml2.h と tinyxml2.cpp を自分のプロジェクトに新規作成して、下記サイトのコードを書き入れます。
tinyxml2.h
tinyxml2.cpp


つぎに、ステージの.json形式について[右クリックでVisualStudioで開く]して中身に目を通してみましょう。
XMLより量は多いですが、
基本構造は { ~ } で囲まれた中に "height":400, ..などの情報や
や "data":[1, 1, 1, ..............] のように[ , , ...]四角カッコ[]と ,カンマで配列のデータ構造が保管されています。
なので基本は[ 1, 1, 1, ... ] の []四角カッコの中身はエクセルで吐き出すcsv形式と同じような保管形態になっているのが読み取れます。

jsonも一般に流通するフォーマットの形式なので、
タイルエディタから[ファイル]→[名前をつけてエクスポート]→[保存形式を.json形式に]して保存すれば自分で読み解くことが可能です。
C++であれば色々なXML形式を読み取るオープンソースのコードはありますが、
使うならnloman-jsonが決定版です。
導入方法は json.hpp を新規作成して、下記サイトのコードを書き入れます。
json.hpp




さて実際に上記のtinyxmlを#includeして、基底ベースとなるDataXMLクラスを継承overrideしてデータを読み取る処理を作成していきましょう。

DataXML.hを新規作成して、基底ベースとなるDataXMLクラスを作成します。

#ifndef DATA_XML_H_

#include <string>
#include <sstream> // std::istringstream で >>で 文字列を数値に変換する
#include "tinyxml2.h" //[軽量シンプルなXML読込パーサー] https://github.com/leethomason/tinyxml2

struct DataXML
{
    enum class XMLType
    {
        XML,
        TSX,
    };
    XMLType type;
    bool isInitialized = false; //[継承2重ロード対策]ベース基底でロードしたらtrueに
    // 読込んだデータファイルの情報
    std::string FilePath{ "" }; // ファイルの保存パス
   
    tinyxml2::XMLDocument Data; // XMLのデータ本体

    // 初期化コンストラクタでファイル名を指定して初期化と同時にファイル読込
    DataXML(const std::string& FilePath = "", XMLType xmlType = XMLType::XML) : type{ xmlType } {}
    virtual ~DataXML() { clear(); } // 仮想デストラクタ

    // データをクリアしてメモリを節約する
    virtual void clear()
    {
        Data.Clear(); // データ本体をクリア
        isInitialized = false; //ロード済みフラグをOFF
    }

    virtual void Load(const std::string& filePath, XMLType xmlType = XMLType::XML)
    {
        // ここには未実装 継承overrideして個別の読み取り処理を実装してください
    }
};

#endif DATA_XML_H_




そして、このDataXML.hを継承overrideして.tsx形式のファイルを読み取るDataTsxクラスで読み取る処理を作成していきましょう。

DataTsx.hを新規作成して、基底ベースとなるDataXMLクラスを継承overrideして.tsx形式を読み取る処理を作成します。

#ifndef DATA_TSX_H_
#define DATA_TSX_H_

#include "DataXML.h"
#include "MyHash.h" // hash32でタグの高速化
#include "Resource.h" // タイルのテクスチャ画像ファイル読込み

#include "json.hpp" // jsonを使用
using json = nlohmann::json;

// .tsxファイルの情報
struct TsxInfo
{
    static std::vector<std::pair<int, std::weak_ptr<Texture>>> id_images; // タイルのgid → タイルの<id,画像の弱リンク> への辞書
    // tilewidth:幅, tileheight:高さ, tilecount:タイルの数, columns:1列のタイルの数
    int tilewidth{ 32 }, tileheight{ 32 }, tilecount{ 64 }, columns{ 8 };
    int imageWidth{ 256 }, imageHeight{ 256 }; // 画像ファイルの imageWidth:幅 imageHeight:高さ
    std::string name{ "" }; // タイルの管理名
    std::string imagePath{ "" }; // タイルの画像ファイルパス
    std::vector<unsigned int> gid; // タイルのID → gid の配列辞書
};

// TSXファイルを読込むデータ型
struct DataTsx : public DataXML
{
    TsxInfo tsxInfo; //.tsxファイルの情報

    // 初期化コンストラクタでファイル名を指定して初期化と同時にファイル読込
    DataTsx(const std::string& FilePath = "") : DataXML(FilePath, XMLType::TSX)
    {
        if (FilePath != "") // .tsxファイルの読込み
            Load(FilePath); // ファイル読込み
    }
    virtual ~DataTsx() { clear(); } // 仮想デストラクタ

    virtual void Load(const std::string& filePath, XMLType xmlType = XMLType::TSX) override
    {
        if (filePath == "" || isInitialized) return; //ファイル名がないもしくはロード済
        this->FilePath = filePath; // ファイル名を保管
        Data.Clear();
        if (Data.LoadFile(FilePath.c_str()) != 0) return; // ファイルパスからデータ読み込み

#if 0    // TSX形式のXMLのフォーマット例
        < tileset version = "1.10" tiledversion = "1.10.2" name = "mapchip" tilewidth = "32" tileheight = "32" tilecount = "64" columns = "8" >
            <image source = "mapchip.png" width = "256" height = "256" / >
            < / tileset>
#endif

        //[tinyXMLの使い方] https://w.atwiki.jp/bokuyo/pages/186.html
        tinyxml2::XMLElement * tileset = Data.FirstChildElement("tileset"); // 最初の要素を得る
        if (tileset == nullptr) return;
        const char* pName = tileset->Attribute("name"); // 名前
        if (nullptr != pName) tsxInfo.name = std::string(pName);
        const char* pTilewidth = tileset->Attribute("tilewidth"); // 幅
        if (nullptr != pTilewidth) { std::istringstream ss(pTilewidth); ss >> tsxInfo.tilewidth; }
        const char* pTileheight = tileset->Attribute("tileheight"); // 高さ
        if (nullptr != pTileheight) { std::istringstream ss(pTileheight); ss >> tsxInfo.tileheight; }
        const char* pTilecount = tileset->Attribute("tilecount"); // タイル総数
        if (nullptr != pTilecount) { std::istringstream ss(pTilecount); ss >> tsxInfo.tilecount; }
        const char* pColumns = tileset->Attribute("columns"); // 1列のタイル数
        if (nullptr != pColumns) { std::istringstream ss(pColumns); ss >> tsxInfo.columns; }

        tinyxml2::XMLElement* image = tileset->FirstChildElement("image"); // "tileset"内の"image"要素を得る
        if (image != nullptr)
        {
            const char* pImagePath = image->Attribute("source"); // タイル画像パス
            if (nullptr != pImagePath) tsxInfo.imagePath = std::string(pImagePath);
            const char* pImageWidth = image->Attribute("width"); // タイル画像の幅
            if (nullptr != pImageWidth) { std::istringstream ss(pImageWidth); ss >> tsxInfo.imageWidth; }
            const char* pImageHeight = image->Attribute("height"); // タイル画像の高さ
            if (nullptr != pImageHeight) { std::istringstream ss(pImageHeight); ss >> tsxInfo.imageHeight; }
        }

        isInitialized = true; //ロード済みフラグをON
    }
};




#if 0
struct Tileset // 塗りのパレットとなっている画像ファイルなどのタイル情報
{   // 各パレットの0から始まるが他パレットとかぶるとどっちの番号か確定しないので
    int firstgid = -1; // 各パレットのfirstgid=最初の0にあたる番号
    std::string source{ "" }; // 塗りのパレットとなっている"mapchip.tsx"など
    Tileset(int firstgid, std::string source) : firstgid{ firstgid }, source{ source } {};
    ~Tileset() {};
};
#endif

// あるgidに対して log N の速度で その firstgid や そのgidを含むタイルファイル名を 計算できる
struct Tilesets : std::map<unsigned int, std::string>
{
    static std::unordered_map<hash32, TsxInfo> umTsxInfo; // <ファイルパス, .tsxの情報> の static 辞書
    static std::vector<unsigned int> firstgidLog; // 過去に一度計算したfirstgid→1回計算済のfirstgid の static 配列

    Tilesets() = default;
    inline ~Tilesets() { clear(); }

    void LoadTsx(json* pData, std::string* jsonFolderPath)
    {    // データの中身を事前処理する
        assert((*pData).count("tilesets") != 0 && "jsonにtileset情報が見つからない");
        int i = 0;
        for (auto& tileset : (*pData)["tilesets"]) {
            std::string tsxPath = tileset["source"];
            if (jsonFolderPath != nullptr) tsxPath = *jsonFolderPath + tsxPath;
            //auto pos = tileName.find(".", 0);
            //if (pos != std::string::npos) tileName = tileName.substr(0, pos); // mapchip.tsxのドット.までの文字列mapchip
            int firstgid = tileset["firstgid"]; // .tsxファイルの最初のタイルのgid番号
            this->emplace(firstgid, tsxPath); // this すなわち std::mapの辞書 にfirstgid と タイルのパス を登録
            if (umTsxInfo.count(tsxPath) == 0)
            {
                DataTsx tsxData{ tsxPath }; // .tsxからタイルに関する情報をロード
                std::string mapChipPath = tsxData.tsxInfo.imagePath; // マップチップのパス
                if (jsonFolderPath != nullptr) // jsonファイルと同一フォルダを探す
                    mapChipPath = *jsonFolderPath + tsxData.tsxInfo.imagePath;
                // .tsxファイルの情報をもとにテクスチャファイルをロード
                Resource::MakeShared<Texture>(mapChipPath, tsxData.tsxInfo.columns,
                                                tsxData.tsxInfo.tilecount / tsxData.tsxInfo.columns,
                                                tsxData.tsxInfo.tilewidth, tsxData.tsxInfo.tileheight)->Load();
                //[辞書]TsxInfo::id_imagesのタイルのgidぶんのページ数が足りなければresizeでページを増やしておく
                if (firstgid + tsxData.tsxInfo.tilecount >= TsxInfo::id_images.size())
                    TsxInfo::id_images.resize(firstgid + tsxData.tsxInfo.tilecount + 1);
                tsxData.tsxInfo.gid.resize(tsxData.tsxInfo.tilecount);

                auto pMapChip = std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]);
                for (int id = 0, iSize = pMapChip->m_handles.size(); id < iSize; ++id)
                {
                    TsxInfo::id_images[firstgid + id] = { id, pMapChip }; // TsxInfoの辞書にロードしたidと画像の弱リンクを登録
                    tsxData.tsxInfo.gid[id] = firstgid + id; // gidを辞書に登録
                }
                //umTsxInfo.erase(tilePath); // すでに辞書に読込み済データが有ったら削除
                umTsxInfo.emplace(tsxPath, tsxData.tsxInfo); // DataTsx::umTsxInfoの辞書に登録
            }
        }
    }

    void clear() { std::map<unsigned int, std::string>().swap(*this); }
#if 0
    Tileset* operator[] (std::string tsxFilename) { // []オペレータで["mapchip"]タイル名でアクセス
        if (listDic.count(tsxFilename) == 0) return nullptr;
        return &list[listDic[tsxFilename]]; // 書き込み
    }
#endif
    // 例.入力gid:129 ファイル:mapchip1.tsx(0~128番) mapchip2.tsx(0~128番)ならば
    // gid=129なら id は 129 - 128 = 1で mapchip2 の1番のタイルだと求めるために firstgidは128を返したい
    inline unsigned int firstgid(unsigned int gid)
    {   
        if (gid >= firstgidLog.size()) { firstgidLog.resize(gid + 1, -1); }
        // すでに1回計算済のfirstgidがあればログ辞書↓からその計算済のログを返せば高速
        if (firstgidLog[gid] != -1) return firstgidLog[gid]; // std::vectorなら O( 1 )の計算速度
       
        // lower_boundで2分探索すれば、std::map<int,string>のsize()に対して ★log N の速度で探索できる
        auto it = std::prev(this->lower_bound(gid + 1)); // [参考] https://rsk0315.hatenablog.com/?page=1604038627
        if (it == this->end() || it->first == 0) return firstgidLog[gid] = 1; // 範囲例外のfirstgidは 1
        return firstgidLog[gid] = it->first;
    }

    // gid から ローカルな id に変換する
    inline int id(unsigned int gid) { return gid - firstgid(gid); }

    // gidの最大値(最後尾のfirstgidに.tsxファイルのtilecountを足す)を求める
    inline unsigned int gidMax()
    {
        if (this->size() == 0) return 1;
        auto pair = this->end(); //辞書の最後から一つ後 end()を得る
        --pair;// end()からひとつ前
        if (umTsxInfo.count(pair->second) == 0) return 1;
        return pair->first + umTsxInfo[pair->second].tilecount; // gidの最大値を求める
    }

    inline std::string tilepath(unsigned int gid) const
    {    // lower_boundで2分探索すれば、std::map<int,string>のsize()に対して log N の速度で探索できる
        auto it = std::prev(this->lower_bound(gid + 1)); // [参考] https://rsk0315.hatenablog.com/?page=1604038627
        if (it == this->end() || it->first == 0) return "";
        return it->second;
    }


#if 0
    int operator()(std::string gid) const { // ★()読み取りの処理を定義
        std::priority_queue<int, std::vector<int>, std::greater<int>> priQue;// 昇順 (デフォルトはstd::less<TypeT>)
        for (auto& tileset : list)
            priQue.push(tileset.firstgid);

        int firstgid = 1;
        while (!priQue.empty())
        {
            int topId = priQue.top();
            priQue.pop(); // 一番トップを削除する(取り出す) 計算速度 log N
            if (gid < topId) { break; }
            firstgid = topId;
        }
        return firstgid;
    }
#endif
};


#endif


DataTsx.cppを新規作成して.hのstatic関係の定義を作成します。

#include "DataTsx.h"

std::vector<std::pair<int, std::weak_ptr<Texture>>> TsxInfo::id_images; // タイルのgid → タイルの<id,画像の弱リンク> への辞書

std::unordered_map<hash32, TsxInfo> Tilesets::umTsxInfo; // <ファイルパス, tsxの情報> の static 辞書
std::vector<unsigned int> Tilesets::firstgidLog; // <過去に一度計算したfirstgid, 1回計算済のfirstgid> の static 辞書



さて.tsx形式の次はステージの.json形式のファイルを読み取るためのクラスを作っていきましょう。
まずは.json形式をnloman-jsonで読み取るための基底ベースクラスDataJsonを定義して、
これを継承overrideしてタイルマップエディタのステージの.tmx形式由来の.jsonファイルを読み取るための準備をします。

DataJson.hを新規作成して.json形式をnloman-jsonで読み取るための基底ベースクラスDataJsonを作成します。

#ifndef DATAJSON_H_
#define DATAJSON_H_

#include <fstream> // ファイル読み出しifstreamに必要
#include <string> //文字列に必要
#include <sstream> // 文字列ストリームに必要

#include "json.hpp" // jsonを使用
using json = nlohmann::json;

// .json形式のファイルをnlohman::jsonで読み取るための基底ベースクラス
// このクラスを継承overrideして個別のフォーマットに合わせたファイルの読み取りを実装する
class DataJson
{
public:
    bool isInitialized = false; //[継承2重ロード対策]ベース基底でロードしたらtrueに
    // 読込んだデータファイルの情報
    std::string FilePath{ "" };
    json Data;// jsonデータ

    virtual std::size_t size()
    {
        return Data.size();
    }
    // 初期化コンストラクタでファイル名を指定して初期化と同時にファイル読込
    DataJson(const std::string& FilePath = "") :FilePath{ FilePath }
    {// jsonファイルの読込み
        if (FilePath != "") Load(FilePath); // ファイル読込み
    };
    virtual ~DataJson()
    {// 仮想デストラクタ
        Data.clear();// 2次元配列データのお掃除
        isInitialized = false; //ロード済みフラグをOFF
    };

    // データをクリアしてメモリを節約する
    virtual void clear()
    {   // データをクリアしてメモリを節約する
        Data.clear();// 2次元配列データのお掃除
    }
    // jsonファイルの読み込み
    virtual void Load(const std::string& filePath)
    {
        if (filePath == "" || isInitialized) return; //ファイル名がないもしくはロード済
        FilePath = filePath; // ファイル名を保管
        Data.clear(); //データを一旦クリア

        std::ifstream loadStream(FilePath); // ファイルをロード
        loadStream >> Data; // jsonデータ型に変換

        // コンストラクタの初期化でロードの場合とLoad関数経由で読む経路があるから
        isInitialized = true; // 読込み初期化済みフラグをON
        return;
    }

    // jsonファイルのセーブ
    virtual void Save(const std::string& filePath = "")
    {
        std::string savePath = (filePath != "") ? filePath : this->FilePath;
        if (savePath == "") return; // セーブするファイル名指定が空の場合

        std::ofstream saveStream(FilePath); // ファイルの書き込みストリーム
        saveStream << Data; // jsonデータをセーブ
    }
};

#endif


タイルマップエディタ(.tmx形式)のステージは大元のマップのサイズなどのレイヤーをまたぐ共通情報を持ちます。
まずは、この共通情報を定義するTMXInfoクラスを定義しましょう。

TMXInfo.hを新規作成してタイルマップエディタ(.tmx形式)のレイヤーをまたぐ共通情報を読み取る処理を準備します。

#ifndef TMXINFO_H_
#define TMXINFO_H_

#include <string>
#include <assert.h> // データ読込み失敗表示用

#include "json.hpp" // jsonを使用
using json = nlohmann::json;

struct TMXInfo // TMX形式から書き出されたjsonの持つ基本情報
{
    int width{ 0 }, height{ 0 }, tilewidth{ 0 }, tileheight{ 0 };
    int nextlayerid{ 1 }, nextobjectid{ 1 }, compressionlevel{ -1 };
    bool infinite{ false };
    json* pJson{ nullptr };
    std::string version{ "1.10" }, tiledversion{ "1.10.2" };

    inline TMXInfo(json* pJson = nullptr) : pJson{ pJson }
    {
        if (pJson == nullptr) return;
        *this << *pJson; // jsonからデータをロード
    }
    ~TMXInfo() {}

    inline void operator << (const json& Data)
    {   // TMX形式から出力されたjsonデータの中身を読み取る
        assert(Data.count("width") != 0 && "jsonに width が見つからない");
        width = Data["width"];
        assert(Data.count("height") != 0 && "jsonに height が見つからない");
        height = Data["height"];
        assert(Data.count("tilewidth") != 0 && "jsonに tilewidth が見つからない");
        tilewidth = Data["tilewidth"];
        assert(Data.count("tileheight") != 0 && "jsonに tileheight が見つからない");
        tileheight = Data["tileheight"];

        nextlayerid = Data["nextlayerid"];
        nextobjectid = Data["nextobjectid"];
        compressionlevel = Data["compressionlevel"];
        infinite = Data["infinite"];
        version = Data["version"];
        tiledversion = Data["tiledversion"];
    }
};

struct TMXLayerInfo
{
    enum class Type : short { None, TMXLayer, ObjectGroup, Group };
    int x{ 0 }, y{ 0 }, Width{ 0 }, Height{ 0 }, gid{ 0 };
    float opecity = 1.0f;
    Type type{ Type::None };
    bool visible{ true };
    json* pLayer{ nullptr };
    std::string name{ "" }; // レイヤの名前

    inline TMXLayerInfo(json* pLayer = nullptr) : pLayer{ pLayer }
    {
        if (pLayer == nullptr) return;
        *this << *pLayer;
    }
    ~TMXLayerInfo() {}

    inline void operator << (const json& layerData)
    {
        if (layerData["type"] == "tilelayer") type = Type::TMXLayer;
        else if (layerData["type"] == "objectgroup") type = Type::ObjectGroup;
        else if (layerData["type"] == "group") type = Type::Group;
        if (layerData.count("x") > 0) x = layerData["x"];
        if (layerData.count("y") > 0) y = layerData["y"];
        if (layerData.count("width") > 0) Width = layerData["width"];
        if (layerData.count("height") > 0) Height = layerData["height"];
        if (layerData.count("opecity") > 0) opecity = layerData["opecity"];
        if (layerData.count("visible") > 0) visible = layerData["visible"];
        gid = layerData["id"];
        name = layerData["name"];
    }
};

#endif



さて、ステージの.json形式を読み取る前に、ステージには出現する敵がいますので
.json形式を読み取る際にその敵の出現するスポーン半径の定義も必要ですので、
先に出現レンジを表すMapRangeクラスとグリッドから敵を出現スポーンさせるMapSpawnGridクラスを準備します。

MapRange.hを新規作成して、出現レンジを表すクラスを作成します。

#ifndef MAP_RANGE_H_
#define MAP_RANGE_H_

#include <vector>
#include <map>
#include <unordered_map>

#include "CellXYZ.h" // マス目XYZを辞書配列のキーとするためのクラス

// 敵を出現(スポーン)させるかを判定するマス目処理クラス
class MapRange
{
public:
    // 敵の出現などの射程のレンジ【注意!単位はマス目1マス】
    int CellRangeX{ 30 }, CellRangeY{ 30 }, CellRangeZ{ 0 };

    enum class Type : unsigned char { // 敵の新規出現レンジのタイプ
        Rect,   // 四角の出現レンジ
        Ellipse, // 楕円の出現レンジ
        //Ellipsoid, // 3D楕円体の出現レンジ https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E4%BD%93
    };
    Type type = Type::Rect;
    bool isFirstAccess = true; // 初期位置のときにはレンジ内の敵を全部出す
   
    std::unordered_map<CellXYZ, bool, CellXYZ::Hash> umRange; // 辞書:umRangeOpened[{10,5}]==trueなら出現範囲
   
    MapRange() = default;
    MapRange(int SpawnRangeX, int SpawnRangeY, Type type = Type::Rect)
        : CellRangeX{ SpawnRangeX }, CellRangeY{ SpawnRangeY }, type{ type }
    {
        InitRange(type, SpawnRangeX, SpawnRangeY);
    }
    // 3D版のコンストラクタ
    MapRange(int SpawnRangeX, int SpawnRangeY, int SpawnRangeZ, Type type = Type::Rect)
        : CellRangeX{ SpawnRangeX }, CellRangeY{ SpawnRangeY }, CellRangeZ{ SpawnRangeZ }, type{ type }
    {
        InitRange(type, SpawnRangeX, SpawnRangeY);
    }

    virtual ~MapRange() {} // 仮想デストラクタ

    // 敵出現射程の辞書初期化【先に計算して辞書化】すると計算が必要なくなり【高速化する】
    //【注意!XとYの単位はマス目】
    virtual void InitRange(Type type, int rangeCellX, int rangeCellY, int rangeCellZ = 0)
    {
        this->CellRangeX = rangeCellX; this->CellRangeY = rangeCellY; this->CellRangeZ = rangeCellZ; this->type = type;
        umRange.clear();//一旦辞書をクリアするのでゲーム中の再設定も可(だが処理時間はかかる)
        if (type == Type::Ellipse || type == Type::Rect)
        {
            // 敵出現射程の辞書初期化
            // ★ X = A cosθ Y = B sinθ(←楕円の方程式)
            // ★ 楕円の半径 r = √(A×A×cosθ×cosθ + B×B×sinθ×sinθ)
            // ★ xの2乗 + yの2乗 < rならば楕円の内側
            float A2 = (float)(rangeCellX * rangeCellX); // 2乗
            float B2 = (float)(rangeCellY * rangeCellY); // 2乗
            for (int x = -rangeCellX; x <= rangeCellX; ++x)
            {
                for (int y = -rangeCellY; y <= rangeCellY; ++y)
                {   //★[逆三角関数] https://cpprefjp.github.io/reference/cmath/atan2.html
                    if (type == Type::Ellipse)
                    {   // 楕円の出現レンジにしたいときは
                        float theta = (float)std::atan2(y, x); // ★[逆三角関数]
                        float cos_t = std::cos(theta), sin_t = std::sin(theta);
                        float r2 = A2 * cos_t * cos_t + B2 * sin_t * sin_t;
                        if (x * x + y * y <= r2) // ★ xの2乗 + yの2乗 < rならば楕円の内側
                        {   //楕円の内側なら辞書SpawnDic[{ x,y }] = true;として登録
                            if (rangeCellZ == 0)
                                umRange[CellXYZ(x, y)] = true; //【例】SpawnDic[{ 3,2 }] = true;
                            else
                                for (int z = -rangeCellZ; z < rangeCellZ; ++z)
                                    umRange[CellXYZ(x, y, z)] = true;
                        }
                    }
                    else if (type == Type::Rect)
                    {   //【★四角形の出現レンジにしたいときはこちら】
                        if (rangeCellZ == 0)
                            umRange[CellXYZ(x, y)] = true;
                        else
                            for (int z = -rangeCellZ; z < rangeCellZ; ++z)
                                umRange[CellXYZ(x, y, z)] = true;
                    }
                }
            }
        }
    }

    virtual std::vector<CellXYZ> Spawn(int deltaCellX, int deltaCellY, int deltaCellZ = 0)
    {
        std::vector<CellXYZ> spawnCells; // 新しく開くマス目(敵を新規生成するマス目)
        for (auto iter = umRange.begin(); iter != umRange.end(); iter++)
        {   //★例 iter->firstが"3,1" iter->secondが[trueかfalse]の射程マスかどうかのペア
            if (iter->second == true)
            {   // 例.(3,1)のマス目が出現射程マスか[trueかfalse]か?
                CellXYZ xyz = iter->first; // iter->firstに(x,y)=(3,1)が入っている時
                // 例.↑(3,1)のマス目xy.X = 3, xy.Y = 1
                // 例.deltaCellX = 1 deltaCellY = 2のとき
                int searchX = xyz.X + deltaCellX; //例. 3+1 = 4 移動先は4
                int searchY = xyz.Y + deltaCellY; //例. 1+2 = 3 移動先は3
                int searchZ = xyz.Z + deltaCellZ;
                //例.↓もし(4,3)が未登録もしくはfalseなら【新規出現マス!】
                CellXYZ searchXYZ(searchX, searchY, searchZ);
                if (isFirstAccess)
                    spawnCells.emplace_back(searchXYZ); // 初回アクセスのときはレンジ内の範囲は全部返す
                else if (umRange.count(searchXYZ) == 1)
                {   // 例.辞書に↑(4,3)登録があるならcount( (4,3) )は 1
                    //★例.(4,3)がtrueなら↓【被りマス】移動前にすでに敵の出現判定【済】
                    if (umRange[searchXYZ] == true) continue; // 敵を出現させず別マスの判定を続ける
                }
                else
                    spawnCells.emplace_back(searchXYZ); // 新しく開くマス目(敵を新規生成するマス目)
            }
        }
        if (isFirstAccess) isFirstAccess = false; // 初回アクセスフラグをオフに
        return spawnCells;
    }
};

#endif


MapSpawnGrid.hを新規作成して、マップをScroll関数でスクロールさせたときに敵を出現スポーンさせるため、スクロールで新たに開かれたグリッドを配列で返すクラスを作成します。

#ifndef MAP_SPAWN_GRID_H_
#define MAP_SPAWN_GRID_H_

#include <functional> // スポーン処理をラムダ式で Scroll関数の引数にできるように

#include "MapRange.h"
#include "Vector3.h"
#include "GridObject.h"

class MapSpawnGrid : public Grid
{
public:
    inline MapSpawnGrid(int cellWidth = 64, int cellHeight = 64, int cellDepth = 64)
        : Grid( cellWidth, cellHeight, cellDepth )
    {
       
    }
    virtual ~MapSpawnGrid() {}

    // グリッドをスクロールして敵などのオブジェクトをスポーンさせる
    virtual void Scroll(Vector3 prevPos, Vector3 delta, MapRange& spawnRange, std::function<void(int,int,int)> spawnFunc)
    {
        // マス目を求める
        int nextCellX = cellX(prevPos.x + delta.x);
        int prevCellX = cellX(prevPos.x);
        int nextCellZ = cellY(prevPos.y + delta.y); // マス目なので Z と Y 入れ替え
        int prevCellZ = cellY(prevPos.y);
        int nextCellY = cellZ(prevPos.z + delta.z); // マス目なので Z と Y 入れ替え
        int prevCellY = cellZ(prevPos.z);

        bool isMoveCell = false; // マス目移動が発生するか?発生しないならオブジェクト出現検索処理を回避したい
        if (nextCellX != prevCellX || nextCellY != prevCellY || nextCellZ != prevCellZ)
        {
            isMoveCell = true;
        }

        // マス目移動以外のときはオブジェクト出現検索処理を省いて高速化
        if (isMoveCell)
        {   // 移動が1マス幅を超えればオブジェクト新規出現判定
            int deltaCellX = nextCellX - prevCellX; // セル(マス目)単位での移動数(マス数)
            int deltaCellY = nextCellY - prevCellY; // セル(マス目)単位での移動数(マス数)
            int deltaCellZ = nextCellZ - prevCellZ; // セル(マス目)単位での移動数(マス数)
            //printfDx("\n%d %d", deltaCellX, deltaCellY);

            std::vector<CellXYZ> spawnCells = spawnRange.Spawn(deltaCellX, deltaCellY, deltaCellZ);
            for (const auto& spawnCell : spawnCells)
            {
                int gridX = 0, gridY = 0, gridZ = 0;
                // 現状ポジションを基準に
                gridX = spawnCell.X + prevCellX; // セル(マス目)単位でのX位置
                gridY = spawnCell.Y + prevCellY; // セル(マス目)単位でのY位置
                gridZ = spawnCell.Z + prevCellZ;// セル(マス目)単位でのZ位置
               
                spawnFunc(gridX, gridY, gridZ); // スポーン処理の関数を呼び出し
            }
        }
    }
};

#endif



さて、いよいよ本題のタイルマップエディタ(.tmx形式)の.json形式からステージのタイル番号(gid)と敵などのオブジェクトを読み出す準備に入ります。
まず、事前説明として、ステージにはタイル画像:mapchip.png、mapchip1.png..など「複数のタイルが塗れる必要」があります。
この複数タイルがやっかいです。
なぜなら、タイル番号がかぶる可能性が出るからです。
タイル画像:mapchip.png → 画像の左上のタイル番号 2 番
タイル画像:mapchip1.png → 画像の左上のタイル番号 2 番
上記2つのタイルは全く別物になってほしいですが id = 2 として管理すると
id番号 = 2 のデータはmapchip.pngの2番タイルかmapchip1.pngの2番タイルかごっちゃでわからなくなってしまう、からです。
この「タイル画像が複数あるとidがごっちゃになる問題」の解決策として、タイルマップエディタでは
id ではなく gid(通し番号) として保存しています。
mapchip.pngの中にタイルが64個あるときに
タイル画像:mapchip.png → 画像の左上のタイル番号 2 番 → gid = 2 + 1 = 3
タイル画像:mapchip1.png → 画像の左上のタイル番号 2 番 → gid = 64 + 1 + 2 = 67番
このように gid で管理すれば、複数のタイル画像があっても gid がかぶってごっちゃになることはありません。
+1 しているのはなぜ?と思う人もいるでしょうが、gid = 0 番は特別に「 = 0 タイルがない状態」を意味していますので
そのタイルがない状態ぶんの +1つぶんの番号を ずらすため +1 しています。
ですので「gid(通し番号) 」から 「タイル画像ごとの個別の id 」に戻す際には
id = gid - 64 - 1 = 67 - 64 - 1 = 67 - 65 = 2 で元の id に戻すことができます。 なので、id と gid を変換するには 最低限 64+1 = 65 などのタイルの最初の番号「firstgid」も必要になることがわかると思います。
実際、stage1.jsonのファイルをテキストで開いて、ファイルの最後のほうを見ると
"tilesets":[    {       "firstgid":1,       "source":"mapchip.tsx"    },    {       "firstgid":65,       "source":"mapchip1.tsx"    }], 上記のようにちゃんとmapchip1.tsxのタイルの"firstgid":65 のようにタイルの最初の番号「firstgid」も記録されていることがわかります。
"mapchip.tsx " : id は -1, 0 ~ 63番 gidは 0, 1 ~ 64
"mapchip1.tsx" : id は -1, 0 ~ 63番 gidは 0, 65 ~ 128 → 例.gid:64 - 65 = -1(id) 例.gid:128 - 65 = 63(id)
このように何もない id:-1 gid:0 も含めて firstgid を引き算すれば id と gidの変換のつじつまがあっていることがわかります。
これが複数タイルの番号を数値で分別して記録するカラクリとなっています。

よって、id と gidを区別するために、int型(マイナスあり)、unsigned int型(マイナスのない型)をつかいわけます。
int id;
unsigned int gid;
このように型でマイナスのある無しで数字の形式を区別すれば、変数がgidなのかidなのかをマイナスのある無しを手掛かりに勘違い防止できます。


では、いよいよ gid 以外にも色んなステージのデータを含むタイルマップエディタのデータを読み取るTMXJsonクラスを準備しましょう。
色んなデータを含むということは当然、コード量もとてつもなくデカくなるになることを覚悟してください。

TMXJson.hを新規作成して、タイルマップエディタのステージデータを読み取る「クラス群」を作成します。

#ifndef TMX_JSON_H_
#define TMX_JSON_H_

#include <memory>
#include <vector>
#include <assert.h> // マップ読込み失敗表示用

#include "DataJson.h"
#include "DataTsx.h"
#include "DataCsv.h"
#include "CellXYZ.h" // 敵の配置など、地面のタイルと違って隙間なく埋まっていないタイプのデータはstd::unordered_mapで扱いデータ量を減らす

#include "TMXInfo.h" // .tmx形式の基本データ構造
#include "MyHash.h"

#include "MapSpawnGrid.h"
#include "GridObject.h"

// タイルマップエディタのTMX形式に由来するマップのタイルデータの番号 gid を表すクラス
// ただの数字だけじゃなく、数字を書き換えたときにおおもとのjsonのデータも連動して書き換えるためjson*のリンクも保持できる
class TMX_gid
{
public:
    int gid = 0; // 例.129 のとき Tilesetsで求めるfirstgidが128なら ローカルなidは1番
    unsigned int firstgid = 1; // gid =0 firstgid = 1ならば 0 - 1 = -1でデフォルトは-1になる
    json* pJsonTarget; // 元データのjsonを直で書き換えるためのポインタ参照
    Tilesets*& tilesets; // firstgidを求めるのに必要

public:
    // コンストラクタ
    inline TMX_gid(int gid, json* pJsonTarget, Tilesets*& tilesets)
        : gid{ gid }, pJsonTarget{ pJsonTarget }, tilesets{ tilesets }
    {
        if (tilesets != nullptr)
            this->firstgid = tilesets->firstgid(gid); // TMXのjsonはタイルごとに最初のgidもマス目ごとに保管しないとidかぶって保存されるとjsonとしてセーブするときに困る
    }

    inline ~TMX_gid() {}; // デストラクタ

    void operator=(const int& intValue) // int型の id の代入演算子
    {
        if (intValue <= -1) {
            if (pJsonTarget != nullptr) *pJsonTarget = 0;
            this->gid = 0;
            return;
        }
        if (pJsonTarget != nullptr)
            *pJsonTarget = intValue + firstgid;
        this->gid = intValue + firstgid;
    }
    void operator=(const unsigned int& uintValue) // unsigned int つまり gidの 代入演算子
    {
        if (pJsonTarget != nullptr)
            *pJsonTarget = uintValue;
        if (tilesets != nullptr)
            firstgid = tilesets->firstgid((int)uintValue);
        this->gid = uintValue;
    }
    inline void copy(const TMX_gid& value)
    {
        this->tilesets = value.tilesets;
        this->pJsonTarget = value.pJsonTarget;
        this->gid = value.gid;
        this->firstgid = value.firstgid;
    }
    inline void operator=(const TMX_gid& value) { copy(value); } // 代入演算子
    // コピーコンストラクタ
    inline TMX_gid(const TMX_gid& other)
        : pJsonTarget{ other.pJsonTarget }, gid{ other.gid }, firstgid{ other.firstgid }, tilesets{ other.tilesets }
    { }

    inline operator int() // int型 つまり id の変換オペレータ
    {
        //★gidを引くことでjsonの保存形式の数字からcsvの0から始まる出力に合わせる
        return (gid == 0) ? -1 : gid - firstgid;
    }

    inline operator unsigned int() // unsigned int つまり gidの 変換オペレータ
    {
        return gid;
    }

    inline operator json* () const // おおもとのjsonへのリンクを得る
    {
        return pJsonTarget;
    }
};

// タイルの縦×横のCSV形式のデータの1行を表すためにstd::vector形式を継承したLineを定義
// []オペレータで配列サイズが足りないとき = マップの範囲外に新たにタイル設置した際に配列サイズを増やす機能を持つ
struct TMXLine_gid : public std::vector<TMX_gid>
{
    Tilesets*& tilesets; // firstgidのためにTilesetsのリンクが必要だからTMX_gidに引き渡すための変数定義
    inline TMXLine_gid(Tilesets*& tilesets) : tilesets{ tilesets } {}

    inline TMX_gid& operator[] (size_t x) // 書き込み
    {
        if (x >= size()) // ★xが範囲外のときは拡張(注意:でかい数字を入れるとメモリパニック:使う側の性善説に基づく)
            this->resize(x + 1, { 0, nullptr, tilesets });
        return this->at(x);
    }
};


struct TMXCsv; // 前方宣言

// タイルマップ辞書構造(3D:z方向あり)何もない空中をデータとしてあらかじめ敷き詰めるのはメモリのムダなので辞書構造に
struct TMXterrains : public std::unordered_map<int, std::shared_ptr<TMXCsv>>
{
    TMXCsv& operator[](int z)
    {
        if (this->count(z) == 0)
            this->emplace(z, std::make_shared<TMXCsv>());
        return *this->at(z); // 読取り
    }
#if 0
    inline json& operator() (int x, int y, int z)
    {
        if (this->count(z) == 0)
            this->emplace(z, std::make_shared<TMXCsv>());
        return (*this->at(z))(x, y); // 読取り
    }
#endif
};

// TMX由来の.jsonファイルのオブジェクトごとに設定されているプロパティ
struct TMXProperty
{
    enum class Type : unsigned char { Float, Int, String };
    Type type = Type::Int;
    union {
        int intValue{ 0 };
        float floatValue;
        std::string stringValue;
    };
    std::string name{ "" };
    TMXProperty() = default;
    TMXProperty(const std::string& name, int value) : name{ name }, intValue{ value }, type{ Type::Int } {}
    TMXProperty(const std::string& name, float value) : name{ name }, floatValue{ value }, type{ Type::Float } {}
    TMXProperty(const std::string& name, const std::string& value) : name{ name }, stringValue{ value }, type{ Type::String } {}
    ~TMXProperty() {}
    inline operator int() { return intValue; }
    inline operator float() { return floatValue; }
    inline operator std::string() { return stringValue; }
    inline operator Type() { return type; }
};

// Objectクラスを継承overrideしたGameObjectでTMXのjsonからプロパティデータを受け取って
// 各GameObjectのInit関数での初期化処理を行う
struct InitTMXProperty : public InitParams
{
    // Initが終わったら破棄される一時的な変数なので 生ポインタで tmxのプロパティデータを受け取る
    std::unordered_map<hash32, TMXProperty>* properties;
    inline InitTMXProperty(std::unordered_map<hash32, TMXProperty>* tmxProperties = nullptr)
        : properties{ tmxProperties }, InitParams() {}
    virtual ~InitTMXProperty() {}
};

// TMXのオブジェクトレイヤの敵やライトやアイテムデータをグリッドで出現スポーンするためGridObjectを継承
struct TMXobject : public GridObject
{
    // 出現させるスポーンのタイプ
    enum class SpawnType : unsigned char { Light, Enemy, Item, Object };
    inline TMXobject(Grid* pGrid = nullptr, SpawnType spawnType = SpawnType::Object) : spawnType{ spawnType }, GridObject(pGrid) {};
    virtual ~TMXobject() {}

    float x{ 0 }, y{ 0 }, z{ 0 };
    float rotation{ 0 };
    SpawnType spawnType = SpawnType::Object;
    bool visible{ true };
    bool point{ true };
    //int spawnID{ -1 }; // 敵の種類ID や オブジェクトの種類IDなど
    std::string objectName{ "" };
    std::string baseType{ "" }; // Factory.cppで生成する際に使う「基底ベースクラス名」
    std::string type{ "" }; // Factory.cppで生成する際に使う「クラス名」
    std::unordered_map<hash32, TMXProperty> properties;

    // TMX形式のjsonから読み取ったプロパティの辞書を
    // Object.hのInitParamsをoverride継承したInitTMXProperty型として返す
    // これをFactory.h の MakeSharedObject関数に渡せれば、
    // ★各GameObjectのInit関数の初期化にjsonから読み取ったデータを渡せる
    inline std::shared_ptr<InitTMXProperty> GetInitProperty()
    {
        return std::make_shared<InitTMXProperty>(&properties);
    }

    // jsonから << オペレータでデータを読み取り
    inline void operator << (json& objectData)
    {
        objectName = objectData["name"];
        type = objectData["type"];
        visible = objectData["visible"];
        point = objectData["point"];
        rotation = objectData["rotation"];
        x = objectData["x"], y = objectData["y"], z = 0;

        for (auto&& propertyData : objectData["properties"])
        {
            std::string propertyName = propertyData["name"];
            std::string propertyType = propertyData["type"];
            if (propertyName == "Z" && propertyType == "float") z = propertyData["value"];
            else if (propertyName == "z" && propertyType == "float") z = propertyData["value"];
            //else if (propertyName == "spawnID") spawnID = propertyData["value"];
            else if (propertyType == "float")
            {
                float value = propertyData["value"];
                properties.emplace(std::piecewise_construct, std::forward_as_tuple(propertyName), std::forward_as_tuple(propertyName, value));
            }
            else if (propertyType == "int")
            {
                int value = (int)propertyData["value"];
                properties.emplace(std::piecewise_construct, std::forward_as_tuple(propertyName), std::forward_as_tuple(propertyName, value));
            }
            else
            {
                std::string value = propertyData["value"];
                properties.emplace(std::piecewise_construct, std::forward_as_tuple(propertyName), std::forward_as_tuple(propertyName, value));
            }
        }
    }
};



// 敵の出現のタイルマップのようにほとんどのデータが空白(-1)なのに
// -1でデータを埋めるのはメモリの無駄なのでstd::unordered_mapを内部に持つCellGridを継承して[{0,4,5}] X=0,y=4,Z=5 の辞書配列でデータを保管できる
struct TMXobjects : public MapSpawnGrid
{
    TMXobjects(int cellWidth = 64, int cellHeight = 64, int cellDepth = 64)
        : MapSpawnGrid(cellWidth, cellHeight, cellDepth) {}

    virtual ~TMXobjects() {}

    TMXobject::SpawnType spawnType = TMXobject::SpawnType::Object;
    std::string GroupName{ "" }; // レイヤ名(例."Object0") 複数レイヤz=0~... を一つのCellGrid上のデータCellGrid.dataのunordered_mapで管理する
    std::string BaseName{ "" }; // レイヤ名の先頭(例."Object0"のObject) ★Factory.cppの基底ベースクラスの判定にも使う
    //std::unordered_map<int, int> umObjectsCount; // <レイヤ番号, そのレイヤ階層にあるオブジェクト数>

    std::shared_ptr<MapRange> pSpawnRange{ nullptr };
    std::unordered_map<hash32, TMXProperty> properties; // ユーザの定義した追加プロパティ
    std::list<std::shared_ptr<TMXobject>> data; // オブジェクトのデータの実体をキープする共有リスト
    // データを消す
    void Erase(TMXobject* targetObj)
    {
        auto it = data.begin();
        while (it != data.end()) // リストを回している途中でデータを消すには https://qiita.com/satoruhiga/items/fa6eae09c9d89bd48b5d
            if (it->get() == targetObj)
                it = data.erase(it);
            else ++it;
    }

    //int Width{ 0 }, Height{ 0 };

    // プロパティを読み取り、グリッドのセルのサイズやスポーンのレンジ設定を読み取る
    inline void LoadProperty(json& objectsData)
    {
        assert(objectsData.count("objects") != 0 && "jsonの objectgroup に objects が見つからない");

        if (objectsData.count("properties") == 0) return; // ユーザーの追加定義したプロパティがなければ終了

        // ユーザーの追加定義したプロパティがあれば読み取る
        for (auto&& propertyData : objectsData["properties"])
        {
            std::string propertyName = propertyData["name"];
            std::string propertyType = propertyData["type"];
            if (propertyType == "float")
            {
                float value = propertyData["value"];
                properties.emplace(std::piecewise_construct, std::forward_as_tuple(propertyName), std::forward_as_tuple(propertyName, value));
            }
            else if (propertyType == "int")
            {
                int value = propertyData["value"];
                properties.emplace(std::piecewise_construct, std::forward_as_tuple(propertyName), std::forward_as_tuple(propertyName, value));
            }
            else
            {
                std::string value = propertyData["value"];
                properties.emplace(std::piecewise_construct, std::forward_as_tuple(propertyName), std::forward_as_tuple(propertyName, value));
            }
        }

        // グリッドのサイズの設定が追加プロパティにユーザが定義していたら読み取る なければMapSetting.csvの設定が優先される
        if (properties.count("CellSizeX") > 0) this->cellWidth = properties["CellSizeX"];
        if (properties.count("CellSizeY") > 0) this->cellHeight = properties["CellSizeY"];
        if (properties.count("CellSizeZ") > 0) this->cellDepth = properties["CellSizeZ"];

        // スポーンのレンジ設定がプロパティに追加ユーザが定義していたら読み取る なければMapSetting.csvの設定が優先される
        if (properties.count("SpawnRangeX") > 0)
        {
            int SpawnRangeX{ 10 }, SpawnRangeY{ 10 }, SpawnRangeZ{ 1 };
            MapRange::Type spawnType = MapRange::Type::Ellipse;
            if (properties.count("SpawnType") > 0) { std::string str = properties["SpawnType"]; spawnType = (str == "Ellipse") ? MapRange::Type::Ellipse : MapRange::Type::Rect; }
            if (properties.count("SpawnRangeX") > 0) SpawnRangeX = properties["SpawnRangeX"];
            if (properties.count("SpawnRangeY") > 0) SpawnRangeY = properties["SpawnRangeY"];
            if (properties.count("SpawnRangeZ") > 0) SpawnRangeZ = properties["SpawnRangeZ"];
            pSpawnRange = std::make_shared<MapRange>(SpawnRangeX, SpawnRangeY, SpawnRangeZ, spawnType);
            pSpawnRange->InitRange(spawnType, SpawnRangeX, SpawnRangeY, SpawnRangeZ); // 各マップごとに違うスポーンのレンジ設定があればInitしておく
        }
    }

    // jsonのオブジェクトレイヤからデータを << オペレータで読み取り
    inline void operator << (json& objectsData)
    {
        //assert(GroupName != "" && "レイヤをフィルタするグループ名:GroupNameを設定してから << 読込してください。");
        assert(objectsData.count("objects") != 0 && "jsonの objectgroup に objects が見つからない");

        // プロパティを読み取り、グリッドのセルのサイズやスポーンのレンジ設定を読み取る
        LoadProperty(objectsData); // グリッドのセルのサイズによっては下で使う cellX() cellY() cellZ() 関数の結果が変わってくるので先に "properties"を読み取り

        for (auto&& object : objectsData["objects"])
        {
            std::shared_ptr<TMXobject> pTmxObject = std::make_shared<TMXobject>(this, spawnType); // this は Grid* 型 のリンクでオブジェクトからグリッドへのリンクを張る
            *pTmxObject << object; // jsonから << でデータを読み取り
            data.emplace_back(pTmxObject); // データのキープはこちらの data で行う
        }
    }

    // baseClassName : Factory.cppで生成するときの「基底ベースクラス名」を設定する
    // タイルマップエディタとのスケールの違いの比率を計算して補正した位置のグリッドへ data から objectをaddする
    inline void InitObjects(const std::string& baseClassName, int tilewidth, int tileheight)
    {
        float ratioX = (float)cellWidth / tilewidth;
        float ratioY = (float)cellHeight / tileheight; // タイルマップエディタとのスケールの違いの比率を計算
        float ratioZ = (float)cellDepth / tilewidth;
        for (auto& pTmxObject : data)
        {
            pTmxObject->baseType = baseClassName; // Factory.cppで生成するときの「基底ベースクラス名」を設定
            CellXYZ gridPos{ cellX(pTmxObject->x * ratioX),cellY(pTmxObject->y * ratioY),cellZ(pTmxObject->z * ratioZ) };
            pTmxObject->add(&gridPos); // グリッドのgridPosのマス目位置に オブジェクトへのリンクを追加
        }
    }
};

class TMXJson; // 前方宣言

// タイルマップのステージの.json形式のdata:[1,1,1, ....]部分からマップのタイル番号を読み取りgid_Dataに保持するクラス
struct TMXCsv : public DataCsv
{
    Tilesets* tilesets{ nullptr };
    size_t Width{ 0 }, Height{ 0 };
    int z{ 0 }; // z方向(マップ上の上y方向)
    inline std::size_t size() { return Height; }
    json* pData{ nullptr }; // jsonのおおもとデータへのリンク
    //TMXCsvLine tmxCsvLine{ Width, pData, tilesets };
    std::vector<TMXLine_gid> gid_Data; // タイルのデータはこの配列に保持される
    std::string LayerName{ "" };

    inline TMXCsv(const std::string& FilePath = "")
        : DataCsv() { if (FilePath != "") this->FilePath = FilePath; }
    virtual ~TMXCsv() {}

    inline TMXLine_gid& operator[] (size_t y)
    {
        if (y >= gid_Data.size())
            gid_Data.resize(y + 1, tilesets); // yが範囲外のときは拡張(使う側の性善説に基づく)
        return gid_Data[y];
    }

    // jsonのレイヤからデータを読み取る
    inline void operator << (json& layerData)
    {
        assert(tilesets != nullptr && "tilesetsがnullptrです。tilesetsに=でタイルセットへのリンクを張ってから << 読込してください。");
        assert(layerData.count("data") != 0 && "jsonのレイヤーにdataが見つからない");
        assert(layerData.count("width") != 0 && "jsonのレイヤーにwidthが見つからない");
        assert(layerData.count("height") != 0 && "jsonのレイヤーにheightが見つからない");
        //int dataWidth = layerData["width"], dataHeight = layerData["height"];
        Width = layerData["width"];
        Height = layerData["height"];
        assert(Width > 0 && "jsonのレイヤーのwidthが0以下、データがおかしい");
        assert(Height > 0 && "jsonのレイヤーのheightが0以下、データがおかしい");
        int commaCount = 0; //カンマの数をカウント
        int widthCount = 0; //行の数字の数をカウント
        int readHeight = 0; //行数をカウント
        pData = &layerData["data"];
        TMXLine_gid valuelist{ tilesets }; // tilesetsへの参照を渡す
        for (auto&& gid : *pData)
        {   //幅をカウントアップしlineWidthと一致したら1行ぶんemplace_backする方法で1行区切る(layerのdataには改行がないので)
            ++widthCount;
            ++commaCount;
            //int firstgid = tilesets->firstgid((int)gid);
            valuelist.emplace_back(TMX_gid((int)gid, &gid, tilesets)); // 数字をリスト(1行リスト)に追加
            if (widthCount >= Width)
            {
                // 1行分をvectorに追加
                if (valuelist.size() != 0) gid_Data.emplace_back(valuelist);
                valuelist.clear();//1行push_backできたら次の行読みの為クリア
                ++readHeight; //マップの高さをカウントアップ
                widthCount = 0;//次の行のカウントを再び始めるのでカウント幅を0に
            }
        }
        this->LayerName = layerData["name"];
        this->isInitialized = true;
    }

    inline json& operator () (int x, int y)
    {
        assert(0 <= x && x < Width && 0 <= y && y < Height && "x y のアクセス位置が範囲外です。");
        return (*pData)[x + y * Width];
    }
};

// タイルマップエディタに特化したJSON読み書きクラス
class TMXJson : public DataJson
{
public:
    // 初期化コンストラクタでファイル名を指定して初期化と同時にファイル読込
    TMXJson(const std::string& FilePath = "") :DataJson(FilePath)
    {   // jsonファイルの読込みは継承もとのDataJsonで終わっている前提でデータの中身を事前処理する
        if (FilePath == "") return;

        this->Load(FilePath);
    };

    virtual ~TMXJson() {}

    TMXInfo tmxInfo; // .tmx形式の基本データ
    std::string folderPath{ "" }; // jsonの入っているフォルダ
    Tilesets tilesets; // 塗りのパレットタイル情報のリスト

    struct Group : std::list<int>
    {
        int gid;
        std::list<Group> children;
        inline Group(int _id = 0) : gid{ _id } {}
        inline ~Group() {}
    };
    Group groups; // 複数のレイヤを子に持てるフォルダの gid の 親子構造を保持する

    TMXterrains terrains; // z方向にも複数タイルを保管できる辞書
    TMXobjects lights; //[光源のスポーン] CellGridを継承して辞書配列ベースで 空白 -1 を保管せずにデータを管理
    TMXobjects enemies; //[敵のスポーン] CellGridを継承して辞書配列ベースで 空白 -1 を保管せずにデータを管理
    TMXobjects items; //[アイテムのスポーン] CellGridを継承して辞書配列ベースで 空白 -1 を保管せずにデータを管理
    TMXobjects objects; //[その他のスポーン] CellGridを継承して辞書配列ベースで 空白 -1 を保管せずにデータを管理
    void SetGridSize(Vector3 gridSize)
    {
        lights.cellWidth = gridSize.x; lights.cellHeight = gridSize.y; lights.cellDepth = gridSize.z;
        enemies.cellWidth = gridSize.x; enemies.cellHeight = gridSize.y; enemies.cellDepth = gridSize.z;
        items.cellWidth = gridSize.x; items.cellHeight = gridSize.y; items.cellDepth = gridSize.z;;
        objects.cellWidth = gridSize.x; objects.cellHeight = gridSize.y; objects.cellDepth = gridSize.z;
    }
   
    std::size_t size() { return terrains.size(); }

    // [] オペレータでアクセスされたらterrainsを返す
    inline TMXCsv& operator[] (int z)
    {
        return terrains[z];
    }

    // filePathにあるjson形式のファイルをタイルマップのTMXの形式として読み取る
    virtual void Load(const std::string& filePath)
    {
        if (filePath == "") return; //ファイル名がない
        this->FilePath = filePath;

        // [親フォルダを抽出] https://qiita.com/takano_tak/items/acf34b4a30cb974bab65
        size_t path_i = FilePath.find_last_of("\\/");
        folderPath = FilePath.substr(0, path_i + 1);

        DataJson::Load(FilePath); // 基底クラスDataJsonでjsonをロード

        assert(Data.count("type") != 0 && "jsonファイルの中身が空かタイルマップの.tmx由来じゃないかも");
        assert(Data["type"] == "map" && "jsonファイルがタイルマップの.tmx由来じゃないかも");

        tilesets.LoadTsx(&Data, &folderPath); // .tsxからタイル情報を取得

        tmxInfo << Data; // データの中身を事前読み取り
        SetGridSize({ (float)tmxInfo.tilewidth,(float)tmxInfo.tileheight,(float)tmxInfo.tilewidth });

        assert(Data.count("layers") != 0 && "jsonにlayers情報が見つからない");

        LoadTMXLayer(filePath, Data, groups);
    }

protected:
    void LoadTMXLayer(const std::string& filePath, json& layerData, Group& groupNode, int terrainNum = 0, int objectsNum = 0, int AStarNum = 0)
    {   // 各レイヤをループ
        for (auto& layer : layerData["layers"])
        {
            if (layer["type"] == "tilelayer" || layer["type"] == "objectgroup" || layer["type"] == "group")
            {
                if (layer["type"] == "tilelayer")
                {
                    //[文字列発見] https://cpprefjp.github.io/reference/string/basic_string/find.html
                    std::string layerName = layer["name"];
                    if (layerName.find("AStar") != std::string::npos)
                    {   // "AStar..."てレイヤーなので強制的にAStarNumで0123..と番号をつけなおす
                        layer["name"] = "AStar" + std::to_string(AStarNum++);
                    }
                }
            }
            else
                assert("対応していない形式(tilelayer,objectgroup,group以外)のレイヤーが見つかりました(タイルエディタで未対応形式のレイヤー作ってない?)" == "");

            if (layer["type"] == "group") // レイヤのタイプが groupのとき
            {
                groupNode.children.emplace_back((int)layer["id"]);
                LoadTMXLayer(filePath, layer, groupNode.children.back(), terrainNum, objectsNum, AStarNum);
            }
            else if (layer["type"] == "objectgroup") // レイヤのタイプが objectgroupのとき
            {
                groupNode.emplace_back((int)layer["id"]);
                //bool isTerrain = false;
                objects.GroupName = layer["name"];
                if (objects.GroupName.find("Enemy") != std::string::npos)
                {
                    enemies.spawnType = TMXobject::SpawnType::Enemy;
                    enemies << layer; // 敵のスポーンデータを読み取り
                    enemies.InitObjects("Enemy", tmxInfo.tilewidth, tmxInfo.tileheight);
                }
                else if (objects.GroupName.find("Light") != std::string::npos)
                {
                    lights.spawnType = TMXobject::SpawnType::Light;
                    lights << layer; // 光源のデータを読み取り
                    lights.InitObjects("Light", tmxInfo.tilewidth, tmxInfo.tileheight);
                }
                else if (objects.GroupName.find("Item") != std::string::npos)
                {
                    items.spawnType = TMXobject::SpawnType::Item;
                    items << layer; // アイテムのデータを読み取り
                    items.InitObjects("Item", tmxInfo.tilewidth, tmxInfo.tileheight);
                }
                else
                {
                    objects.spawnType = TMXobject::SpawnType::Object;
                    objects << layer; // その他オブジェクトのデータを読み取り
                    objects.InitObjects("Object", tmxInfo.tilewidth, tmxInfo.tileheight);
                }
            }
            else if (layer["type"] == "tilelayer")
            {
                assert(layer.count("width") != 0 && layer.count("height") != 0 && "json(TMX)のレイヤーのwidthかheightの情報がない??");
                groupNode.emplace_back((int)layer["id"]);
                bool isTerrain = false;
                std::string layerName = layer["name"];
                {
                    auto pos = layerName.find("terrain");
                    if (pos != std::string::npos)
                    {
                        size_t endPos = std::string("terrain").size();
                        std::string numStr = layerName.substr(pos + endPos, layerName.size() - 1);
                        std::istringstream ss(numStr); //文字列ストリーム
                        int numZ; ss >> numZ;
                        if (pos != std::string::npos)
                        {
                            auto& terrainZ = terrains[numZ];
                            terrainZ.tilesets = &(this->tilesets);
                            terrainZ.FilePath = this->FilePath;
                            terrainZ.z = numZ;
                            terrainZ << layer; // layer["data"]を <<で読み取る
                            isTerrain = true;
                        }
                    }
                }
            }
        }
    }
};

#endif



CollisionParams.hを新規作成して、jsonから衝突関連のパラメータを読み取れるようにしましょう。

#ifndef COLLISIONPARAMS_H_
#define COLLISIONPARAMS_H_

#include "TMXJson.h"
#include "Object.h"

struct CollisionParams : public InitParams
{    //[離散要素法] https://qiita.com/konbraphat51/items/157e5803c514c60264d2
    float k = 10.0f; // バネ係数(めり込みに対するバネ反発の掛け率)
    float e = 0.3f; // 跳ね返り係数(直前と比べどれくらいの速さで離れるか 0)
    inline float log_e() { return std::log(e); };
    const float pi_2 = DX_PI_F * DX_PI_F;
    // 粘性係数
    inline float eta(float mass) { return -2.0f * log_e() * std::sqrt(k * mass / (log_e() * log_e() + pi_2)); }

    float pushPower{ 5.0f }; // kやeの設定がなかった場合はデフォルトのpushPowerで法線方向に押し返す
    bool isDEMcollision = false; //[離散要素法:DEM]のパラメータを使用するか(falseのときはpushPowerで単純な押し戻し)

    inline CollisionParams(std::shared_ptr<InitParams> initParams = nullptr)
    {
        if (initParams == nullptr) return;
        auto tmxParams = std::static_pointer_cast<InitTMXProperty>(initParams);
        if (tmxParams->properties == nullptr) return;
        auto tmxProperties = tmxParams->properties;

        // tmxのjsonの追加プロパティからデータを読み取る
        auto itr = tmxProperties->find("collision_e");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の collision_e のタイプが Floatでない!jsonのプロパティを見直してください");
            e = itr->second.floatValue;
            isDEMcollision = true;
        }

        itr = tmxProperties->find("collision_k");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の collision_k のタイプが Floatでない!jsonのプロパティを見直してください");
            k = itr->second.floatValue;
            isDEMcollision = true;
        }

        itr = tmxProperties->find("pushPower");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の pushPower のタイプが Floatでない!jsonのプロパティを見直してください");
            pushPower = itr->second.floatValue;
        }
    }
    ~CollisionParams() {}
};


#endif



いかがでしょう?ここまでで、一旦ビルドしてエラーがでないか確かめておきましょう。
コード量が多いのでここまでで一旦エラー原因を絞り込むためです。
タイルの世界を表示させるにはまだまだ必要なものが沢山あります。
タイル世界だけでなく、普通の3Dの世界も「jsonの"objectgroup"レイヤに色んな3Dファイルを配置」すれば作成できます。
本来2Dのタイルゲーム向けのエディタのjson構造を自分で解析して、Z方向に積み上げたり、
"objectgroup"レイヤを応用して3Dのゲームのデータにも拡張できるように対応しているのです。
jsonやxmlなどのよく知られたフォーマットを知っていれば、
ツールのデータ構造を調査して、自分のゲーム向けにデータ構造を工夫すれば「2Dツールなのに3Dゲームを作る」ことに応用できてしまうのです。
つまり、大事なのは「誰かが決めた仕様のツールを使える」ことではなく「自分のデータを作るためにツールを自分の用途向けに応用できる」ことなのです。
「ツールに振り回される」のではなく「自分の必要なデータ目的のためにツールを振り回す」ためには「世界共通のフォーマットを勉強して知っている」ほうが重要なのです。
「タイルマップエディタが使えること」には大した意味などありません。
ひるがえって「Unityを使えること」にも「かつてのAdobeのFlashのオワコン化」のように意味がなくなる時代が来るかもしれません

ほんとうに応用の効く重要なことは「ツールよりデータ構造」なのです。
「XML」「JSON」などを読み取れるスキルは応用が効きます。しっかり勉強する意味があります。





つづいてタイルの当たり判定などの「タイル自体の持つ特性の情報の定義」に移ります。
jsonのステージには「タイルの番号」は保持されていますが、
それはあくまでもタイル番号にすぎないわけで「そのタイル番号が壁なのか床なのか壊せるのか」などの情報はまた別物です。

まずは、タイルにどの箇所から当たったかを表すヒット箇所 HitPartをビットとして表すデータ定義をしておきます。

HitPart.hを新規作成して、タイルなどに当たった箇所を表すデータ定義を作成します。

#ifndef HIT_PART_H_
#define HIT_PART_H_

#include <string>

// タイルなどに当たった箇所を表すenum と Bit演算 HitBit型(int)を使う

typedef int HitBit; // 当たった箇所を表すビット特性 Left | Top なら 左上部分が当たった の特性を表す

enum class HitPart : HitBit // 当たった箇所を表すビット
{
    None = 0,
    Left     = 0b0000'0000'0000'0000'0000'0000'0000'0001,
    Right    = 0b0000'0000'0000'0000'0000'0000'0000'0010,
    Top      = 0b0000'0000'0000'0000'0000'0000'0000'0100,
    Bottom  = 0b0000'0000'0000'0000'0000'0000'0000'1000,
    XMiddle = 0b0000'0000'0000'0000'0000'0000'0001'0000,
    YMiddle = 0b0000'0000'0000'0000'0000'0000'0010'0000,
    Forward = 0b0000'0000'0000'0000'0000'0000'0100'0000,
    Back    = 0b0000'0000'0000'0000'0000'0000'1000'0000,
    ZMiddle = 0b0000'0000'0000'0000'0000'0001'0000'0000,
};



// 文字列 stringType から HitPart型に変換する
inline HitPart enumHitPart(const std::string& stringType)
{
    return
        (stringType == "Left") ? HitPart::Left :
        (stringType == "Right") ? HitPart::Right :
        (stringType == "Top") ? HitPart::Top :
        (stringType == "Bottom") ? HitPart::Bottom :
        (stringType == "XMiddle") ? HitPart::XMiddle :
        (stringType == "YMiddle") ? HitPart::YMiddle :
        (stringType == "Forward") ? HitPart::Forward :
        (stringType == "Back") ? HitPart::Back :
        (stringType == "ZMiddle") ? HitPart::ZMiddle :
        HitPart::None;
}


// [ビット論理和 演算子 | のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator | (HitPart left, HitPart right) { return static_cast<HitBit>(left) | static_cast<HitBit>(right); }
// [ビット論理和 演算子 | のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator | (HitBit left, HitPart right) { return left | static_cast<HitBit>(right); }
// [ビット論理和 演算子 | のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator | (HitPart left, HitBit right) { return static_cast<HitBit>(left) | right; }

// [ビット論理積 演算子 & のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator & (HitPart left, HitPart right) { return static_cast<HitBit>(left) & static_cast<HitBit>(right); }
// [ビット論理積 演算子 & のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator & (HitBit left, HitPart right) { return left & static_cast<HitBit>(right); }
// [ビット論理積 演算子 & のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator & (HitPart left, HitBit right) { return static_cast<HitBit>(left) & right; }

// [ビット排他的論理和 演算子 ^ のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator ^ (HitPart left, HitPart right) { return static_cast<HitBit>(left) ^ static_cast<HitBit>(right); }
// [ビット排他的論理和 演算子 ^ のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator ^ (HitBit left, HitPart right) { return left ^ static_cast<HitBit>(right); }
// [ビット排他的論理和 演算子 ^ のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr HitBit operator ^ (HitPart left, HitBit right) { return static_cast<HitBit>(left) ^ right; }

// [一致演算子 == ]のオーバーロード HitPart型を HitBit型に変換して比較
inline constexpr bool operator == (HitPart left, HitBit right) { return static_cast<HitBit>(left) == right; }
// [一致演算子 == ]のオーバーロード HitPart型を HitBit型に変換して比較
inline constexpr bool operator == (HitBit left, HitPart right) { return left == static_cast<HitBit>(right); }

// [不一致演算子 != ]のオーバーロード HitPart型を HitBit型に変換して比較
inline constexpr bool operator != (HitPart left, HitBit right) { return !(left == right); }
// [不一致演算子 != ]のオーバーロード HitPart型を HitBit型に変換して比較
inline constexpr bool operator != (HitBit left, HitPart right) { return !(left == right); }

#endif


つづいて、壁や床や壊せるブロックなどタイルの特性をビットとして取り扱えるようにするTileクラスを準備します。

Tile.hを新規作成して、壁、床、壊せるなどのビット特性を表現する定義と設定を「Map/MapSetting.csv」から読み出すクラスを作成します。

#ifndef TILE_H_
#define TILE_H_

#include <string> //文字列に必要
#include <unordered_map>
#include <vector>

#include "DataCsv.h"
#include "DataTsx.h"
#include "MyDraw.h"
#include "MyHash.h" // hash32でタグを高速化

#include "HitPart.h" // タイルに当たった箇所を表すenum と Bit演算 HitBit型(int)を使う

typedef int TileBit; // タイルのビット特性 Wall | Bounce なら はずむ壁 の特性を持つ

enum class TileType : TileBit //★ブロックの特性を32桁のビットで表現
{
    None = 0,
    Wall =      0b0000'0000'0000'0000'0000'0000'0000'0001,
    Damage = 0b0000'0000'0000'0000'0000'0000'0000'0010,
    Bounce = 0b0000'0000'0000'0000'0000'0000'0000'0100, // 叩くと弾むブロック
    Change = 0b0000'0000'0000'0000'0000'0000'0000'1000, // 叩くとブロックの種類が変わる
    Switch =  0b0000'0000'0000'0000'0000'0000'0001'0000, // スイッチを叩くと赤ブロックや青ブロックの無効⇔有効が切り替え
    Goal =     0b0000'0000'0000'0000'0000'0000'0010'0000,
    Floor =    0b0000'0000'0000'0000'0000'0000'0100'0000,
};

// 文字列 stringType から TileType型に変換する
inline TileType enumTileBit(const std::string& stringType)
{
    return
        (stringType == "Wall") ? TileType::Wall :
        (stringType == "Damage") ? TileType::Damage :
        (stringType == "Bounce") ? TileType::Bounce :
        (stringType == "Change") ? TileType::Change :
        (stringType == "Switch") ? TileType::Switch :
        (stringType == "Goal") ? TileType::Goal :
        (stringType == "Floor") ? TileType::Floor :
        TileType::None;
}


// [ビット論理和 演算子 | のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator | (TileType left, TileType right) { return static_cast<TileBit>(left) | static_cast<TileBit>(right); }
// [ビット論理和 演算子 | のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator | (TileBit left, TileType right) { return left | static_cast<TileBit>(right); }
// [ビット論理和 演算子 | のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator | (TileType left, TileBit right) { return static_cast<TileBit>(left) | right; }

// [ビット論理積 演算子 & のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator & (TileType left, TileType right) { return static_cast<TileBit>(left) & static_cast<TileBit>(right); }
// [ビット論理積 演算子 & のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator & (TileBit left, TileType right) { return left & static_cast<TileBit>(right); }
// [ビット論理積 演算子 & のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator & (TileType left, TileBit right) { return static_cast<TileBit>(left) & right; }

// [ビット排他的論理和 演算子 ^ のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator ^ (TileType left, TileType right) { return static_cast<TileBit>(left) ^ static_cast<TileBit>(right); }
// [ビット排他的論理和 演算子 ^ のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator ^ (TileBit left, TileType right) { return left ^ static_cast<TileBit>(right); }
// [ビット排他的論理和 演算子 ^ のオーバーロードでenum型を暗黙変換] https://marycore.jp/prog/cpp/enum-bitmask-bitwise-operation/
inline constexpr TileBit operator ^ (TileType left, TileBit right) { return static_cast<TileBit>(left) ^ right; }

// [一致演算子 == ]のオーバーロード HitPart型を HitBit型に変換して比較
inline constexpr bool operator == (TileType left, TileBit right) { return static_cast<TileBit>(left) == right; }
// [一致演算子 == ]のオーバーロード HitPart型を HitBit型に変換して比較
inline constexpr bool operator == (TileBit left, TileType right) { return left == static_cast<TileBit>(right); }

// [不一致演算子 != ]のオーバーロード HitPart型を HitBit型に変換して比較
inline constexpr bool operator != (TileType left, TileBit right) { return !(left == right); }
// [不一致演算子 != ]のオーバーロード HitPart型を HitBit型に変換して比較
inline constexpr bool operator != (TileBit left, TileType right) { return !(left == right); }


struct Tile // タイル情報
{
    static constexpr unsigned int None = 0; // 何もないことを表す gid

    Tile() = default;
    inline Tile(int id, float cost = 1.0f, TileBit typeBit = (TileBit)TileType::None, const std::string& typeTag = "", const CsvValue& value = CsvValue())
        : id{ id }, cost{ cost }, typeBit{ typeBit }, typeTag{ typeTag }, value{ value } {};
    ~Tile() {};// デストラクタ

    int id = -1; // タイルid
    float cost = 1; // AIの経路判定で使う移動コスト
    TileBit typeBit = (TileBit)TileType::None; // タイルのタイプ
    int changeID{ 0 }; // 変化先ID
    hash32 typeTag{ "" }; // タイルのタグ
    CsvValue value{ "" }; // 値(ダメージ値など 文字列も使用できる)
    std::vector<int> syncIDs; // 連動するID (sync:シンクロ)ブロックを叩いたら連動して他も変わるなどに使う
};

// "Map/MapSettings.csv"から読み出すタイルの情報
struct TileInfo
{
protected:
    TileBit noneBit{ 0 }; // id = -1 のときのBit
    // <gid→idに対応したタイプのbit> へ変換するvector型の辞書
    std::vector<TileBit> idTypeBits;
public:
    std::unordered_map<int, Tile> tiles; // タイル情報
    inline Tile& operator[](int id) {
        return tiles[id]; // &参照で読み書き
    }

    inline TileBit& idTypeBit(int id)
    {
        if (id >= (int)idTypeBits.size()) idTypeBits.resize(id + 1, 0);
        return (id < 0) ? noneBit : idTypeBits[id];
    }
    // id→ビットのタイプ の辞書に |= でbitTypeをビット論理和して、Wall:壁 や Bounce:はずむ などの特性を追加する
    TileBit SetTypeBit(int id, TileBit bitType) { return idTypeBit(id) |= bitType; }

    // id→ビットのタイプ の辞書で & ビット論理積で 指定したidが Wall:壁 や Bounce:はずむ などの特性を持つかを判定する
    inline bool isType(int id, TileType bitType) { return idTypeBit(id) & bitType; }

    // id→ビットのタイプ の辞書で & ビット論理積で 指定したidが Wall:壁 や Bounce:はずむ などの特性を持つかを判定し一致したBitを返す(複数が1のビットもありうる)
    inline TileBit matchTypes(int id, TileBit bitType) { return idTypeBit(id) & bitType; }

    inline bool IsWall(int id) {
        return isType(id, TileType::Wall);
    }
};

// "Map/MapSettings.csv"からタイルの設定情報を読み出すクラス
struct TileSetting
{
    TileSetting(const std::string& csvFileName = "")
    {
        if (csvFileName != "") Load(csvFileName);
    }
    ~TileSetting() {} //デストラクタ

    //[辞書].tsxファイル名→タイル情報
    std::unordered_map<hash32, std::shared_ptr<TileInfo>> info;

    inline std::shared_ptr<TileInfo> operator[](const std::string& tsxPath) {
        return info[tsxPath]; // 読み書き
    }

    bool isInitialized = false; //[継承2重ロード対策]
    std::string FilePath{ "" }; // csvのファイルパス
    DataCsv Data; // マップ情報一覧のデータ

    // 例.csvのセルに"Wall | Bounce | Change"と書かれたいたら TileType::Wall Bounce Changeが1となったビットを返す
    TileBit bitFromCsvCell(const std::string& csvCellString)
    {
        TileBit returnBit = (TileBit)TileType::None;
        if (csvCellString == "") return returnBit;
        std::istringstream ss(csvCellString); // 文字列ストリーム
        std::string typeString; // | での分割文字列
        while (std::getline(ss, typeString, { '|' }))
        {   // [std::isspaceで空白除去] https://b.0218.jp/20150625194056.html
            typeString.erase(std::remove_if(typeString.begin(), typeString.end(), std::isspace), typeString.end());
            returnBit |= (TileBit)enumTileBit(typeString); // ビット論理和でタイルのビット特性を付加していく 例. Wall | Bounce はずむ壁
        }
        return returnBit;
    }

    // 例..csvのセルに"12 | 13 | 15"と書かれていたら配列に[12][13][15]を入れて返す
    std::vector<int> intFromCsvCell(const std::string& csvCellString)
    {
        std::vector<int> returnInts;
        if (csvCellString == "") return returnInts;
        std::istringstream ss(csvCellString); // 文字列ストリーム
        std::string intString; // | での分割文字列
        while (std::getline(ss, intString, { '|' }))
        {   // [std::isspaceで空白除去] https://b.0218.jp/20150625194056.html
            intString.erase(std::remove_if(intString.begin(), intString.end(), std::isspace), intString.end());
            returnInts.emplace_back((int)std::stol(intString)); // stolで文字列→数値に
        }
        return returnInts;
    }


    void Load(const std::string& csvFileName = "")
    {
        if (isInitialized) return; // 2重ロード対策
        if (csvFileName != "") this->FilePath = csvFileName;
        if (this->FilePath == "") return;

        Data.Load(this->FilePath, DataCsv::CsvType::CsvValue);// CSVファイルからデータをロード
        std::string tsxFile; //tsxファイル名
        for (int j = 0; j < Data.size(); ++j)
        {
            //std::string test = (std::string)Data[j][0];
            if ((std::string)Data[j][0] == "//") continue; // 一番左の列が//なら読み飛ばしのコメント行
            else if ((std::string)Data[j][0] == "TsxFile")
            {
                tsxFile = Data[j][1]; //tsxファイル名
                info.emplace(tsxFile, std::make_shared<TileInfo>());
                continue;
            }

            if (tsxFile == "") continue; //ステージファイル名が未確定
            else if (Data[j].size() > 1 && (std::string)Data[j][1] != "")
            {
                int tileID = Data[j][1]; // タイルID
                info[tsxFile]->tiles[tileID].id = tileID; // タイルIDを辞書登録
                if ((std::string)Data[j][2] != "")
                    info[tsxFile]->tiles[tileID].cost = Data[j][2]; //コスト登録
                info[tsxFile]->tiles[tileID].typeTag = (std::string)Data[j][3]; //タグ登録
                if ((std::string)Data[j][4] != "") // タイプ登録 None,Wall,Floor..など
                    info[tsxFile]->tiles[tileID].typeBit = info[tsxFile]->SetTypeBit(tileID, bitFromCsvCell(Data[j][4]));
                if (Data[j].size() > 5) // 値(ダメージ値など)
                    info[tsxFile]->tiles[tileID].value = Data[j][5];
                if (Data[j].size() > 6) // 変化先ID
                    info[tsxFile]->tiles[tileID].changeID = Data[j][6];
                if (Data[j].size() > 7) // 連動ID
                    info[tsxFile]->tiles[tileID].syncIDs = std::move(intFromCsvCell(Data[j][7]));
            }
        }
        isInitialized = true;
    }
};

#endif



さてここでいよいよマップを表すMap.hを定義しましょう。

Map.hを新規作成して、タイルマップエディタで作成したステージを表現するMapクラスを作成してタイルマップを3D表示させます(現状当たり判定と敵の出現はまだない)。

#ifndef MAP_H_
#define MAP_H_

#include <vector> // C#におけるListリストの機能
#include <unordered_map>    // 辞書型連想配列に必要
#include <memory> // メモリを扱う【共有ポインタに必要】
#include <limits> //intやfloatなどの無限大∞に使う  https://cpprefjp.github.io/reference/limits/numeric_limits/max.html

#include <fstream> // ファイル読み出しifstreamに必要
#include <string> //文字列に必要
#include <sstream> // 文字列ストリームに必要

#include <assert.h> // マップ読込み失敗表示用

#include "Screen.h"
#include "MyMath.h"
#include "MyHash.h"
#include "MyDraw.h"
#include "DataCsv.h"
#include "TMXJson.h"

#include "Singleton.h"
#include "GameObject.h"
#include "World.h"
#include "Camera.h"

#include "MapRange.h" // マス目のレンジ表現するクラス

#include "Tile.h"

#include "Vector3.h"

class AI_Map; // 前方宣言

// csvから読み取るマップの基本情報
struct MapInfo
{
    MapInfo() = default;
    inline MapInfo(unsigned int CellSize, int SpawnRangeX, int SpawnRangeY, MapRange::Type type = MapRange::Type::Rect)
        : CellSize{ CellSize }, spawnRange{ MapRange(SpawnRangeX,SpawnRangeY,type) } {};
    ~MapInfo() {};

    unsigned int CellSize{ 1 }; // マップの1マスのピクセル数
    MapRange spawnRange; // 敵の新規出現レンジ
};

struct MapSetting // マップ情報をMapSetting.csvなどから読みだす
{
    MapSetting(const std::string& csvFileName = "")
    {
        if (csvFileName != "")
            Load(csvFileName);
    }
    ~MapSetting() {}; //デストラクタ

    std::string FilePath{ "" }; // csvのファイルパス

    std::unordered_map<hash32, std::shared_ptr<MapInfo>> info; //[辞書]stageファイル名→マップ情報

    MapInfo& operator[](const std::string& stageFilePath) {
        assert(info.count(stageFilePath) != 0 && "マップのステージ情報が読み取れていません Map/MapSettings.csv にステージ情報があるか確認!");
        return *info[stageFilePath]; // 読み書き
    }

    void Load(const std::string& csvFileName = "")
    {
        if (csvFileName != "") this->FilePath = csvFileName;
        if (this->FilePath == "") return;

        // マップ情報のデータ CSVファイルからデータをロード
        DataCsv Data{ this->FilePath, DataCsv::CsvType::CsvValue };
        std::string stageFile; //ステージファイル名
        for (int j = 0; j < Data.size(); ++j)
        {
            if ((std::string)Data[j][0] == "//")
                continue; // 一番左の列が//なら読み飛ばしのコメント行
            else if ((std::string)Data[j][0] == "File")
            {
                stageFile = Data[j][1]; //ステージファイル名
                info.emplace(stageFile, std::make_shared<MapInfo>());
                continue;
            }

            if (stageFile == "") continue; //ステージファイル名が未確定
            else if ((std::string)Data[j][0] == "CellSize")
                info[stageFile]->CellSize = (int)Data[j][1]; //マス目サイズ
            else if ((std::string)Data[j][0] == "SpawnRange")
            {   // 敵出現レンジ
                MapRange::Type rangeType = ((std::string)Data[j][3] == "Ellipse") ? MapRange::Type::Ellipse : MapRange::Type::Rect; //敵出現のタイプ
                info[stageFile]->spawnRange.InitRange(rangeType, Data[j][1].intFromString(), Data[j][2].intFromString()); //敵出現のタイプを初期化設定
            }
        }
    }
};

// ステージのタイルマップのクラス
class Map
{
public:
    // コンストラクタ
    Map(World* world, const std::string& filePath = "")
        : m_world{ world }
    {
        if (filePath == "") return;

        Load(filePath);
    }

    // デストラクタ
    ~Map()
    {   // マップデータのお掃除
        enemyData.clear();
        //terrains.clear(); //★地形のお掃除
    }

    static MapSetting settings; // マップに関する設定 Map.cpp で初期読込み デフォルトは"Map/MapSetting.csv" を読み込む
    static TileSetting tileSettings; // タイルに関する設定 Map.cpp で初期読込み デフォルトは"Map/MapSetting.csv" を読み込む
    // タイルのチップ画像のパスとidからTileの情報とタイルのgid情報を得る
    static Tile GetTile(const std::string& mapChipPath, int id, unsigned int& gid)
    {
        auto pos = mapChipPath.find(".", 0);
        std::string tsxPath{ "" };
        if (pos != std::string::npos) tsxPath = mapChipPath.substr(0, pos) + ".tsx";
        auto found = Tilesets::umTsxInfo.find(tsxPath);
        if (found != end(Tilesets::umTsxInfo) && found->second.gid.size() > id)
            gid = found->second.gid[id]; // 辞書で id から gidに変換しておく
        else return Tile(); // id が-1のときは見つからなかったとき
        pos = tsxPath.find("/", 0);
        if (pos != std::string::npos) tsxPath = tsxPath.substr(pos + 1, tsxPath.size() - 1);
        return Map::tileSettings.info[tsxPath]->tiles[id];
    }

protected:
    World* m_world{ nullptr }; // マップに配置する敵などのポインタを保持するワールド
public:
    // 現在いるワールドへのポインタを得る
    World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    void world(World* changeWorld) { m_world = changeWorld; }

    std::shared_ptr<AI_Map> aiMap{ nullptr }; // AIの経路探索に使うAIマップ

    // 読込んだデータファイルの情報
    std::string FilePath{ "" };
protected:
    unsigned int m_CellSize{ 1 }; // マップの1マスのピクセル数 1のときはtmxに設定されたサイズが採用される
    unsigned int m_HalfCellSize{ 1 }; // マス目サイズの 1/2
public:
    inline unsigned int CellSize() { return m_CellSize; }
    inline unsigned int HalfCellSize() { return m_HalfCellSize; }
    inline void SetCellSize(unsigned int cellSize) { m_CellSize = cellSize; m_HalfCellSize = m_CellSize / 2; }
    MapRange spawnRange; // 敵の新規出現レンジをプレイヤから半径X,Yの楕円にする

    inline float centerX(int cellX) const { return (cellX >= 0) ? (float)((cellX * m_CellSize) + m_HalfCellSize) : (float)((cellX + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline float centerY(int cellY) const { return (cellY >= 0) ? (float)((cellY * m_CellSize) + m_HalfCellSize) : (float)((cellY + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline float centerZ(int cellZ) const { return (cellZ >= 0) ? (float)((cellZ * m_CellSize) + m_HalfCellSize) : (float)((cellZ + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline int cellX(float worldX) const { return (worldX >= 0) ? (int)worldX / m_CellSize : (int)worldX / m_CellSize - 1; } // マイナス方向にも対応
    inline int cellY(float worldY) const { return (worldY >= 0) ? (int)worldY / m_CellSize : (int)worldY / m_CellSize - 1; } // マイナス方向にも対応
    inline int cellZ(float worldZ) const { return (worldZ >= 0) ? (int)worldZ / m_CellSize : (int)worldZ / m_CellSize - 1; } // マイナス方向にも対応

    TMXCsv enemyData;// 敵配置データ
    TMXJson tmxData; // タイルマップエディタのjsonデータ

protected:
    // <gid→idに対応したタイプのbit> へ変換するvector型の辞書
    std::vector<TileBit> gidTypeBits{ 0 };
public:
    inline TileBit& gidTypeBit(unsigned int gid)
    {
        if (gid >= (int)gidTypeBits.size()) gidTypeBits.resize(gid + 1, 0);
        return (gid < 0) ? gidTypeBits[0] : gidTypeBits[gid];
    }
    // id→ビットのタイプ の辞書に |= でbitTypeをビット論理和して、Wall:壁 や Bounce:はずむ などの特性を追加する
    TileBit SetTypeBit(unsigned int gid, TileBit bitType) { return gidTypeBit(gid) |= bitType; }

    // id→ビットのタイプ の辞書で & ビット論理積で 指定したidが Wall:壁 や Bounce:はずむ などの特性を持つかを判定する
    inline bool isType(unsigned int gid, TileType bitType) { return gidTypeBit(gid) & bitType; }

    // id→ビットのタイプ の辞書で & ビット論理積で 指定したidが Wall:壁 や Bounce:はずむ などの特性を持つかを判定し一致したBitを返す(複数が1のビットもありうる)
    inline TileBit matchTypes(unsigned int gid, TileBit bitType) { return gidTypeBit(gid) & bitType; }

    // tilesetsの.tsxのfirstgidをたどって、SetTypeBitでビット辞書に
    // csvのtileSettingsから読み取っておいたビット設定を登録
    bool InitTypeBit(const Tilesets& tilesets)
    {
        assert(tilesets.size() > 0 && "ステージのjsonファイルにタイルセット設定が一つもありません");
        if (tilesets.size() == 0) return false;
        for (auto& pair : tilesets)
        {
            auto firstgid = pair.first;
            auto& tsxFilePath = pair.second;
            auto& tsxInfo = tilesets.umTsxInfo.at(tsxFilePath);
            // [親フォルダを抽出] https://qiita.com/takano_tak/items/acf34b4a30cb974bab65
            size_t path_i = tsxFilePath.find_last_of("\\/");
            if (path_i + 1 >= tsxFilePath.size()) continue;
            auto tsxFile = tsxFilePath.substr(path_i + 1);
            auto csvInfo = tileSettings.info.find(tsxFile);
            if (csvInfo == end(tileSettings.info)) continue; // csv側にタイルの情報がなかった
            // gidが0 idが-1 のデフォルトのタイルのビット特性を設定
            SetTypeBit(0, csvInfo->second->idTypeBit(-1));
            int id = 0; // gidじゃなく0から始まるローカルなid
            for (auto gid = firstgid, iEnd = firstgid + tsxInfo.tilecount; gid < iEnd; ++gid)
            {   // このMapのgidのビット辞書にcsvのtileSettingsから読み取っておいたビット設定を登録
                SetTypeBit(gid, csvInfo->second->idTypeBit(id));
                ++id; // ローカルなidも次に進める
            }
        }
    }

    // マップファイルのロード
    void Load(const std::string& filePath)
    {
        this->FilePath = filePath;

        // ★jsonファイルの読み込み
        tmxData.Load(filePath);

        // tilesetsの.tsxのfirstgidをたどって、SetTypeBitでビット辞書に
        // csvのtileSettingsから読み取っておいたビット設定を登録
        InitTypeBit(tmxData.tilesets);

        auto info = settings[filePath];
        if (info.spawnRange.umRange.size() != 0)
            spawnRange = info.spawnRange;
        else
            spawnRange.InitRange(MapRange::Type::Ellipse, 10, 10);// 敵の新規出現レンジをプレイヤから半径X,Yの楕円にする

        if (info.CellSize != 1)
        {
            SetCellSize(info.CellSize); // csvに書かれているCellSizeを設定
            tmxData.SetGridSize({(float)info.CellSize ,(float)info.CellSize ,(float)info.CellSize });
        }
        else // if (info.CellSize == 1)
            SetCellSize(tmxData.tmxInfo.tilewidth); // tmxのタイルの幅を取得
    }


    // 指定された座標(ワールド座標)の地形データを取得する
    unsigned int GetTerrain(float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        if (worldZ != (std::numeric_limits<float>::min)())
        {
            float tmpY = worldY;
            worldY = worldZ; //【YとZを変換】Zの入力があるときはZをYとして扱う
            worldZ = tmpY; //【YとZを変換】
        }

        // 負の座標が指定された場合は、何も無いものとして扱う
        if (worldX < 0 || worldY < 0)
            return Tile::None;

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = (int)(worldX / m_CellSize);
        int mapY = (int)(worldY / m_CellSize);
        int mapZ = (int)(worldZ / m_CellSize);

        if (tmxData.terrains.count(mapZ) == 0) // zの階層がまだなかったとき
            return Tile::None; // 配列の範囲外は、何も無いものとして扱う

        auto& terrainZ = tmxData.terrains[mapZ]; // 辞書からZに対応するデータへの参照を得る
        if (mapY >= terrainZ.size() || mapX >= terrainZ[mapY].size())
            return Tile::None; // 配列の範囲外は、何も無いものとして扱う

        return terrainZ[mapY][mapX]; // 二次元配列から地形IDを取り出して返却する
    }

    // 指定された座標(ワールド座標)の地形データを書き換える return には書き換える前のgidが返る
    bool SetTerrain(unsigned int gid, float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        if (worldZ != (std::numeric_limits<float>::min)())
        {
            float tmpY = worldY;
            worldY = worldZ; //【YとZを変換】Zの入力があるときはZをYとして扱う
            worldZ = tmpY; //【YとZを変換】
        }

        // 負の座標が指定された場合は、書き換えられない
        if (worldX < 0 || worldY < 0)
            return false;

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = (int)(worldX / m_CellSize);
        int mapY = (int)(worldY / m_CellSize);
        int mapZ = (int)(worldZ / m_CellSize);

        // 二次元配列の範囲外は、書き換えられない
        //if (mapY >= terrain.size() || mapX >= terrain[mapY].size())
        //    return;
        bool isNewZ = false;
        if (tmxData.terrains.count(mapZ) == 0) isNewZ = true; // 新しい z の階層の場合

        auto& terrainZ = tmxData.terrains[mapZ]; // 辞書からZに対応するデータへの参照を得る

        if (isNewZ) // 新しい z の階層の場合
        {
            terrainZ.z = mapZ;
            terrainZ.tilesets = &(tmxData.tilesets); //  新しい z の階層のtilesetsを設定する
            terrainZ.Width = tmxData.tmxInfo.width; terrainZ.Height = tmxData.tmxInfo.height;
            auto& terrinZYX = terrainZ[mapY][mapX];
            terrinZYX.tilesets = &(tmxData.tilesets);
            terrinZYX = gid; // 地形データ書き換え
        }
        else
            terrainZ[mapY][mapX] = gid; // 地形データ書き換え
        return true;
    }

    // gidに応じてタイルを描き分ける
    void DrawTile(unsigned int gid, float worldX, float worldY, float worldZ, float ExRate)
    {
        auto texture = TsxInfo::id_images[gid].second.lock();
        int id = TsxInfo::id_images[gid].first;
        if (isType(gid, TileType::Floor)) // 板ポリゴンで床を描く
            MyDraw::DrawDivRotaFloorF3D(MyDraw::Plane::Y, worldX, worldY, worldZ, ExRate, 0, *texture, id);
        else if (isType(gid, TileType::Wall)) //壁のときはボックスを描く
            MyDraw::DrawDivBoxF3D(worldX, worldY, worldZ, ExRate, *texture, id);
    }

    // 地形をタイルで描く
    void DrawTerrain()
    {
        for (auto&& terrainN : tmxData.terrains)
        {
            auto& terrainZ = *terrainN.second;
            int cellZ = terrainN.first;
            for (int cellY = 0, ySize = terrainZ.size(); cellY < ySize; cellY++)
            {
                for (int cellX = 0, xSize = terrainZ[cellY].size(); cellX < ySize; cellX++)
                {
                    float x = centerX(cellX); //マス目サイズ/2ずらし
                    float y = centerY(cellY); //マス目サイズ/2ずらし
                    float z = centerZ(cellZ); //マス目サイズ/2ずらし
                    int id = -1;
                    unsigned int gid = 0;
                    if (cellY < terrainZ.size() && cellX < terrainZ[cellY].size())
                    {
                        gid = terrainZ[cellY][cellX];
                    }
                    if (0 <= gid && gid < TsxInfo::id_images.size())
                    {
                        auto pTileImage = TsxInfo::id_images[gid].second.lock();
                        if (pTileImage == nullptr) continue;
                        // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
                        for (auto&& keyValue : world()->cameras) // ワールドにあるすべてのカメラぶんループ
                        {
                            auto& camera = keyValue.second;
                            //auto stepXYZs = MyMath::CrossGridVec(camera->pos - camera->m_targetXYZ, Vector3{ CellSize, CellSize, CellSize });
                            camera->Draw([&]()
                                {
                                    float ExRate = (float)m_CellSize / (float)pTileImage->m_XSize;
                                    DrawTile(gid, x, z, y, ExRate); // idに応じてタイルを描き分ける
                                });
                        }
                    }
                }
            }
        }
    }

    // 指定された座標(ワールド座標)の地形が壁か調べる
    inline bool IsWall(float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        auto terrainID = GetTerrain(worldX, worldY, worldZ); // 指定された座標の地形のIDを取得

        return IsWall(terrainID);
    }

    //あるIDが壁かどうかだけ調べる
    inline bool IsWall(unsigned int gid)
    {   // 地形が壁ならtrue、違うならfalseを返却する
        return isType(gid, TileType::Wall);
    }

    // 指定された座標worldPosの地形が 壁 や ダメージブロックかなどをチェックする関数
    bool checkID(TileBit checkBit, float worldX, float worldY, float worldZ,
        int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, unsigned int* pTerrainID = nullptr, TileBit* pMatchBits = nullptr)
    {
        unsigned int terrainID; // 指定された座標の地形のID

        if (worldZ != (std::numeric_limits<float>::min)())
        {   //【YとZを変換】Zの入力があるときはZをYとして扱う タイルマップの X,Yは3DではYがZ:奥行に対応するから
            float tmpY = worldY;
            worldY = worldZ;
            worldZ = tmpY; //【YとZを変換】
        }

        if (worldX < 0 || worldY < 0) return false; // 負の座標が指定された場合

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = cellX(worldX), mapY = cellY(worldY), mapZ = cellZ(worldZ);

        if (tmxData.terrains.count(mapZ) == 0) // zの階層がまだなかったときは 0
            terrainID = 0; // 範囲外のときは 0
        else // zの階層がすでにあったとき
        {   // 二次元配列の範囲内か判定
            auto& terrainZ = tmxData.terrains[mapZ];
            if (mapY >= terrainZ.size() || mapX >= terrainZ[mapY].size())
                terrainID = 0; // 範囲外のときは 0
            else
                terrainID = terrainZ[mapY][mapX]; // 配列から地形IDを取り出す
        }
        if (pTerrainID != nullptr)
            *pTerrainID = terrainID;

        TileBit matchBits = matchTypes(terrainID, checkBit); // ビット論理積 & で地形のchekckBitを確かめる
        bool isCheckBit = matchBits != (TileBit)TileType::None;
        if (isCheckBit && pMapX != nullptr && pMapY != nullptr && pMapZ != nullptr)
            *pMapX = mapX, * pMapY = mapY, * pMapZ = mapZ; // ブロックのマス目のセル番号をポインタに記録(nullptrの場合は記録しない)
        if (pMatchBits != nullptr)
            *pMatchBits = matchBits; // マッチしたビットをポインタ先に代入して返す

        return isCheckBit;
    }
};

#endif


Map.cppを新規作成して、static変数の初期化でcsvなどをロードしましょう。

#include "Map.h"

MapSetting Map::settings{ "Map/MapSetting.csv" }; // マップ一覧情報

TileSetting Map::tileSettings{ "Map/MapSetting.csv" }; // タイル情報



World.hを変更して、Mapの前方宣言と共有ポインタとマップをロードする関数を宣言しましょう。

#ifndef WORLD_H_
#define WORLD_H_

#include <list>
#include <memory>
#include <unordered_map>
#include <functional> // std::functionのラムダ式 で 関数を Draw関数の引数にして、カメラすべてに対して描画処理を発動させる

#include "MyHash.h" // ワールドのタグをhash32型で管理する


// [前方宣言] 宣言だけして、#includeはしてないので、ポインタだけはこのファイル内でつかえる
// #includeはしてないので、hpなどの変数には#include "~.h"しないとアクセスできないので循環インクルード防止のため.cppでインクルードしてください
class GameScene;
class Player;
class Bullet;
class Enemy;
class Camera;
class Grid;
class Map;

// プレイヤや敵や弾などの共有ポインタを管理し、EraseRemoveIfで消して参照カウンタが0になったらそれらを消滅させるクラス(C#のガベージコレクタの代わり)
class World
{
public:
    inline World(GameScene* pScene, const std::string& worldTag = "") : m_pScene{ pScene }, tag{ worldTag } {}
    inline ~World() {}
protected: // public:にすると他で外部で後からタグやシーンを書き換えられると辞書での管理などの前提がおかしくなるのでprotected:する
    hash32 tag{ "" }; // ワールド名のタグ
    GameScene* m_pScene{ nullptr }; // ワールドを所有するシーン
public:
    hash32 Tag() const { return tag; }
    GameScene* scene() { return m_pScene; }

    //std::unordered_map<hash32, std::shared_ptr<Player>> players; // プレイヤのタグ辞書
    // ワールドを所有するシーンにプレイヤを追加する
    void AddPlayer(const std::string& playerTag, std::shared_ptr<Player> pPlayer);
    // ワールドにいるプレイヤを得る
    std::vector<std::weak_ptr<Player>> GetPlayers();

    bool LoadMap(const std::string& mapFilePath); // ワールドのマップをロード

    std::shared_ptr<Map> map{ nullptr }; // マップ(共有ポインタ) マップのメモリ解放は World と運命をともにして消える

protected:
    std::shared_ptr<Grid> m_pGrid{ nullptr }; // 当たり判定を高速にするためグリッドで空間分割
public:
    Grid* grid() { return m_pGrid.get(); } // グリッドを返す
    Grid* makeGrid(int cellWidth = 128, int cellHeight = 128, int cellDepth = 128); // グリッドを生成する

    std::unordered_map<hash32, std::shared_ptr<Player>> players; // プレイヤのタグ辞書

    std::list<std::shared_ptr<Bullet>> bullets; // 自機弾のリスト

    std::list<std::shared_ptr<Enemy>> enemies; // 敵のリスト

    // 削除処理を共通テンプレート関数にする
    // [共通テンプレート関数]https://programming-place.net/ppp/contents/cpp/language/009.html#function_template
    template <typename TypeT, class T_if>
    void EraseRemoveIf(std::list<TypeT>& v, T_if if_condition)
    {   //            特定のタイプT↑  ↑配列v   ↑条件式if_condition
        v.erase(
            std::remove_if(v.begin(), v.end(), if_condition),
            v.end() //  ↓remove_ifの位置
        );//例.[生][生][死][死][死]← v.end()の位置
    };

    std::unordered_map<hash32, std::shared_ptr<Camera>> cameras; // このワールドに存在するカメラの辞書<カメラにつけたタグ, カメラの共有ポインタ>
    // 指定されたタグのカメラへの共有ポインタを得る カメラ辞書に存在しないタグでアクセスしたときにunordered_mapの特性で勝手に意図しないカメラができるのを防ぐ関数
    std::shared_ptr<Camera> camera(const std::string& cameraTag) { auto itr = cameras.find(cameraTag); return (itr != end(cameras)) ? itr->second : nullptr; }
    // このワールドに存在するカメラすべてに対して drawFuncで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
    void Draw(std::function<void()> drawFunc) noexcept;
    // カメラにタグをつけてワールドに追加 カメラにもワールドへのポインタリンクを張る
    inline bool AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera);
    // 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
    inline bool EraseCamera(const std::string& cameraTag);
    // カメラを nowWorld から nextWorld に移動させる
    static bool MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld);

};

#endif


World.cppを変更して、Mapをstd::make_sharedで生成する処理を記述しましょう。

#include "World.h"

#include "Player.h"
#include "Camera.h"
#include "Screen.h"

#include "GameScene.h"

#include "GridObject.h" // Gridをmake_sharedで生成するため
#include "Map.h" // Mapをmake_sharedで生成するため

Grid* World::makeGrid(int cellWidth, int cellHeight, int cellDepth)
{
    if (this->m_pGrid == nullptr) // グリッドが未生成の場合は生成
        this->m_pGrid = std::make_shared<Grid>(cellWidth, cellHeight, cellDepth);
    return this->m_pGrid.get();
}

void World::AddPlayer(const std::string& playerTag, std::shared_ptr<Player> pPlayer)
{
    if (m_pScene != nullptr)
    {
        m_pScene->players.emplace(playerTag, pPlayer); // シーンにプレイヤを追加する
    }
}

// このワールドにいるプレイヤを得る
std::vector<std::weak_ptr<Player>> World::GetPlayers()
{
    std::vector<std::weak_ptr<Player>> players;
    for (auto&& pPlayer : players)
    {
        players.emplace_back(pPlayer);
    }
    return players;
}

// ワールドのマップをロード
bool World::LoadMap(const std::string& mapFilePath)
{
    map = std::make_shared<Map>(this, mapFilePath); // マップをファイルから読み込む
    return map->tmxData.isInitialized; // 読込に成功したかを返す
}


// カメラにタグをつけてワールドに追加 カメラにもワールドへの生ポインタリンクを渡す
bool World::AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera)
{
    if (pCamera == nullptr) return false;
    if (cameras.count(cameraTag) > 0) return false; // すでに同じタグのカメラがワールドにあったら false
    if (pCamera->world() != nullptr) pCamera->world()->EraseCamera(cameraTag); // すでにカメラが別ワールドに配置されていたら別ワールドから削除
    cameras.emplace(cameraTag, pCamera); // ワールドにカメラを追加する
    pCamera->world(this); // カメラにもワールドへのポインタリンクを張る(カメラ側に渡すのは共有ポインタではない生ポインタだから共有デッドロックにはならない)
    return true;
}

// 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
bool World::EraseCamera(const std::string& cameraTag)
{
    auto itr = cameras.find(cameraTag); // 指定したタグのカメラを探す
    if (itr == end(cameras)) return false; // 指定したタグのカメラがワールドにない
    auto pCamera = itr->second;
    pCamera->world(nullptr); // カメラ側の worldへのリンクのポインタも nullptrに
    cameras.erase(cameraTag); // カメラを辞書から削除

    return true;
}

// カメラを nowWorld から nextWorld に移動させる
bool World::MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld)
{
    if (nowWorld == nullptr) return false;
    auto itr = nowWorld->cameras.find(cameraTag); // タグでカメラを探す
    if (itr == end(nowWorld->cameras)) return false; // タグに関連づいたカメラがなかった
    auto targetCamera = itr->second; // カメラの共有ポインタをキープ(次の行でeraseしてもカメラの共有カウンタは0にはならずこのリンクのおかげでカメラはメモリに残存)
    targetCamera->world(nullptr); // カメラ側の worldへのリンクのポインタを一旦 nullptrに
    nowWorld->cameras.erase(cameraTag); // nowWorld からは カメラを消す
    if (nextWorld == nullptr) return false;

    return nextWorld->AddCamera(cameraTag, targetCamera); // 次のワールドにカメラを追加する
}

// このワールドに存在するカメラすべてに対して funcで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
void World::Draw(std::function<void()> drawFunc) noexcept
{
    for (auto&& keyValue : cameras)    // このワールドにあるすべてのカメラぶんループ
    {
        VECTOR_D beforeCamPos; // 設定変更前のカメラのパラメータを一旦、保管
        double beforeVRot, beforeHRot, beforeTRot, beforeNear, beforeFar, beforeFov;
        Camera::GetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);

        auto& camera = keyValue.second;
        //camera->screenID
        DxLib::SetCameraPositionAndAngle(camera->pos, camera->rot.V, camera->rot.H, camera->rot.T); // 各カメラの位置、回転を設定

        Screen::Start(camera->screenHandle, true, camera->fovAngle); // カメラに関連付けられたスクリーンIDを描画先にする
        {
            drawFunc(); // 渡された描画処理を実行
        }
        Screen::End(true); // 描画先を元に戻す

        // カメラの設定をもとに戻しておく
        Camera::SetCameraSettings(&beforeCamPos, &beforeVRot, &beforeHRot, &beforeTRot, &beforeFov, &beforeNear, &beforeFar);
    }
}


GameScene.cppを変更して、タイルマップ読み込む処理と3D表示で描く処理を実装しましょう。

#include "GameScene.h"

#include "Player.h"
#include "Map.h"

GameSceneSetting GameScene::settings{ "Map/GameSceneSetting.csv" }; // ゲームシーンの設定情報

// 指定したゲームシーンのタグに関連づいたファイル名に対応したワールドを初期化する
bool GameScene::LoadWorlds(const std::string& gameSceneTag)
{
    if (settings.info.count(gameSceneTag) == 0)
        return false;

    auto& info = settings[gameSceneTag];
    for (auto&& tag_mapPath : info)
    {
        CreateWorld(tag_mapPath.second.tag); // ワールドをタグをつけて生成
        // ワールドのマップをロード
        worlds[tag_mapPath.second.tag]->LoadMap(tag_mapPath.second.mapFilePath);

    }
    return true;
}

void GameScene::ChangeWorld(const std::string& worldTag, std::shared_ptr<Player> pPlayer)
{
    if (pPlayer == nullptr) return;
    if (worlds.count(worldTag) == 0) return; // 存在しないタグだった

    pPlayer->world(worlds[worldTag].get()); // ワールドへのリンクをすげ替える
}


void GameScene::Initialize()
{// Init処理

    // Zバッファを有効にする [Zの深度]
    //[参考]https://docs.google.com/presentation/d/1Z23t1yAS7uzPDVakgW_M02p20qt9DeTs4ku4IiMDLco/edit?usp=sharing
    //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
    DxLib::SetUseZBuffer3D(TRUE);
    // Zバッファへの書き込みを有効にする
    DxLib::SetWriteZBuffer3D(TRUE);
    // 光の明暗計算を無効に
    DxLib::SetUseLighting(FALSE);

    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);


    Resource::MakeShared<Texture>(mapChipPath, 8, 8, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath1, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath2, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする


    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    // プレイヤの生成
    if (pWorld != nullptr)
    {
        //pWorld->AddCamera("カメラ1", Camera::defaultCamera); // カメラ1というタグをデフォルトカメラにつけてワールドにデフォルトカメラを配置
        pWorld->AddCamera("カメラ1", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera1 = pWorld->camera("カメラ1"); // カメラ1 というタグのついたカメラへの共有ポインタを得る
        pCamera1->SetScreenHandle(screenHandle0); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer1 = Object::Create<Player>(nullptr, pWorld, Pad::Key, Vector3(90 + 256, 32, 95 + 256));
        pWorld->AddPlayer("プレイヤ1", pPlayer1); // シーンにプレイヤを追加する
        pPlayer1->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);

        pPlayer1->camera = pCamera1.get(); // カメラをプレイヤにリンクする
        pPlayer1->mass = 100.0f; // 体重を100にして衝突されてもびくともしないようにする


        pWorld->AddCamera("カメラ2", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera2 = pWorld->camera("カメラ2"); // プレイヤ2 というタグのついたカメラへの共有ポインタを得る
        pCamera2->SetScreenHandle(screenHandle1); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer2 = Object::Create<Player>(nullptr, pWorld, Pad::Two, Vector3(190, 32, 195));
        pWorld->AddPlayer("プレイヤ2", pPlayer2); // シーンにプレイヤを追加する
        pPlayer2->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);
       
        pPlayer2->camera = pCamera2.get(); // カメラをプレイヤにリンクする
        pPlayer2->mass = 1.0f; // 体重を1にして普通にふっとぶようにする

    }

};


void GameScene::Update()
{// 更新処理

    Screen::ClearDrawScreen(screenHandle0); // 一旦キャンバスをきれいにまっさらに
    Screen::ClearDrawScreen(screenHandle1); // 一旦キャンバスをきれいにまっさらに

    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    for (const auto& player : players)
        player.second->Update(); // プレイヤの更新【忘れるとプレイヤが動かない】

    // グリッドを更新して隣接するグリッドに含まれる敵や弾の当たり判定を行う
    for (const auto& world : worlds)
        if (world.second->grid() != nullptr)
            for (const auto& cell : world.second->grid()->cellData)
            {
                auto pGridObject = cell.second;
                while (pGridObject != nullptr)
                {   // while文で数珠つなぎのm_pNextを次々とたどって最後にたどり着くと次がなくてnullptrになる
                    pGridObject->checkCell();
                    pGridObject = pGridObject->m_pNext; // リンクリスト構造を次々とたどる
                }
            }

}

void GameScene::Draw()
{// 描画処理
    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    std::unordered_set<World*> drawEndSet; // すでに描いたWorldをポインタのsetでかぶらないように判定する

    for (const auto& player : players)
    {
        player.second->Draw(); // プレイヤの描画【忘れるとプレイヤ表示されない】

        auto world = player.second->world(); // プレイヤのいるワールド

        if (drawEndSet.count(world) > 0) continue; // すでに描いたWorldは描かずスキップする
        drawEndSet.emplace(world); // 描いたWorldのポインタ住所をsetに登録する

        world->map->DrawTerrain(); // マップの描画


        // プレイヤのいるワールドのカメラのみを描けば、プレイヤのいないワールドは描かずに済む
        // 複数のカメラを 範囲 for文 で回す
        for (auto&& camera : player.second->world()->cameras)
        {   // ラムダ式[&](){ ~ }で{}の外側の変数すべてを & キャプチャで&参照として「Draw関数の内側へ引き連れて」処理できる
            camera.second->Draw([&]()
                {
                    // Draw関数() の 外側にある変数 mapChipPath も[&]効果で { } の内側で問題なくアクセスできている↓

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(40, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 2);

                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 40, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 0);
                    // 3Dで6枚のタイル(12ポリゴン)でボックスを描く
                    MyDraw::DrawDivBoxF3D(0, 70, 40, 1.0, *std::dynamic_pointer_cast<Texture>(Resource::files[mapChipPath]), 8);

                    if (camera.first == "カメラ1")
                        DxLib::DrawString(0, 0, "こちらがプレイヤ1", GetColor(255, 0, 0));
                    else if (camera.first == "カメラ2")
                        DxLib::DrawString(0, 0, "こちらはプレイヤ2", GetColor(0, 255, 0));
                });
        }

#ifdef _DEBUG  // デバッグのときだけ_DEBUGが定義され描画される
        if (world->grid() != nullptr)
            for (const auto& cell : world->grid()->cellData)
                cell.second->Draw(); // グリッドの描画
#endif
    }

    // スクリーン0の内容を画面に描く
    DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
    DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));

    // スクリーン1の内容を画面に描く
    DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
    DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));

}


いかがでしょう?マップにめり込む形ですが、タイルマップが3D表示で描かれたでしょうか?
現状マップに当たり判定はないので、地面の代わりに 0 の位置までプレイヤはめり込みます
では次の章では、マップの当たり判定を作成していきましょう。


Map/MapSetting.csvの設定に合わせてタイルとの当たり判定をする


では次は、タイルと当たり判定する処理をコンポーネントにして、タイルの上を歩けるようにしてゆきましょう。

HitBoxComponent.hを新規作成して、コンポーネントのオーナーの当たり判定のボックスの上下左右奥手前を取得できる機能を実装しましょう。

#ifndef HITBOX_COMPONENT_H_
#define HITBOX_COMPONENT_H_

#include "Component.h"
#include "GameObject.h"
#include "Vector3.h"
#include "MyDraw.h"

class HitBoxComponent : public Component
{
public:
    HitBoxComponent(std::shared_ptr<GameObject> pOwner, Vector3 boxSize, Vector3 offsetLeftBottomBack, Vector3 offsetRightTopForward)
        : Component(pOwner), m_BoxSize{ boxSize }, m_OffsetLeftBottomBack{ offsetLeftBottomBack }, m_OffsetRightTopForward{ offsetRightTopForward }
    {
        StorePostionAndHitBox(); // ヒットボックスの現在の境界を一旦 prev に 初期保管
    }
    virtual ~HitBoxComponent() {}

    Vector3 m_BoxSize; // ボックスの幅X、高さY、奥行Z
   
    union {
        Vector3 m_OffsetLeftBottomBack; // ヒットボックスのオフセット(X左、Y底、Z手前)
        struct { float hitboxOffsetLeft, hitboxOffsetBottom, hitboxOffsetBack; };
    };
    union {
        Vector3 m_OffsetRightTopForward; // ヒットボックスのオフセット(X右、Y上、Z奥)
        struct { float hitboxOffsetRight, hitboxOffsetTop, hitboxOffsetForward; };
    };

    Vector3 m_PrevPos; // 1フレーム前の位置 (動く雲に乗るなどに使う)

    union {
        Vector3 m_PrevLeftBottomBack; // 1フレーム前のボックスの境目位置 (動く雲に乗るなどに使う)
        struct { float prevLeft, prevBottom, prevBack; };
    };

    union {
        Vector3 m_PrevRightTopForward; // 1フレーム前のボックスの境目位置 (動く雲に乗るなどに使う)
        struct { float prevRight, prevTop, prevForward; };
    };

    // 当たり判定の左端を取得
    virtual inline float GetLeft()
    {
        return (owner()->x - m_BoxSize.x / 2) + hitboxOffsetLeft;
    }

    // 左端を指定することにより位置を設定する
    virtual inline void SetLeft(float left)
    {
        owner()->x = left - hitboxOffsetLeft + m_BoxSize.x / 2;
    }

    // 右端を取得
    virtual inline float GetRight()
    {
        return (owner()->x - m_BoxSize.x / 2) + m_BoxSize.x - hitboxOffsetRight;
    }

    // 右端を指定することにより位置を設定する
    virtual inline void SetRight(float right)
    {
        owner()->x = right + hitboxOffsetRight - m_BoxSize.x + m_BoxSize.x / 2;
    }

    // 上端を取得
    virtual inline float GetTop()
    {
        return (owner()->y - m_BoxSize.y / 2) + m_BoxSize.y - hitboxOffsetTop;
    }

    // 上端を指定することにより位置を設定する
    virtual inline void SetTop(float top)
    {
        owner()->y = top + hitboxOffsetTop - m_BoxSize.y + m_BoxSize.y / 2;
    }

    // 下端を取得する
    virtual inline float GetBottom()
    {
        return (owner()->y - m_BoxSize.y / 2) + hitboxOffsetBottom;
    }

    // 下端を指定することにより位置を設定する
    virtual inline void SetBottom(float bottom)
    {
        owner()->y = bottom - hitboxOffsetBottom + m_BoxSize.y / 2;
    }

    // 当たり判定のZ手前側を取得
    virtual inline float GetBack()
    {
        return (owner()->z - m_BoxSize.z / 2) + hitboxOffsetBack;
    }

    // Z手前側を指定することにより位置を設定する
    virtual inline void SetBack(float back)
    {
        owner()->z = back - hitboxOffsetBack + m_BoxSize.z / 2;
    }

    // Z奥行き方向位置を取得
    virtual inline float GetForward()
    {
        return (owner()->z - m_BoxSize.z / 2) + m_BoxSize.z - hitboxOffsetForward;
    }

    // Z奥行き方向位置を指定することにより位置を設定する
    virtual inline void SetForward(float forward)
    {
        owner()->z = forward + hitboxOffsetForward - m_BoxSize.z + m_BoxSize.z / 2;
    }

    // 雲に乗る系のための1フレーム前処理関数群
   
    // 1フレーム前からの移動量(x方向)
    virtual inline float GetDeltaX()
    {
        return owner()->x - m_PrevPos.x;
    }

    // 1フレーム前からの移動量(y方向)
    virtual inline float GetDeltaY()
    {
        return owner()->y - m_PrevPos.y;
    }

    // 1フレーム前からの移動量(z方向)
    virtual inline float GetDeltaZ()
    {
        return owner()->z - m_PrevPos.z;
    }


    // 1フレーム前の X 左端を取得する
    virtual inline float GetPrevLeft()
    {
        return prevLeft;
    }

    // 1フレーム前の X 右端を取得する
    virtual inline float GetPrevRight()
    {
        return prevRight;
    }

    // 1フレーム前の Y 上端を取得する
    virtual inline float GetPrevTop()
    {
        return prevTop;
    }

    // 1フレーム前の Y 下端を取得する
    virtual inline float GetPrevBottom()
    {
        return prevBottom;
    }

    // 1フレーム前の Z 前方を取得する
    virtual inline float GetPrevForward()
    {
        return prevForward;
    }

    // 1フレーム前の Z 後方を取得する
    virtual inline float GetPrevBack()
    {
        return prevBack;
    }

    // 1フレーム前の場所と当たり判定を記憶する
    virtual inline void StorePostionAndHitBox()
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        m_PrevPos = pOwner->position;
        prevLeft = GetLeft();
        prevRight = GetRight();
        prevTop = GetTop();
        prevBottom = GetBottom();
        prevForward = GetForward();
        prevBack = GetBack();
    }

    // 当たり判定を描画(デバッグ用)
    virtual void Draw(GameObject* obj = nullptr) override
    {
#ifdef _DEBUG
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        // 赤で当たり判定の四角Boxを描画
        MyDraw::DrawLineBox(Vector3(GetLeft(), GetBottom(), GetBack()), Vector3(GetRight(), GetTop(), GetForward()), GetColor(255, 0, 0));
#endif
    }
};

#endif


HitBoxComponentを継承overrideしてMapと当たり判定するコンポーネントを作成しましょう。

MapHitBoxComponent.hを新規作成して、マップのタイルから壁や床などのビットを取得して当たり判定と押し戻しする機能を実装しましょう。

#ifndef MAP_HITBOX_COMPONENT_H_
#define MAP_HITBOX_COMPONENT_H_

#include "HitBoxComponent.h"

#include "Tile.h" // Tile::isType(terrainID, checkBit)関数を使って ビット論理積 & で地形のchekckBitを確かめる
#include "Map.h"

class MapHitBoxComponent : public HitBoxComponent
{
public:
    MapHitBoxComponent(std::shared_ptr<GameObject> pOwner, Vector3 boxSize, Vector3 offsetLeftBottomBack, Vector3 offsetRightTopForward)
        : HitBoxComponent(pOwner, boxSize, offsetLeftBottomBack, offsetRightTopForward) {}
    virtual ~MapHitBoxComponent() {}
#ifdef _DEBUG
    // デバッグ用に最後に当たったXYZの位置などを記録する
    float debugHitX = 0.0f, debugHitY = 0.0f, debugHitZ = 0.0f;
    HitBit debugHitPart = (HitBit)HitPart::None;
#endif
    // 当たり判定を描画(デバッグ用)
    virtual void Draw(GameObject* obj = nullptr) override
    {
        HitBoxComponent::Draw(obj); // 継承もとのDrawも呼び出す
#ifdef _DEBUG
        // ヒットしたパートを青四角で描画
        if (debugHitPart & HitPart::Left || debugHitPart & HitPart::Right)
            MyDraw::DrawLineBox(Vector3(debugHitX - 1, debugHitY - m_BoxSize.y / 2, debugHitZ - m_BoxSize.z / 2),
                Vector3(debugHitX + 1, debugHitY + m_BoxSize.y / 2, debugHitZ + m_BoxSize.z / 2), GetColor(0, 0, 255));

        if (debugHitPart & HitPart::Bottom || debugHitPart & HitPart::Top)
            MyDraw::DrawLineBox(Vector3(debugHitX - m_BoxSize.x / 2, debugHitY - 1, debugHitZ - m_BoxSize.z / 2),
                Vector3(debugHitX + m_BoxSize.x / 2, debugHitY + 1, debugHitZ + m_BoxSize.z / 2), GetColor(0, 0, 255));

        if (debugHitPart & HitPart::Back || debugHitPart & HitPart::Forward)
            MyDraw::DrawLineBox(Vector3(debugHitX - m_BoxSize.x / 2, debugHitY - m_BoxSize.y / 2, debugHitZ - 1),
                Vector3(debugHitX + m_BoxSize.x / 2, debugHitY + m_BoxSize.y / 2, debugHitZ + 1), GetColor(0, 0, 255));
#endif
    }

    // ヒットボックスの当たり判定の隅の座標(±0.01fで1ピクセル隣)を取得
    virtual inline bool GetHitBorder(float* pXLeft, float* pXMiddle, float* pXRight, float* pYBottom, float* pYMiddle, float* pYTop, float* pZBack, float* pZMiddle, float* pZForward)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return false; // オーナーがすでにメモリから解放済のときは return

        // 当たり判定の隅の座標(±0.01fで1ピクセル隣)を取得
        float xLeft = GetLeft();
        float xRight = GetRight();
        if (pXLeft != nullptr) *pXLeft = xLeft + 0.01f;
        if (pXMiddle != nullptr) *pXMiddle = xLeft + (xRight - xLeft) / 2; // xの中間点
        if (pXRight != nullptr) *pXRight = xRight - 0.01f;

        float yBottom = GetBottom();
        float yTop = GetTop();
        if (pYBottom != nullptr) *pYBottom = yBottom; // 底はisGround状態を維持するため +0.01fしない
        if (pYMiddle != nullptr) *pYMiddle = yBottom + (yTop - yBottom) / 2; // yの中間点
        if (pYTop != nullptr) *pYTop = yTop - 0.01f;

        float zBack = GetBack();
        float zForward = GetForward();
        if (pZBack != nullptr) *pZBack = zBack + 0.01f;
        if (pZMiddle != nullptr) *pZMiddle = zBack + (zForward - zBack) / 2; // zの中間点
        if (pZForward != nullptr) *pZForward = zForward - 0.01f;

        return true;
    }

    virtual void OnHitLeft(HitBit hitPartBitX, TileBit checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return
        pOwner->OnHitTile(HitPart::Left, hitPartBitX, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
       
        //★[C++は小数点の % 余り演算がない!] https://marycore.jp/prog/c-lang/modulo-floating-point-number/
        float wallRight = xLeft - (float)std::fmod(xLeft, pOwner->map()->CellSize()) + pOwner->map()->CellSize(); // 壁の右端
        SetLeft(wallRight); // キャラの左端を壁の右端に沿わす
        pOwner->vx = 0;
#ifdef _DEBUG
        debugHitPart = (HitBit)HitPart::Left; debugHitX = xLeft; debugHitY = yMiddle; debugHitZ = zMiddle;
#endif
    }

    virtual void OnHitRight(HitBit hitPartBitX, TileBit checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return
        pOwner->OnHitTile(HitPart::Right, hitPartBitX, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
       
        float wallLeft = xRight - (float)std::fmod(xRight, pOwner->map()->CellSize()); // 壁の左端
        SetRight(wallLeft); // キャラの右端を壁の左端に沿わす
        pOwner->vx = 0;
#ifdef _DEBUG
        debugHitPart = (HitBit)HitPart::Right; debugHitX = xRight; debugHitY = yMiddle; debugHitZ = zMiddle;
#endif
    }

    // X方向[左端、右端]が checkBit かどうかのビット論理積 & をとり、OnHitLeft か OnHitRight をコールバックする
    inline HitBit hitCheckX(TileBit checkBit, unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return (HitBit)HitPart::None; // オーナーがすでにメモリから解放済のときは return
        if (pOwner->map() == nullptr) return (HitBit)HitPart::None;

        // 当たり判定の隅の座標(±0.01fで1ピクセル隣)を取得
        float xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward;
        GetHitBorder(&xLeft, &xMiddle, &xRight, &yBottom, &yMiddle, &yTop, &zBack, &zMiddle, &zForward);

        HitBit hitPartBitX = hitcheckX(checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        //★ 左端が壁にめりこんでいるか?
        if (HitPart::Left & hitPartBitX)
        {   // 左端がぶつかったときのコールバック関数を呼ぶ
            OnHitLeft(hitPartBitX, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        }
        // 右端が壁にめりこんでいるか?
        else if (HitPart::Right & hitPartBitX)
        {   // 左端がぶつかったときのコールバック関数を呼ぶ
            OnHitRight(hitPartBitX, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        }

        return hitPartBitX;
    }


    virtual void OnHitBottom(HitBit hitPartBitY, TileBit checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return
        pOwner->OnHitTile(HitPart::Bottom, hitPartBitY, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);

        float yBottomHit = yBottom; // めり込んだy位置
        float pushUpY = pOwner->map()->CellSize(); // Y方向押し上げ高さ
        if (pMatchBits != nullptr && (*pMatchBits) & (TileBit)TileType::Floor) // 床にめり込んだ場合
        {
            yBottomHit = yBottom + pOwner->map()->CellSize();
            pushUpY = 0; // 床の場合はマス目サイズCellSizeぶん上にはあげない
        }

        float wallTop = yBottomHit - (float)std::fmod(yBottomHit, pOwner->map()->CellSize()) + pushUpY; // 壁や床の上側
        SetBottom(wallTop); // キャラの足元を壁のトップや床に沿わす
        pOwner->vy = -0.01f;
        pOwner->isGround = true;
        pOwner->isFlying = false;
#ifdef _DEBUG
        debugHitPart = (HitBit)HitPart::Bottom; debugHitY = yBottom; debugHitX = xMiddle; debugHitZ = zMiddle;
#endif
    }

    virtual void OnHitTop(HitBit hitPartBitY, TileBit checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr); // オーナーがすでにメモリから解放済のときは return
        pOwner->OnHitTile(HitPart::Top, hitPartBitY, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);

        float wallBottom = yTop - (float)std::fmod(yTop, pOwner->map()->CellSize()); // 壁の下側
        SetTop(wallBottom); // キャラのトップを壁の下側に沿わす
        pOwner->vy = 0;
#ifdef _DEBUG
        debugHitPart = (HitBit)HitPart::Top; debugHitY = yTop; debugHitX = xMiddle; debugHitZ = zMiddle;
#endif
    }

    // 空中にいるときのコールバック
    virtual void OnAir(HitBit hitPartBitY, TileBit checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return

        pOwner->isGround = false; // 空中にいるので isGround を false に
    }

    // Y方向[下端、上端]が checkBit かどうかのビット論理積 & をとり、OnHitBottom か OnHitTop をコールバックする
    inline HitBit hitCheckY(TileBit checkBit, unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return (HitBit)HitPart::None; // オーナーがすでにメモリから解放済のときは return
        if (pOwner->map() == nullptr) return (HitBit)HitPart::None;
        auto pMap = pOwner->map();

        // 当たり判定の隅の座標(±0.01fで1ピクセル隣)を取得
        float xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward;
        GetHitBorder(&xLeft, &xMiddle, &xRight, &yBottom, &yMiddle, &yTop, &zBack, &zMiddle, &zForward);
#if 0
        if (checkBit & (TileBit)TileType::Floor)
        {   // 床かをチェックする場合は現在より一つ上のCellSizeぶん上のタイルが床なら一つ下に壁があるときに上方向に押し戻したのと同じような形になる

            if ((int)HitPart::Bottom & hitPartFloorBitY)
            {   // 下端がぶつかったときのコールバック関数を呼ぶ 床の場合
                OnHitBottom(hitPartFloorBitY, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop,
                    zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, &matchFloorBit); // matchFloorBitには TileType::Floor のビットだけが1になったビットが渡される
                if (pMatchBits != nullptr) *pMatchBits = (TileBit)TileType::Floor; // ずらしてヒット判定したのであてになるのは TileType::Floor のビットのみなのでそれを |= ビット論理和する
                return hitPartFloorBitY;
            }
        }
#endif
        TileBit matchFloorBit = (TileBit)TileType::None;
        // 床にめりこんでいるか(床は特殊で一つ上のマス目が床であれば キャラが床より下にめりこんでいるということなので、CellSizeぶん↓上のマス目が床か調べ、下のマス目のTopに沿わせる)
        HitBit hitPartFloorBitY = hitcheckBottomY((TileBit)TileType::Floor, xLeft, xMiddle, xRight, yBottom + pMap->CellSize(), yMiddle + pMap->CellSize(), yTop + pMap->CellSize(),
            zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, &matchFloorBit); // matchFloorBit は床にめり込んでいたらTileBit::Floorの 1の部分が1になっている
        // 下側が床にめりこんでいるか?
        if (HitPart::Bottom & hitPartFloorBitY)
        {
            OnHitBottom(hitPartFloorBitY, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop,
                zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, &matchFloorBit); // ←matchFloorBit経由でFloorの 1の部分を床の押し戻しかの判定用に渡す
            if (pMatchBits != nullptr) *pMatchBits |= (TileBit)TileType::Floor; // ずらしてヒット判定したのであてになるのは TileType::Floor のビットのみなのでそれを |= ビット論理和する
            return hitPartFloorBitY;
        }

        // 下側が「床以外」にめりこんでいるか★↓ ^ 排他的論理和をFloorに対してとると、checkBitのFloorのビットが1だったら打ち消すことができる
        HitBit hitPartBitY = hitcheckY(checkBit ^ (TileBit)TileType::Floor, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        // 下側が床以外にめり込んでいるか?
        if (HitPart::Bottom & hitPartBitY)
        {   // 下端がぶつかったときのコールバック関数を呼ぶ
            OnHitBottom(hitPartBitY, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        }
        // 上側が床以外にめりこんでいるか?
        else if (HitPart::Top & hitPartBitY)
        {   // 上端がぶつかったときのコールバック関数を呼ぶ
            OnHitTop(hitPartBitY, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        }
        else // 空中にいるときのコールバック関数をよぶ
            OnAir(hitPartBitY, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);

        return hitPartBitY;
    }



    virtual void OnHitBack(HitBit hitPartBitZ, TileBit checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return
        if (pOwner->map() == nullptr) return;
        auto pMap = pOwner->map();
        pOwner->OnHitTile(HitPart::Back, hitPartBitZ, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);

        float wallForward = zBack - (float)std::fmod(zBack, pMap->CellSize()) + pMap->CellSize(); // 壁の奥行き方向
        SetBack(wallForward); // キャラの背中を壁のフォワードに沿わす
        pOwner->vz = 0;
#ifdef _DEBUG
        debugHitPart = (HitBit)HitPart::Back; debugHitZ = zBack; debugHitX = xMiddle; debugHitY = yMiddle;
#endif
    }

    virtual void OnHitForward(HitBit hitPartBitZ, TileBit checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return; // オーナーがすでにメモリから解放済のときは return
        if (pOwner->map() == nullptr) return;
        auto pMap = pOwner->map();
        pOwner->OnHitTile(HitPart::Forward, hitPartBitZ, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);

        float wallBack = zForward - (float)std::fmod(zForward, pMap->CellSize()); // 壁の手前側
        SetForward(wallBack); // キャラのおでこを壁のバックに沿わす
        pOwner->vz = 0;
#ifdef _DEBUG
        debugHitPart = (HitBit)HitPart::Forward; debugHitZ = zForward; debugHitX = xMiddle; debugHitY = yMiddle;
#endif
    }

    // Z方向[z手前、Z奥]が checkBit かどうかのビット論理積 & をとり、OnHitBack か OnHitForward をコールバックする
    inline HitBit hitCheckZ(TileBit checkBit, unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return (HitBit)HitPart::None; // オーナーがすでにメモリから解放済のときは return
        //if (pOwner->map() == nullptr) return (HitBit)HitPart::None;
        //auto pMap = pOwner->map();

        // 当たり判定の隅の座標(±0.01fで1ピクセル隣)を取得
        float xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward;
        GetHitBorder(&xLeft, &xMiddle, &xRight, &yBottom, &yMiddle, &yTop, &zBack, &zMiddle, &zForward);

        HitBit hitPartBitZ = hitcheckZ(checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        // 手前側が壁にめりこんでいるか?
        if (HitPart::Back & hitPartBitZ)
        {   // Z手前 がぶつかったときのコールバック関数を呼ぶ
            OnHitBack(hitPartBitZ, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        }
        // 奥行き側が壁にめりこんでいるか?
        else if (HitPart::Forward & hitPartBitZ)
        {   // Z奥 がぶつかったときのコールバック関数を呼ぶ
            OnHitForward(hitPartBitZ, checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        }

        return hitPartBitZ;
    }

    // 指定された座標worldPosの地形が 壁 や ダメージブロックかなどをチェックする関数
    inline bool checkID(TileBit checkBit, float worldX, float worldY, float worldZ,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        unsigned int terrain_gid; // 指定された座標の地形のID

        if (worldZ != (std::numeric_limits<float>::min)())
        {   //【YとZを変換】Zの入力があるときはZをYとして扱う タイルマップの X,Yは3DではYがZ:奥行に対応するから
            float tmpY = worldY;
            worldY = worldZ;
            worldZ = tmpY; //【YとZを変換】
        }

        if (worldX < 0 || worldY < 0) return false; // 負の座標が指定された場合

        auto pOwner = owner(); // コンポーネントを所有するオーナーへの共有ポインタを得る
        if (pOwner == nullptr) return false; // オーナーがすでにメモリから解放済のときは return
        if (pOwner->map() == nullptr) return false;
        Map* map = pOwner->map();

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = map->cellX(worldX), mapY = map->cellY(worldY), mapZ = map->cellZ(worldZ);

        if (map->tmxData.terrains.count(mapZ) == 0) // zの階層がまだなかったときは 0
            terrain_gid = 0; // 範囲外のときは0
        else // zの階層がすでにあったとき
        {   // 二次元配列の範囲内か判定
            auto& terrainZ = map->tmxData.terrains[mapZ];
            if (mapY >= terrainZ.size() || mapX >= terrainZ[mapY].size())
                terrain_gid = 0; // 範囲外のときは0
            else
                terrain_gid = terrainZ[mapY][mapX]; // 配列から地形IDを取り出す
        }


        TileBit matchBits = map->matchTypes(terrain_gid, checkBit); // ビット論理積 & で地形のchekckBitを確かめる
        bool isCheckBit = matchBits != TileType::None;
        if (isCheckBit && pMapX != nullptr && pMapY != nullptr && pMapZ != nullptr)
            *pMapX = mapX, * pMapY = mapY, * pMapZ = mapZ; // ブロックのマス目のセル番号をポインタに記録(nullptrの場合は記録しない)
        if (isCheckBit && pMatchBits != nullptr && matchBits & (TileBit)TileType::Floor)
            *pMatchBits |= matchBits; // マッチしたビットをポインタにビット論理和して返す
        if (isCheckBit && pTerrainID != nullptr)
            *pTerrainID = terrain_gid; // チェックビット条件に当てはまったらその地形IDを返す

        return isCheckBit;
    }

    // [X方向左]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckLeftX(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 左端がめりこんでいるか?
        if (checkID(checkBit, xLeft, yTop, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Top | HitPart::Back; // 左上手前がめりこんでいる
        if (checkID(checkBit, xLeft, yTop, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Top | HitPart::ZMiddle; // 左上中がめりこんでいる
        if (checkID(checkBit, xLeft, yTop, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Top | HitPart::Forward; // 左上奥がめりこんでいる
        if (checkID(checkBit, xLeft, yMiddle, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::YMiddle | HitPart::Back; // 左中手前がめりこんでいる
        if (checkID(checkBit, xLeft, yMiddle, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::YMiddle | HitPart::ZMiddle; // 左中中がめりこんでいる
        if (checkID(checkBit, xLeft, yMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::YMiddle | HitPart::Forward; // 左中奥がめりこんでいる
        if (checkID(checkBit, xLeft, yBottom, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Bottom | HitPart::Back; // 左下手前がめりこんでいる
        if (checkID(checkBit, xLeft, yBottom, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Bottom | HitPart::ZMiddle; // 左下中がめりこんでいる
        if (checkID(checkBit, xLeft, yBottom, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Bottom | HitPart::Forward; // 左下奥がめりこんでいる

        return (HitBit)HitPart::None;
    }

    // [X方向右]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckRightX(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 右端がめりこんでいるか?
        if (checkID(checkBit, xRight, yTop, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Top | HitPart::Back; // 右上手前がめりこんでいる
        if (checkID(checkBit, xRight, yTop, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Top | HitPart::ZMiddle; // 右上中がめりこんでいる
        if (checkID(checkBit, xRight, yTop, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Top | HitPart::Forward; // 右上奥がめりこんでいる
        if (checkID(checkBit, xRight, yMiddle, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::YMiddle | HitPart::Back; // 右中手前がめりこんでいる
        if (checkID(checkBit, xRight, yMiddle, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::YMiddle | HitPart::ZMiddle; // 右中中がめりこんでいる
        if (checkID(checkBit, xRight, yMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::YMiddle | HitPart::Forward; // 右中奥がめりこんでいる
        if (checkID(checkBit, xRight, yBottom, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Bottom | HitPart::Back; // 右下手前がめりこんでいる
        if (checkID(checkBit, xRight, yBottom, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Bottom | HitPart::ZMiddle; // 右下中がめりこんでいる
        if (checkID(checkBit, xRight, yBottom, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Bottom | HitPart::Forward; // 右下奥がめりこんでいる

        return (HitBit)HitPart::None;
    }

    // [X方向左右]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckX(TileType checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {   // 下の関数にTileBit型をキャスト変換して取次ぎ
        return hitcheckX((TileBit)checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
    }
    // [X方向左右]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckX(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 左端がめりこんでいるか?
        HitBit hitPartLeftBit = hitcheckLeftX(checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        if (hitPartLeftBit != HitPart::None)
            return hitPartLeftBit;

        // 右端がめりこんでいるか?
        HitBit hitPartRightBit = hitcheckRightX(checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        if (hitPartRightBit != HitPart::None)
            return hitPartRightBit;

        return (HitBit)HitPart::None;
    }

    // [Y方向下]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckBottomY(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 足元が壁にめりこんでいるか?
        if (checkID(checkBit, xLeft, yBottom, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Bottom | HitPart::Back; // 左下手前がめりこんでいる
        if (checkID(checkBit, xLeft, yBottom, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Bottom | HitPart::ZMiddle; // 左下中がめりこんでいる
        if (checkID(checkBit, xLeft, yBottom, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Bottom | HitPart::Forward; // 左下奥がめりこんでいる
        if (checkID(checkBit, xMiddle, yBottom, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Bottom | HitPart::Back; // 中下手前がめりこんでいる
        if (checkID(checkBit, xMiddle, yBottom, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Bottom | HitPart::ZMiddle; // 中下中がめりこんでいる
        if (checkID(checkBit, xMiddle, yBottom, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Bottom | HitPart::Forward; // 中下奥がめりこんでいる
        if (checkID(checkBit, xRight, yBottom, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Bottom | HitPart::Back; // 右下手前がめりこんでいる
        if (checkID(checkBit, xRight, yBottom, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Bottom | HitPart::ZMiddle; // 右下中がめりこんでいる
        if (checkID(checkBit, xRight, yBottom, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Bottom | HitPart::Forward; // 右下奥がめりこんでいる

        return (HitBit)HitPart::None;
    }

    // [Y方向下]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckTopY(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 頭の部分がめりこんでいるか?
        if (checkID(checkBit, xLeft, yTop, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Top | HitPart::Back; // 左上手前がめりこんでいる
        if (checkID(checkBit, xLeft, yTop, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Top | HitPart::ZMiddle; // 左上中がめりこんでいる
        if (checkID(checkBit, xLeft, yTop, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Top | HitPart::Forward; // 左上奥がめりこんでいる
        if (checkID(checkBit, xMiddle, yTop, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Top | HitPart::Back; // 中上手前がめりこんでいる
        if (checkID(checkBit, xMiddle, yTop, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Top | HitPart::ZMiddle; // 中上中がめりこんでいる
        if (checkID(checkBit, xMiddle, yTop, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Top | HitPart::Forward; // 中上奥がめりこんでいる
        if (checkID(checkBit, xRight, yTop, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Top | HitPart::Back; // 右上手前がめりこんでいる
        if (checkID(checkBit, xRight, yTop, zMiddle, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Top | HitPart::ZMiddle; // 右上中がめりこんでいる
        if (checkID(checkBit, xRight, yTop, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Top | HitPart::Forward; // 右上奥がめりこんでいる

        return (HitBit)HitPart::None;
    }

    // [Y方向上下]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckY(TileType checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {   // 下の関数にTileBit型をキャスト変換して取次ぎ
        return hitcheckY((TileBit)checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
    }
    // [Y方向上下]地形の ダメージブロックなどにぶつかったかをチェックする関数
    HitBit hitcheckY(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 下端がめりこんでいるか?
        HitBit hitPartBottomBit = hitcheckBottomY(checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        if (hitPartBottomBit != HitPart::None)
            return hitPartBottomBit;

        // 上端がめりこんでいるか?
        HitBit hitPartTopBit = hitcheckTopY(checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        if (hitPartTopBit != HitPart::None)
            return hitPartTopBit;

        return (HitBit)HitPart::None;
    }
#if 0
    // [Y方向フロア床]地形の Floor:床にぶつかったかをチェックする関数(一つ上のタイルが床:Floorならtrue)
    HitBit hitcheckFloorY(//Tile::Bit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 一つ上のタイルが Floor:床 で 下端がめりこんでいるか?
        HitBit hitPartBottomBit = hitcheckBottomY((int)TileType::Floor, xLeft, xMiddle, xRight, yBottom + CellSize, yMiddle + CellSize, yTop + CellSize, zBack, zMiddle, zForward,
            pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        if (hitPartBottomBit != HitPart::None)
            return hitPartBottomBit;

        return (HitBit)HitPart::None;
    }
#endif
    // [Z方向手前]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckBackZ(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 手前の部分がめりこんでいるか?
        if (checkID(checkBit, xLeft, yTop, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Top | HitPart::Back; // 左上手前がめりこんでいる
        if (checkID(checkBit, xMiddle, yTop, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Top | HitPart::Back; // 中上手前がめりこんでいる
        if (checkID(checkBit, xRight, yTop, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Top | HitPart::Back; // 右上手前がめりこんでいる
        if (checkID(checkBit, xLeft, yMiddle, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::YMiddle | HitPart::Back; // 左中手前がめりこんでいる
        if (checkID(checkBit, xMiddle, yMiddle, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::YMiddle | HitPart::Back; // 中中手前がめりこんでいる
        if (checkID(checkBit, xRight, yMiddle, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::YMiddle | HitPart::Back; // 右中手前がめりこんでいる
        if (checkID(checkBit, xLeft, yBottom, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Bottom | HitPart::Back; // 左下手前がめりこんでいる
        if (checkID(checkBit, xMiddle, yBottom, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Bottom | HitPart::Back; // 中下手前がめりこんでいる
        if (checkID(checkBit, xRight, yBottom, zBack, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Bottom | HitPart::Back; // 右下手前がめりこんでいる

        return (HitBit)HitPart::None;
    }

    // [Z方向奥]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckForwardZ(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 奥が壁にめりこんでいるか?
        if (checkID(checkBit, xLeft, yTop, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Top | HitPart::Forward; // 左上奥がめりこんでいる
        if (checkID(checkBit, xMiddle, yTop, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Top | HitPart::Forward; // 中上奥がめりこんでいる
        if (checkID(checkBit, xRight, yTop, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Top | HitPart::Forward; // 右上奥がめりこんでいる
        if (checkID(checkBit, xLeft, yMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::YMiddle | HitPart::Forward; // 左中奥がめりこんでいる
        if (checkID(checkBit, xMiddle, yMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::YMiddle | HitPart::Forward; // 中中奥がめりこんでいる
        if (checkID(checkBit, xRight, yMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::YMiddle | HitPart::Forward; // 右中奥がめりこんでいる
        if (checkID(checkBit, xLeft, yBottom, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Left | HitPart::Bottom | HitPart::Forward; // 左下奥がめりこんでいる
        if (checkID(checkBit, xMiddle, yBottom, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::XMiddle | HitPart::Bottom | HitPart::Forward; // 中下奥がめりこんでいる
        if (checkID(checkBit, xRight, yBottom, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits))
            return HitPart::Right | HitPart::Bottom | HitPart::Forward; // 右下奥がめりこんでいる

        return (HitBit)HitPart::None;
    }


    // [Z方向手前奥]地形の ダメージブロックなどにぶつかったかをチェックする関数
    inline HitBit hitcheckZ(TileType checkBit, float xLeft, float xMiddle, float xRight, float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {   // 下の関数にTileBit型をキャスト変換して取次ぎ
        return hitcheckZ((TileBit)checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
    }
    // [Z方向手前奥]地形の ダメージブロックなどにぶつかったかをチェックする関数
    HitBit hitcheckZ(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 手前がめりこんでいるか?
        HitBit hitPartBackBit = hitcheckBackZ(checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        if (hitPartBackBit != HitPart::None)
            return hitPartBackBit;

        // 奥がめりこんでいるか?
        HitBit hitPartForwardBit = hitcheckForwardZ(checkBit, xLeft, xMiddle, xRight, yBottom, yMiddle, yTop, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
        if (hitPartForwardBit != HitPart::None)
            return hitPartForwardBit;

        return (HitBit)HitPart::None;
    }

    // 地形の ダメージブロックなどにぶつかったかをチェックする一時関数
    HitBit hitcheck(TileBit checkBit,
        float xLeft, float xMiddle, float xRight,
        float yTop, float yMiddle, float yBottom,
        float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        return hitcheckX(checkBit, xLeft, xMiddle, xRight, yTop, yMiddle, yBottom, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits)
            | hitcheckY(checkBit, xLeft, xMiddle, xRight, yTop, yMiddle, yBottom, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits)
            | hitcheckZ(checkBit, xLeft, xMiddle, xRight, yTop, yMiddle, yBottom, zBack, zMiddle, zForward, pTerrainID, pMapX, pMapY, pMapZ, pMatchBits);
    }
};

#endif

地獄のようなコード量になりましたが仕方ありません。上下左右奥手前がめり込んでいるかをifで判定するので条件判定式がかさむからです。
沢山処理があるように見えてますが、実はほぼ全部checkID関数に上下左右奥手前の色んなバリエーションを渡して当たっているか判定しているだけです。
縦一列に「checkID 関数が並ぶ様子はまさに圧巻」です。この一つバグがありでもしたら...ゾッとします。教育教育教育教育教育教育...修正修正修正修正修正修正...
上下左右手前奥の色んな組み合わせがあるから、当然 if判定のコードが増える、というわけです。
なので、むしろ注目してほしいのはOnHitLeft や OnHitBottomなどのOnHit系の当たった時の押し戻し関数やOnAir関数などの当たっていない関数です。
足元がタイルに当たったら、オーナーのvy = 0;にして速度を0にして、めり込みぶんを押し戻しています。これで地面に立てるのです。

では、今度はGameObject側にもOnHitTile関数を追加して、map()関数でMapHitBoxComponent側がオーナーのいるマップにアクセスできるようにしましょう。

GameObject.hを変更して、OnCollisionなどの衝突判定のコールバック関数を準備しましょう。

#ifndef GAMEOBJECT_H_
#define GAMEOBJECT_H_

#include <memory> // 共有ポインタの定義のため

#include "Vector3.h"
#include "Quaternion.h"
#include "Tile.h" // OnHitTileのコールバックに必要

#include "Object.h" // インスタンスIDや検索タグを持ち、shared_from_this_as<継承先クラス名>などができる
#include "World.h"

class World; // 前方宣言
class Map; // 前方宣言
class Component; // 前方宣言
class Collision; // 前方宣言

// ゲーム上に表示される物体の基底クラス。
// プレイヤーや敵、アイテムなどはこのクラスを継承して作る。
class GameObject : public Object
{
protected:
    // コンストラクタはprotected:にしてあるのでCreate関数経由で初期生成してください
    GameObject(World* world = nullptr, Vector3 position = { 0,0,0 }, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : Object(), m_world{ world }, position{ position }, rotation{ rotation }, velocity{ velocity }, force{ force }
    {

    }

public:
    // 仮想デストラクタ
    virtual ~GameObject() {}


    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    }; // unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    Quaternion rotation; // 回転クオータニオン

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

    // 重力を返す、重力を変えたいときはoverrideして処理を変えてください、重力を変えてない場合は 0.0f
    virtual inline float gravity() { return 0.0f; }

protected:
    World* m_world; // GameObjectが配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }
    // GameObjectが現在いるマップへのポインタを返す
    Map* map() { return m_world->map.get(); }

   
    bool isDead{ false }; // 死んだ(削除対象)フラグ
    bool isStatic{ false }; // 動かない(静的オブジェクトか?)
    bool isGround{ false }; // 地面についているか
    bool isJumping{ false }; // ジャンプ中か
    bool isFlying{ false }; // 空をとんでいるか

    float pushPower{ 5.0f }; // コライダで接触したときに押し返す力(相手側が強いと押される)
    float mass{ 1.0f }; // 質量(重さ)コライダで接触したときに重さでforceを割り算すると動きにくくなる

    hash32 typeTag{ "" }; // 小カテゴリ Zako0など個別のタイプ
    hash32 baseTag{ "" }; // 大カテゴリ Enemyなど大まかなベースジャンル

protected:
    // コンポーネントの<ハッシュID, 共有所有ポインタ> の辞書でコンポーネントの共有カウンタ+1を保持する
    std::unordered_map<size_t, std::shared_ptr<Component>> components;
public:
    // コンポーネントを得る 例えば GetComponent<BoxCollider>でBoxColliderという名前のコンポーネントをcomponents辞書から得る
    template<class ComponentT>
    std::vector<std::weak_ptr<ComponentT>> GetComponents()
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        std::vector<std::weak_ptr<ComponentT>> returnList; // "ComponentT" 例."CircleCollider"など をキーとするコンポーネント一覧
        for (auto ite = range.first; ite != range.second; ++ite)
            returnList.emplace_back(ite->second); // 辞書で見つかったコンポーネントを返すリストに追加
        return returnList;
    }

    // コンポーネントを追加する .AddComponent<BoxCollider>(...)で追加できる
    template<class ComponentT, typename ...ArgsT>
    std::weak_ptr<ComponentT> AddComponent(ArgsT&&...args)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]

        // shared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        std::shared_ptr<ComponentT> newComponent = std::make_shared<ComponentT>(shared_from_this_as<GameObject>(), std::forward<ArgsT>(args)...);
       
        // https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        // std::unrodered_multimap<> と同型の値を取る std::pair<> を作って insert 関数でcomponents辞書に追加
        components.insert(std::pair<size_t, std::shared_ptr<ComponentT>>(hashID, newComponent));
        return newComponent;
    }

    // コンポーネントを削除する
    template<typename ComponentT>
    bool RemoveComponent(std::shared_ptr<ComponentT> removeComponent)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/

        //[イテレート中に探しながら消す] https://cpprefjp.github.io/reference/unordered_map/unordered_multimap/erase.html
        for (auto ite = range.first; ite != range.second;)
            if (ite->second == removeComponent)
            {
                components.erase(ite); // 削除された要素の次を指すイテレータが返される
                return true; // 見つかって消せた場合は true
            }
            else
                ++ite; // 次の要素へイテレータを進める

        return false; // 見つからずに消せなかった場合は false
    }

    // 更新処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Update() = 0; // 純粋仮想関数 = 0 にすると【絶対 Update()関数はoverrideしなきゃいけない義務を付与できる 】

    // 描画処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Draw() = 0; // 純粋仮想関数 = 0 にすると【絶対 Draw()関数はoverrideしなきゃいけない義務を継承先クラスに付与 】

    // 衝突開始したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollisionEnter(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突開始したときの処理などをオブジェクトの種類ごとに実装する
    };

    // 衝突したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollision(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    };

    // 衝突終了したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollisionExit(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突終了したときの処理などをオブジェクトの種類ごとに実装する
    };

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump()
    {
        // overrideしてジャンプした瞬間に音を鳴らしたりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中でジャンプの上昇中に呼ばれる関数
    virtual void OnJumping()
    {
        // overrideして空中でジャンプの上昇中にエフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中で落ちている最中に呼ばれる関数
    virtual void OnFalling()
    {
        // overrideして空中で落ちている最中に音を鳴らしたり、エフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // タイルに当たったときのコールバック
    virtual void OnHitTile(HitPart hitPart, HitBit hitPartBit, TileBit checkBit, float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 継承 overrideしてタイルに当たったときの処理を実装する
    }

};

#endif


Player.hを変更して、タイルマップと当たり判定をするコンポーネントの初期 Init 設定を追加しましょう。

#ifndef PLAYER_H_
#define PLAYER_H_

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "GameObject.h"
#include "PhysicsComponent.h"
#include "MapHitBoxComponent.h"
#include "GridCollidersComponent.h"
#include "Collider.h"

class World; // 前方宣言
class Camera; // 前方宣言

class Player : public GameObject
{
protected:
    // コンストラクタはprotected:にしてあるので、Create関数経由で初期生成してください
    Player(World* world, Pad pad, Vector3 position, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : pad{ pad }, GameObject(world, position, rotation, velocity, force)
    {
        this->typeTag = "Player"; // タグは個別の"プレイヤ1"などに使い、タイプはtypeTagで判別する形にしてタイプのジャンルを区別
        this->baseTag = "Player";
        this->pushPower = 5.0f; // コライダで衝突したときに押し返す力
    }

public:
    // 仮想デストラクタ
    virtual ~Player() {}

    Pad pad; // 操作するコントローラー番号
    hash32 worldTag{ "" }; // 現在いるワールドのタグ
    float MoveSpeedMax = 6; // 移動速度Max値
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // プレイヤの動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

    std::shared_ptr<Texture> image; // プレイヤの板ポリゴンで描くタイル分割画像への共有リンク

    Camera* camera{ nullptr }; // プレイヤを追随するカメラ
    void SetCamera(Camera* pCamera) { camera = pCamera; }

    // overrideして物理落下コンポーネントからの重力値を返す
    virtual float gravity() override { auto pGravityPhysics = gravityPhysics.lock(); return (pGravityPhysics != nullptr) ? pGravityPhysics->gravity() : 0.0f; }
protected:
    std::weak_ptr<JumpComponent> jumpPhysics; // ジャンプの物理処理コンポーネント
    std::weak_ptr<GravityComponent> gravityPhysics; // 落下の重力の物理処理コンポーネント
    // マップ地形に対するヒットボックスの当たり判定と押し戻しをするコンポーネント
    std::weak_ptr<MapHitBoxComponent> mapHitBox;


    std::weak_ptr<GridCollidersComponent> gridCollision; // グリッドマス目分割コンポーネント
    std::weak_ptr<SphereCollider> sphereCollider; // 球コライダのコンポーネント

public:
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        std::vector<float> vyJumpSpeed{ 13, 14, 15, 4 }; // ジャンプ開始の初期速度
        std::vector<float> vyForceJump{ 0.5f, 0.4f, 0.3f, 0.1f }; // ジャンプ上昇にかかる力の初期値
        std::vector<float> vyGravity{ 0.8f,0.8f,0.8f,0.8f }; // 重力の設定値
        std::vector<float> vyDownSpeedMax{ 16, 16, 16, 8 }; // 降下スピードのリミット
        // AddComponent内部でshared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        jumpPhysics = AddComponent<JumpComponent>(vyJumpSpeed, vyForceJump);
        gravityPhysics = AddComponent<GravityComponent>(vyGravity, vyDownSpeedMax);
        // 半径16(直径32)の球体コライダ
        sphereCollider = AddComponent<SphereCollider>(Vector3(0, 0, 0), 16);
        gridCollision = AddComponent<GridCollidersComponent>("Player"); //とりあえずわかりやすいように"Player"というタグをつけておく(別に何でもいい)
        gridCollision.lock()->LinkCollider(sphereCollider); // コライダをリンクする

        float mapHitBoxSize = sphereCollider.lock()->m_radius * 2.0f; // 球体コライダの直径に合わす
        Vector3 boxSize{ mapHitBoxSize,mapHitBoxSize,mapHitBoxSize };
        Vector3 offsetLeftBottomBack{ 3, 0, 3 }; // ボックスの端を内側にどれだけ削るか(キャラの画像に合わせて小さくできる)
        Vector3 offsetRightTopForward{ 3, 8, 3 }; // ボックスの端を内側にどれだけ削るか(頭を8削る)
        mapHitBox = AddComponent<MapHitBoxComponent>(boxSize, offsetLeftBottomBack, offsetRightTopForward);

    }

    // 入力を受けての処理
    virtual void HandleInput();

    // 更新処理
    virtual void Update() override;

    // 描画処理
    virtual void Draw() override;

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump() override;

    // コライダの衝突した際に呼ばれるコールバック関数
    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override;
};

#endif


Player.cppを変更して、コンポーネントを使ってタイルマップと当たり判定をしましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}

// ジャンプ開始した直後に呼ばれる関数
void Player::OnStartJump()
{
    this->isGround = false; // ジャンプ中はisGroundフラグがfalse
}


// 更新処理
void Player::Update()
{
    vxForce *= 0.9f; // かかっている力の減衰率
    vyForce *= 0.9f; // かかっている力の減衰率
    vzForce *= 0.9f; // かかっている力の減衰率
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    auto pJumpPhysics = jumpPhysics.lock();
    if (pJumpPhysics != nullptr)
    {
        if (isGround)
        {
            if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
                || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
            {
                pJumpPhysics->JumpStart(); // ジャンプのスタート処理を発動
            }
        }
        else if (vy > 0 && Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
            || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
        {
            pJumpPhysics->JumpCancel(); // ジャンプのキャンセル処理を発動
        }
        pJumpPhysics->Update(); // ジャンプのコンポーネントを更新する
    }
    auto pGravityPhysics = gravityPhysics.lock();
    if (pGravityPhysics != nullptr)
        pGravityPhysics->Update(); // 重力落下のコンポーネントを更新する

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    float vyForceJump = (pJumpPhysics != nullptr) ? pJumpPhysics->vyForce() : 0.0f;
    float vyForceGravity = (pGravityPhysics != nullptr) ? pGravityPhysics->vyForce() : 0.0f;

    vx += vxForce; // 力をかけて速度に加算する
    vy += vyForce + vyForceJump + vyForceGravity; // 勢い(力・フォースを加算
    vz += vzForce; // 力をかけて速度に加算する

    // 実際に位置を動かす

    auto pMapHitBox = mapHitBox.lock();

    // まず横に移動する
    x += vx;
    if (pMapHitBox != nullptr)
        pMapHitBox->hitCheckX((TileBit)TileType::Wall);


    // 次に縦に動かす
    y += vy;
    if (pMapHitBox != nullptr)
        pMapHitBox->hitCheckY((TileBit)TileType::Wall | (TileBit)TileType::Floor);


    // 次にZ奥行き方向に動かす
    z += vz;
    if (pMapHitBox != nullptr)
        pMapHitBox->hitCheckZ((TileBit)TileType::Wall);


    if (y <= 0.0f) // 0.0fを地面として 0.0f以下になったら地面に着地したと判定する
    {
        y = 0.0f; // 地面に沿わせる
        vy = -0.01f; // 下方向への速度をほぼ 0 にする
        isGround = true;
    }


    if (auto pGridCollision = gridCollision.lock()) // lock()してnullptrじゃなければ
        pGridCollision->Update(); // 所属するマス目セルを更新する

    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

void Player::OnCollision(std::shared_ptr<Collision> pCollision)
{
    if (isDead) return; // すでにisDeadなら return
    auto otherCollider = pCollision->contacts[0].otherCollider.lock(); // コライダの弱共有ポインタをロック
    if (otherCollider == nullptr) return; // すでにコライダが消去済だった
    auto other = otherCollider->owner();

    if (other->isDead) return; // other側がisDeadだったら処理をスルー

    if (other->baseTag == "Enemy" || other->baseTag == "Player")
    {
        //[離散要素法:DEM] https://qiita.com/konbraphat51/items/157e5803c514c60264d2
        constexpr float k = 1.0f; // バネ係数(めり込みに対するバネ反発の掛け率)
        constexpr float e = 0.1f; // はね返り係数(直前と比べどれくらいの速さで離れるか0.5だと半分の速さではね返る)
        float log_e = std::log(e); // はね返り係数の自然対数log
        constexpr float pi_2 = DX_PI_F * DX_PI_F; // πの2乗
        float eta = -2.0f * log_e * std::sqrt(k * mass / (log_e * log_e + pi_2)); // 粘性係数η(イータ)
        auto norm = pCollision->contacts[0].normal; // 衝突の法線 * other->pushPower; // pushPowerの力でotherに押し返される
        auto v_norm = dot(other->velocity - velocity, norm) * norm; // [相対速度v] 法線norm方向のv = (v1 - v0)・norm [ドット積]
        auto sep = pCollision->contacts[0].separation; // 衝突点どうしの距離(めりこみ距離)
        auto F = -k * sep * norm - eta * v_norm; // F (法線方向の力) = -k × sep - η × v (相対速度:自分から見た相手otherの速度)
        force += F / mass; // 衝突法線方向の力を加える
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

#ifdef _DEBUG // デバッグのときだけ_DEBUGが定義され描かれる
                if (auto pSphereCollider = sphereCollider.lock()) pSphereCollider->Draw(); // コライダを描く
                if (auto pMapHitBox = mapHitBox.lock()) pMapHitBox->Draw(); // ヒットボックスを描く
#endif
            });

    }
}


いかがでしょう?Debugビルドで当たり判定のボックスが表示され、タイルの上を歩くことができるようになりましたか?
ただし、現状では高速でタイルと当たり判定するときは、
当然 vx などが タイルのサイズ 32 を超える35などで動けば隣の隣のタイルと当たり判定することになりすり抜けてしまいます

実験として、プレイヤ1を勢いよくプレイヤ2にぶつけてみてください。タイルのブロックをすり抜ける現象が発生するはずです。
この現象は当然、球体同士のコライダでも起こりえます。
ではどうすればすり抜けないようにできるでしょうか?
すり抜け現象が起こってしまうのは タイルの大きさ 32 に対して速度 vx が 35になり超えてしまっているからです。
では、32以下の 30 などのスピードならすり抜けは起こらないのでしょうか?
いいえ、実はめり込み具合が 32の半分の 16 を超えてしまうとめり込みの 小さい壁の向こう側に向けて押し返されてしまうのです。

ですので、vx や vz などを16以下の細切れにして少しづつ移動させながら当たり判定をループさせて
最終的に 16 + 16 + 3 = 35のように35移動すればすり抜けずに止まるか、壁がなければ +35 まで動くことができます。
斜め方向の移動の場合はどうしましょうか?
vx が 35で vz が 70 の速さだとしましょう。
この場合は、70のほうに合わせて細切れにすればいい
のです。
vz を 16ずつ動かす場合には vx を 16 × 35 / 70 = 8 ずつ動かせば斜めの方向の比率としては同じになるはずです。


GridSteps.hを新規作成して、当たり判定がすり抜けないように細切れのグリッドの数値を計算する機能を準備しましょう。

#ifndef GRID_STEPS_H_
#define GRID_STEPS_H_

#include "Vector3.h"

// 超高速すぎて当たり判定をすり抜けないように gridSize で分割して
// グリッドを飛び越えずに1つ1つ 細かい step で進んですり抜けないように div 回分進んで、
// 最後に余りの mod ぶん進む と vec と同じぶん、進んだことになり、当たり判定もすり抜けずにすむ
struct GridSteps
{
    int div; // stepを何回ぶん繰り返すと vec と等しくなるか
    Vector3 step; //[ステップ] vec = step * div + mod で細かく div 回進むようにする
    Vector3 mod; //[余り] vec = step * div + mod で細かく div 回進んで最後に 余りmodぶん進む
    inline GridSteps(const Vector3& vec, const Vector3& gridSize, int divLimit = 1000)
    {
        float vx = vec.x, vy = vec.y, vz = vec.z;
        float xGridSize = gridSize.x, yGridSize = gridSize.y, zGridSize = gridSize.z;
        // ブロックなどのグリッドを貫通するほどの速さのvxがあるとき
        // グリッドを1つ1つまたぐ分割されたスピートを vx の割り算とその余りで求める
        float vxAbs = (vx < 0) ? -vx : vx, vyAbs = (vy < 0) ? -vy : vy, vzAbs = (vz < 0) ? -vz : vz;
        float signX = (vx < 0) ? -1 : +1, signY = (vy < 0) ? -1 : +1, signZ = (vz < 0) ? -1 : +1;
        float vxDiv = vxAbs / xGridSize; // vxから±を消した絶対値 を グリッドのサイズで割ると必要ステップ数
        float vyDiv = (yGridSize == 0) ? 1.0f : vyAbs / yGridSize;
        float vzDiv = (zGridSize == 0) ? 1.0f : vzAbs / zGridSize;
        if (vxDiv <= 1.0f && vyDiv <= 1.0f && vzDiv <= 1.0f)
        {
            this->div = 1;
            this->step = vec; // 1マスしか進まない場合は速度をそのまま返す
            return;
        }

        if (vxDiv > divLimit) vxDiv = divLimit;
        if (vyDiv > divLimit) vyDiv = divLimit; // divが大きくなりすぎるとStepが∞に近づくのでリミットをかける
        if (vzDiv > divLimit) vzDiv = divLimit;
        float vxMod = std::fmod(vxAbs, xGridSize); // グリッドのサイズで速度を割った余りmod
        float vyMod = (yGridSize == 0) ? 0 : std::fmod(vyAbs, yGridSize);
        float vzMod = (zGridSize == 0) ? 0 : std::fmod(vzAbs, zGridSize);
        // ∞=infや計算不能 nan を !isfiniteで 検出したら 0 にする https://qiita.com/N700A/items/36b9383eaf923f4bf6ae
        if (vxMod > xGridSize || !isfinite(vxMod)) vxMod = 0;
        if (vyMod > yGridSize || !isfinite(vyMod)) vyMod = 0; // 速度が∞に近づくと余りも∞になるので 0 にする
        if (vzMod > zGridSize || !isfinite(vzMod)) vzMod = 0;

        int maxDiv; // 通過する区切りの回数が一番多いもの(= maxDiv)に合わせて進むx,y,zの1回のステップを決める
        float xStep = xGridSize, yStep = yGridSize, zStep = zGridSize;
        if (vxDiv >= vyDiv && vxDiv >= vzDiv) // 1番割り算したDivの区切りの多いものにあわせて他はその区切りで割るので
            maxDiv = (int)vxDiv, yStep = vyAbs / maxDiv, zStep = vzAbs / maxDiv, vyMod = 0, vzMod = 0; // 他の余りは 0 にする
        else if (vyDiv >= vzDiv && vyDiv >= vxDiv)
            maxDiv = (int)vyDiv, xStep = vxAbs / maxDiv, zStep = vzAbs / maxDiv, vxMod = 0, vzMod = 0;
        else
            maxDiv = (int)vzDiv, xStep = vxAbs / maxDiv, yStep = vyAbs / maxDiv, vxMod = 0, vyMod = 0;
        // ステップの回数 div,1回のステップ step, 最後のステップのつじつまを合わせる余り mod を返す
        this->div = maxDiv;
        this->step = Vector3{ signX * xStep, signY * yStep, signZ * zStep };
        this->mod = Vector3{ signX * vxMod, signY * vyMod, signZ * vzMod };
    }
};

#endif


Player.cppを変更して、GridStepsで細切れに移動してすり抜けないように当たり判定をしましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"
#include "GridSteps.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}

// ジャンプ開始した直後に呼ばれる関数
void Player::OnStartJump()
{
    this->isGround = false; // ジャンプ中はisGroundフラグがfalse
}


// 更新処理
void Player::Update()
{
    vxForce *= 0.9f; // かかっている力の減衰率
    vyForce *= 0.9f; // かかっている力の減衰率
    vzForce *= 0.9f; // かかっている力の減衰率
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    auto pJumpPhysics = jumpPhysics.lock();
    if (pJumpPhysics != nullptr)
    {
        if (isGround)
        {
            if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
                || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
            {
                pJumpPhysics->JumpStart(); // ジャンプのスタート処理を発動
            }
        }
        else if (vy > 0 && Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
            || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
        {
            pJumpPhysics->JumpCancel(); // ジャンプのキャンセル処理を発動
        }
        pJumpPhysics->Update(); // ジャンプのコンポーネントを更新する
    }
    auto pGravityPhysics = gravityPhysics.lock();
    if (pGravityPhysics != nullptr)
        pGravityPhysics->Update(); // 重力落下のコンポーネントを更新する

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    float vyForceJump = (pJumpPhysics != nullptr) ? pJumpPhysics->vyForce() : 0.0f;
    float vyForceGravity = (pGravityPhysics != nullptr) ? pGravityPhysics->vyForce() : 0.0f;

    vx += vxForce; // 力をかけて速度に加算する
    vy += vyForce + vyForceJump + vyForceGravity; // 勢い(力・フォースを加算
    vz += vzForce; // 力をかけて速度に加算する

    // 実際に位置を動かす

    auto pMapHitBox = mapHitBox.lock();
    float cellHalf = (map() != nullptr) ? (float)map()->CellSize() / 2.0f : 16.0f; // マップのセルのサイズの半分
    // 速すぎて当たり判定がすり抜けないように細切れのステップのグリッドで移動させつつ当たり判定する
    Vector3 gridStepSize = (pMapHitBox == nullptr) ? Vector3{ cellHalf, cellHalf, cellHalf } : pMapHitBox->m_BoxSize / 2;
    GridSteps vSteps{ velocity, gridStepSize }; // 細切れのステップの数値を計算
#ifdef _DEBUG
    if (vSteps.div >= 1000) // 分割が1000を超える場合は物理式かどこかで∞=inf や 計算不可能数=nanが発生してそう
    {   // vx,vy,vzに原因がありそうなのでデバッグ表示
        if (!isfinite(vx)) printfDx(("vx:∞かnan x:" + std::to_string(x) + "\n").c_str());
        if (!isfinite(vy)) printfDx(("vy:∞かnan y:" + std::to_string(y) + "\n").c_str());
        if (!isfinite(vz)) printfDx(("vz:∞かnan z:" + std::to_string(z) + "\n").c_str());
    }
#endif

    bool isCollisionX{ false }, isCollisionY{ false }, isCollisionZ{ false }, isCollisionCollider{ false };
    for (int i = 1, iSize = vSteps.div + 1; i <= iSize; ++i)
    {
        auto& vStep = (i < iSize) ? vSteps.step : vSteps.mod;
        // まず横に移動する
        if (pMapHitBox == nullptr)
            x += vx;
        else if (isCollisionX == false) // まだ前のステップでX方向の衝突がないなら
        {
            x += vStep.x;
            isCollisionX = pMapHitBox->hitCheckX((TileBit)TileType::Wall);
        }

        // 次に縦に動かす
        if (pMapHitBox == nullptr)
            y += vy;
        else if (isCollisionY == false) // まだ前のステップでY方向の衝突がないなら
        {
            y += vStep.y;
            isCollisionY = pMapHitBox->hitCheckY((TileBit)TileType::Wall | (TileBit)TileType::Floor);
        }

        // 次にZ奥行き方向に動かす
        if (pMapHitBox == nullptr)
            z += vz;
        else if (isCollisionZ == false) // まだ前のステップでZ方向の衝突がないなら
        {
            z += vStep.z;
            isCollisionZ = pMapHitBox->hitCheckZ((TileBit)TileType::Wall);
        }


        if (auto pGridCollision = gridCollision.lock())
        {
            pGridCollision->Update(); // 所属するマス目セルを更新する
            for (auto& collider : *pGridCollision->colliders())
                isCollisionCollider |= collider.lock()->IsCollision();
        }

        // 衝突があったら、速すぎてすり抜けちゃう前に今のステップでbreakで抜ける
        if (isCollisionCollider)
            break; // ぶつかっているのにさらに次のステップの速度も足すとすり抜けちゃう
    }


    // まず横に移動する
    x += vx;
    if (pMapHitBox != nullptr)
        pMapHitBox->hitCheckX((TileBit)TileType::Wall);

    // 次に縦に動かす
    y += vy;
    if (pMapHitBox != nullptr)
        pMapHitBox->hitCheckY((TileBit)TileType::Wall | (TileBit)TileType::Floor);

    // 次にZ奥行き方向に動かす
    z += vz;
    if (pMapHitBox != nullptr)
        pMapHitBox->hitCheckZ((TileBit)TileType::Wall);

   
    if (auto pGridCollision = gridCollision.lock()) // lock()してnullptrじゃなければ
        pGridCollision->Update(); // 所属するマス目セルを更新する


    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

void Player::OnCollision(std::shared_ptr<Collision> pCollision)
{
    if (isDead) return; // すでにisDeadなら return
    auto otherCollider = pCollision->contacts[0].otherCollider.lock(); // コライダの弱共有ポインタをロック
    if (otherCollider == nullptr) return; // すでにコライダが消去済だった
    auto other = otherCollider->owner();

    if (other->isDead) return; // other側がisDeadだったら処理をスルー

    if (other->baseTag == "Enemy" || other->baseTag == "Player")
    {
        //[離散要素法:DEM] https://qiita.com/konbraphat51/items/157e5803c514c60264d2
        constexpr float k = 1.0f; // バネ係数(めり込みに対するバネ反発の掛け率)
        constexpr float e = 0.1f; // はね返り係数(直前と比べどれくらいの速さで離れるか0.5だと半分の速さではね返る)
        float log_e = std::log(e); // はね返り係数の自然対数log
        constexpr float pi_2 = DX_PI_F * DX_PI_F; // πの2乗
        float eta = -2.0f * log_e * std::sqrt(k * mass / (log_e * log_e + pi_2)); // 粘性係数η(イータ)
        auto norm = pCollision->contacts[0].normal; // 衝突の法線 * other->pushPower; // pushPowerの力でotherに押し返される
        auto v_norm = dot(other->velocity - velocity, norm) * norm; // [相対速度v] 法線norm方向のv = (v1 - v0)・norm [ドット積]
        auto sep = pCollision->contacts[0].separation; // 衝突点どうしの距離(めりこみ距離)
        auto F = -k * sep * norm - eta * v_norm; // F (法線方向の力) = -k × sep - η × v (相対速度:自分から見た相手otherの速度)
        force += F / mass; // 衝突法線方向の力を加える
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

#ifdef _DEBUG // デバッグのときだけ_DEBUGが定義され描かれる
                if (auto pSphereCollider = sphereCollider.lock()) pSphereCollider->Draw(); // コライダを描く
                if (auto pMapHitBox = mapHitBox.lock()) pMapHitBox->Draw(); // ヒットボックスを描く
#endif
            });

    }
}


いかがでしょう?プレイヤ1で助走をつけてプレイヤ2にぶつかってプレイヤ2が高速で壁にぶつかってもすり抜けないようになったでしょうか?
泥臭いアナログなやり方ではありますが、すり抜け防止の機能は実現できるようになりました。
Unityなどのゲームエンジンの内部では高速の移動直線と交わるかの難しい数学ですり抜けない当たり判定をしているでしょうが、
今回のように、タイルの大きさがほぼ固定の 32 、キャラの大きさも だいたい32 という限定的な条件設定であれば、細切れステップのすり抜け防止も有効策となるわけです。



Factory.cppにZako生成などの#includeを集約(依存性の逆転)させてMapでZakoなどの敵を出現スポーンさせる

さて、次はマップの出現レンジをスクロールさせてザコなどの敵を出現スポーンさせましょう。
ただ、ちゃんと設計しないとザコ0、ザコ1,2,3.... と種類が増えるとZako0.h Zako1.h Zako2.h... とファイルはどんどん増え#includeもどんどん増えます。
#includeの機会が増えるということは循環インクルードする可能性も増えますし、
何よりどこで#include してたっけ?とかいちいちあちこちで#include書きたくないよ、とかの問題もでます。
さらに「あちこちで new や std::make_shared やObject::Create で実体を生成する処理を書くと引数など仕様が変わった際にコードを直す作業がちらばってて苦痛」です。
ですので、こういった「生成の処理を一括代行する工場(Factory)パターン」がコードのデザインパターンとしてあります。
ファクトリーパターンの書き方は人それぞれ違っては来ますが、大事なポイントは「依存性の逆転」です。
あるコードが別のコードやクラスを#includeして使用するということは「依存している」ということです。
MapでZako0を生成するということはMapがZako0.hを#include するということなので「MapはZako0.hに依存している」ことになります。
今回はFactoryクラスを導入してMapクラスではZako0を直接#includeする必要がない形にします。
そうするとMapの立場はどうなるか?MapクラスはZako0に「依存する形」からFactoryを「使用する立場」に変わります
「立場が[依存者]から[使用者]に逆転して変わります」Mapクラスの#includeはFactoryだけになります。
Mapクラス以外でも#includeはFactoryだけになり、#includeを書く数は格段に減ります。
なるほど、#includeがcppに集中するという点では仲介者Managerのパターンと似てはいますね。
仲介者Managerが「連絡先の窓口」のニュアンスが強いのに対して、ファクトリーパターンは「新規生成工場」のニュアンスが強いです。
ですので、以前作成したObject.hクラスのCreate関数でいろいろなGameObjectを新規生成するのもファクトリーパターンの一種といえます。
今回作成するFactoryクラスではそれに加えて、"Zako0"などの文字列からZako0というクラスを新規生成できる機能も追加します。
文字列"Zako0"からクラスを新規生成できれば、.jsonのステージに配置したオブジェクトのプロパティに"Zako0"などの文字列を指定すれば、
jsonのファイルの配置内容の文字列"Zako0"を判別してゲーム上に敵キャラを新規スポーン生成で出現させることができるようになります。

文字列"Zako0"からクラスを新規生成するには「文字列 → newやmake_sharedやCreateなどの新規生成手段の辞書があれば可能」です。
ですが、本来「プログラム上のクラス名はビルドされた後は機械語のビットに変わりプログラム上で見えているクラス名とは別物に翻訳」されてしまいます。
本来は無理な話のはずなのですよね。ただし、#マクロ を使うとビルド時に プログラム上のクラス名が文字列に変わるマジックがあります。

Factory.hを新規作成して、ザコやアイテムなど「生成する処理を集約して呼び出す工場の窓口」を準備しましょう。

#ifndef FACTORY_H_
#define FACTORY_H_

#include <string>
#include <memory> // 共有ポインタを使う
#include <unordered_map>
#include "Vector3.h"
#include "Quaternion.h"
#include <assert.h>
#include <list>

#include "Object.h"

class GameObject; // 前方宣言でクラス宣言だけしてインクルードしない(.hではポインタしか使ってないから)
class World;
class Enemy;
class Bullet;
class Light;
class Item;

// 無名名前空間で区切ることで関数名など被らないように(★constructorとかよくある関数名で不安なので)
namespace // 無名名前空間(他ファイルから参照できない変数や関数を宣言) https://marycore.jp/prog/cpp/unnamed-namespace/
{
    //template <class classT>
    //void* Constructor(World* world, Vector3 position, Quaternion rotation)
    //{    // ★classTをZako1とすると new classT(.. は初期化コンストラクタへの住所ポインタ(void*)型を返す
    //    return (void*) new classT(world, position, rotation); // newの生成コンストラクタへのポインタを辞書に入れる場合
    //}

    template <class classT>
    std::shared_ptr<Object> Constructor(std::shared_ptr<InitParams> initParams, World* world, Vector3 position, Quaternion rotation)
    {   
        return Object::Create<classT>(initParams, world, position, rotation);
        //return std::make_shared<classT>(world, position, rotation); // make_sharedの生成コンストラクタへのポインタを辞書に入れる場合
    }

    //[関数へのポインタの応用:コンストラクタも関数の一種なので] https://blog.cryolite.net/entry/01000226/p1
    //typedef void* (*constructorT)(World*, Vector3, Quaternion);

    typedef std::shared_ptr<Object>(*constructorT)(std::shared_ptr<InitParams>, World*, Vector3, Quaternion);
    typedef std::unordered_map<std::string, constructorT> ConstructorDictionary;//クラス名⇒初期化コンストラクタへの辞書

    //★<クラス名→コンストラクタ>を呼び出す辞書★★★★★★★★★★
    ConstructorDictionary g_Constructors; // クラス名⇒コンストラクタへの辞書配列
    //★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

    // クラス辞書に任意のclassTの初期化コンストラクタを登録する
    template <class classT>
    void RegisterConstructor(std::string const& className)
    {    // insertでクラス辞書配列にクラス名とその初期化コンストラクタのアドレスのペアを登録する
        g_Constructors.insert(std::make_pair(className, &Constructor<classT>));
    }

    std::shared_ptr<Object> Construct(std::string const& className, std::shared_ptr<InitParams> initParams, World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 })
    {    // classNameでクラス辞書をfind検索する
        ConstructorDictionary::iterator ite = g_Constructors.find(className);
        if (ite == g_Constructors.end()) // .end()なら辞書の最後なので見つからなかったってこと
        {
            assert("登録されていないクラスを初期化しようとしました!ちゃんと Factory.cpp で REGISTER_CONSTRUCTORしてください" == "");
            return 0;
        }
        //辞書のsecondには初期化コンストラクタが入っているのでそれを返す⇒returnと同時に初期化コンストラクタが駆動する
        return ite->second(initParams, world, position, rotation); // _VSTD::forward<_Args>(__args)...));
    }

}


// ★工場ファクトリーパターン(プレイヤーや敵、アイテムなどのメモリ上の実体の初期化コンストラクトやmake_sharedを作成を一手に任せる)
// 【効果】新規生成処理の経路を共通の窓口にすることで色んなとこに散らばった生成処理の経路を一本にまとめる
class Factory
{
public:
    //friend Object; // フレンド宣言でObjectクラスのprivateな関数にもアクセスできるようにする

    // 初回だけ Zako0 などのクラスのコンストラクタを辞書に登録を1回だけする
    // class_name に対応するコンストラクタが辞書登録済かもチェック
    static void Init(const std::string& class_name);

    // class_nameに応じて敵などのオブジェクトを生成してreturnで返す
    template <typename TypeT> //[templateを.hと.cppに分けて書くのが難しい] https://qiita.com/i153/items/38f9688a9c80b2cb7da7
    static std::shared_ptr<TypeT> ConstructByClassName(const std::string& class_name, std::shared_ptr<InitParams> initParams, World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 });

    //IDに応じて作り分ける役目を工場でMapなどの代わりに請け負う
    template<typename TypeT>
    static std::shared_ptr<TypeT> MakeSharedObject(const std::string& class_name, std::shared_ptr<InitParams> initParams, World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 })
    {
        // class_nameの名前のコンストラクタがあるかチェックし、初回だけ Zako0 などのクラスのコンストラクタを辞書に登録を1回だけする
        Init(class_name);

        // class_name に応じて敵などのオブジェクトを生成してreturnで返す
        auto pObject = ConstructByClassName<TypeT>(class_name, initParams, world, position, rotation);

        return pObject; // 共有ポインタに変換して回し読み状態で返す
    }


};

#endif

Factory.hをみればわかるようにZako0.hやItem.hなどの実際のインクルードはなく代わりにclass Zako0; など前方宣言にしてあります。
そして、MakeSharedObject関数を窓口にしてConstructByClassNameclass_nameからZako0などをTypeTとして初期生成する処理の窓口が用意されてます。
実際のZako0などの初期化には#include が必須なので ConstructByClassName関数は.hでは定義だけして.cppに初期生成の処理を逃がせるようにしています。

Factory.cppを新規作成して、Zako0やItemなどの「文字列 → Zako0などのコンストラクタへの辞書」でZako0などの実体を生成できるようにしましょう。

#include "Factory.h"

#include <unordered_map> //★ #文字列 マクロで<クラス名→コンストラクタ>をビルドする瞬間に 辞書に登録

// new や std::make_shared1 や Createで初期生成するには
// #includeしてコンストラクタを呼び出せる必要が出てくる
#include "Enemy.h"
#include "Zako0.h"
//#include "Zako1.h"
//#include "Zako2.h"
//#include "Zako3.h"
//#include "Boss.h"

//#include "Item.h"
//#include "ItemBlock.h"
//#include "ItemBullet.h"
//#include "ItemMoney.h"

//#include "Bullet.h"

#include "World.h"

//【上級1】★以下サイトを参考に【文字列"Zako1"とかからクラスを初期化するテクニック】
// https://stackoverflow.com/questions/5447675/how-to-convert-a-string-to-a-class-name/5447776
//
// 以下のようにクラスの型タイプを書かずに初期化できるので★if else文をいちいちコピーして増やさずに済む
// 例. いちいち書く例
// if (gid == 0)     return Factory::make_shared<Zako0>(x + 32, y + 32);
// else if(gid == 1) return Factory::make_shared<Zako1>(x + 32, y + 32);
//  .......(敵の種類が増えるたびにいちいちif elseコピペで書かないと..)
// ↓
// ↓★"Zako1"の文字列で初期化できれば1行で書けるようになる余地が出てくる!(CSVのクラス名の文字から初期化も目指せる)
// ↓
// 例. 初期化に<Zako1>を使わずに済むようになる "Zako1"で書ける
// auto enemy = g_ClassFactory.Construct("Zako1", x + 32, y + 32);//←"Zako1"で初期化 <Zako1>だとコードに書くしかない


// クラス名 classTypeName 例. Zako0 のコンストラクタを s_umConstructors 辞書に登録するための↓#文字取得マクロ
#define REGISTER_CONSTRUCTOR(classTypeName) RegisterConstructor<classTypeName>( #classTypeName )

// Factoryクラスの「依存性の逆転」を引き起こすには.hでは★#includeしたくない https://blog.ecbeing.tech/entry/2021/01/20/114000
// だが、テンプレート関数は普通.hに実装までする必要があるが、それを回避する方法もある https://qiita.com/i153/items/38f9688a9c80b2cb7da7
// .cppでtemplate関数の実体を.hと分けて定義するには「★最小限下記の定義をオブジェクトごとにする必要」がある
template std::shared_ptr<Object> Factory::ConstructByClassName<Object>(const std::string&, std::shared_ptr<InitParams>, World*, Vector3, Quaternion);
template std::shared_ptr<Enemy> Factory::ConstructByClassName<Enemy>(const std::string&, std::shared_ptr<InitParams>, World*, Vector3, Quaternion);


namespace // 無名名前空間(他ファイルから参照できない変数や関数を宣言)
{
    bool isConstructorRegistered{ false }; // 初回だけクラスのコンストラクタ登録を1回だけするためのフラグ

    // 初回だけ Zako0 などのクラスのコンストラクタを辞書に登録を1回だけする
    inline void InitConstructors()
    {
        if (!isConstructorRegistered) // 初回だけクラスのコンストラクタ登録を1回だけする
        {    //クラス登録されていないなら初回登録して "文字列"→コンストラクタ を呼び出せるようにしておく
            REGISTER_CONSTRUCTOR(Zako0); // Zako0のコンストラクタを辞書に登録しておく
            //REGISTER_CONSTRUCTOR(Zako1);
            //REGISTER_CONSTRUCTOR(Zako2);
            //REGISTER_CONSTRUCTOR(Zako3);
            //REGISTER_CONSTRUCTOR(Boss);
            //REGISTER_CONSTRUCTOR(ItemBullet);
            //REGISTER_CONSTRUCTOR(ItemBlock);
            //REGISTER_CONSTRUCTOR(ItemMoney);

            //REGISTER_CONSTRUCTOR(Bullet);

            isConstructorRegistered = true;
        }
    }
}

void Factory::Init(const std::string& class_name)//int id)
{
    // 初回だけ Zako0 などのクラスのコンストラクタを辞書に登録を1回だけする
    InitConstructors();

    // 辞書に class_name が登録されているかチェック
    assert(g_Constructors.count(class_name) != 0 && "まだ登録してない クラス名 が Factory.cpp にありました。");
}

// class_name に応じて敵などのオブジェクトを生成してreturnで返す
template <typename TypeT> //[templateを.hと.cppに分けて書くのが難しい] https://qiita.com/i153/items/38f9688a9c80b2cb7da7
std::shared_ptr<TypeT> Factory::ConstructByClassName(const std::string& class_name, std::shared_ptr<InitParams> initParams, World* world, Vector3 position, Quaternion rotation)
{
    // class_nameに応じて敵などのオブジェクトを生成してreturnで返す
    // ★Zako1*でもBoss*でもOK、ベース基底のObject型であいまいな状態で受け取る
    auto pObject = Construct(class_name, initParams, world, position, rotation);
    return std::dynamic_pointer_cast<TypeT>(pObject); // タイプをキャストでTypeTで指定した型に変換して返す
}


GameObject.hを変更して、OnDamageなどダメージ系の処理のベースを準備しましょう。

#ifndef GAMEOBJECT_H_
#define GAMEOBJECT_H_

#include <memory> // 共有ポインタの定義のため

#include "Vector3.h"
#include "Quaternion.h"
#include "Tile.h" // OnHitTileのコールバックに必要

#include "Object.h" // インスタンスIDや検索タグを持ち、shared_from_this_as<継承先クラス名>などができる
#include "World.h"

class Map; // 前方宣言
class Component; // 前方宣言
class Collision; // 前方宣言
class CollisionParams; // 前方宣言

// ゲーム上に表示される物体の基底クラス。
// プレイヤーや敵、アイテムなどはこのクラスを継承して作る。
class GameObject : public Object
{
protected:
    // コンストラクタはprotected:にしてあるのでCreate関数経由で初期生成してください
    GameObject(World* world = nullptr, Vector3 position = { 0,0,0 }, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : Object(), m_world{ world }, position{ position }, rotation{ rotation }, velocity{ velocity }, force{ force }
    {

    }

public:
    // 仮想デストラクタ
    virtual ~GameObject() {}


    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    }; // unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    Quaternion rotation; // 回転クオータニオン

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

    // 重力を返す、重力を変えたいときはoverrideして処理を変えてください、重力を変えてない場合は 0.0f
    virtual inline float gravity() { return 0.0f; }

protected:
    World* m_world; // GameObjectが配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }
    // GameObjectが現在いるマップへのポインタを返す
    Map* map() { return m_world->map.get(); }
   
    bool isDead{ false }; // 死んだ(削除対象)フラグ
    bool isStatic{ false }; // 動かない(静的オブジェクトか?)
    bool isGround{ false }; // 地面についているか
    bool isJumping{ false }; // ジャンプ中か
    bool isFlying{ false }; // 空をとんでいるか

    float pushPower{ 5.0f }; // コライダで接触したときに押し返す力(相手側が強いと押される)
    float mass{ 1.0f }; // 質量(重さ)コライダで接触したときに重さでforceを割り算すると動きにくくなる
    std::shared_ptr<CollisionParams> collisionParams; // 衝突関係の物理パラメータ

    hash32 typeTag{ "" }; // 小カテゴリ Zako0など個別のタイプ
    hash32 baseTag{ "" }; // 大カテゴリ Enemyなど大まかなベースジャンル

protected:
    // コンポーネントの<ハッシュID, 共有所有ポインタ> の辞書でコンポーネントの共有カウンタ+1を保持する
    std::unordered_map<size_t, std::shared_ptr<Component>> components;
public:
    // コンポーネントを得る 例えば GetComponent<BoxCollider>でBoxColliderという名前のコンポーネントをcomponents辞書から得る
    template<class ComponentT>
    std::vector<std::weak_ptr<ComponentT>> GetComponents()
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        std::vector<std::weak_ptr<ComponentT>> returnList; // "ComponentT" 例."CircleCollider"など をキーとするコンポーネント一覧
        for (auto ite = range.first; ite != range.second; ++ite)
            returnList.emplace_back(ite->second); // 辞書で見つかったコンポーネントを返すリストに追加
        return returnList;
    }

    // コンポーネントを追加する .AddComponent<BoxCollider>(...)で追加できる
    template<class ComponentT, typename ...ArgsT>
    std::weak_ptr<ComponentT> AddComponent(ArgsT&&...args)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]

        // shared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        std::shared_ptr<ComponentT> newComponent = std::make_shared<ComponentT>(shared_from_this_as<GameObject>(), std::forward<ArgsT>(args)...);
       
        // https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        // std::unrodered_multimap<> と同型の値を取る std::pair<> を作って insert 関数でcomponents辞書に追加
        components.insert(std::pair<size_t, std::shared_ptr<ComponentT>>(hashID, newComponent));
        return newComponent;
    }

    // コンポーネントを削除する
    template<typename ComponentT>
    bool RemoveComponent(std::shared_ptr<ComponentT> removeComponent)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/

        //[イテレート中に探しながら消す] https://cpprefjp.github.io/reference/unordered_map/unordered_multimap/erase.html
        for (auto ite = range.first; ite != range.second;)
            if (ite->second == removeComponent)
            {
                components.erase(ite); // 削除された要素の次を指すイテレータが返される
                return true; // 見つかって消せた場合は true
            }
            else
                ++ite; // 次の要素へイテレータを進める

        return false; // 見つからずに消せなかった場合は false
    }

    // 更新処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Update() = 0; // 純粋仮想関数 = 0 にすると【絶対 Update()関数はoverrideしなきゃいけない義務を付与できる 】

    // 描画処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Draw() = 0; // 純粋仮想関数 = 0 にすると【絶対 Draw()関数はoverrideしなきゃいけない義務を継承先クラスに付与 】

    // 衝突開始したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollisionEnter(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突開始したときの処理などをオブジェクトの種類ごとに実装する
    };

    // 衝突したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollision(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    };

    // 衝突終了したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollisionExit(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突終了したときの処理などをオブジェクトの種類ごとに実装する
    };

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump()
    {
        // overrideしてジャンプした瞬間に音を鳴らしたりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中でジャンプの上昇中に呼ばれる関数
    virtual void OnJumping()
    {
        // overrideして空中でジャンプの上昇中にエフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中で落ちている最中に呼ばれる関数
    virtual void OnFalling()
    {
        // overrideして空中で落ちている最中に音を鳴らしたり、エフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // タイルに当たったときのコールバック
    virtual void OnHitTile(HitPart hitPart, HitBit hitPartBit, TileBit checkBit, float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 継承 overrideしてタイルに当たったときの処理を実装する
    }

    // 継承 overrideしてGameObjectのもつ個別のダメージの値を返す( otherの種類などでダメージ計算式をカスタムできる )
    virtual float Damage(std::shared_ptr<GameObject> other) { return 0.0f; }
    // 継承 overrideして個別のダメージの値を返す
    virtual float Damage() { return 0.0f; }
   
    virtual void OnDamage(std::shared_ptr<GameObject> other)
    {
        // overrideしてダメージを受けた時の処理を個別に実装
    }


};

#endif



Enemy.hを新規作成して、Zako0などの敵を表現するベース基底クラスを準備しましょう。

#ifndef ENEMY_H_
#define ENEMY_H_

#include "GameObject.h"
#include "Collider.h"
#include "CollisionParams.h"

class World; //前方宣言

// アイテムの情報のベース基底クラス 継承override してアイテム情報をjsonなどから受け取る
struct EnemyInfo : public InitParams
{
    float life{ 1.0f }; // HP lifeが0になったらisDeadになる
    float damage{ 0.0f }; // プレイヤが当たったときに受けるダメージ
    float speedMax{ 3.0f }; // 敵の移動スピードのMAX値
    float colliderRadius{ 8.0f }; // アイテムとしてのコライダの半径
    float bulletRadius{ 16.0f }; // 敵が弾を撃つ際の弾としてのコライダの半径
    int bulletCount{ -1 }; // 弾を撃てる数のカウント値( -1のときは無限に撃てるタイプの敵 )
    float bulletDamage{ 1 }; // 弾の与えるダメージ
    float bulletSpeed{ 10.0f }; // 弾のスピード
    std::shared_ptr<CollisionParams> collisionParams; // 衝突関係の物理パラメータ
    std::weak_ptr<GameObject> shooter; // 弾を撃った後に弾の撃ち手の弱共有ポインタのリンクをBulletに渡す
    float shooterAngleY{ 0.0f }; // 撃ち手の撃った際の角度
    int imageIndex{ -1 }; // 敵の画像が分割画像だったらどの位置のIDの画像か
    int bulletImageIndex{ -1 }; // 弾の画像が分割画像だったらどの位置のIDの画像か
    std::string imagePath{ "" }; // 敵の画像のパス
    std::string bulletImagePath{ "" }; // 敵の撃つ弾の画像のパス
    inline EnemyInfo(std::shared_ptr<InitParams> initParams = nullptr) : InitParams{}
    {
        auto tmxParams = std::static_pointer_cast<InitTMXProperty>(initParams);
        if (tmxParams->properties == nullptr) return;
        auto tmxProperties = tmxParams->properties;
        // tmxのjsonの追加プロパティからデータを読み取る
        if (tmxProperties->count("pushPower") > 0 || tmxProperties->count("collision_e") > 0)
            collisionParams = std::make_shared<CollisionParams>(initParams); // 物理パラメータを読み取り

        auto itr = tmxProperties->find("life");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の imagePath のタイプが Floatでない!jsonのプロパティを見直してください");
            life = itr->second.floatValue;
        }

        itr = tmxProperties->find("damage");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の damage のタイプが Floatでない!jsonのプロパティを見直してください");
            damage = itr->second.floatValue;
        }

        itr = tmxProperties->find("imageIndex");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の imageIndex のタイプが Intでない!jsonのプロパティを見直してください");
            imageIndex = itr->second.intValue;
        }

        itr = tmxProperties->find("imagePath");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の imagePath のタイプが Stringでない!jsonのプロパティを見直してください");
            imagePath = itr->second.stringValue;
        }

        itr = tmxProperties->find("bulletImageIndex");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の bulletImageIndex のタイプが Intでない!jsonのプロパティを見直してください");
            bulletImageIndex = itr->second.intValue;
        }

        itr = tmxProperties->find("bulletImagePath");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の bulletImagePath のタイプが Stringでない!jsonのプロパティを見直してください");
            bulletImagePath = itr->second.stringValue;
        }


        itr = tmxProperties->find("colliderRadius");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の colliderRadius のタイプが Floatでない!jsonのプロパティを見直してください");
            colliderRadius = itr->second.floatValue;
        }

        itr = tmxProperties->find("bulletRadius");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の bulletRadius のタイプが Floatでない!jsonのプロパティを見直してください");
            bulletRadius = itr->second.floatValue;
        }

        itr = tmxProperties->find("bulletSpeed");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の bulletSpeed のタイプが Floatでない!jsonのプロパティを見直してください");
            bulletSpeed = itr->second.floatValue;
        }

        itr = tmxProperties->find("bulletCount");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の bulletCount のタイプが Intでない!jsonのプロパティを見直してください");
            bulletCount = itr->second.intValue;
        }

        itr = tmxProperties->find("bulletDamage");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の bulletDamage のタイプが Floatでない!jsonのプロパティを見直してください");
            bulletDamage = itr->second.floatValue;
        }

    }
    virtual ~EnemyInfo() {}
};


// 敵の基底クラス。
// 全ての敵は、このクラスを継承して作る。
// ★【勉強】abstract型はC++にない!!【純粋仮想関数】=0を使って作る
class Enemy : public GameObject
{
public:
    // コンストラクタ
    Enemy(World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 })
        //    Enemy(const Object::ID& stopper, World* world, Vector3 position)
        : GameObject(world, position, rotation)
    {
        this->baseTag = "Enemy";
    }
public:
    float speedMax{ 1.0f };
    float life{ 1.0f }; // 耐久力
    float m_Damage{ 0 }; // 敵に当たった時に与えるダメージ
    float Damage() override { return m_Damage; }
    std::shared_ptr<Resource> m_pImage; // 画像や3Dモデルなどのパス
    std::shared_ptr<EnemyInfo> m_enemyInfo; // 敵のjsonから読み取った情報

    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        if (initParams == nullptr) return;
        m_enemyInfo = std::make_shared<EnemyInfo>(initParams); // アイテム情報を受け取る
        collisionParams = m_enemyInfo->collisionParams; // 物理衝突パラメータを受け取る
        //if (Resource::TypeByExt(params->imagePath) == Resource::Type::Texture)
        if (m_enemyInfo->imagePath != "")
            m_pImage = Resource::MakeShared<Texture>(m_enemyInfo->imagePath);
        m_Damage = m_enemyInfo->damage;
        life = m_enemyInfo->life;
        speedMax = m_enemyInfo->speedMax;
    }

    virtual ~Enemy() {}

    // 更新処理。派生クラスでオーバーライドして使う
    virtual void Update() = 0;//virtual ~ = 0;で【純粋仮想関数に】これで【C#のabstract型に】

    // 描画処理。派生クラスでオーバーライドして使う
    virtual void Draw() = 0; //virtual ~ = 0;で【純粋仮想関数に】【関数 = 0で未定義状態を表現】


    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override
    {
        if (isDead) return;
        auto otherCollider = pCollision->contacts[0].otherCollider.lock();
        if (otherCollider == nullptr) return;
        auto other = otherCollider->owner();

        if (other->isDead) return; // other側がisDeadだったら処理をスルー


        if (other->typeTag == "PlayerBullet" || other->baseTag == "Enemy" || other->baseTag == "Player")
        {   // 相手方がPlayerBulletの場合は相手側の物理パラメータを使用する
            auto colObj = (other->typeTag == "PlayerBullet") ? other : shared_from_this_as<GameObject>();
            float objPushPower = (colObj->collisionParams != nullptr) ? colObj->collisionParams->pushPower : other->pushPower;
            if (colObj->collisionParams != nullptr && colObj->collisionParams->isDEMcollision)
            {   //[離散要素法] https://qiita.com/konbraphat51/items/157e5803c514c60264d2
                float k = colObj->collisionParams->k; // バネ係数(めり込みに対するバネ反発の掛け率)
                float e = colObj->collisionParams->e; // 跳ね返り係数(直前と比べどれくらいの速さで離れるか 0)
                float log_e = colObj->collisionParams->log_e();
                float eta = colObj->collisionParams->eta(mass); // 粘性係数 の mass は自分側を使う?
                auto norm = pCollision->contacts[0].normal;// *other->pushPower; // pushPowerの力でotherに押し返される
                auto v_norm = dot(other->velocity - velocity, norm) * norm; // 法線norm方向のv = (v1 - v0)・norm [ドット積]
                auto sep = pCollision->contacts[0].separation; // 衝突点どうしのはなれ具合
                auto F = -k * sep * norm - eta * v_norm;
                force += F / mass; // 衝突法線方向の力を加える
            }
            else
            {
                auto norm = pCollision->contacts[0].normal * objPushPower;
                force += -norm;
            }
        }
    }

    // 継承overrideしてダメージ計算式で値を返す
    virtual float Damage(std::shared_ptr<GameObject> other) override
    {
        return other->Damage(); // 弾の側のダメージ値をそのまま採用(防御力とかで式をカスタムもできる)
    }

    virtual void OnDamage(std::shared_ptr<GameObject> other)
    {
        if (other->baseTag == "Bullet")
        {
            life -= Damage(other); // ライフを減らす  
        }

        // ライフが無くなったら、死亡
        if (life <= 0)
        {
            isDead = true;
        }
    }
};

#endif


Zako0.hを新規作成して、Enemyを継承overrideして敵の実体を定義しましょう。

#ifndef ZAKO_0_H_
#define ZAKO_0_H_

#include "Enemy.h"

#include "MyRandom.h"
#include "Resource.h"

#include "MapHitBoxComponent.h"

#include "Collider.h"
#include "GridCollidersComponent.h"
#include "GridSteps.h"

#include "PhysicsComponent.h" // 重力落下などの物理処理コンポーネントを使う

class World; // 前方宣言

// ザコ0クラス
class Zako0 : public Enemy
{
public:
    std::shared_ptr<Texture> Image()
    {
        return std::static_pointer_cast<Texture>(m_pImage);
    }
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // 動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

protected:
    std::weak_ptr<GravityComponent> gravityPhysics; // 落下の重力の物理処理コンポーネント
    // マップ地形に対するヒットボックスの当たり判定と押し戻しをするコンポーネント
    std::weak_ptr<MapHitBoxComponent> mapHitBox;
   
    std::weak_ptr<GridCollidersComponent> gridCollision;
    std::weak_ptr<SphereCollider> sphereCollider;
public:
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        Enemy::Init(initParams); // 基底ベースで共通パラメータを読み取り
        if (m_enemyInfo->imagePath == "" && m_pImage == nullptr)
            m_pImage = Resource::MakeShared<Texture>("Image/zako0.png");
       
        std::vector<float> vyGravity{ 0.8f,0.8f,0.8f,0.8f }; // 重力の設定値
        std::vector<float> vyDownSpeedMax{ 16, 16, 16, 8 }; // 降下スピードのリミット
        // AddComponent内部でshared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        gravityPhysics = AddComponent<GravityComponent>(vyGravity, vyDownSpeedMax);

        // 半径16(直径32)の球体コライダ
        sphereCollider = AddComponent<SphereCollider>(Vector3(0, 0, 0), 16);
        gridCollision = AddComponent<GridCollidersComponent>("Enemy");
        gridCollision.lock()->LinkCollider(sphereCollider); // コライダをリンクする

        float mapHitBoxSize = sphereCollider.lock()->m_radius * 2.0f; // 球体コライダの直径に合わす
        Vector3 boxSize{ mapHitBoxSize,mapHitBoxSize,mapHitBoxSize };
        Vector3 offsetLeftBottomBack{ 3, 0, 3 }; // ボックスの端を内側にどれだけ削るか(キャラの画像に合わせて小さくできる)
        Vector3 offsetRightTopForward{ 3, 4, 3 }; // ボックスの端を内側にどれだけ削るか(頭を4削る)
        mapHitBox = AddComponent<MapHitBoxComponent>(boxSize, offsetLeftBottomBack, offsetRightTopForward);
    }
    // overrideして物理落下コンポーネントからの重力値を返す
    virtual float gravity() override { auto pGravityPhysics = gravityPhysics.lock(); return (pGravityPhysics != nullptr) ? pGravityPhysics->gravity() : 0.0f; }
   
    // コンストラクタ
    Zako0(World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 }) : Enemy(world, position, rotation)
    {
        this->typeTag = "Zako0";
        this->pushPower = 5.0f;
    }

    // 更新処理
    void Update() override
    {
        vxForce *= 0.9f; // かかっている力の減衰率
        vyForce *= 0.9f; // かかっている力の減衰率
        vzForce *= 0.9f; // かかっている力の減衰率
        deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
        //MoveSpeed *= 0.9F; // 移動速度も減速する

       
        MoveSpeed += MyRandom::Range(-0.07f, 0.1f); //アクセルを踏む
        if (MoveSpeed > speedMax) MoveSpeed = speedMax;//Maxスピードで止める
        if (MoveSpeed < -0.7f) MoveSpeed = -0.7f;//ちょっとだけバックできるように

       
        // ランダムAI動作処理
        deltaAngle += MyRandom::PlusMinus(0.3f); // プレイヤがみつからないときはランダム(同じフロアにいないとき)
        moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
       

        if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
        else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

        auto pGravityPhysics = gravityPhysics.lock();
        if (pGravityPhysics != nullptr)
            pGravityPhysics->Update(); // 重力落下のコンポーネントを更新する


        // ↓進行方向角度をX方向とZ方向の速度に変える
        vx = (float)std::cos(moveAngle) * MoveSpeed;
        vz = (float)std::sin(moveAngle) * MoveSpeed;
       
        float vyForceGravity = (pGravityPhysics != nullptr) ? pGravityPhysics->vyForce() : 0.0f;
       
        vx += vxForce;
        vy += vyForce + vyForceGravity; // 勢い(力・フォースを加算
        vz += vzForce;

        vy += vyForce; // 勢い(力・フォースを加算

        auto pMapHitBox = mapHitBox.lock();
        float cellHalf = (map() != nullptr) ? (float)map()->CellSize() / 2.0f : 16.0f; // マップのセルのサイズの半分
        // 速すぎて当たり判定がすり抜けないように細切れのステップのグリッドで移動させつつ当たり判定する
        Vector3 gridStepSize = (pMapHitBox == nullptr) ? Vector3{ cellHalf, cellHalf, cellHalf } : pMapHitBox->m_BoxSize / 2;
        GridSteps vSteps{ velocity, gridStepSize }; // 細切れのステップの数値を計算

        bool isCollisionX{ false }, isCollisionY{ false }, isCollisionZ{ false }, isCollisionCollider{ false };
        for (int i = 1, iSize = vSteps.div + 1; i <= iSize; ++i)
        {
            auto& vStep = (i < iSize) ? vSteps.step : vSteps.mod;
            // まず横に移動する
            if (pMapHitBox == nullptr)
                x += vx;
            else if (isCollisionX == false) // まだ前のステップでX方向の衝突がないなら
            {
                x += vStep.x;
                isCollisionX = pMapHitBox->hitCheckX((TileBit)TileType::Wall);
            }

            // 次に縦に動かす
            if (pMapHitBox == nullptr)
                y += vy;
            else if (isCollisionY == false) // まだ前のステップでY方向の衝突がないなら
            {
                y += vStep.y;
                isCollisionY = pMapHitBox->hitCheckY((TileBit)TileType::Wall | (TileBit)TileType::Floor);
            }

            // 次にZ奥行き方向に動かす
            if (pMapHitBox == nullptr)
                z += vz;
            else if (isCollisionZ == false) // まだ前のステップでZ方向の衝突がないなら
            {
                z += vStep.z;
                isCollisionZ = pMapHitBox->hitCheckZ((TileBit)TileType::Wall);
            }


            if (auto pGridCollision = gridCollision.lock())
            {
                pGridCollision->Update(); // 所属するマス目セルを更新する
                for (auto& collider : *pGridCollision->colliders())
                    isCollisionCollider |= collider.lock()->IsCollision();
            }

            // 衝突があったら、速すぎてすり抜けちゃう前に今のステップでbreakで抜ける
            if (isCollisionCollider)
                break; // ぶつかっているのにさらに次のステップの速度も足すとすり抜けちゃう
        }

    }

    // 描画処理
    void Draw() override
    {
        if (world() == nullptr) return;

        auto image = Image();
        int animIndex = animCount / 20;
        if (image->m_handles.size() > 8)
        {
            float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
            float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
            if (abs_vx > 0.01f || abs_vz > 0.01f)
                animCount += 6;
            if (animCount >= 60) animCount = 0; // アニメ時間を0へループ
        }

        // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
        for (auto&& keyValue : world()->cameras) // GameObjectのいるワールドにあるすべてのカメラぶんループ
        {
            auto& camera = keyValue.second;
            camera->Draw([&]()
                {
                    // 常にカメラ方向を向くビルボード回転の角度を得る
                    VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                    angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                    int imgIndex = 0; // キャラチップ画像の起点番号
                    if (image->m_handles.size() > 8)
                    {   // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                        // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                        float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                        imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // 奥向きの画僧
                            : (difAngleY < -45) ? 6 // 右 向きの画像
                            : (difAngleY > 45) ? 3  // 左 向きの画像
                            : 9; // 前向きの画像
                    }
                    if (0 <= imgIndex && imgIndex < image->m_handles.size())
                    {
                        int handleWidth, handleHeight; // 描画するサイズを得る
                        DxLib::GetGraphSize(image->m_handles[imgIndex], &handleWidth, &handleHeight);
                        float ExRate = 32.0f / handleWidth;
                        MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, ExRate, angle, *image, imgIndex + animIndex, TRUE);
                    }
                    else
                        imgIndex = 0;
                   
#ifdef _DEBUG
                    if (auto pSphereCollider = sphereCollider.lock()) pSphereCollider->Draw(); // コライダを描く
                    if (auto pMapHitBox = mapHitBox.lock()) pMapHitBox->Draw(); // ヒットボックスを描く
#endif
                });
        }
    }
};


#endif


GameScene.cppを変更して、敵を更新する処理と描く処理を実装しましょう。

#include "GameScene.h"

#include "Player.h"
#include "Enemy.h"
#include "Map.h"

GameSceneSetting GameScene::settings{ "Map/GameSceneSetting.csv" }; // ゲームシーンの設定情報

// 指定したゲームシーンのタグに関連づいたファイル名に対応したワールドを初期化する
bool GameScene::LoadWorlds(const std::string& gameSceneTag)
{
    if (settings.info.count(gameSceneTag) == 0)
        return false;

    auto& info = settings[gameSceneTag];
    for (auto&& tag_mapPath : info)
    {
        CreateWorld(tag_mapPath.second.tag); // ワールドをタグをつけて生成
        // ワールドのマップをロード
        worlds[tag_mapPath.second.tag]->LoadMap(tag_mapPath.second.mapFilePath);
    }
    return true;
}

void GameScene::ChangeWorld(const std::string& worldTag, std::shared_ptr<Player> pPlayer)
{
    if (pPlayer == nullptr) return;
    if (worlds.count(worldTag) == 0) return; // 存在しないタグだった

    pPlayer->world(worlds[worldTag].get()); // ワールドへのリンクをすげ替える
}


void GameScene::Initialize()
{// Init処理

    // Zバッファを有効にする [Zの深度]
    //[参考]https://docs.google.com/presentation/d/1Z23t1yAS7uzPDVakgW_M02p20qt9DeTs4ku4IiMDLco/edit?usp=sharing
    //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
    DxLib::SetUseZBuffer3D(TRUE);
    // Zバッファへの書き込みを有効にする
    DxLib::SetWriteZBuffer3D(TRUE);
    // 光の明暗計算を無効に
    DxLib::SetUseLighting(FALSE);

    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);


    Resource::MakeShared<Texture>(playerImgPath1, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath2, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする

    Resource::MakeShared<Texture>("Image/zako0.png")->Load(); // 敵画像を読込
    Resource::MakeShared<Texture>("Image/skullman.png", 3, 4, 32, 32)->LoadDivGraph(); // 敵画像を読込



    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    // プレイヤの生成
    if (pWorld != nullptr)
    {
        //pWorld->AddCamera("カメラ1", Camera::defaultCamera); // カメラ1というタグをデフォルトカメラにつけてワールドにデフォルトカメラを配置
        pWorld->AddCamera("カメラ1", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera1 = pWorld->camera("カメラ1"); // カメラ1 というタグのついたカメラへの共有ポインタを得る
        pCamera1->SetScreenHandle(screenHandle0); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer1 = Object::Create<Player>(nullptr, pWorld, Pad::Key, Vector3(90 + 256, 32, 95 + 256));
        pWorld->AddPlayer("プレイヤ1", pPlayer1); // シーンにプレイヤを追加する
        pPlayer1->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);

        pPlayer1->camera = pCamera1.get(); // カメラをプレイヤにリンクする
        pPlayer1->mass = 100.0f; // 体重を100にして衝突されてもびくともしないようにする


        pWorld->AddCamera("カメラ2", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera2 = pWorld->camera("カメラ2"); // プレイヤ2 というタグのついたカメラへの共有ポインタを得る
        pCamera2->SetScreenHandle(screenHandle1); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer2 = Object::Create<Player>(nullptr, pWorld, Pad::Two, Vector3(190, 32, 195));
        pWorld->AddPlayer("プレイヤ2", pPlayer2); // シーンにプレイヤを追加する
        pPlayer2->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);
       
        pPlayer2->camera = pCamera2.get(); // カメラをプレイヤにリンクする
        pPlayer2->mass = 1.0f; // 体重を1にして普通にふっとぶようにする

    }

};


void GameScene::Update()
{// 更新処理

    Screen::ClearDrawScreen(screenHandle0); // 一旦キャンバスをきれいにまっさらに
    Screen::ClearDrawScreen(screenHandle1); // 一旦キャンバスをきれいにまっさらに

    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    for (const auto& player : players)
        player.second->Update(); // プレイヤの更新【忘れるとプレイヤが動かない】

    // 敵の更新処理
    for (const auto& world : worlds)
        for (const auto& e : world.second->enemies)
            e->Update();


    // グリッドを更新して隣接するグリッドに含まれる敵や弾の当たり判定を行う
    for (const auto& world : worlds)
        if (world.second->grid() != nullptr)
            for (const auto& cell : world.second->grid()->cellData)
            {
                auto pGridObject = cell.second;
                while (pGridObject != nullptr)
                {   // while文で数珠つなぎのm_pNextを次々とたどって最後にたどり着くと次がなくてnullptrになる
                    pGridObject->checkCell();
                    pGridObject = pGridObject->m_pNext; // リンクリスト構造を次々とたどる
                }
            }

}

void GameScene::Draw()
{// 描画処理
    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    std::unordered_set<World*> drawEndSet; // すでに描いたWorldをポインタのsetでかぶらないように判定する

    for (const auto& player : players)
    {
        player.second->Draw(); // プレイヤの描画【忘れるとプレイヤ表示されない】

        auto world = player.second->world(); // プレイヤのいるワールド

        if (drawEndSet.count(world) > 0) continue; // すでに描いたWorldは描かずスキップする
        drawEndSet.emplace(world); // 描いたWorldのポインタ住所をsetに登録する

        world->map->DrawTerrain(); // マップの描画

        // 敵の描画
        for (const auto& enemy : world->enemies)
        {
            enemy->Draw();
#ifdef _DEBUG
            //enemy->DrawHitBox(); // 当たり判定の描画
#endif
        }


#ifdef _DEBUG  // デバッグのときだけ_DEBUGが定義され描画される
        if (world->grid() != nullptr)
            for (const auto& cell : world->grid()->cellData)
                cell.second->Draw(); // グリッドの描画
#endif
    }

    // スクリーン0の内容を画面に描く
    DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
    DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));

    // スクリーン1の内容を画面に描く
    DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
    DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));

}




Map.hを変更して、Factoryから敵を生成する処理を追加します。

#ifndef MAP_H_
#define MAP_H_

#include <vector> // C#におけるListリストの機能
#include <unordered_map>    // 辞書型連想配列に必要
#include <memory> // メモリを扱う【共有ポインタに必要】
#include <limits> //intやfloatなどの無限大∞に使う  https://cpprefjp.github.io/reference/limits/numeric_limits/max.html

#include <fstream> // ファイル読み出しifstreamに必要
#include <string> //文字列に必要
#include <sstream> // 文字列ストリームに必要

#include <assert.h> // マップ読込み失敗表示用

#include "Screen.h"
#include "MyMath.h"
#include "MyHash.h"
#include "MyDraw.h"
#include "DataCsv.h"
#include "TMXJson.h"

#include "Singleton.h"
#include "GameObject.h"
#include "World.h"
#include "Camera.h"
#include "Factory.h"

#include "MapRange.h" // マス目のレンジ表現するクラス

#include "Tile.h"

#include "Vector3.h"

class AI_Map; // 前方宣言

// csvから読み取るマップの基本情報
struct MapInfo
{
    MapInfo() = default;
    inline MapInfo(unsigned int CellSize, int SpawnRangeX, int SpawnRangeY, MapRange::Type type = MapRange::Type::Rect)
        : CellSize{ CellSize }, spawnRange{ MapRange(SpawnRangeX,SpawnRangeY,type) } {};
    ~MapInfo() {};

    unsigned int CellSize{ 1 }; // マップの1マスのピクセル数
    MapRange spawnRange; // 敵の新規出現レンジ
};

struct MapSetting // マップ情報をMapSetting.csvなどから読みだす
{
    MapSetting(const std::string& csvFileName = "")
    {
        if (csvFileName != "")
            Load(csvFileName);
    }
    ~MapSetting() {}; //デストラクタ

    std::string FilePath{ "" }; // csvのファイルパス

    std::unordered_map<hash32, std::shared_ptr<MapInfo>> info; //[辞書]stageファイル名→マップ情報

    MapInfo& operator[](const std::string& stageFilePath) {
        assert(info.count(stageFilePath) != 0 && "マップのステージ情報が読み取れていません Map/MapSettings.csv にステージ情報があるか確認!");
        return *info[stageFilePath]; // 読み書き
    }

    void Load(const std::string& csvFileName = "")
    {
        if (csvFileName != "") this->FilePath = csvFileName;
        if (this->FilePath == "") return;

        // マップ情報のデータ CSVファイルからデータをロード
        DataCsv Data{ this->FilePath, DataCsv::CsvType::CsvValue };
        std::string stageFile; //ステージファイル名
        for (int j = 0; j < Data.size(); ++j)
        {
            if ((std::string)Data[j][0] == "//")
                continue; // 一番左の列が//なら読み飛ばしのコメント行
            else if ((std::string)Data[j][0] == "File")
            {
                stageFile = Data[j][1]; //ステージファイル名
                info.emplace(stageFile, std::make_shared<MapInfo>());
                continue;
            }

            if (stageFile == "") continue; //ステージファイル名が未確定
            else if ((std::string)Data[j][0] == "CellSize")
                info[stageFile]->CellSize = (int)Data[j][1]; //マス目サイズ
            else if ((std::string)Data[j][0] == "SpawnRange")
            {   // 敵出現レンジ
                MapRange::Type rangeType = ((std::string)Data[j][3] == "Ellipse") ? MapRange::Type::Ellipse : MapRange::Type::Rect; //敵出現のタイプ
                info[stageFile]->spawnRange.InitRange(rangeType, Data[j][1].intFromString(), Data[j][2].intFromString()); //敵出現のタイプを初期化設定
            }
        }
    }
};

// ステージのタイルマップのクラス
class Map
{
public:
    // コンストラクタ
    Map(World* world, const std::string& filePath = "")
        : m_world{ world }
    {
        if (filePath == "") return;

        Load(filePath);
    }

    // デストラクタ
    ~Map()
    {   // マップデータのお掃除
        enemyData.clear();
        //terrains.clear(); //★地形のお掃除
    }

    static MapSetting settings; // マップに関する設定 Map.cpp で初期読込み デフォルトは"Map/MapSetting.csv" を読み込む
    static TileSetting tileSettings; // タイルに関する設定 Map.cpp で初期読込み デフォルトは"Map/MapSetting.csv" を読み込む
    // タイルのチップ画像のパスとidからTileの情報とタイルのgid情報を得る
    static Tile GetTile(const std::string& mapChipPath, int id, unsigned int& gid)
    {
        auto pos = mapChipPath.find(".", 0);
        std::string tsxPath{ "" };
        if (pos != std::string::npos) tsxPath = mapChipPath.substr(0, pos) + ".tsx";
        auto found = Tilesets::umTsxInfo.find(tsxPath);
        if (found != end(Tilesets::umTsxInfo) && found->second.gid.size() > id)
            gid = found->second.gid[id]; // 辞書で id から gidに変換しておく
        else return Tile(); // id が-1のときは見つからなかったとき
        pos = tsxPath.find("/", 0);
        if (pos != std::string::npos) tsxPath = tsxPath.substr(pos + 1, tsxPath.size() - 1);
        return Map::tileSettings.info[tsxPath]->tiles[id];
    }

protected:
    World* m_world{ nullptr }; // マップに配置する敵などのポインタを保持するワールド
public:
    // 現在いるワールドへのポインタを得る
    World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    void world(World* changeWorld) { m_world = changeWorld; }

    std::shared_ptr<AI_Map> aiMap{ nullptr }; // AIの経路探索に使うAIマップ

    // 読込んだデータファイルの情報
    std::string FilePath{ "" };
protected:
    unsigned int m_CellSize{ 1 }; // マップの1マスのピクセル数 1のときはtmxに設定されたサイズが採用される
    unsigned int m_HalfCellSize{ 1 }; // マス目サイズの 1/2
public:
    inline unsigned int CellSize() { return m_CellSize; }
    inline unsigned int HalfCellSize() { return m_HalfCellSize; }
    inline void SetCellSize(unsigned int cellSize) { m_CellSize = cellSize; m_HalfCellSize = m_CellSize / 2; }
    MapRange spawnRange; // 敵の新規出現レンジをプレイヤから半径X,Yの楕円にする

    inline float centerX(int cellX) const { return (cellX >= 0) ? (float)((cellX * m_CellSize) + m_HalfCellSize) : (float)((cellX + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline float centerY(int cellY) const { return (cellY >= 0) ? (float)((cellY * m_CellSize) + m_HalfCellSize) : (float)((cellY + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline float centerZ(int cellZ) const { return (cellZ >= 0) ? (float)((cellZ * m_CellSize) + m_HalfCellSize) : (float)((cellZ + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline int cellX(float worldX) const { return (worldX >= 0) ? (int)worldX / m_CellSize : (int)worldX / m_CellSize - 1; } // マイナス方向にも対応
    inline int cellY(float worldY) const { return (worldY >= 0) ? (int)worldY / m_CellSize : (int)worldY / m_CellSize - 1; } // マイナス方向にも対応
    inline int cellZ(float worldZ) const { return (worldZ >= 0) ? (int)worldZ / m_CellSize : (int)worldZ / m_CellSize - 1; } // マイナス方向にも対応

    TMXCsv enemyData;// 敵配置データ
    TMXJson tmxData; // タイルマップエディタのjsonデータ

protected:
    // <gid→idに対応したタイプのbit> へ変換するvector型の辞書
    std::vector<TileBit> gidTypeBits{ 0 };
public:
    inline TileBit& gidTypeBit(unsigned int gid)
    {
        if (gid >= (int)gidTypeBits.size()) gidTypeBits.resize(gid + 1, 0);
        return (gid < 0) ? gidTypeBits[0] : gidTypeBits[gid];
    }
    // id→ビットのタイプ の辞書に |= でbitTypeをビット論理和して、Wall:壁 や Bounce:はずむ などの特性を追加する
    TileBit SetTypeBit(unsigned int gid, TileBit bitType) { return gidTypeBit(gid) |= bitType; }

    // id→ビットのタイプ の辞書で & ビット論理積で 指定したidが Wall:壁 や Bounce:はずむ などの特性を持つかを判定する
    inline bool isType(unsigned int gid, TileType bitType) { return gidTypeBit(gid) & bitType; }

    // id→ビットのタイプ の辞書で & ビット論理積で 指定したidが Wall:壁 や Bounce:はずむ などの特性を持つかを判定し一致したBitを返す(複数が1のビットもありうる)
    inline TileBit matchTypes(unsigned int gid, TileBit bitType) { return gidTypeBit(gid) & bitType; }

    // tilesetsの.tsxのfirstgidをたどって、SetTypeBitでビット辞書に
    // csvのtileSettingsから読み取っておいたビット設定を登録
    bool InitTypeBit(const Tilesets& tilesets)
    {
        assert(tilesets.size() > 0 && "ステージのjsonファイルにタイルセット設定が一つもありません");
        if (tilesets.size() == 0) return false;
        for (auto& pair : tilesets)
        {
            auto firstgid = pair.first;
            auto& tsxFilePath = pair.second;
            auto& tsxInfo = tilesets.umTsxInfo.at(tsxFilePath);
            // [親フォルダを抽出] https://qiita.com/takano_tak/items/acf34b4a30cb974bab65
            size_t path_i = tsxFilePath.find_last_of("\\/");
            if (path_i + 1 >= tsxFilePath.size()) continue;
            auto tsxFile = tsxFilePath.substr(path_i + 1);
            auto csvInfo = tileSettings.info.find(tsxFile);
            if (csvInfo == end(tileSettings.info)) continue; // csv側にタイルの情報がなかった
            // gidが0 idが-1 のデフォルトのタイルのビット特性を設定
            SetTypeBit(0, csvInfo->second->idTypeBit(-1));
            int id = 0; // gidじゃなく0から始まるローカルなid
            for (auto gid = firstgid, iEnd = firstgid + tsxInfo.tilecount; gid < iEnd; ++gid)
            {   // このMapのgidのビット辞書にcsvのtileSettingsから読み取っておいたビット設定を登録
                SetTypeBit(gid, csvInfo->second->idTypeBit(id));
                ++id; // ローカルなidも次に進める
            }
        }
    }

    // マップファイルのロード
    void Load(const std::string& filePath)
    {
        this->FilePath = filePath;

        // ★jsonファイルの読み込み
        tmxData.Load(filePath);

        // tilesetsの.tsxのfirstgidをたどって、SetTypeBitでビット辞書に
        // csvのtileSettingsから読み取っておいたビット設定を登録
        InitTypeBit(tmxData.tilesets);

        auto info = settings[filePath];
        if (info.spawnRange.umRange.size() != 0)
            spawnRange = info.spawnRange;
        else
            spawnRange.InitRange(MapRange::Type::Ellipse, 10, 10);// 敵の新規出現レンジをプレイヤから半径X,Yの楕円にする

        if (info.CellSize != 1)
        {
            SetCellSize(info.CellSize); // csvに書かれているCellSizeを設定
            tmxData.SetGridSize({(float)info.CellSize ,(float)info.CellSize ,(float)info.CellSize });
        }
        else // if (info.CellSize == 1)
            SetCellSize(tmxData.tmxInfo.tilewidth); // tmxのタイルの幅を取得
    }

    // オブジェクトを Factoryクラスで className に応じて見きわめて 生成・配置する
    template<typename TypeT>
    inline std::shared_ptr<TypeT> SpawnTmxObject(const std::string& className, std::shared_ptr<InitParams> initParams, Vector3 spawnPos, Quaternion rotation = { 0,0,0,1 })
    {
        //IDに応じてFactory工場で作り分ける
        return Factory::MakeSharedObject<TypeT>(className, initParams, m_world, spawnPos);
    }

    // 画面スクロール(位置の更新)
    void Scroll3D(Vector3 prevPos, Vector3 delta, MapRange* pSpawnRange = nullptr)
    {
        std::vector<TMXobjects*> tmxObjectsList{ &tmxData.enemies,&tmxData.lights,&tmxData.items,&tmxData.objects };
        for (auto& tmxObjects : tmxObjectsList)
        {
            if (tmxObjects->data.size() == 0) continue; // リストにデータが一つもなければスクロールせず次へ

            tmxObjects->Scroll(prevPos, delta, (pSpawnRange != nullptr) ? *pSpawnRange : (tmxObjects->pSpawnRange != nullptr) ? *(tmxObjects->pSpawnRange) : spawnRange,
                [&](int gridX, int gridY, int gridZ)
                {
                    auto pObject = tmxObjects->top(gridX, gridY, gridZ); // グリッドからオブジェクトを取り出す
                    while (pObject != nullptr)
                    {
                        auto pObj = (TMXobject*)pObject;

                        //int spawnID = pObj->spawnID; // 生成する敵のID情報をTMXの jsonファイルのデータから読み取る
                        float ratioX = (float)tmxObjects->cellWidth / tmxData.tmxInfo.tilewidth;
                        float ratioY = (float)tmxObjects->cellHeight / tmxData.tmxInfo.tileheight; // タイルマップエディタとのスケールの違いの比率を計算
                        float ratioZ = (float)tmxObjects->cellDepth / tmxData.tmxInfo.tilewidth;

                        //クラス名とベースクラス名に応じて工場で作り分ける
                        if (pObj->baseType == "Enemy")
                            m_world->enemies.emplace_back(SpawnTmxObject<Enemy>(pObj->type, pObj->GetInitProperty(), Vector3(pObj->x * ratioX, pObj->z * ratioZ, pObj->y * ratioY)));
                        //if (pObj->baseType == "Light")
                        //    m_world->lights.emplace_back(SpawnTmxObject<Light>(pObj->type, Vector3(pObj->x * ratioX, pObj->z * ratioZ, pObj->y * ratioY)));
                        //if (pObj->baseType == "Item")
                        //    m_world->items.emplace_back(SpawnTmxObject<Item>(pObj->type, pObj->GetInitProperty(), Vector3(pObj->x * ratioX, pObj->z * ratioZ, pObj->y * ratioY)));
                        //if (pObj->baseType == "Object")
                        //    m_world->objects.emplace_back(SpawnTmxObject<Object>(pObj->type, Vector3(pObj->x * ratioX, pObj->z * ratioZ, pObj->y * ratioY)));
                        //m_world->enemies.emplace_back(Factory::MakeSharedTmxObject(spawnID,m_world, Vector3(pEnemy->x * ratioX, pEnemy->z * ratioZ, pEnemy->y * ratioY)));

                        pObject = pObject->m_pNext; // 同じグリッドの次のオブジェクトへ

                        tmxObjects->Erase(pObj); // tmxObjectsのdataとグリッドからデータを消して再びスポーンしないようにする
                    }
                    // このグリッドには何もいなくなるのでマス目自体eraseして高速化
                    tmxObjects->cellData.erase(CellXYZ(gridX, gridY, gridZ));
                });
        }
    }


    // 指定された座標(ワールド座標)の地形データを取得する
    unsigned int GetTerrain(float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        if (worldZ != (std::numeric_limits<float>::min)())
        {
            float tmpY = worldY;
            worldY = worldZ; //【YとZを変換】Zの入力があるときはZをYとして扱う
            worldZ = tmpY; //【YとZを変換】
        }

        // 負の座標が指定された場合は、何も無いものとして扱う
        if (worldX < 0 || worldY < 0)
            return Tile::None;

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = (int)(worldX / m_CellSize);
        int mapY = (int)(worldY / m_CellSize);
        int mapZ = (int)(worldZ / m_CellSize);

        if (tmxData.terrains.count(mapZ) == 0) // zの階層がまだなかったとき
            return Tile::None; // 配列の範囲外は、何も無いものとして扱う

        auto& terrainZ = tmxData.terrains[mapZ]; // 辞書からZに対応するデータへの参照を得る
        if (mapY >= terrainZ.size() || mapX >= terrainZ[mapY].size())
            return Tile::None; // 配列の範囲外は、何も無いものとして扱う

        return terrainZ[mapY][mapX]; // 二次元配列から地形IDを取り出して返却する
    }

    // 指定された座標(ワールド座標)の地形データを書き換える return には書き換える前のgidが返る
    bool SetTerrain(unsigned int gid, float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        if (worldZ != (std::numeric_limits<float>::min)())
        {
            float tmpY = worldY;
            worldY = worldZ; //【YとZを変換】Zの入力があるときはZをYとして扱う
            worldZ = tmpY; //【YとZを変換】
        }

        // 負の座標が指定された場合は、書き換えられない
        if (worldX < 0 || worldY < 0)
            return false;

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = (int)(worldX / m_CellSize);
        int mapY = (int)(worldY / m_CellSize);
        int mapZ = (int)(worldZ / m_CellSize);

        // 二次元配列の範囲外は、書き換えられない
        //if (mapY >= terrain.size() || mapX >= terrain[mapY].size())
        //    return;
        bool isNewZ = false;
        if (tmxData.terrains.count(mapZ) == 0) isNewZ = true; // 新しい z の階層の場合

        auto& terrainZ = tmxData.terrains[mapZ]; // 辞書からZに対応するデータへの参照を得る

        if (isNewZ) // 新しい z の階層の場合
        {
            terrainZ.z = mapZ;
            terrainZ.tilesets = &(tmxData.tilesets); //  新しい z の階層のtilesetsを設定する
            terrainZ.Width = tmxData.tmxInfo.width; terrainZ.Height = tmxData.tmxInfo.height;
            auto& terrinZYX = terrainZ[mapY][mapX];
            terrinZYX.tilesets = &(tmxData.tilesets);
            terrinZYX = gid; // 地形データ書き換え
        }
        else
            terrainZ[mapY][mapX] = gid; // 地形データ書き換え
        return true;
    }

    // gidに応じてタイルを描き分ける
    void DrawTile(unsigned int gid, float worldX, float worldY, float worldZ, float ExRate)
    {
        auto texture = TsxInfo::id_images[gid].second.lock();
        int id = TsxInfo::id_images[gid].first;
        if (isType(gid, TileType::Floor)) // 板ポリゴンで床を描く
            MyDraw::DrawDivRotaFloorF3D(MyDraw::Plane::Y, worldX, worldY, worldZ, ExRate, 0, *texture, id);
        else if (isType(gid, TileType::Wall)) //壁のときはボックスを描く
            MyDraw::DrawDivBoxF3D(worldX, worldY, worldZ, ExRate, *texture, id);
    }

    // 地形をタイルで描く
    void DrawTerrain()
    {
        for (auto&& terrainN : tmxData.terrains)
        {
            auto& terrainZ = *terrainN.second;
            int cellZ = terrainN.first;
            for (int cellY = 0, ySize = terrainZ.size(); cellY < ySize; cellY++)
            {
                for (int cellX = 0, xSize = terrainZ[cellY].size(); cellX < ySize; cellX++)
                {
                    float x = centerX(cellX); //マス目サイズ/2ずらし
                    float y = centerY(cellY); //マス目サイズ/2ずらし
                    float z = centerZ(cellZ); //マス目サイズ/2ずらし
                    int id = -1;
                    unsigned int gid = 0;
                    if (cellY < terrainZ.size() && cellX < terrainZ[cellY].size())
                    {
                        gid = terrainZ[cellY][cellX];
                    }
                    if (0 <= gid && gid < TsxInfo::id_images.size())
                    {
                        auto pTileImage = TsxInfo::id_images[gid].second.lock();
                        if (pTileImage == nullptr) continue;
                        // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
                        for (auto&& keyValue : world()->cameras) // ワールドにあるすべてのカメラぶんループ
                        {
                            auto& camera = keyValue.second;
                            //auto stepXYZs = MyMath::CrossGridVec(camera->pos - camera->m_targetXYZ, Vector3{ CellSize, CellSize, CellSize });
                            camera->Draw([&]()
                                {
                                    float ExRate = (float)m_CellSize / (float)pTileImage->m_XSize;
                                    DrawTile(gid, x, z, y, ExRate); // idに応じてタイルを描き分ける
                                });
                        }
                    }
                }
            }
        }
    }

    // 指定された座標(ワールド座標)の地形が壁か調べる
    inline bool IsWall(float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        auto terrainID = GetTerrain(worldX, worldY, worldZ); // 指定された座標の地形のIDを取得

        return IsWall(terrainID);
    }

    //あるIDが壁かどうかだけ調べる
    inline bool IsWall(unsigned int gid)
    {   // 地形が壁ならtrue、違うならfalseを返却する
        return isType(gid, TileType::Wall);
    }

    // 指定された座標worldPosの地形が 壁 や ダメージブロックかなどをチェックする関数
    bool checkID(TileBit checkBit, float worldX, float worldY, float worldZ,
        int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, unsigned int* pTerrainID = nullptr, TileBit* pMatchBits = nullptr)
    {
        unsigned int terrainID; // 指定された座標の地形のID

        if (worldZ != (std::numeric_limits<float>::min)())
        {   //【YとZを変換】Zの入力があるときはZをYとして扱う タイルマップの X,Yは3DではYがZ:奥行に対応するから
            float tmpY = worldY;
            worldY = worldZ;
            worldZ = tmpY; //【YとZを変換】
        }

        if (worldX < 0 || worldY < 0) return false; // 負の座標が指定された場合

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = cellX(worldX), mapY = cellY(worldY), mapZ = cellZ(worldZ);

        if (tmxData.terrains.count(mapZ) == 0) // zの階層がまだなかったときは 0
            terrainID = 0; // 範囲外のときは 0
        else // zの階層がすでにあったとき
        {   // 二次元配列の範囲内か判定
            auto& terrainZ = tmxData.terrains[mapZ];
            if (mapY >= terrainZ.size() || mapX >= terrainZ[mapY].size())
                terrainID = 0; // 範囲外のときは 0
            else
                terrainID = terrainZ[mapY][mapX]; // 配列から地形IDを取り出す
        }
        if (pTerrainID != nullptr)
            *pTerrainID = terrainID;

        TileBit matchBits = matchTypes(terrainID, checkBit); // ビット論理積 & で地形のchekckBitを確かめる
        bool isCheckBit = matchBits != (TileBit)TileType::None;
        if (isCheckBit && pMapX != nullptr && pMapY != nullptr && pMapZ != nullptr)
            *pMapX = mapX, * pMapY = mapY, * pMapZ = mapZ; // ブロックのマス目のセル番号をポインタに記録(nullptrの場合は記録しない)
        if (pMatchBits != nullptr)
            *pMatchBits = matchBits; // マッチしたビットをポインタ先に代入して返す

        return isCheckBit;
    }
};

#endif


Player.cppを変更して、マップをスクロールして敵を出現させましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"
#include "GridSteps.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}

// ジャンプ開始した直後に呼ばれる関数
void Player::OnStartJump()
{
    this->isGround = false; // ジャンプ中はisGroundフラグがfalse
}


// 更新処理
void Player::Update()
{
    vxForce *= 0.9f; // かかっている力の減衰率
    vyForce *= 0.9f; // かかっている力の減衰率
    vzForce *= 0.9f; // かかっている力の減衰率
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    auto pJumpPhysics = jumpPhysics.lock();
    if (pJumpPhysics != nullptr)
    {
        if (isGround)
        {
            if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
                || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
            {
                pJumpPhysics->JumpStart(); // ジャンプのスタート処理を発動
            }
        }
        else if (vy > 0 && Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
            || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
        {
            pJumpPhysics->JumpCancel(); // ジャンプのキャンセル処理を発動
        }
        pJumpPhysics->Update(); // ジャンプのコンポーネントを更新する
    }
    auto pGravityPhysics = gravityPhysics.lock();
    if (pGravityPhysics != nullptr)
        pGravityPhysics->Update(); // 重力落下のコンポーネントを更新する

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    float vyForceJump = (pJumpPhysics != nullptr) ? pJumpPhysics->vyForce() : 0.0f;
    float vyForceGravity = (pGravityPhysics != nullptr) ? pGravityPhysics->vyForce() : 0.0f;

    vx += vxForce; // 力をかけて速度に加算する
    vy += vyForce + vyForceJump + vyForceGravity; // 勢い(力・フォースを加算
    vz += vzForce; // 力をかけて速度に加算する

    // 実際に位置を動かす

    auto pMapHitBox = mapHitBox.lock();
    float cellHalf = (map() != nullptr) ? (float)map()->CellSize() / 2.0f : 16.0f; // マップのセルのサイズの半分
    // 速すぎて当たり判定がすり抜けないように細切れのステップのグリッドで移動させつつ当たり判定する
    Vector3 gridStepSize = (pMapHitBox == nullptr) ? Vector3{ cellHalf, cellHalf, cellHalf } : pMapHitBox->m_BoxSize / 2;
    GridSteps vSteps{ velocity, gridStepSize }; // 細切れのステップの数値を計算
#ifdef _DEBUG
    if (vSteps.div >= 1000) // 分割が1000を超える場合は物理式かどこかで∞=inf や 計算不可能数=nanが発生してそう
    {   // vx,vy,vzに原因がありそうなのでデバッグ表示
        if (!isfinite(vx)) printfDx(("vx:∞かnan x:" + std::to_string(x) + "\n").c_str());
        if (!isfinite(vy)) printfDx(("vy:∞かnan y:" + std::to_string(y) + "\n").c_str());
        if (!isfinite(vz)) printfDx(("vz:∞かnan z:" + std::to_string(z) + "\n").c_str());
    }
#endif
   
    bool isCollisionX{ false }, isCollisionY{ false }, isCollisionZ{ false }, isCollisionCollider{ false };
    for (int i = 1, iSize = vSteps.div + 1; i <= iSize; ++i)
    {
        auto& vStep = (i < iSize) ? vSteps.step : vSteps.mod;
        // まず横に移動する
        if (pMapHitBox == nullptr)
            x += vx;
        else if (isCollisionX == false) // まだ前のステップでX方向の衝突がないなら
        {
            x += vStep.x;
            isCollisionX = pMapHitBox->hitCheckX((TileBit)TileType::Wall);
        }

        // 次に縦に動かす
        if (pMapHitBox == nullptr)
            y += vy;
        else if (isCollisionY == false) // まだ前のステップでY方向の衝突がないなら
        {
            y += vStep.y;
            isCollisionY = pMapHitBox->hitCheckY((TileBit)TileType::Wall | (TileBit)TileType::Floor);
        }

        // 次にZ奥行き方向に動かす
        if (pMapHitBox == nullptr)
            z += vz;
        else if (isCollisionZ == false) // まだ前のステップでZ方向の衝突がないなら
        {
            z += vStep.z;
            isCollisionZ = pMapHitBox->hitCheckZ((TileBit)TileType::Wall);
        }


        if (auto pGridCollision = gridCollision.lock())
        {
            pGridCollision->Update(); // 所属するマス目セルを更新する
            for (auto& collider : *pGridCollision->colliders())
                isCollisionCollider |= collider.lock()->IsCollision();
        }

        // 衝突があったら、速すぎてすり抜けちゃう前に今のステップでbreakで抜ける
        if (isCollisionCollider)
            break; // ぶつかっているのにさらに次のステップの速度も足すとすり抜けちゃう
    }

    map()->Scroll3D(position, Vector3(vx, vy, vz)); // マップ(敵)データをスクロール

    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

void Player::OnCollision(std::shared_ptr<Collision> pCollision)
{
    if (isDead) return; // すでにisDeadなら return
    auto otherCollider = pCollision->contacts[0].otherCollider.lock(); // コライダの弱共有ポインタをロック
    if (otherCollider == nullptr) return; // すでにコライダが消去済だった
    auto other = otherCollider->owner();

    if (other->isDead) return; // other側がisDeadだったら処理をスルー

    if (other->baseTag == "Enemy" || other->baseTag == "Player")
    {
        //[離散要素法:DEM] https://qiita.com/konbraphat51/items/157e5803c514c60264d2
        constexpr float k = 1.0f; // バネ係数(めり込みに対するバネ反発の掛け率)
        constexpr float e = 0.1f; // はね返り係数(直前と比べどれくらいの速さで離れるか0.5だと半分の速さではね返る)
        float log_e = std::log(e); // はね返り係数の自然対数log
        constexpr float pi_2 = DX_PI_F * DX_PI_F; // πの2乗
        float eta = -2.0f * log_e * std::sqrt(k * mass / (log_e * log_e + pi_2)); // 粘性係数η(イータ)
        auto norm = pCollision->contacts[0].normal; // 衝突の法線 * other->pushPower; // pushPowerの力でotherに押し返される
        auto v_norm = dot(other->velocity - velocity, norm) * norm; // [相対速度v] 法線norm方向のv = (v1 - v0)・norm [ドット積]
        auto sep = pCollision->contacts[0].separation; // 衝突点どうしの距離(めりこみ距離)
        auto F = -k * sep * norm - eta * v_norm; // F (法線方向の力) = -k × sep - η × v (相対速度:自分から見た相手otherの速度)
        force += F / mass; // 衝突法線方向の力を加える
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

#ifdef _DEBUG // デバッグのときだけ_DEBUGが定義され描かれる
                if (auto pSphereCollider = sphereCollider.lock()) pSphereCollider->Draw(); // コライダを描く
                if (auto pMapHitBox = mapHitBox.lock()) pMapHitBox->Draw(); // ヒットボックスを描く
#endif
            });

    }
}



いかがでしょう?敵キャラが表示されたでしょうか?
敵キャラはあとからAIで賢くすることとして、現在はまだふらふらしているだけです。
ジャンプして踏みつければ大ジャンプできるか試しておきましょう。


ItemクラスとItemActionクラスを作成し継承して弾やブロックなどのアイテムを取って使用できるようにする

Item.hを新規作成して、アイテムのベース基底クラスを定義しましょう。

#ifndef ITEM_H_
#define ITEM_H_

#include "GameObject.h"
#include "Collider.h"

class World; //前方宣言

// アイテムの情報のベース基底クラス 継承override してアイテム情報をjsonなどから受け取る
struct ItemInfo : public InitParams
{
    //std::shared_ptr<InitParams> initParams{ nullptr };
    hash32 itemTag{ "" }; // アイテムを区別して、アイコンを区別して辞書に分けていれるためのタグ
    inline ItemInfo(std::shared_ptr<InitParams> initParams = nullptr) : InitParams()
    {}
    virtual ~ItemInfo() {}
};

// アイテムの基底クラス。
// 全てのアイテムは、このクラスを継承して作る。
class Item : public GameObject
{
public:
    std::shared_ptr<ItemInfo> m_ItemInfo{ nullptr }; // アイテムに関する情報
protected:
    // コンストラクタ
    Item(World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 })
        : GameObject(world, position, rotation)
    {
        this->baseTag = "Item";
    }
public:
    virtual ~Item() {}
   
    // 継承overrideしてアイテムを取った時にアイテム一覧で選択できるアクションを持つかをtrueかfalseで返す
    virtual bool hasAction() { return false; }

    // Initをoverrideしてアイテムの情報をjsonなどから受け取っておく
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        m_ItemInfo = std::make_shared<ItemInfo>(initParams); // アイテム情報を持っておく
    }

    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override
    {
        if (isDead) return;
        auto otherCollider = pCollision->contacts[0].otherCollider.lock();
        if (otherCollider == nullptr) return;
        auto thisCollider = pCollision->contacts[0].thisCollider.lock();
        if (thisCollider == nullptr) return;
        auto other = otherCollider->owner();

        if (other->isDead) return; // other側がisDeadだったら処理をスルー

        if (other->baseTag == "Player")
        {
            if (!thisCollider->isTrigger)
            {   // 跳ね返したいアイテムの場合は forceをかける
                auto norm = pCollision->contacts[0].normal * other->pushPower;
                force += -norm;
            }
            other->OnItem(shared_from_this_as<Item>()); // プレイヤのときはアイテムの効果を発動
            OnItem(shared_from_this_as<Item>()); // 自身のOnItemも呼ぶ(アイテム自身が爆発したりできる)
        }
    }

    // アイテムを取ったときの共通処理を overrideして実装
    virtual void OnItem(std::shared_ptr<Item> pItem) override
    {
        if (pItem == shared_from_this_as<Item>())
        {
            // 自分自身からOnItemが呼ばれた場合はこちら
        }
        else
        {
            //if (pItem->baseTag == "Player")
            //{
            //    //life += 1; // ライフを増やすなど
            //}
        }
    }
};


#endif


ボタンを押して選択中のアイテムを次のアイテムに変えるのに向いているのは「次、前のリンクリスト構造」です。
各アイテムのアイコンを表すItemActionクラスには前Prev、次Nextのリンクリスト構造を持たせることにしましょう。

ItemAction.hを新規作成して、アイテムを使ったときに起こるアクションのベース基底クラスを定義しましょう。

#ifndef ITEM_ACTION_H_
#define ITEM_ACTION_H_

#include "Component.h"
#include "Vector3.h"
#include "Resource.h"
#include "Item.h"

class ItemListComponent; // 前方宣言

// アイテムを使ったときに起きるアクション
class ItemAction : public Component
{
    //protected:
public:
    inline ItemAction(ItemListComponent* pItemList, std::shared_ptr<GameObject> pOwner)
        : m_items{ pItemList }, Component(pOwner)//, m_itemTag{ itemTag }
    {
        this->baseTag = "ItemAction";
    }
    virtual ~ItemAction() {}
    std::shared_ptr<ItemInfo> m_ItemInfo{ nullptr }; // アイテムに関する情報
    // アイテムを区別して、アイコンを区別して辞書に分けていれるためのタグ
    virtual hash32 ItemTag() { return (m_ItemInfo == nullptr) ? "" : m_ItemInfo->itemTag; };

    ItemListComponent* m_items; // プレイヤの持っているアイテム一覧へのリンク
    std::weak_ptr<ItemAction> m_prev; // リンクリスト構造の前へのポインタ
    std::weak_ptr<ItemAction> m_next; // リンクリスト構造の次へのポインタ
    //hash32 m_itemTag{ "" }; // アイテムのタグ
    //int order{ 0 }; // アイテムの並び変え順番の
public:
    std::shared_ptr<ItemAction> Prev() { auto pPrev = m_prev.lock(); return pPrev; }
    void Prev(std::shared_ptr<ItemAction> pPrev) { m_prev = pPrev; }
    std::shared_ptr<ItemAction> Next() { auto pNext = m_next.lock(); return pNext; }
    void Next(std::shared_ptr<ItemAction> pNext) { m_next = pNext; }
    // 一番最初を返す
    std::shared_ptr<ItemAction> Top()
    {
        auto pPrev = Prev();
        if (pPrev == nullptr)
            return shared_from_this_as<ItemAction>(); // 前がないならすでに1番最初
        for (;;)
        {    // 前の前がnullptrなら一番最初
            auto pPrevPrev = pPrev->Prev();
            if (pPrevPrev == nullptr) return pPrev; // 1番最初
            pPrev = pPrevPrev;
        }
    }
    // 次がnullptrのときにも一番最初にループして次を返す
    std::shared_ptr<ItemAction> LoopNext()
    {
        auto pNext = Next();
        if (pNext != nullptr) return pNext;
        auto pTop = Top();
        auto pSelf = shared_from_this_as<ItemAction>(); // 自分自身
        if (pTop == pSelf) return pSelf; // 1番最初が自身になるのは1つしかない場合
        return pTop; // 一番最初へループする
    }

    // 例えば、すでに取得したプレイヤの弾と同じ種類の弾のアイテムをゲットしたら、
    // アイテムとしてダブらせるのではなく使用回数を増やす処理をこの関数をoverrideしてやる
    virtual void AddSameItem(std::shared_ptr<InitParams> initParam)
    {
        // 継承 overrideして、アイテムとしてダブらせるのではなく例えば、弾の使用回数を増やす処理などを実装
    }

    // なにかアイテムのアクション系のコンポーネントで共通して更新したいことがあればここに実装
    virtual void Update(GameObject* obj = nullptr) override
    {
    }

    // なにかアイテムのアクション系のコンポーネントで共通して描画したいことがあればここに実装
    virtual inline void Draw(GameObject* obj = nullptr) override;

    // アイコンを描く
    virtual Vector3 DrawIcon(const Vector3& drawPos, const Vector3& limitPos, int nextIntervalX = 30)
    {
        // overrideしてアイコンを描く処理を個別実装する
        return drawPos;
    }

    // 次のアイテムのUIを数珠つなぎで描く nextPos に次はどのくらいずらすか指定
    // limitPosを超える位置には描かない、超えない範囲でループしてselectingに戻るまでつぎつぎと描く
    virtual void DrawNextUI(const Vector3& nextPos, const Vector3& limitPos, std::shared_ptr<ItemAction> pSelecting);
};


class Item; // 前方宣言

// 複数のアイテムをリストとして管理するコンポーネント(UIも描画する)
class ItemListComponent : public Component
{
    //protected:
public:
    inline ItemListComponent(std::shared_ptr<GameObject> pOwner) : Component(pOwner)
    {
        this->baseTag = "ItemListComponent";
    }
    virtual ~ItemListComponent() {}
public:
    int heightUI{ 32 };
    int frameWidth{ 2 }; // アイテムの枠の幅
    unsigned int frameColor = GetColor(255, 255, 255); // アイテムの枠の色
    Vector3 drawPos{ (float)frameWidth, (float)frameWidth + 20, 0 }; // アイテムを描くUI上の2D位置の起点
protected:
    //std::map<PlayerItemAction, hash32> order; // アイテムの並び順番
    //hash32 m_selectingTag{ "" }; // 取得したアイテムの選択中のアクションのタグ
    std::weak_ptr<ItemAction> m_selecting;
    std::weak_ptr<ItemAction> m_topAction;
    std::unordered_map<hash32, std::shared_ptr<ItemAction>> m_actions; // 取得したアイテムのアクションのリスト
public:

    // アイテムのアクションを得る 例えば Get<ItemBulletAction>でItemBulletActionという名前のアクションをm_actions辞書から得る
    template<class ActionT>
    std::shared_ptr<ActionT> Get(const std::string& itemTag)
    {
        auto itr = m_actions.find(itemTag);
        if (itr == end(m_actions)) return nullptr;
        return itr->second;
    }

    // アイテムを削除する
    bool RemoveAction(std::shared_ptr<ItemAction> removeAction)
    {
        if (removeAction == nullptr || removeAction->ItemTag() == "")
            return false; // 見つからずに消せなかった場合は false
        m_topAction = removeAction->LoopNext(); // 消すの前に次をトップにしておく
        m_actions.erase(removeAction->ItemTag());
        return true;
    }


    // アイテムを追加する .Add<ItemBulletAction>(...)で追加できる
    template<class ActionT, typename ...ArgsT>
    std::shared_ptr<ActionT> Add(std::shared_ptr<ItemInfo> initParam, ArgsT&&...args)
    {
        //★[ActionTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        //size_t hashID = typeid(ActionT).hash_code(); //[まずハッシュでID化]
        auto found = m_actions.find(initParam->itemTag);
        if (found != end(m_actions))
        {    // すでに取得済の種類のアイテムだったらAddSameItemで取得済のアイテムの追加処理を呼ぶ
            found->second->AddSameItem(initParam);
            return found->second->shared_from_this_as<ActionT>();
        }
        // shared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        std::shared_ptr<ActionT> newAction = Create<ActionT>(initParam, std::forward<ArgsT>(args)...);
        m_actions.emplace(initParam->itemTag, newAction);

        auto pSelecting = Selecting(); // 現在選択中のアイテムのリンクを取得
        if (pSelecting == nullptr) // 現在ひとつもアイテムがない場合は先頭Topとして追加する
        {
            m_topAction = newAction; // Topを newAction に設定する
            return newAction;
        }

        //↓リンクリスト構造のリストを使ってリンクを相互に張る
        auto selectingNext = pSelecting->Next(); // [今選択中のアイテムの★次]
        pSelecting->Next(newAction); // 「今選択中のアイテム」の → 次を newActionにする
        newAction->Prev(pSelecting); // 「今選択中のアイテム」を ← newAction の前 にする
        newAction->Next(selectingNext); // selecting と selectingのNext の間にはさむ
        if (selectingNext)
            selectingNext->Prev(newAction); // newAction ←[今選択中のアイテムの★次] のリンク
        //↑ここまでの処理で数珠つなぎのリンクリスト構造になる

        return newAction;
    }

    std::shared_ptr<ItemAction> Add(std::shared_ptr<Item> pItem);

    virtual std::shared_ptr<ItemAction> Top()
    {
        auto pTopAction = m_topAction.lock(); // 弱共有ポインタをlock()する
        if (m_actions.size() == 0 || pTopAction == nullptr)
            return nullptr; // リストにひとつもアクションがない場合は nullptr を返す
        return pTopAction;
    }

    virtual std::shared_ptr<ItemAction> Selecting()
    {
        auto pSelecting = m_selecting.lock(); // 弱共有ポインタをlock()する
        if (pSelecting == nullptr) // なにも選択中のアイテムがない場合は
            return Top(); // リンクリスト構造の一番最初のトップを返す
        return pSelecting;
    }

    template <typename ActionT>
    std::shared_ptr<ItemAction> Select()
    {
        size_t hashID = typeid(ActionT).hash_code(); //[まずハッシュでID化]
        auto found = m_actions.find(hashID);
        if (found == end(m_actions)) return nullptr; // 見つからなかったら
        return m_selecting = found->second; // 見つかったアクションをm_selectingに設定してそのアクションを返す
    }

    // 選択中のアイテムの次のアイテムを返す
    virtual std::shared_ptr<ItemAction> SelectNext()
    {
        auto pSelecting = Selecting();
        if (pSelecting == nullptr) return pSelecting;
        auto pNext = pSelecting->Next();
        if (pNext == nullptr) pNext = Top(); // リンクリスト構造の次がないときはループして一番最初のTopを返す
        m_selecting = pNext; // 現在選択中のアイテムを更新する
        return pNext;
    }

    virtual void Update(GameObject* obj = nullptr) override
    {
        auto pTop = Top();
        if (pTop == nullptr) return;
        pTop->Update();
        auto pNext = pTop->Next();
        while (pNext != nullptr)
        {
            pNext->Update();
            pNext = pNext->Next();
        }
    }

    virtual void Draw(GameObject* obj = nullptr) override
    {
        auto pSelecting = Selecting();
        if (pSelecting == nullptr) return;
        pSelecting->Draw();
        //auto pNext = pSelecting->Next();
        //pNext->Draw();
    }
};


// なにかアイテムのアクション系のコンポーネントで共通して描画したいことがあればここに実装
inline void ItemAction::Draw(GameObject* obj)
{
    Vector3 limitPos{ 400, -1, -1 };
    Vector3 nextIconPos = DrawIcon(m_items->drawPos, limitPos); // アイコンを描く
    if (nextIconPos == m_items->drawPos) return; // nextの位置とdrawPosが同じならアイコンを描けなかった
    auto pNext = Next(); // 次のアイテムへのリンクを得る
    auto pSelf = shared_from_this_as<ItemAction>(); // 自分自身
    if (pNext != nullptr)
        pNext->DrawNextUI(nextIconPos, limitPos, pSelf); // 数珠つなぎで次のアイコンを描く
    else
    {
        auto pLoopNext = LoopNext(); // 次がnullptrでも最初のアイテムへループして描く
        if (pLoopNext == pSelf) return; // ループしても自分自身ならアイテムは一つしかないので終了
        pLoopNext->DrawNextUI(nextIconPos, limitPos, pSelf); // 数珠つなぎで次のアイコンを描く
    }
}

// 次のアイテムのUIを数珠つなぎで描く nextPos に次はどのくらいずらすか指定
// limitPosを超える位置には描かない、超えない範囲でループしてselectingに戻るまでつぎつぎと描く
inline void ItemAction::DrawNextUI(const Vector3& nextPos, const Vector3& limitPos, std::shared_ptr<ItemAction> pSelecting)
{
    Vector3 nextIconPos = DrawIcon(nextPos, limitPos); // アイコンを描く
    if (nextIconPos == m_items->drawPos) return; // nextの位置とdrawPosが同じならアイコンを描けなかった
    auto pLoopNext = LoopNext(); // 次のアイテムへのリンクを得る
    if (pLoopNext == pSelecting) return; // 次が現在選択中アイテムならすべてループしたのでここで終了
    pLoopNext->DrawNextUI(nextIconPos, limitPos, pSelecting); // 数珠つなぎで次のアイコンを描く
}

#endif



ItemAction.cppを新規作成して.cpp側でItemBlock.h(ブロック)やItemBullet.h(弾)などをインクルードすることで、
アイテムの種類に応じてアイテムを使ったときに起こるアクションをAddでItemListComponent(アイテムのリスト)に追加しましょう。

#include "ItemAction.h"

#include "Item.h"
#include "ItemBullet.h"
#include "ItemBlock.h"

// .cppで各種Itemを継承したクラスとItemActionを継承したクラスを#includeして
// アイテムの種類に応じてItemからItemActionを初期化する

std::shared_ptr<ItemAction> ItemListComponent::Add(std::shared_ptr<Item> pItem)
{
    if (pItem->typeTag == "") return nullptr;

    if (pItem->typeTag == "ItemBullet")
        return this->Add<ItemBulletAction>(pItem->m_ItemInfo, this, owner());
    else if (pItem->typeTag == "ItemBlock")
        return this->Add<ItemBlockAction>(pItem->m_ItemInfo, this, owner());
    return nullptr;
}

このItemAction.cppフィールド上のアイテム(Itemクラス)と取得した画面UIのアイテムのリスト(ItemListComponentクラス)と
アイコン上の使用するアイテムの動作アクション(ItemActionクラスを継承したItemBulletActionItemBlockActionなど)の
「異なるアイテムに関わる3要素をつなぐ一番大事な役割」をこなしています。
ブロックや弾以外にも後から「剣」「ばくだん」など自作のアイテムを作った場合にはこのItemAction.cppで新アイテムを#includeしてAdd処理を書く必要が出てきます。
当然、様々な新種類のアイテムのインクルードが、この.cppファイルに集約して集まるように設計してありますので忘れないでください。


GameObject.hを変更して、OnItemなどアイテムを取ったときのコールバック関数を準備しましょう。

#ifndef GAMEOBJECT_H_
#define GAMEOBJECT_H_

#include <memory> // 共有ポインタの定義のため

#include "Vector3.h"
#include "Quaternion.h"
#include "Tile.h" // OnHitTileのコールバックに必要

#include "Object.h" // インスタンスIDや検索タグを持ち、shared_from_this_as<継承先クラス名>などができる
#include "World.h"

class Map; // 前方宣言
class Component; // 前方宣言
class Collision; // 前方宣言
class CollisionParams; // 前方宣言
class Item; // 前方宣言

// ゲーム上に表示される物体の基底クラス。
// プレイヤーや敵、アイテムなどはこのクラスを継承して作る。
class GameObject : public Object
{
protected:
    // コンストラクタはprotected:にしてあるのでCreate関数経由で初期生成してください
    GameObject(World* world = nullptr, Vector3 position = { 0,0,0 }, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : Object(), m_world{ world }, position{ position }, rotation{ rotation }, velocity{ velocity }, force{ force }
    {

    }

public:
    // 仮想デストラクタ
    virtual ~GameObject() {}


    union { // ★共用体unionテクニック https://inemaru.hatenablog.com/entry/2016/03/02/005408

        //★下記3つはメモリ上で共用状態になる(position、x,y,z、xyzどの名前から数値を変えたり読出してもメモリ上は同じ)
        // 同じデータに3種類の名前を付けたイメージ、しかもVector3の機能や配列としてのアクセスの仕方もできて便利
        struct { float x, y, z; }; // XYZ座標  [匿名共用体とは] https://code.i-harness.com/ja-jp/q/4d437c
        Vector3 position; // XYZ座標
        std::array<float, 3> xyz; // float xyz[3];と同じ意味 float 3個ぶんのデータサイズでx,y,z 3個ぶんと一致するので★unionで共用
    }; // unionは異なる複数のものをメモリ上の同一の番地に割り当てられる⇒x,y,z分けて記述するの面倒なとき配列xyz[3]をfor文i=0~3で回せる

    Quaternion rotation; // 回転クオータニオン

    union {
        struct { float vx, vy, vz; }; // XYZ方向の速度
        Vector3 velocity; // XYZ方向の速度
        std::array<float, 3> vxyz;
    };

    union {
        struct { float vxForce, vyForce, vzForce; }; // XYZ方向にかかる力(Unityでいうと AddForce関数 )
        Vector3 force; // XYZ方向の力(物理的には加速度:1フレームごとにvelocityの増える=加速する量)
        std::array<float, 3> vxyzForce;
    };

    // 重力を返す、重力を変えたいときはoverrideして処理を変えてください、重力を変えてない場合は 0.0f
    virtual inline float gravity() { return 0.0f; }

protected:
    World* m_world; // GameObjectが配置されたワールドへのリンク
public:
    // 現在いるワールドへのポインタを得る
    virtual World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    virtual void world(World* changeWorld) { m_world = changeWorld; }
    // GameObjectが現在いるマップへのポインタを返す
    Map* map() { return m_world->map.get(); }
   
    bool isDead{ false }; // 死んだ(削除対象)フラグ
    bool isStatic{ false }; // 動かない(静的オブジェクトか?)
    bool isGround{ false }; // 地面についているか
    bool isJumping{ false }; // ジャンプ中か
    bool isFlying{ false }; // 空をとんでいるか

    float pushPower{ 5.0f }; // コライダで接触したときに押し返す力(相手側が強いと押される)
    float mass{ 1.0f }; // 質量(重さ)コライダで接触したときに重さでforceを割り算すると動きにくくなる
    std::shared_ptr<CollisionParams> collisionParams; // 衝突関係の物理パラメータ

    hash32 typeTag{ "" }; // 小カテゴリ Zako0など個別のタイプ
    hash32 baseTag{ "" }; // 大カテゴリ Enemyなど大まかなベースジャンル

protected:
    // コンポーネントの<ハッシュID, 共有所有ポインタ> の辞書でコンポーネントの共有カウンタ+1を保持する
    std::unordered_map<size_t, std::shared_ptr<Component>> components;
public:
    // コンポーネントを得る 例えば GetComponent<BoxCollider>でBoxColliderという名前のコンポーネントをcomponents辞書から得る
    template<class ComponentT>
    std::vector<std::weak_ptr<ComponentT>> GetComponents()
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        std::vector<std::weak_ptr<ComponentT>> returnList; // "ComponentT" 例."CircleCollider"など をキーとするコンポーネント一覧
        for (auto ite = range.first; ite != range.second; ++ite)
            returnList.emplace_back(ite->second); // 辞書で見つかったコンポーネントを返すリストに追加
        return returnList;
    }

    // コンポーネントを追加する .AddComponent<BoxCollider>(...)で追加できる
    template<class ComponentT, typename ...ArgsT>
    std::weak_ptr<ComponentT> AddComponent(ArgsT&&...args)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]

        // shared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        std::shared_ptr<ComponentT> newComponent = std::make_shared<ComponentT>(shared_from_this_as<GameObject>(), std::forward<ArgsT>(args)...);
       
        // https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/
        // std::unrodered_multimap<> と同型の値を取る std::pair<> を作って insert 関数でcomponents辞書に追加
        components.insert(std::pair<size_t, std::shared_ptr<ComponentT>>(hashID, newComponent));
        return newComponent;
    }

    // コンポーネントを削除する
    template<typename ComponentT>
    bool RemoveComponent(std::shared_ptr<ComponentT> removeComponent)
    {   //★[ComponentTのハッシュコードを得るテク] https://cpprefjp.github.io/reference/typeindex/type_index/hash_code.html
        size_t hashID = typeid(ComponentT).hash_code(); //[まずハッシュでID化]
        // equal_range 関数を使って、指定したキーの最初と終端の位置を示すイテレータを std::pair<> で取得します。
        auto range = components.equal_range(hashID); //https://ez-net.jp/article/9E/DFLX8hNG/G5J8DPmsltz4/

        //[イテレート中に探しながら消す] https://cpprefjp.github.io/reference/unordered_map/unordered_multimap/erase.html
        for (auto ite = range.first; ite != range.second;)
            if (ite->second == removeComponent)
            {
                components.erase(ite); // 削除された要素の次を指すイテレータが返される
                return true; // 見つかって消せた場合は true
            }
            else
                ++ite; // 次の要素へイテレータを進める

        return false; // 見つからずに消せなかった場合は false
    }

    // 更新処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Update() = 0; // 純粋仮想関数 = 0 にすると【絶対 Update()関数はoverrideしなきゃいけない義務を付与できる 】

    // 描画処理[純粋仮想関数 = 0 なのでoverride必須の関数]
    virtual void Draw() = 0; // 純粋仮想関数 = 0 にすると【絶対 Draw()関数はoverrideしなきゃいけない義務を継承先クラスに付与 】

    // 衝突開始したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollisionEnter(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突開始したときの処理などをオブジェクトの種類ごとに実装する
    };

    // 衝突したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollision(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    };

    // 衝突終了したときの関数 overrideして衝突したときの処理などをオブジェクトの種類ごとに実装する
    virtual void OnCollisionExit(std::shared_ptr<Collision> pCollision)
    {
        // overrideして衝突終了したときの処理などをオブジェクトの種類ごとに実装する
    };

    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump()
    {
        // overrideしてジャンプした瞬間に音を鳴らしたりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中でジャンプの上昇中に呼ばれる関数
    virtual void OnJumping()
    {
        // overrideして空中でジャンプの上昇中にエフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // 空中で落ちている最中に呼ばれる関数
    virtual void OnFalling()
    {
        // overrideして空中で落ちている最中に音を鳴らしたり、エフェクトを出したりする処理などをオブジェクトの種類ごとに実装する
    };

    // タイルに当たったときのコールバック
    virtual void OnHitTile(HitPart hitPart, HitBit hitPartBit, TileBit checkBit, float xLeft, float xMiddle, float xRight,
        float yBottom, float yMiddle, float yTop, float zBack, float zMiddle, float zForward,
        unsigned int* pTerrainID = nullptr, int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, TileBit* pMatchBits = nullptr)
    {
        // 継承 overrideしてタイルに当たったときの処理を実装する
    }

    // 継承 overrideしてGameObjectのもつ個別のダメージの値を返す( otherの種類などでダメージ計算式をカスタムできる )
    virtual float Damage(std::shared_ptr<GameObject> other) { return 0.0f; }
    // 継承 overrideして個別のダメージの値を返す
    virtual float Damage() { return 0.0f; }
   
    virtual void OnDamage(std::shared_ptr<GameObject> other)
    {
        // overrideしてダメージを受けた時の処理を個別に実装
    }

   
    virtual void OnItem(std::shared_ptr<Item> pItem)
    {
        // overrideしてアイテムに触れたときのコールバック処理を個別に実装
    }


    virtual void OnMoney(int getMoney)
    {
        // overrideしてアイテムに触れたときのコールバック処理を個別に実装
    }


};

#endif



ItemBullet.hを新規作成して、ステージ上のエネルギー弾などのアイテムとそれを使ったときのアクションを準備しましょう。

#ifndef ITEM_BULLET_H_
#define ITEM_BULLET_H_

#include "Item.h"
#include "ItemAction.h"
#include "Resource.h"
#include "DataJson.h"
#include "GridCollidersComponent.h"
#include "Factory.h"
#include "Player.h"
#include "CollisionParams.h"

// アイテムの情報のクラス 継承override して情報をjsonなどから受け取る
struct ItemBulletInfo : public ItemInfo
{
    //int bulletId{ 0 }; // 弾の種類
    int count{ -1 }; // 弾を撃てる数のカウントアップ値( -1のときは一度取得したら無限に撃てるタイプのアイテム )
    float colliderRadius{ 8.0f }; // アイテムとしてのコライダの半径
    float bulletRadius{ 16.0f }; // 撃つ際の弾としてのコライダの半径
    float bulletDamage{ 1 }; // 弾の与えるダメージ
    float bulletSpeed{ 10.0f }; // 弾のスピード
    std::shared_ptr<CollisionParams> collisionParams; // 衝突関係の物理パラメータ
    std::weak_ptr<GameObject> shooter; // 弾を撃った後に弾の撃ち手の弱共有ポインタのリンクをBulletに渡す
    float shooterAngleY{ 0.0f }; // 撃ち手の撃った際の角度
    int imageIndex{ -1 }; // 弾の画像が分割画像だったらどの位置のIDの画像か
    int iconImageIndex{ -1 }; // 弾のアイコン画像が分割画像だったらどの位置のIDの画像か
    std::string imagePath{ "" }; // 弾の画像のパス
    std::string iconImagePath{ "" }; // 弾のアイテムのアイコン画像のパス
    inline ItemBulletInfo(std::shared_ptr<InitParams> initParams = nullptr)
        : ItemInfo{ initParams }
    {
        auto tmxParams = std::static_pointer_cast<InitTMXProperty>(initParams);
        if (tmxParams->properties == nullptr) return;
        auto tmxProperties = tmxParams->properties;
        // tmxのjsonの追加プロパティからデータを読み取る
        if (tmxProperties->count("pushPower") > 0 || tmxProperties->count("collision_e") > 0)
            collisionParams = std::make_shared<CollisionParams>(initParams); // 物理パラメータを読み取り

        auto itr = tmxProperties->find("itemTag");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の itemTag のタイプが Stringでない!jsonのプロパティを見直してください");
            itemTag = itr->second.stringValue;
        }

        itr = tmxProperties->find("count");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の count のタイプが Intでない!jsonのプロパティを見直してください");
            count = itr->second.intValue;
        }

        itr = tmxProperties->find("imagePath");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の imagePath のタイプが Stringでない!jsonのプロパティを見直してください");
            imagePath = itr->second.stringValue;
        }

        itr = tmxProperties->find("imageIndex");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の imageIndex のタイプが Intでない!jsonのプロパティを見直してください");
            imageIndex = itr->second.intValue;
        }

        itr = tmxProperties->find("iconImagePath");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の iconImagePath のタイプが Stringでない!jsonのプロパティを見直してください");
            iconImagePath = itr->second.stringValue;
        }

        itr = tmxProperties->find("iconImageIndex");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の iconImageIndex のタイプが Intでない!jsonのプロパティを見直してください");
            iconImageIndex = itr->second.intValue;
        }


        itr = tmxProperties->find("colliderRadius");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の colliderRadius のタイプが Floatでない!jsonのプロパティを見直してください");
            colliderRadius = itr->second.floatValue;
        }

        itr = tmxProperties->find("bulletRadius");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の bulletRadius のタイプが Floatでない!jsonのプロパティを見直してください");
            bulletRadius = itr->second.floatValue;
        }

        itr = tmxProperties->find("bulletSpeed");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の bulletSpeed のタイプが Floatでない!jsonのプロパティを見直してください");
            bulletSpeed = itr->second.floatValue;
        }

        itr = tmxProperties->find("bulletDamage");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の bulletDamage のタイプが Floatでない!jsonのプロパティを見直してください");
            bulletDamage = itr->second.floatValue;
        }

    }
    virtual ~ItemBulletInfo() {}
};


class ItemBulletAction : public ItemAction
{
protected:
    inline ItemBulletAction(ItemListComponent* pItemList, std::shared_ptr<GameObject> pOwner)
        : ItemAction(pItemList, pOwner)
    {
        this->baseTag = "ItemBulletAction";
    }
public:
    virtual ~ItemBulletAction() {}

    inline virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        auto bulletInfo = std::static_pointer_cast<ItemBulletInfo>(initParams);
        m_ItemInfo = bulletInfo;

        if (bulletInfo->iconImagePath != "")
        {
            m_pIconImage = Resource::at<Texture>(bulletInfo->iconImagePath);
            m_iconImageIndex = bulletInfo->iconImageIndex;
        }
        else if (bulletInfo->imagePath != "")
        {
            m_pIconImage = Resource::at<Texture>(bulletInfo->imagePath);
            m_iconImageIndex = bulletInfo->imageIndex;
        }

        // 弾の数を増やす(-1のときは無限撃ち可能、マイナス指定のときは弾の数を減らす)
        AddBullet(bulletInfo->count);
    }

    // 弾の数を増やす(-1のときは無限撃ち可能、マイナス指定のときは弾の数を減らす)
    void AddBullet(int newBulletCount)
    {
        if (m_count != -1) // -1のときは無限に撃てる状態だから無理に変えない
            if (newBulletCount == -1)
                m_count = -1; // -1にすると無限に撃てる状態
            else
            {
                m_count += newBulletCount;
                if (m_count < 0) m_count = 0;
            }
    }

    int m_count{ 0 }; // 弾の残数
    std::shared_ptr<Texture> m_pIconImage; // アイコン画像への共有ポインタ
    int m_iconImageIndex{ -1 }; // アイコン画像が分割画像だったときの画像位置ID

    // 例えば、すでに取得したプレイヤの弾と同じ種類の弾のアイテムをゲットしたら、
    // アイテムとしてダブらせるのではなく使用回数を増やす処理をこの関数をoverrideしてやる
    virtual void AddSameItem(std::shared_ptr<InitParams> initParam)
    {
        auto params = std::static_pointer_cast<ItemBulletInfo>(initParam);
        // 弾の数を増やす(-1のときは無限撃ち可能、マイナス指定のときは弾の数を減らす)
        AddBullet(params->count);
    }

    virtual void Update(GameObject* obj = nullptr) override
    {
        if (m_count == 0) return;
        auto pOwner = owner();
        if (pOwner == nullptr) return;

        auto selecting = m_items->Selecting();
        auto pWorld = pOwner->world();
        if (pOwner->baseTag == "Player" && selecting != nullptr && selecting == shared_from_this_as<ItemAction>())
        {
            auto pPlayer = std::static_pointer_cast<Player>(pOwner);
            // ボタン押下で自機弾を発射
            if (Input::GetButtonDown(pPlayer->pad, PAD_INPUT_C) // コントローラのCボタン(キーボードのCキー)を押したら
                || (pPlayer->pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_T))) // プレイヤ2はキーボードのTを押したら
            {
                auto bulletInfo = std::static_pointer_cast<ItemBulletInfo>(m_ItemInfo);
                bulletInfo->shooter = pPlayer; // 撃ち手の弱共有ポインタをInfoに設定してBulletに渡す
                bulletInfo->shooterAngleY = pPlayer->moveAngle + 90; // 撃ち手の角度をInfoに渡す
                pWorld->bullets.emplace_back(Factory::MakeSharedObject<Bullet>("Bullet", bulletInfo, pWorld, pPlayer->position));
                if (m_count != -1) // -1のときは∞なので減らさない
                    --m_count; // 弾の数を減らす
            }
            if (m_count == 0) // 弾の数が0になったらアイテムのアクションのリストからも削除
                m_items->RemoveAction(shared_from_this_as<ItemAction>());
        }
    }

    // アイコンを描く
    virtual Vector3 DrawIcon(const Vector3& drawPos, const Vector3& limitPos, int nextIntervalX = 30) override
    {
        Vector3 nextPos{ drawPos };
        if (m_pIconImage != nullptr)
        {
            int iconWidth = m_pIconImage->m_XSize, iconHeight = m_pIconImage->m_YSize;
            int widthUI = m_items->heightUI * iconWidth / iconHeight;
            int heightUI = m_items->heightUI;
            int frameWidth = m_items->frameWidth;
            unsigned int frameColor = m_items->frameColor;
            int iconIndex = (m_iconImageIndex != -1 && m_iconImageIndex < m_pIconImage->m_handles.size()) ? m_iconImageIndex : 0;
            int iconHandle = (m_pIconImage->m_handles.size() > 0) ? m_pIconImage->m_handles[iconIndex] : -1;
            if (limitPos.x < drawPos.x + widthUI + nextIntervalX)
                return nextPos; // 描いたら limitPosの位置を超えちゃうときは描かない
            // アイテムのアイコンのフレームの枠を描く
            DxLib::DrawBox(drawPos.x - frameWidth, drawPos.y - frameWidth, drawPos.x + widthUI + frameWidth, drawPos.y + heightUI + frameWidth, frameColor, TRUE);
            DxLib::DrawBox(drawPos.x, drawPos.y, drawPos.x + widthUI, drawPos.y + heightUI, GetColor(0, 0, 0), TRUE);
            // アイテムのアイコン画像を描く
            DxLib::DrawRotaGraph(drawPos.x + widthUI / 2, drawPos.y + heightUI / 2, 1.0, 0.0, iconHandle, TRUE);
            // アイテムの残りの数を表示する
            std::string str{ (m_count == -1) ? "∞" : std::to_string(m_count) };
            int fontHeight = DxLib::GetFontSize();
            DxLib::DrawString(drawPos.x + widthUI + 2, drawPos.y + heightUI - fontHeight, str.c_str(), GetColor(255, 255, 255));
            nextPos = Vector3{ drawPos.x + widthUI + nextIntervalX, drawPos.y, 0 }; // 次のアイコンのずらし位置を計算
        }
        return nextPos;
    }
};


// プレイヤの撃つ弾のアイテムのクラス
class ItemBullet : public Item
{
protected:
    // コンストラクタ
    ItemBullet(World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 })
        : Item(world, position, rotation)
    {
        this->typeTag = "ItemBullet";
    }
public:
    virtual ~ItemBullet() {}

    // 継承overrideしてアイテムを取った時にアイテム一覧で選択できるアクションを持つかをtrueかfalseで返す
    virtual bool hasAction() override { return true; }

    std::shared_ptr<Texture> m_pImage{ nullptr }; // 画像テクスチャへの共有リンク +1
    int m_imageIndex{ -1 }; // 分割画像だったら表示させたい画像番号
    std::shared_ptr<GridCollidersComponent> gridCollision; // 当たり判定用のグリッドのコンポーネント
    std::shared_ptr<SphereCollider> sphereCollider; // 球体コライダ

    // Initをoverrideしてアイテムの情報をjsonなどから受け取っておく
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        auto itemBulletInfo = std::make_shared<ItemBulletInfo>(initParams); // アイテム情報を持っておく
        m_ItemInfo = itemBulletInfo;
        m_pImage = Resource::MakeShared<Texture>(itemBulletInfo->imagePath);

        // 球体コライダ
        sphereCollider = std::make_shared<SphereCollider>(shared_from_this_as<ItemBullet>(), Vector3(0, 0, 0), itemBulletInfo->colliderRadius);
        gridCollision = std::make_shared<GridCollidersComponent>(shared_from_this_as<ItemBullet>(), "ItemBullet");
        gridCollision->LinkCollider(sphereCollider); // コライダをリンクする
    }

    // プレイヤの弾の画像のハンドル を 事前にItemInfo から読み取ったtextureから返す(画像番号があるときはindexで返される)
    std::shared_ptr<Texture> image(int& index) { if (m_imageIndex >= 0) index = m_imageIndex; return m_pImage; }

    // 更新処理を override
    virtual void Update() override
    {

    }

    // 描画処理を override
    virtual void Draw() override
    {
        if (world() == nullptr) return;

        // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
        world()->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                int index = 0; // 画像番号
                auto pImage = image(index); // 画像の共有リンク(indexで画像番号を受け取る)
                float radius = (sphereCollider != nullptr) ? sphereCollider->m_radius : 8; // コライダの半径(なかったら8)
                float ExRate = radius / pImage->m_XSize; // 画像の拡大率
                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, ExRate, angle, *pImage, (index == -1) ? 0 : index, TRUE);
#ifdef _DEBUG
                if (sphereCollider != nullptr) sphereCollider->Draw();
#endif
            });
    }

    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override
    {
        Item::OnCollision(pCollision); // ベース基底の共通処理のOnCollisionを呼ぶ
    }

    // アイテムを取ったときの処理を overrideして実装
    virtual void OnItem(std::shared_ptr<Item> pItem) override
    {
        if (pItem == shared_from_this_as<Item>())
        {   // 自分自身からOnItemが呼ばれた場合はこちら
            isDead = true; // 取られたら消える
        }
    }
};

#endif



ItemBlock.hを新規作成して、ステージ上のレンガなどブロックのアイテムとそれを使ったときのアクションを準備しましょう。

#ifndef ITEM_BLOCK_H_
#define ITEM_BLOCK_H_

#include "Item.h"
#include "ItemAction.h"
#include "Resource.h"
#include "TMXJson.h"
#include "DataTsx.h"
#include "GridCollidersComponent.h"
#include "Factory.h"
#include "Player.h"
#include "Map.h"

// アイテムの情報のクラス 継承override して情報をjsonなどから受け取る
struct ItemBlockInfo : public ItemInfo
{
    unsigned int gid{ 0 }; // タイルのgid
    int count{ -1 }; // アイテム数のカウントアップ値( -1のときは一度取得したら無限に撃てるタイプのアイテム )
    int cost{ 1 }; // 経路探索のコスト
    float colliderRadius{ 8.0f }; // アイテムとしてのコライダの半径
    std::weak_ptr<GameObject> shooter; // 撃ち手の弱共有ポインタのリンクをBulletに渡す
    float shooterAngleY{ 0.0f }; // 撃ち手の撃った際の角度
    int imageIndex{ -1 }; // 画像が分割画像だったらどの位置のIDの画像か
    //int iconImageIndex{ -1 }; // アイコン画像が分割画像だったらどの位置のIDの画像か
    std::string imagePath{ "" }; // 画像のパス
    //std::string iconImagePath{ "" }; // アイテムのアイコン画像のパス
    inline ItemBlockInfo(std::shared_ptr<InitParams> initParams = nullptr)
        : ItemInfo{ initParams }
    {
        auto tmxParams = std::static_pointer_cast<InitTMXProperty>(initParams);
        if (tmxParams->properties == nullptr) return;
        auto tmxProperties = tmxParams->properties;
        // tmxのjsonの追加プロパティからデータを読み取る
        auto itr = tmxProperties->find("itemTag");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の itemTag のタイプが Stringでない!jsonのプロパティを見直してください");
            itemTag = itr->second.stringValue;
        }

        itr = tmxProperties->find("count");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の count のタイプが Intでない!jsonのプロパティを見直してください");
            count = itr->second.intValue;
        }

        itr = tmxProperties->find("imagePath");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の imagePath のタイプが Stringでない!jsonのプロパティを見直してください");
            imagePath = itr->second.stringValue;
        }

        itr = tmxProperties->find("imageIndex");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の imageIndex のタイプが Intでない!jsonのプロパティを見直してください");
            imageIndex = itr->second.intValue;
        }

        if (imageIndex >= 0) // id が 0以上なら
        {
            Tile tile = Map::GetTile(imagePath, imageIndex, gid);
            cost = tile.cost;
        }

        itr = tmxProperties->find("colliderRadius");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の colliderRadius のタイプが Floatでない!jsonのプロパティを見直してください");
            colliderRadius = itr->second.floatValue;
        }
    }
    virtual ~ItemBlockInfo() {}
};

// アイテムでブロックを選択中のアクション
class ItemBlockAction : public ItemAction
{
protected:
    inline ItemBlockAction(ItemListComponent* pItemList, std::shared_ptr<GameObject> pOwner)
        : ItemAction(pItemList, pOwner)
    {
        this->baseTag = "ItemBulletAction";
    }
public:
    virtual ~ItemBlockAction() {}

    inline virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        auto blockInfo = std::static_pointer_cast<ItemBlockInfo>(initParams);
        m_ItemInfo = blockInfo;
        if (blockInfo->imagePath != "")
        {
            m_pIconImage = Resource::at<Texture>(blockInfo->imagePath);
            m_iconImageIndex = blockInfo->imageIndex;
        }

        // ブロックの数を増やす(-1のときは無限撃ち可能、マイナス指定のときは弾の数を減らす)
        AddBlock(blockInfo->count);
    }

    // ブロックの数を増やす(-1のときは無限置き可能、マイナス指定のときはブロックの数を減らす)
    void AddBlock(int newBlockCount)
    {
        if (m_count != -1) // -1のときは無限に置ける状態だから無理に変えない
            if (newBlockCount == -1)
                m_count = -1; // -1にすると無限に置ける状態
            else
            {
                m_count += newBlockCount;
                if (m_count < 0) m_count = 0;
            }
    }

    int m_count{ 0 }; // ブロックの残数
    std::shared_ptr<Texture> m_pIconImage; // アイコン画像への共有ポインタ
    int m_iconImageIndex{ -1 }; // アイコン画像が分割画像だったときの画像位置ID

    // 例えば、すでに取得したプレイヤのブロックと同じ種類のブロックのアイテムをゲットしたら、
    // アイテムとしてダブらせるのではなく使用回数を増やす処理をこの関数をoverrideしてやる
    virtual void AddSameItem(std::shared_ptr<InitParams> initParam)
    {
        auto params = std::static_pointer_cast<ItemBlockInfo>(initParam);
        // ブロックの数を増やす(-1のときは無限置き可能、マイナス指定のときはブロックの数を減らす)
        AddBlock(params->count);
    }

    // ボタンを押したときにブロックを置く処理
    virtual void Update(GameObject* obj = nullptr) override
    {
        if (m_count == 0) return;
        auto pOwner = owner();
        if (pOwner == nullptr) return;

        auto selecting = m_items->Selecting();
        auto pWorld = pOwner->world();
        if (pOwner->baseTag == "Player" && selecting != nullptr && selecting == shared_from_this_as<ItemAction>())
        {
            auto pPlayer = std::static_pointer_cast<Player>(pOwner);
            // ボタン押下でステージにタイルを配置する
            if (Input::GetButtonDown(pPlayer->pad, PAD_INPUT_C) // コントローラのCボタン(キーボードのCキー)を押したら
                || (pPlayer->pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_T))) // プレイヤ2はキーボードのTを押したら
            {
                auto blockInfo = std::static_pointer_cast<ItemBlockInfo>(m_ItemInfo);
                blockInfo->shooter = pPlayer; // 置き手の弱共有ポインタをInfoに設定
                blockInfo->shooterAngleY = pPlayer->moveAngle + 90; // 置き手の角度をInfoに渡す
                float cellSize = pWorld->map->CellSize();

                float putX = (float)std::cos((pPlayer->moveAngle + 90) * MyMath::Deg2Rad) * cellSize;
                float putZ = (float)std::sin((pPlayer->moveAngle + 90) * MyMath::Deg2Rad) * cellSize;

                float writeX = pPlayer->position.x + putX; // 地形を書き換えるワールド位置
                float writeY = pPlayer->position.y;
                float writeZ = pPlayer->position.z + putZ;
                unsigned int gid = blockInfo->gid; // 書き換える gid
                bool isWall = pWorld->map->IsWall(writeX, writeY, writeZ); // 書き換え位置が壁か
                int mapX, mapY, mapZ; unsigned int gid_now = 0; // checkID関数で読み出す数値
                TileBit matchBit;
                bool isMatch = pWorld->map->checkID(TileType::Wall | TileType::Floor, writeX, writeY, writeZ, &mapX, &mapY, &mapZ, &gid_now, &matchBit);
                if (matchBit & TileType::Wall) return; // マッチしたbitが壁か
                if (gid_now != 0 && gid_now == gid) return; // 書き換え先の現在のgidがこれから書き換えるgidと同じならアイテム数は減らさず return
                //if ((matchBit & TileType::Floor) && !(pWorld->map->matchTypes(gid, (TileBit)TileType::Floor) & TileType::Floor))
                //    return;

                // 地形を書き換え
                pWorld->map->SetTerrain(gid, writeX, writeY, writeZ);

                if (m_count != -1) // -1のときは∞なので減らさない
                    --m_count; // ブロックの数を減らす
            }
            if (m_count == 0) // ブロックの数が0になったらアイテムのアクションのリストからも削除
                m_items->RemoveAction(shared_from_this_as<ItemAction>());
        }
    }

    // アイコンを描く
    virtual Vector3 DrawIcon(const Vector3& drawPos, const Vector3& limitPos, int nextIntervalX = 30) override
    {
        Vector3 nextPos{ drawPos };
        if (m_pIconImage != nullptr)
        {
            int iconWidth = m_pIconImage->m_XSize, iconHeight = m_pIconImage->m_YSize;
            int widthUI = m_items->heightUI * iconWidth / iconHeight; // アイコンの実際の画像サイズの縦横比率×heightUI
            int heightUI = m_items->heightUI;
            int frameWidth = m_items->frameWidth;
            unsigned int frameColor = m_items->frameColor;
            int iconIndex = (m_iconImageIndex != -1 && m_iconImageIndex < m_pIconImage->m_handles.size()) ? m_iconImageIndex : 0;
            int iconHandle = (m_pIconImage->m_handles.size() > 0) ? m_pIconImage->m_handles[iconIndex] : -1;
            if (limitPos.x < drawPos.x + widthUI + nextIntervalX)
                return nextPos; // 描いたら limitPosの位置を超えちゃうときは描かない
            // アイテムのアイコンのフレームの枠を描く
            DxLib::DrawBox(drawPos.x - frameWidth, drawPos.y - frameWidth, drawPos.x + widthUI + frameWidth, drawPos.y + heightUI + frameWidth, frameColor, TRUE);
            DxLib::DrawBox(drawPos.x, drawPos.y, drawPos.x + widthUI, drawPos.y + heightUI, GetColor(0, 0, 0), TRUE);
            // アイテムのアイコン画像を描く
            DxLib::DrawRotaGraph(drawPos.x + widthUI / 2, drawPos.y + heightUI / 2, 1.0, 0.0, iconHandle, TRUE);
            // アイテムの残りの数を表示する
            std::string str{ (m_count == -1) ? "∞" : std::to_string(m_count) };
            int fontHeight = DxLib::GetFontSize();
            DxLib::DrawString(drawPos.x + widthUI + 2, drawPos.y + heightUI - fontHeight, str.c_str(), GetColor(255, 255, 255));
            nextPos = Vector3{ drawPos.x + widthUI + nextIntervalX, drawPos.y, 0 }; // 次のアイコンのずらし位置を計算
        }
        return nextPos;
    }
};


// ブロックのアイテムのクラス
// 壊したブロックなどを回収してステージに再配置できる
class ItemBlock : public Item
{
protected:
    // コンストラクタ
    ItemBlock(World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 })
        : Item(world, position, rotation)
    {
        this->typeTag = "ItemBlock";
    }
public:
    virtual ~ItemBlock() {}

    // 継承overrideしてアイテムを取った時にアイテム一覧で選択できるアクションを持つかをtrueかfalseで返す
    virtual bool hasAction() override { return true; }

    std::shared_ptr<Texture> m_pImage{ nullptr }; // 画像テクスチャへの共有リンク +1
    int m_imageIndex{ -1 }; // 分割画像だったら表示させたい画像番号
    std::shared_ptr<GridCollidersComponent> gridCollision; // 当たり判定用のグリッドのコンポーネント
    std::shared_ptr<SphereCollider> sphereCollider; // 球体コライダ

    // Initをoverrideしてアイテムの情報をjsonなどから受け取っておく
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        auto itemBlockInfo = std::make_shared<ItemBlockInfo>(initParams); // アイテム情報を持っておく
        m_ItemInfo = itemBlockInfo;
        m_pImage = Resource::MakeShared<Texture>(itemBlockInfo->imagePath);
        m_imageIndex = itemBlockInfo->imageIndex;

        // 球体コライダ
        sphereCollider = std::make_shared<SphereCollider>(shared_from_this_as<ItemBlock>(), Vector3(0, 0, 0), itemBlockInfo->colliderRadius);
        gridCollision = std::make_shared<GridCollidersComponent>(shared_from_this_as<ItemBlock>(), "ItemBlock");
        gridCollision->LinkCollider(sphereCollider); // コライダをリンクする
    }

    // プレイヤの弾の画像のハンドル を 事前にItemInfo から読み取ったtextureから返す(画像番号があるときはindexで返される)
    std::shared_ptr<Texture> image(int& id) { if (m_imageIndex >= 0) id = m_imageIndex; return m_pImage; }

    // ブロックの gid を ItemInfo から読み取って返す
    unsigned int gid()
    {
        if (m_ItemInfo == nullptr) return 0;
        return std::static_pointer_cast<ItemBlockInfo>(m_ItemInfo)->gid;
    }

    // 更新処理を override
    virtual void Update() override
    {

    }

    // 描画処理を override
    virtual void Draw() override
    {
        if (world() == nullptr) return;

        int id = 0; // 画像番号
        auto pImage = image(id); // 画像の共有リンク(indexで画像番号を受け取る)
        if (pImage == nullptr || id < 0) return;
        float radius = (sphereCollider != nullptr) ? sphereCollider->m_radius : 8; // コライダの半径(なかったら8)

        // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
        world()->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                float ExRate = radius / pImage->m_XSize; // 画像の拡大率
                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, ExRate, angle, *pImage, (id == -1) ? 0 : id, TRUE);
#ifdef _DEBUG
                if (sphereCollider != nullptr) sphereCollider->Draw();
#endif
            });
    }

    // [アイテムとして]フィールドに落ちているブロックと衝突したときの処理
    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override
    {
        Item::OnCollision(pCollision); // ベース基底の共通処理のOnCollisionを呼ぶ
    }

    // アイテムを取ったときの処理を overrideして実装
    virtual void OnItem(std::shared_ptr<Item> pItem) override
    {
        if (pItem == shared_from_this_as<Item>())
        {   // 自分自身からOnItemが呼ばれた場合はこちら
            isDead = true; // 取られたら消える
        }
    }
};

#endif



ItemMoney.hを新規作成して、フィールド上のお金のクラスを準備しましょう。

#ifndef ITEM_MONEY_H_
#define ITEM_MONEY_H_

#include "Item.h"
#include "ItemAction.h"
#include "Resource.h"
#include "DataJson.h"
#include "GridCollidersComponent.h"
#include "Factory.h"
#include "Player.h"

// アイテムの情報のクラス 継承override して情報をjsonなどから受け取る
struct ItemMoneyInfo : public ItemInfo
{
    int money{ 0 }; // 取得する金額
    float colliderRadius{ 8.0f }; // アイテムとしてのコライダの半径
    int imageIndex{ -1 }; // 画像が分割画像だったらどの位置のIDの画像か
    std::string imagePath{ "" }; // 画像のパス
    inline ItemMoneyInfo(std::shared_ptr<InitParams> initParams = nullptr)
        : ItemInfo{ initParams }
    {
        auto tmxParams = std::static_pointer_cast<InitTMXProperty>(initParams);
        if (tmxParams->properties == nullptr) return;
        auto tmxProperties = tmxParams->properties;
        // tmxのjsonの追加プロパティからデータを読み取る
        auto itr = tmxProperties->find("itemTag");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の itemTag のタイプが Stringでない!jsonのプロパティを見直してください");
            itemTag = itr->second.stringValue;
        }

        itr = tmxProperties->find("imagePath");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::String && "json の imagePath のタイプが Stringでない!jsonのプロパティを見直してください");
            imagePath = itr->second.stringValue;
        }

        itr = tmxProperties->find("imageIndex");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の imageIndex のタイプが Intでない!jsonのプロパティを見直してください");
            imageIndex = itr->second.intValue;
        }

        itr = tmxProperties->find("colliderRadius");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Float && "json の colliderRadius のタイプが Floatでない!jsonのプロパティを見直してください");
            colliderRadius = itr->second.floatValue;
        }

        itr = tmxProperties->find("money");
        if (itr != end(*tmxProperties))
        {
            assert(itr->second == TMXProperty::Type::Int && "json の colliderRadius のタイプが Intでない!jsonのプロパティを見直してください");
            money = itr->second.intValue;
        }
    }
    virtual ~ItemMoneyInfo() {}
};


class ItemMoneyAction : public ItemAction
{
protected:
    inline ItemMoneyAction(ItemListComponent* pItemList, std::shared_ptr<GameObject> pOwner)
        : ItemAction(pItemList, pOwner)
    {
        this->baseTag = "ItemMoneyAction";
    }
public:
    virtual ~ItemMoneyAction() {}

    std::shared_ptr<ItemMoneyInfo> m_moneyInfo; // アイテムの情報

    inline virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        m_moneyInfo = std::static_pointer_cast<ItemMoneyInfo>(initParams);
        if (m_moneyInfo->money != 0)
        {
        }
    }

    virtual void Update(GameObject* obj = nullptr) override
    {
    }

    virtual void Draw(GameObject* obj = nullptr) override
    {
    }
};


// お金のアイテムのクラス
class ItemMoney : public Item
{
protected:
    // コンストラクタ
    ItemMoney(World* world, Vector3 position, Quaternion rotation = { 0,0,0,1 })
        : Item(world, position, rotation)
    {
        this->typeTag = "ItemMoney";
    }
public:
    virtual ~ItemMoney() {}

    // 継承overrideしてアイテムを取った時にアイテム一覧で選択できるアクションを持つかをtrueかfalseで返す
    virtual bool hasAction() override { return false; }
    int m_money{ 0 };
    std::shared_ptr<Texture> m_pImage{ nullptr }; // 画像テクスチャへの共有リンク +1
    int m_imageIndex{ -1 }; // 分割画像だったら表示させたい画像番号
    std::shared_ptr<GridCollidersComponent> gridCollision; // 当たり判定用のグリッドのコンポーネント
    std::shared_ptr<SphereCollider> sphereCollider; // 球体コライダ

    // Initをoverrideしてアイテムの情報をjsonなどから受け取っておく
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        auto itemMoneyInfo = std::make_shared<ItemMoneyInfo>(initParams); // アイテム情報を持っておく
        m_ItemInfo = itemMoneyInfo;
        m_pImage = Resource::MakeShared<Texture>(itemMoneyInfo->imagePath);
        m_money = itemMoneyInfo->money; // お金の額の情報を得る
        // 球体コライダ
        sphereCollider = std::make_shared<SphereCollider>(shared_from_this_as<ItemMoney>(), Vector3(0, 0, 0), itemMoneyInfo->colliderRadius);
        gridCollision = std::make_shared<GridCollidersComponent>(shared_from_this_as<ItemMoney>(), "ItemMoney");
        gridCollision->LinkCollider(sphereCollider); // コライダをリンクする
    }

    // プレイヤの弾の画像のハンドル を 事前にItemInfo から読み取ったtextureから返す(画像番号があるときはindexで返される)
    std::shared_ptr<Texture> image(int& index) { if (m_imageIndex >= 0) index = m_imageIndex; return m_pImage; }

    // 更新処理を override
    virtual void Update() override
    {

    }

    // 描画処理を override
    virtual void Draw() override
    {
        if (world() == nullptr) return;

        // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
        world()->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                int index = 0; // 画像番号
                auto pImage = image(index); // 画像の共有リンク(indexで画像番号を受け取る)
                float radius = (sphereCollider != nullptr) ? sphereCollider->m_radius : 8; // コライダの半径(なかったら8)
                float ExRate = radius / pImage->m_XSize; // 画像の拡大率
                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, ExRate, angle, *pImage, (index == -1) ? 0 : index, TRUE);
#ifdef _DEBUG
                if (sphereCollider != nullptr) sphereCollider->Draw();
#endif
            });
    }

    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override
    {
        Item::OnCollision(pCollision); // ベース基底の共通処理のOnCollisionを呼ぶ
        auto otherCollider = pCollision->contacts[0].otherCollider.lock();
        if (otherCollider == nullptr) return;
        auto other = otherCollider->owner();
        other->OnMoney(m_money); // お金をゲットした時の処理を呼ぶ
    }

    // アイテムを取ったときの処理を overrideして実装
    virtual void OnItem(std::shared_ptr<Item> pItem) override
    {
        if (pItem == shared_from_this_as<Item>())
        {   // 自分自身からOnItemが呼ばれた場合はこちら
            isDead = true; // 取られたら消える
        }
    }
};

#endif


Bullet.hを新規作成して、エネルギー弾などの撃つ弾のクラスを準備しましょう。

#ifndef PLAYERBULLET_H_
#define PLAYERBULLET_H_

#include <cmath>

#include "DxLib.h"
#include "Screen.h"
#include "MyDraw.h"

#include "GameObject.h"

#include "Collider.h"
#include "GridCollidersComponent.h"
#include "ItemBullet.h"

class World; // 前方宣言

// 弾クラス
class Bullet : public GameObject
{
public:
    // プレイヤ弾と敵の当たり判定を空間分割Gridで高速化
    std::shared_ptr<GridCollidersComponent> gridCollision;
    std::shared_ptr<SphereCollider> sphereCollider;
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        if (initParams == nullptr) return;
        auto params = std::static_pointer_cast<ItemBulletInfo>(initParams); // アイテム情報を受け取る
        collisionParams = params->collisionParams; // 物理衝突パラメータを受け取る
        m_pImage = Resource::MakeShared<Texture>(params->imagePath);

        sphereCollider = std::make_shared<SphereCollider>(shared_from_this_as<Bullet>(), Vector3(0, 0, 0), params->bulletRadius);
        gridCollision = std::make_shared<GridCollidersComponent>(shared_from_this_as<Bullet>(), "Bullet");
        gridCollision->LinkCollider(sphereCollider); // コライダをリンクする
        m_shooter = params->shooter; // 弾の撃ち手の弱共有リンクを得ておく
        auto pShooter = m_shooter.lock();
        if (pShooter != nullptr)
        {   // 撃ち手のタグに応じて弾のタイプをPlayerBulletとEnemyBulletに切り分ける
            if (pShooter->baseTag == "Player")
                this->typeTag = "PlayerBullet";
            else if (pShooter->baseTag == "Enemy")
                this->typeTag = "EnemyBullet";
        }
        Speed = params->bulletSpeed;
        m_Damage = params->bulletDamage;

        vx = (float)std::cos(params->shooterAngleY * MyMath::Deg2Rad) * Speed;
        vz = (float)std::sin(params->shooterAngleY * MyMath::Deg2Rad) * Speed;

    }


    int deadCounter = 60 * 5; // 5秒後にisDead
    float Speed{ 10.0 }; // 移動速度
    float m_Damage{ 0 }; // 弾が当たった時に与えるダメージ
    float Damage() override { return m_Damage; }
protected:
    std::weak_ptr<GameObject> m_shooter; // 弾の撃ち手への弱共有ポインタ
public:
    // 弾の撃ち手への共有ポインタ(撃ち手がすでに死んでいた場合は nullptr)
    std::shared_ptr<GameObject> Shooter() { auto pShooter = m_shooter.lock(); return pShooter; }

    std::shared_ptr<Texture> m_pImage; // 画像のパス

    // コンストラクタ
    Bullet(World* world, Vector3 position, Quaternion rotation)
        : GameObject(world, position, rotation)
    {
        this->baseTag = "Bullet";
        //this->pushPower = 15.0f;
#if 0
        double angle = rotation.getPitch();
        if (auto a = angle / MyMath::Deg2Rad)
        {
            printfDx("%f\n", (float)a);
        }
        vx = (float)std::cos(angle) * Speed;
        vz = (float)std::sin(angle) * Speed;
#endif
    }

    // デストラクタ
    virtual ~Bullet()
    {
    }

    // 更新処理
    void Update()
    {

        // 移動
        x += vx;
        z += vz;

        deadCounter--;
        if (deadCounter < 0) isDead = true; // 一定の秒数がたったら消す

        if (gridCollision != nullptr)
            gridCollision->Update(); // 所属するマス目セルを更新する
    }

    // 描画処理
    void Draw()
    {
        if (world() == nullptr) return;

        // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
        world()->Draw([&]()
            {

                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *m_pImage, 0, TRUE);
#ifdef _DEBUG
                if (sphereCollider != nullptr) sphereCollider->Draw();
#endif
            });
    }

    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override
    {
        if (isDead) return; // すでにisDeadだったらreturn
        auto otherCollider = pCollision->contacts[0].otherCollider.lock();
        if (otherCollider == nullptr) return;
        auto other = otherCollider->owner();
        if (other->isDead) return; // other側がisDeadだったら処理をスルー

        if (typeTag == "PlayerBullet" && other->baseTag != "Enemy") return; // Enemyタイプ以外はスルー
        if (typeTag == "EnemyBullet" && other->baseTag != "Player") return; // Playerタイプ以外はスルー

        if (other->typeTag == "Zako1")
        {   //名前タグでぶつかった相手方を判別できる

        }

        if (m_Damage != 0)
        {
            other->OnDamage(shared_from_this_as<Bullet>());
        }
        //isDead = true;
    }
};


#endif




Factory.cppを変更して、Item系やBulletなどの初期化コンストラクタ発動して生成できるようにしましょう。

#include "Factory.h"

#include <unordered_map> //★ #文字列 マクロで<クラス名→コンストラクタ>をビルドする瞬間に 辞書に登録

// new や std::make_shared1 や Createで初期生成するには
// #includeしてコンストラクタを呼び出せる必要が出てくる
#include "Enemy.h"
#include "Zako0.h"
//#include "Zako1.h"
//#include "Zako2.h"
//#include "Zako3.h"
//#include "Boss.h"

#include "Item.h"
#include "ItemBlock.h"
#include "ItemBullet.h"
#include "ItemMoney.h"

#include "Bullet.h"

#include "World.h"

//【上級1】★以下サイトを参考に【文字列"Zako1"とかからクラスを初期化するテクニック】
// https://stackoverflow.com/questions/5447675/how-to-convert-a-string-to-a-class-name/5447776
//
// 以下のようにクラスの型タイプを書かずに初期化できるので★if else文をいちいちコピーして増やさずに済む
// 例. いちいち書く例
// if (gid == 0)     return Factory::make_shared<Zako0>(x + 32, y + 32);
// else if(gid == 1) return Factory::make_shared<Zako1>(x + 32, y + 32);
//  .......(敵の種類が増えるたびにいちいちif elseコピペで書かないと..)
// ↓
// ↓★"Zako1"の文字列で初期化できれば1行で書けるようになる余地が出てくる!(CSVのクラス名の文字から初期化も目指せる)
// ↓
// 例. 初期化に<Zako1>を使わずに済むようになる "Zako1"で書ける
// auto enemy = g_ClassFactory.Construct("Zako1", x + 32, y + 32);//←"Zako1"で初期化 <Zako1>だとコードに書くしかない


// クラス名 classTypeName 例. Zako0 のコンストラクタを s_umConstructors 辞書に登録するための↓#文字取得マクロ
#define REGISTER_CONSTRUCTOR(classTypeName) RegisterConstructor<classTypeName>( #classTypeName )

// Factoryクラスの「依存性の逆転」を引き起こすには.hでは★#includeしたくない https://blog.ecbeing.tech/entry/2021/01/20/114000
// だが、テンプレート関数は普通.hに実装までする必要があるが、それを回避する方法もある https://qiita.com/i153/items/38f9688a9c80b2cb7da7
// .cppでtemplate関数の実体を.hと分けて定義するには「★最小限下記の定義をオブジェクトごとにする必要」がある
template std::shared_ptr<Object> Factory::ConstructByClassName<Object>(const std::string&, std::shared_ptr<InitParams>, World*, Vector3, Quaternion);
template std::shared_ptr<Enemy> Factory::ConstructByClassName<Enemy>(const std::string&, std::shared_ptr<InitParams>, World*, Vector3, Quaternion);
template std::shared_ptr<Item> Factory::ConstructByClassName<Item>(const std::string&, std::shared_ptr<InitParams>, World*, Vector3, Quaternion);
template std::shared_ptr<Bullet> Factory::ConstructByClassName<Bullet>(const std::string&, std::shared_ptr<InitParams>, World*, Vector3, Quaternion);



namespace // 無名名前空間(他ファイルから参照できない変数や関数を宣言)
{
    bool isConstructorRegistered{ false }; // 初回だけクラスのコンストラクタ登録を1回だけするためのフラグ

    // 初回だけ Zako0 などのクラスのコンストラクタを辞書に登録を1回だけする
    inline void InitConstructors()
    {
        if (!isConstructorRegistered) // 初回だけクラスのコンストラクタ登録を1回だけする
        {    //クラス登録されていないなら初回登録して "文字列"→コンストラクタ を呼び出せるようにしておく
            REGISTER_CONSTRUCTOR(Zako0); // Zako0のコンストラクタを辞書に登録しておく
            //REGISTER_CONSTRUCTOR(Zako1);
            //REGISTER_CONSTRUCTOR(Zako2);
            //REGISTER_CONSTRUCTOR(Zako3);
            //REGISTER_CONSTRUCTOR(Boss);
            REGISTER_CONSTRUCTOR(ItemBullet);
            REGISTER_CONSTRUCTOR(ItemBlock);
            REGISTER_CONSTRUCTOR(ItemMoney);

            REGISTER_CONSTRUCTOR(Bullet);

            isConstructorRegistered = true;
        }
    }
}

void Factory::Init(const std::string& class_name)//int id)
{
    // 初回だけ Zako0 などのクラスのコンストラクタを辞書に登録を1回だけする
    InitConstructors();

    // 辞書に class_name が登録されているかチェック
    assert(g_Constructors.count(class_name) != 0 && "まだ登録してない クラス名 が Factory.cpp にありました。");
}

// class_name に応じて敵などのオブジェクトを生成してreturnで返す
template <typename TypeT> //[templateを.hと.cppに分けて書くのが難しい] https://qiita.com/i153/items/38f9688a9c80b2cb7da7
std::shared_ptr<TypeT> Factory::ConstructByClassName(const std::string& class_name, std::shared_ptr<InitParams> initParams, World* world, Vector3 position, Quaternion rotation)
{
    // class_nameに応じて敵などのオブジェクトを生成してreturnで返す
    // ★Zako1*でもBoss*でもOK、ベース基底のObject型であいまいな状態で受け取る
    auto pObject = Construct(class_name, initParams, world, position, rotation);
    return std::dynamic_pointer_cast<TypeT>(pObject); // タイプをキャストでTypeTで指定した型に変換して返す
}



Player.hを変更して、所持しているアイテムを更新し描画する処理を追加しましょう。

#ifndef PLAYER_H_
#define PLAYER_H_

#include "DxLib.h"
#include "Input.h"
#include "Resource.h"
#include "GameObject.h"
#include "ItemAction.h" // 複数アイテムを所持しItemActionを継承して使うためのコンポーネント
#include "PhysicsComponent.h"
#include "MapHitBoxComponent.h"
#include "GridCollidersComponent.h"
#include "Collider.h"

class World; // 前方宣言
class Camera; // 前方宣言

class Player : public GameObject
{
protected:
    // コンストラクタはprotected:にしてあるので、Create関数経由で初期生成してください
    Player(World* world, Pad pad, Vector3 position, Quaternion rotation = { 0,0,0,1 }, Vector3 velocity = { 0,0,0 }, Vector3 force = { 0,0,0 })
        : pad{ pad }, GameObject(world, position, rotation, velocity, force)
    {
        this->typeTag = "Player"; // タグは個別の"プレイヤ1"などに使い、タイプはtypeTagで判別する形にしてタイプのジャンルを区別
        this->baseTag = "Player";
        this->pushPower = 5.0f; // コライダで衝突したときに押し返す力
    }

public:
    // 仮想デストラクタ
    virtual ~Player() {}

    Pad pad; // 操作するコントローラー番号
    hash32 worldTag{ "" }; // 現在いるワールドのタグ
    int money{ 0 }; // ゲームのお金
    float MoveSpeedMax = 6; // 移動速度Max値
    float MoveSpeed = 0;//移動速度

    float moveAngle = 0; // X軸→方向から何度か
    float deltaAngle = 0; // プレイヤの動く角度の変化率、ハンドルを切ったらだんだんもとに戻る
    int animCount = 0;

    std::shared_ptr<Texture> image; // プレイヤの板ポリゴンで描くタイル分割画像への共有リンク

    Camera* camera{ nullptr }; // プレイヤを追随するカメラ
    void SetCamera(Camera* pCamera) { camera = pCamera; }

    // overrideして物理落下コンポーネントからの重力値を返す
    virtual float gravity() override { auto pGravityPhysics = gravityPhysics.lock(); return (pGravityPhysics != nullptr) ? pGravityPhysics->gravity() : 0.0f; }
protected:
    std::weak_ptr<JumpComponent> jumpPhysics; // ジャンプの物理処理コンポーネント
    std::weak_ptr<GravityComponent> gravityPhysics; // 落下の重力の物理処理コンポーネント
    // マップ地形に対するヒットボックスの当たり判定と押し戻しをするコンポーネント
    std::weak_ptr<MapHitBoxComponent> mapHitBox;

    std::weak_ptr<GridCollidersComponent> gridCollision; // グリッドマス目分割コンポーネント
    std::weak_ptr<SphereCollider> sphereCollider; // 球コライダのコンポーネント

    std::weak_ptr<ItemListComponent> itemList; // アイテムの所持リスト
public:
    virtual void Init(std::shared_ptr<InitParams> initParams = nullptr) override
    {
        std::vector<float> vyJumpSpeed{ 13, 14, 15, 4 }; // ジャンプ開始の初期速度
        std::vector<float> vyForceJump{ 0.5f, 0.4f, 0.3f, 0.1f }; // ジャンプ上昇にかかる力の初期値
        std::vector<float> vyGravity{ 0.8f,0.8f,0.8f,0.8f }; // 重力の設定値
        std::vector<float> vyDownSpeedMax{ 16, 16, 16, 8 }; // 降下スピードのリミット
        // AddComponent内部でshared_from_this()を必ず渡してセットで初期化して、オーナーがリンク切れのコンポーネントができないように気をつける
        jumpPhysics = AddComponent<JumpComponent>(vyJumpSpeed, vyForceJump);
        gravityPhysics = AddComponent<GravityComponent>(vyGravity, vyDownSpeedMax);
        // 半径16(直径32)の球体コライダ
        sphereCollider = AddComponent<SphereCollider>(Vector3(0, 0, 0), 16);
        gridCollision = AddComponent<GridCollidersComponent>("Player"); //とりあえずわかりやすいように"Player"というタグをつけておく(別に何でもいい)
        gridCollision.lock()->LinkCollider(sphereCollider); // コライダをリンクする

        float mapHitBoxSize = sphereCollider.lock()->m_radius * 2.0f; // 球体コライダの直径に合わす
        Vector3 boxSize{ mapHitBoxSize,mapHitBoxSize,mapHitBoxSize };
        Vector3 offsetLeftBottomBack{ 3, 0, 3 }; // ボックスの端を内側にどれだけ削るか(キャラの画像に合わせて小さくできる)
        Vector3 offsetRightTopForward{ 3, 8, 3 }; // ボックスの端を内側にどれだけ削るか(頭を8削る)
        mapHitBox = AddComponent<MapHitBoxComponent>(boxSize, offsetLeftBottomBack, offsetRightTopForward);

        itemList = AddComponent<ItemListComponent>();
    }

    // 入力を受けての処理
    virtual void HandleInput();

    // 弾を撃ったりブロックの配置などアイテムのインプット操作を受け取る
    void HandleItemInput();


    // 更新処理
    virtual void Update() override;

    // 描画処理
    virtual void Draw() override;

    // UIの描画処理はDrawとは別にすることで、一番最後にスクリーンに上書きで描ける
    virtual void DrawUI();


    // ジャンプ開始した直後に呼ばれる関数
    virtual void OnStartJump() override;

    // コライダの衝突した際に呼ばれるコールバック関数
    virtual void OnCollision(std::shared_ptr<Collision> pCollision) override;

    // アイテムを取得した時に呼ばれる処理を override して実装
    virtual void OnItem(std::shared_ptr<Item> pItem) override;

    // お金を取得した時に呼ばれる処理を override して実装
    virtual void OnMoney(int getMoney) override;

};

#endif



World.hを変更して、BulletやItemなどのリストを追加しましょう。

#ifndef WORLD_H_
#define WORLD_H_

#include <list>
#include <memory>
#include <unordered_map>
#include <functional> // std::functionのラムダ式 で 関数を Draw関数の引数にして、カメラすべてに対して描画処理を発動させる

#include "MyHash.h" // ワールドのタグをhash32型で管理する


// [前方宣言] 宣言だけして、#includeはしてないので、ポインタだけはこのファイル内でつかえる
// #includeはしてないので、hpなどの変数には#include "~.h"しないとアクセスできないので循環インクルード防止のため.cppでインクルードしてください
class GameScene;
class Player;
class Bullet;
class Item;

class Enemy;
class Camera;
class Grid;
class Map;

// プレイヤや敵や弾などの共有ポインタを管理し、EraseRemoveIfで消して参照カウンタが0になったらそれらを消滅させるクラス(C#のガベージコレクタの代わり)
class World
{
public:
    inline World(GameScene* pScene, const std::string& worldTag = "") : m_pScene{ pScene }, tag{ worldTag } {}
    inline ~World() {}
protected: // public:にすると他で外部で後からタグやシーンを書き換えられると辞書での管理などの前提がおかしくなるのでprotected:する
    hash32 tag{ "" }; // ワールド名のタグ
    GameScene* m_pScene{ nullptr }; // ワールドを所有するシーン
public:
    hash32 Tag() const { return tag; }
    GameScene* scene() { return m_pScene; }

    //std::unordered_map<hash32, std::shared_ptr<Player>> players; // プレイヤのタグ辞書
    // ワールドを所有するシーンにプレイヤを追加する
    void AddPlayer(const std::string& playerTag, std::shared_ptr<Player> pPlayer);
    // ワールドにいるプレイヤを得る
    std::vector<std::weak_ptr<Player>> GetPlayers();

    bool LoadMap(const std::string& mapFilePath); // ワールドのマップをロード

    std::shared_ptr<Map> map{ nullptr }; // マップ(共有ポインタ) マップのメモリ解放は World と運命をともにして消える

protected:
    std::shared_ptr<Grid> m_pGrid{ nullptr }; // 当たり判定を高速にするためグリッドで空間分割
public:
    Grid* grid() { return m_pGrid.get(); } // グリッドを返す
    Grid* makeGrid(int cellWidth = 128, int cellHeight = 128, int cellDepth = 128); // グリッドを生成する

    std::unordered_map<hash32, std::shared_ptr<Player>> players; // プレイヤのタグ辞書

    std::list<std::shared_ptr<Bullet>> bullets; // 弾のリスト
    std::list<std::shared_ptr<Item>> items; // アイテムのリスト

    std::list<std::shared_ptr<Enemy>> enemies; // 敵のリスト

    // 削除処理を共通テンプレート関数にする
    // [共通テンプレート関数]https://programming-place.net/ppp/contents/cpp/language/009.html#function_template
    template <typename TypeT, class T_if>
    void EraseRemoveIf(std::list<TypeT>& v, T_if if_condition)
    {   //            特定のタイプT↑  ↑配列v   ↑条件式if_condition
        v.erase(
            std::remove_if(v.begin(), v.end(), if_condition),
            v.end() //  ↓remove_ifの位置
        );//例.[生][生][死][死][死]← v.end()の位置
    };

    std::unordered_map<hash32, std::shared_ptr<Camera>> cameras; // このワールドに存在するカメラの辞書<カメラにつけたタグ, カメラの共有ポインタ>
    // 指定されたタグのカメラへの共有ポインタを得る カメラ辞書に存在しないタグでアクセスしたときにunordered_mapの特性で勝手に意図しないカメラができるのを防ぐ関数
    std::shared_ptr<Camera> camera(const std::string& cameraTag) { auto itr = cameras.find(cameraTag); return (itr != end(cameras)) ? itr->second : nullptr; }
    // このワールドに存在するカメラすべてに対して drawFuncで描きたい処理を渡して 全カメラのスクリーンに対して描画処理を走らせる
    void Draw(std::function<void()> drawFunc) noexcept;
    // カメラにタグをつけてワールドに追加 カメラにもワールドへのポインタリンクを張る
    inline bool AddCamera(const std::string& cameraTag, std::shared_ptr<Camera> pCamera);
    // 指定したタグのカメラをこのワールドから削除する カメラ側の worldへのリンクのポインタも nullptr にする
    inline bool EraseCamera(const std::string& cameraTag);
    // カメラを nowWorld から nextWorld に移動させる
    static bool MoveCamera(const std::string& cameraTag, World* nowWorld, World* nextWorld);

};

#endif



Player.cppを変更して、マップをスクロールして敵を出現させましょう。

#include "Player.h"

#include <cmath> // 浮動小数点の余り%演算子がC++にないのでfmodを使うため
#include "MyMath.h"

#include "MyDraw.h"

#include "Camera.h"
#include "World.h"
#include "GridSteps.h"

#include <vector>

void Player::HandleInput()
{

    if (Input::GetButton(pad, PAD_INPUT_LEFT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_A)) )
    {
        //vx = - MoveSpeed; // 左
        deltaAngle += 1; //ハンドルを左に回す
    }
    if (Input::GetButton(pad, PAD_INPUT_RIGHT)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_D)))
    {
        //vx = MoveSpeed; // 右
        deltaAngle -= 1; //ハンドルを右に回す
    }

    if (Input::GetButton(pad, PAD_INPUT_UP)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_W)))
    {
        //vy = - MoveSpeed; // 上
        MoveSpeed += 0.6f; //アクセルを踏む
        if (MoveSpeed > MoveSpeedMax)
            MoveSpeed = MoveSpeedMax;//Maxスピードで止める
    }
    else if (Input::GetButton(pad, PAD_INPUT_DOWN)
        || (pad == Pad::Two && Input::GetButton(Pad::Keyboard, KEY_INPUT_S)))
    {
        //vy = MoveSpeed; // 下
        MoveSpeed -= 0.6f; //ブレーキを踏む
        if (MoveSpeed < -0.9f)
            MoveSpeed = -0.9f;//ちょっとだけバックできるように
    }

    if (pad == Pad::Key || pad == Pad::One) // コントローラ1またはキーボード操作のプレイヤのときだけ
    {
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGUP)) // キーボードのPageUp ボタンを押したときは
        {
            vy = 2.0f; // テスト用に空中の上方向に移動
        }
        if (Input::GetButton(Pad::Keyboard, KEY_INPUT_PGDN)) // キーボードのPageDown ボタンを押したときは
        {
            vy = -2.0f; // テスト用に空中の下方向に移動
        }
    }

}

// ジャンプ開始した直後に呼ばれる関数
void Player::OnStartJump()
{
    this->isGround = false; // ジャンプ中はisGroundフラグがfalse
}


// 更新処理
void Player::Update()
{
    vxForce *= 0.9f; // かかっている力の減衰率
    vyForce *= 0.9f; // かかっている力の減衰率
    vzForce *= 0.9f; // かかっている力の減衰率
    deltaAngle *= 0.5F; // 車のハンドルの方向をだんだんニュートラルに戻す
    MoveSpeed *= 0.9F; // 移動速度も減速する

    // 入力を受けての処理
    HandleInput();

    // 弾を撃ったりブロックの配置などアイテムのインプット操作を受け取る
    HandleItemInput();


    auto pJumpPhysics = jumpPhysics.lock();
    if (pJumpPhysics != nullptr)
    {
        if (isGround)
        {
            if (Input::GetButtonDown(pad, PAD_INPUT_A) // Zキーを押した瞬間
                || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを押した瞬間
            {
                pJumpPhysics->JumpStart(); // ジャンプのスタート処理を発動
            }
        }
        else if (vy > 0 && Input::GetButtonUp(pad, PAD_INPUT_A) // Zキーを離した瞬間
            || (pad == Pad::Two && Input::GetButtonUp(Pad::Keyboard, KEY_INPUT_Q))) // プレイヤ2がQキーを離した瞬間
        {
            pJumpPhysics->JumpCancel(); // ジャンプのキャンセル処理を発動
        }
        pJumpPhysics->Update(); // ジャンプのコンポーネントを更新する
    }
    auto pGravityPhysics = gravityPhysics.lock();
    if (pGravityPhysics != nullptr)
        pGravityPhysics->Update(); // 重力落下のコンポーネントを更新する

    moveAngle += deltaAngle; // ハンドルを回したぶんプレイヤの進行方向を変える
    if (moveAngle < -180) moveAngle = 360 + moveAngle; // -180以下の角度にならないようにマイナスになったら+180度にループさせる
    else if (moveAngle > +180) moveAngle = -360 + moveAngle; // +180以下の角度にならないようにマイナスになったら-180度にループさせる

    rotation.SetRotation(VGet(0, (180 - moveAngle) * MyMath::Deg2Rad, 0)); // 前向きの3Dモデルなら180度回転させて奥方向の向きに設定

    // ↓進行方向角度をX方向とZ方向の速度に変える
    vx = (float)std::cos((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;
    vz = (float)std::sin((moveAngle + 90) * MyMath::Deg2Rad) * MoveSpeed;

    float vyForceJump = (pJumpPhysics != nullptr) ? pJumpPhysics->vyForce() : 0.0f;
    float vyForceGravity = (pGravityPhysics != nullptr) ? pGravityPhysics->vyForce() : 0.0f;

    vx += vxForce; // 力をかけて速度に加算する
    vy += vyForce + vyForceJump + vyForceGravity; // 勢い(力・フォースを加算
    vz += vzForce; // 力をかけて速度に加算する

    // 実際に位置を動かす

    auto pMapHitBox = mapHitBox.lock();
    float cellHalf = (map() != nullptr) ? (float)map()->CellSize() / 2.0f : 16.0f; // マップのセルのサイズの半分
    // 速すぎて当たり判定がすり抜けないように細切れのステップのグリッドで移動させつつ当たり判定する
    Vector3 gridStepSize = (pMapHitBox == nullptr) ? Vector3{ cellHalf, cellHalf, cellHalf } : pMapHitBox->m_BoxSize / 2;
    GridSteps vSteps{ velocity, gridStepSize }; // 細切れのステップの数値を計算
#ifdef _DEBUG
    if (vSteps.div >= 1000) // 分割が1000を超える場合は物理式かどこかで∞=inf や 計算不可能数=nanが発生してそう
    {   // vx,vy,vzに原因がありそうなのでデバッグ表示
        if (!isfinite(vx)) printfDx(("vx:∞かnan x:" + std::to_string(x) + "\n").c_str());
        if (!isfinite(vy)) printfDx(("vy:∞かnan y:" + std::to_string(y) + "\n").c_str());
        if (!isfinite(vz)) printfDx(("vz:∞かnan z:" + std::to_string(z) + "\n").c_str());
    }
#endif
   
    bool isCollisionX{ false }, isCollisionY{ false }, isCollisionZ{ false }, isCollisionCollider{ false };
    for (int i = 1, iSize = vSteps.div + 1; i <= iSize; ++i)
    {
        auto& vStep = (i < iSize) ? vSteps.step : vSteps.mod;
        // まず横に移動する
        if (pMapHitBox == nullptr)
            x += vx;
        else if (isCollisionX == false) // まだ前のステップでX方向の衝突がないなら
        {
            x += vStep.x;
            isCollisionX = pMapHitBox->hitCheckX((TileBit)TileType::Wall);
        }

        // 次に縦に動かす
        if (pMapHitBox == nullptr)
            y += vy;
        else if (isCollisionY == false) // まだ前のステップでY方向の衝突がないなら
        {
            y += vStep.y;
            isCollisionY = pMapHitBox->hitCheckY((TileBit)TileType::Wall | (TileBit)TileType::Floor);
        }

        // 次にZ奥行き方向に動かす
        if (pMapHitBox == nullptr)
            z += vz;
        else if (isCollisionZ == false) // まだ前のステップでZ方向の衝突がないなら
        {
            z += vStep.z;
            isCollisionZ = pMapHitBox->hitCheckZ((TileBit)TileType::Wall);
        }


        if (auto pGridCollision = gridCollision.lock())
        {
            pGridCollision->Update(); // 所属するマス目セルを更新する
            for (auto& collider : *pGridCollision->colliders())
                isCollisionCollider |= collider.lock()->IsCollision();
        }

        // 衝突があったら、速すぎてすり抜けちゃう前に今のステップでbreakで抜ける
        if (isCollisionCollider)
            break; // ぶつかっているのにさらに次のステップの速度も足すとすり抜けちゃう
    }

    map()->Scroll3D(position, Vector3(vx, vy, vz)); // マップ(敵)データをスクロール

    // カメラのプレイヤ追尾
    float camDistance = 150; // プレイヤからカメラまでのY平面上の距離
    float camHeight = 100; // プレイヤからカメラまでのY方向上の高さ
    // moveAngleは2D画像のX方向右を0度とするから、-90度回すと画像を正面に捉える位置にカメラを置ける
    float camAngleX = (float)std::cos((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    float camAngleZ = (float)std::sin((moveAngle - 90) * MyMath::Deg2Rad) * camDistance;
    if (camera != nullptr)
    {   // カメラの位置をプレイヤの進行方向と真逆に設定
        camera->SetPosition(position + Vector3(camAngleX, camHeight, camAngleZ));
        camera->LookAt(position); //カメラはプレイヤの方を見る
    }
}

// 弾を撃ったりブロックの配置などアイテムのインプット操作を受け取る
void Player::HandleItemInput()
{
    if (auto pItemList = itemList.lock())
    {
        pItemList->Update(); // アイテムのリストをUpdateすると弾を撃つボタンを押したら弾が出るようになる

        if (Input::GetButtonDown(pad, PAD_INPUT_B)
            || (pad == Pad::Two && Input::GetButtonDown(Pad::Keyboard, KEY_INPUT_R)))
        {
            pItemList->SelectNext();
        }
    }
}

// アイテムを取得した時に呼ばれる処理を override して実装
void Player::OnItem(std::shared_ptr<Item> pItem)
{
#if 0
    if (pItem->typeTag == "ItemBlock") // アイテムブロックの場合はOnItemBlockに引き渡す
    {
        OnItemBlock(std::static_pointer_cast<ItemBlock>(pItem));
        //itemList->Add<>
    }
    //else if (pItem->typeTag == "ItemBullet")
#endif

    if (pItem->hasAction())
        itemList.lock()->Add(pItem);

}


// お金を取得した時に呼ばれる処理を override して実装
void Player::OnMoney(int getMoney)
{
    money += getMoney; // getMoneyがマイナスのときはお金は減ることもある
    //if (money < 0) money = 0; // 借金できない場合はコメントはずす
}

// UIの描画処理はDrawとは別にすることで、一番最後にスクリーンに上書きで描ける
void Player::DrawUI()
{
    if (camera != nullptr)
    {
        camera->Draw([&]()
            {
                std::string strMoney{ std::to_string(money) + "円" };
                DxLib::DrawString(0, 0, strMoney.c_str(), GetColor(255, 255, 255));
                if (auto pItemList = itemList.lock())
                    pItemList->Draw();
            });
    }
}


void Player::OnCollision(std::shared_ptr<Collision> pCollision)
{
    if (isDead) return; // すでにisDeadなら return
    auto otherCollider = pCollision->contacts[0].otherCollider.lock(); // コライダの弱共有ポインタをロック
    if (otherCollider == nullptr) return; // すでにコライダが消去済だった
    auto other = otherCollider->owner();

    if (other->isDead) return; // other側がisDeadだったら処理をスルー

    if (other->baseTag == "Enemy" || other->baseTag == "Player")
    {
        //[離散要素法:DEM] https://qiita.com/konbraphat51/items/157e5803c514c60264d2
        constexpr float k = 1.0f; // バネ係数(めり込みに対するバネ反発の掛け率)
        constexpr float e = 0.1f; // はね返り係数(直前と比べどれくらいの速さで離れるか0.5だと半分の速さではね返る)
        float log_e = std::log(e); // はね返り係数の自然対数log
        constexpr float pi_2 = DX_PI_F * DX_PI_F; // πの2乗
        float eta = -2.0f * log_e * std::sqrt(k * mass / (log_e * log_e + pi_2)); // 粘性係数η(イータ)
        auto norm = pCollision->contacts[0].normal; // 衝突の法線 * other->pushPower; // pushPowerの力でotherに押し返される
        auto v_norm = dot(other->velocity - velocity, norm) * norm; // [相対速度v] 法線norm方向のv = (v1 - v0)・norm [ドット積]
        auto sep = pCollision->contacts[0].separation; // 衝突点どうしの距離(めりこみ距離)
        auto F = -k * sep * norm - eta * v_norm; // F (法線方向の力) = -k × sep - η × v (相対速度:自分から見た相手otherの速度)
        force += F / mass; // 衝突法線方向の力を加える
    }
}

// 描画処理
void Player::Draw()
{
    int animIndex = animCount / 20; // キャラのパラパラアニメの画像番号(最大3)
    float abs_vx = (vx > 0) ? vx : -vx; // x方向絶対値
    float abs_vz = (vz > 0) ? vz : -vz; // y方向絶対値
    if (abs_vx > 1.00f || abs_vz > 1.00f)
        animCount += 6; // キャラが地面のXとZ方向に少しでもスピードがあったらアニメを走らせる
    if (animCount >= 60) animCount = 0; // アニメ時間を0へループ

    // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
    for (auto&& keyValue : world()->cameras) // プレイヤのいるワールドにあるすべてのカメラぶんループ
    {
        auto& camera = keyValue.second;
        camera->Draw([&]()
            {
                // 常にカメラ方向を向くビルボード回転の角度を得る
                VECTOR_D angle = MyDraw::GetAngle(DxLib::GetCameraBillboardMatrixD());
                angle.x = 0; angle.z = 0; // Y軸周りのビルボード回転だけ有効にする X軸,Z軸まわりの回転は 0 にする

                int imgIndex = 9; // キャラチップ画像の起点番号
                // ビルボードの角度 と キャラの向いている向き の 差 によって、別のプレイヤのカメラから見たキャラチップの向きの画像を切り替える
                // -180 ~ +180 : angle.y  moveAngle : -180 ~ +180 ↓
                float difAngleY = angle.y * 180.0f / DX_PI_F + moveAngle;
                imgIndex = (difAngleY < -135 || 135 < difAngleY) ? 0 // カメラ向きの画像
                    : (difAngleY < -45) ? 6 // 右 向きの画像
                    : (difAngleY > 45) ? 3  // 左 向きの画像
                    : 9; // 奥向きの画像

                MyDraw::DrawDivRotaGraphF3D(MyDraw::Plane::Z, x, y, z, 1.0f, angle, *image, imgIndex + animIndex, TRUE);

#ifdef _DEBUG // デバッグのときだけ_DEBUGが定義され描かれる
                if (auto pSphereCollider = sphereCollider.lock()) pSphereCollider->Draw(); // コライダを描く
                if (auto pMapHitBox = mapHitBox.lock()) pMapHitBox->Draw(); // ヒットボックスを描く
#endif
            });

    }
}



Map.hを変更して、アイテムをフィールドにFactoryで生成する処理を追加しましょう。

#ifndef MAP_H_
#define MAP_H_

#include <vector> // C#におけるListリストの機能
#include <unordered_map>    // 辞書型連想配列に必要
#include <memory> // メモリを扱う【共有ポインタに必要】
#include <limits> //intやfloatなどの無限大∞に使う  https://cpprefjp.github.io/reference/limits/numeric_limits/max.html

#include <fstream> // ファイル読み出しifstreamに必要
#include <string> //文字列に必要
#include <sstream> // 文字列ストリームに必要

#include <assert.h> // マップ読込み失敗表示用

#include "Screen.h"
#include "MyMath.h"
#include "MyHash.h"
#include "MyDraw.h"
#include "DataCsv.h"
#include "TMXJson.h"

#include "Singleton.h"
#include "GameObject.h"
#include "World.h"
#include "Camera.h"
#include "Factory.h"

#include "MapRange.h" // マス目のレンジ表現するクラス

#include "Tile.h"

#include "Vector3.h"

class AI_Map; // 前方宣言

// csvから読み取るマップの基本情報
struct MapInfo
{
    MapInfo() = default;
    inline MapInfo(unsigned int CellSize, int SpawnRangeX, int SpawnRangeY, MapRange::Type type = MapRange::Type::Rect)
        : CellSize{ CellSize }, spawnRange{ MapRange(SpawnRangeX,SpawnRangeY,type) } {};
    ~MapInfo() {};

    unsigned int CellSize{ 1 }; // マップの1マスのピクセル数
    MapRange spawnRange; // 敵の新規出現レンジ
};

struct MapSetting // マップ情報をMapSetting.csvなどから読みだす
{
    MapSetting(const std::string& csvFileName = "")
    {
        if (csvFileName != "")
            Load(csvFileName);
    }
    ~MapSetting() {}; //デストラクタ

    std::string FilePath{ "" }; // csvのファイルパス

    std::unordered_map<hash32, std::shared_ptr<MapInfo>> info; //[辞書]stageファイル名→マップ情報

    MapInfo& operator[](const std::string& stageFilePath) {
        assert(info.count(stageFilePath) != 0 && "マップのステージ情報が読み取れていません Map/MapSettings.csv にステージ情報があるか確認!");
        return *info[stageFilePath]; // 読み書き
    }

    void Load(const std::string& csvFileName = "")
    {
        if (csvFileName != "") this->FilePath = csvFileName;
        if (this->FilePath == "") return;

        // マップ情報のデータ CSVファイルからデータをロード
        DataCsv Data{ this->FilePath, DataCsv::CsvType::CsvValue };
        std::string stageFile; //ステージファイル名
        for (int j = 0; j < Data.size(); ++j)
        {
            if ((std::string)Data[j][0] == "//")
                continue; // 一番左の列が//なら読み飛ばしのコメント行
            else if ((std::string)Data[j][0] == "File")
            {
                stageFile = Data[j][1]; //ステージファイル名
                info.emplace(stageFile, std::make_shared<MapInfo>());
                continue;
            }

            if (stageFile == "") continue; //ステージファイル名が未確定
            else if ((std::string)Data[j][0] == "CellSize")
                info[stageFile]->CellSize = (int)Data[j][1]; //マス目サイズ
            else if ((std::string)Data[j][0] == "SpawnRange")
            {   // 敵出現レンジ
                MapRange::Type rangeType = ((std::string)Data[j][3] == "Ellipse") ? MapRange::Type::Ellipse : MapRange::Type::Rect; //敵出現のタイプ
                info[stageFile]->spawnRange.InitRange(rangeType, Data[j][1].intFromString(), Data[j][2].intFromString()); //敵出現のタイプを初期化設定
            }
        }
    }
};

// ステージのタイルマップのクラス
class Map
{
public:
    // コンストラクタ
    Map(World* world, const std::string& filePath = "")
        : m_world{ world }
    {
        if (filePath == "") return;

        Load(filePath);
    }

    // デストラクタ
    ~Map()
    {   // マップデータのお掃除
        enemyData.clear();
        //terrains.clear(); //★地形のお掃除
    }

    static MapSetting settings; // マップに関する設定 Map.cpp で初期読込み デフォルトは"Map/MapSetting.csv" を読み込む
    static TileSetting tileSettings; // タイルに関する設定 Map.cpp で初期読込み デフォルトは"Map/MapSetting.csv" を読み込む
    // タイルのチップ画像のパスとidからTileの情報とタイルのgid情報を得る
    static Tile GetTile(const std::string& mapChipPath, int id, unsigned int& gid)
    {
        auto pos = mapChipPath.find(".", 0);
        std::string tsxPath{ "" };
        if (pos != std::string::npos) tsxPath = mapChipPath.substr(0, pos) + ".tsx";
        auto found = Tilesets::umTsxInfo.find(tsxPath);
        if (found != end(Tilesets::umTsxInfo) && found->second.gid.size() > id)
            gid = found->second.gid[id]; // 辞書で id から gidに変換しておく
        else return Tile(); // id が-1のときは見つからなかったとき
        pos = tsxPath.find("/", 0);
        if (pos != std::string::npos) tsxPath = tsxPath.substr(pos + 1, tsxPath.size() - 1);
        return Map::tileSettings.info[tsxPath]->tiles[id];
    }

protected:
    World* m_world{ nullptr }; // マップに配置する敵などのポインタを保持するワールド
public:
    // 現在いるワールドへのポインタを得る
    World* world() { return m_world; }
    // 現在いるワールドのポインタを変更する
    void world(World* changeWorld) { m_world = changeWorld; }

    std::shared_ptr<AI_Map> aiMap{ nullptr }; // AIの経路探索に使うAIマップ

    // 読込んだデータファイルの情報
    std::string FilePath{ "" };
protected:
    unsigned int m_CellSize{ 1 }; // マップの1マスのピクセル数 1のときはtmxに設定されたサイズが採用される
    unsigned int m_HalfCellSize{ 1 }; // マス目サイズの 1/2
public:
    inline unsigned int CellSize() { return m_CellSize; }
    inline unsigned int HalfCellSize() { return m_HalfCellSize; }
    inline void SetCellSize(unsigned int cellSize) { m_CellSize = cellSize; m_HalfCellSize = m_CellSize / 2; }
    MapRange spawnRange; // 敵の新規出現レンジをプレイヤから半径X,Yの楕円にする

    inline float centerX(int cellX) const { return (cellX >= 0) ? (float)((cellX * m_CellSize) + m_HalfCellSize) : (float)((cellX + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline float centerY(int cellY) const { return (cellY >= 0) ? (float)((cellY * m_CellSize) + m_HalfCellSize) : (float)((cellY + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline float centerZ(int cellZ) const { return (cellZ >= 0) ? (float)((cellZ * m_CellSize) + m_HalfCellSize) : (float)((cellZ + 1) * m_CellSize) - m_HalfCellSize; } //マス目サイズ/2ずらし
    inline int cellX(float worldX) const { return (worldX >= 0) ? (int)worldX / m_CellSize : (int)worldX / m_CellSize - 1; } // マイナス方向にも対応
    inline int cellY(float worldY) const { return (worldY >= 0) ? (int)worldY / m_CellSize : (int)worldY / m_CellSize - 1; } // マイナス方向にも対応
    inline int cellZ(float worldZ) const { return (worldZ >= 0) ? (int)worldZ / m_CellSize : (int)worldZ / m_CellSize - 1; } // マイナス方向にも対応

    TMXCsv enemyData;// 敵配置データ
    TMXJson tmxData; // タイルマップエディタのjsonデータ

protected:
    // <gid→idに対応したタイプのbit> へ変換するvector型の辞書
    std::vector<TileBit> gidTypeBits{ 0 };
public:
    inline TileBit& gidTypeBit(unsigned int gid)
    {
        if (gid >= (int)gidTypeBits.size()) gidTypeBits.resize(gid + 1, 0);
        return (gid < 0) ? gidTypeBits[0] : gidTypeBits[gid];
    }
    // id→ビットのタイプ の辞書に |= でbitTypeをビット論理和して、Wall:壁 や Bounce:はずむ などの特性を追加する
    TileBit SetTypeBit(unsigned int gid, TileBit bitType) { return gidTypeBit(gid) |= bitType; }

    // id→ビットのタイプ の辞書で & ビット論理積で 指定したidが Wall:壁 や Bounce:はずむ などの特性を持つかを判定する
    inline bool isType(unsigned int gid, TileType bitType) { return gidTypeBit(gid) & bitType; }

    // id→ビットのタイプ の辞書で & ビット論理積で 指定したidが Wall:壁 や Bounce:はずむ などの特性を持つかを判定し一致したBitを返す(複数が1のビットもありうる)
    inline TileBit matchTypes(unsigned int gid, TileBit bitType) { return gidTypeBit(gid) & bitType; }

    // tilesetsの.tsxのfirstgidをたどって、SetTypeBitでビット辞書に
    // csvのtileSettingsから読み取っておいたビット設定を登録
    bool InitTypeBit(const Tilesets& tilesets)
    {
        assert(tilesets.size() > 0 && "ステージのjsonファイルにタイルセット設定が一つもありません");
        if (tilesets.size() == 0) return false;
        for (auto& pair : tilesets)
        {
            auto firstgid = pair.first;
            auto& tsxFilePath = pair.second;
            auto& tsxInfo = tilesets.umTsxInfo.at(tsxFilePath);
            // [親フォルダを抽出] https://qiita.com/takano_tak/items/acf34b4a30cb974bab65
            size_t path_i = tsxFilePath.find_last_of("\\/");
            if (path_i + 1 >= tsxFilePath.size()) continue;
            auto tsxFile = tsxFilePath.substr(path_i + 1);
            auto csvInfo = tileSettings.info.find(tsxFile);
            if (csvInfo == end(tileSettings.info)) continue; // csv側にタイルの情報がなかった
            // gidが0 idが-1 のデフォルトのタイルのビット特性を設定
            SetTypeBit(0, csvInfo->second->idTypeBit(-1));
            int id = 0; // gidじゃなく0から始まるローカルなid
            for (auto gid = firstgid, iEnd = firstgid + tsxInfo.tilecount; gid < iEnd; ++gid)
            {   // このMapのgidのビット辞書にcsvのtileSettingsから読み取っておいたビット設定を登録
                SetTypeBit(gid, csvInfo->second->idTypeBit(id));
                ++id; // ローカルなidも次に進める
            }
        }
    }

    // マップファイルのロード
    void Load(const std::string& filePath)
    {
        this->FilePath = filePath;

        // ★jsonファイルの読み込み
        tmxData.Load(filePath);

        // tilesetsの.tsxのfirstgidをたどって、SetTypeBitでビット辞書に
        // csvのtileSettingsから読み取っておいたビット設定を登録
        InitTypeBit(tmxData.tilesets);

        auto info = settings[filePath];
        if (info.spawnRange.umRange.size() != 0)
            spawnRange = info.spawnRange;
        else
            spawnRange.InitRange(MapRange::Type::Ellipse, 10, 10);// 敵の新規出現レンジをプレイヤから半径X,Yの楕円にする

        if (info.CellSize != 1)
        {
            SetCellSize(info.CellSize); // csvに書かれているCellSizeを設定
            tmxData.SetGridSize({(float)info.CellSize ,(float)info.CellSize ,(float)info.CellSize });
        }
        else // if (info.CellSize == 1)
            SetCellSize(tmxData.tmxInfo.tilewidth); // tmxのタイルの幅を取得
    }

    // オブジェクトを Factoryクラスで className に応じて見きわめて 生成・配置する
    template<typename TypeT>
    inline std::shared_ptr<TypeT> SpawnTmxObject(const std::string& className, std::shared_ptr<InitParams> initParams, Vector3 spawnPos, Quaternion rotation = { 0,0,0,1 })
    {
        //IDに応じてFactory工場で作り分ける
        return Factory::MakeSharedObject<TypeT>(className, initParams, m_world, spawnPos);
    }

    // 画面スクロール(位置の更新)
    void Scroll3D(Vector3 prevPos, Vector3 delta, MapRange* pSpawnRange = nullptr)
    {
        std::vector<TMXobjects*> tmxObjectsList{ &tmxData.enemies,&tmxData.lights,&tmxData.items,&tmxData.objects };
        for (auto& tmxObjects : tmxObjectsList)
        {
            if (tmxObjects->data.size() == 0) continue; // リストにデータが一つもなければスクロールせず次へ

            tmxObjects->Scroll(prevPos, delta, (pSpawnRange != nullptr) ? *pSpawnRange : (tmxObjects->pSpawnRange != nullptr) ? *(tmxObjects->pSpawnRange) : spawnRange,
                [&](int gridX, int gridY, int gridZ)
                {
                    auto pObject = tmxObjects->top(gridX, gridY, gridZ); // グリッドからオブジェクトを取り出す
                    while (pObject != nullptr)
                    {
                        auto pObj = (TMXobject*)pObject;

                        //int spawnID = pObj->spawnID; // 生成する敵のID情報をTMXの jsonファイルのデータから読み取る
                        float ratioX = (float)tmxObjects->cellWidth / tmxData.tmxInfo.tilewidth;
                        float ratioY = (float)tmxObjects->cellHeight / tmxData.tmxInfo.tileheight; // タイルマップエディタとのスケールの違いの比率を計算
                        float ratioZ = (float)tmxObjects->cellDepth / tmxData.tmxInfo.tilewidth;

                        //クラス名とベースクラス名に応じて工場で作り分ける
                        if (pObj->baseType == "Enemy")
                            m_world->enemies.emplace_back(SpawnTmxObject<Enemy>(pObj->type, pObj->GetInitProperty(), Vector3(pObj->x * ratioX, pObj->z * ratioZ, pObj->y * ratioY)));
                        //if (pObj->baseType == "Light")
                        //    m_world->lights.emplace_back(SpawnTmxObject<Light>(pObj->type, Vector3(pObj->x * ratioX, pObj->z * ratioZ, pObj->y * ratioY)));
                        if (pObj->baseType == "Item")
                            m_world->items.emplace_back(SpawnTmxObject<Item>(pObj->type, pObj->GetInitProperty(), Vector3(pObj->x * ratioX, pObj->z * ratioZ, pObj->y * ratioY)));

                        //if (pObj->baseType == "Object")
                        //    m_world->objects.emplace_back(SpawnTmxObject<Object>(pObj->type, Vector3(pObj->x * ratioX, pObj->z * ratioZ, pObj->y * ratioY)));
                        //m_world->enemies.emplace_back(Factory::MakeSharedTmxObject(spawnID,m_world, Vector3(pEnemy->x * ratioX, pEnemy->z * ratioZ, pEnemy->y * ratioY)));

                        pObject = pObject->m_pNext; // 同じグリッドの次のオブジェクトへ

                        tmxObjects->Erase(pObj); // tmxObjectsのdataとグリッドからデータを消して再びスポーンしないようにする
                    }
                    // このグリッドには何もいなくなるのでマス目自体eraseして高速化
                    tmxObjects->cellData.erase(CellXYZ(gridX, gridY, gridZ));
                });
        }
    }

    // 指定された座標(ワールド座標)の地形データを取得する
    unsigned int GetTerrain(float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        if (worldZ != (std::numeric_limits<float>::min)())
        {
            float tmpY = worldY;
            worldY = worldZ; //【YとZを変換】Zの入力があるときはZをYとして扱う
            worldZ = tmpY; //【YとZを変換】
        }

        // 負の座標が指定された場合は、何も無いものとして扱う
        if (worldX < 0 || worldY < 0)
            return Tile::None;

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = (int)(worldX / m_CellSize);
        int mapY = (int)(worldY / m_CellSize);
        int mapZ = (int)(worldZ / m_CellSize);

        if (tmxData.terrains.count(mapZ) == 0) // zの階層がまだなかったとき
            return Tile::None; // 配列の範囲外は、何も無いものとして扱う

        auto& terrainZ = tmxData.terrains[mapZ]; // 辞書からZに対応するデータへの参照を得る
        if (mapY >= terrainZ.size() || mapX >= terrainZ[mapY].size())
            return Tile::None; // 配列の範囲外は、何も無いものとして扱う

        return terrainZ[mapY][mapX]; // 二次元配列から地形IDを取り出して返却する
    }

    // 指定された座標(ワールド座標)の地形データを書き換える return には書き換える前のgidが返る
    bool SetTerrain(unsigned int gid, float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        if (worldZ != (std::numeric_limits<float>::min)())
        {
            float tmpY = worldY;
            worldY = worldZ; //【YとZを変換】Zの入力があるときはZをYとして扱う
            worldZ = tmpY; //【YとZを変換】
        }

        // 負の座標が指定された場合は、書き換えられない
        if (worldX < 0 || worldY < 0)
            return false;

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = (int)(worldX / m_CellSize);
        int mapY = (int)(worldY / m_CellSize);
        int mapZ = (int)(worldZ / m_CellSize);

        // 二次元配列の範囲外は、書き換えられない
        //if (mapY >= terrain.size() || mapX >= terrain[mapY].size())
        //    return;
        bool isNewZ = false;
        if (tmxData.terrains.count(mapZ) == 0) isNewZ = true; // 新しい z の階層の場合

        auto& terrainZ = tmxData.terrains[mapZ]; // 辞書からZに対応するデータへの参照を得る

        if (isNewZ) // 新しい z の階層の場合
        {
            terrainZ.z = mapZ;
            terrainZ.tilesets = &(tmxData.tilesets); //  新しい z の階層のtilesetsを設定する
            terrainZ.Width = tmxData.tmxInfo.width; terrainZ.Height = tmxData.tmxInfo.height;
            auto& terrinZYX = terrainZ[mapY][mapX];
            terrinZYX.tilesets = &(tmxData.tilesets);
            terrinZYX = gid; // 地形データ書き換え
        }
        else
            terrainZ[mapY][mapX] = gid; // 地形データ書き換え
        return true;
    }

    // gidに応じてタイルを描き分ける
    void DrawTile(unsigned int gid, float worldX, float worldY, float worldZ, float ExRate)
    {
        auto texture = TsxInfo::id_images[gid].second.lock();
        int id = TsxInfo::id_images[gid].first;
        if (isType(gid, TileType::Floor)) // 板ポリゴンで床を描く
            MyDraw::DrawDivRotaFloorF3D(MyDraw::Plane::Y, worldX, worldY, worldZ, ExRate, 0, *texture, id);
        else if (isType(gid, TileType::Wall)) //壁のときはボックスを描く
            MyDraw::DrawDivBoxF3D(worldX, worldY, worldZ, ExRate, *texture, id);
    }

    // 地形をタイルで描く
    void DrawTerrain()
    {
        for (auto&& terrainN : tmxData.terrains)
        {
            auto& terrainZ = *terrainN.second;
            int cellZ = terrainN.first;
            for (int cellY = 0, ySize = terrainZ.size(); cellY < ySize; cellY++)
            {
                for (int cellX = 0, xSize = terrainZ[cellY].size(); cellX < ySize; cellX++)
                {
                    float x = centerX(cellX); //マス目サイズ/2ずらし
                    float y = centerY(cellY); //マス目サイズ/2ずらし
                    float z = centerZ(cellZ); //マス目サイズ/2ずらし
                    int id = -1;
                    unsigned int gid = 0;
                    if (cellY < terrainZ.size() && cellX < terrainZ[cellY].size())
                    {
                        gid = terrainZ[cellY][cellX];
                    }
                    if (0 <= gid && gid < TsxInfo::id_images.size())
                    {
                        auto pTileImage = TsxInfo::id_images[gid].second.lock();
                        if (pTileImage == nullptr) continue;
                        // ワールドに存在するすべてのカメラに関連づいたスクリーンに対して描画する
                        for (auto&& keyValue : world()->cameras) // ワールドにあるすべてのカメラぶんループ
                        {
                            auto& camera = keyValue.second;
                            //auto stepXYZs = MyMath::CrossGridVec(camera->pos - camera->m_targetXYZ, Vector3{ CellSize, CellSize, CellSize });
                            camera->Draw([&]()
                                {
                                    float ExRate = (float)m_CellSize / (float)pTileImage->m_XSize;
                                    DrawTile(gid, x, z, y, ExRate); // idに応じてタイルを描き分ける
                                });
                        }
                    }
                }
            }
        }
    }

    // 指定された座標(ワールド座標)の地形が壁か調べる
    inline bool IsWall(float worldX, float worldY, float worldZ = (std::numeric_limits<float>::min)())
    {
        auto terrainID = GetTerrain(worldX, worldY, worldZ); // 指定された座標の地形のIDを取得

        return IsWall(terrainID);
    }

    //あるIDが壁かどうかだけ調べる
    inline bool IsWall(unsigned int gid)
    {   // 地形が壁ならtrue、違うならfalseを返却する
        return isType(gid, TileType::Wall);
    }

    // 指定された座標worldPosの地形が 壁 や ダメージブロックかなどをチェックする関数
    bool checkID(TileBit checkBit, float worldX, float worldY, float worldZ,
        int* pMapX = nullptr, int* pMapY = nullptr, int* pMapZ = nullptr, unsigned int* pTerrainID = nullptr, TileBit* pMatchBits = nullptr)
    {
        unsigned int terrainID; // 指定された座標の地形のID

        if (worldZ != (std::numeric_limits<float>::min)())
        {   //【YとZを変換】Zの入力があるときはZをYとして扱う タイルマップの X,Yは3DではYがZ:奥行に対応するから
            float tmpY = worldY;
            worldY = worldZ;
            worldZ = tmpY; //【YとZを変換】
        }

        if (worldX < 0 || worldY < 0) return false; // 負の座標が指定された場合

        // マップ座標系(二次元配列の行と列)に変換する
        int mapX = cellX(worldX), mapY = cellY(worldY), mapZ = cellZ(worldZ);

        if (tmxData.terrains.count(mapZ) == 0) // zの階層がまだなかったときは 0
            terrainID = 0; // 範囲外のときは 0
        else // zの階層がすでにあったとき
        {   // 二次元配列の範囲内か判定
            auto& terrainZ = tmxData.terrains[mapZ];
            if (mapY >= terrainZ.size() || mapX >= terrainZ[mapY].size())
                terrainID = 0; // 範囲外のときは 0
            else
                terrainID = terrainZ[mapY][mapX]; // 配列から地形IDを取り出す
        }
        if (pTerrainID != nullptr)
            *pTerrainID = terrainID;

        TileBit matchBits = matchTypes(terrainID, checkBit); // ビット論理積 & で地形のchekckBitを確かめる
        bool isCheckBit = matchBits != (TileBit)TileType::None;
        if (isCheckBit && pMapX != nullptr && pMapY != nullptr && pMapZ != nullptr)
            *pMapX = mapX, * pMapY = mapY, * pMapZ = mapZ; // ブロックのマス目のセル番号をポインタに記録(nullptrの場合は記録しない)
        if (pMatchBits != nullptr)
            *pMatchBits = matchBits; // マッチしたビットをポインタ先に代入して返す

        return isCheckBit;
    }
};

#endif



GameScene.cppを変更して、アイテムとUIのアイコンを更新して、描く処理を追加しましょう。

#include "GameScene.h"

#include "Player.h"
#include "Enemy.h"
#include "Bullet.h"
#include "Item.h"
#include "ItemBlock.h"
#include "ItemBullet.h"

#include "Map.h"

GameSceneSetting GameScene::settings{ "Map/GameSceneSetting.csv" }; // ゲームシーンの設定情報

// 指定したゲームシーンのタグに関連づいたファイル名に対応したワールドを初期化する
bool GameScene::LoadWorlds(const std::string& gameSceneTag)
{
    if (settings.info.count(gameSceneTag) == 0)
        return false;

    auto& info = settings[gameSceneTag];
    for (auto&& tag_mapPath : info)
    {
        CreateWorld(tag_mapPath.second.tag); // ワールドをタグをつけて生成
        // ワールドのマップをロード
        worlds[tag_mapPath.second.tag]->LoadMap(tag_mapPath.second.mapFilePath);
    }
    return true;
}

void GameScene::ChangeWorld(const std::string& worldTag, std::shared_ptr<Player> pPlayer)
{
    if (pPlayer == nullptr) return;
    if (worlds.count(worldTag) == 0) return; // 存在しないタグだった

    pPlayer->world(worlds[worldTag].get()); // ワールドへのリンクをすげ替える
}


void GameScene::Initialize()
{// Init処理

    // Zバッファを有効にする [Zの深度]
    //[参考]https://docs.google.com/presentation/d/1Z23t1yAS7uzPDVakgW_M02p20qt9DeTs4ku4IiMDLco/edit?usp=sharing
    //[参考] https://dxlib.xsrv.jp/function/dxfunc_3d_draw.html#R14N12
    DxLib::SetUseZBuffer3D(TRUE);
    // Zバッファへの書き込みを有効にする
    DxLib::SetWriteZBuffer3D(TRUE);
    // 光の明暗計算を無効に
    DxLib::SetUseLighting(FALSE);

    // SetDrawScreen に引数として渡せる( 描画対象として使用できる )グラフィックハンドルを作成するかどうかを設定する
    // ( TRUE:描画可能グラフィックハンドルを作成する  FLASE:通常のグラフィックハンドルを作成する( デフォルト ) )
    DxLib::SetDrawValidGraphCreateFlag(TRUE);

    // サブのスクリーンを2つ生成 画面の全体の縦方向サイズの Height / 2 にして、上下分割で配置できるようにする
    screenHandle0 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);
    screenHandle1 = DxLib::MakeScreen(Screen::Width, Screen::Height / 2, FALSE);

    // スクリーンを作成したらデフォルト設定に戻しておかないと
    DxLib::SetDrawValidGraphCreateFlag(FALSE);


    Resource::MakeShared<Texture>(playerImgPath1, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする
    Resource::MakeShared<Texture>(playerImgPath2, 3, 4, 32, 32)->Load(); // メソッドチェーン -> でMakeShared読込後にLoadもする

    Resource::MakeShared<Texture>("Image/zako0.png")->Load(); // 敵画像を読込
    Resource::MakeShared<Texture>("Image/skullman.png", 3, 4, 32, 32)->LoadDivGraph(); // 敵画像を読込

    Resource::MakeShared<Texture>("Image/yen1.png")->Load(); // お金の画像を読込
    Resource::MakeShared<Texture>("Image/yen10.png")->Load(); // お金の画像を読込
    Resource::MakeShared<Texture>("Image/bullet.png")->Load(); // 弾の画像を読込


    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    // プレイヤの生成
    if (pWorld != nullptr)
    {
        //pWorld->AddCamera("カメラ1", Camera::defaultCamera); // カメラ1というタグをデフォルトカメラにつけてワールドにデフォルトカメラを配置
        pWorld->AddCamera("カメラ1", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera1 = pWorld->camera("カメラ1"); // カメラ1 というタグのついたカメラへの共有ポインタを得る
        pCamera1->SetScreenHandle(screenHandle0); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer1 = Object::Create<Player>(nullptr, pWorld, Pad::Key, Vector3(90 + 256, 32, 95 + 256));
        pWorld->AddPlayer("プレイヤ1", pPlayer1); // シーンにプレイヤを追加する
        pPlayer1->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath1]);

        pPlayer1->camera = pCamera1.get(); // カメラをプレイヤにリンクする
        pPlayer1->mass = 100.0f; // 体重を100にして衝突されてもびくともしないようにする


        pWorld->AddCamera("カメラ2", std::make_shared<Camera>(pWorld, Vector3{ 0,0,0 }));
        auto pCamera2 = pWorld->camera("カメラ2"); // プレイヤ2 というタグのついたカメラへの共有ポインタを得る
        pCamera2->SetScreenHandle(screenHandle1); // カメラから見える3Dを描く スクリーンのハンドル を関連付ける
        auto pPlayer2 = Object::Create<Player>(nullptr, pWorld, Pad::Two, Vector3(190, 32, 195));
        pWorld->AddPlayer("プレイヤ2", pPlayer2); // シーンにプレイヤを追加する
        pPlayer2->image = std::dynamic_pointer_cast<Texture>(Resource::files[playerImgPath2]);
       
        pPlayer2->camera = pCamera2.get(); // カメラをプレイヤにリンクする
        pPlayer2->mass = 1.0f; // 体重を1にして普通にふっとぶようにする

    }

};


void GameScene::Update()
{// 更新処理

    Screen::ClearDrawScreen(screenHandle0); // 一旦キャンバスをきれいにまっさらに
    Screen::ClearDrawScreen(screenHandle1); // 一旦キャンバスをきれいにまっさらに

    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    for (const auto& player : players)
        player.second->Update(); // プレイヤの更新【忘れるとプレイヤが動かない】

    // 弾の更新処理
    for (const auto& world : worlds)
        for (const auto& b : world.second->bullets)
            b->Update();


    // 敵の更新処理
    for (const auto& world : worlds)
        for (const auto& e : world.second->enemies)
            e->Update();

    // アイテムの更新処理
    for (const auto& world : worlds)
        for (const auto& item : world.second->items)
            item->Update();


    // グリッドを更新して隣接するグリッドに含まれる敵や弾の当たり判定を行う
    for (const auto& world : worlds)
        if (world.second->grid() != nullptr)
            for (const auto& cell : world.second->grid()->cellData)
            {
                auto pGridObject = cell.second;
                while (pGridObject != nullptr)
                {   // while文で数珠つなぎのm_pNextを次々とたどって最後にたどり着くと次がなくてnullptrになる
                    pGridObject->checkCell();
                    pGridObject = pGridObject->m_pNext; // リンクリスト構造を次々とたどる
                }
            }

    // 敵のリストから死んでるものを除去する
    for (auto&& world : worlds)
        world.second->EraseRemoveIf(world.second->enemies,
            [](std::shared_ptr<Enemy>& ptr) { return ptr->isDead; });//isDead==trueの条件のものを削除

    // 自機弾の削除処理
    for (auto&& world : worlds)
        world.second->EraseRemoveIf(world.second->bullets,
            [](std::shared_ptr<Bullet>& ptr) { return ptr->isDead; });//isDead==trueの条件のものを削除

    // アイテムのリストから死んでるものを除去する
    for (auto&& world : worlds)
        world.second->EraseRemoveIf(world.second->items,
            [](std::shared_ptr<Item>& ptr) { return ptr->isDead; });//isDead==trueの条件のものを削除

}

void GameScene::Draw()
{// 描画処理
    auto pWorld = world("ステージ1"); // ワールドへのリンクを得る

    std::unordered_set<World*> drawEndSet; // すでに描いたWorldをポインタのsetでかぶらないように判定する

    for (const auto& player : players)
    {
        player.second->Draw(); // プレイヤの描画【忘れるとプレイヤ表示されない】

        auto world = player.second->world(); // プレイヤのいるワールド

        if (drawEndSet.count(world) > 0) continue; // すでに描いたWorldは描かずスキップする
        drawEndSet.emplace(world); // 描いたWorldのポインタ住所をsetに登録する

        world->map->DrawTerrain(); // マップの描画

        // 敵の描画
        for (const auto& enemy : world->enemies)
        {
            enemy->Draw();
#ifdef _DEBUG
            //enemy->DrawHitBox(); // 当たり判定の描画
#endif
        }

        // 弾の描画処理
        for (const auto& bullet : world->bullets)
            bullet->Draw();

        // アイテムの描画処理
        for (const auto& item : world->items)
            item->Draw();


#ifdef _DEBUG  // デバッグのときだけ_DEBUGが定義され描画される
        if (world->grid() != nullptr)
            for (const auto& cell : world->grid()->cellData)
                cell.second->Draw(); // グリッドの描画
#endif
    }

    // UIの描画処理はDrawとは別にすることで、他のマップなどに塗りつぶされずに一番上のレイヤでスクリーンに上書きで描ける
    for (const auto& player : players)
        player.second->DrawUI();


    // スクリーン0の内容を画面に描く
    DxLib::DrawGraph(0, 0, screenHandle0, FALSE);
    DxLib::DrawLineBox(0, 0, Screen::Width, Screen::Height / 2, GetColor(255, 0, 0));

    // スクリーン1の内容を画面に描く
    DxLib::DrawGraph(0, Screen::Height / 2, screenHandle1, FALSE);
    DxLib::DrawLineBox(0, Screen::Height / 2, Screen::Width, Screen::Height, GetColor(0, 255, 0));

}


いかがでしょう?アイテムを取ったら、画面左上にアイテムのアイコンが表示され、
プレイヤ1はBボタン(キーボードのX)で次のアイテムを選択でき、Cボタン(キーボードのC)で弾発射やブロック配置ができ
プレイヤ2はBボタン(キーボードのR)で次のアイテムを選択でき、Cボタン(キーボードのT)で弾発射やブロック配置ができたでしょうか?
次のアイテムの選択には「次、前のリンクリスト構造」が使われています。
リンクリスト構造ぐらいは便利で使い勝手があるデータの構造なので、自分で気軽に自作できるようになっておきたいですね。