ラベル PONG の投稿を表示しています。 すべての投稿を表示
ラベル PONG の投稿を表示しています。 すべての投稿を表示

2009年2月20日金曜日

TinyでPONG!(ソフトウェア4)

水平1ライン分を描画するプログラムの仕上げとしてスコア表示を説明しましょう。

スコアは2桁の数字です。普通に考えれば1桁分の「数字フォント」を0から9まで10種類用意することになるのですが、ここではフォントの幅=ドット数も勘案して2桁で1つの数字として扱っています。2桁だと100種類のバリエーションができてフォントの格納領域が膨大になってしまいそうですが、そこはPONG。0点から15点まで16種類しか必要としません。その定義が以下の"fconst.asm"です。

fconst.asm ("1"を強調表示しています)

.setと.dbがあって一瞬なんだこりゃ?ですが、フォントをビジュアルで定義するための小技です。1のビットが白――文字部分――になります。1つの数字は横×縦=4×5ドットの枠の中に3×5ドットとして表現されています。これを横に2桁分並べて8×5ドットの「2桁数字」を1フォントにしています。

一方、画面デザイン上の問題として、フォントの1ドット=画面上の1ドットにしてしまうと数字があまりにも小さくなりすぎてバランスが良くありません。数字をある程度の大きさで見せるためにはサイズを拡大してあげないといけない。そこでフォントの1ドット=画面上の4ドットと縦横4倍になるようにしています。

また、スコアの数値自体はSRAM上の変数ScoAとScoBに入っているのですが、そこから上記のフォントデータに変換して、4倍にふくらませて…とやっていると水平ブランキング期間内に処理が終わりません。そこで、スコア→フォントデータへの変換部分だけはあらかじめ――垂直ブランキング期間内に――終わらせておくという措置をとります。その処理をするのが"font_cache.asm"です。ScoA/Bの値を表すフォントデータがSRAM上のバッファ変数Ftbufに格納されます。

さて、Ftbufに格納されたフォントデータを描画する部分が"draw.asm"の最後の部分です。まず、スコアが描かれる縦位置に来たかどうか判定します。

;; --- draw scores ---
drawscore:
mov zl, nxline ; +1=42 prepare for font load
subi nxline, DGT_TOP ; +1=43
cpi nxline, DGT_BOT+1-DGT_TOP; +1=44
brsh eol ; +1=45

スコアが描かれる位置に来ました。次はフォントを縦4倍に広げるのですが、どうしたらよいのでしょう。答えは「4回足踏みして待つ」です。次にどこの行を走査するのかを示す変数nextlineは1ずつ増加するのに対し、4回同じデータを描画すれば縦が4倍に伸びることになります。nxline(のコピーのZL)を4で割っている(LSR命令が2回)のはそのためです。

lsr zl ; +1=46
lsr zl ; +1=47
addi zl, low(Ftbuf)-DGT_TOP/4; +1=48
clr zh ; +1=49

そして横の4倍化。「11110000」と「00001111」の2つのビットパターンを用意しておいて、フォントデータの1になっているビットの位置によってこれら2つを使い分けて行のデータと論理和を取ります。分かりにくいと思うので実例で。

今、描画しようとしている先の行には下記のようにボールの一部が描画されているとします。R7-R10はスコアを表示する位置にあたります。


ここにたとえばフォントデータ"14"を重ねて描画しようとしたとして、
    11001010
    01001010
    01001110 ←この行を描画
    01000010
    11100010

今はそのフォントの3行目だとすると、その描画データは01001110です。これを上位ビットから判定していきます。ルールは0なら何もせず、1ならそれが偶数ビットであればパターン11110000を、奇数ビットであればパターン00001111を重ねるというものです。するとこうなります。
    ビット7は0 … 何もしない
    ビット6は1 … パターン00001111をR7に重ねる
    ビット5は0 … 何もしない
    ビット4は0 … 何もしない
    ビット3は1 … パターン11110000をR9に重ねる
    ビット2は1 … パターン00001111をR9に重ねる
    ビット1は1 … パターン11110000をR10に重ねる
    ビット0は0 … 何もしない

図示するとこうです。

