iPad専用アクションゲーム「スライムは投げる」

最近のエントリー

2005
3月
18

いろいろ溜め込んでいたので更新しておきました。

また、euさん、真帆さん、ドット絵掲示板のバナー有難うございました。
(早速組み込んでおきました)

風たまごさんのサイトが何時の間にかcloseになっていますね_| ̄|○

ドット絵掲示板のバナーを描いてくださった とくさん、ほっとみるくさん、たぴすさん、でゅぽるさん、写楽さん、ありがとうございましたm(_ _)m

おおやけに募集していた訳ではないためか(?)なかなか応募が無く、1ヶ月でようやく6つ集まりました!

どれか一つだけ使うというもの非常に勿体無いので、バナー画像はランダムで表示するようにしました(JavaScriptがONの人だけ)。

写楽さんのバナーの濁点が、最近やっているPSPのゲーム「ルミネス」のブロックに見えてくるのはゲームのやり過ぎですか。
1340_6_1

土曜日に書いたツールバードッキングで嫌な感じ事件ですが、無事(?)に解決いたしました。

当初は闘う前から負けていた(だってドッキング部分はいろいろなサイトで難解と言われていましたから)のですが、きよのさんの協力もあってやる気が出まして(笑)、駄目元でデバッグをしていったところ、それっぽい原因を見つけ出すことができました。

折角ですので、VC6.0を使ってどうやって問題を解決したかなどを簡単に書き記しておこうと思います。

さてさて、ドッキングしているツールバーをドラッグ&ドロップした際、ドロップした位置より下側にずれてドッキングしていまう、というのが今回の不具合です(詳しくは12日の日記を参照してください)。

なんとなく「ドロップしたあとのツールバー位置を決める処理」に不具合があるんだろうなぁとは予想出来ましたので、その辺をさぐろうと思ったのですが、ソースを見るに当たってまずは大体どんなかんじでドッキングしたツールバーなどを管理しているのか、データ構造を探ることにしました(その方がソース見ていて解りやすくなると思いまして)。

もともとCToolBarやCStatusBarのヘルプを見たときに、それらがCControlBarというクラスから派生していたのを何度か目にしていたため、「きっとメインフレームがCControlBarの一覧を持ってるんだろう」という憶測はしていました。

早速、CMainFrameの親クラスを参照機能でたどって行くとCFrameWndというクラスに

CPtrList m_listControlBars;

という名前がそのままの変数を見つけました。

ためしに一つずつポインタを取得してみてGetWindowRectでウィンドウ座標を取り、その位置に四角形を描画してみたのですが、思ったとおり、全てのツールバーやステータスバーが格納されていました。

しかし実際にはそれだけでなく、謎のクラスCDockBarというクラス(これもCControlBarの派生クラス)が上下左右に1つずつ存在していました。どうやらこのCDockBarというのがツールバーのドッキングを受け入れるバーのようです(受け入れるバーと受け入れられるツールバーが同じリスト内に存在している、というのも不思議な気がしますが)。

050314_00

#ちなみにCDockBarのコンストラクタにブレークポイントを設定してみたところ、
 上下左右の4つだけでなく、ツールバーを浮かせた状態にした際にも生成されるようです。

CDockBarがドッキングを受け入れるバーならば、そのCDockBarがドッキングしているコントロールバーの座標を持っているんじゃないかと思い、さっそくソースを見ていったところ、

CPtrArray m_arrBars;    // each element is a CControlBar

という怪しいポインタ配列を見つけたので、例によって一つずつポインタを取得してみてGetWindowRectでウィンドウ座標を取り、その位置に四角形を描画してみましたが、またも思ったとおり、ドッキングされているツールバーなどが格納されていました。
ところが、中にはNULLという情報も含まれており、どういったルールでこの配列の中身が決まっているかが気になりました。

そこで、ツールバーのドッキング状態をいろいろ変えながらm_arrBarsがどのように変化するか試したところ、上の行から順番にコントロールバーのポインタが格納されており、NULLは「コントロールバーの改行」を意味するようでした。

つまり、
050314_01

