お絵かきの基礎2
描画の基礎後編を紹介しましょう。
前章末に書いた通り、今章のあらましは「塗り潰しと高速描画の為に行うべき命令の紹介」です。
早速塗りつぶしから…。
塗り潰しとは、その意味の通り、実行時に表示されるHSP実行ウィンドウを塗り潰すことです。
塗り潰しという意味であれば、前章に紹介した命令でも実現できます。
そういう意味であれば塗り潰し命令なんていらないのではないだろうか?
そう考える方がいるかもしれませんので、まずは下記に書いたサンプルを見てください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
	pos 0, 0 : mes "これから点のみで塗り潰しを実行します。"
	wait 100
	color 100, 100, 100
	repeat 480
		repeat 640
			pset cnt, y
		loop
		y++
	loop
	color 255, 255, 255
	pos 0, 0 : mes "点のみの塗り潰しが完了しました。"
もしくはループをネストさせないやり方なら、
 1
 2
 3
 4
 5
 6
 7
 8
	pos 0, 0 : mes "これから点のみで塗り潰しを実行します。"
	wait 100
	color 100, 100, 100
	repeat 640 * 480
		pset cnt \ 640, cnt / 640
	loop
	color 255, 255, 255
	pos 0, 0 : mes "点のみの塗り潰しが完了しました。"
点での塗り潰しであれば上記、線であれば下記の感じです。
 1
 2
 3
 4
 5
 6
 7
 8
	pos 0, 0 : mes "これから線のみで塗り潰しを実行します。"
	wait 100
	color 100, 100, 100
	repeat 480
		line 640, cnt, 0, cnt
	loop
	color 255, 255, 255
	pos 0, 0 : mes "線のみの塗り潰しが完了しました。"
…さて、pset命令とline命令プログラムを実行してみて気付いた違いはなんでしょう?
高性能なパソコンであれば、ひょっとして違いが見られなかった可能性はありますが、
塗り潰す時間がpset命令に比べてあきらかにline命令の方が早い。
ものすごく簡単に考えて、点1ピクセルと線100ピクセルとでは領域差に比例して速度差は100倍ですが、
塗り潰し領域が全く同じであれば点100ピクセルと線100ピクセルでは速度差がないはずです。
しかし、pset命令とline命令で同じ範囲を塗り潰したのに、
見ていただいた通り、速度差に大きな違いが見られたことと思います。
つまり、何か別の要因があるから違いが見られたという事になる訳ですね。
pset命令の塗り潰しに比べてline命令のスクリプトの方がシンプル(綺麗でスッキリしている)
一見すると関係なさそうですが、コレが今回の速度差に関わる大きな要因ではないかと推測されます。
内容が複雑であればあるほど時間が掛かるのは人間だけでなく機械でも同じなんです。
結果としてはまったく同じであっても、命令数(途中工程)が多ければ多いほど、
命令実行時間そのものが見ても分かるほど露骨に感じることが出来るということなんです。
ココまでで何がいいたいのかお分かりいただけたでしょうか?
単純に一面を塗り潰す作業でも命令の数が少ないほど実現する時間を掛けずに処理できますよね。
line命令だけの塗り潰しはpset命令に比べ高速でしたが、
先ほどのスクリプトでline命令の塗り潰し部分は480回繰り返して実現しています。
もしも、1回で塗り潰せばもっと高速になるとは考えられませんか…?
boxf 矩形開始X座標, 矩形開始Y座標, 矩形終了X座標, 矩形終了Y座標
矩形開始X座標領域を塗り潰すHSPウィンドウ上のある一点のX座標を指定する。
矩形開始Y座標領域を塗り潰すHSPウィンドウ上のある一点のY座標を指定する。
矩形終了X座標開始X座標に対して塗り終わる終了X座標を指定する。
矩形終了Y座標開始Y座標に対して塗り終わる終了Y座標を指定する。
boxf命令は開始点から終了点までの矩形(直角四辺形)を塗り潰す命令です。
四辺形というのは点が4つなければ表せませんが、指定するのは2点だけです。
残りの2点は下記イメージ通り、自動的に補完されるという事です。
矩形は「くけい」と読みますが、皆さんはきちんと読めましたか?
私は当初、読み方を知らずに「きょけい」と読んでいたものです。実にどうでもいい話ですね…。
早速スクリプトを見てみましょう。
 1
 2
 3
 4
	pos 0, 0 : mes "これから一度に塗り潰しを実行します。"
	wait 100
	color 100, 100, 100 : boxf 0, 0 , 640, 480 : color 255, 255, 255 // 背景色で塗り潰し文字色に戻す
	pos 0, 0 : mes "塗り潰しが完了しました。"
