フルーツ ミラージュ 製作過程(1)

Home PC-6001mkII Program etc

 このページでは、作成開始から画面表示までの過程を書いています。

フルーツ ミラージュ 製作過程 Top


2009/08/10:ネタ仮決定
 HSPプログラムコンテストが開始してからも10日になるのだが、今回も何をするのかが決まっていない。

 今回新しく加わった物理エンジンOBAQも面白そうだと思い、サンプルを実行してみる。プログラムの長さの割に面白い動きになることはわかったが、ちょっと理解するのに時間がかかりそうだ。とりあえず今回も標準命令だけで頑張る事にしよう。

 さてここで目に着いたのが、先月まで作成を続けていた P6版「フルーツ ミラージュ」だ。このセミグラフィックの雰囲気を持っていけば、少しインパクトがあるかも知れない。

 キャラクタや面のデータが多いので、サイズが入りきるかが不安だが、まずは基本的な部分を作っていくことにしよう。

 仮決定ではあるが、こうして作成が開始された。

2009/08/10:データ形式
 さて、いつもどおりまずは見た目だ。P6のセミグラフィックキャラを、どうデータ化してどう表示させるかを考える。

 ここで、P6のセミグラフィックを知らない人のために少し補足しておく。セミグラフィックキャラクタは、アルファベット等のキャラクタとほぼ同じ扱いで、1文字は2x3のドットで構成されている。
 背景色は黒のみで、文字色は文字単位に8色(緑黄青赤白水桃橙)から1色選択できる。つまり1文字を構成する2x3ドット(赤枠部分)は文字色(8色中1色)+背景色(黒)になる。

画面

 P6版のキャラクタは、6x6ドット(3x2文字)で構成している。文字単位で色をうまく振り分ける事で、カラフルなキャラクタを作成している。

 さて、このキャラクタをHSPでどう描画するかだが、今回はドットパターン部分と色部分に処理を分けようと思う。

 まずはキャラクタ部分のデータ形式だ。1文字分のデータが6ドット分、つまり2の6乗=64種類なので、暫定的に「0(コード48)」〜「o(コード111)」で表現する事にする。とりあえず、1キャラクタ分だけキャラクタのドットパターンとキャラクタコード表を見比べてデータを打ち込む。

 キャラクタの描画だが、まず64種類のドットパターンを作成する処理を作成し、データに応じたドットパターンを拡大コピーする。これをキャラクタの数だけ繰り返す。

画面

 次に色だ。データは、暫定的に色番号と同じ「1(コード49)」〜「8(コード56)」を割り当てる。
描画は、「減色コピー」の機能を利用して色付けしていく。白色から本来表示したい色の反転色を減色する事で、思い通りの色が付けられる。
 例えば、緑色(RGB:0,255,0)に色付けしたい場合は、以下のようになる。
  1. 緑の反転色(RGB:255,0,255)を用意する。
  2. 1.の色を減色で使用する。
    白(RGB:255,255,255)部分:(RGB:255-255,255-0,255-255)→ 緑(RGB:0,255,0)
    黒(RGB:0,0,0)部分:(RGB:0-255,0-0,0-255) → 黒(0,0,0)
 これでキャラクタ1つ分だけ完成だ。あとは地道に全てのキャラクタデータを打ち込む。この後、データサイズ縮小のため、色データ2文字分を1文字(コード64〜127)で表現するよう変更する。

画面

 今日はこんな所だろう。

2009/08/11:面表示
 キャラクタが一通り出来上がったので、面データ形式の検討に移る。

 面データの構成キャラの種類は、数えてみると11ある(空白, 岩, 壁, トム君, ドア, フルーツ6種類)。
 1キャラ分を1文字のデータで表現すると簡単ではあるが、それだと1面あたり64文字、16面だと1024文字分のデータが必要になる。データ部分のサイズは極力押さえたいので、ここは2キャラ分を1文字のデータに集約する事にする。

 ここで、文字で表現できるコード番号範囲の問題が出てくる。使用可能と思われる文字コード番号の範囲は、20H〜7FH(16進表記)の96種類だ。
 一方、2キャラ分を1つのデータに集約させようとすると、11×11=121種類必要となる。ダメだ、入りきらない。

 ...とあきらめるには、まだ早い。トム君とドアは、面に1つずつなので、この2つを別データにして、残りの9種類のキャラ2つ分を1文字のデータに集約させることにする。これだと81種類で事足りるので文字データとしても収める事が出来る。

 昨日のキャラクタデータ手動変換は結構手間がかかったので、今度は面データ変換用のミニプログラムをHSPで作成する。人はこうやって学習していく。
 とりあえず画面表示する事が目標なので、1面のデータのみちゃんと入力して、後の15面分はダミーデータを入れる。

 ここまで来たら、あとは表示するだけだ。単純に表示するだけなら簡単なので、サクサクっと作成する。

