藤川帳

Studio F#の音楽とか吉里吉里とか担当の藤川ヒロヒコのブログ。
<< March 2024 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 >>
 
Studio F# on Youtube
ARCHIVES
みんなのブログポータル JUGEM
SPONSORED LINKS
 
スポンサーサイト

一定期間更新がないため広告を表示しています

- | | - | - | pookmark
Vista

藤川は普段Mac使いなんですが、Windowsも仕事やゲーム開発で使ってます。Windowsは主に2000とXPを使ってて、Vistaはまだ導入してません。ですが、「『渡り鳥』はWindows Vistaサポート外です」というのは流石にそろそろマズいので、Home Premium程度でいいからとりあえず入れてみるかと思って店を見てみたんですが、相変わらず高いですね、Windowsは。びっくりだ。びっくりしすぎて、買わずに帰ってきました。

いやまあそれは冗談なんですが、まずはうちのパソコンのスペックを確認してみないと。ちゃんと動くのかな。それにしてもOSだけでメモリ1GB以上必要だなんて、すごい時代になったもんだ。

『砂』がVistaでも動くというのは知ってるんですが(ある人に聞いた)、というか吉里吉里自体Vistaでもある程度ちゃんと動くんですけど、やっぱり何かと面倒みたいです。手元に確認環境があるのとないのとでは大違いなので、7月までにはちゃんと環境を整えたいです。ああ、でも、そのパソコンで使ってる他のアプリがVistaに対応してるのかな。なんと厄介な。

「既読スキップ」が未読部分までスキップする件

こんばんは。藤川です。最近、普段からは考えられないほどの頻度(頻度ってほどでもないですが)でブログを更新していますが、世間ではこういうのを逃避行動とかいうそうです。

ところで、『砂』には「メッセージスキップ」という名前で付いている、いわゆる既読スキップ機能ですが、これは当然ながらKAG SYSTEM標準のものを利用しています。とても便利でいいんですが、確認作業をしていて、ふとあることに気付きました。メッセージスキップをすると未読部分までスキップしてしまうという、割とアレな不具合です。

といっても、これはKAG SYSTEMのせいでも吉里吉里のせいでもなくて、『砂』特有の不具合です。どういうことかというと。

『砂』は章立てのあるストーリーなので、シナリオを章ごとにフォルダ分けして格納しています。で、シナリオファイルは、章ごとに通し番号のファイル名にしているのですが、これが原因なんですね。

KAGのドキュメントの「未読/既読処理について」の項にある通り、KAG SYSTEMは既読フラグを記録するシステム変数名として、シナリオファイル名を用いています。要するに、上のような方式だと、章なんて関係なく通し番号だけで既読フラグが記録されてしまう訳です(実際にはラベル名も使われますが、『砂』の場合は諸般の事情でラベル名も統一してあるので、やっぱり同じ変数名になってしまうのです)。

これを解決する方法として、すぐ思いつくのは二通り。まず、ファイル名に章番号を挿入すること。でもこれは全てのファイルのjump先を書き直さなくてはならないので、胃がひっくり返りそうです。もう一つの方法は、KAG SYSTEMに少しだけ手を加えること。すなわち既読フラグ記録変数名の決定方法の変更です。

面倒くさがりの藤川としては、当然後者の手でいくことにしました。これはMainWindow.tjsのsetRecordLabel()をオーバーライドするだけなので、大変お手軽です。このメソッドが"trail_hoge_piyo"という文字列を作っている箇所に章番号を挿入して、"trail_1hoge_piyo"ってな感じになるようにしました。

もし同じような問題で悩んでいる方がいらっしゃれば、ご参考までにどうぞ。

メモリその後

こんばんは。藤川です。今夜は随分外が明るいです。

メモリの件、同じ方(多分)から再びアドバイスを頂戴しまして、曰く吉里吉里の実行コアだけ入れ替えてみては如何。かたじけないことに既に体験版で動作の確認までしていただいたようでした。早速2.26をダウンロード。最初いきなりハードウェア例外が出てビビりましたが、dllのリンクでこけているだけでしたので、使っている各種プラグインも移植して再起動。問題なく動きました。

とりあえず、あれですね。通常動作が急に軽くなってびっくりしました。や、私がびっくりしててもアレなんですが(汗)。吉里吉里は日夜進歩しているのであるなぁと詠嘆した次第です。最初にいただいた「System.doCompact()を使ふ」というアドバイスも無事に成功しました。ありがとうございました。