どうでしょうか。
背景色と文字色のカレントカラー変換を1行にしてはいるものの、
他の部分もline命令よりもシンプルに、また高速に処理することができるようになりました。
ヘルプには、パラメータ1と2は、左上、3と4は右下と書かれていますが、
当講座ではあえて開始と終了という表現を使用しました。
この方が分かりにくいですが、必ずしも左上と右下という組み合わせにはならないからです。
パラメータ1と2に右下、3と4に左上、つまり指定位置を逆に書くこともできる、ということです。
まぁ、通常は左上と右下の組み合わせにするわけですけどね…。
開始側座標パラメータを指定しない場合、(0,0)の指定を、
終了側座標パラメータを指定しない場合、(639,479)厳密にはウィンドウ右下指定をしたことを意味します。
つまり、「boxf」だけで画面全体を塗り潰すことになります。
この塗り潰し作業は、ゲーム・ツールを問わず、様々な場面で使用するものです。
内容はきわめて単純ですが、少々長い下記スクリプトのような感じで使用したりします。
 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
32
33
34
35
36
37
38
39
40
41
42
43
	mes "例えば・・・。"
	wait 200
	mes "今まではこのように同じ位置に文章を書くことすら出来ませんでした。"
	wait 200
	mes "同じ位置に書くと"
	wait 200
	pos 0, 55 : mes "ココに何が書いているか"
	wait 100
	pos 0, 55 : mes "潰れて見えなくなっちゃう。"
	wait 100
	pos 0, 55 : mes "あぁ・・・もうダメ。"
	wait 100
	pos 0, 55 : mes "何書いてるかわかんねぇよ。"
	wait 200
	mes "このように見えなくなるのです。"
	wait 200
	mes "しかし・・・"
	wait 300
	color 255, 255, 255 : boxf : color
	pos 0, 0 : mes "今回の命令「boxf」を使うと"
	wait 200
	color 255, 255, 255 : boxf : color
	pos 0, 0 : mes "一旦背景色で画面を綺麗にできて"
	wait 200
	color 255, 255, 255 : boxf : color
	pos 0, 0 : mes "同じ位置に何度も書けるようになるのです!"
	wait 200
	color 255, 255, 255 : boxf : color
	pos 0, 0 : mes "これは今後アニメーションを紹介するときのキモにもなります。"
	wait 200
	color 255, 255, 255 : boxf : color
	pos 0, 0 : mes "ほらこんなパラパラ漫画風に"
	wait 100
	pos 0, 50 : mes "( −_−) ┳"
	wait 30
	color 255, 255, 255 : boxf : color
	pos 0, 50 : mes "(o ̄_ ̄)o ┫"
	wait 30
	color 255, 255, 255 : boxf : color
	pos 0, 50 : mes "(シToT)シ・'.:・┻┻:・. "
	wait 30
	color 255, 255, 255 : boxf : color
	pos 0, 50 : mes "(ノTOT)ノ:・'.::・┻┻:・'.::・ "
