HOME | PRODUCT | BBS | CODE
Code | Memorial | Sample | Reference
since 2000/10/01
#44 (2002/01/14 )
 順序なしリスト
#45 (2002/02/14 )
 キーリピート
#46 (2002/03/18 )
 How do 誘導?
#47 (2002/05/22 )
 初歩のテーリング
#48 (2003/03/18 )
 バーチャル大儲け計画

Tip Mark
欄外情報FOOTNOTE  参考リンクLINK  補足事項NOTICE
sanami@aya.or.jp

第48回バーチャル大儲け計画
はじめに

みなさん、お久しぶり。気が付いてみたら前回の更新からずいぶん経っているようで、僕の過ごしている時間と実際の時間の流れは違う速度なんでしょうか。ちなみに僕の時間では前回の更新から 9 ヶ月経過しているわけで・・・い、忙しかったんです、きっと。とはいえ、わざわざサイトを訪問して頂いている方に忙しい忙しいなどと聞かせるのは、不愉快にさせるだけ(少なくとも僕は不愉快になる)なので、今後「忙しい」は自粛することにします。ということで、忙しくもなく半年以上も放置していたことになるのでした。

それではまずいので、放置の理由を他所に求めるために、原因の数々を考えてみるも、株価の値動きへの相関関係ほどにしかありません(つまり、ない。あ、いや、ありますよ?)。そこで、当暁のコーダ部屋では、このような「実際はないんだけど、そーだったらいーなー」的関係のことを「バーチャル」と表現することを提案したいと思います。これによって、「半年間の放置期間」は「半年間のバーチャル更新期間」にすげかえることができました。完璧(どこが

バーチャル更新のバーチャル原因

バーチャル更新のバーチャル原因、要するに言い訳ですが、最も有力な言い訳は、更新に対するモチベーション(やる気)です。忙しい、忙しいなどと言っても、それこそ寝食の時間を削りこむところまでつきつめれば更新のための時間ぐらい、なんとかなるってものです。問題は、その時間を作る気になるかという点で、これはモチベーションにかかっているわけです。したがって、モチベーションの回復・増強が更新の維持につながります。 さて、そもそものモチベーションはと言えば、何故サイトを開いたか、という話になりますが、これはソフトウェアの公開についての議論にも通ずるところです。しかし、モチベーションがどこから生まれるか、という問題は人それぞれですから、これらの議論は発散せざるを得ません。実際、コーダ部屋を更新するモチベーションがどこにあるかすら、僕にはわかりませんし、D5.がゲームをなぜ作るか、と言われても同様です。

ただ、モチベーションの起因についての議論が不毛であることは多くの人の認めるところですが、だからといって、モチベーション全般の議論が無駄であることにはなりません。すでに存在しているモチベーションを補強する方法についての議論は有効で、最初のモチベーションを単なる思い付きでなく、身のあるものに結び付ける上では非常に重要とも言えます。

モチベーションの補強方法

まずは何をされたら嬉しいか、から入りたいと思います。と言ったところで結論は単純で、なんらかの形でサービスの受け手(読者・利用者)からの反応(以下、レスポンス)が大部分を占めていることが確実です。このレスポンスには金銭や意見・感想なども含まれますので、これら以外の補強方法としてはサイト更新の手間を軽減するためのツール提供のようなものになります。

それでは、実際に実行に移されている以下の補強手法について、特徴と長所・短所を見ていきたいと思います。

  1. アクセスカウンタ
  2. Web ランキング
  3. 匿名感想
  4. web 投げ銭
  5. アクセス解析
  6. 検索エンジン
  7. 一筆啓上運動
  8. ダウンロード販売
  9. あゆ板
  10. イベントでの感想

1.アクセスカウンタ(Web ページ)
古典的ではありますが、それなりの効果が認められます。フリーソフトだとダウンロードカウンタにあたります。
長所:お手軽。数字の推移で評価を推し量ることも可能。
短所:数が多くなりすぎると補強効果が低くなりやすい。意見・感想の率が低い現実を確認させられる。

2.Web ランキング(Web ページ)
アクセスカウンタよりも積極的に評価を得る方法。そういえば、プログラム系の読み物で Web ランキングって見たことないですね。あっても参加しないけどな!否定する気はまったくありません。1]
長所:アクセス数とは独立な動的評価を得られる。順位による優越感に基づく補強効果。順位があがることによる宣伝効果。
短所:いやおうがなしに他人との比較・競争になる。異なる方向性を同じ土俵で評価。村意識にもつながる。低い順位は逆効果。
 1参加していない人間から見た印象で、否定するなどという気持ちは一切ありません。


3.匿名感想(Web ページ/ソフトウェア)
名を明かさずに意見・感想を言うことができる。
長所:名前を明かさずに一方的にメッセージを発することができる。メーラーを立ち上げずにすむ。
短所:実は、名前を明かさないことは重要でなかったり。当コーダ部屋では、2 〜 3 レス/年ぐらい。感想に対する返事の機会がない。

4.web 投げ銭(Web ページ)
テキストサイト界、ろじっくぱらだいすのワタナベさんが提案僕の偏見です。2]。提案の文章では根本的なモチベーションとして金銭の取得として論じられているが、補強の手段として有効。
長所:お金という即物的な評価、実益。ルールの明確化による将来への責任の回避。
短所:まだまだ投げ銭側の手間が大きい。金銭の受け取りによる義務感の発生。守銭奴的イメージの発生僕の偏見です。3]
 2ワタナベさん:ろじっくぱらだいす
http://www.juno.dti.ne.jp/~logicp/index.html
ろじっくぱらだいす内:Web 投げ銭の投げ方
http://www.juno.dti.ne.jp/~logicp/zaregoto/nagesen/give.html
 3僕が採用することを考えたときのイメージであり、まったくの偏見ですし、否定するとかいう意図は全くありません。自分の文章がどれくらいのお金になるかは興味ありますが、実際の金稼ぎにはしたいと思わないんですよね。


5.アクセス解析(Web ページ)
主にリンク元ページが有効。日記ページからリンクされている場合は、率直な感想を得ることができるが、同時に脊髄反射で書かれた批判的な意見も多いので注意が必要批判的意見も参考にしてます。4]
長所:お手軽。リンクのほとんどを捕捉可能。
短所:ログが流れない程度に定期的なチェックが必要。
 4D5.と僕は批判的な内容も含めて楽しんでますので遠慮なく好き勝手書いてください。リンク張られている時点で嬉しいですし。


6.検索エンジン(Web ページ/ソフトウェア)
ページ名、ソフト名を検索。検索エンジンによって結果が微妙に異なるので、いろいろなところで試すと良い。infoseek はまったく関係ない単語で拾ってくるので困る。こちらも批判的な内容を拾ってくることが多いので注意が必要。批判自体は参考になるので問題ないですが、つまらない文章のネタで使われるだけ、ってのは閉口してしまいますな僕個人の感情的な意見です。5]
長所:お手軽。リンクが張ってなくても捕捉可能。
短所:他と識別できる程度の名前をつけておく必要がある。キャッシュが見れない場合は内容が流れてしまっている場合も。
 5D5.と僕は批判的な内容も含めて楽しんでますので遠慮なく好き勝手書いてください。リンク張られている時点で嬉しいですし。


7.一筆啓上運動(ソフトウェア)
Palm 界、パーム航空の機長さんが提案パーム航空、いっぴつけいじょう。6]。ソフトウェアをダウンロードしたら簡単なメールを作者へ送ろう、という運動。
長所:ルール化による半強制力提案では一切の強制をしていません。7]に基づいたレスポンス数の確保。ルール化による簡素なメールの正当化によりレスポンスの支援。
短所:それでも、まだ、敷居が高い。性善説。受け取る側の返信レスをルール化していないため、人によっては負担増の可能性も。
 6機長さん:パーム航空