画面

 いつもながら、まだ全く出来ていないが、出来た気になる。

2009/08/11:サイズ削減その1
 面表示まで終わったところで、ソースを見直してサイズ削減できるところがないかを調べる。

 面データの展開部分は、こんな風に変更する事で80バイト削減した。と、これだけ見てもわかりづらいので、具体的に説明しようと思う。
変更前変更後
1
2
3
4
5
6
7
8
9
10
11
12
13
14
repeat 8
  c = cnt * 4
  repeat 4
    n = peek(maze, c + cnt)
    p_l = n / 9 - 4
    if p_l > 2: p_l += 3
    p_r = n \ 9
    if p_r > 2: p_r += 3
    a_p(v) = p_l
    a_p(v + 1) = p_r
    v += 2
  loop
  v++
loop
repeat 32


  n = peek(maze, cnt)
  p = n / 9 - 4
  repeat 2
    a_p(v) = p + (p > 2) * 3
    v++
    p = n \ 9


  loop
  v += ((cnt & 3) = 3)
loop

 まず、4〜11行について説明する。ここでは、同じ処理にしてからまとめる作戦を取っている。

 4〜11行は、以下のように処理が置き換えられる。この時、赤字部分が同じ処理(=まとめられる処理)となる。
置き換え前置き換え後
1
2
3
4
5
6
7
8
9
n = peek(maze, c + cnt)
p_l = n / 9 - 4
if p_l > 2: p_l += 3
p_r = n \ 9
if p_r > 2: p_r += 3
a_p(v) = p_l
a_p(v + 1) = p_r
v += 2
n = peek(maze, c + cnt)
p = n / 9 - 4
if p > 2: p += 3
a_p(v) = p
v++
p = n \ 9
if p > 2: p += 3
a_p(v) = p
v ++

 全てで通用するわけではないが、処理A → 共通処理 → 処理B → 共通処理 となる場合は以下のような処理に置き換えることが出来る。

処理A → repeat 2 → 共通処理 → 処理B → loop

 これだと処理Bが2回実行されるから同じではないだろ、というツッコミがありそうだが、それはその通りだ。しかし、2回目の処理Bが他に影響を与えなければ、結果的に同じ動作をすることになる。
 今回の場合は、処理Bにあたるのは「p = n \ 9」だが、変更されたpの値はこの後使用されないため、特に問題なく動作する。

 次に、残りの1〜3行と12〜14行だ。
処理
1
2
3
4
5
6
7
repeat 8
  c = cnt * 4
  repeat 4
    :
  loop
  v++
loop

 ここでは、逆にループを省くことでサイズを削減する。

 そもそも、ここで二重ループにしていた理由は、4回処理が終る毎に変数vを+1したかったからだ。なら、4回に1度だけ+1する式を毎回実行すれば、ループは1つ要らないだろう。

 という事で、外側のループカウンタを利用して以下のようにカウントアップ処理を変更する。

v += ((cnt & 3) = 3)

 太字で書いたわりに大したことではないが、これでループ処理が一回分減らせる。
この他、副産物として、外側のループカウンタを変数を介して内側のループに持っていく手間が省けるので、サイズが削減される。

 こういった、処理内容変更でサイズ削減する方法は、なかなか難しい(説明も難しい)。ただ、これが楽しめるようになると、プログラムは面白くなっていく。

2009/09/03追記:kさんから、上の処理のサイズ削減版が来たので、ちょっと紹介。
repeat 64
  p = (peek(maze, cnt/2) -36) / (9 - (cnt \ 2) * 8) \ 9
  a_p(cnt * 9 / 8 + (vの初期値)) = p + (p > 2) * 3