さて、boxf命令は以上で説明終了し、続いてHSP3で新登場の命令を説明します。
boxf命令は矩形領域を塗り潰す命令、即ち塗り潰し領域は常に四角形です。
これから紹介する命令は四角形以外の多角形、というわけではなく、
矩形領域に収まる最大円を描画する命令です。
circle 矩形開始X座標, 矩形開始Y座標, 矩形終了X座標, 矩形終了Y座標, 塗り潰しモード
矩形開始X座標円を描画するHSPウィンドウ上のある一点の開始X座標を指定する。
矩形開始Y座標円を描画するHSPウィンドウ上のある一点の開始Y座標を指定する。
矩形終了X座標開始X座標に対して塗り終わる終了X座標を指定する。
矩形終了Y座標開始Y座標に対して塗り終わる終了Y座標を指定する。
塗り潰しモード円内を塗り潰さず枠だけに(0を指定)するか、塗り潰し(0以外を)指定するかを制御する。
パラメータはboxf命令と同じ矩形の開始と終了座標点の計4つに加え、塗り潰しモードも入っています。
boxf命令はこのモードがないのですね。
恐らく、以前(HSP2)から使える命令であり、上位互換性を出来る限り保つ為に、
パラメータを増やしたくなかったのでしょう。
ということで、circle命令同様の塗り潰しモードが入った拡張boxf命令を別途用意しましょう。
話をcircle命令に戻しますが、説明をするほどのものではないですね。
boxf命令同様、開始座標と終了座標、つまりパラメータ1と2が右下、3と4が左上となる指定も可能です。
また、開始座標を省くと(0, 0)を、終了座標を省くと(639, 479)を指定したことになります。
塗り潰しモードは通常、0の枠のみ、1の塗り潰しを指定しますが、
0以外の全ての値は1を指定したこととなる塗り潰しとなります。
スクリプトを見てみましょう。
 1
 2
 3
 4
	pos 100, 100 : mes "これから円形に塗り潰しを実行します。"
	wait 100
	color 100, 100, 100 : circle 0, 0, 640, 480 : color 255, 255, 255 // パラメータ指定なしでも同じ
	pos 100, 100 : mes "円形塗り潰しが完了しました。"
矩形全体ではない為に、左上は塗り潰されません。
従って、上記スクリプトは先ほどまでとは違う位置にテキストを表示しています。
このように完全に塗り潰されるわけではなく、当たり前ですが角に隙間が出来てしまいます。
その為、boxf命令に比べると使用頻度は落ち、用途は限られてしまうものと思われますが、
boxf命令のようにline命令等の別命令で代替することが難しく、
ペイント系ツールやアニメーション時のエフェクト部分等では重宝するものでしょう。
こんなのもいいのじゃないですか?
 1
 2
 3
 4
	repeat 100, 1
		color cnt + 100, cnt + 150, cnt + 100
		circle 120 + cnt * 4 / 5, 50 + cnt * 4 / 5, 420 - cnt * 2, 350 - cnt * 2
	loop