http://palm.org/
パーム航空内 PAL071便:いっぴつけいじょう。
http://palm.org/f_pal_classic/f_pal_flight_000/p071_freedom.html
 7提案では一切の強制をしていません。


8.ダウンロード販売(ソフトウェア)
特定サイトにおいて、ソフトウェアをダウンロード販売。広義では同人ショップでの販売も含まれる、か。直接的な利益による補強よりも、話題にされることによって他手法での補強を期待できる。
長所:実益。
短所:実は、全然売れない。

9.あゆ板(ソフトウェア)
ダウンロード時に書き込みをしないとダウンロードURLが表示されない CGI 。一般人は見たことがない。
長所:利用者のレスポンスによってダウンロードが可能になるため、レスポンス数とダウンロード数はほぼ等しくなる。
短所:ダウンロード対象を得る時点でのレスポンスなので、「いただきます」「ありがとうございます」と言ったコメントがほとんど。そもそも、製作者がアップすることはないので、第三者が礼賛を受けることに。バカもおだてりゃ木に登る。そーいえば、ゆーとくんは今ごろどうしているだろうか。

10.イベントでの感想(Web ページ/ソフトウェア)
イベントで本人に直接簡単な感想・励ましの言葉をかける言われて複雑なことも・・・8]
長所:レスポンスする側の満足感、達成感が大きい。
短所:ブースにずっといなければならない。具体的な指摘はもらえない、もらっても思い出せない。時々反応に悩む感想も。
 8よく言われるけど言われて困る言葉 No.1 は「いつも読んでます!よくわかんないけどすごいです!」です。コーダ部屋は説明の文章で独自性を打ち出しているため、「よくわからない」=「お前の文章は全然意味不明」を意味することもありえるわけで、すごく複雑な気分(笑)。


暁のコーダ部屋における場合

当コーダ部屋では、アクセスカウンタ、匿名感想、アクセス解析、検索エンジンの他に[面白かった][つまらなかった]の評価システムを実装したことがあります。このシステム、通算でパーセンテージを弾き出すとサンプル数が多くなればなるほど収束していき、1つの評価の重みが少なくなりますし、逆に近傍の一定サンプルから弾き出すと簡単に 100 %になるという、どうしようもない結果に落ち着きました。

匿名感想については、返事や反論をする場がありませんので、機能しているとは言いがたい状況なのが現実です。書いた人は、読まれているかわかりませんし、書かれた側は返事したくてもコンタクトできない、と。ついでに公開していいものか、という問題もあります。

以上のことから、コーダ部屋における理想的な補強手法の仕様を浮かび上がらせることができます。

  1. レスポンスする側もレスポンスを得られなければならない。
  2. レスポンスする側に金銭的な負担を発生させない。
  3. レスポンスする際の手間は限りなく 0 に近い。
  4. レスポンス数に対する負担が発生しない。

これらの問題をクリアする手法について提案・実装したいと思います。

提案する手法

提案する手法は、各回の記事について金額を支払うことで評価を行います。この金額の累計を表示することで、提供側と評価側の両方にレスポンスを提供します。これで a がクリアできます。

次に、この支払い時の金銭をバーチャル化します。このバーチャル化により b がクリアされ、「金払ってんだから文句言わせろ」的意見を排除することが可能となります。

金銭のやり取りをバーチャル化することで、全体を CGI 等で扱うことが容易になるので、システム全体を CGI プログラムで実装することとします。これにより c がクリアできます。振り込み時にはページ上のボタンをクリックするだけで支払いが完了します。

CGI プログラムで自動的に処理することにより、レスポンスに対する作業の手間という負担を回避することができます。また、支払いは金額の累計として表れるのみですから、レスポンス数の増大は累計金額が増えるだけであり、一切の負担は生じません。これで d もクリアされました。

提案するシステムをまとめると、以下のようになります。

実装

コーダ部屋が置いてある Aya インターネットでは SSI が使えませんので、金額の出力は PNG 画像とします。したがって、HTML は出力となる PNG 画像を img タグで使えばいいだけになります。

CGI プログラムの処理は以下のようになります。

  1. 金額リストファイルの読込み
  2. 指定の回に金額を加算
  3. 金額リストファイルの書込み
  4. PNG ファイルの出力
  5. 支払い完了の通知

一定金額をただ支払うのではあまり面白くありませんので、記事の内容に応じて支払いの金額を変えられるようにしたいと思います。ただし、突拍子もない金額を支払われては企画自体の価値が無になりますので、1 円、10 円、100 円の 3 つから選べることにします。この金額は僕が自分の書いた文章を売りつけるときにあり得る金額、ということになります。これをボタンのイメージによって選択してもらうことにします(図 1 )。初めての人が悩まなくて済むように、ヘルプボタンを用意し、これはバーチャル支払いシステムの説明へのリンクとしておきます。

支払いボタン
図1:支払いボタン

CGI プログラムに渡すべきデータは、第何回か、およびいくら支払うか、の 2 つのデータですが、ついでにチェックコードまで含めてみます。チェックコードには PNG で使っている CRC のルーチンがあるので、これを使います。支払いのためのリンクは次のようになります(リスト 1 )。

リスト1:支払い用リンクの書式
<a href="price.cgi?支払いコード">


支払いコードは CRC によって算出されたコード部を含んでいるため、手作業で計算するのは非常に手間です。どうせ CGI プログラムでチェックルーチンを実装するのですから、支払いコードも出力できるようにしてしまいましょう。ということで、0 〜 1023 の数値が与えられた場合は支払いコード生成モード、それ以外の場合は支払い手続きに入ることとします(図 2 )当ページではカスタマイズしています。9]
 9なーんて説明してますが、第 3 者に好き勝手されては困るので当ページではこの機能、殺してあります。CRC 算出もカスタム化しておくのは基本です。


支払いシステムのフロー
図2:支払いシステムのフロー

出力となる PNG 画像は、文字のドットパターンを配列で持ち、これをもとに再構成します。いろいろな大きさで試してみましたが、固定幅ではどうにも格好がつかないため、数字はすべて当幅とした上でプロポーショナルにしました。文字ごとに送り幅を持っています(リスト 2 )。このデータの出力を図 3 に示します。

リスト2:文字パターンのデータ
@letter_pattern = (

0x78,0x84, ... ,0x78,0x00, #0

0x20,0xe0, ... ,0xf8,0x00, #1

0x78,0x84, ... ,0xfc,0x00, #2

0x78,0x84, ... ,0x78,0x00, #3

0x08,0x18, ... ,0x08,0x00, #4

0x7c,0x40, ... ,0x78,0x00, #5

0x38,0x40, ... ,0x78,0x00, #6

0xfc,0x04, ... ,0x20,0x00, #7

0x78,0x84, ... ,0x78,0x00, #8

0x78,0x84, ... ,0x70,0x00, #9


0x82,0x44, ... ,0x10,0x00, #\

0x00,0x00, ... ,0x00,0x00, #-

0x00,0x00, ... ,0x40,0x80, #,

);

@letter_skip = (7,7,7,7,7,7,7,7,7,7,10,16,3);


支払い累計金額の出力例
図3:支払い累計金額の出力例

以上のような CGI プログラムを作成、使用することにします。使いたい方はご自由にどうぞバーチャル支払いシステム CGI プログラム10]
 10バーチャル支払いシステムの CGI プログラム:48-pricing.lzh (4915 bytes)


おわりに

そんなわけで設置したので、気が向いたら評価してみてください。

このシステムでは同一人物が連続で評価することも可能にしてありますが、それを実行するにはそれなりの努力が必要ということで、有効にしてあります。 つまり、1000円振り込むためには、100円を10回繰り返す動作が必要なわけで、そうまでして振込みたいその気分を無駄にするのは間違い、と。あ、F5アタックしないでください。

