DXライブラリとC言語でゲーム制作。今回は敵の実装について。まず1体の敵を出現させます。
敵の実装の土台
敵の実装だが基本プレイヤーの処理の流れと同じ。初期化や更新、描画、終了処理は今まで実装してきたのを敵用の変数に書き変えれば大半はOK。
プレイヤーと違う処理をする必要があるのはまずは移動。プレイヤーと違い自動で動くのでいろいろな処理を書くことになる。決まったところを往復する、ランダムに動く、静止しているがプレイヤーを検知すると追いかけてくるetc。
他には特定のタイミングでの敵の生成・消去処理など。特定の区間に入ったら出現させる(更新を始める)、HPが0になったら退場させるといったものが必要になるはず。あとRPGとかなら敵の思考ルーチンなども必要。
今回は叩き台としてマップ上に敵を1体出してテキトーに動くようにしてみる。プレイヤーとの当たり判定や出現・退場処理はまた今度。
敵の実装
では実装。まずプレイヤーのジャンプ処理で使用したジャンプ関連の列挙体を敵にも使えるようにするため、新しく Define.h を追加して移動させる。Player.hのインクルードで済まさないのは他にもNPCなどで必要になる可能性があり、そうなると共通して参照したい定義を他のも含めて特定のヘッダーファイルにまとめた方が便利だと思ったため。
Define.h
#ifndef DEF_DEFINE_H #define DEF_DEFINE_H //プレイヤーなどのジャンプ関連の列挙体(Player.hから移動) typedef enum { NO_JUMP, //ジャンプしていない JUMP_HIGH, //大ジャンプ JUMP_LOW, //小ジャンプ FALL //落下中 }Object_Jump; extern Object_Jump object_jump; #endif
Define.hに書くのは値が固定されているもののみ(#define、列挙体enun、const static~などで宣言されたもの)。値の変更が可能な変数はDefine.h書かないように。
Player.cppに Define.h をインクルードする。
Player.cpp
//以下をインクルード #include "Define.h"
Enemy.cpp/hを新しく追加して以下のコードを貼る。
Enemy.h
#ifndef DEF_ENEMY_H #define DEF_ENEMY_H void Enemy_Initialize(); void Enemy_Update(); void Enemy_AngleUpdate(); void Enemy_Animation(); void Enemy_Draw(); void Enemy_Finalize(); #endif
Enemy.cpp
基本Player.cppの使い回し。敵の移動について今回は(見た目は)決まったところを往復するような感じにした。
#include "DxLib.h" #include "Enemy.h" #include "Map.h" #include <math.h> #include "Define.h" //敵の構造体 typedef struct { int ModelHandle; //モデルハンドル VECTOR Position; //座標 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; enemy_t enemy; 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; //敵のアニメーション総数 //敵の初期化 void Enemy_Initialize() { //座標 enemy.Position = VGet(-15.0f, 0.0f, 40.0f); //モデル読み込み enemy.ModelHandle = MV1LoadModel("(敵の3Dモデルのパス)"); //向く方向の初期化 enemy.TargetMoveDirection = VGet(0.0f, 0.0f, 1.0f); 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() { VECTOR MoveVec; //一定時間X軸正方向に移動→X軸負の方向に移動を繰り返す if (enemy.MoveTimer < 180) { MoveVec = VGet(ENEMY_MOVE_SPEED, 0.0f, 0.0f); } else { 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軸方向の速度にする MoveVec.y = enemy.JumpPower; } //敵の方向を変える Enemy_AngleUpdate(); //マップとの当たり判定 { VECTOR PolyPos1, PolyPos2; PolyPos1 = enemy.Position; PolyPos2 = VGet(PolyPos1.x, PolyPos1.y + 1.0f, PolyPos1.z); Map_CheckCollision(&enemy.Position, PolyPos1, PolyPos2, MoveVec, &enemy.JumpStatus, &enemy.JumpPower); } //プレイヤーの座標の更新 MV1SetPosition(enemy.ModelHandle, enemy.Position); } //敵の向きを更新 void Enemy_AngleUpdate() { 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(){ //プレイヤーのアニメーション処理を参考 } //敵の描画 void Enemy_Draw() { MV1DrawModel(enemy.ModelHandle); } //終了処理 void Enemy_Finalize() { MV1DeleteModel(enemy.ModelHandle); }
これを土台にいろいろと処理を追加していくのだが次にやるべきなのは「当たり判定」と「敵が複数いる場合に対応した処理」。後者は敵の数だけ初期化関数などを書くのは非常に非効率なので「管理部」を作って対応できるようにする。