この部分のソースはこれです。ScoA側だけ示します。SBRC命令を使って0のビットをスキップしています。ScoBについても同じ処理になります。

.def fontd =r24
.def pat10 =r27
.def pat01 =r26
ldi pat10, 0b11110000; +1=50
ldi pat01, 0b00001111; +1=51
;; for Score A
ld fontd, z ; +2=53 load a font row of A
sbrc fontd, 7 ; +1=54
or rast7, pat10 ; +1=55
sbrc fontd, 6 ; +1=56
or rast7, pat01 ; +1=57
sbrc fontd, 5 ; +1=58
or rast8, pat10 ; +1=59
sbrc fontd, 3 ; +1=60
or rast9, pat10 ; +1=61
sbrc fontd, 2 ; +1=62
or rast9, pat01 ; +1=63
sbrc fontd, 1 ; +1=64
or rast10, pat10 ; +1=65

以上で映像出力と描画関連のプログラムの説明は終わりです。ゲームの進行については垂直ブランキング期間――1/60秒に1回かつ1.28msの間だけ――に行われますが、それはgame.asmを読んでいただくことにしましょう。

長々と説明をしてきましたが、とりあえずソフトウェアの説明はこれで終了です。お疲れ様でした!

2009年2月7日土曜日

TinyでPONG!(ソフトウェア3)

TinyでPONG!(ソフトウェア2)では映像出力の最前線の部分の説明をしました。1水平ライン分の映像データは24個の汎用レジスタのR0~R23に用意されているということが前提でした。逆にいえばデータをレジスタに入れさえすればいいわけです。

全速力で1ラインの映像を出力した後は、次のライン出力の出番が来るまでのつかの間にデータを仕込みます。ソースは"draw.asm"にあります。最初に判定すべきは何のデータを仕込まなければならないのかです。つまり次に何行目を走るのかで書きだすものを変える。大くくりでは、
  • 1~10行目 → サイドライン(横一本ずっと白=「1」)
  • 11~226行目 → コート内部(センターライン、スコア、ボール、パドル)
  • 227~236行目 → サイドライン
となります。この判定をしているのが以下の部分。

;; --- prepare next raster ---
cpi nxline, COURT_MIN; +1=2
brlo sideline ; +1=3
cpi nxline, COURT_MAX; +1=4
brlo field ; +2=6

;; --- draw sidelines ---
sideline: ; draw sideline
filrast ; (+24)
rjmp eol

次に何行目を走るのかはnxline(ここではR27と同義)に入っています。それがコート内部にあるかどうかの比較があって、外部であればサイドラインを書くべくfilrastを実行して終わりです。filrastはマクロで、汎用レジスタR0~R23の全ビットを1に立てるだけです。これは簡単。

コート内部だったら前出の通りサイドラインとボールとスコアとパドルのデータを用意しなくてはなりません。これらは排他的ではなくて、すべて書かなければならない状態もあり得ますから1つ書いてサヨナラというわけにはいきません。全部説明するのも冗長なのでここではまずボールを書く部分を。

;; --- draw ball ---
.def pos =r24
drawball:
lds pos, Ballt ; +2=10
cp nxline, pos ; +1=11 skip if (line > Ball_top)
brlo drawpad ; +1=12
addi pos, BAL_HEI ; +1=13
cp pos, nxline ; +1=14 skip if (line < Ball_bot)
brlo drawpad ; +1=15 / +2=16

.def pattern =r26
clr zh ; +1=16
lds zl, Ballad ; +2=18
lds pattern, Ballpt ; +2=20
st z, pattern ; +2=22
com pattern ; +1=23
std z+1, pattern ; +2=25 (r24 can be written)

次の行nxlineがボールの上辺の位置Balltと下辺の位置Ballt+BAL_HEIの間にあるかどうかの判定が冒頭にあります。なければそこにはボールがないので書く必要がないということになり、次のパドルを書きに行きます。