こんなシステムの導入を考えながら冬コミを迎えていたのですが、夏コミで数十人からあった「ホームページ見てます」という声も、今回はありませんでした。やはり、評価されるには努力が必要ってことなのですね。
それでわ。

Virtual Payment System100えん10えん1えんヘルプ



匿名感想フォーム
引用を許可する


第44回順序なしリスト
はじめに

みなさん、おぃーっス。年末年始も僕にとっては銀行の開いていない連休と同じで、目新しいことと言えば、風邪をひいて寝込んでいたぐらいしかありません。

さて、新年早々、圧縮技術について興味を惹かれる発表がなされています。一言で言えば、「ランダムデータに対して圧縮が可能」ということで、「100分の1以下を実現する」との主張です元記事1]
 1ZDNN: 100 分の 1 以下を実現するデータ圧縮の新技術
http://www.zdnet.co.jp/news/reuters/020110/e_researchers.html


これに対して「眉唾もの」との評価が多いようですが、僕は実現可能性は高いと思っています。その根拠について圧縮方法から簡単に説明することにします。っつーか、そんなに詳しくないので簡単にしか説明できませんけどね!

圧縮、ここでは可逆圧縮(lossless)を実現する場合、一般的に二つの手法が核となっています。

  1. 符号化
  2. 辞書

符号化を用いた圧縮

出現頻度の高いデータを短いデータ列で表現することで全体としてのデータ量を少なくします。符号化技術は各種特許がとられているので、使用には注意が必要なようです。今回の話には深く関係しないので、詳細は割愛します。

辞書を用いた圧縮

データ列が辞書のどこにあるかという情報で表現することで、データ量を少なくします。例えば、sample という単語を辞書で圧縮するなら、p379、l82 のようにするわけです。この辞書を静的とする方法や、動的に更新する方法があります。ランレングスも一種の辞書として考えてもいいと思います(僕の勝手な分類です)。

乱数列の圧縮

ファイル圧縮を試してみればわかりますが、どんなに圧縮率がよい圧縮法で圧縮を繰り返しても、ある程度よりは小さくできなくなります。このラインがファイルの持つ本質的な情報量と言われます。この状態では、すべてのビット列の並びの出現頻度はほぼ等しくなるために符号化での圧縮ができませんし、ビット列の繰り返しや規則性もありませんので辞書を用いた圧縮もできません。このビット列は乱数列と見なすことができ、乱数列に対して圧縮ができない、と言うことができます。

ここで辞書として数列を選択します。もし、圧縮対象となるデータ列と同じデータ列を生成する数列を知ることができるなら、乱数列でも圧縮することができます。データ列全体が無理にしても、それなりの長さのデータ列を自由に生成することができれば圧縮が可能となります。

数列を辞書として使う利点は、少ないデータからデータ列を得ることができる点です。例えば、数列を生成する漸化式が次の形で表せるとします。

xn+1 = F(xn)
この場合、任意のデータ列は xn といくつのデータを得るかの二つの情報で表すことができるようになります。

乱数列を辞書とする方法によって、それなりに乱数列の圧縮が可能であるとします。圧縮後のデータ列が乱数列であれば同様の手法を、乱数列でなければ従来の圧縮を行うことによって再圧縮が可能となります。これを繰り返せば最終的には元データの 100 分の 1 どころかもっと短くすることも可能なはずです。

したがって乱数列の圧縮は、任意の乱数列を生成する方法を発見することに他ならず、そのことは

ZeoSyncは数学用語を使って,自社の技術を以下のように説明している。「自然発生パターンを意図的に作り出して,エントロピーライクなランダムシーケンスを形成するもの」
という説明からも理解できると思います。数列を生成する漸化式を複数用意し、圧縮もとのデータに演算を施す(png のように前のデータと add/sub/xor 等の演算をしておく)ことで任意のデータ列とマッチする乱数列を見つけやすくするなどの方法も考えられます。

従来の圧縮手法の共通の問題点は、複数回の圧縮(高次圧縮)が有効でないことにあります。一回の圧縮率が高くなくても複数回の圧縮が保証されるのであれば、用途が限定される(動的圧縮には適さない)とはいえ、有効には違いありません。以上の理由から可能性が高いと判断するわけです。

メモリフラグメンテーション

このように、一定に順序付けられたデータはそれだけで有用です。乱数を数列によって得るようにすれば、すべてのフレームにおける情報を保持しなくてもリプレイを再生することが容易に実現できます。

その一方で、順序付けすることにコストを要求するものもあります。ぎゃるぱにXでは、弾を敵より前に表示するなど、表示順序が重要でした。これを維持するためにリンクリストでオブジェクトを管理しているわけですが、このことはメモリの断片化(フラグメンテーション)を引き起こします。

セルを配列で確保しているとし、リンクリストにおいて順番に使用されている、つまり断片化されていない状態にあるとします(図1)。これは、オブジェクトが一回も破棄されていない状態に相当します。


図1:断片化の起こっていないリンクリスト

ここで、2 番目のセルが破棄されるとリンクリストのセルは 1 番目から 3 番目に接続されます(図2)。


図2:セルを破棄したリンクリスト

新しいオブジェクトが生成され、セルが再利用されると、4 番目から 2 番目に接続されます(図3)。これが断片化です。


図3:断片化したリンクリスト

オブジェクトの生成と破棄が繰り返されると、リンクリストの接続状況は悪化の一途をたどります。これに伴い、メモリアクセスはランダムアクセスとなり、キャッシュ機構を持つシステムでは不利な状況へとなっていきます。

順序なしリスト

コストを必要とするリンクリストですが、条件付きならメモリの断片化からおさらばすることができます。

話は簡単です。オブジェクトの破棄による空きセルが後ろにつくから断片化が起こるのであって、セルの順序を維持したまま再利用すればいいのです。オブジェクトの順序を保持したまま実装するには、破棄したセルより後方のセルをすべて複写する必要があります(図4)。これでは先頭のセルを破棄した場合に、すべてのセルを複写しなければならなく、オブジェクト数が増えた場合に実用的でありません。


図4:セルの複写による断片化の回避

ここで、オブジェクトの順序を保証しなくてよければ、オブジェクトの破棄による空きセルに適当なオブジェクトを突っ込むことで断片化が避けられます。そうです、何も登場人物が死んでしまったからといって、同じポジションに新しいキャラを待つ必要は無く、すでにいる登場人物でも構わないのです。どこから持ってくるかが問題ですが、単純に最後尾とするのが自然です。その結果、千砂さんは水無瀬さんではなく一砂くんを選ぶことになったのです羊のうた (c)冬目景/幻冬舎2](図5)。


図5:千砂さんのラブラブ対象

 2羊のうた 羊のうた (c)冬目景/幻冬舎
和服好きなら絶対に外せない一冊。回を追うごとに柔らかくなっていく千砂さんの表情もそうですが、中学生の千砂さんがたまりません。吸血鬼の話なので(そうか?)、月姫にはまった人は楽しめるでしょう(本当か?)


実装

順序なしリストの実装は非常に簡単です。リストのセルを配列で確保しておきます。オブジェクトを追加する場合は、リスト最後尾の次のセルに対して初期化し、セル使用数を一つ増やします。オブジェクトの削除では、リスト最後尾のセルを削除するセルにコピーします。

これをプログラムにすると次のようになります。

// セルの定義
struct SCell cell[MAX_CELL];
int numcell = 0;

// オブジェクトの追加
cell[numcell].x = 320;
cell[numcell].y = 240;
numcell ++;

// すべてのオブジェクトの処理
int idx = 0;
while(idx < numcell)
{
if(process(&cell[idx]) == false)
{ // 削除処理
numcell --;
cell[idx] = cell[numcell];
}
else
idx ++;
}

