クラスメモPart5
Mapを作る経過ではまったことをメモ。
まず、.txtをbinaryモードで読み込むと、
\r\nが残る。
これは、char型の
\r : 10 \n : 13
にあたる。
あとは、Mapではrow,colではわかりにくいので、x,yとか、height,widthであらわすが、その時、
二次元配列上の”たて”、”よこ”と混同してしまってすごくはまった(わかってはいるんだけどね。)
どう対策を立てようか??やっぱり一貫して、row,colにしてしまったほうがわかりやすいかな??
やっぱり本を見てやると、わかった気になってしまって実は全然わかっていないということが往々にしてあるので、
ある意味でできるのに時間がかかって良かったかなと思う。
デバッグの使い方も何となくだが、分かってきた。こういうのは、実際に自分でやってみないとわからないところだし。
クラスメモPart3-1
今回はRPG風のゲームを簡単ながら作りたいかな、と考えているので、次はMapで。
とりあえず、典型的なやつを一回作りたいので、行き当たりばったりでやってみる。
マップを作るので、マップチップの性質を列挙型に定義する。
#include "Array2D.h" //宣言だけではだめで、インクルードする必要がある。 class Map { public: Map(const char* filename); ~Map(); int getHeight(); int getWidth(); void Show(); private: //列挙型の値は int 型、unsigned int 型の変数に代入することができる //つまり /* Object o = OBJ_FLOOR; int a = o; //OK unsigned b = o; //OK */ //が可能 //unsigned int : 0 〜 65535 //int : -32768 〜 32767 enum Object { //列挙子の値はint型 OBJ_WALL =(0), OBJ_FLOOR =(1<<0), OBJ_EQUIP = (1<<1), }; int mHeight; int mWidth; Array2D<Object> mStageData; }; //ファイルから読み込むようにした。
この時、列挙型Objectは0,1,2しか取りえないので、たとえば、
「壁にアイテムが埋まっている」
みたいなのをこのままでは表現できない。
一つの方法としては、列挙型にありうる可能性のものをすべて書いておくという方法がある。
enum Object { //列挙子の値はint型 OBJ_WALL =(0), OBJ_FLOOR =(1<<0), OBJ_EQUIP = (1<<1), OBJ_EQUIP_ON_WALL = (1<<2) };
こうすると、もっと複雑になってしまうとき、「この状態は何て名前の列挙子にしたんだろうか」といちいち参照しなければ
ならなくなってしまう。「壁にアイテムが埋まっているけど、実は罠がある」みたいなのは3つの複合系と考えられるが、
とてもじゃないが記述しきれない。
一つの解決法としては、
enum Object { //列挙子の値はint型 OBJ_WALL =(0), OBJ_FLOOR =(1<<0), OBJ_EQUIP = (1<<1), }; unsigned mFlag; //ここにObjectの列挙子を突っ込む int mHeight; int mWidth; Array2D<unsigned> mStageData;
と書いてしまうという方法だ。しかしながら、unsignedをtypenameにしているので、unsigned型ならなんでも好き勝手に
値を格納できてしまう。(何とかして、「これはObject型の値を含んでいるんだよ。」と知らせたい。
そこで
unsignedではなくて、ObjectとmFlagを構造体として一つにまとめ、その構造体によって、Array2Dを動かそう.
#include "Array2D.h" //宣言だけではだめで、インクルードする必要がある。 class Map { public: Map(const char* filename); ~Map(); int getHeight(); int getWidth(); void Show(); private: //これをcpp側に書くとエラーになる。(たぶん、変数の定義しかしていないので)enumと同じように内部に書く必要がある? struct FlagOpr { enum Object { //列挙子の値はint型 OBJ_WALL =(0), OBJ_FLOOR =(1<<0), OBJ_EQUIP = (1<<1), }; unsigned flags; //これで列挙子のフラグが排他的でなくなる。 }; int mHeight; int mWidth; Array2D<FlagOpr> mStageData; };
これでよさげ。
あとは、フラグを動かせるような関数を作っておけば、可読性が増しそうだ.C++ではstructにも関数は置けるが、structはやめてclass
で定義しよう。(そうすれば、cpp側に処理を記述でき、ヘッダファイルに書かなくて済むというメリットもある。)
ほかのやり方としては、上塗りするという方法がある。
下-------------------------------上
床 壁、森、池など 家 アイテム 人
ごとにそれぞれクラスを作り、順々に上塗りする。
今のところ、マップチップの種類を2,3種類に限定しているので、この方法は後でする。
クラスメモPart2
本に沿って、Fileクラスも自分で作る。
//File.h class File { public: File(const char* filename); ~File(); int getSize() const; //constメンバ関数はなるべくつける。 unsigned getUnsigned() const; const char* getData() const; private: int mSize; char* mData; }; //File.cpp #include "File.h" #include <fstream> using namespace std; File::File(const char* filename) : mSize(0),mData(0) { ifstream in(filename, ifstream::binary); in.seekg(0,ios::end); mSize = static_cast<int>(in.tellg()); //C言語でのキャストよりもC++流に指定したほうが良い。 in.seekg(0,ios::beg); mData = new char[mSize]; in.read(mData, mSize); in.close(); } File::~File() { delete mData; } int File::getSize() const { return mSize; } const char* File::getData() const { return mData; } //getUnsignedはまだ実装していない。
次は、Mapクラスの原型を作りたいと思う。
クラスメモPart1-2
Part1をテンプレートで書いてみる。
#ifndef INCLUDED_ARRAY2D_H__ #define INCLUDED_ARRAY2D_H__ template <typename TYPE> class Array2D { public: Array2D(int i,int j); ~Array2D(); void Show(); //表示させる関数 TYPE& operator()(int i, int j); //参照つけないと、代入の時「式は変更可能な左辺値である必要があります」と出てくる。 private: TYPE* mArray; int mRow; //行 int mCol; //列 }; #include "GameLib/Framework.h" //#include "Array2D.h" using namespace GameLib; template <typename TYPE> Array2D<TYPE>::Array2D(int i,int j) : mRow(i), mCol(j),mArray(0) { mArray = new TYPE[mRow*mCol]; } template <typename TYPE> Array2D<TYPE>::~Array2D() { delete[] mArray; mArray = 0; } template <typename TYPE> void Array2D<TYPE>::Show() { for(int i=0;i<mRow;i++) { for(int j=0;j<mCol;j++) { cout << this->operator()(i,j) << "a"; //呼び出されたインスタンスtestのtest(i,j)を返す。 } cout << endl; } } template <typename TYPE> TYPE& Array2D<TYPE>::operator()(int i,int j) { return mArray[i*mCol+j]; //mArray[i*mCol+j]を値戻しではなく、エイリアスをつくって、そっくりそのまま渡す。 //書き込まれる時は、mArray[i*mCol+j]の箱に書き込まれ、(このことから、関数の後ろにconstを付けてはならない。) //読み込まれる時は、mArray[i*mCol+j]の値を参照する。 } #endif
注意:テンプレートの実装と宣言は分離できない。vc++がtemplateに関してはinclusion-modelを採用しているため、実体化したいtemplateの実装はheaderに書かなければならない。(詳しくはよくわからない。)
インラインにする必要のないとき、
template <typename TYPE> TYPE& Array2D<TYPE>::operator()(int i,int j) { // }
という風にかく。特にArray2D
テンプレートってできることの幅がかなり増える割に書き方が恐ろしく単純だよね。
でも、クラスの顔であるヘッダーに見せるのだから、バグが起こったときに問題を発見するのが難しいという欠点がありそう。
c++ における"&"について
参照&
ポインタがメモリ上に実体をもったひとつの変数であるのに対し、
参照はあくまで変数の別名であり、メモリ上に実体がない点が異なります。
int a = 5; int b = 10; int& Ref = a; //必ず初期化する必要がある。参照先は配列型であってはならない。 Ref = 6; //Refを書き込めば、aが6になる。(Refとaとは同じ) //ポインタでint* p = a; *p = 6; とするのと同じ。 int& Ref2 = b; //Ref2 = a; さすものを変えることができない。 a = Ref2; //ref2はbと同じ。
参照戻し(オペレータのオーバーロード時によく使う。それ以外の使用頻度は少ない。)
class C { C(); ~C(); int operator(int i,int j) { //処理 } これは値を格納するとき(つまり左辺のとき)うまくいかない。 int main(void) { C test; test(2,2) = 4; } //こうすれば、test(2,2)はただの定数値なので、式は変更可能な左辺値である必要があります。といわれる。 //ここを値と認識されたくない。
class C { C(); ~C(); int& operator(int i,int j) { //処理 } int main(void) { C test; test(2,2) = 4; //int& ref = 4;の時と同じような動作になる。 int a = test(2,2); //returnの変数の値を直接渡せる。 //ポインタのようにint* a = (int*型)とするとかなり煩わしい。 }
クラスメモPart1
ゲームプログラマになる前に覚えておきたい技術をやっているが、自分の力不足を認識したので、第一部から第2部に移るときに
なんかいろいろ作ったほうがいいだろうと思い、メモ。この本のライブラリを使用。
まず、動的に生成される二次元配列クラスを作る。
普通にやるとこれだけの手順を踏む必要がある。
//動的生成 int** zTest; zTest = new int*[row]; for(int i=0;i<row;i++) { zTest[i] = new int[col]; } //delete for(int i=0;i<row;i++) { delete[] zTest[i]; } delete[] zTest;
いかにも、めんどくさい。
めんどくさい理由の一つ目はクラスにしていないため、明示的にnew,deleteしなければならないこと。
クラスだと、コンストラクタ、デストラクタで自動的に呼び出されるので、new,deleteを忘れてしまうという心配をしなくて良い。
もう一つの理由は、二次元配列にしているため、コードが一次元配列の時よりもずいぶん長くなってしまうことだ。
一応、二次元配列を一次元配列に書き換える方法がある。
arr2[i][j] <----> arr1[i*col + j]
の対応を付ければいいが、いちいちコードの中にこれを書くのはめんどくさいし、可読性が落ちるだろう。バグも増える。
ということで、内部では、一次元配列で処理しつつ、実際使うときにあたかも二次元配列を動かしているかのように作るクラスを作ろう。
まず、arr2(i,j)の形で処理できるようにしたい。
また、動的生成なので、new,deleteをコンストラクタ、デストラクタに書かねばならない。
//Array2D.h #ifndef INCLUDED_ARRAY2D_H__ #define INCLUDED_ARRAY2D_H__ class Array2D { public: Array2D(int i,int j); ~Array2D(); void Show(); //表示させる関数 int& operator()(int i, int j); //参照つけないと、代入の時「式は変更可能な左辺値である必要があります」と出てくる。 private: int* mArray; int mRow; //行 int mCol; //列 }; #endif
//Array2D.cpp #include "GameLib/Framework.h" #include "Array2D.h" using namespace GameLib; Array2D::Array2D(int i,int j) : mRow(i), mCol(j),mArray(0) { mArray = new int[mRow*mCol]; } Array2D::~Array2D() { delete[] mArray; mArray = 0; } void Array2D::Show() { for(int i=0;i<mRow;i++) { for(int j=0;j<mCol;j++) { cout << this->operator()(i,j) << "a"; //呼び出されたインスタンスtestのtest(i,j)を返す。 } cout << endl; } } int& Array2D::operator()(int i,int j) { return mArray[i*mCol+j]; //mArray[i*mCol+j]を値戻しではなく、エイリアスをつくって、そっくりそのまま渡す。 //書き込まれる時は、mArray[i*mCol+j]の箱に書き込まれ、 //読み込まれる時は、mArray[i*mCol+j]の値を参照する。 }
#include "GameLib/Framework.h" #include "Array2D.h" using namespace GameLib; namespace GameLib{ void Framework::update(){ int row = 4; int col = 5; Array2D test(row,col); for(int i=0;i<row;i++) { for(int j=0;j<col;j++) { test(i,j) = i*j; } } for(int i=0;i<row;i++) { for(int j=0;j<col;j++) { cout << test(i,j) << " "; } cout << endl; } test.Show(); } }
thisポインタは、そのクラスのインスタンス自体のポインタ
thisはポインタなので * を使えば、インスタンス自身を間接参照できる。
ポカ集 c++
名前を付けるのになぜか数字から始まる名前にする。