玉が光っているぽくって綺麗じゃないですか。
まぁ、使い方はプログラマ次第になってくるわけですけど…。
最後に、高速で描画する為に心得ておくことを説明します。
アニメーションを行うとした時のことを想像してください。
アニメーションをする上で一番大切なことはなんでしょうか?
もしかすると、違うという人もいるかと思いますが、
管理人を含め大半の人は、「見せる」ということではないでしょうか?
見せる、とは途中経過、つまり移り行くさまのことです。
アニメーションする前の状態と終わりの状態の2つだけを見ても
あまりなんとも思わないと思います。
以前のある状態のものから徐々に位置や形等を変更していくのがいいんですよね?
人は、このアニメーションを楽しむ反面、望んでいないアニメーションは嫌います。
今章の初めにpset命令による塗り潰しを行いました。
あれも徐々に画面が塗り潰されていくアニメーションであることには違いないでしょう。
ソレがいいんだという人もいるかとは思いますが、
そのスクリプトの目的は何かを考えてください。
目的は、画面全体を塗り潰すことです。
塗り潰すのに時間が掛かって好ましく思う人は少ないのではないでしょうか?
塗り潰すくらい一瞬で終われよ、と不愉快にさえ思うことが普通ではないかと思います。
パソコンやインターネットはどんどん高速化していますが、
その背景には待つことを嫌う人間が伺えます。不要なものに時間を割くことを好みません。
…と、前振りが今回も長くなりましたが、高速で描画する為の下記命令を紹介します。
redraw 描画モード, 描画開始X座標, 描画開始Y座標, 対象Xサイズ, 対象Yサイズ
描画モード描画したものを実際の画面に反映するか否か、またはモードの変更のみを指定する。
描画開始X座標描画の対象とする左上X座標を指定する。
描画開始Y座標描画の対象とする左上Y座標を指定する。
対象Xサイズ描画の対象とする横幅を指定する。
対象Yサイズ描画の対象とする高さを指定する。
描画モードは、処理した描画を画面に反映させるか仮想画面のみを反映させるか指定させます。
書いている意味がよく分かりませんね…。
例えば、画面を黒で塗りつぶすとします。
今までは黒で塗りつぶす処理が実行されると、その時に黒色で塗り潰されていました。
当たり前ですよね、黒で塗り潰す処理を実行したわけですから。
とりあえず、この今まで普通に行っていたものが、実際画面を書き換えるモード(1指定)だったのです。
仮想画面のみを書き換えるモード(0指定)とは、いわば描画処理の予約です。
黒で塗り潰す処理を行ってもその場ですぐには塗り潰されません。
次に実際画面に書き換えるモードに変更した際に画面に反映され、塗り潰しを確認できるのです。
…何故、後で書きえる必要があるのでしょう?
今章初めのpset命令による画面塗り潰しを思い出してください。
画面を塗り潰すのに一点一点画面に反映させていました。塗り潰すのが遅かったですね。
時間が掛かった原因の一つに、画面に対して一点一点描画すること自体も含まれています。
実際画面を反映せず、仮想画面のみを書き換えて、
最後に一度だけ画面描画すると、描画処理は一回だけとなります。
数値に直すと、640×480なので307,200回の描画が1回になったわけです。
他のパラメータ説明を軽く終えた後に実際にどうなるのか試してみましょう。
パラメータ2と3には、この書き換えを制御する領域左上座標を指定し、
パラメータ4と5には、この書き換えを制御する領域サイズを指定します。
画面全体の書き換えだけでなく、画面内の一部のみの制御も出来るということなんですね。
プログラム次第で一部のみの書き換えは有効だと思いますが、
通常の大半は画面全体となることでしょう。
画面全体の場合はその他命令同様にパラメータ2〜5までの省略で全体を意味します。
それではお待ちかね、pset命令の画面書き換え高速版を体験していただきましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
	pos 0, 0 : mes "これから点のみで塗り潰しを実行します。"
	wait 100
	color 100, 100, 100
	redraw 0 // 終了するまで画面への反映を中断する
	repeat 480
		repeat 640
			pset cnt, y
		loop
		y++
	loop
	redraw 1 // 画面へ反映する
	color 255, 255, 255
	pos 0, 0 : mes "点のみの塗り潰しが完了しました。"
4行目と11行目のredraw命令以外は、初めのスクリプトと全く同じですが、高速になりましたよね。
少し前にも書いたように描画処理回数が307,200分の1になったわけです。
pset命令による描画もboxf命令のように全体を一瞬で塗りつぶせるようになりましたが、
それって初めからboxf命令で塗り潰せば、redraw命令なんていらないのでは、と思う方もいるはず。
下記スクリプトを実行してみてください。
 1
 2
 3
 4
 5
 6
 7
	repeat 128, 1
		color cnt * 2 - 1, cnt * 2 - 1, cnt * 2 - 1
		boxf
		color 255
		if cnt < 128 : pos 0, 0 : mes "描画中・・・"
	loop
	pos 0, 0 : mes "終了。"