これならバグも発生しにくいですね。とかいいつつ、コーディング、ミスりましたが(笑)

比較評価

順序なしリストはリンクリストと比較して、オブジェクトの廃棄時にメモリコピーを余分に行っています。しかし、これはオーバーヘッドとはなりません。なぜなら、書き込み先のデータは次に処理されるオブジェクトとなるからです。順序なしリストの利点は、最大オブジェクト数を多くとってもメモリの断片化による性能低下が起きないこと、リンクリストのための next ポインタが不要になることです。

ということで、同条件で速度に大きな違いが出るか、試してみます。計測は以下の条件で行います。メモリ断片化の影響を見るために多少現実離れしているのがポイントです(笑)。コードも頑張って書き下ろしましたですよ計測プログラム3]

 3計測プログラム:44-list.cpp (4824 bytes)


実行結果を表 1 および図 6 に示します。セル最大数が小さい場合はデータがキャッシュ内にあるため、差がありません。セル最大数を増やしていくと順序なしリストが明らかに高速となります。実際のゲームに使う場合は、グラフィック描画によってキャッシュは毎フレーム破棄されると考えられますので、より有効、かもしれません(弱っ

表1:計測結果(PentiumII 333MHz)
オブジェクト
最大数
リンクリスト
(msec)
順序なしリスト
(msec)
100228219
200376397
400734684
80017191800
160043503853
320092597659
64001903815275



図6:計測結果のグラフ(PentiumII 333MHz)
おわりに

一見すると利用法のなさそうな順序なしリストですが、そんなことはありません。2D のゲームにしても、各オブジェクトを 3D のポリゴンのテクスチャとして描画することとし、Z オーダで描画順序をコントロールすれば処理順序は重要でなくなります。

2D のゲームであってもアイデア次第で用途はあります。実際、ぎゃるぱにXでもある弾幕では順序なしリストを使用しています。使用用途が気になる人は通販でゲットだ!(笑)オブジェクトの New/Delete による負荷の回避、弾の最大数の確保といった点を考慮して弾幕を見ていれば、どの弾幕か一目でわかるんではないかと思います。

そんなこんなで意外に有用な順序なしリストですが、日常の作業の優先順位を順序なしリストで管理してはいけません。後から追加された現実逃避ばかりが先に処理されて、肝心の仕事がいつまでたっても処理されなくなります。
・・・。む、そうか。そういうことだったのか。それでわ。

Virtual Payment System100えん10えん1えんヘルプ

第45回キーリピート
はじめに

みなさん、フィギャー。ここのところ、どうもモデラづいてます。モデラといっても MS-Word についてくる Visual Modeller みたいにプログラムモジュールのモデリングではなく、プラモデルとかのモデリング方面です。最初はペーパークラフトだったのですが、次の月にはガンプラ、そしてその次はガレージキットのフィギュアへ進みつつあります。

なんでそうなったのか、いまいち判らないのですが、ドールマスタードールマスター (c)井原裕士/メディアワークス1] に大きく影響を受けたことは間違いありません。ドールマスターの久具津さんはまるでスケッチでもするみたいに作成しており、それを見て(読んで)これならと始めてみると、実際は最初から最後までやすりがけしているようなものです。だ、騙された!(笑)
 1ドールマスター ドールマスター (c)井原裕士/メディアワークス
電撃王で連載中のフィギュアモデラーのお話。雛子さんのキャラは言うまでもなく、ときおり垣間見せる久具津さんの圧倒的な造形力にもうメロメロ。妙な迫力や小気味よいテンポで進む展開は読む人を飽きさせません。早くも 3 巻が待ち遠しい・・・


そんなわけで、やすってやすってやすりまくっている今日この頃なのですが、反復作業は慣れるもので、段々作業が高速化されているようです。そもそも人間の感性は同じ刺激に対しては鈍くなる性質を持っているため、音の強さの単位であるデシベル(db)のような対数比による尺度が利用されることになります。また、感性だけでなく動作に関しても慣れの要素は大きな意味を持ち、たとえばユーザインタフェースでは初回の操作感もさることながら反復して行う場合には決まった手順を繰り返せるようにする必要があります。というわけで、Zwei! のアイテム移動は改善して欲しいところです。

パソコンにおいて、反復を前提とした場合のユーザインタフェースではキーボードが有利です。このページを読まれる方なら一定の操作時にキーを順序よく順番に押していくという経験があるんではないでしょうか。僕の場合、ノートパソコンをサスペンドするときに[CTRL]+[ESC]→[U]→[T]→[ENTER]の連続コンボを叩き込んでいます。

キーリピート

キーボードで繰り返し作業を行う場合、キーリピート機能は必須です。一応、説明しておくと、キーリピートとはキーを押しっぱなしにしておくと自動的にキーを連打したように入力が行われる機能のことです。

さて、キーリピートはキーボード以外にも適用できます。これをパッドでの操作に適用することでゲームにおいても快適なメニュー操作が可能となります。というか、キーリピートのないメニューはストレスの素です。

リピート無し入力処理

キー入力を実装する上で、最も簡単なのは残念なことにリピートをサポートしない方法です。この場合、キー入力に対する処理は、キーが押された瞬間だけです。これをチャートで示すと、キー入力によって信号が立ち上がったときで、これは yaneSDK では KEY_PUSH マクロでキャッチできます(図 1 )。

リピート無しのチャート
図1:リピート無しのチャート
定間隔リピート処理

リピートを実装する場合に、始めに思いつくのはカウンタを用いて一定間隔ごとに処理する方法です。この場合、キー入力に対する処理は以下の 2 つに対して起動されます(図 2 )。

  1. キーが押された瞬間
  2. キーが押されている状態において一定間隔

定間隔リピートのチャート
図2:定間隔リピートのチャート

チャートから処理を起動する条件は以下の 2 つになります。

  1. KEY_PUSH
  2. KEY_PRESS 状態において、カウンタが一定値
これをプログラムする場合は、次のようになります(リスト 1)。

リスト1:定間隔リピートのアルゴリズム
if(KEY_PRESS)
{
if(KEY_PUSH || counter == REPEAT_TIME)
{
// 処理
counter = 0;
}
else
counter ++;
}
else
counter = 0;
一般的リピート

定間隔でのリピートは実装が簡単ですが、よい操作感を得るのは困難です。リピート間隔が短い方が、慣れに伴う操作感の低下が起こらないのですが、リピート間隔を短くした場合、すばやくキーを離さないと意図に反して二文字分のキー入力が発生しかねません。とはいえ、リピート間隔を長くするとイライラすることになるわけです。

手元にあるキーボードで試してみればわかると思いますが、実際のキーリピートでは定間隔でのリピートではなく、リピートによるキー入力が始まるまでの時間が長めにとられています。このようなリピートは、カウンタに対する処理を工夫することで実装できます。まずは、カウンタについて考えることにします。

カウンタを使う場合、リスト 1 のようにカウンタの初期値とリセットしたときの値を等しくするとリミット値との比較が面倒になります。ここで、リセットするときの値にゲタを履かせると初回と二回目以降の間隔が異なるリピートが可能です(図 3 )。

ゲタを用いた不定間隔の生成
図3:ゲタを用いた不定間隔の生成

実際にプログラムする場合には、リピートが始まるまでの時間と、リピート間隔で指定する方が直感的なので、ダウンカウンタとして実装します。このカウンタとその他もろもろの情報からリピート処理を行います(図 4 )。

不定間隔リピートのチャート
図4:不定間隔リピートのチャート

チャートから、処理を起動する条件が次のときであることがわかります。

KEY_PUSH || (KEY_PRESS && counter == 0)