↑このような状態では、

    m_arrBars[0] = NULL;
    m_arrBars[1] = ツールバー1のアドレス;
    m_arrBars[2] = NULL;
    m_arrBars[3] = ツールバー2のアドレス;
    m_arrBars[4] = NULL;
    m_arrBars[5] = ツールバー3のアドレス;
    m_arrBars[6] = NULL;

このような配列状態になり、

050314_02

↑このような状態では、

    m_arrBars[0] = NULL;
    m_arrBars[1] = ツールバー1のアドレス;
    m_arrBars[2] = ツールバー4のアドレス;
    m_arrBars[3] = NULL;
    m_arrBars[4] = ツールバー2のアドレス;
    m_arrBars[5] = NULL;
    m_arrBars[6] = ツールバー3のアドレス;
    m_arrBars[7] = NULL;

このような配列状態になりました。

「・・・ということは、ツールバーをドロップした際、この配列への挿入場所を導き出す処理がうまくいかず、こちらが期待する位置にツールバーが配置されず、下の段にドッキングしてしまうのではないか」、というような考えにいきつきました。

データ構造は大体解りましたので(?)ようやくここからドッキング部分のソースを追うことにします。

・・・どうやって追うのか?

10秒ぐらい悩みましたが、ドッキング後には、メインフレームのRecalLayoutが呼び出されると思い、ここにブレークポイントを設置し、実際にツールバーをドラッグ&ドロップしてみたところ、見事にひっかかりました。

050314_03

そこからコールスタックウィンドウを駆使して呼び出し元を辿り、
050314_04

CDockContext::EndDrag()という関数の最初の方でまたブレークポイントを設置し、また実行してドラッグ&ドロップして停止させて、今度はステップ実行でひとつずつ怪しいところが無いか見ていきました。

しばらく追っていき、
CDockContext::EndDrag() → CFrameWnd::DockControlBar() → CDockBar::DockControlBar() →CDockBar::Insert() この関数内で先に出た「CPtrArray m_arrBars;」というポインタ配列へバーを挿入している事が判明しました。

というわけでこの関数の先頭にブレークポイントを設定して、実行→ツールバーをドラッグ&ドロップ→ブレークポイントで停止させ、
今度は変数ウォッチに、この関数内で使用している変数を表示させ、変数の変化を見ながらステップ実行していきました。

このCDockBar::Insert()という関数、完璧に理解したわけではありませんが、
配列の先頭(=一番上のコントロールバー)から順番に座標を照らし合わせながら、
ドッキングしたいコントロールバーの「順番(配列番号)」を求め、ポインタを挿入している関数だと思われます。

int CDockBar::Insert(CControlBar* pBarIns, CRect rect, CPoint ptMid)
{
    ASSERT_VALID(this);
    ASSERT(pBarIns != NULL);

    int nPos = 0;
    int nPosInsAfter = 0;
    int nWidth = 0;
    int nTotalWidth = 0;
    BOOL bHorz = m_dwStyle & CBRS_ORIENT_HORZ;

    for (nPos = 0; nPos < m_arrBars.GetSize(); nPos++)
    {
        CControlBar* pBar = GetDockedControlBar(nPos);
        if (pBar != NULL && pBar->IsVisible())
        {
            CRect rectBar;
            pBar->GetWindowRect(&rectBar);
            ScreenToClient(&rectBar);
            nWidth = max(nWidth,
                bHorz ? rectBar.Size().cy : rectBar.Size().cx - 1);
            if (bHorz ? rect.left > rectBar.left : rect.top > rectBar.top)
                nPosInsAfter = nPos;
        }
        else // end of row because pBar == NULL
        {
            nTotalWidth += nWidth - afxData.cyBorder2;
            
            nWidth = 0;
            if ((bHorz ? ptMid.y : ptMid.x) < nTotalWidth)
            {
                if (nPos == 0) // first section
                    m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL);
                m_arrBars.InsertAt(nPosInsAfter+1, pBarIns);
                return nPosInsAfter+1;
            }
            nPosInsAfter = nPos;
        }
    }

    // create a new row
    m_arrBars.InsertAt(nPosInsAfter+1, (CObject*)NULL);
    m_arrBars.InsertAt(nPosInsAfter+1, pBarIns);

    return nPosInsAfter+1;
}