塗り潰しに使用している命令は画面全体を一度に塗り潰すboxf命令を使用しています。
実行してみるとフェード(徐々に色を変えていく)プログラムであることが分かります。
その実行中、画面の左上(スクリプト5行目)にメッセージを表示させていました。
画面の色を変えてはメッセージを表示する作業を繰り返していたわけですが、
ものすごくチラついていましたよね?
塗り潰しとメッセージ表示の間には特に重くなる処理していないはずが
塗り潰した直後とメッセージを表示するまでの一瞬が見えてしまっているのです。
一度であれば気になるほどではないにしても、何度も繰り返している為にチラつきが気になります。
こういう場合にもredraw命令は有効です。
塗り潰しとメッセージ表示を一度に行った後に画面に反映させることで余分な部分が見えなくなります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
	repeat 128, 1
		redraw 0 // ココから
		color cnt * 2 - 1, cnt * 2 - 1, cnt * 2 - 1
		boxf
		color 255
		if cnt < 128 : pos 0, 0 : mes "描画中・・・"
		redraw 1 // ココまでの書き換えを1つの描画処理とする
	loop
	pos 0, 0 : mes "終了。"
どうです?メッセージが赤々と綺麗に表示され続けたままとなっているのを実感できましたか?
アニメーションをさせる場合は、画面全体ではないにしろ、
このチラつきを抑える努力を怠ってはなりません。
見るものを気持ちよくさせられるよう、常に気を配りプログラムするようにしてください。
さて、描画モードについてですが、実は0と1だけではなく、2と3も用意されてます。
0と1の画面書き換えは、実際に書き換えるか仮想画面のみを書き換えるかを制御していましたが、
2と3は、書き換えフラグのみを変更し、画面更新を行わないモードなのです。
分かりにくいですね。
言葉だけでの説明は行いにくいので、実際スクリプトを実行してもらった後に説明しましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
	mes "redraw描画モード2と3の説明用スクリプト"
	wait 300
	redraw 0 // モード0実行
	color 200, 200, 200 : boxf : color 50, 100, 100
	pos 0, 0 : mes "0と1の組み合わせは既に述べた通りです。"
	redraw 1 // モード1実行
	wait 300
	redraw 0 // モード2実行
	color 150, 50, 50 : boxf : color 100, 255, 255
	pos 0, 40 : mes "2と3の組み合わせではモード変更のみです。(このテキストは↑より前に書かれた内容)"
	redraw 3 // モード3実行
	pos 0, 0 : mes "モード2〜3内の仮想画面塗り潰しは現段階ではまだ反映されてません。"
	wait 300
	redraw 1 // モード1実行(全書き換え)
	pos 0, 80 : mes "ココに来てようやく全てが反映されます。"
スクリプト8行目で仮想画面のみ書き換えるようにモードを変更しています。
スクリプト11行目で実画面にも書き換えるようモードを変えましたが、
この時点ではモード3のフラグ変更のみで実際画面書き換えは行ってません。
これはスクリプト9行目と10行目の描画処理が反映されてないにも関わらず、
スクリプト12行目が表示されていることからわかりますね。
スクリプト14行目を実行することで、仮想画面で変わっている内容に書き換えられます。
1点気になるのが、スクリプト12行目のメッセージ表示箇所の背景のみ、
スクリプト10行目の仮想画面側塗り潰しが反映されています。
どうやら、テキストの描画で、テキストのみ画面に描画されるわけではなく、
テキストエリアが更新されるようになっているようなので、
モード2から3までの間は1で全描画モードに変えるまで完全に止めるわけではないということですね。
難しいですが、通常の方はモード0と1だけしかほとんど使わないなくて事足りると思います。
よって、この件が理解できるまで留まろうとしなくてもよいと思います。
チラつきを抑える為にはredraw命令のモード0と1とだけ覚えておけば十分でしょう。
FAQにもredraw命令のパラメータの違いについて書いてますので参照してください。