ここで、KEY_PUSH が立ち上がった場合、カウンタは 0 と初回のリピート時間のどちらの値として考えても構わない(プログラム順序によってどちらかが確定される)ことに注目すると、KEY_PRESS && counter == 0 の条件でも満足できることがわかります。このことから、プログラムは次のように書くことができます(リスト 2 )。

リスト2:ダウンカウンタによる不定間隔リピートのアルゴリズム
if(KEY_PRESS)
{
if(counter == 0)
{
// 処理
if(KEY_PUSH)
counter = FIRST_REPEAT;
else
counter = NORMAL_REPEAT;
}
else
counter --;
}
else
counter = 0;

ちなみに、マクロ化するのであれば、ちょっと工夫して書くといいかもしれません(リスト 3 )。これなら処理以外の部分を一つのマクロに置き換えられます。

リスト3:ダウンカウンタによる不定間隔リピートのアルゴリズム2
if(KEY_PUSH)
counter = FIRST_REPEAT + NORMAL_REPEAT;
else if(KEY_PRESS && counter == 0)
counter = NORMAL_REPEAT;
else if(counter > 0)
counter --;
if(counter == NORMAL_REPEAT || counter == (FIRST_REPEAT +NORMAL_REPEAT))
// 処理
加速リピート

いろいろなゲームでメニューやランキングを触っていると反面教師を含めてユーザインタフェースの勉強になるわけで、その一例としてリピート速度が加速するタイプを実装してみます。と言っても、カウンタにセットする値をいじるだけです。ここでは、カウンタのためのカウンタ、subcounter を用意しておきます。あとは、リピート間隔を subcounter の値に応じて変更することで加減速も朝飯前です。あぁ、腹減った(リスト 4 )。

リスト4:subcounter によるリピート間隔の変化
if(subcount < REPEAT_TIME -1) subcount ++;
count = REPEAT_TIME - subcount;

え?チャート?わかりにくいっしょ。で、操作感にどれほどの違いがあるかをテストするためにそれぞれの方法を実装してみました(図 5)。パラメータは適当に設定してありますが、うまく調整すれば実用レベルになるはずです。上下でリピートのモード切り替え、左右で文字選択です。試してみてくださいキーリピートテストプログラム2]

キーリピートのテストプログラム
図5:キーリピートのテストプログラム

 2キーリピートのテストプログラム:45-keyrepeat.lzh (66569 bytes)


おわりに

今回は、簡単のために一瞬で隣の文字へ移動しますが、滑らか〜に移動するようにすれば市販ソフトと同等の見栄えを演出できるようになります。メニューはよく操作するところなだけに、ストレスが溜まりやすいところでもあります。このような細かい部分を一つ一つ作りこんでいくことで作品全体のレベルアップが果たせるのです。

そんなわけで、作品全体の完成度を追求すべく、ほとんど見えない場所の合わせ目を消すためのやすりがけを繰り返す日々です。しゃこしゃこ。
それでわ。


参考文献
 ドールマスター (c)井原裕士/メディアワークス1 ドールマスター (c)井原裕士/メディアワークス
雛子さんのみょーなハイテンションでの百面相が楽しい。とりあえず、僕的にもツボです(1 巻 105P 参照)。読む人をフィギュア界に取り込む罪作りなタイトル。さぁ、僕と一緒にフィギュアを作ろう!
井原裕士さんのページぱら・さいと 〜井原裕士の小部屋〜

 キーリピートのテストプログラム2 キーリピートのテストプログラム:45-keyrepeat.lzh (66569 bytes)
紹介したリピートの実装例。コンパイルには yaneSDK 1.63 あたりが必要です。コンパイル方法の詳細は 第34回 を参照ください。
上下でリピートのモード選択、左右で文字選択、ESC で終了です。

Virtual Payment System100えん10えん1えんヘルプ

第46回How do 誘導?
はじめに

みなさん、ナエナエ〜。ガレージキットのナエさんを買ってきたはいいのですが、リアルタイムで見ていなかったために資料が全く無く、困っている今日この頃、いかがお過ごしでしょうか。幸いなことに、ビデオはリリースされているのでレンタルビデオ屋に行くわけですが・・・ねぇ!全然ねぇよ!ということで、レンタルビデオ屋をはしごする毎日でした。3 週間かけてようやく全巻見ることができました(笑)。

いちおう、メダロット魂のビデオは一般販売もされているようですが・・・って、1 巻が出て打ち切りですか!だめすぎる・・・

なんでこんなに血眼になって資料を探すかというと、今年の目標を達成するためですが、どんな目標を立てたかは秘密です。ともあれ、今年は目標に向かって邁進する覚悟です・・・って、3 月になって言うことではないような。

さて、目標に向かってと言えばホーミングなわけで、ホーミング系の弾幕を実装することは直進系の弾幕を実装するよりも、それなりにレベルの高い人でないと理解できないらしい(某氏談)ネタです。1]。そんなですから、みなさんも暁のコーダ部屋を読んでレヴェルアップしましょう!
 1...それなりにレベルの高い人...
ネタです、ネタ。そんなことはまったくないので誤解なきよう。というか、この言い回しは「おまえ、なに言ってっかわかんねぇよ!」と言っているようにしか思えんのですが、褒め言葉らしいです。世の中には不思議なことが多いですなぁ。


ホーミングの基本分類

ぎゃるぱにXにおいてホーミング系の実装は大きく次の二つに分類できます。

世にあるホーミングは二次元・三次元を問わずこの二つのどちらかで表せます。とか言いつつ、他にもあるんじゃないの〜?と懐疑的なので知っていたら教えてください。

回頭型ホーミング

回頭型は、弾の移動を速度と方向で表しておき、方向を目標方向へ変化させます(図 1 )。ミサイルやレーザーをホーミングさせる場合はほとんど回頭型となります。

回頭型ホーミングの基本動作
図1:回頭型ホーミングの基本動作

回頭型では、毎フレームの回頭角度(図 1 内 delta)の与え方がキモとなります。目標方向に応じて一定値を加える方法が簡単です。

if(目標が左側)
angle += ROTATE_VALUE;
else
angle -= ROTATE_VALUE;


ただし、この場合は蛇行する恐れがあるので実用的とは言えません。

実際のミサイルを参考にすると、一定時間内の最大回頭角度は決まっており、その中でなら自由に回頭できると考えることができます速度変化は無視します。2]。これをアルゴリズムに適用すると次のようになります。
 2厳密には摩擦による速度低下、バーナーによる増速、回頭によって生じる空気抵抗による速度低下などなどありますが、速度一定であるとしたときの話です。


delta = 目標方向 - angle;
if(delta < - 回頭最大値)
delta = - 回頭最大値;
else if(delta > 回頭最大値)
delta = 回頭最大値;
angle += delta;


実装してみるとわかるのですが、角度はラップアラウンド359 度の次に 0 度がくること3]するために最初の一行をどのように書くかが面倒です。問題が発生する例は現在の方向が 359 度、目標方向が 0 度の場合で、左へ回頭することができなく、358 度の方向へ回頭してしまいます。
 3角度を 0 〜 359 度の範囲で表した場合、359 度の次は 0 度になるという性質です。


で、正解の一つは次のように、回頭角度を正規化0 度から 359 度の範囲の値で表すこと4]して 180 度よりも大きい場合は反対回り、です。
 4...回頭角度を正規化...
角度を 0 〜 359 度の範囲で表すこと。この場合は、360 で割った余り、です。


delta = 正規化(目標方向 - angle);
if(delta > 180 度)
delta -= 360 度;


んで、実装してホーミングさせてみると図 2 のようになります。単純な回頭型は一定半径の円の組合せとなるような軌道を描くことになり、不自然に見えることがあります。これを解消する場合、速度を可変とする、速度に応じた回頭量に変化させる、などの方法があります。

