コードスペランカー

ゲーム開発日誌など

当たり判定の処理

一言に当たり判定といっても、色々種類がある。
1、点と点の処理
 点同士なのでお互いの座標を比較し完全に一致すれば当たっているという極単純なもの。これ以上簡単な処理はないが点であるので範囲を表せない。マス目にはまったタイプの処理をするゲームであればこれでいい
2、円と円の処理
 円なので範囲がある。お互いの中心からの距離をだしてお互いの半径の和より小さければ当たっているという処理になる。処理速度と利便性の2つを兼ね備える使い勝手が良い処理。
3、回転しない長方形の処理
 長方形同士の衝突は回転さえしなければかなり単純化できる。処理は円よりすばやく可能だ。汎用性という意味では円の方に軍配が上がるかも。
3、回転する長方形の処理
 今までの処理のなかで、ぶっちぎりで遅い処理になる。少なくても円の処理の8倍以上使うことになるだろう。シビアな当たり判定を求めるのなら避けては通れないので、円で概略を判定して当たってそうなら本格的にこっちでやるとかって方法が良いかも。
4、その他
 三角形を組み合わせた多角形とか、円と回転する長方形とか様々なことが考えられるが、一般にどれも処理が重い。

Minecraftに学ぶ

Minecraftでは16×16マスの平面を1chunkという単位にして処理している。
キャラクタは確実に1chunkより小さいのでキャラクタのいるchunkとその周囲の処理をすれば確実に処理される。おそらくその仕様上1chunkより大きな処理を1度に行うことはできないだろうが、広大なマップのどこで処理がされても、必要な部分の処理だけど、全てのchunkの処理をしなくても良いのが魅力的なシステムだ。

ゲームデータ隠蔽の必要性

前回マップデータを暗号化してみるとか言ってみたが、ゲームデータやプログラムの隠蔽をする意味が、そもそもあるか?って疑問にぶちあたった。
想定している環境がPCである以上、俺が知っている限りの手法で隠蔽をしたとしても、ものの数日でクラックされるのが落ちだろう。クラックする手法が存在するなら、生半可な隠蔽は無意味であるといえる。
隠蔽の意味があるそするなら、ゲームとして表示していないデータであることを示す、作者の意思表示以上の意味はない。
同様にゲームそのものの改造。たとえばMODのような存在も同様にいえる。ゲームの実行形式を1つのEXEとして作成すれば、MODやパッチを作るのは面倒な作業になるが、不可能なわけでもなく、実行中のメモリをいじってしまう手法もあるので、完全な防御なんかは考えるだけ無駄ともいえる。
なので、適当にDLL化をすることで、メンテナンスと開発の利便性を向上させたほうが、有利といえるだろう。ゲームを改造してほしくないならインタフェースを非公開にしてどこかに「改造しないでください」とでも書いておけば十分だといえる。改造の難易度を上げたとしても、する人はするのだ。
もっとも、個人作成のゲームを改造して遊ぼうなんて暇な人間は多くはないので、俺が作るゲームがよっぽど面白いものにならないかぎり、ゲーム中のすべてのデータが生データだとしても、あまり問題はでないだろう。

ダンジョンの作成と読み込み

しばらく前で自動作成したダンジョンを保存して読み込んだり、手作業で作成したダンジョンを読み込んだりするのに、ダンジョンローダーが必要だとおもったので、こいつの使用を決めないといけない。
現在、ダンジョンのマップチップとして存在しているのは、壁、部屋の床、通路の床の3種類。こいつを基本にして拡張可能なフォーマットを作成することにする。
ダンジョンのデータそのものは、XMLあたりで保持すれば色々な拡張に耐えるデータが作れると思うが、XMLデータを気軽に見れてしまうのは、ちょっと萎えるので、ゲームで遊ぶだけの人にはわからない程度に暗号化もしてみようかと思う。
1、ファイルの拡張子を変更する
 割とオーソドックスな方法だけど、中身は変わらないので、もう一手間加えたい
2、適当な暗号化アルゴリズムを使ってみる
 .NET Frameworkには複数の暗号化方法が用意されているようだ。この辺が参考になるかもしれない。
