Simplestar Game

How do I create a 3D game ?
http://simplestar.syuriken.jp/
since 17/01/2009
  • BACK
  • HOME
  • NEXT
  • |

:::::::::::030話 Blender独自フォーマット出力C:::::::::::

ガガッ..ピー..どうも、星姫です。
C#アプリの作成途中でしたね。

「沈黙の艦隊」よりカイエダ艦長…のマネをする星姫。
さっぱり更新が進まないと思ったら「デモンズソウル」に続いて
沈黙の艦隊を全巻読んでいたからでした…重ねて申し訳ない。

ここから本題に入ります。
諸君、前回作成したC#アプリのプロジェクト「SimpleConverter」を開いているかな?
続いて中間XMLファイルを読み込み、描画用モデルデータを作成しますよ。

まずはXMLファイルの読み込みです。
XML ファイルは仕様が決められているファイル形式なので
読み込み用クラスが用意されています。
俗に言う「XMLパーサ」です。

C#には最初からXMLパーサが用意されています。
今回はC#からXMLファイルにアクセスするための
・XmlDocumentクラス
・XmlNodeListクラス
・XmlElementクラス
について、その使用方法をまとめてみます。

XMLはタグが属性を持ち、タグの中にテキストが書かれているだけなので
タグ名で検索をかけて、そのタグの属性、タグの中のテキストを取得することができればOKかと感じてます。
とりあえず、実装方法から書いてみましょう。

プロジェクトを右クリック、「追加」→「新しい項目」→「code」カテゴリの「クラス」を選択して
Converterクラスのファイルを作成します。
ファイルオープンのイベントで、ファイルパスを追加情報に渡してあげます。
以下のコードは、XMLファイルのパスから、そのファイルを読み込む関数です。
マジックコードとバージョン番号の確認をしています。

