さて、面倒な前準備が終わりましたので、見える部分をドンドン作っていきましょう。
まずはプレイヤーを作ってみます。
Playerクラスを作り、先ほど作ったパッドクラスの入力に応じて前後左右に動かせるようにしてみます。
さて、ここで具体的なコーディングに入っていく前に一つゲームプログラミングの設計の大きな基本を紹介します。
ゲームプログラミング設計に登場するパーツのほとんどはTaskという抽象クラスを継承して作ります。
Task.h
#pragma once class Task { public: Task() = default; virtual ~Task() = default; virtual bool update() = 0; virtual void draw() const = 0; };
ほぼ全てのパーツはupdateとdrawで出来ています。
従ってほぼ全てのクラスはこのクラスを継承して実装することにします。
何故このような共通の抽象クラスを利用するかというと、
Taskを継承した敵クラス
Taskを継承した自機クラス
Taskを継承したエフェクトクラス
Taskを継承した弾クラス・・・と様々なクラスのオブジェクトが出てきますが、
これらが共通の抽象クラスから派生していれば全てまとめてTaskポインタで所持することができます。
更にそのすべてにupdateやdraw等の指示が出来るので何かをまとめて処理したり、
または何かの処理をどこかに委譲するような場合が楽になるためです。
今後出てくるゲームの登場人物として必要なものはほぼ全てTaskクラスを継承して作りましょう。
さて、Playerクラスの実装を見て行きます。
Player.h
#pragma once #include "Task.h" class Player : public Task { public: Player(); virtual ~Player() = default; bool update() override; void draw() const override; private: void move(); float _x, _y; //座標 int _image; //画像ハンドル };
Player.cpp
#include "Player.h" #include "Pad.h" #include <DxLib.h> const static float SPEED = 9; Player::Player() : _x(100), _y(100) { _image = LoadGraph("../dat/image/player/body/seishi0.png"); } bool Player::update() { move(); return true; } void Player::draw() const { DrawRotaGraphF(_x, _y, 1.0, 0.0, _image, TRUE); } void Player::move() { float moveX = 0, moveY = 0; if (Pad::getIns()->get(ePad::left) > 0) { moveX -= SPEED; } if (Pad::getIns()->get(ePad::right) > 0) { moveX += SPEED; } if (Pad::getIns()->get(ePad::down) > 0) { moveY += SPEED; } if (Pad::getIns()->get(ePad::up) > 0) { moveY -= SPEED; } if (moveX && moveY) { //斜め移動 moveX /= (float)sqrt(2.0); moveY /= (float)sqrt(2.0); } if (Pad::getIns()->get(ePad::slow) > 0) {//低速移動 moveX /= 3; moveY /= 3; } _x += moveX; _y += moveY; }
moveではパッドの入力状態に応じてプレイヤーの座標を動かしています。
ここでポイントなのが、斜め移動の時はルート2で割っていることです。
前後左右に移動する時は、1フレームにSPEEDピクセルずつ動くわけですが、
斜めに移動するときはこの計算をしなければ速度がルート2倍になってしまいます。
何故なら45°の正三角形は底辺と高さが1の時、斜辺はルート2になります。
斜辺分ほど移動するので、斜めに移動する時だけ少し早く移動するように見えてしまいます。
従って斜めに移動する時はルート2で割らなければなりません。
また、低速移動ボタンが押されていたら移動分を÷3してゆっくり移動する様にしています。
このインスタンスを保持しているのはGameSceneクラスです。
GameScene.h
#pragma once #include "AbstractScene.h" #include <memory> #include "Player.h" class GameScene : public AbstractScene { public: const static char* ParameterTagStage;//パラメータのタグ「ステージ」 const static char* ParameterTagLevel;//パラメータのタグ「レベル」 GameScene(IOnSceneChangedListener* impl, const Parameter& parameter); virtual ~GameScene() = default; void update() override; void draw() const override; private: std::shared_ptr<Player> _player; };
GameScene.cpp
#include "GameScene.h" #include <DxLib.h> #include "Macro.h" using namespace std; const char* GameScene::ParameterTagStage = "ParameterTagStage";//パラメータのタグ「ステージ」 const char* GameScene::ParameterTagLevel = "ParameterTagLevel";//パラメータのタグ「レベル」 GameScene::GameScene(IOnSceneChangedListener* impl, const Parameter& parameter) : AbstractScene(impl, parameter) { _player = make_shared<Player>(); } void GameScene::update() { _player->update(); } void GameScene::draw() const { _player->draw(); }
updateでupdate、drawでdrawをしている単純な処理を追加しただけです。
今までLooperからTitleSceneを作って格納していましたが、これからしばらくはTitleSceneは使わずGameSceneを作っていきます。
実行結果