「Qt」カテゴリーアーカイブ

Window Athletic の仕組み (その2)

前回 に引き続き Window Athletic の紹介です。 今回は、プロ生ちゃんのテーマを構成するファイルの説明と、テーマを駆動する Lua スクリプトの解説です。

pronama_screenshot

まず、テーマファイルは、以下のファイル群から構成されます。

  • README.txt
  • COPYING.txt
  • logo.png
  • events.lua
  • resource.yaml
  • 画像ファイルとか

テーマファイルとは、これらのファイルを Zip (のような) 圧縮したものになっています。
(Zip のような 圧縮、という表現になる理由は、「zlib 使って圧縮まわりを実装したんだけど、途中で手を抜いたせいで既存の Zip ツールでは展開できないから」 です。 察して頂けると助かります)
(11/28 追記: zip コマンドが扱えるようになりました!)

それぞれのファイルについて簡単に説明すると、README.txt, COPYING.txt, logo.png は、アプリのテーマ変更フォームで表示されるデータになっています。 また、resource.yaml には利用する画像ファイル名や、画像ファイルをどう組み合わせて 1つのセルにするか、という定義がなされています。

resource.yaml の抜粋。(全文へのリンクは、こちら)

- Image:
  - Background: background.png
  - Parts:
      left_0: [ 01-1.png, +16+0 ]
      left_1: [ 01-2.png, +16+0 ]
      left_2: [ 01-3.png, +16+0 ]

そして events.lua には、アプリから定期的に呼び出される関数などが定義されていて、この Lua スクリプトで、マスコット画像をどの位置に、どの画像で表示するか、といったことを制御しています。 以下では、この events.lua について具体的に説明していきます。
events.lua の API については、このページ にドキュメントがあります。

events.lua の役割は、アプリから「これの処理をしてね」っていう依頼が来た時に、それをこなすことです。 具体的には、以下の関数がアプリ側から呼び出されます。

  • initialize_theme() … テーマが読み出された時に1回だけ呼び出される関数です。
  • window_rects_changed() … デスクトップ上のウィンドウの位置や大きさが変更されたら呼び出される関数です。
  • add_actor(actor) … マスコットが追加されたときに呼び出される関数です。マスコッとの初期化などを行います。
  • remove_actor(actor) … マスコットが削除されるときに呼び出される関数です。
  • update_actor(actor) … マスコット毎に 25 msec 間隔で呼び出される関数です。 ここでマスコット位置や画像の更新を指示します。 この関数の戻り値で false を返すことでマスコットの削除を指示します。 (false を返すことで、引き続き remove_actor() が呼び出されます)

プロ生ちゃんのテーマの場合だと、各関数の役割は、以下のようになります。

  • initialize_theme() … 乱数シードの初期化。
  • window_rects_changed() … ウィンドウ情報から、地面とみなす直線を計算する。
  • add_actor(actor) … 新たなプロ生ちゃん生成の処理を行う。
  • remove_actor(actor) … 特に何もしていない。
  • update_actor(actor) … プロ生ちゃんの足元に地面があるかを判定して、落下させたり移動させたりする。 プロ生ちゃんが画面外まで移動したら false を返してプロ生ちゃんを削除する。

events.lua の全文は このリンク になります。 このスクリプトを修正して pronama.dat を作り直すと、テーマの挙動が変更できます。

最後に events.lua において注意するべき点を、2点ほど説明します。

  1. events.lua は、マスコットが作られる毎に読み込まれる。
  2. マスコット(character) 毎のデータは character:set_data(), character:data() を用いて管理する。
マスコット(character) 毎のデータは character:set_data(), character:data() を用いて管理する。

マスコットのデータを保存するために、add_character(), remove_character(), update_character() の引数に渡される character を用います。 character:set_data(key, value), data = character:data(key) といった使い方ができます。 set_data(), data() では文字しか扱えないので、数値を扱う場合には文字に変換してから処理する必要があります。

-- 文字データを保存したり読み出したりする場合
character:set_data('state', 'falling')
state = character:data('state')
数値データを保存したり読み出したりする場合
character:set_data('x', "" .. x)
x = character:data('x') + 0

ここまでで、Window Athletic の簡単な説明はおしまいになります。

何か不明な点があれば、質問して頂ければ追記などいたしますので、コメントをお願いします。

Window Athletic の仕組み (その1)

Window Athletic とは、デスクトップ上のウィンドウ情報を取得して、マスコット画像を表示するアプリです。 (http://hyakuren-soft.sakura.ne.jp/window_athletic/)
ウィンドウの上をマスコットが走ったり、落ちてくるずんだ餅を食べさせたりと、色々できます。

pronama_screenshot zunko_screenshot

この記事では、このアプリの仕組みを解説していきます。
まず、大雑把に説明すると、

  1. ウィンドウの位置情報を取得する。
  2. マスコットを表示するためのウィンドウを作成して、描画する。
  3. マスコットの位置と、どの画像を描画するかは、Lua スクリプトで制御する。
  4. ウィンドウの情報が更新されたら、アプリから Lua スクリプトに通知する。

という処理をしています。
というか、それだけです。

以下の画像を見ると、仕組みがイメージしやすいと思います。

window_athletic_frame
ウィンドウ枠を消さずに表示したときの動作

アプリ全体の動作は、アプリとテーマとの組み合わせで実現されています。
テーマの中には Lua スクリプトや画像ファイルが Zip (のような) 圧縮をされており、アプリがプレイヤーで、テーマがデータとして動作します。

具体的な実装に興味がある方は、ソースコード をご覧ください。
それでは、次回はプロ生ちゃんのテーマのスクリプト紹介をしていこうと思います。

Qt で Windows のタスクバーにアプリ名を表示させないようにする

Qt でマスコット系のアプリを作っていると、タスクバーにアプリ名が表示されるのを抑制したいことがあります。(抑制すると Alt-Tab を押したときにも表示されなくなる) そういう場合、単に

#if defined(Q_OS_WIN)
    WId wid = winId();
    LONG style = GetWindowLong(wid, GWL_EXSTYLE);
    SetWindowLong(wid, GWL_EXSTYLE, style | WS_EX_TOOLWINDOW);
#endif

を追記してもよいのですが QWidget::setWindowFlags() が SetWindowLong() を呼び出しているせいか、設定がうまく反映されなかったりしました。

仕方がないので、タスクバーへのアプリ名表示を抑制する Widget クラスに(例えば Balloon_widget) の setWindowFlags() をオーバーライドして、以下のようにしました。

void Balloon_widget::setWindowFlags(Qt::WindowFlags flags)
{
    QWidget::setWindowFlags(flags);
#if defined(Q_OS_WIN)
    WId wid = winId();
    LONG style = GetWindowLong(wid, GWL_EXSTYLE);
    SetWindowLong(wid, GWL_EXSTYLE, style | WS_EX_TOOLWINDOW);
#endif
}

親クラスのメソッドを呼び出してから、setWindowLong() を呼び出すようにしただけですが、こんな感じでうまくいきます。