でも、まだ微妙に何者かがリークしている模様。ホストの訪問履歴かとも思いましたが、どうも違うみたいです(一度終了してから起動しなおしてあるデータをロードすると、履歴はきちんと復帰されているが、メモリ使用量はそれほど増えないから)。何だろうな。ホストの構造データを読んだり消したりするときに断片化してるんだろうか・・・。あとはもう純粋に私の努力次第っぽいです。

とりあえずの対処法として、重くなったら一旦終了して、もう一度起動すると何とかなるかと思います。まあその、ゲームは一日一時間(©高橋名人)ということで(^^;

メモリ

こんばんは。藤川です。今日も今日とて組み込み作業中。あんまり肩が凝るので、先刻少しだけ息抜きにスーパー銭湯まで行ってきました。大分ほぐれましたが、まだ首が痛い・・・。

ところでほづみさんがFEBLOGで書いてたメモリの件ですが、早速お一方からご連絡を頂戴しました。ありがたいことです。いそいそと試してみたのですが、どうやら手元の吉里吉里のバージョンには実装されていないメソッドのようでした。無念。仕方がないのでもう少しいろいろと模索してみます。

吉里吉里の最近のバージョンはますます機能が豊富になってるんですね。ムービー複数同時再生とかやってみたいです。

MVC

こんにちは。藤川@質問箱です。MVCというのが何物なのかというご質問をいただいたので、僭越ながら簡単にご説明をば。

MVCというのはModel-View-Controllerの略語で、プログラミングに於ける概念のひとつです。要するに「コード(オブジェクト)の役割分担をはっきりと定めて、しっかり守ろう」ということなんですね。

MVCのうち、Modelというのはデータを保持したり、そのデータについて簡単な処理(ソートとかフィルタリングとか)が実装されたものをいいます。Viewは、画像ビューワのViewと同じで、つまり結果や操作ボタンなどを表示するもの。Controllerは、ちょっとひとことでまとめるのが難しいんですけど、いわばModelとViewの調整役みたいなもんです。

などということをダラダラ書いても仕方がないので、擬人化して考えてみましょう。例として、シノニアシティの観光案内所に登場してもらうことにします。

この観光案内所にはスタッフが三人常駐していて、それぞれをMさん、Vさん、Cさんとしましょう。Mさんは裏方担当、Vさんは窓口担当です。Cさんは案内所のマネージャで、二人の上司という立場です。

窓口のVさんは、人当たりが良くてコミュニケーション能力には長けているものの、肝心のシノニアの名所旧跡についてあまり詳しくありません。裏方のMさんはその逆で、コミュニケーション能力ゼロですが、名所旧跡に関する知識量には目を見張るものがあります。マネージャのCさんはこの二人を上手に動かし、連携させ、案内所をうまく運営していくことには手腕を発揮しますが、実は窓口対応も苦手なら、名所についてもほとんど知りません。

ある日、旅行者が案内所を訪れて「シノニアの歴史、特に三国志に関係の深い名所を周遊したい」と言いました。Vさんは「かしこまりました、少々お待ちくださいね」と旅行者に言うと、その質問をそのままCさんに伝えます。Cさんはこの質問を「西暦180年〜210年頃の大陸中国の歴史に関係が深い場所」と解釈して、その条件に該当する場所を探すようMさんに指示します。

Cさんからの指示を受け取ったMさんは、条件に合致する場所を一瞬にしてリストにまとめると、それをCさんに渡します。Cさんは、Mさんから受け取ったリストに目を通して、いわゆる名所とされていないところがあった場合はそれを除外しつつ、旅行者が見やすいように整理し、Vさんに渡します。受け取ったVさんはそれをもとに、「お待たせしました、こんな感じですね」といいつつ、旅行者に説明します。

およそこんな感じで観光案内所はうまく稼働しています。プログラミングにおけるMVCも、つまるところこれと同じです。

MVCな感じじゃないプログラムも、もちろん書くことが出来ますが、MVCなんていう用語が出来るくらいに概念化されているのは、そこにちゃんとしたメリットがあるからです。すなわち、メンテナンス性とか生産性とか、そういったものが格段に上がる訳です。

観光案内所の例で言えば、例えばVさんが産休で何ヶ月か別の人が窓口に入ることになったとしても、コミュニケーション能力さえその人に備わっていれば、案内所がうまく稼働しそうですよね? シノニアに新しい名所が出来たときも、Mさんがその知識を増やせば、再教育コストが低く抑えられそうですよね? そういうことです。

このMVCは、特にオブジェクト指向プログラミングを学習する際、教科書とか教本とかには必ず紹介されるほど、基本的かつ重要な考え方です。オブジェクト指向プログラミングでは、MVCの他にもいろいろと構築についての概念があります。それらを一般にデザインパターンといいますが、藤川はヘタレプログラマなので、MVC以外はあまり理解できてないです(^^;

さてさて、何だか意外にも長くなってしまったので、今日のところはこんな感じで。砂のシステムでこれがどのように活かされているかは、また機会があったら語ってみたいと思います。

ちなみにMacやWindowsに代表されるGUIなOSは、内部的にはオブジェクトの集合体です。これらのOSでももちろんMVCが徹底されています。何がどういう役割を担っているか、考えてみるのも面白いですよ。

システム画像とか

どうも皆さんお久しぶりです。月刊藤川帳のお時間です(笑えん)。

ほづみさんに比べてちっとも更新しないのでゲームの開発も怠けてるんだろうとか思われそうですが、 これは全くその通りで 一応、合間を見て作業はしてるんですよ。ただ、ここに書けるような面白げなネタがあまりないだけです。プログラミングって地味だからねぃ。

まあそれはそれとして、ほづみさんにネタ振りをされたので、ちょこっとだけ。

作中でムービーを使うかどうかですが、オープニングとエンディングならともかく、ストーリー途中に挿入することは考えていません(オープニングとエンディングがムービーになる、という意味ではないですよ。なるかもしれないけど)。容量的にも厳しいですし、それにムービーを作って入れるというのは、案外効率が良くありません。や、私の場合ですが。

そんなわけで、単純なアニメーションは吉里吉里に描いてもらってます。ほとんど機械的作業なんで、こういうところこそコンピュータに自動で処理してもらわないと。怠惰ってのはプログラマにとって最大の美徳ですから(©ラリー・ウォール)。

ホスト突入時の演出に関して言えば、まず最初の3D平面が動いているように見えるのは画像二枚だけのパラパラアニメです。で、Ω−NETのロゴが飛んでくるのは、タイマーで間隔をとりつつロゴ画像をstretchCopyしています。あと、真ん中に来るように座標調整。昔書いたコードなので、この辺はEXLayerControlに置き換えようかと思ってますが。とにかく、種明かしをするとこれだけのことだったりします。

大事なのは着想ですね。このシーンをどう演出したら、最も効果的か。それさえ思いついてしまえば、方法は何通りもあるものです。上で述べた分にしても、別にTJSをガリガリ書かなくても、KAGで充分処理できる範囲ですし。とにかく思いつきとかセンスが大切。その意味でFate/staynightはやっぱりすごいです(横で見てただけで未プレイだけど)。

あー、それにしてもこんだけ更新しないのも問題ですね。独断と偏見に満ちたTJS初心者講座でもやろうかなぁ(笑

無名関数

ADVモードを組み始めている藤川です。最近寒いですね。なんか台風とか来てますが。大丈夫か日本。

さて、ADVモードではいろいろと変数を操作したり、変数をもとに何かを生成したりという作業が必要になります。ここで活躍するのがKAGサブルーチンなんですが、KAGサブルーチンで書くと見通しが悪くなるといいますか、つまるところ面倒な場合、藤川はTJSで関数を用意してそれとKAGサブルーチンを組み合わせて記述します。TJSはプログラミング言語そのものですから、こういう用途には大変向いてますし、表示部分はKAGに任せるとラクできて幸せになれます。

より幸せになりたい藤川は、関数を定義するときにそれを辞書配列の要素として記述しています。つまり、次のような無名関数にするわけです。

var se = %[];
se.cancel = function(buf=0)
{
    kag.se[buf].play(%[
        storage : "cancel",
        loop : false
    ]);
} incontextof kag;
この関数は、例えばキャンセルボタンのexp属性に仕込んで「exp="se.cancel()"」のように使います。引数に効果音バッファをとるが省略した場合は0番をデフォルトで使う、という記述になっています。

このように辞書配列の要素として無名関数を記述しておくと何がうれしいのかというと、辞書配列の識別子にさえ気を使えば、関数名(というか要素名)は事実上どんな名前でも許されるというところが素敵なんですね。例えばレベルアップ時の効果音とBGMをそれぞれTJS関数経由で鳴らしたい場合に(そんなことをする人がいるのかどうかはともかく)、se.levelup()とbgm.levelup()を呼ぶようにしておけば、とてもすっきりしたコードになります。

ちなみに最後のincontextof kagというのは、まあおまじないみたいなもんです。乱暴に要約すると「これはある辞書配列の要素のひとつである無名関数だけど、実行時にはKAG Systemの関数として扱うよ」っていう感じ。

クラスにまとめるまでもないけどTJS関数にしておきたいものがあるときは、藤川はこんな感じで好き勝手に関数を書き散らかしています。

前景レイヤ操作

ほづみさんがFate/Stay Nightをやっているのを横で眺めていて、ズームを上手く使うことで随分楽しげな演出が出来るということにあらためて気付きました。そこで、いっちょそういうのを作ってやろう、と。今回は拡大縮小表示と、回転表示、部分拡大表示に挑戦です。

といっても、吉里吉里にはもともと画像の拡縮や回転に必要な機能が備わっているので、それを扱うスクリプトを記述すれば万事OKです。拡縮にはLayerクラスのstretchCopyメソッドを、回転には同じくLayerクラスのaffineCopyメソッドを使用します。

さて、これをどういう風に記述するかですが、使い回しをしたいので、HumanSystemクラス同様に、海賊タグを追加することにします。そのためにEXLayerControlというクラスを──ま、そんなに複雑なスクリプトじゃないんですけど。テンポラリレイヤを作る必要があるので、オブジェクトにしてしまった方が後々幸せになれるのです。で、まあそのEXLayerControlを書き、目的は達成できました。めでたし。

ところが、聞けば回転ズームプラグインというものが、既に存在するというではないですか。聞いた時には半分ほど書いた後だったので、結局は全部書きましたけど。でも無駄だったのかなぁと思いながら回転ズームプラグインをダウンロードし、ソースを眺めてみると、使用目的が若干違うような感じで、ひとまず安心というか……。

KAGプラグイン形式にしたものも書いたので、準備が出来次第、F#のサイトで配布します。物好きな方はお試しあれ。


(2004.11.22 追記)
Fate/staynight、でしたね。ごめんなさい。タイトルは正確に。Fateといえば別の場面で「背景がゆらゆらしている」という演出がありまして、ほづみさんに「どうやってるんだろうね」と訊かれましたが、わかるわけありません。本当にどうやってるんだか。技術的なこともそうですが、流石に演出にセンスがあって、素敵です。

立ち絵オブジェクト

お久しぶりです。藤川です。それにしても、あらためて見てみると……更新の少ないblogだ……。

や、まあそれはともかく。

ほづみさんが書いてましたけど、立ち絵の描画方針が変更になりました。つまり、人物本体と装着品を別レイヤに描画することになったんですけれども、これで二、三日悩んでました。普通に前景レイヤで適当な親子関係を作ってやってると、頭がこんがらがりそうな気がしたからです。皆さんはどうやってるんでしょうねぇ。

とりあえず、KAG的に正当な方法が思いつかなかったので、いっそオブジェクトにしてやることにしました。管理を楽にするためにKAGPluginクラスを継承して、人物本体レイヤとその子レイヤを3枚持つ、HumanSystemというクラスを書きました。年齢がバレますよとか、言いっこなしです。

やってることは至極単純で、4枚のレイヤを管理しやすくしてるだけなんですが、kagオブジェクトをちょっとhogehogeして専用のタグを作ったので、結果としてスクリプトの見通しがかなりよくなりました。バグがなさそうだったら、そのうちF#のサイトで配布する……かもしれません(需要あんのか?)。

ちなみに、KAGPluginを継承したのは、onStoreとonRestoreメソッドが欲しかったからです。ここにレイヤの保存と復帰(これはCharacterLayerクラスのstore/restoreを呼ぶだけ)を書いて、オブジェクトをkag.addPlugin()しておかないと、吉里吉里が栞関連の動作をしたときとか、トランジション時とかに、いろいろ不都合があるんですねー。さらに、MainWindow.tjsのswapCharacterLayerとstopAllTransitionメソッドをオーバーライドして、自前の前景レイヤを処理してやらないと、これまた困ったことになります。具体的には、栞をたどったときに画像が残ったままになったり、スキップ動作がおかしくなったりするなどの現象が起きます。

まあ、詳しくはソースを公開した後に、そっちを適当に読んでいただければ、と。
// プログラムは最高の仕様書である──詠み人知らず

テキスト描画

引き続きコンフィグ画面を作っている藤川です。引き続きといっても、大分時間があいてしまいました。まあ仕方がありません。

音量調整を組んだことでコツをつかみ、今日は残りを一気に作り上げました。まああまり項目も多くないんですけど。BGMとSEの音量、文字表示速度、フルスクリーン切り替え、それとマウスカーソルスナップのON/OFFくらいです。ノベルゲームではないので、フォント変更はつけていません。

コンフィグ画面は変更結果がすぐわかるように作りたい、というのが目標としてありました。カーソルスナップについてはスナップする対象がないのでアレですが、それ以外、つまりBGM、SEは音量をリアルタイムに変更し、文字表示速度は変更したらサンプルテキストが流れ、スクリーンモードは……まあこれは変わるだけですけど。とにかく、どうせコンフィグ画面を作るならそういうのを作りたいな、と。そうでないなら純正のメニューバーで充分なわけです。

音量調整については前回書いた通り実装できました。スクリーンモードも、つまるところkag.onFullScreenMenuItemClick()やkag.onWindowedMenuItemClick()を呼ぶだけで、別に面倒ではありません。では、サンプルテキストはどうするか?

ところで、TJSからKAGのシナリオファイルを実行するのは案外簡単で、別ファイルにシナリオを用意しておけば、そのファイル名と開始したいラベル名をkag.process()に渡してやるだけで実現できます。が、当然この場合には文字描画がカレントレイヤで実行されるので、今回の用途には向きません。そこで自前で実装することにします。

理屈としては、用意した文字列をcharAt()で一文字ずつ切り取って、タイマーで間隔をとりつつ、順番に並べていけば良いわけです。KAG Systemの「文字描画速度」というのは実際には一文字ずつ描画するときのウェイト時間なので、「速い」ならkag.chSpeeds.fastを(どうせならConfig.tjsで用意してくれている変数を使いましょう)タイマーのintervalにしてやれば、実際の画面と同じような描画を実現できます(眠いので文章がややこしいことになってるかもしれない)。

まあとにかく、次のようなコードになります。ちなみにこのメソッドは、ちゃんとLayerを継承したクラスに置かないとちゃんと動かないので、そのつもりで。

var teststr; // テスト文字列
var timer; // タイマー
var counter; // カウンター

function speedTest(speed)
{
  teststr = "ほげほげ。";
  fillRect(0, 0, 575, 66, 0x88000000); // 前回のテスト文字列を消す
  if (speed == 0) {
    drawText(10, 35, teststr, 0xffffff);
  }
  else {
    timer = new Timer(onTimer, '');
    timer.interval = speed;
    timer.enabled = true;
  }
}

function onTimer()
{
  drawText(
    10 + font.getTextWidth(teststr.substring(0, counter)),
    35,
    teststr.charAt(counter),
    0xffffff
  );

  counter++;
  if (counter == teststr.length) {
    invalidate timer;
    timer = void;
    counter = 0;
  }
}
speedTestメソッドにはkag.chSpeeds.fastなどを渡します。

ある文字を描画するX座標は、font.getTextWidth(teststr.substring(0, counter))で表現できます。counterという変数はcharAt()とsubstring()のふたつのメソッドに渡していますが、意味はまったく違っていて、charAt()ではn文字目、substring()ではn文字分、という感じになります。上の例でいうと、teststr.charAt(2)は「ほ」が帰ってきますが、teststr.substring(0, 2)だと「ほげ」が帰ってきます。

つまりteststr.substring(0, 2)をfont.getTextWidth()に渡してやることで「ほげ」の描画幅が帰ってくるので、その値をX座標として指定することで、teststr.charAt(2)の結果である「ほ」を「ほげ」の次にうまく描画できるという寸法です。

ちょっとややこしいかもしれませんけど、落ち着いて考えればわかると思います。