数字をmesの文字ではなく画像で表示する事で、自分の思い通りのデザインの時計を作ることが出来ます。今回はこんなのを用意しました。
この画像をソースコードと同じフォルダ(作業フォルダ)に保存してください。画像を読み込むためには、picload
命令を使います。第一パラメータにファイル名を指定するだけにしておくと、ウインドウの大きさを画像に合わせて変更します。使える画像ファイルは.bmp、.jpg、.png、.ico、.gif、.psd、.tgaです。(ファイル名の指定にはいろいろ細かい決まりがあるのですが、それについてはコラムで説明します。今は、「ソースコードと同じフォルダにあるファイルはファイル名だけ指定すればよい」ということだけ頭に入れておいてください。)
picload "number.png"
とりあえずこれでメインウインドウに画像がロードされます。が、もちろんこれでは困ります。見えるのは見せたい時刻表示の部分だけになっていないとかっこ悪いです。
そこで、buffer
命令の登場です。これは「仮想画面」という、見えないウインドウのようなものを用意する命令です。これはどちらかというと「仮想ウインドウ」と言った方が近いかもしれません。描き込み、読み込みなど普通のウインドウのようにかのうですが、ディスプレイに表示されることは未来永劫ありません。
buffer 1
picload "number.png"
buffer
の第1パラメータは「ウインドウID」というものです。これは、仮想画面とウインドウについている番号で、複数の仮想画面とウインドウがある時にどれに対する書き込みなのか、読み込みなのかを表すために付けられているものです。一番最初に表示されているウインドウには最初から0というウインドウIDが割り当てられており、あとは自然数を自由に使えます。
さて、画像を見えないところに読み込むことには成功したのですが、実はちょっと困ったことになっています。次のソースコードを実行してみてください。
buffer 1
picload "number.png"
mes "見えるかい?(^_^)"
なんとmesで表示したはずの文字が見えません…。pos 0,0
とか書いてもやっぱり駄目です。これはどうしたことでしょう…。
このように仮想画面やウインドウが複数存在している時は、「現在操作対象になっているウインドウ(または仮想画面)」というものが存在します。buffer
で仮想画面を初期化すると、その仮想画面が操作先に自動で設定されます。なので、直後にpicload
を実行する事で、初期化した直後の仮想画面に画像を読み込むことが出来るわけです。つまるところ、いまはこの仮想画面が操作対象になっていて、全ての描画がこの仮想画面に行われてしまっているのです。
そんなわけで、操作対象のウインドウIDを変更する命令がちゃんと用意されています。gsel
命令です。第1引数に操作対象にするウインドウIDを指定します。
buffer 1
picload "number.png"
gsel 0
mes "見えるかい?(^_^)"
これで無事問題は解決しました。続いて、一つずつ数字のパーツを表示していきましょう。別のウインドウID上にある画像データをコピーして表示するためには、gmode
命令とgcopy
命令を使います。
gmode p1,p2,p3,p4
gcopy p1,p2,p3,p4
上の画像は、一つの数字の幅が16ピクセル、高さが32ピクセルなので、次のようにすると数字の7が表示されます。
buffer 1
picload "number.png"
gsel 0
gmode 2,16,32
pos 0,0
gcopy 1,16*7,0
さて、これらを使って、時刻を画像表示してみましょう。いろいろやり方はあるのですが、今回は素直にやってみようと思います。
;★画像の準備
buffer 1
picload "number.png"
gsel 0 ;操作先ウインドウを戻しておく
gmode 2,16,32 ;ずっと同じ設定なのでここでセット
;ウインドウの準備
title "clock"
width 16*12,32
*main
;時刻の取得
hor=gettime(4)
min=gettime(5)
sec=gettime(6)
;時刻更新の準備
redraw 0
color 185,122,87
boxf
;★時刻を画像表示
pos 0,0
if hor<12 {
gcopy 1,16*12,0;A
} else {
gcopy 1,16*14,0;P
}
pos 16 ,0 : gcopy 1,16*13,0
if hor>12 { hor-=12 };12より大きかったら調整
pos 16*3 ,0 : gcopy 1,16*(hor/10),0
pos 16*4 ,0 : gcopy 1,16*(hor\10),0
pos 16*5 ,0 : gcopy 1,16*10,0
pos 16*6 ,0 : gcopy 1,16*(min/10),0
pos 16*7 ,0 : gcopy 1,16*(min\10),0
pos 16*8 ,0 : gcopy 1,16*10,0
pos 16*9 ,0 : gcopy 1,16*(sec/10),0
pos 16*10,0 : gcopy 1,16*(sec\10),0
redraw 1
;繰り返し
await 1000
goto *main
HSPでは、割り算の結果は小数点以下切り捨てになります(小数を扱うことも出来ますが、今回は必要ないので省略します)。つまり、10で割れば10の位の数字が何なのかが特定できます。また、\
を使うと割り算の余りを得ることが出来ます。a\10
とすれば、aの1の位の数字を知ることが出来ます。あとは、画像が左から順番に並んでいるので、その数値に数字の幅である16を掛けてやれば、その数字の左上の座標が出ます。それを順番にコピーして並べているだけです。(htmlの都合上、\
(半角バックスラッシュ)で表示されていますが、日本語キーボードでは普通打てないので¥を使います。)
今回はgmodeの2を使用し、背景をただ色で塗りつぶしているだけですが、例えば背景も画像にしてしまう事で、よりデザインを綺麗にすることも出来ます。
また、HSPには様々な画面制御命令が用意されています。line(直線を描く)、circle(円を描く)、grotate(矩形画像を回転・拡大縮小してコピー)などはその一例です。
さて、これで完成でも全く問題ありませんが、少しソースコードを綺麗にしましょう。というのも、同じような部分が一杯ならんでいて、なんだか納得がいかないのです。なんとかこれをループで表現できないでしょうか。例えばコピー先は、ただ単に横に並べて行くだけですから、1ループ毎横の座標を16ずつ増やせばよいはずです。そして、gcopyのコピー元さえなんとかループの中で表現できれば…。
そんなわけで今回登場する新アイテムが「配列型変数」なるものです。これは、「連なった箱」のイメージを持ってください。例えばtimeという配列型変数は、time(0)、time(1)、time(2)、…と、番号が付いた複数の変数を一つにまとめたものになっています。数値型配列変数を用意するためには、dim
命令を使います。
これが何の役に立つかと言いますと、何の法則性のないデータも連番号として扱うことが出来て、リボルバーのような、ベルトコンベアのような、整理番号のような働きをします。ごちゃごちゃ文章書くより見た方が早いです。
dim a,5
a(0)=1 : a(1)=1 : a(2)=2 : a(3)=3 : a(4)=5
i=0
*main
mes "a("+i+")="+a(i) : i++
if i<5 : goto *main
補足しておくと、「:
」は1行に複数の命令や代入を記述する事の出来る区切り記号です。短い時にはこうして複数の命令を1行に収めてしまった方が解り易くなることもあります。また、「i++
」は、iを1増やすという意味です。i+=1
と同値です。
このように、法則性のない数値の並びを0番、1番、2番、…と順番に処理していく事が可能になるのです。この()
内の番号の事を「添え字」といいます。time(0)などの一つ一つの変数の事を配列変数のことをその配列変数の「要素」といいます。
配列変数は代入が大変なので、色々な表記が出来るようになっています。
dim a,12
a=1,1,2,3,5,8,13,21,34,55,89,144
;↑a(0)=1 : a(1)=1 : a(2)=2 : ... : a(11)=144 と同じ
dim b,126
;添え字110から代入していきたいときは以下のようにする
b(120)=5,6,7,8,9,0
;これで b(120)=5 : b(121)=6 : ... : b(125) と同じ
配列変数を使うことで、時計のソースコードはこんな感じになります。
;画像の準備
buffer 1
picload "number.png"
gsel 0 ;操作先ウインドウを戻しておく
gmode 2,16,32 ;ずっと同じ設定なのでここでセット
;ウインドウの準備
title "clock"
width 16*12,32
;★変数の準備
dim time,11
*main
;時刻の取得
hor=gettime(4)
min=gettime(5)
sec=gettime(6)
;★変数に代入
if hor<12 { time(0)=12 }else{ time(0)=14 };AかPか
if hor>12 { hor-=12 };12より大きかったら調整
time(1)=13,11,(hor/10),(hor\10),10,(min/10),(min\10),10,(sec/10),(sec\10)
;時刻更新の準備
redraw 0
color 185,122,87
boxf
;★時刻を画像表示
i=0
*subloop
pos 16*i,0 : gcopy 1,16*time(i),0
i++
if i<11 : goto *subloop
redraw 1
;繰り返し
await 1000
goto *main
代入の部分は少し複雑になってしまいましたが、これで同じ部分が減ってちょっとだけすっきしりしました。
配列型変数は、ゲームなどでも非常に有効な道具の一つです。配列型変数は、添え字を4つまで増やすことができるのですが、例えば添え字を二つにしておいて、そこにマップデータを代入し、画面にマップを表示することも可能です。以下は簡単な迷路を表示するサンプルです。
title "map"
width 12*16,12*16
dim map,12,12
map(0, 0) = 1,0,1,1,1,1,1,1,1,1,1,1
map(0, 1) = 1,0,0,0,0,0,0,0,0,0,0,1
map(0, 2) = 1,1,1,1,1,1,1,1,1,1,0,1
map(0, 3) = 1,0,0,0,0,0,1,0,0,0,0,1
map(0, 4) = 1,0,1,1,1,0,1,0,1,1,1,1
map(0, 5) = 1,0,1,0,0,0,1,0,1,0,0,0
map(0, 6) = 1,0,1,0,1,0,0,0,1,0,0,1
map(0, 7) = 1,0,1,0,1,1,1,1,1,1,0,1
map(0, 8) = 1,0,1,0,0,0,0,0,0,0,0,1
map(0, 9) = 1,0,1,1,1,1,1,1,1,1,0,1
map(0,10) = 1,0,0,0,0,0,0,0,0,0,0,1
map(0,11) = 1,1,1,1,1,1,1,1,1,1,1,1
j=0
*main
i=0
*subloop
if map(i,j)==1 {color 0,0,0} else {color 255,255,255}
boxf 16*i,16*j,16*(i+1),16*(j+1)
i++ : if i<12 : goto *subloop
j++ : if j<12 : goto *main
picload
で読み込むbuffer
で「仮想画面」を作ってまずはそこに読み込むgsel
で操作先の「ウインドウID」を決め、gmode
でコピーモードを設定し、pos
で表示する位置を決め、gcopy
などでコピーするdim
命令によって「配列変数」を作ることで、データの管理が楽になり、ソースコードも簡潔になる配列変数を使って無駄は省かれました。が、正直逆に見辛くなってしまったというか、解り辛くなってしまった感じがします。今度は、文字列の持っているある仕組みを使って、ソースコードをスッキリさせてみたいとおもいます。
今回、picload命令の第一パラメータにはファイル名だけを指定しました。しかし、実はこれは「プログラムがいるフォルダにあったから」ファイル名だけを指定すればよかったのです。
どういう事かと言いますと、プログラムがファイルの中身を手に入れるためには、OSに「このファイルの中身教えてー」と頼む必要があります。しかし、OSからしてみると、例えばnumber.pngとだけいわれても、もしかしたら他のフォルダにも同じnumber.pngというファイルがあるかもしれませんし、そもそもどのフォルダの中に入ってるのかが全然わかりません。
どのドライブのどのフォルダにそのファイルが含まれているかまでファイル名に付け足したものを「絶対ファイルパス」と呼びます。例えは、DドライブのHSPというフォルダの中のclockというフォルダの中のnumber.pngというファイルなら、「d:¥HSP¥clock¥number.png」となります。本来なら、プログラムはこの絶対ファイルパスでOSにファイルを指名しなければなりません。
しかし、これは不便です。このままだとアプリを他のフォルダに移動した場合、一緒にしてあったファイルのファイルパスも変わってしまうので、絶対ファイルパスが変わってしまってエラーになってしまいます。
そこで、「相対ファイルパス」というものができました。これは、プログラムが作業をしているフォルダから相対的に見てどこにファイルがあるかを示すものです。例えば、あるフォルダ「d:¥HSP¥clock」にいる時、絶対ファイルパスでは「d:¥HSP¥clock¥number.png」と表さなければならないところを、同じフォルダにあるのだから「number.png」とだけ書くものです。もし「d:¥HSP¥clock」の中にsndというフォルダがあって、その中にあるal.midというファイルを指定したいときは、「snd\al.mid」とだけ書けばよい、となっています。
こうすることで、プログラムと同じフォルダにファイルをまとめておいても、相対ファイルパスでファイルを指定すれば問題は起きません。ちなみに、プログラムが作業している(プログラムがいる)フォルダのことを「カレントディレクトリ」といいます。ディレクトリとフォルダは大体同じ意味です。
また、相対ファイルパスでは、特殊な表記として、「..」は一つ上のフォルダ、「.」はカレントディレクトリそのものを指します。