using System.Xml;          // for XML
using System.Reflection;    // for Assembly
    /// <summary>
    /// 中間モデルデータをゲームで使用するモデルデータに変換するクラス
    /// </summary>
    public partial class Converter
    {
   
        //------------------------------------------------------------------------
        // 定数定義
        //------------------------------------------------------------------------

        private const string MAGIC_CODE = "SimpleStar";    // シンプルスターデータの識別子
        private const uint DATA_FORMAT_ONLY_MESH = 1;      // メッシュのみのデータフォーマット識別子
        private const uint HEADER_SIZE = 8 * sizeof(uint);  // ヘッダサイズ
        private const uint VERTEX_SIZE_ONLY_MESH = 3 * sizeof(float); // 頂点サイズ
        private const uint TRIINDEX_SIZE = 3 * sizeof(uint);// 三角形一つのインデックスリストサイズ

        //------------------------------------------------------------------------
        // Private Data
        //------------------------------------------------------------------------

        private bool m_isFileLoaded = false;                //! 有効なデータが読み込まれているかどうか
        private XmlDocument m_xmlDoc = new XmlDocument();  //! 読み込んだXMLドキュメント
        /// <summary>
        /// XMLファイルを読み込む
        /// </summary>
        /// <param name="sender">イベント発行元</param>
        /// <param name="openE">イベント追加情報</param>
        /// <returns></returns>
        public bool ReadXMLData(object sender, MenuFrame.OpenEventArgs openE)
        {
            bool retVal = true;
           
            // ファイルの読み込み
            try
            {
                m_xmlDoc.Load(openE.FilePath);
            }
            catch
            {
                retVal = false;
            }

            // ルートノードの取得
            XmlElement root = m_xmlDoc.DocumentElement;
            if (MAGIC_CODE == root.GetAttribute("code"))
            {
                // バージョンの取得
                //@{
                Assembly assembly = Assembly.GetExecutingAssembly();
                Version appVersion = assembly.GetName().Version;
                //}@
                String fileVersion = root.GetAttribute("version");
                float version = float.Parse(fileVersion);
                if (!appVersion.Major.Equals((int)version))
                {
                    retVal = false;
                }
            }
            else
            {
                retVal = false;
            }

            return retVal;
        }

ルートのタグが持つ、属性(attribute)を取得して、マジックコードのチェック
コンバータのバージョンと一致するか見ています。

さて、オブジェクト名を指定して、モデルデータファイルを出力する関数を作りました。
その際に、オブジェクトの情報を XML ファイルから取り出します。
どうやって取り出すかを、以下に示します。

        /// <summary>
        /// メッシュデータのみ書き出す
        /// </summary>
        /// <param name="filePath">出力ファイルパス</param>
        /// <param name="objectName">出力するオブジェクト名</param>
        /// <returns>成功判定</returns>
        public bool WriteMesh(string filePath, string objectName)
        {
            uint numVerts = 0;  // オブジェクトの頂点数
            uint numFaces = 0;  // オブジェクトの三角形面数
            XmlElement elemVert = (XmlElement)m_xmlDoc.DocumentElement;    // XML頂点要素
            XmlElement elemIndex = (XmlElement)m_xmlDoc.DocumentElement;  // XMLインデックス要素

            // 名前の一致するオブジェクトを探す
            XmlNodeList elemList = m_xmlDoc.DocumentElement.GetElementsByTagName("Object");
            for (int i = 0; i < elemList.Count; ++i)
            {
                XmlElement node = (XmlElement)elemList.Item(i);
                // 名前が一致するオブジェクトを発見
                if (node.GetAttribute("name") == objectName)
                {
                    // オブジェクトのメッシュ情報を設定
                    XmlNodeList meshList = node.GetElementsByTagName("Mesh");
                    if (1 == meshList.Count)
                    {
                        XmlElement mesh = (XmlElement)meshList.Item(0);

                        // 頂点数の設定
                        XmlNodeList vertList = mesh.GetElementsByTagName("Vertices");
                        if (1 == vertList.Count)
                        {
                            elemVert = (XmlElement)vertList.Item(0);
                            numVerts = uint.Parse(elemVert.GetAttribute("num"));
                        }

                        // 三角面数の設定
                        XmlNodeList indexList = mesh.GetElementsByTagName("Indices");
                        if (1 == indexList.Count)
                        {
                            elemIndex = (XmlElement)indexList.Item(0);
                            numFaces = uint.Parse(elemIndex.GetAttribute("num"));
                        }
                    }
                }
            }

タグ名"Object"で検索して、一致したタグリストの中から、オブジェクト名が一致するモノを探します。
あとは、メッシュタグの中の頂点情報タグとインデックスリストタグの属性データを取得しています。

次に、タグ内のテキストデータを取得する方法を示します。
バイナリ形式でファイルに書き込む操作も一緒に示します。
あまり C# を分かっていないので、頭の悪い書き方をしていると思いますが、そこは気にしないでください。

            FileStream fileStream = new FileStream(filePath, FileMode.Create);
            BinaryWriter binaryWriter = new BinaryWriter(fileStream);

            // ヘッダファイル内容の作成
            StHeader header = new StHeader();
            {
                // ファイル情報の設定
                {
                    header.IsLittleEndian = true;      // エンディアン
                    header.Reserved = new char[3];
                    header.Reserved[0] = (char)0x00;    // 未使用フラグ1
                    header.Reserved[1] = (char)0x00;    // 未使用フラグ2
                    header.Reserved[2] = (char)0x00;    // 未使用フラグ3
                    header.MagicCode = new char[4];
                    header.MagicCode[0] = 's';          // マジックコード's'
                    header.MagicCode[1] = 'm';          // マジックコード'm'
                    header.MagicCode[2] = 'p';          // マジックコード'p'
                    header.MagicCode[3] = 'l';          // マジックコード'l'
                    header.Format = DATA_FORMAT_ONLY_MESH;
                }

                // メッシュ情報の設定
                {
                    uint offsetVertices = 0;
                    uint offsetIndices = 0;
                    if (DATA_FORMAT_ONLY_MESH == header.Format)
                    {
                        offsetVertices = HEADER_SIZE;
                        offsetIndices = HEADER_SIZE + VERTEX_SIZE_ONLY_MESH * numVerts;
                    }
                    header.OffsetVertices = offsetVertices;
                    header.NumVertices = numVerts;
                    header.OffsetIndices = offsetIndices;
                    header.NumFaces = numFaces;
                }

                // ファイルサイズの設定
                header.IntDataSize = HEADER_SIZE + VERTEX_SIZE_ONLY_MESH * header.NumVertices + TRIINDEX_SIZE * header.NumFaces;
            }
           
            // ヘッダファイルの書き込み
            {
                // HEADER_SIZE = 32byte
                binaryWriter.Write(header.IsLittleEndian);  // 1byte
                binaryWriter.Write(header.Reserved);        // 3byte
                binaryWriter.Write(header.MagicCode);      // 4byte
                binaryWriter.Write(header.Format);          // 4byte
                binaryWriter.Write(header.OffsetVertices);  // 4byte
                binaryWriter.Write(header.NumVertices);    // 4byte
                binaryWriter.Write(header.OffsetIndices);  // 4byte
                binaryWriter.Write(header.NumFaces);        // 4byte
                binaryWriter.Write(header.IntDataSize);    // 4byte
            }

            // 頂点情報の書き込み
            {
                string[] vertices = elemVert.InnerText.Split(new char[] { ',' });
                for (uint i = 0; i < header.NumVertices; ++i)
                {
                    binaryWriter.Write(float.Parse(vertices[3 * i + 0]));
                    binaryWriter.Write(float.Parse(vertices[3 * i + 2]));
                    binaryWriter.Write(float.Parse(vertices[3 * i + 1]));
                }
            }

            // インデックス情報の書き込み
            {
                string[] indeces = elemIndex.InnerText.Split(new char[] { ',' });
                for (uint i = 0; i < header.NumFaces; ++i)
                {
                    binaryWriter.Write(uint.Parse(indeces[3 * i + 0]));
                    binaryWriter.Write(uint.Parse(indeces[3 * i + 1]));
                    binaryWriter.Write(uint.Parse(indeces[3 * i + 2]));
                }
            }
           
            binaryWriter.Close();
            fileStream.Close();
           
            return true;
        }

頂点の出力の順番が 0 2 1 になっているのは仕様です。
理由は Blender の座標系がZ up の右手系で
DirectX の座標系が Y up の左手系だからです。

ファイルヘッダ情報は、以下に書いたとおりです。
.smplファイルを使いたいという方がもしいましたら、このファイルヘッダ情報を参考にしてください。

        /// <summary>
        /// バイナリデータのヘッダ
        /// </summary>
        private struct StHeader
        {
            private bool isLittleEndian;    // リトルエンディアンかどうか
            private char[] reserved;        // 予備フラグ(未使用時は0)
            private char[] magicCode;      // マジックコード
            private uint format;            // 頂点のフォーマット(位置だけorテクスチャ座標が2つなど)
            private uint offsetVertices;    // 頂点配列までのオフセット
            private uint numVertices;      // 頂点数
            private uint offsetIndices;    // インデックス配列までのオフセット
            private uint numFaces;          // 三角面の数
            private uint intDataSize;      // ファイルデータサイズ
        }

で、作成してみたデータ変換器がこちら↓

現在はメッシュ情報しか出力しません、今後拡張していく予定です。

SimpleConverter.zip

使い方ですが、オブジェクトのプロパティを確認して変換ボタンを押すだけで
バイナリ形式のモデルデータを出力します。

こちらがシンプルコンバータのUIのイメージです。

え!?、いきなり超初心者が C# を使い、このレベルの GUI は作れないって?
大丈夫!、前回にて C# を用いた GUI 作成の基礎を学べますので
あとは、次のサイトで必要な実装方法を調べれば1日、2日で完成します。
ユーザコントロールの作成方法と delegate を使ったイベント処理が書けるようになれば、きっと大丈夫なはず!(また雑でゴメンネ…)

DOBON.NET プログラミング道

ウィンドウサイズを変えた時に、コントロールが伸び縮みするにはどうしたらいいのかだけは、調べ方に困ると思うので
先にキーワードを残しておきます。

ユーザコントロールに対して「Dock」プロパティを Fill に、コントロール追加先にてプロパティ「アンカー」をいじります。
これだけは、触ってみないとちょっと理解できないので、頑張ってもらいたい。

コンバータを使って作成されるバイナリデータを確認するにはバイナリエディタを使用します。
私は「Stirling」を使っています。

Stirling

え、入手できない? うーん、Vector とかで落とせなかったかな?
まぁバイナリエディタなら何でもいいです。

出力されるバイナリデータの中身は以下のようになっています。

ファイルヘッダに32byte
頂点情報(float型が 3 つ ) * 頂点数
インデックスリスト(プリミティブトポロジはTriangleList unsigned int 型が 3 つ ) * 三角面の数

さて、テストでもしてみましょうか。
Blender にて次のようなモデルを作成し、SimpleStarExporter を使って XML ファイルを出力します。

今回作成した SimpleConverter を使って.smpl ファイルに変換します。
バイナリエディタで見てみると、こんな感じ↓(ファイルヘッダが32byteまであって、その先が頂点バッファなのがわかるかな?)

これを読み込んで、DirectX10のチュートリアル05を動かすと次のように
Blenderで作成したデータをDirectXで表示できるようになります。

DirectX10 の説明も含めて、詳細は次回にやります。
お楽しみに!

今回でBlender独自フォーマット出力が完了しました。

2010/09/19 初記.
2010/10/10 追加。

:::::::::::030話 Blender独自フォーマット出力C:::::::::::

  • BACK
  • HOME
  • NEXT
ホームページ制作 フリー素材 無料WEB素材
Copyright (C) Simplestar All Rights Reserved.