Appearance
第13回 新作プロジェクトとゲームの土台
4 / 4 背景とプレイヤーを表示する
このステップの目的
Gameが入力を確認し、背景とプレイヤーを繰り返し描画します。
この回のゲームループ
第13回は静止表示を作ります。Game.hにはInitialize、MainLoopと描画用関数を宣言します。プレイヤーの移動を行うUpdateは第14回で追加します。
Game.hcpp
#pragma once
#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"
namespace dxgame::platformer
{
// 入力、プレイヤー、描画順を調整する
class Game
{
public:
// 必須画像を読み込む
bool Initialize();
// ウィンドウ終了まで更新と描画を繰り返す
void MainLoop();
private:
void Draw() const;
void DrawBackground() const;
void DrawHud() const;
Input input_;
ImageManager images_;
Player player_;
};
}入力の取得、画面の消去、描画、画面反転、待機をこの順に呼びます。待機処理は前の作品と同じ目的で使用するため、そのまま残します。
Game.cppcpp
void Game::MainLoop()
{
// 小数分の待ち時間を次フレームへ持ち越す
double nextFrame = GetNowCount();
while (ProcessMessage() == 0)
{
input_.Update();
if (input_.IsHeld(Key::Escape))
break;
ClearDrawScreen();
Draw();
ScreenFlip();
nextFrame += dxgame::config::FrameMilliseconds;
const int waitMilliseconds =
static_cast<int>(nextFrame - GetNowCount());
if (waitMilliseconds > 0)
WaitTimer(waitMilliseconds);
else
nextFrame = GetNowCount();
}
}背景の前へプレイヤーを描く
Initializeで画像を読み込み、Player::Resetへ画面中央を渡します。Drawでは背景、プレイヤー、操作案内の順に描きます。各描画関数をGame.cppの名前空間内へ定義してください。
Game.cppcpp
bool Game::Initialize()
{
if (!images_.LoadAll())
return false;
player_.Reset({
dxgame::config::ScreenWidth * 0.5f,
dxgame::config::ScreenHeight * 0.5f
});
return true;
}
void Game::Draw() const
{
DrawBackground();
player_.Draw(images_);
DrawHud();
}
void Game::DrawBackground() const
{
images_.Draw(ImageId::Background, { 0.0f, 0.0f,
static_cast<float>(dxgame::config::ScreenWidth),
static_cast<float>(dxgame::config::ScreenHeight) });
}
void Game::DrawHud() const
{
const int textColor = GetColor(255, 255, 255);
DrawString(12, 10, L"ESC: QUIT", textColor);
}次回は既存のPlayerへ左右移動を加えます。
チェックリスト
- 背景の前にプレイヤーが静止表示される。
- Escapeを押すと終了する。
- MainLoopの待機処理を残している。
参考実装
今回追加・変更するファイルの一例です。宣言や補助処理を確認したいときに開いてください。自分のコードへ必要な変更を反映し、既存の調整値や素材をそのまま上書きしないようにします。
Source/main.cpp
Source/main.cppcpp
#include "DxLib.h"
#include "Config/ApplicationConfig.h"
#include "Platformer/Game.h"
// DXライブラリを初期化し、ゲームの所有者を先に破棄する
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
ChangeWindowMode(TRUE);
SetGraphMode(dxgame::config::ScreenWidth,
dxgame::config::ScreenHeight, dxgame::config::ColorBits);
SetWaitVSyncFlag(FALSE);
SetMainWindowText(dxgame::config::WindowTitle);
if (DxLib_Init() == -1)
return -1;
SetDrawScreen(DX_SCREEN_BACK);
int result = 0;
{
dxgame::platformer::Game game;
if (!game.Initialize())
{
result = -1;
}
else
{
game.MainLoop();
}
}
DxLib_End();
return result;
}Source/DxGame/Rect.h
Source/DxGame/Rect.hcpp
#pragma once
#include "DxGame/Vector2.h"
namespace dxgame
{
// 左上座標と幅・高さで表す矩形
struct Rect
{
float x = 0.0f;
float y = 0.0f;
float width = 0.0f;
float height = 0.0f;
// 中央を原点とする矩形を作る
static constexpr Rect Centered(float width, float height)
{
return { -width * 0.5f, -height * 0.5f, width, height };
}
// 矩形を移動した値を返す
constexpr Rect MovedBy(const Vector2& offset) const
{
return { x + offset.x, y + offset.y, width, height };
}
// 矩形の中央座標を返す
constexpr Vector2 GetCenter() const
{
return { x + width * 0.5f, y + height * 0.5f };
}
// 面積を持って重なっているかを返す
constexpr bool Overlaps(const Rect& other) const
{
return
x < other.x + other.width && other.x < x + width &&
y < other.y + other.height && other.y < y + height;
}
};
}Source/DxGame/Vector2.h
Source/DxGame/Vector2.hcpp
#pragma once
namespace dxgame
{
// 2D空間の位置や速度を表す値型
struct Vector2
{
float x = 0.0f;
float y = 0.0f;
};
}Source/Platformer/Game.cpp
Source/Platformer/Game.cppcpp
#include "DxLib.h"
#include "Config/ApplicationConfig.h"
#include "Platformer/Game.h"
namespace dxgame::platformer
{
bool Game::Initialize()
{
if (!images_.LoadAll())
return false;
player_.Reset({
dxgame::config::ScreenWidth * 0.5f,
dxgame::config::ScreenHeight * 0.5f
});
return true;
}
void Game::MainLoop()
{
// 小数分の待ち時間を次フレームへ持ち越す
double nextFrame = GetNowCount();
while (ProcessMessage() == 0)
{
input_.Update();
if (input_.IsHeld(Key::Escape))
break;
ClearDrawScreen();
Draw();
ScreenFlip();
nextFrame += dxgame::config::FrameMilliseconds;
const int waitMilliseconds =
static_cast<int>(nextFrame - GetNowCount());
if (waitMilliseconds > 0)
WaitTimer(waitMilliseconds);
else
nextFrame = GetNowCount();
}
}
void Game::Draw() const
{
DrawBackground();
player_.Draw(images_);
DrawHud();
}
void Game::DrawBackground() const
{
images_.Draw(ImageId::Background, { 0.0f, 0.0f,
static_cast<float>(dxgame::config::ScreenWidth),
static_cast<float>(dxgame::config::ScreenHeight) });
}
void Game::DrawHud() const
{
const int textColor = GetColor(255, 255, 255);
DrawString(12, 10, L"ESC: QUIT", textColor);
}
}Source/Platformer/Game.h
Source/Platformer/Game.hcpp
#pragma once
#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"
namespace dxgame::platformer
{
// 入力、プレイヤー、描画順を調整する
class Game
{
public:
// 必須画像を読み込む
bool Initialize();
// ウィンドウ終了まで更新と描画を繰り返す
void MainLoop();
private:
void Draw() const;
void DrawBackground() const;
void DrawHud() const;
Input input_;
ImageManager images_;
Player player_;
};
}Source/Platformer/ImageManager.cpp
Source/Platformer/ImageManager.cppcpp
#include <Windows.h>
#include <array>
#include <string>
#include "DxLib.h"
#include "Platformer/ImageManager.h"
namespace dxgame::platformer
{
namespace
{
struct ImageDefinition
{
ImageId id;
const wchar_t* path;
};
constexpr ImageDefinition imageDefinitions[] =
{
{ ImageId::Background, L"Assets/Images/Background.png" },
{ ImageId::Player, L"Assets/Images/Player.png" },
{ ImageId::Enemy, L"Assets/Images/Enemy.png" },
{ ImageId::Tile, L"Assets/Images/Tile.png" },
{ ImageId::TileMoss, L"Assets/Images/TileMoss.png" },
{ ImageId::Goal, L"Assets/Images/Goal.png" }
};
constexpr std::size_t ImageIdCount =
static_cast<std::size_t>(ImageId::Count);
constexpr std::size_t ImageDefinitionCount =
sizeof(imageDefinitions) / sizeof(imageDefinitions[0]);
static_assert(ImageDefinitionCount == ImageIdCount,
"Register one imageDefinitions entry for every ImageId.");
bool HasCompleteImageDefinitions()
{
std::array<bool, ImageIdCount> registered = {};
for (const ImageDefinition& definition : imageDefinitions)
{
const std::size_t index =
static_cast<std::size_t>(definition.id);
if (index >= ImageIdCount || registered[index] ||
definition.path == nullptr ||
definition.path[0] == L'\0')
{
return false;
}
registered[index] = true;
}
for (bool isRegistered : registered)
{
if (!isRegistered)
return false;
}
return true;
}
}
ImageManager::~ImageManager()
{
Release();
}
bool ImageManager::LoadAll()
{
Release();
if (!HasCompleteImageDefinitions())
{
MessageBoxW(nullptr,
L"ImageIdと画像定義の登録に重複または不足があります。",
L"Image Definition Error", MB_OK);
return false;
}
for (const ImageDefinition& definition : imageDefinitions)
{
const std::size_t index = static_cast<std::size_t>(definition.id);
ImageResource& resource = resources_[index];
resource.handle = LoadGraph(definition.path);
if (resource.handle == -1 ||
GetGraphSize(resource.handle, &resource.width,
&resource.height) == -1)
{
std::wstring message = definition.path;
message += L" の読み込みまたはサイズ取得に失敗しました。";
MessageBoxW(nullptr, message.c_str(), L"Asset Load Error", MB_OK);
Release();
return false;
}
}
return true;
}
const ImageResource& ImageManager::Get(ImageId id) const
{
return resources_[static_cast<std::size_t>(id)];
}
void ImageManager::Draw(ImageId id, const dxgame::Rect& rect) const
{
const ImageResource& image = Get(id);
DrawExtendGraph(
static_cast<int>(rect.x),
static_cast<int>(rect.y),
static_cast<int>(rect.x + rect.width),
static_cast<int>(rect.y + rect.height),
image.handle, TRUE);
}
void ImageManager::DrawFrame(ImageId id, int frameIndex, int frameCount,
const dxgame::Rect& rect, bool flipX) const
{
const ImageResource& image = Get(id);
const int sourceWidth = image.width / frameCount;
const int sourceX = sourceWidth * frameIndex;
const double scaleX = static_cast<double>(rect.width) / sourceWidth;
const double scaleY = static_cast<double>(rect.height) / image.height;
DrawRectRotaGraph3(
static_cast<int>(rect.x + rect.width * 0.5f),
static_cast<int>(rect.y + rect.height * 0.5f),
sourceX, 0, sourceWidth, image.height,
sourceWidth / 2, image.height / 2,
scaleX, scaleY, 0.0,
image.handle, TRUE, flipX ? TRUE : FALSE);
}
void ImageManager::Release()
{
for (ImageResource& resource : resources_)
{
if (resource.handle != -1)
DeleteGraph(resource.handle);
resource = {};
}
}
}Source/Platformer/ImageManager.h
Source/Platformer/ImageManager.hcpp
#pragma once
#include <array>
#include <cstddef>
#include "DxGame/Rect.h"
namespace dxgame::platformer
{
enum class ImageId
{
Background,
Player,
Enemy,
Tile,
TileMoss,
Goal,
Count
};
// DXライブラリの画像ハンドルと元画像の大きさを所有する
struct ImageResource
{
int handle = -1;
int width = 0;
int height = 0;
};
// 画像の読み込み、解放、矩形描画をまとめて管理する
class ImageManager
{
public:
ImageManager() = default;
~ImageManager();
ImageManager(const ImageManager&) = delete;
ImageManager& operator=(const ImageManager&) = delete;
// 必須画像をすべて読み込む
bool LoadAll();
// 識別子から画像情報を取得する
const ImageResource& Get(ImageId id) const;
// 画像を指定した矩形へ拡大縮小して描画する
void Draw(ImageId id, const dxgame::Rect& rect) const;
// スプライトシートの1フレームを指定した矩形へ描画する
void DrawFrame(ImageId id, int frameIndex, int frameCount,
const dxgame::Rect& rect, bool flipX) const;
private:
void Release();
static constexpr std::size_t imageCount =
static_cast<std::size_t>(ImageId::Count);
std::array<ImageResource, imageCount> resources_ = {};
};
}Source/Platformer/Input.cpp
Source/Platformer/Input.cppcpp
#include "DxLib.h"
#include "Platformer/Input.h"
namespace dxgame::platformer
{
void Input::Update()
{
previous_ = current_;
for (std::size_t index = 0; index < keyCount; index++)
{
const Key key = static_cast<Key>(index);
current_[index] = CheckHitKey(ToDxKey(key)) != 0;
}
}
bool Input::IsHeld(Key key) const
{
return current_[static_cast<std::size_t>(key)];
}
bool Input::IsPressed(Key key) const
{
const std::size_t index = static_cast<std::size_t>(key);
return current_[index] && !previous_[index];
}
bool Input::IsReleased(Key key) const
{
const std::size_t index = static_cast<std::size_t>(key);
return !current_[index] && previous_[index];
}
int Input::ToDxKey(Key key)
{
switch (key)
{
case Key::Left:
return KEY_INPUT_LEFT;
case Key::Right:
return KEY_INPUT_RIGHT;
case Key::Jump:
return KEY_INPUT_Z;
case Key::Debug:
return KEY_INPUT_F1;
case Key::Escape:
return KEY_INPUT_ESCAPE;
case Key::Count:
break;
}
return 0;
}
}Source/Platformer/Input.h
Source/Platformer/Input.hcpp
#pragma once
#include <array>
#include <cstddef>
namespace dxgame::platformer
{
enum class Key
{
Left,
Right,
Jump,
Debug,
Escape,
Count
};
// キーの現在状態と前フレーム状態を管理する
class Input
{
public:
// DXライブラリからキー状態を読み取り、前フレームとの差を更新する
void Update();
// キーが押され続けているかを返す
bool IsHeld(Key key) const;
// 今フレームに押されたかを返す
bool IsPressed(Key key) const;
// 今フレームに離されたかを返す
bool IsReleased(Key key) const;
private:
static constexpr std::size_t keyCount =
static_cast<std::size_t>(Key::Count);
static int ToDxKey(Key key);
std::array<bool, keyCount> current_ = {};
std::array<bool, keyCount> previous_ = {};
};
}Source/Platformer/Player.cpp
Source/Platformer/Player.cppcpp
#include "DxGame/Rect.h"
#include "Platformer/ImageManager.h"
#include "Platformer/Player.h"
namespace dxgame::platformer
{
void Player::Reset(const dxgame::Vector2& position)
{
position_ = position;
}
void Player::Draw(const ImageManager& images) const
{
const dxgame::Rect drawRect =
dxgame::Rect::Centered(32.0f, 48.0f).MovedBy(position_);
images.Draw(ImageId::Player, drawRect);
}
}Source/Platformer/Player.h
Source/Platformer/Player.hcpp
#pragma once
#include "DxGame/Vector2.h"
namespace dxgame::platformer
{
class ImageManager;
// 第13回では中央位置と描画だけを管理する
class Player
{
public:
void Reset(const dxgame::Vector2& position);
void Draw(const ImageManager& images) const;
private:
dxgame::Vector2 position_ = {};
};
}