カギの問題とかフォーマットの問題とか色々細かな点が出てくるが、大まかな方針としてこの方法でいいだろう

DXライブラリでのマルチスレッド

以前、DXライブラリを使ってのマルチスレッドプログラムは無理っぽいと書いたが、なんとかいけそうなので、メモしておく。

// マルチスレッドフラグ
DX.SetMultiThreadFlag(DX.TRUE);
// DXライブラリの初期化
DX.DxLib_Init();

という感じに書いておくと、非公式だけど一部のマルチスレッド処理に対応してくれるようだ。
描画関連なんかは避けたほうがよいようだが、メモリへの書き込みを行わないような処理であれば問題なさそうだし、DXライブラリを使ったの処理でなければ特に問題が出ないようだった。

壁との当たり判定

壁のように自分から動かないオブジェクトの当たり判定は、能動的に行う必要がない。なので、その他の動くオブジェクトと別の当たり判定として考えるのが妥当だと考えられる。
壁となるものが一定の大きさであると考えると、能動的に当たり判定を行うオブジェクトの現在位置から一意的に壁が所属するエリアを導き出すことは出来る。エリアの大きさを一定の値に限定するならば、エリアを定義した構造は1次元の配列で表現できる。
であれば、1エリアの呼び出しは1アクション行うことが可能になる。
実際には複数エリアにまたがってオブジェクトが存在することが考えられるので1アクションでは判定できないだろうが、全ての壁と判定を行わなくてもよくなるはずだ。

大量の当たり判定(4分木空間分割)

内容的にはとっても多いあたり判定を少なくする方法の続きとなる。色々な大きさの当たり判定がごちゃ混ぜになっている状況で、比較的早く総当り判定が出来る方法があったので、メモしておく。
その名を4分木空間分割という方法で詳しくはここで紹介されている。
けっこう込み入った内容なので理解するのに結構時間がかかるけど、実装できればかなりの処理を軽減できると思う。
ってわけでその第一歩である、XY座標をモートン順序へ変換する関数を作ってみる。といっても情報元のサイトに書かれていた内容そのままになる。

namespace SpatialPartitioning
{
    public static class MortonMath
    {
        /// <summary>
        /// XY座標をモートン順序に変換
        /// </summary>
        /// <param name="x">Xの値</param>
        /// <param name="y">Yの値</param>
        /// <returns></returns>
        public static uint XYtoMotton(ushort x, ushort y)
        {
            return (BitSeparate32(x) | (BitSeparate32(y) << 1));
        }

        /// <summary>
        /// ビットを1つおきにわける
        /// </summary>
        /// <param name="n">分けるビット列</param>
        /// <returns></returns>
        public static uint BitSeparate32(ushort n)
        {
            uint i = (uint)n;
            i = (i | (i << 8)) & 0x00ff00ff;
            i = (i | (i << 4)) & 0x0f0f0f0f;
            i = (i | (i << 2)) & 0x33333333;
            return (i | (i << 1)) & 0x55555555;
        }
    }
}

これで二次元座標上のものをモートン順序として表すことができる。モートン順序には4分木空間分割以外にも使い道がありそうなんで、覚えていて損はなさそうだ。
さて、実装するにあたって、割り当てることが出来る空間を決めなくてはいけない。理論上は無限に細かく割り当てることができるが、今回「XY座標→モートン順序」として用意した関数はuint型のものでXYそれぞれの1辺は0〜65535で表されることになる。これはルート階層を0階層としたときに15層までを表現可能としている。なので、最大階層を15層そして実装するのがよいと思われる。
しかし、15層ともなると空間の数が4294967295という大きな値になるので各空間に割り当てられるメモリのことを考えると、実際に使う場合にはもっと少ない状態にするのがよいと思える。
また、4分木空間分割をそのまま導入した場合には、欠点もある。第1層での作成された境界をまたぐようなオブジェクトはどんなに小さいオブジェクトであっても全てのオブジェクトと判定を行う必要があることだ。
この問題を解決する場合別な視点からの空間分割を取り入れる必要があるとおもえる。