PR

【DXライブラリ】似た要素をまとめて処理する管理部を作る

DXライブラリとC言語でゲーム制作。今回は複数のプレイヤーや敵、画像などを一括で更新・描画する管理部の実装について。

似た要素をまとめて管理する

これまでプレイヤー、敵、画像などといろんな要素を実装してきたがどれも「1つだけ」の実装だった。実際のゲームだとどれも同時に複数出てくるのが当たり前。なので似た要素をたくさん作って更新したり描画したりする必要があるのだがここで

/*----------ダメな例----------*/
//敵の構造体
typedef struct{
  //中身は省略
}Enemy_t;

//以下実体宣言が続く
Enemy_t enemy1;
Enemy_t enemy2;
Enemy_t enemy3;
//…

こんな感じにコードを書いてしまうと実体数だけ初期化や更新などの関数を用意する必要があったり超長文コードになったりと非常に面倒なことになってしまう。

ということで今まで実装した関数などを使いまわせるようにしつつ似た要素をまとめて制御する「管理部」を実装していく。

管理部のサンプルコード

では実装。今回は例として敵を複数実体化して管理する処理を作ってみる。

まず Define.h に実体化させる敵の最大数を指定する ENEMY_NUM を追加する。

#ifndef DEF_DEFINE_H

#define DEF_DEFINE_H

//略

const static int ENEMY_NUM = 3; //敵の最大数

#endif

今回の肝である敵の「管理部」となるEnemyMng.cpp/hを新たに追加する。MngはManager(マネージャー)の略。人によってMgrだったりMnrだったりしますがここではMngで。

EnemyMng.h

#ifndef DEF_ENEMYMNG_H

#define DEF_ENEMYMNG_H

void EnemyMng_Initialize();
bool EnemyMng_GetDataForCollision(VECTOR* EmPos, VECTOR* EmPolyPos1, VECTOR* EmMoveVec, const int number); //Enemy.hから移動
void EnemyMng_Update();
void EnemyMng_Draw();
void EnemyMng_Finalize();

#endif

EnemyMng.cpp

#include "DxLib.h"
#include "EnemyMng.h"
#include "Enemy.h"
#include "Define.h"

static enemy_t enemy[ENEMY_NUM]; //敵の実体
static int EnemyModelHandle; //敵のモデルハンドル

//初期化
void EnemyMng_Initialize() {
    //敵のモデルの読み込み
    EnemyModelHandle = MV1LoadModel("読み込みたい敵の3Dモデルのパス");

    //初期化
    //同じモデルを渡す場合はMV1DuplicatedModel()で複製したモデルを渡すこと
    //直接渡すと表示がおかしくなる。ブログ主の環境だとenemy[ENEMY_NUM - 1]のモデルだけ表示される
    Enemy_Initialize(&enemy[0], VGet(10.0f, 0.0f, 10.0f), VGet(0.0f, 0.0f, 1.0f), MV1DuplicateModel(EnemyModelHandle));
    Enemy_Initialize(&enemy[1], VGet(10.0f, 0.0f, -10.0f), VGet(0.0f, 0.0f, 1.0f), MV1DuplicateModel(EnemyModelHandle));
    Enemy_Initialize(&enemy[2], VGet(0.0f, 0.0f, 20.0f), VGet(0.0f, 0.0f, 1.0f), MV1DuplicateModel(EnemyModelHandle));
}

//敵の情報を他ソースファイルから取得する用
bool EnemyMng_GetDataForCollision(VECTOR* EmPos, VECTOR* EmPolyPos1, VECTOR* EmMoveVec, const int number) {
    if (number < 0 || number > ENEMY_NUM) return false;
    
    *EmPos = enemy[number].Position;
    *EmPolyPos1 = enemy[number].Position;
    *EmMoveVec = enemy[number].MoveVec;
    return true;
}

//更新
void EnemyMng_Update() {
    for (int i = 0; i < ENEMY_NUM; i++) {
        Enemy_Update(&enemy[i]);
    }
}

//描画
void EnemyMng_Draw() {
    for (int i = 0; i < ENEMY_NUM; i++) {
        Enemy_Draw(enemy[i]);
    }
}

//終了処理
void EnemyMng_Finalize() {
    for (int i = 0; i < ENEMY_NUM; i++) {
        Enemy_Finalize(enemy[i]);
        MV1DeleteModel(enemy[i].ModelHandle);
    }

    MV1DeleteModel(EnemyModelHandle);
}

enemy[ENEMY_NUM]と配列を使うのがポイント。これで複数の敵を1つのまとめることができるようになる。あとはfor文で回せば一括で更新・描画などができるようになる。

リストを使う方法もありますがそれは後日別記事にて紹介する予定。