回頭型ホーミングの実行例
図2:回頭型ホーミングの実行例
引力型ホーミング

引力型は、ヲタクが秋葉原に吸い寄せられるように、春香さんが優しい男に引き寄せられるように新暗行御史 (c)尹仁完・梁慶一/小学館5]移動するタイプで、弾の移動をベクトルで表しておき、目標方向に応じたベクトルを加算することで新しい移動ベクトルを得ます(図 3 )。手っ取り早く楕円軌道を得たい場合は引力型を使うと簡単・・・と言いたいところですが、パラメータ調整が結構微妙です。
 5新暗行御史 (c)尹仁完・梁慶一/小学館 新暗行御史 (c)尹仁完・梁慶一/小学館
サンデー GX で連載中。春香さんの最初のコスチュームは、そういう事情なのだろうなぁと思っていたら、それが標準服だったり。本編自体も面白いですが、ことあるごとの春香さんの表情が密かな楽しみ。


引力型ホーミングの基本動作
図3:引力型ホーミングの基本動作

引力型は、どれだけ引力の影響を受けるかがキモですが、その程度によらず図 3 のように移動方向と引力方向の成す角が 90 度以内の場合は加速します。あまり移動速度が速くなってしまうのも問題なので、最高速を設定する必要があります。

direction = 自機方向取得( );
atr_x = 引力影響率 * Cos(direction);
atr_y = 引力影響率 * Sin(direction);
mov_x += atr_x;
mov_y += atr_y;
if(mov_x*mov_x + mov_y*mov_y > 最高速*最高速)
{
a = ArcTan(mov_x, mov_y);
mov_x = 最高速 * Cos(a);
mov_y = 最高速 * Sin(a);
}


引力型ホーミングを実装した例を図 4 に示します。基本的に最高速制限は必須ですが、楕円軌道で最高速となるのは長軸と並行な状態なわけで、最高速を遅くしてしまうと弾を引きつけてよけることができなくなるので注意です。ちなみに、ものによっては最低速の制限もつけることも必要です。プログラム的には最高速の制限と同じですな。

引力型ホーミングの実行例
図4:引力型ホーミングの実行例

万有引力では距離が近いほど影響を受けますが、このコード例では、目標との距離にかかわらず一定の引力としています。万有引力ライクに実装する場合は、自機方向から引力を計算するのではなく、弾の位置から見た自機の座標をベクトル成分とし、係数をかけて引力とします。

実装例

ホーミング系の弾を実装する場合、画面外でのオブジェクト削除だけでなく、一定時間でホーミング動作を終了するようにしておく必要があります。そうでないと、いつまでたっても画面内でグルグルと回っている状態になりかねません。

引力型ホーミングの描く軌跡の楕円は円のうちってわけで、自機との関係や最高速・最低速の制限によっては円に近くなります。実際のホーミングを体感してみてください(図 5 )ホーミングテストプログラム6]
 6ホーミングのテストプログラム:46-homing.lzh (68749 bytes)


ホーミングのテストプログラムの実行例
図5:ホーミングのテストプログラムの実行例

ぎゃるぱにXで実際に使用しているホーミングはこの2種類を基本に多少のカスタマイズを行っていますが、パラメータをいろいろ変更するだけでも楽しめます。

おわりに

ホーミングは誘導弾だけでなく、オブジェクトを目的地まで移動させるのにも使えます。この場合、目的地に到達する保証がないので注意が必要です。そういえば、今年の目標のうちの一つが定期更新だったのですが、いきなり遅刻していまいました。・・・ってことはあれですか、僕の目標へ向かう力はホーミング程度ですか。

ちなみに、目的地の近傍を通り過ぎたホーミングは目的地が移動しない限り、到達できなかったりするんですが。
それでわ。

参考文献

 新暗行御史 (c)尹仁完・梁慶一/小学館5 新暗行御史 (c)尹仁完・梁慶一/小学館
サンデー GX で連載中。作者は韓国の方だそうで、マンガ大国日本の漫画家さんもうかうかしていられませんな。絵のクオリティが高く、展開も面白いので下手な日本マンガなど足元にも及びません。最初は「好みの絵じゃない」と遠慮していたのを都市夫くんが貸し付けてくれました。・・・ってゆーか、なんでもっと早く貸してくれなかったんだ!あなたも韓国マンガを読んでインターナショナルな人間へレヴェルアップ!春香さんを見ていると萌えは万国共通なのかと思ってしまいます(笑)

 ホーミングのテストプログラム6 ホーミングのテストプログラム:46-homing.lzh (68749 bytes)
紹介したホーミングの実装例。コンパイルには yaneSDK 1.63 あたりが必要です。コンパイル方法の詳細は 第34回 を参照ください。
カーソルキーで自機移動、Z で回頭型ホーミングを発射、X で引力型ホーミングを発射、ESC で終了です。

補箋(2002/05/21)

 Nobo さん:Nobo's Homepage
http://www.yk.rim.or.jp/~noboyama/
http://www.yk.rim.or.jp/~noboyama/program/game/bezier1.html
別件についてサーベイしていたところ、たまたま発見しました。ベジェを使っての軌道計算です。問題点がなくはないですが、検討の価値は十分すぎるほどにあります。実用されていたりするんでしょうか・・・

Virtual Payment System100えん10えん1えんヘルプ

第47回初歩のテーリング
はじめに

みなさん、こんにちわ。今年から WRC が地上波で放送されるようになってウハウハな今日この頃、いかがお過ごしでしょうか。放映時間はたったの 30 分なので中身は推して知るべしですが、多くは望みません。がんばれスパイク!そのためならラリーゲームの一つや二つ、いくらでも買いますよ!それがいかにラリーゲームとしては根本的な問題があったとしても!コドライバーのナビが遅すぎます1]
 1ラリーでは助手席に乗ったコドライバーが「100m 先左カーブ」とナビしてくれる内容でスピードとラインを決めてコーナーに突っ込みます。当然ながら「ヘアピン」と言われてからブレーキングすることになるのですが、・・・言うのが遅ぇよ!
でも、ラリーファンなら買いです。


実のところ、好きなのはラリーに限った話ではなく F1 は言うに及ばず、JGTC も当然で F1 がつまらなかった一頃は、バイクのバトルの熱さにはまっていたりしました。高校のときはわざわざ 10Km の道のりを電車通学せずに走破するぐらいで、自転車並木橋通りアオバ自転車店 (c)宮尾岳/少年画報社2] も大好きです。
 2並木橋通りアオバ自転車店 (c)宮尾岳/少年画報社 並木橋通りアオバ自転車店 (c)宮尾岳/少年画報社
自動車、バイクのマンガは数多しと言えど、自転車のマンガと言えば、これに決まり。アオバ自転車店を中心に、自転車と人との触れ合いを描いた作品、とは建て前で毎回の表紙に男が出てこないあたり、よくわかってます。これを読んでいると、自転車が欲しくなってきます。・・・思うツボだよ!


これらに共通するのは車輪がついていることで、まさに車輪は世紀の大発明(だっけか?)なのが実感できます。車輪のどこがすごいかと言うと、回転することによって摩擦面を無限の長さで得ることができる点です。これの延長上として無限軌道のキャタピラがあるわけですが、こちらは前後にしか動けないのに対し、車輪なら左右へもスライドで自由自在です(違

テーリング処理

車輪のスライドはタイヤ痕となって路面に刻み込まれます。車のゲームでドリフトするとタイヤ痕が残っていくのはみなさんご存知の通りです。サーキットを周回する場合はすべてのタイヤ痕を記憶するのが理想ですが、タイヤ痕が多くなればなるほど記憶するためのメモリ領域を必要です。パソコンのように事実上無限のメモリ領域を使えるのならいざ知らず、そうでない場合には見えなくなるぐらいまで保持しておけば十分です。

タイヤ痕というと車のゲームに限定されるように感じられますが、そんなことはなく、自機の動きをトレースするオプションやレーザーといった効果も同様の処理で扱うことが可能です。簡単のために第 46 回暁のコーダ部屋,第 46 回:How do 誘導?3]のホーミングとその残像について考えてみることにします。
 3暁のコーダ部屋,第 46 回:How do 誘導?
