参考書の2章で作るゲームである2Dゲームをつくります。
Visual Studioで新しいプロジェクトを作ります。Project2でc++の空のオブジェクト。
SDL_imageをダウンロードします。SDL_imageは様々な形式の画像ファイルを扱うことができるライブラリです。
Development LibrariesのSDL2_image-devel-2.0.5-VC.zipをダウンロードします。ファイルの場所は自由でいいですが、パスが必要なので覚えます。なお私のgithub上ではプロジェクトディレクトリ一つ戻りのExternalディレクトリにあります。
プロパティ-> C/C++ -> 全般 -> 追加のインクルードディレクトリ -> 編集から インクルードディレクトリ を変更していきます。パスしだいで変わります。SDL2がインクルードできるようになります。
C:\SDL2-2.0.18\include
C:\SDL2_image-2.0.5\include
私のgithub上
..\..\external\SDL2-2.0.18\include;
..\..\external\SDL2_image-2.0.5\include;
同じように プロパティ-> リンカー->全般->追加のライブラリディレクトリ->編集にも追加します。
C:\SDL2-2.0.18\lib\$(PlatformTarget)
C:\SDL2_image-2.0.5\lib\$(PlatformTarget)
私のgithub上
..\..\external\SDL2-2.0.18\lib\$(PlatformTarget)
..\..\external\SDL2_image-2.0.5\lib\$(PlatformTarget)
同じように プロパティ-> リンカー->入力->追加の依存ファイル->編集にも追加します。
SDL2.lib
SDL2main.lib
SDL2_image.lib
同じように プロパティ-> ビルドイベント->ビルド後のイベント->コマンドライン->編集に追加。
xcopy "C:\SDL2-2.0.18\lib\$(PlatformTarget)\*.dll" "$(OutDir)" /i/s/y
xcopy "C:\SDL2_image-2.0.5\lib\$(PlatformTarget)\*.dll" "$(OutDir)" /i/s/y
私のgithub上
xcopy "..\..\external\SDL2-2.0.18\lib\$(PlatformTarget)\*.dll" "$(OutDir)" /i/s/y
xcopy "..\..\external\SDL2_image-2.0.5\lib\$(PlatformTarget)\*.dll" "$(OutDir)" /i/s/y
最後にAssetフォルダ(画像)をGame.cppなどが置かれるフォルダにコピーして配置すれば準備完了です。ReleaseもしくはDebugディレクトリにも配置すれば、完成exeファイルの起動ができます。
設計思想(ゲームオブジェクトモデル)
参考書第2章はゲーム設計の思想について書かれています。クラス階層構造による設計とコンポーネントベースによる設計を紹介したうえで、参考書で扱うゲームオブジェクトモデルがハイブリッド設計である旨が書かれています。
ゲームオブジェクトはActorクラスを継承し、コンポーネントを持つことで必要な機能を得ます。
#pragma once
#include <vector>
#include "Math.h"
class Actor
{
public:
// アクターの状態管理用
enum State
{
EActive,
EPaused,
EDead
};
// コンストラクタとデストラクタ
Actor(class Game* game);
virtual ~Actor();
// ゲームから呼び出される更新関数(オーバーライド不可)
void Update(float deltaTime);
// アクターが持つ全コンポーネントを更新(オーバーライド不可)
void UpdateComponents(float deltaTime);
// アクター独自の更新処理(オーバーライド可能)
virtual void UpdateActor(float deltaTime);
// ゲッター / セッター
//...
// コンポーネントの追加 / 消去
void AddComponent(class Component* component);
void RemoveComponent(class Component* component);
private:
// アクターの状態
State mState;
// 座標
Vector2 mPosition;//中心座標
float mScale;//大きさ(1.0fが100%)
float mRotation;// 回転の角度 (ラジアン)
std::vector<class Component*> mComponents;
class Game* mGame;
};
#pragma once
class Component
{
public:
// コンストラクタ
// updateOrderが小さいコンポーネントほど早く更新される
Component(class Actor* owner, int updateOrder = 100);
// デストラクタ
virtual ~Component();
// このコンポーネントをデルタタイムで更新する
virtual void Update(float deltaTime);
int GetUpdateOrder() const { return mUpdateOrder; }
protected:
// 所有アクター
class Actor* mOwner;
// コンポーネントの更新順序
int mUpdateOrder;
};
スプライト
スプライトは2Dゲームで使用されるオブジェクト表現に使用されます。画像を読み込んでゲーム上で保存し、ポインタでアクセスします。
SDL_Texture* Game::GetTexture(const std::string& fileName)
{
SDL_Texture* tex = nullptr;
// テクスチャがすでにマップに入っているのか?
auto iter = mTextures.find(fileName);
if (iter != mTextures.end())
{
tex = iter->second;
}
else
{
// ファイルからロード
SDL_Surface* surf = IMG_Load(fileName.c_str());
if (!surf)
{
SDL_Log("テクスチャファイルのロードに失敗 %s", fileName.c_str());
return nullptr;
}
// サーフェイスからテクスチャを作成
tex = SDL_CreateTextureFromSurface(mRenderer, surf);
// サーフェイスを消去
SDL_FreeSurface(surf);
if (!tex)
{
SDL_Log("テクスチャへの変換に失敗 %s", fileName.c_str());
return nullptr;
}
// mTexturesに作成したテクスチャを追加
mTextures.emplace(fileName.c_str(), tex);
}
return tex;
}
SDLでテクスチャを描画する関数を使い、rendererに描画します。
SDL_RenderCopyEx(
renderer, // 描画するレンダーターゲット
mTexture, // 描画したいテクスチャ
nullptr, // 描画したいテクスチャの範囲
&sdl_rect, // 出力先の矩形
-Math::ToDegrees(mOwner->GetRotation()), // 変換された回転角
nullptr, // 回転中心
SDL_FLIP_NONE);
練習問題
異なるアニメーションのサポートとループしないアニメーションを実装します。
配列にあるテクスチャの範囲を操作することで(mAnimNumBegとmAnimNumLast)アニメーションのループの範囲としています。また、mLoopFlagで挙動が変化します。
void AnimSpriteComponent::Update(float deltaTime)
{
SpriteComponent::Update(deltaTime);
if (mAnimTextures.size() > 0)
{
// フレームレートとデルタタイムに基づいて
// カレントフレームを更新する
mCurrFrame += mAnimFPS * deltaTime;
// ループさせないアニメーションは止める
if (mLoopFlag == false) {
if(mCurrFrame >= mAnimNumLast - (mAnimNumBeg - 1))mCurrFrame = mAnimNumLast - (mAnimNumBeg);
if (mLoopFlag == false)std::cout << static_cast<int>(mCurrFrame) << "\n";
}
else {
// 必要に応じてカレントフレームを巻き戻す
while (mCurrFrame >= mAnimNumLast - (mAnimNumBeg - 1))
{
mCurrFrame -= (mAnimNumLast - (mAnimNumBeg - 1));
}
}
// 現時点でのテクスチャを設定する
SetTexture(mAnimTextures[static_cast<int>(mCurrFrame) + (mAnimNumBeg - 1)]);
}
}
タイルマップを実装します。
csvファイルを読み込んで、タイルマップを格納する2次元vector(mMapDatas)を作ります。drawをこんな感じでオーバーライドすればOKです。
void TileMapComponent::Draw(SDL_Renderer* renderer) {
SDL_Rect temp;
// タイルマップからマップを描画
for (int j = mMapDatas.size() - 1; j >= 0; j--) {
for (int i = 0; i < MAP_HEIGHT * MAP_WIDTH; i++) {
temp.h = TILE_HEIGHT;
temp.w = TILE_WIDTH;
temp.x = static_cast<int>((i % MAP_WIDTH) * TILE_WIDTH);
temp.y = static_cast<int>((i / MAP_WIDTH) * TILE_HEIGHT);
SDL_RenderCopyEx(renderer,
mTileTexture,
&mMapDatas[j][i],
&temp,
0,
nullptr,
SDL_FLIP_NONE);
}
}
}
参考プログラム(課題2.3は99%このプログラム)
コメント