最終形態を考えて設計をする

年末年始でcocos2d-xの勉強の一環でポーカーを作り、一通りの流れは把握しました。
本格的なゲームを1本作ることが目的なので、そのゲームの設計を現在考えております。


DLCの対応
StoreへのアップはiPhoneは50MB、Andoroidは100MBまでしか許可されていないため
昨今のゲームは起動後にリソースをダウンロードするものが多くなってきています。
開発中にリソースダウンロードを行うのは効率が悪いので、開発中はResourceフォルダにアクセスし
本番環境ではユーザーキャッシュフォルダにアクセスする作りにしなくてはいけません。


■整合性チェック
ダウンロードしたリソースが正常であるかどうかを確認し、正常でない場合は再ダウンロードしなくてはいけません。
最初にファイルリストを取得してCRCMD5などを使い、データ整合性を確認する必要があります。


■Atlas化
複数の画像を一つにまとめて扱う「Atlas化」をするべきです。
Atlasにする理由は以下となります。
・テクスチャー容量を少なくする
・描画速度を向上化する


「テクスチャー容量を少なくする」について
テクスチャーの幅と高さは2の乗数(2,4,8,16,32,64,128,256,512,1024,2048)で持つ必要があります。
2の乗数でないテクスチャーも使えるように見えますが、大半のビデオカードは2の乗数でないテクスチャーを指定されたとき
内部でそのサイズが収まる2の乗数のテクスチャーを作成し、コピーを行います。
従って、あらかじめ2の乗数の画像を作成して、その中に複数の画像を納めることで最終的なメモリ消費を削減できます。
また、このコピーコストはかなり高く、これもまたビデオカードにもよりますが2の乗数のテクスチャー転送に比べて数十倍の処理時間がかかりますので
ロード速度の向上にも繋がります。


「描画速度を向上化する」について
描画コストがかかる理由のひとつにDraw Callというものがあります。
これは描画命令が1回呼ぶ=1Callとなります。
しかし、100個のオブジェクトを表示すると100コール必ず呼ばれるかといわれるとそうではありません。
OpenGLDirectXやその他多くのゲーム機には、描画物をまとめて表示する機能があり
こちらを使うことで、描画速度を向上することが可能です。
ただし、描画まとめて行うためにはまとめて表示するオブジェクトに条件をつける必要があります。
・同一のテクスチャーであること
・同一のレンダリング設定であること
・同一の描画優先度であること(まとめるオブジェクト内では優先度を変更できる)


「同一のレンダリング設定であること」がわかりづらいと思いますが
これはブレンディング設定(アルファ、加算など)やテクスチャーフィルターなどが該当します。
逆にまとめるオブジェクトの中で個別に設定できることは以下となります。
・座標移動
・拡大縮小
・回転
・カラー乗算
・アルファ値変更
・テクスチャー切り取り座標の変更


■圧縮テクスチャー
スマホは画面サイズが大きく、またパレット(インデックスカラー)が使えないため1枚のテクスチャーの
サイズが非常に大きくなります。
例えばiPhone5(640x1136)に背景を1枚出そうとすると、テクスチャーのサイズは1024x2048で
更にフルカラーは1ピクセルに4byte(ARGB)消費するので
1024 x 2048 x 4 = 8388608
と、背景1枚表示するのにメモリを8MBも消費してしまいます。


この問題を解決する手法の一つに圧縮テクスチャーがあります。
圧縮テクスチャーは非可逆形式で、アルファありで1/4、アルファなしで1/8のサイズでメモリに乗るため
容量削減に大いに役立ちます。
当然デメリットもあります。
・画質が荒くなる
・環境ごとにフォーマットが異なる


「画質が荒くなる」について
仕様です。諦めて下さい。何かを得るためには何かを失うのが世の中です。


「環境ごとにフォーマットが異なる」について
これがかなり曲者です。
WindowsではDXT、iOSではPVRTC、AndroidではETC1となります。
各環境ごとに画像を用意しなくてはいけないのも面倒ですが、何よりも問題なのが
AndroidのETC1で、この形式はアルファを扱うことができません。
正確にはAndroidはアルファのある圧縮テクスチャーを使うことが不可能ではないのですが
使える機種と使えない機種が存在します。
全ての機種で確実に使えるのはこのETC1しか存在しません。
ETC1でアルファを扱う場合、通常の画像とは別にアルファマスクのグレースケール画像を用意し
シェーダを使用して、合成して描画する必要があります。