あとは Enemy.cpp/h を以下のように変更する。

Enemy.h

#ifndef DEF_ENEMY_H

#define DEF_ENEMY_H

//敵の構造体
typedef struct {
    int ModelHandle; //モデルハンドル
    VECTOR Position; //座標
    VECTOR TargetMoveDirection; //モデルが向くべき方向
    VECTOR MoveVec; //移動ベクトル
    float Angle; //モデルが向いている方向
    float JumpPower; //Y軸方向の速度
    int JumpStatus; //ジャンプ関連
    int Action; //現在の敵の行動
    int PrevAction; //1F前の敵の行動
    int AnimIndex; //任意のアニメーションの番号を取得するときに使う
    int AttachIndex; //アタッチするアニメーションの番号
    float TotalTime; //アニメーションの総再生時間
    float PlayTime; //アニメーションの再生時間

    int MoveTimer; //移動処理関連のタイマー
}enemy_t;

void Enemy_Initialize(enemy_t* enemy, VECTOR Position, VECTOR Direction, int ModelHandle);
void Enemy_AngleUpdate(enemy_t* enemy);
void Enemy_MoveUpdate(enemy_t* enemy);
void Enemy_Animation(enemy_t* enemy);
void Enemy_Update(enemy_t* enemy);
void Enemy_Draw(enemy_t enemy);
void Enemy_Finalize(enemy_t enemy);

#endif

Enemy.cpp

#include "DxLib.h"
#include "Enemy.h"
#include "EnemyMng.h"
#include "Map.h"
#include <math.h>
#include "Define.h"

const static float ENEMY_MOVE_SPEED = 0.05f; //敵の移動速度
const static float ENEMY_ANGLE_SPEED = 0.2f; //敵の角度変化速度
const static float ENEMY_JUMP_POWER = 0.25f; //ジャンプ力
const static float ENEMY_GRAVITY = 0.01f; //敵の重力
const static float ENEMY_MAX_FALL_SPEED = -1.5f; //敵の落下速度の下限

const static int ENEMY_ANIMATION_NUM = 4; //敵のアニメーション総数

//敵の初期化
void Enemy_Initialize(enemy_t* enemy, VECTOR Position, VECTOR Direction, int ModelHandle){
    //座標
    enemy->Position = Position;

    //モデル読み込み
    enemy->ModelHandle = ModelHandle;

    //向く方向の初期化
    enemy->TargetMoveDirection = Direction;

    //移動ベクトル初期化
    enemy->MoveVec = VGet(0.0f, 0.0f, 0.0f);

    //角度の初期化
    enemy->Angle = 0.0f;

    //ジャンプパワーの初期化
    enemy->JumpPower = 0.0f;

    enemy->JumpStatus = OBJ_NO_JUMP;

    //アクションの初期化
    enemy->Action = 0;
    enemy->PrevAction = 0;

    enemy->AttachIndex = 0;

    enemy->PlayTime = 0.0f;

    enemy->MoveTimer = 0;
}

//敵の向きを更新
void Enemy_AngleUpdate(enemy_t *enemy) {
    float TargetAngle; // 目標角度
    float SaAngle; // 目標角度と現在の角度との差

    // 目標の方向ベクトルから角度値を算出する
    TargetAngle = (float)atan2(enemy->TargetMoveDirection.x, enemy->TargetMoveDirection.z);

    // 目標の角度と現在の角度との差を割り出す
    {
        // 最初は単純に引き算
        SaAngle = TargetAngle - enemy->Angle;

        // ある方向からある方向の差が180度以上になることは無いので
        // 差の値が180度以上になっていたら修正する
        if (SaAngle < -DX_PI_F)
        {
            SaAngle += DX_TWO_PI_F;
        }
        else
            if (SaAngle > DX_PI_F)
            {
                SaAngle -= DX_TWO_PI_F;
            }
    }

    // 角度の差が0に近づける
    if (SaAngle > 0.0f)
    {
        // 差がプラスの場合は引く
        SaAngle -= ENEMY_ANGLE_SPEED;
        if (SaAngle < 0.0f)
        {
            SaAngle = 0.0f;
        }
    }
    else
    {
        // 差がマイナスの場合は足す
        SaAngle += ENEMY_ANGLE_SPEED;
        if (SaAngle > 0.0f)
        {
            SaAngle = 0.0f;
        }
    }

    // モデルの角度を更新
    enemy->Angle = TargetAngle - SaAngle;
    MV1SetRotationXYZ(enemy->ModelHandle, VGet(0.0f, enemy->Angle + DX_PI_F, 0.0f));
}

