インタラクティブ・ゲーム制作 <プログラミングコース> 第8回 ファイル入出力と アルゴリズム
今日の内容 やるやる言いつつできなかった 本質ではないけど見た目もこだわる アルゴリズムをガチで考える ファイル入出力 石のアニメーション 置ける場所とひっくり返し判定
こ、今度こそ! ファイル入出力
ファイル入出力の重要性 セーブ/ロードをしたいなら必須処理 ソース中にパラメータを直書きするのは 「ハードコーディング」と言われて、 現場では忌み嫌われる行為 ちょっとマップを手直しするのにもビルドが必要=Visual Studioが必要=マップも自分で作らなくてはいけなくなる=あばばばばばb
読み書きのターゲットは とりあえずテキストファイル マップデータ、キャラクタパラメータ、 シナリオデータ、セーブデータなどなど 特にCSVやタブ区切りテキストは扱えると 超便利 マップデータ、キャラクタパラメータ、 シナリオデータ、セーブデータなどなど 「テキストファイルとか見られたら 恥ずかしいし…」とか言うのは後だ!
とりあえず使うもの C++の標準機能だけでやってみよう インクルードは以下の通り 読み込みは「ifstream」クラス 書き込みは「ofstream」クラス インクルードは以下の通り #include <iostream> #include <fstream> 両方必要なので気をつける 必要に応じてusing namespace std;も
書き込みは絶望的に簡単だ! インスタンス生成時にファイル名指定 is_open()で開けているかチェック <<で書き込むデータを繋げて流し込む 最後はclose() ofstream out_file(“ファイル名”); if(out_file.is_open() == false) エラー処理; out_file << “書き込みたい文字” << endl; // int値やdouble値も<<で繋いで書ける // もちろんstring型やchar型もOK out_file << x << “,” << y << endl; //書き込み終わったらcloseして終了 out_file.close();
読み込みも ただ読み込むだけなら簡単だ! インスタンス生成時にファイル名指定 is_open()で開けて いるかチェック getline()で 1行ずつ取り出し、 処理する 最後はclose() ifstream in_file(“ファイル名”); string lineStr; vector<string> readBuffer; if(in_file.is_open() == false) { エラー処理; } // 1行ずつwhileループで読み出す while(getline(in_file, lineStr) == true) { // lineStrに1行分入る // とりあえず配列にしまうならこう readBuffer.push_back(lineStr); in_file.close();
結局何が面倒って 読み込んだ後の文字列処理なんです C++のstringクラスは基本的な機能しか ないので、ちょっと高度なことをやろう とすると、自前で頑張るか、boostなどに頼ることになります。 最低限の処理を使えるようになり、 データを取り出しやすいファイル構造を作ろう
区切り取り出し関数実装例 vector<string> fk_StrSplit(string argStr, string argToken) { vector<string> retStrArray; string::size_type curPos = 0, nextPos = 0; while(nextPos != string::npos) { nextPos = argStr.find(argToken, curPos); retStrArray.push_back(argStr.substr(curPos, nextPos-curPos)); curPos = nextPos+argToken.size(); } return retStrArray;
区切って取り出す fk_StrSplit()関数 FKUT/Misc.hで定義してあるので、 利用する場合はこれをインクルードする 第1引数の文字列を、第2引数の文字で 区切り、バラした結果を返す vector<string>型で受け取る FKUT/Misc.hで定義してあるので、 利用する場合はこれをインクルードする
整数値・実数値変換 Cの標準関数をなんだかんだで使う ただしstring型の変数は直接引数に 渡せないので、c_str()関数を使う atoi()が整数変換にあたる atof()が実数変換にあたる ただしstring型の変数は直接引数に 渡せないので、c_str()関数を使う atoi(anyStr.c_str())のようにする 実数値は誤差に厳しいものだと変換時に値がズレることがあるので注意
データ形式を仮決めしよう 次のようなテキストファイルを作ることにする 最初の行に[MY BOARD DATA]と表記する 次の行に石の個数を記述する 3行目以降から、 1行で石1個分を表すことにする X座標, Z座標, 色指定(とりあえず英語で)
データの例 [BOARD DATA] 4 -5.0, -5.0, Black 5.0, -5.0, White 5.0, 5.0, Black -5.0, 5.0, White
石のアニメーションで 使っているテクニック 細かいところだけど重要な 石のアニメーションで 使っているテクニック
ポイントになるもの update()関数の利用 三角関数おいしい! 毎フレーム呼び出して動きを更新する アニメーションの進行状態が分かるように メンバ変数を持たせておく 三角関数おいしい! 一番好きな関数です! 時間経過に応じて0~1の値を得るのに便利
動きを付ける時によくやるやり方 線形補間 半分半分また半分 色々な数式や関数の特徴を覚えておくと、 色んな場面で役立ちます! P = (1 - t)*(startPos) + t*(endPos) 半分半分また半分 P = 0.5*(nowPos) + 0.5*(endPos) 色々な数式や関数の特徴を覚えておくと、 色んな場面で役立ちます!
本日のメインディッシュ 置ける場所とひっくり返し判定
置ける場所の定義(1) 置くことで ひっくり返せること ○ ● そのためには、 置く場所の 周囲8マスに 相手の石が 置かれているのが 前提条件になる 初手で黒が置けるのは、 前提条件のみだと 右の赤いセル ○ ●
あるセルにおける 周囲の情報を得るには cellInfo[x][y]の周囲8セル 範囲外(0未満、8以上)の場合は対象外 [x-1][y-1], [x][y-1], [x+1][y-1] [x-1][y], [x+1][y] [x-1][y+1], [x][y+1], [x+1][y+1] 範囲外(0未満、8以上)の場合は対象外 これをこのままコーディングすると ださいので、座標のペアを配列に しまってループで処理できるとクール
置ける場所の定義(2) 相手の石があった 方向の先に、 自分の石があること ○ ● 端に到達した場合は その時点でNG 隣接する相手の石が 複数存在した場合は、それぞれの方向で判定 初手の場合青いセルが最終的にOKになる ○ ●
実装方針 位置と方向を与えて、その方向を調べる関数を作る 先ほどリストアップした隣接した相手の石リストを関数に流し込んでいく あとは実演!
To be continued…