ボールを書く場合、ボールのバイトアドレスBalladでレジスタ番号を指定し、ボールのビットパターンBallptをそのレジスタに書き込みます。ここがAVRのアーキテクチャの特徴をうまく使った部分で、レジスタがメモリアドレスのゼロ側にマップされていることを利用しています。つまり、メモリアドレス0x0000はR0を、0x001fはR31を指し示し、この例のようにZレジスタ間接などでもあたかもSRAMをアクセスしているかのごとしです。このようにできることはデータシートには書いてなくて(探しが足りなかったのかも)、実験してみたらできてしまってラッキーでした。こいつは便利だ。AVRすごい!

ボールのビットパターンを書くと言いましたが、ボールの幅が8ドット=8ビット、レジスタが8ビット単位なので(ほとんどの場合)2つのレジスタにまたがって書かなければなりません。この例ではまず指定されたレジスタに左側を書き、次のレジスタに右側を書いています。右側のパターンはちょうど左側のパターンのビット反転になっているのが分かりますでしょうか。(だからcom命令で反転しています)

ボールの形を書く


今回はこのくらいで。次はスコアの表示です。

そうそう、ソースアーカイブにレジスタマップ(regmap.txt)を入れておいたのを忘れていました。今Excelで開いてもう少し見やすくしたイメージを下に貼っておきます。

レジスタマップ

2009年2月6日金曜日

TinyでPONG!(液晶テレビで映らない?)

このPONGの記事を参考に実際に作ってみてくださった方がいらっしゃるらしくてとても光栄な気持ちです。この場を借りて御礼します。

そちらの記事の中で液晶テレビには映らないというご報告がありました。本当に申し訳ないのですがそのことを説明するのを忘れていました。そうなんです。確かに私も確認していました。仕事で使っているNTSC入力付きのデルの液晶モニターには映りませんでした。これは液晶テレビ全般がだめかと思いきや、自宅で使っている三菱製の32インチの液晶テレビ(2005年製の地デジ・BSデジ対応機)では映ります。

実は、今回の作例ではNTSCの信号規格を完全には満たしていません。一番効いているのが、簡略化のためなのですが、インターレースにしていないところでしょう。本来は秒間60フレームではなくて2フィールド×30フレームにするために同期タイミングを小細工しなくてはならないのです。これが、シビアな同期判定の仕組みを持つ最近の液晶テレビだと認識してくれない(だまされてくれない)ケースがあるんじゃないかと。説明が不足していてすみません。

むか~し同じような簡略化をしていたやつ(ゲーム機だったか、コンピュータだったか)があったような。そういうのは液晶テレビには映らない(ことがある)んでしょうね。

(次回はソフトウェアの解説の続きをします。)

2009年1月20日火曜日

TinyでPONG!(ソフトウェア2)

映像出力の仕方を具体的に説明します。

映像データを出力ポートに滞りなく書きこまねばならないのですが、どのくらいの速度でなければならないのでしょう。水平方向192ドットを、水平映像期間48μs=384クロックで出力するので、1ドット当たり2クロックにすればよいのです。

この速度はプログラムをいくら効率よく書いてもループ(分岐)を使った時点で達成はできません。分岐命令は、後方分岐(ループ)の場合それだけで2クロックを消費してしまうからです。そこで、ループを「展開する」ことにしました。ループを展開するというのは同じ命令をループの回数だけずらっと並べて書くということで、当然プログラムの容量は増大します。

ループを展開するとはということです。簡単ですね。

ループの展開をすれば1ドット出力に2クロックを使うことができます。しかし2クロックしかありませんから、これとてもいくつかの工夫をしなくてはなりません。
  1. 汎用レジスタにあらかじめ映像データを用意しておく。
  2. 1つめのクロックでポート出力を完了させ、
  3. 次の1クロックで汎用レジスタ内の映像データを1ビット(左)シフトする。
直前の周期であらかじめ1行分の映像データを汎用レジスタ(群)に仕込んでおくことで上記のアイディアが実現できます。これは汎用レジスタが32本と多いAVRだからこそできる技で、そのうちの24本をこの目的のためにつかいます。24本×8ビット=192ドットですから。