■画像の圧縮
上記の圧縮テクスチャーはメモリ展開時のお話でしたが、こちらはストレージに置くときの画像の圧縮となります。
テクスチャーをgzipで圧縮し、ゲーム内で解凍して使用します。
画像にもよりますが、40〜90%くらいの圧縮が見込め、多くのリソースを入れやすくなります。
なお、pngの場合は元々圧縮がかかっているので、ほとんどサイズは変わりません。


■Cocos Studioに対応
UIを配置するのに、Cocos Studioという便利なツールが公式から無料で提供されております。
このツールをデザイナさんに使ってもらって画面を構成し、それをそのまま表示することで
開発効率が上がるのは間違いありません。
ただし、実機のリソース配置がCocos Studioと同じでなくてはいけないため
DLC、圧縮テクスチャー、gzip圧縮と組み合わせるためにはテクニックが必要となってきます。


■今後について
DLCの対応
・整合性チェック
・Atlas化
・圧縮テクスチャー
・画像の圧縮
・Cocos Studioに対応
以上の6点を今後の課題とし、実装方法を突き詰めていきます。
といっても、頭の中ではすでに実装済みとなっており、後は検証を重ねるだけです。


現時点では
・Atlas化
・圧縮テクスチャー
の2つが検証済みとなっていますので、近いうちに記事を書きます。
ただ、圧縮テクスチャーのシェーダ化はDraw Callの数がかさんでしまうので
こちらをどのように回避するかが現在の課題です。

★追伸
今後はQiitaで記事を書いていきます。

windowサイズを変更しよう

ウインドウサイズを変更するためにはGLViewImplのcreateWithRectメソッドを使用する。
ただ、他の環境ではウインドウサイズはデバイスのサイズになるため、書き換えは以下のようになる。

    const int width = 640;
    const int height = 1136;
    auto glview = director->getOpenGLView();
    if(!glview) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
        glview = GLViewImpl::createWithRect("Game Title", Rect(0, 0, width, height));
#else
        glview = GLViewImpl::create("Game Title");
#endif
    }

また、cocos2d-xでは実際のデバイスサイズとは別に、仮想サイズを設定することができる。

    // ゲームの仮想画面のサイズを設定する。
    glview->setDesignResolutionSize(width, height, ResolutionPolicy::SHOW_ALL);

cocos2d-xをはじめよう

スマホでゲーム開発を始めるにあたり、cocos2d-xの勉強を始めました。
まだ開発環境しか整えていない状態ですが、今後開発を進めるにつれて
必要になることが多数出てくると思うので、細かくメモを取っていこうと思います。


環境は
Windows7 Professional
VisualStudio2013 Professional
cocos2d-x 3.3


ここを参考に環境構築を整えました。

C++でメモリストリームを扱う方法

C++はファイルストリームやストリングストリームはあるのに、何故かメモリストリームが存在しない!!!


今回はストリングストリームをメモリストリームとして扱う方法を紹介する。
まずは出力ストリームの使い方だ。

#include <iostream>
#include <sstream>
#include <vector>

int main()
{
    // データの書き込み
    char n[] = { 1, 3, 0, 4, 13, };           // 書き込むデータを用意
    std::ostringstream os;                    // 書き込みインスタンスを作成
    os.write(n, sizeof(n));                   // データを書き込む

    // 書き込んだデータの取得
    std::vector<char> buf(os.str().size());   // ストリームのサイズ分の領域を確保
    memcpy(&buf[0], os.str().data(), os.str().size());    // バッファにコピー

    // コピーしたデータを確認
    for (auto it = buf.begin(); it != buf.end(); ++it) {
        std::cout << static_cast<int>(*it) << " ";
    }
}

出力結果は以下となる。

1 3 0 4 13


ここまでは特に問題ないだろう。
ストリングバッファにバイナリを書き込むのが少し抵抗があるが、機能が存在しないのだから仕方ない。


次は入力ストリームである。こいつが曲者だ。
まずは次のプログラムを見て欲しい。

#include <iostream>
#include <sstream>
#include <vector>

