DXライブラリとC言語でゲーム制作。今回は複数のプレイヤーや敵がいる場合など似た要素がたくさんあるときの処理の方法について。
似た要素をまとめて処理したい
前回敵を実装してきたが出てくるのは1体のみ。実際は敵が同時に複数出てくるのが当たり前。なので「似た要素」をたくさん作る必要があるのだがここで
/*----------ダメな例----------*/
//敵の構造体
typedef struct{
//中身は省略
}Enemy_t;
//以下実体宣言が続く
Enemy_t enemy1;
Enemy_t enemy2;
Enemy_t enemy3;
//…
こんな感じにコードを書いてしまうと敵の実体数だけ初期化や更新などの関数を用意する必要があったり超長文コードになったりと非常に面倒なことになってしまう。プレイヤーやNPC、エフェクトなども複数用意する場合も同様。
ということで今まで実装した関数などを使いまわせるようにしつつ似た要素をまとめて制御する「管理部」を実装していく。
管理部の実装
では実装。さっそくコードをペタリ。まず前回作成したDefine.hに敵の最大数を指定する ENEMY_NUM を書く。
Define.h
#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(); 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("(読み込みたい敵のモデルのパス)");
//初期化
//同じモデルを渡す場合はMV1DuplicatedModel()で複製したモデルを渡すこと
//直接渡すと表示がおかしくなる。ブログ主の環境だとenemy[ENEMY_NUM - 1]のモデルだけ表示される
Enemy_Initialize(&enemy[0], VGet(0.0f, 0.0f, 20.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(10.0f, 0.0f, 30.0f), VGet(0.0f, 0.0f, 1.0f), MV1DuplicateModel(EnemyModelHandle));
}
//更新
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_t enemy[ENEMY_NUM] と配列を使うのが大きなポイント。これで複数の敵を1つにまとめることができるようになる。配列の変わりにリストを使う方法もあるがそれは別の機会に。
こうすることであとはfor文で回せば一括で更新・描画ができるようになる。
あと初期化関数にてモデルハンドルを渡す部分にて、同じモデルを渡す場合は MV1DuplicatedModel() で複製したものを渡すこと。
そして Enemy.cpp/h を以下のように変更する。
Enemy.h
#ifndef DEF_ENEMY_H
#define DEF_ENEMY_H
//敵の構造体。Enemy.cppから移動
typedef struct {
int ModelHandle; //モデルハンドル
VECTOR Position; //座標
VECTOR MoveVec; //移動ベクトル
VECTOR TargetMoveDirection; //モデルが向くべき方向
float Angle; //モデルが向いている方向
float JumpPower; //Y軸方向の速度
char 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_Update(Enemy_t* enemy);
void Enemy_AngleUpdate(Enemy_t* enemy);
void Enemy_Animation(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 "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 float ENEMY_HIT_HEIGHT = 1.3f; //当たり判定用の高さ
const static int ENEMY_ANIMATION_NUM = 4; //敵のアニメーション総数
static int debug;
//敵の初期化
void Enemy_Initialize(Enemy_t *enemy, VECTOR Position, VECTOR Direction, int ModelHandle) {
//座標
enemy->Position = Position;
//モデル読み込み
enemy->ModelHandle = ModelHandle;
//向く方向の初期化
enemy->TargetMoveDirection = Direction;
enemy->Angle = 0.0f;
//ジャンプパワーの初期化
enemy->JumpPower = 0.0f;
enemy->JumpStatus = (char)NO_JUMP;
//アクションの初期化
enemy->Action = 0;
enemy->PrevAction = 0;
enemy->AttachIndex = 0;
enemy->PlayTime = 0.0f;
enemy->MoveTimer = 0;
}
//敵の更新
void Enemy_Update(Enemy_t *enemy) {
//一定時間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 == (char)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;
}
//敵の方向を変える
Enemy_AngleUpdate(enemy);
//マップとの当たり判定
{
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_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_Animation(Enemy_t *enemy) {
//プレイヤーのアニメーション処理を参考
}
//敵の描画
void Enemy_Draw(Enemy_t enemy) {
MV1DrawModel(enemy.ModelHandle);
}
//終了処理
void Enemy_Finalize(Enemy_t enemy) {
}
ポインタを使うのでそれに合わせた書き方にしているのとあと Enemy_Update() にて宣言しているMoveVecを敵の構造体の方に移動した。プレイヤーとの衝突判定で使う機会があるため。
最後にGame.cppを変更する。
Game.cpp
//EnemyMng.hをインクルード
#include "EnemyMng.h"
//中略
//初期化
void Game_Initialize() {
//中略
//敵の情報初期化
EnemyMng_Initialize();
}
//更新
void Game_Update() {
//中略
//敵の情報更新
EnemyMng_Update();
//中略
}
//描画
void Game_Draw() {
//中略
//敵の描画
EnemyMng_Draw();
//中略
}
//終了処理
void Game_Finalize() {
//中略
EnemyMng_Finalize();
}
Enemy_***() を EnemyMng_***() に変更。以降敵の処理を行いたい場合はEnemyMngを通じて行う。
プレイヤーやNPC、エフェクトなどほかのオブジェクトも複数扱う場合は同じように管理部を作って制御していく。