また、出力命令のOUTは1クロックで完了するのですが、8ビットいっぺんに出力されます。8ビットマイコンなんで当たり前のことですね。その中の1ビットだけ出力したい(変化させたい)という場合、通常はポートを8ビットで読んで→狙ったビットをクリアし→狙ったビット以外の部分を0でマスクしたデータと論理和し→ポートに書き戻すとか、ビットクリア/ビットセット命令を分岐で使い分けたりと結構面倒なもので、とても1クロックで済むような話ではありません。

そこで、出力ポートの他のビットを無視すなわち未使用にすることにしました。そうすると面倒くさい1ビット書き換え処理が全く不要になり、単に8ビットいっぺんに出力するだけで済んでしまうのです。以前「映像出力がポートの最上位ビットであることと、同じポートの他のビットに出力が割り当てられていないことがとても重要」と書いたのはこのためだったのです。

これらを図で示すと下記のようになります。

プログラムの中でこの処理をやっているのはファイル"draw.asm"の以下の部分です。ループの展開はまだるっこしく読みにくいのでマクロを使っています。

8ビット分のループ展開(マクロ定義)

.macro vout8px
out PORTB, @0 ; +1=1 output bit7
lsl @0 ; +1=2
out PORTB, @0 ; +1=3 output bit6
lsl @0 ; +1=4
out PORTB, @0 ; +1=5 output bit5
lsl @0 ; +1=6
out PORTB, @0 ; +1=7 output bit4
lsl @0 ; +1=8
out PORTB, @0 ; +1=9 output bit3
lsl @0 ; +1=10
out PORTB, @0 ; +1=11 output bit2
lsl @0 ; +1=12
out PORTB, @0 ; +1=13 output bit1
lsl @0 ; +1=14
out PORTB, @0 ; +1=15 output bit0
clr @0 ; +1=16 clear this segment for next timing
.endmacro

24レジスタ分のループ展開

vout8px r0 ; 16*24=384cycles(visible period)
vout8px r1
vout8px r2
vout8px r3
vout8px r4
vout8px r5
vout8px r6
vout8px r7
vout8px r8
vout8px r9
vout8px r10
vout8px r11
vout8px r12
vout8px r13
vout8px r14
vout8px r15
vout8px r16
vout8px r17
vout8px r18
vout8px r19
vout8px r20
vout8px r21
vout8px r22
vout8px r23
out PORTB, r23 ; 0+1=1 set to blank level

最後のOUT命令は描画が終わったら背景の黒レベルにもどす処理です。これをやらないと右端のドットが1だった場合、白い線が画面の右端までびよ~んと尾を引いてしまいます。

2009年1月19日月曜日

TinyでPONG!(ソフトウェア1)

さあ、ここからが本番、ソフトウェアの説明に入ります。早速ソースコードをアップロードしておきました。


トップレベルのエントリーは"pong.asm"です。拡張子を見ただけで分かると思いますが、8MHzという低速のCPUでビデオ信号のタイミングにシビアに合わせるためアセンブラを使っています。AVRのような簡易なマイコンはパイプライニングなどの高級な高速化技術を一切使っていないため、命令表の消費クロック数の値がそのままタイミングカウントとして簡単に計算できますので、アセンブラで記述すればタイミングを正確に合わせることができます。

時代はデジタルハイビジョンに向かっているのに今更ながらアナログTV信号(NTSC方式)を扱いますが、アナログ放送の停波まで公式にはまだ2年ちょっとありますし、全世帯からアナログTVがなくなるのはまだまだ時間がかかると思います。逆にデジタルTVへの切り替えで不要になるアナログTV(壊れても泣かなくて済む!)を実験などに存分に使えるようになる絶好の機会が到来すると前向きに考えましょう。

NTSC信号を使ってTVに絵を出すためにどんな信号をどのようなタイミングで出さなければならないかは、こちらなどのサイトに素晴らしい説明がされていますので参照していただくことにして、本プロジェクトではそのタイミングをどのようにして作り出しているのかを説明します。なお、本プロジェクトで扱う映像信号は白黒で、カラーではありません。また、グレースケールはなく2値の白黒ーーすなわち1画素=1ビットです。