int main()
{
    // データの書き込み
    char n[] = { 1, 3, 0, 4, 13, };         // 書き込むデータを用意
    std::istringstream is(n, sizeof(n));    // インスタンスを作成し、同時に書き込む

    // 書き込んだデータを確認する
    std::string& ss = is.str();
    for (auto it = ss.begin(); it != ss.end(); ++it) {
        std::cout << static_cast<int>(*it) << " ";
    }
}

出力結果は以下となる。

1 3

書き込んだバイナリデータの中にナル文字が入っていた場合、そこで書き込みが中断されてしまう。
次のプログラムはこの問題を回避する。

#include <iostream>
#include <sstream>
#include <vector>

int main()
{
    // データの書き込み
    char n[] = { 1, 3, 0, 4, 13, };            // 書き込むデータを用意
    std::string s(n, n + sizeof(n));           // バイナリから文字列を生成(STLのbegin()とend()の指定方法)
    std::istringstream is(s, std::istringstream::binary);    // インスタンスをバイナリ指定で生成

    // 書き込んだデータを確認する
    std::string& ss = is.str();
    for (auto it = ss.begin(); it != ss.end(); ++it) {
        std::cout << static_cast<int>(*it) << " ";
    }
}

出力結果は以下となる。

1 3 0 4 13


この方法を使うことで入力ストリームでバイナリを扱うことが可能となる。

会社設立

長くブログを更新していませんでしたが、その間に会社を設立いたしました。
正確には設立自体は2012年の末に終わっていたのだが、勤め先の会社を辞める時が2013年の9月であったため
実質1年目は何もしていません。


新しい会社に移動して1ヶ月は仕事が無く、割と焦っていましたが10月から仕事が着始め
今年の10月くらいまでは仕事に困ることはなさそうです。
アーケードメダルゲームの開発を行っており、Windowsベースの仕事と言うことで
今まではメモリをどれだけ節約できるかと言うところに四苦八苦していたわけですが
今回は3.4GBも使用できるので、特に気にすることも無く、リソースをがんがん詰めていって
現在の使用メモリは2GBをオーバーしているというね・・・。


ブログを再開したのは、会社が落ち着いたからと言うわけではなく、単にカードの明細をみていたら
はてなからお金を取られていることに気づいただけです。それまでブログの存在自体を忘れていた・・・。

ダイアログ生成時にウインドウハンドルがNULLになるが、GetLastErrorは0を返す問題の対応

WINAPIのDialogBoxやCreateDialogでウインドウを作成した場合
WM_INITDIALOGのメッセージが飛んでくるわけだが、なぜか0x0090が飛んできた後に
WM_DESTROYが来て終了してしまうというパターンが存在する。


なお海外の掲示板ではこのようなパターンも存在するらしいが今回は別のパターンがあることを紹介しよう。


使用しているダイアログの中に、dllを事前にロードしておかなくてはいけないものが存在する。
例えばリッチエディットである。
リッチエディットは"Riched20.dll"をLoadLibraryを用いて事前にロードしておく必要がある。


これを行っておかないと上記のようにWM_INITDIALOGが来る前に終了してしまう。
GetLastErrorは正常値を返すために、中々問題に気づくことができない。

労働基準法っておかしくね?

例えばネット回線をNTTと契約していて、速度おっせーとか、値段たっけーってことでKDDIに切り替えようと思ったら
NTTと契約を切って、KDDIと契約すればいいよね。
でもさ、会社が労働者を雇って、こいつつかえねーと思っても契約切れないんですよ、労働基準法ってのに邪魔されてね。
首切れるのは会社が傾いた時か、そいつが大損害出した時のみ。


これをさっきのネット契約に置き換えると、自己破産に陥るか、NTT回線がいきなり爆発して家の壁が壊れたとか
そういう状況になって初めてNTTと契約が切れるんですよ。


こんな世の中じゃ、会社も正社員を雇うのに躊躇するようになって、派遣使ったりバイト使ったりするわな。
正社員がもっと簡単に切れるようになれば就職率も、もうちょっとどうにかなるかもね。
辞めやすくすると社員を使いつぶすような形になるって言う人が出てくるかもしれんけど
今の派遣とかバイトがそんな感じだから別に何も変わらないよね。