http://www.aya.or.jp/~sanami/peace/memorial/code41-50.html#CODE46


残像ちっくな動きを作るには、次の二つの方法があります方式名称は勝手に命名4]

チップ方式:残像ごとにオブジェクトを生成
バッファ方式:残像を発生するオブジェクトに各残像の情報を保持するバッファを用意

両者は一長一短なので、使用する場面に応じて選択することが重要です。
 4チップ方式、バッファ方式という名称は便宜上、独自に命名しています。


チップ方式

先頭のオブジェクトが一定間隔で残像オブジェクトを生成します。それぞれの残像はアニメおよび移動その他もろもろの処理が行われることになります。

利点
先頭のオブジェクトは、一定間隔で残像オブジェクトを生成するだけなため、プログラムが容易です。残像オブジェクト側もオブジェクトごとにアニメや移動などを実装されるため、プログラムの見通しがよくなります。

欠点
先頭のオブジェクトと残像オブジェクトが異なるオブジェクトになるため、残像が先頭の動きに依存するような場合(残像が先頭に引かれるような場合)には、特別な処理が必要です。見た目よりもオブジェクトを大量に消費するため、オブジェクトの最大数の問題やメモリフラグメンテーションによるパフォーマンスの低下に注意する必要があります。先頭オブジェクトと残像オブジェクト間の描画順序、残像オブジェクト同士の描画順序と生成順序が微妙な関係になるため(図 1 )、描画順序を制御する機構が必要です。

先頭と残像の生成および描画順序
図1:先頭と残像の生成および描画順序
バッファ方式

先頭と残像すべてを一つのオブジェクトで管理します。

利点
最低限の情報だけをバッファに保存することで無駄のないメモリ管理が可能です。単一のオブジェクトなため、描画の順序をプログラムとして固定することができます。先頭の軌跡をバッファに保存するため、残像がその軌跡をなぞるように移動できます。

欠点
残像の数だけバッファを必要とするため、オブジェクトサイズの肥大化につながります。残像を含むオブジェクトが複数となる場合、残像同士の前後関係が正しくなくなります(図 2 )。

消えかけの残像が新しい残像を消す例
図2:消えかけの残像が新しい残像を消す例

両者の方式は優劣をつけるべきものではありませんし、二つをマージした手法も考えられます。場面に応じて柔軟に適用するのがお得です。今回は、バッファ方式について考えていくことにします。チップ方式はまた後日に。

有限長のバッファ

前回のホーミングのように残像の最大表示数が一定の場合、(実用的な長さという意味で)有限長のバッファで処理できます。このとき、残像が移動するか移動しないかでバッファサイズが変わります。

残像が移動する場合
残像が移動する場合には、先頭の位置を毎フレーム記憶しておきます。残像の表示位置はバッファの何番目の要素、で決まります(図 3 )。毎フレーム、バッファの内容をスライドすると先頭が停止しているときに残像が先頭の位置に集まります。どこぞのゲームのオプションのようにする場合は、先頭の位置が停止しているフレームでは、バッファの内容をスライドしなければいいわけです。

残像が移動する場合のバッファと残像の関係
図3:残像が移動する場合のバッファと残像の関係

残像が移動しない場合
残像が移動しない場合には、二つのアプローチが考えられます。

後者で実現可能なことから分かるとおり、前者は無駄にバッファサイズを大きく取っています。というのも使わないデータまで律儀に保持しているからです。ということで、一般的に後者のアプローチが取られます、はずです。この場合、各残像の表示位置はバッファ内の一定位置に保持されます(図 4 )。

T=t<sub>i</sub>
(a) T=ti
T=t<sub>i+j</sub> (0 < j < F)
(b) T=ti+j (0 < j < F)
T=t<sub>i+F</sub>
(c) T=ti+F
図4:残像が移動しない場合のバッファと残像の関係

処理の手順は次のようになります。

  1. 先頭の位置を移動(図 4 .b、c )
  2. 残像を生成するフレームならバッファの内容をスライド(図 4 .c )
このとき、新しく生成する残像の位置は先頭の位置を複写します(図 4 .c )。

処理の優劣を判断する材料となるのは 2 の部分になるわけですが、前回のホーミングでは次のように記述しています。

for(int i = SHADE_ANIM_PATTERN -1; i > 0; i --)
{
my->x[i] = my->x[i -1];
my->y[i] = my->y[i -1];
}


アニメーションの滑らかさを重視する場合には、各残像をアニメーションの 1 フェーズに対応させます。これを独立にした場合、1 フレーム内に同一パターンが存在したり、連続するアニメーションパターンのうち 1 パターンが存在しないことが発生し、見た目にガクガク感として現れることになります。レーザーのように通常状態において同一パターンが複数存在する場合はその限りではありません。

無限長のバッファ

有限長のバッファを用いる際の問題としては、次の二つが考えられます。

バッファサイズの決定は、十分な大きさを確保するため、残像の最後のパターンが消滅するまでのフレーム数を求めることで解決できます。たとえば、一つのパターンを 10 フレーム表示するようなアニメーションが 5 パターン用意されているなら、

10[frame] x 5[pattern] = 50[frame]


となり、50 フレーム分のバッファを用意すれば十分です。オブジェクト管理のシステムやオブジェクト自体のコードによりますが、安全のためにオブジェクト生成時の 1 フレームとオブジェクト破棄時の 1 フレームを加えたサイズよりも大きくとると安心です。

みなし無限長バッファ
近年のパソコンは急速に高速化されており、バッファ内容のスライドによるオーバーヘッドが問題になるようなことは稀だと思いますが、なんにせよバッファの内容をスライドさせるたびにバッファサイズ -1 分の要素の複写処理が発生します。特に、残像が移動する場合は毎フレーム発生することになり、オブジェクト数が複数あるなら、その数分の処理が発生してくることになりますので、あまりバカにできません。

このメモリコピーを回避する手段として、十分に大きなバッファを確保し、参照位置を移動する方法が考えられます(図 5 )。実際には有限長にも関わらず、前提を満たしていれば事実上、バッファサイズを無限長とみなすことができるわけです。

みなし無限長バッファ
図5:みなし無限長バッファ

たとえば、ぎゃるぱにXでは各ステージのプレイ時間は 240 秒を超えませんから 240x60 フレーム分のバッファを用意することになります。仮に x、y 座標だけを保持するならバッファサイズは次式で求められます。

4[byte] x 2 x (240x60) [frame] = 115200[byte] = 112.5[Kbyte]


このサイズを見てのとおり、ちょっとした場合でも大量のメモリ領域を必要とします。また、ある瞬間に注目するとバッファ全体において有効な情報が保持される割合(使用中の要素数)は常に数 % 程度に過ぎません。たとえば、全アニメパターンを 50 フレームで終了するオブジェクトをぎゃるぱにXで実装した場合を考えると、

50[frame] / (240x60) [frame] * 100 = 0.35 %


となり、せっかく用意したバッファの 1 % も使っていません。いくらなんでも、これではもったいないお化けが出てきてしまいます。

リングバッファ
そのための実装法としてリングバッファがあります。リングバッファとは、バッファの両端を連結することで有限長のバッファを無限長として扱う手法です(図 6 )。環状になったバッファを回転させるか、バッファの参照位置をずらすかはどちらを基準に考えるかなので、概念的には一緒ですレジスタをリング状に配置して命令数を削減5]
 5RISC プロセッサには、いくつかのレジスタを一まとまりとしてリング状に配置し、リングを左右に移動する命令と組み合わせることで命令数を少なくするものがあります。