loop

2009/08/14:基本操作
 さて、最低限動くところまで一気に持っていくか。

 まず、カーソルキーでトム君を4方向に動かす処理を入れ、その次に壁の辺り判定と、岩/通常フルーツを押す処理をあっさりと加える。

 次に、岩変化とくっつき処理を実装する。ここはP6版と全く同じ考え方で行けるので、以前に比べてかなり楽だ。P6版の時の試行錯誤ぶりはこちらを参照という事で、以下処理概要を示す。

 岩変化は、3つ以上の同種フルーツが繋がった時に起こる。
この「3つ以上繋がる」処理は、「上下左右のうち2方向以上同種フルーツが隣り合っていれば岩に変える」処理を画面上の全てのフルーツで行う。

 くっつき処理は岩変化処理が完了した後に「下に同種フルーツがあれば縦長、右に同種フルーツがあれば横長のフルーツに変化させる」処理を画面上の全てのフルーツで行う。

 大まかな処理はこれだけだ。

 ちなみに「同種フルーツ」の判定は、キャラクタの並び順を利用している。
キャラクタ番号は、以下のように設定している。
  • 0:空白
  • 1:岩
  • 2:壁
  • 3:トム君
  • 4〜5:ドア(閉/開)
  • >
  • 6〜11:通常フルーツ(6種類)
  • 12〜17:縦長フルーツ上部分(6種類)
  • 18〜23:縦長フルーツ下部分(6種類)
  • 24〜29:横長フルーツ左部分(6種類)
  • 30〜35:横長フルーツ右部分(6種類)
 そう、キャラクタ番号が6以上の場合、6で割った余りが同じなら、同種フルーツとわかる。シンプルな処理になるようにデータの順序を仕組む事も、重要なことになる。

 フルーツがくっつくようになったので、縦長/横長フルーツを押す処理を追加する。
縦長のフルーツを縦に押す場合、移動先の2つ先が開いていれば押す事ができる。
縦長のフルーツを横に押すときは、移動先の1つ先と、対になっているフルーツ部分の1つ先の両方が開いていれば押す事ができる。当然、上部分を押すか下部分を押すかで対になっているフルーツの位置は異なる。

 上部分と下部分の区別は、キャラクタ番号を6で割った商の値で判定している。偶然そうなったようにも見えるが、これも仕組んだ上でキャラクタの並び順を決めている。

 これで、基本的な移動処理は完了した。

画面


2009/08/15:サイズ削減その2
 最低限動くようになったところで、またサイズ削減をしておこう。8/10に作成したドットパターン作成処理に、8/11で行ったような変更を加えてみる。

これがこうきてこんな感じ
repeat 64
  y = 90
  if cnt & 32: pset x, y
  if cnt & 16: pset x + 1, y
  y++
  if cnt &  8: pset x , y
  if cnt &  4: pset x + 1, y
  y++
  if cnt &  2: pset x , y
  if cnt &  1: pset x + 1, y
  x += 2
loop
repeat 64
  y = 90
  z = 32
  c = cnt
  repeat 3
    if c & z: pset x, y
    z >>= 1
    if c & z: pset x + 1, y
    z >>= 1
    y++
  loop
  x += 2
loop
repeat 64
  y = 90
  z = 32
  c = cnt
  repeat 3
    repeat 2
      if c & z: pset x + cnt, y
      z >>= 1
    loop
    y++
  loop
  x += 2
loop

 これで98バイト削減だ。全く別の方法でもっと切り詰められるような気もするが、まずはこんなところだろう。

 その他、これは8/10に行っていた事だが、色設定でもサイズ削減できる方法が見つかった。使える色が限定されるのと、なぜかpset命令では使えないのだが、パレット色指定の「palcolor」が使えるのだ。

 パレットコードの0〜19には初期設定として固定の色が設定されている。これらの色の中に使いたい色がある場合、サイズ削減が可能となる。

 例えば黄色(RGB:FFFF00)なら、color 255, 255 → palcolor 15 となり、パラメータが1つ削減できる分サイズが少しだけ減ることになる。色設定を多用する場合は、試す価値はあるかもしれない。


フルーツ ミラージュ 製作過程 製作過程(2)