この関数でどんな処理が行われているか、私の解釈で簡単に説明しますと・・・

コントロールバーというのは種類によってバーの高さが異なりますので、行の中で一番高いサイズをnWidthという変数で記憶しておいて、改行(=NULL)または表示されていないバーが来たら「高さの合計」を示すnTotalWidth変数にnWidthを足す、というような処理が入っています。

この高さ合計と、ドッキングしたい場所のY座標(中央)を比較し、条件が合えばその位置にバーを挿入、というような感じです。

#垂直状態のドッキングも考慮しているとはいえ、何故「nWidth」という名前なのかはちょっとわかりません・・・

さて、デバッグ実行しているときに気が付いたのが、改行と表示されていないバーが連続で来たときなどに、

    nTotalWidth += nWidth - afxData.cyBorder2;

という処理が実行されると、nTotalWidthが減るという事です。

nWidthは0ですが、afxData.cyBorder2は0ではないため(2だったかな?)
改行&表示されていないバーが来るたびに
nTotalWidthの値がどんどん減っていきました。
 
 
・・・これってバグじゃないの?
 
 
 
この部分の不具合疑惑が浮上し、ここをなんとか書き換えて実行しようと思いました。
 
書き換えたい気持ちは山々なのですが、ここはMFCソース内部、変えるのが困難な部分です。

誰か変えた人いないの!?ってなかんじでぐぐってみましたが、ヒットせず_| ̄|○