リングバッファの概念
図6:リングバッファの概念

現実のメモリは線形に並んでいるため、バッファをリング状に配置すると言っても、それは概念に過ぎません。これを実際にどのように実現するかを難しく言えば、群環論で論じられる、となると思いますが、簡単に言えば角度を 360 度で表現することと同じです。今、360 個の要素を 1 度に 1 つの要素を対応づけて( 0 度には 0 番目の要素を対応づける)環状のバッファを構築したとします。360 度= 0 度ですから、359 番目の要素の次には 0 番目の要素があります。

この環状バッファに対して n 番目の要素へアクセスすることを考えます。n の範囲は限定されていないとすると、n を 0 から 359 までの範囲で等しいものに置き換えなければなりません(正規化)。n 度にある要素が n 番目の要素であることを思い出せば、角度の正規化という問題になります。その手順は、と言えば n が 360 以上なら 359 以下になるまで 360 を引けばいいのでした。それを、360 で割った商( MOD 演算)、と一言で表現するのです。n が負の値の場合は、一旦正負を逆転してから考えると次のようになります。

(-((-n) % 360) +360) % 360


一回目の MOD 演算の結果が 0 、すなわち割り切れた場合を別条件として考えれば、二回目の MOD 演算は必要なくなります。各演算の結果の範囲に注意すると正規化は次のように行えます。

int normalize(int n)
{
n %= 360;
if(n < 0)
n += 360;
return n;
}


と、一般型として考えてきましたが、実際の場面で使う場合には要素数を 2 のべき乗とし、ビット演算を用いて正規化する方法が一般的と思います。この場合の正規化関数は、要素数を N = 1 << Nb (ただし Nb>1)として、次のように記述できます。

int normalize(int n)
{
return n & (N-1);
}


ここで注目すべきは負の数に対してもビットマスクによる MOD 演算が可能な点です。したがって、わざわざ関数にせず、マクロでも簡単に記述できます。残像のスライド部分は次のようになります。

int idx = Normalize(index-1);
my->x[index] = my->x[idx];
my->y[index] = my->y[idx];
index = idx;


スライド部分の処理が大幅に簡単になったかわり、描画時にはリングバッファのリングに沿って描画するため、次のようなことになります。

for(int i = SHADE_ANIM_PATTERN -1; i >= 0; i --)
{
int idx = Normalize(index+i);
charaPlane->Blt(my->x[idx], my->y[idx], srcrect[idx]);
}


多重リングバッファ(勝手に命名)
リングバッファ(配列 ring[ ] )において、n 番目の要素の次の要素にアクセスするには、以下のように記述されます。

ring[normalize(n +1)]


したがって、その次は・・・、その次は・・・と簡単に想像できると思いますが、毎回正規化するのも大変です。よく使う場合にはテンポラリ変数に保存するなども考えられますが、いつもそれが許される状況であることもありません。

通常、リングバッファを線形に並べると 1 つのバッファ(バッファ A とする)が繰り返されています(図 7 )。

バッファの並び
図7:バッファの並び

ここで、同じ内容のバッファをもう 1 つ用意し(バッファ B とする)、2 つのバッファを交互に並べます(図 8 )。バッファ A の i 番目の要素とバッファ B の i 番目の要素が等しくなるわけです。

二重リングバッファ
図8:二重リングバッファ

このとき、要素番号 n が正規化されているなら n+1 番目の要素は必ず二つのバッファのどちらかに含まれます。二つのバッファを 1 つの配列にバッファ A、バッファ B の順で並べているとすると、n+1 番目の要素にアクセスしたときに配列の中に納まることになり、正規化の必要がなくなります。

n+1 から n+2、n+3、・・・と増やしていくことを考え、これを n+m として表すことにします。バッファサイズを N とすれば、n は正規化されているため 0 ≦ n < N の範囲にあります。また、リングバッファを無限長バッファとして使用する場合は 1 周先の内容は別の内容であることを考えると、0 ≦ m < N が成り立ちます。したがって、0 ≦ n+m < 2N の範囲ですから、バッファを 2 つ並べただけで配列の外側へのアクセスを回避することができます。 m ≧ N でのアクセスが発生するのであれば、それはすなわちバッファサイズが小さすぎることを意味します。

次に、n-1 番目の要素にアクセスすることを考えます。上での議論を踏まえればバッファ C を用意して・・・となると思った人は授業妨害、グラウンド 10 周!論理的に考えれば次のようになります。

  1. 要素番号を n-m とすると 0 ≦ n,m < N 、すなわち -N < -m ≦ 0 。
  2. -N < -m ≦ 0 の各値に N を加えると、-N+N < -m+N ≦ 0+N 。
  3. m' = -(-m+N) とすると n-m' の範囲は
    最小値:n = 0、m' = 0 で n-m' = 0
    最大値:n = N-1、m' = -N (m = 0) で n-m' = 2N-1

0 ≦ n-m+N < 2N、つまり配列の外側へのアクセスがないことがわかるわけで、このことから、n-m のときは n-m+N としてアクセスすれば正規化の必要がなくなります。これを用いることで、スライド部、描画部は次のように記述できます。

my->x[index] = my->x[index +N] = my->x[index -1];
my->y[index] = my->y[index +N] = my->y[index -1];
index = Normalize(index +1);


for(int i = SHADE_ANIM_PATTERN -1; i >= 0; i --)
{
charaPlane->Blt(my->x[index +i], my->y[index +i], srcrect[index +i]);
}


おわりに

シングルのリングバッファと多重リングバッファの違いは、

となります。したがって、インデクス指定による参照が少なく抑えられるかが、どちらを用いるかの判断の分岐点になります。あとは、プログラムの見易さですか(そっちの方が重要な気もする)。

ところで、前回は定期更新に遅刻でしたが、今回を 4 月分と言い張るためには 5 月中の更新が必須となります。う〜ん、なんか、そーゆー運営形態、どっかで聞いたことが・・・

あ、そうか、あれだ。「自転車操業」だ。ワカバさんも納得。
それでわ。

参考文献

 WRC 〜ワールド・ラリー・チャンピオンシップ〜 (c)Spike Co.,Ltd.1 WRC 〜ワールド・ラリー・チャンピオンシップ〜 (c)Spike Co.,Ltd.
ドリフトアングルをつけすぎるとエンジンがストールしたり、コースアウトするとモンスターマシンがサニーよりも非力になったりと不満はありますが、画面はさすがに綺麗です。全体のボリュームも十分ですが、いくら壊れていても次のステージに移ると完全復活するので「壊してでも速く」になっているのは残念。あと、もう一つ難しいレベルがあれば長く楽しめるのですけど。
WRC 〜ワールド・ラリー・チャンピオンシップ〜のページ

 並木橋通りアオバ自転車店 (c)宮尾岳/少年画報社2 並木橋通りアオバ自転車店 (c)宮尾岳/少年画報社
YOUNGKING にて絶賛掲載中。病弱なワカバさんも捨てがたいですが、ミホさんの前向きっぷりに心が洗われます。この本に影響されて電車で 90 分の勤め先へ自転車通勤しようかと本気で画策中。

Virtual Payment System100えん10えん1えんヘルプ


バーチャル支払いシステム
暁のコーダ部屋のそれぞれの記事が面白い、有用だと思われた場合、対価として適当と思った金額をクリックします。これによって、あなたはバーチャルにお金を支払ったことになり(つまり、支払ってない)、そのバーチャルお金はバーチャルに僕の口座へ振り込まれるという、誰のふところも痛まない、いいシステムなのです。


sanami@aya.or.jp