信号出力のタイミングは唯一、16ビットタイマーのTimer1からの割り込みによります。Timer1は原クロック8MHzでカウントアップし、水平同期信号の周期(63.555μs≒64μs)に達したところでゼロクリアして自動的に循環するモードに設定。このカウンタの2つある比較レジスタ一致の1つOCR1Bでの一致により発生する割り込みで映像信号出力を開始します。同時に比較レジスタOCR1Aの値によるPWM動作を設定し、OC1Aピン(=PB1ピン)から水平同期信号を自動的に出力しています。


これをこのように作ります。無理やりテキストで書いて、見にくくてすみません。のこぎり状の波形はTimer1カウンタの内容です。


水平方向にいくつカウントするかは64μs×8MHz=512カウントですのでTimer1は0から511まで変化します。カウンタがOCR1Bレジスタと一致したところで事前に準備してあった水平1行分の映像データをタイミングをきちんと計算されたプログラムで1画素=1ビットずつ出力します(図中の「映像送」)。1行出力した直後には次の行のデータを準備しておきます。図中「備」と書かれた部分がそれに相当します。準備してから次の割り込みが来るまでの間は、たった数クロック程度の余裕しかありませんが、スリープ(図中の「Z」)して待ちます。

割り込みハンドラ(ソースtim1oc1b.asm)は垂直方向のカウント、垂直同期の生成、パドル=簡易A/Dコンバータの状態遷移の管理、音声出力など軽い処理をするのみです。

割り込みハンドラから戻ってくるとメインプログラム(main.asm)のスリープ命令が解除されて、映像送出(draw.asm)と次の行の準備(同)を行います。

OC1A出力はOCR1Aレジスタでの一致で"L"を出力、カウントトップすなわちICR1レジスタとの一致で"H"を自動的に出力するような高速PWMモードの設定にしてあります。これが同期信号出力になります。

水平方向の表示期間が終わり、垂直ブランキング期間に入ったかどうかはメインプログラムの冒頭で判断しています。本プログラムでは水平20行分がその期間となります。その中の3行分の期間は水平同期信号の極性反転を行います。これが垂直同期信号となります。極性反転は前述のPWMの条件を反転するだけで実現しています(つまり、OCR1A一致で"H"、トップで"L"になるように)。

垂直ブランキング期間では、映像出力が必要なくなるのでこの時とばかりにゲーム本来の動作を実行します(game.asm)。表示期間とブランキング期間合わせて全部で256行あるうちたった20行がゲーム進行に使われますので、本来の7%のパフォーマンスしか得られません。8MHzで公称8MIPSのAVRでも約0.6MIPSとなります。それでもこの程度のゲームであれば全く問題なくスムーズに動作しています。

2009年1月7日水曜日

TinyでPONG!(ハードウェア)

長らくのお待たせでした。PONG復活です。今回は回路図の公開とハードウェアの解説をしましょう。

例によって秋葉原の某所(ほとんど秋月ですが…)で入手しやすくできるだけ安価な部品ばかり集めています。基板を除けば千円でお釣りがくるくらいです。

Tiny2313は約8MHzのRC内蔵発振器を持っていますが、わざわざセラロックを用いているのは周波数の安定性が重要になるからです。もちろん水晶発振子でもかまいません。ためしに内蔵発振器を使って画面出力をしてみたところ、絵がゆらゆら、ばらばらでとても見られたものではありませんでした。温度係数もやたら大きくて、温めたり冷やしたりすると揺れ具合も大きく変化します。

映像出力はPB7、同期信号出力OC1A(=PB3)で、これらはダイオードとRを使って混ぜてコンポジット出力としています。実は映像出力がポートの最上位ビットであることと、同じポートの他のビットに出力が割り当てられていないことがとても重要なのです。(PB3が出力として使っているように見えますが、これはあくまでもOC1A出力として使っているので影響ありません。)

音声出力はPD6から出しています。出力コンデンサを省略していますが、心配ならば100uF程度のアルミ電解を直列につなぐと良いでしょう。

パドル入力用の簡易ADCにはコンデンサーへの充放電を制御する出力PD4、PD5と放電時間を計測するための入力PD0、PD1を使います。PD4またはPD5をHにしてダイオードを経由して急速に充電したのちLに切り替えます。放電はボリュームの抵抗を通って徐々に行われます。コンデンサの端子電圧がVILまで下がったことをPD0またはPD1でセンスし、それまでの時間をボリュームの抵抗値=パドルの位置として求めるのです。