//敵の動きの更新
void Enemy_MoveUpdate(enemy_t *enemy) {
    enemy->MoveVec = VGet(0.0f, 0.0f, 0.0f);

    //一定時間X軸正方向に移動→X軸負の方向に移動を繰り返す
    if (enemy->MoveTimer < 180) {
        enemy->MoveVec = VGet(ENEMY_MOVE_SPEED, 0.0f, 0.0f);
    }
    else {
        enemy->MoveVec = VGet(-ENEMY_MOVE_SPEED, 0.0f, 0.0f);
    }

    enemy->MoveTimer++;
    if (enemy->MoveTimer > 360) enemy->MoveTimer = 0;

    //落下状態の計算
    if (enemy->JumpStatus == OBJ_FALL) {
        // Y軸方向の速度を重力分減算する
        enemy->JumpPower -= ENEMY_GRAVITY;

        // 落下速度が下限を超えていたら修正する
        if (enemy->JumpPower < ENEMY_MAX_FALL_SPEED) enemy->JumpPower = ENEMY_MAX_FALL_SPEED;

        // 移動ベクトルのY成分をY軸方向の速度にする
        enemy->MoveVec.y = enemy->JumpPower;
    }

    //マップとの当たり判定
    {
        VECTOR PolyPos1, PolyPos2;
        PolyPos1 = enemy->Position;
        PolyPos2 = VGet(PolyPos1.x, PolyPos1.y + 1.0f, PolyPos1.z);

        Map_CheckCollision(&enemy->Position, PolyPos1, PolyPos2, enemy->MoveVec, &enemy->JumpStatus, &enemy->JumpPower);
    }

    //敵の座標の更新
    MV1SetPosition(enemy->ModelHandle, enemy->Position);
}

//敵のアニメーション
void Enemy_Animation(enemy_t *enemy) {
    //プレイヤーのアニメーション処理を参考
}

//敵の更新
void Enemy_Update(enemy_t* enemy) {
    //敵の移動処理
    Enemy_MoveUpdate(enemy);

    //敵の方向を変える
    Enemy_AngleUpdate(enemy);

    //敵のアニメーション
    Enemy_Animation(enemy);
}

//敵の描画
void Enemy_Draw(enemy_t enemy) {
    MV1DrawModel(enemy.ModelHandle);
}

//終了処理
void Enemy_Finalize(enemy_t enemy) {
    
}

敵の構造体 enemy_t を Enemy.h に移動させ、EnemyMngから参照できるようにした。あとはポインタを使うのでそれに合わせた書き方にしている。

最後に Game.cpp を以下のように変更する。

Game.cpp

#include "Game.h"
#include "SceneMgr.h"
#include "DxLib.h"
#include "Input.h"
#include "Player.h"
#include "EnemyMng.h"
#include "Map.h"
#include "Camera.h"
#include "Image.h"
#include "ShaderMng.h"
#include "SoundMng.h"

const static int GAME_X = 32;
const static int GAME_Y = 32;

//初期化
void Game_Initialize() {
    //プレイヤー情報初期化
    Player_Initialize();

    //敵の情報初期化
    EnemyMng_Initialize();

    //マップ情報初期化
    Map_Initialize();

    //カメラ情報初期化
    Camera_Initialize();

    //画像の情報初期化
    Image_Initialize();

    //音声の情報初期化
    SoundMng_Initialize();
}

//更新
void Game_Update() {
    //マップ情報更新
    Map_Update();

    //敵の情報更新
    EnemyMng_Update();

    //プレイヤー情報更新
    Player_Update();

    //カメラ情報更新
    Camera_Update();

    //画像の情報更新
    Image_Update();

    if (Input_GetKeyboardDown(KEY_INPUT_ESCAPE)) { //Mキーが押されていたら
        SceneMgr_ChangeScene(eScene_Menu);//シーンをメニューに変更
    }
}

//描画
void Game_Draw() {
    DrawString(GAME_X, GAME_Y, "ゲーム画面です。", GetColor(255, 255, 255));
    DrawString(GAME_X, GAME_Y + 20, "Mキーを押すとメニュー画面に戻ります。", GetColor(255, 255, 255));

    //マップの描画
    Map_Draw();

    //敵の描画
    EnemyMng_Draw();

    //プレイヤーの描画
    Player_Draw();

    //カメラの描画
    Camera_Draw();

    //画像の描画
    //Image_Draw();
}

//終了処理
void Game_Finalize() {
    Player_Finalize();

    EnemyMng_Finalize();

    Map_Finalize();

    Camera_Finalize();

    Image_Finalize();

    SoundMng_Finalize();
}

Enemy_***() を EnemyMng_***() に変更。以降敵の処理を行いたい場合はEnemyMngを通じて行う。

プレイヤーや画像などほかのオブジェクトも同じように管理部を作っていく。

コメント

タイトルとURLをコピーしました