しょうがないので自分でやることに(^^;
 
 
まず、CDockBar::Insertという関数はvirtual関数ではないため、オーバーライドさせることが出来ません。そんなわけで、virtual関数が出てくるまで呼び出し元をたどりました。

CDockContext::StartDrag(CPoint pt);
という関数が仮想関数であることがわかったため、
この関数からCDockBar::Insertまでのすべての呼び出し処理を変更すればCDockBar::Insert処理を別のものに変えられそうでした。

具体的には
CDockContext::StartDrag() → CDockContext::Track() → CDockContext::EndDrag() → CFrameWnd::DockControlBar() → CDockBar::DockControlBar() →CDockBar::Insert()
これら全ての関数を修正する必要があるという事です(´・ω・`)ショボーン

クラス別に見て

  • CDockContext
  • CFrameWnd
  • CDockBar

これら3つがありますで、これらから新たにクラスを派生させ、そこで別関数を用意すれば良いという事です。
(CFrameWndに関してはCMainFrameという関数がありますので、クラスを新しく用意する必要はありませんね。)

で、これらのクラスから新たに

CDockContext
→CMyDockContext
CDockBar
→CMyDockBar

というクラスを作るのはまぁ出来るだろうとは思うのですが、
いままでCDockContextクラスをnewしていた部分を今度はCMyDockContextクラスをnewさせるようにしないといけないわけで、それが出来るかどうかの実験から始めました。
 
 
CDockContextのコンストラクタにブレークポイントを設定して生成タイミングなどを調査した結果、
CControlBar::EnableDocking();関数内でnewされている事が解りました。

CToolBarから新たにクラスを派生させ、そこからEnableDocking()を作る、というのが普通かもしれませんが、
なにしろツールバー以外にもコントロールバーというのは多数ありますから(ダイアログバーなど)
少し汚い手ですが、

EnableDocking();させた後で全コントロールバーに対してメンバ変数m_pDockContextを見て、インスタンスあれば一旦deleteして、CMyDockContextをnewさせ、保持させておくという手段を用いました(爆

CDockBarの方はCFrameWnd::EnableDocking時に生成されるようでしたので、その関数のソースをそのままコピペしてnew CDockBar部分を書き換えました。

これでクラスのインスタンスをごっそり変える事が出来ましたので、
CMyDockContextや、CMyDockBarのメンバ変数をコピペしたりして
各関数を実装します。

CMyDockContext::StartDrag() → CMyDockContext::TrackDebug() → CMyDockContext::EndDragDebug() → CMainFrame::DockControlBarDebug() → CMyDockBar::DockControlBarDebug() →CMyDockBar::InsertDebug()

というように呼ばれるわけです。

CMyDockContext::EndDragDebug() → CMainFrame::DockControlBarDebug()
というように、クラスが異なる呼び出しの場合には、
呼び出す際の型キャストも必要です。

    CMainFrame* pMainFrame = dynamic_cast<CMainFrame*>(m_pDockSite);
    if (pMainFrame) {
        pMainFrame->DockControlBarDebug(m_pBar, pDockBar, &rect);
    }
    else {
        m_pDockSite->DockControlBar(m_pBar, pDockBar, &rect);
    }

こんな感じで最終的にCMyDockBar::InsertDebug()が呼ばれるようにし、
これでようやく・・・ようやくCDockBar::Insert部分をいじれるようになりました_| ̄|○

早速例の「高さの合計を示すnTotalWidth変数にnWidth足す」という部分に修正を試みます。

#if 1
            if (nWidth > 0) {
                nTotalWidth += nWidth - afxData.cyBorder2;
            }
#else
            nTotalWidth += nWidth - afxData.cyBorder2;
#endif

これで実行してみたところ、ドッキング不具合は発生しなくなりました!
 
 
・・・長かった。
 
 
そんなわけで治ったっぽいプロジェクトはこちらです。
今後同じような問題にぶちあたった人のお役に立てられれば幸いです・・・。
 
 
・・・Microsoftさん、貴重な休日を返して(T_T

EDGE2のツールバーカスタマイズ周りを実装中なんですが、
カスタマイズとは関係無い部分でおかしな部分が見つかりました_| ̄|○

もし解決策、回避策などが解る技術者の方がおられましたら、
私まで連絡をお願いします。

#Microsoftに質問メール送る方法が解らなかった_| ̄|○
 プロフェッショナルサポートとか契約してないとメールで質問出来ないのかな~?
 変わりにMSへ問い合わせてくれる人でも構いません(^^ゞ

症状1 ツールバードッキングで嫌な感じ (解決したかも?)

→問題部分のみを抽出したプロジェクトファイルはこちら(40KB)
(Visual C++6.0が必要です)
VisualC持ってないけど見てみたいという方はこちらをどうぞ。

プロジェクトの主な作成手順は次の通りです。

  1. ウィザードでMFC→MDIアプリケーションを作成(他の設定はそのまま)
  2. CMainFrame::OnCreateにて
    1. ツールバーを5個以上作成する
    2. 作成したツールバーで、最初の2個だけを残し、残りのツールバーをShowControlBar(・・・,FALSE, TRUE);で非表示する
    3. また、症状がわかり易くなるように、ここでダイアログバーを一つ作成しています。(無くても症状は発生します)
  3. ビルドし、実行します。

症状の再現方法は次の通りです。

  1. EXEを実行します。
    050312_00

    ツールバー5個中、2個が表示されています。
    050312_01

  2. 上のツールバーを、ダイアログバーの下へドラッグ&ドロップしてドッキングさせます。
    050312_02

    すると、普通にドッキングします。
    050312_03

  3. さて、ここでもう一つのツールバーを、さきほどのツールバーの横へドラッグ&ドロップしてドッキングさせようとすると・・・

    050312_04

    下にずれる。
    050312_05

これが嫌な症状です。
ちなみに、がんばると(ちょっとダイアログバーへめり込むようにドロップすると)、隣へドッキングさせることも出来るのですが、また少しずらそうとすると、一段下がった場所へ勝手に配置されてしまいます。

二つしかツールバーが無い程度ならばいいのですが、それが複数になって、隣へ配置出来なくなるのは、ツールとしての使い勝手が悪くなる要因ともなりかねませんので、解決したいところです。

ツールバーは5個中2のみ表示、と書きましたが、5個全て表示したり、最初から2個しか
作らなかった場合には、これらの症状は発生しません。

症状が発生しない期待通りの実行ファイルはこちら

MFC側の不具合のような気もするのですが、もしそうであっても直したい部分ではあります^^;

(20:00 追記)
きよのさんからメールを頂きまして(有難うございます!)
Visual C++ 6.0 Enterprise Edition SP6では症状が発生しないとの情報を頂きました。
ちなみに私の環境は
Visual C++ 6.0 Professional Edition SP6です。
MFCのソースが異なるんでしょうかね~_| ̄|○
もう少し追ってみます。

(20:24 追記)
と思ったらEnterprise Editionでも症状が確認されたようです(^^;
更にはVisual C++ .NET Professional 2003でも再現したそうです_| ̄|○

ツールバーの表示方法とか作成方法が間違ってるのかなぁ、と不安になってきました。

(3/13 0:55 追記)
解決したかも?明日解決方法などを紹介します。

あ、掲示板のSISTさんサンクス!
たしかにでにくくなってましたが、いろいろやっているうちに症状が再現してました。

症状2 ツールサイズ変更でアプリがフリーズ (解決しました!)

こちらはだいぶ解りやすい症状です^^;

→問題部分のみを抽出したプロジェクトファイルはこちら(40KB)
(Visual C++6.0が必要です)
VisualC持ってないけど見てみたいという方はこちらをどうぞ。

プロジェクトの主な作成手順は次の通りです。

  1. ウィザードでMFC→MDIアプリケーションを作成(他の設定はそのまま)
  2. CMainFrame::OnCreateにて、デフォルトで作成されたツールバーの状態をいじり、ドロップダウンボタンをいくつか追加する(2つぐらいあると症状が発生しやすい)
  3. ビルドし、実行します。

症状の再現方法は次の通りです。

  1. EXEを実行します。
  2. ツールバーをドラッグ&ドロップし、浮かせます(ドッキングを解除)
    050312_06

    050312_07

  3. あとは、その浮いたツールバーの上の部分を上へドラッグするとフリーズします。
    050312_08

ドロップダウンボタンが2つぐらい有ると起こる症状のようです。
フリーズ状態でVCのブレークを実行すると、CToolBar::SizeToolBarやらCToolBar::CalcSizeで無限ループに陥っているのかなぁ、というように思えます。
各関数にvirtualがついていないので、単純にオーバーライドして値をTRACEさせるという技も使えず^^;

こちらもMFC側の不具合のような気もするのですが、もしそうであっても直したい部分ではあります。

(追記)
CToolBar::CalcDynamicLayoutがvirtualになっていましたので、オーバーライドし、その中で使用されている関数などをMFCのソースからコピペ、問題を探ったところ、
CMyToolBar::SizeToolBarの中で無限ループに陥っていることが解りました。

無限ループにならないように、勝手にreturnしてみたのですが、これで良いのやら・・・
→修正後のプロジェクトファイルはこちら

        while (sizeMin.cx < sizeMax.cx)
        {
            sizeMid.cx = (sizeMin.cx + sizeMax.cx) / 2;
            WrapToolBar(pData, nCount, sizeMid.cx);
            sizeMid = CalcSize(pData, nCount);

// ここの判定分を追加してみた by takabo soft
#if 1
            if (sizeMax == sizeMid) {
                return;
            }
#endif

            if (nLength < sizeMid.cy)
            {
                if (sizeMin == sizeMid)
                {
                    WrapToolBar(pData, nCount, sizeMax.cx);
                    return;
                }
                sizeMin = sizeMid;
            }
            else if (nLength > sizeMid.cy)
                sizeMax = sizeMid;
            else
                return;
        }

(20:00 追記)
きよのさんからメールを頂きまして(ありがとうございます)、MSのサポートサイトにて解決策があると教えていただきました!
http://support.microsoft.com/default.aspx?scid=kb;ja;190501
すっきりです(笑)

というわけでドット絵の本が発売されます。
ドット絵プロフェッショナルテクニック―ドット打ちからアニメーションまで (Game developer books)

恐れ多くもEDGEをドット絵ツールとして紹介して頂いており(他にはD-Pixed)、それだけでなく見本まで1冊頂いてしまいました^^

まだちょこっとしか読んでいませんが、ドット絵の描き方が丁寧に説明されており、ドット絵素人の私が欲しいと思うような素敵な本になっています。

興味のある方は読んでみてください。

記事検索

コミュニティ

Banner designed by 通天機様
ドット絵掲示板
ドット絵掲示板
自作の「ドット絵」を投稿することができます。ドッターさん同士の交流の場としてご利用ください。
TakaboSoft Wiki
TakaboSoft Wiki
ソフトウェアに関する質問・不具合報告・要望などを書き込むことができます。