ありがたいことにTiny2313は旧製品の90S2313と違って3Vで動作しますので、電源は単三乾電池2本でOKです。この回路では電源スイッチはありませんが、ソフトウェアスイッチ(サーブスイッチSW2を長押し)でパワーダウンモードに入ります。この時の消費電流は極めて低くて1μA未満です。

(2009.2.12 回路図のTYNY2313のシンボルマークを修正。)

2008年10月15日水曜日

TinyでPONG!(概要)

これから製作例を紹介する「TinyでPONG!」は、非常に安価な8ビットマイコンATTiny2313ひとつでこの黎明期のテレビゲームを「安価に」模倣してみようじゃないかというプロジェクトです。

Tiny2313はプログラムメモリー(ROM)が2Kバイト、RAMが128バイトしかありません。RAMといっても当然VRAMではないので、そのまま映像出力ができるような仕組みは一切ありません。

しかも画面表示解像度は水平192ドット×垂直236ラインですので、VRAMで実現しようとすると1ドットあたり1ビットとして5,664バイトが必要になってしまいます。やはりVRAM方式では無理ということになります。

PONGの映像を出力するのと同時に水平・垂直の同期信号を作ります。テレビ側はこれらの信号に合わせて画面の上端と左端のタイミングをつかみます。この同期信号のタイミングをTiny2313内蔵のタイマ/カウンタで作り出します。

プログラムでは、前述のボール左端カウンタ、右端カウンタ、上端カウンタ、下端カウンタ、左パドル上端カウンタ、下端カウンタ…が変数として使われて、ハードウェアカウンタをシミュレートしているような格好になっています。

1「行」分の映像を出力するところは一番工夫したところで、非常に高速に、かつ、安定した速度で動かなければならないので、かなりトリッキーなことをやっています。AVRの汎用レジスタの数の多さを最大限利用しています。PONGの仕組みを模倣すると言っておきながら、この部分ではちょっとずるをしているのですが…VRAMを使わないことが一番の目的なのでまあ許してもらいましょうか。

パドルは10KΩのボリュームで制御します。ボリュームの位置を検出するのに電圧を直読できるA/Dコンバータがほしいところでしたが、あいにくこのマイコンには内蔵されていません。そこで、簡易A/Dコンバータをソフトウェアで実装してあります。コンデンサを決まった電圧に充電しておいて、ボリュームの抵抗で放電させ、電圧が一定レベルまで下がったときまでの時間を測ります。抵抗が小さければ素早く放電され、抵抗が大きければゆっくり放電されます。詳しく計算すると分かるのですが、抵抗値とこの放電時間は比例します。つまり、ボリュームはBカーブ(=直線)であればそのままBカーブとして使えるのです。

サーブをするためのボタンは電源ボタンを兼ねています。電池を入れるとすぐには映像は出てきません。サーブのボタンを押すと初期画面が現れます。映像が出ているときはいつでもサーブのボタンを5秒以上押し続けることにより電源を切ることができます。

そのほかにも音が出たり、ソフトで電源ランプ(LED)の輝度調節をしていたり、さまざまなことをやっています。

プログラムのソースコードを公開する予定ですので、詳しくはそちらをのぞいてみてください。

2008年10月14日火曜日

なぜPONGなのか(4)

ここまで来てやっと「なぜPONGなのか」がお話しできます。

PONGは今や当たり前となっているコンピュータ+VRAM+ソフトウェアの方式とは構造をまったく異にしています。

テレビがスキャンビームを左から右に、上から下に振って絵を描き出すその仕組みを直に利用しています。テレビ画面はもとから2次元ではなく、織物をほどいて時間軸方向に引き伸ばしたものを、逆の手順をたどって織り上げたものです。今まさにスキャンビームが通るその瞬間を捕まえて輝点を打つ、そのタイミングが画面上の座標に対応します。その瞬間を捕まえるのに多数のカウンタとコンパレータが活躍するのです。

ゲームの進行もコンパレータでの一致検出とカウンタの計数方向の反転だけで実現されています。動物に例えると条件反射あるいは脊髄反射的といえましょう(図らずも同じ「反射」ですね…)。そこにはソフトウェアのソの字もありません。

現代人にとっては、ゲームとしては単純すぎて、遊ぶにはすぐに飽きてしまいますが、コンピュータではなく「デジタル回路」と「テレビジョン技術」の学習としては実によくできた教材になるのではないでしょうか。

2008年10月13日月曜日

なぜPONGなのか(3)

PONGの話に戻りましょう。

実はPONGゲームはまだマイコンが普及してなかったころに作られたものです。その前の年にインテルが世界初のマイコンi4004(4bit)を発表したばかりのころ。マイコンを採用しようという概念は当然なかったでしょう。仮に採用したとしてもメモリーも高価で、たった数キロバイト程度でも使えません。

「VRAM→画面の仕組み」の力を借りずして、どうしてテレビにゲームの画面を映し出すのでしょう?これには前述のOSDの話と関係があります。

テレビはアナログの映像信号1本--時刻とともに上がったり下がったりする電圧が一組あるだけ--で面の情報を映し出します。そのためにスキャンビームが画面を走査(スキャン)するのです。最上部の左端から右端へ、右端に到達したら次の行へ。横書きノートに口述筆記をするのと同じでです。ノートと違うのは最下行に到達したら次のページにめくるのではなく、左上に戻ることです。

幸いPONGは複雑な図柄は必要ありません。画面上の1つの「行」に注目すると、一番たくさんの種類の図柄を描く場合は、①左のパドル、②左のスコア(数字2桁)、③ボール、④センターライン、⑤右のスコア、⑥右のパドルの6種類だけです。

テレビのスキャンビームが左端にあるタイミングは、信号を出す側にとって分かりきったこと。③ボールの位置にスキャンビームが到達したその瞬間、映像信号を叩き「白」を出します。ボールの横幅を過ぎたところで今度は「黒」を出します。

OSDの話ではマイコンのプログラムループでこのタイミングをとっていました。しかし実はこれらはハードウェアだけでできるのです。

ビームの水平位置を示すカウンタを、画面の左端でゼロリセットしてからカウントアップの状態にし、カウンタが出力する値をコンパレータ(比較器)で検出し、映像信号を「白」にセットします。コンパレータへのもう一方の入力値はこの場合はボールの左端の位置です。また別のコンパレータを使って、ボールの右端を検出したら「黒」をセットします。

こうして6つの要素について次々と上記と同様のことを行って1行分を映像出力します。もちろんその行にボールがなかったり、パドルがなかったりする場合は「白」の出力はしません。そのためには「ビームが何行目にいるのか」を数えるカウンタ、「ボールが何行目にいるのか」を検出するコンパレータなどが必要です。

それぞれの要素について左端、右端、上端、下端を保持しておくカウンタや一致検出のためのコンパレータなどのハードウェアが必要になります。ゲームの進行もこれらのカウンタ、コンパレータをうまく使って実現することができます。(たとえば右パドルでボールが左から右へと反射するのは、ボールの右端を覚えているカウンタが右パドルの左端を覚えているカウンタと一致したとコンパレータで検出した瞬間に、ボール用のカウンタの計数方向(上昇/下降)をひっくり返すことに他なりません。)

全体としてなんだかものすごいハードウェアの量になりそうですが、それでも当時としてはコンピュータ+メモリのほうがずっと複雑で物量も大きく、なによりも高価だった。カウンターとかコンパレータが山ほどあってもそれで済むのならまだ経済的だったのです。

2008年10月12日日曜日

なぜPONGなのか(2)

そしていつの間にか私もいい歳の大人になって、おやじになって、世の中はテレビゲームのみならず携帯ゲームが世の中を席捲している状況。ひとえにコンピュータ産業の発展のたまものです。

これらはコンピュータを内蔵し、ソフトウェアで動くという点で世代にかかわらず私から見れば「同じもの」。つまり、画面上の座標と一対一に対応付けられたビデオメモリー(VRAM)に、ソフトウェアで指令されたコンピュータのコア(CPU)がせっせとイチゼロのデータを書き換え続けるということです。VRAMのある番地のデータが書き換われば対応する画面の座標の色が変わります。そういうVRAM→テレビ画面の仕組みがチップ内に組み込まれているのです。

私が生まれて初めて触れたコンピュータは、すでにテレビ用のインターフェースを備え、白黒ではあったが文字表示と簡易グラフィック表示ができるものでした。当然画面への表示は前述のVRAM→画面の仕組み(CRTC = Cathode-Ray Tube Controller)が搭載されていました。テレビに何かを表示したい場合にはそのような仕組みにするのが当然であり、それしか方法はないものだと思い込んでいました。まだ何も知らない若造だったのです。



某電器メーカーのコンピュータ設計部門に就職したその年、先輩エンジニアたちと雑談をしていて、私が愚にもつかない質問をしました。

「テレビのOSD*を出すためのマイコンってどのくらいVRAM積んでいるんですかね?」

(* On-Screen Display: チャンネル番号やボリュームレベルをテレビ映像に重ねて表示する機能)

ちょうどテレビの設計部門から転属してきていた先輩エンジニアはすかさず答えます。

「VRAMなんか持っているわけないじゃん。そんなメモリー積んでたら(値段が)高くなっちゃってしょうがないよ。」

自分の常識を覆す答え。え?それじゃあどうやって文字を出すの?

「スキャンビーム(走査電子ビーム)が、うまい位置を通るちょうどその時に映像信号をONするようにプログラムループを使ってタイミングをとるんだよ。テレビなんて何種類も文字出さないし、文字の解像度もすごく低いからからそんなんで十分。アセンブラでゴリゴリよく書いたもんだ。」

目から鱗でした。なるほど、極小のリソースの中でどれだけ複雑なことができるか、これぞ技術だ、技だと思ったものです。

2008年10月10日金曜日

なぜPONGなのか(1)

私が子供の時分にTVコマーシャルで、そしておもちゃ屋の店頭で見た「テレビテニス」はあまりにも鮮烈で脳裏に焼き付いています。なにしろそれまでテレビに映し出されるものはテレビ番組しかなかったわけで、その画面に何やら番組以外のものを映し出して遊ぶという概念さえ持ち得ませんでした。しかも(遊ぶからには当たり前ですが)コントローラ上のノブを回して自分の意のままにパドル(棒の映像)が動く。それはそれはびっくりこいてしまったのです。

我が家は貧乏ではありませんでしたが、当時このようなものを持っている友人は皆無で、当然買ってもらえるような代物ではありませんでした。いや、あまりにも自分の立場とかけ離れた存在だったので、親に遠慮して欲しがらなかったのかもしれません。

今ネットで調べてみるとこれは1975年にエポック社から発売されたものと判明。定価19,500円とは、当時としてはまったく子供向きの価格ではなかったんですね。

また、こちらの情報によれば「得点を記録する機能がなく、本体に回転式の表示装置が付けられている」って…へーそーだったんだー。てことは、ボールを跳ね返すことしかできなかったんだね。当時は技術立国の直前。それでも家庭用ゲームとしては強烈に画期的だったはず。

日本の「テレビテニス」のおおもとは米国アタリ社のアーケードゲーム "PONG" らしい。1972年の発表だったそうです。こちらの "PONG" はネット上で情報が集めやすく、プレイ中の動画も YouTube などから発掘できます。

こっちにはちゃんと得点表示が画面に表示されています(自分としてはなぜかこっちの画面のほうが記憶に残っています)。

2008年10月9日木曜日

TinyでPONG!(動画)


Tiny2313でつくるPONGゲーム。ようやく動画ができました。WindowsMovieMakerはタダであるがゆえあちこち不満はあるものの、ど素人でもこの程度のビデオが作れてしまうのは驚愕、さすがMSと言えます。

製作記事はまたいずれ後日に。

2008年9月30日火曜日

TinyでPONG!(予告)


こんなものを準備中です。レトロすぎますか?あと2年とちょっとでアナログTV放送も終わるというのに…(涙)…。

Tiny2313(秋月で100円!)を使っています。パドルもボールも滑らかに動きます。音も出ますよ。そのうち動画をアップしようと思います。