« 2007年12月 | トップページ | 2008年2月 »

2008年1月の49件の記事

[書評]守屋洋氏「右手に『論語』左手に『韓非子』」

最近「論語」関連の本を次々と読んでいる。自分もかくありたいと思いつつ、でもやっぱり理想論だよなぁ・・・と思うこともある。

続きを読む "[書評]守屋洋氏「右手に『論語』左手に『韓非子』」" »

ノートン2008の不具合_続き

「Norton Internet Security 2008」をインストールしたら、数え切れないほどの不具合が発生して対処法が見つからず苦しんでいることを前回のエントリーに書いた。あれからGoogle先生で色々調べてみたら、自分と同じ症例が実は少ないことに気がついた。

2008

何度か再ンインストールや再起動を繰り返しているうちに気付いたのだが、私が遭遇している不具合の一つは「Symantec Service Framework」が突然終了するというもの。つまり、何か作業をしている最中に発生する症状ではなく、PCの電源を入れてWindowsが起動した状態のまま何もしなくても、10分ほど放置するといきなり上記のエラーメッセージが出るのだ。楽天のみんなで解決!Q&Aによく似た症状が質問されていた。ただし「LiveUpdate」は問題なく起動できている点が私とは状況が違う。こちらのエントリーVista で Go Go)もよく似た症状だが、「Norton Protection Center」も全く開けないようなのである意味私より酷い状況だ。

ところが、他の方々が遭遇している症状の大半は「Symantec Service Framework」がメールの送受信中に終了するというもの。特定のメールヘッダーで詰まってしまうらしく、電子メールスキャン機能をオフにすれば回避できるようだ。こちらのエントリーpc whole blog)が典型的な事例。この場合、特定の動作を行った時だけ発生するのだから、原因は判りやすい。

しかし私の場合、何もしていなくても突然終了してしまうのだ。原因がさっぱり判らない。PC起動直後だけは「Norton Protection Center」が開けるので、ひとまずファイアウォールをオフにする(オンだとFirefoxが使えないため)のと、念のため電子メールスキャン機能をオフに。あまり状況は変わっていないが、とりあえずストレスなく調べることはできるようになった。そこで、恐らく状況としては非常に深刻な「LiveUpdate」が失敗する症状に絞って調べてみた。

Symantecのサポート>よくある質問に記載されていた手順では解決せず。「LiveUpdate」を最新版に入れ直してもダメ。なんでかなーと思いながら、なんとなく「2008」や「シマンテック」などのワードを削って「LiveUpdate」だけで検索したら、こんな記事を発見。日付が2003年2月14日なのでかなり昔の記事だが、「Tempフォルダに格納されているファイルも全て削除しなければならない」という記述がパッと目に入った。そういえば、WindowsのTempフォルダにゴミが残っていると、インストールがうまくいかないことがあるのを思い出した。Tempフォルダの中を全部削除した後、「LiveUpdate」の最新版を入れ直したら、ようやく普通に更新ができるようになった。

これで、修正パッチが当たって不具合が解消されるかも!と淡い期待を抱いたのだが・・・残念ながら「Symantec Service Framework」が突然終了する症状は変わらなかった。非常にむなしい・・・。

と、ここまで記事を書いてきて思いついた。WindowsのTempフォルダにゴミが残っていたからインストールがうまくいかなかったのは「LiveUpdate」だけじゃなく、「Norton Internet Security 2008」そのものもじゃないだろうか?早速この後再インストールを試してみよう。

なお、更新したばかりなのに更新期限切れになってしまう症状はこちらのエントリー気まま徒然、日々かきかき)などを参照して、なんとか回避することに成功した。感謝。

ノートン2008の不具合

事前にもっと調べておくべきだった・・・という自戒の念を込めてmixi日記より転載加筆。


ノートン2008は、1ライセンスで3端末までインストール可というのは知ってたので、正月帰省した際2005が期限切れしていた父と母のPCに導入。今のところ特に連絡は無いので、多分問題は無いのだろう。

今週頭に自分のデスクトップ機に入れてるノートン2006から、更新サービス期限切れというメッセージ。あれー?まだ期限残ってたはずなのになーと思いつつ、まぁいいやと深く考えずに2008へ入れ替えてしまったのが失敗の始まり。

不具合だらけで、もう何回再インストールしたか分かりません。一昨日は5時間くらい格闘しました。

【不具合その1】
タスクトレイにアイコンがあるので常駐はしているようだか、本体のウィンドウが開かない。つまり設定の変更はおろか状況を見ることすらできない。PC起動直後は 大丈夫でも、一旦本体ウィンドウを開いて閉じると二度と起動できなくなる。

【不具合その2】
インターネットへ接続できない。完全に全部ダメな場合もあれば、ソフトによっては大丈夫な場合も。恐らくファイアウォールだが、その1が発生してない場合に個別設定で除外しても、ファイアウォールを切ってもダメ。その1と一緒に発生する場合もある。ファイアウォールの設定を切ろうとしたらエラーで落ちた場合もあった。

【不具合その3】
更新サービスの期限切れメッセージが出る。これが出るとファイアウォールの設定が見られなくなるので、その2と一緒に発生すると完全にアウト。これはどうやらパッチを当てた上で「更新サービス」メニューからシマンテックのサーバーに接続しなおせば回避できるようだ。

他にも、本体ウィンドウは開いても設定ウィンドウへ進めない時があるとか、ヘルプ開いたら真っ白なウィンドウが開く時があるとか、「Symantec Service Framework」が強制終了するとか、5時間の格闘中にいろんな現象が確認できた。Add-Onパックをインストールするまでは大丈夫だったのに入れたらズタズタになった時もあった。

もっと事前に調べてから導入すべきだったと非常に後悔している。こういう状況になってしまってからから調べたら、2008はいろんな不具合が発生しており、しかも未だ修正されていないという状態のようだ。 迷惑メール防止機能(AntiSPAM)は別途Add-Onパックを手動でダウンロードしてインストールしなければならないのだが、AntiSPAMが有効になっているとメールを受信中にエラーが出てしまうという症例もあるようだ。もう二度とシマンテック製品は買わないというような声もあちこちで見かけた。逆に、全く問題無い人もいるというのが不思議。2007までより動作が軽いという声もある。動作が軽くなるというのは魅力的なのだが、現在の状況ではその効果が全く実感できない。

【現在の状況】
更新サービス期限切れエラーは対処できた模様。ただし、ちゃんと「残り350日」などという表示が見えていても、特定のメニュー(ファイアウォールの設定)を開こうとすると「期限内じゃないと変更できません」というようなメッセージが出てしまった。ただ、PC再起動したら大丈夫だったので、その隙にファイアウォールをオフに設定。ついでに不具合が多く報告されていたメール関係のバグ回避のため受信&送信メールのチェックをオフに設定。これセキュリティソフトとして意味がある状態なのだろうか?と少々疑問に感じる。

ていうか、おまえがウィルスだろ>ノートン2008

[書評]スティーブン・R. コヴィー氏「7つの習慣」

7つの習慣―成功には原則があった!
スティーブン・R. コヴィー ジェームス スキナー
キングベアー出版
売り上げランキング: 83

正月休みに読み直し。最初に読んだときも大きな影響を受けたが、やはり何度読んでもいい。正月明けて最初の出勤日に、気分も新たにミッションステートメントの見直しをした。手帳にもデスクトップの壁紙にも貼っておいた。

続きを読む "[書評]スティーブン・R. コヴィー氏「7つの習慣」" »

日経ビジネス2007.12.24-31掲載記事より

ドキュメント_告白「汚された暖簾」
終わらない話_「食の安全」に潜む非合理

2007年を現す漢字1文字が「偽」になったように、去年は食品業界の偽装表示が相次いだ1年だった。記者会見で経営者が頭を下げる姿ばかりを目にしていたようにも思えるが、いつものことながらマスコミ報道が偏ってるようにも感じていた。

さて実は、この号の日経ビジネスは年末に出たものだが、会社の机の引き出しに入れたまま忘れていたものである。既に次の号が出てしまっているので、空き時間に急いで読んでいた。

上のタイトルは赤福についての特集記事で、下のタイトルは吉野家ホールディングス安部修仁社長が寄稿した巻末コラムである。

続きを読む "日経ビジネス2007.12.24-31掲載記事より" »

[書評]梅田望夫氏「ウェブ時代をゆく」

ウェブ時代をゆく ─いかに働き、いかに学ぶか (ちくま新書)
梅田 望夫
筑摩書房
売り上げランキング: 54350

ベストセラー「ウェブ進化論 本当の大変化はこれから始まる」の続編。"Webの進化で世の中が変わる"→"ではどう適応していけばいいのか?"という提案と言っていいだろう。

30歳の時に異動・転勤をし、それをきっかけにビジネス書を大量に読むようになった。東京で様々な人と出会い、自分のレベルの低さや知識の足りなさ を思い知らされ、とにかく勉強しようと思ったのである。本を読むと、過去の叡智が自分のものになったような気がする。考えてみればずいぶん付け焼刃的だ。 読んだばかりの理論・概念を、すぐに使ってみようと試行錯誤したのを思い出す。

続きを読む "[書評]梅田望夫氏「ウェブ時代をゆく」" »

[改造記]インデックス

01:はじめに
02:まずは初期設定
03:画像の準備
04:使えなくなる命令
05:メインルーチン
06:画面描写(es_copy)
07:解剖記のスクリプトを改造しよう
08:スプライトを使う準備
09:スプライトの登録
10:真っ直ぐ進むスプライト
11:スプライトの任意移動制御
12:スプライトの当たり判定

これらの元記事は、ファイルの更新日時を確認する限り、恐らく2000年11月12日頃書いたものです。現在の状況(既にHSP3.0がリリースされてい る)とは整合性が取れていない内容もあると思いますが、当時の記事のまま転載しております。間違っている記述があるかもしれませんが、あらかじめご了承下 さい。

改造記の記述はHSP_version2.61を前提としております。現在でも以下のリンク先から入手可能です。
Get!HSP2.61

[解剖記]インデックス

01:これがサンプルのソースだっ
02:プログラムの構造
03:自機移動と弾の発射
04:敵の生成と移動アルゴリズム1
05:敵の生成と移動アルゴリズム2
06:敵の生成と移動アルゴリズム3
07:敵と自機弾の当り判定
08:敵弾の生成と移動
09:敵弾と自機の当り判定
10:その他の画面効果
11:全画面表示への改造
12:アナログジョイスティックへの対応

これらの元記事は、ファイルの更新日時を確認する限り、恐らく2000年10月16日頃書いたものです。現在の状況(既にHSP3.0がリリースされてい る)とは整合性が取れていない内容もあると思いますが、当時の記事のまま転載しております。間違っている記述があるかもしれませんが、あらかじめご了承下 さい。

解剖記の記述はHSP_version2.61を前提としております。現在でも以下のリンク先から入手可能です。
Get!HSP2.61

[奮闘記]インデックス

01:はじめに
02:文章を表示させるのだ(その1)
03:文章を表示させるのだ(その2
04:文字を動かしてみるのだ(その1)
05:文字を動かしてみるのだ(その2)
06:星空を描いてみるのだ
07:星空を一気に表示するのだ
08:流れる星空を表示するのだ
09:流れる星空を背景に、静止したキャラを表示するのだ
10:流れる星空を背景に、自分が好きなように動かせるキャラを表示するのだ
11:配列を使って星の動きを制御するのだ
12:数式ってのはこういうものなのだ
13:ちゃんと自機がカーソルに従って動いた理由の解析
14:計算式と条件式(その1)
15:実はそんなに難しいことではなかった、カーソルで動くわけ
16:2点間の距離を算出するのだ

これらの元記事は、ファイルの更新日時を確認する限り、恐らく2000年3月24日頃書いたものです。現在の状況(既にHSP3.0がリリースされている)とは整合性が取れていない内容もあると思いますが、当時の記事のまま転載しております。間違っている記述があるかもしれませんが、あらかじめご了承下さい。

奮闘記の記述はHSP_version2.61を前提としております。現在でも以下のリンク先から入手可能です。
Get!HSP2.61

[改造記]スプライトの当たり判定

いよいよ問題の当たり判定処理です。今までとはちょっと違ったやり方になります。「キャラクタ一つ一つに対し、一つ一つ当たり判定をする」という基本は変わりないのですが、その「当たり判定」のやり方が根本的に違います。es_typeで設定したスプライトの種類を使い、es_findで検索、es_checkで判定する、という流れになります。

;■ 敵移動と当たり判定
*enemymove

enemy=0
repeat
es_find enemy,8,enemy ; 敵(8)スプライト検索
if enemy=-1 : break
es_get ex,enemy,3 ; x座標取得
es_get ey,enemy,5 ; y座標取得
es_get en,enemy,12 ; キャラクタ番号取得
if en=31 : gosub *enemy1
if en=32 : gosub *enemy2
if en=33 : gosub *enemy3
es_apos enemy,exv.enemy,eyv.enemy,100+level
et.enemy++ ; タイマー


es_check temp,enemy,4
; ↑自機弾(4)との当たり判定
if temp>0 {
ef.enemy--
es_get wx,temp,3
es_get wy,temp,5
es_kill temp ; 自機弾削除
if ef.enemy>0 {
es_new temp,505
es_set temp,wx,wy+5,7
; 自機弾爆風設置
es_type temp,16
; 効果type値(16)
es_adir temp,32,200
; 自機弾爆風移動指示
es_flag temp,$8306
; 消失タイマー
snd 3
score++
}
else {
es_kill enemy ; 敵削除
repeat 8
es_new temp,600
es_set temp,ex+5,ey+5,21
; 爆風設置
es_type temp,16
; 効果type値(16)
es_adir temp,cnt*8,400
; 爆風移動指示
es_flag temp,$8314
; 消失タイマー
loop
snd 2
gosub *messageborn
}
}

enemy++
loop
return

repeat~loopの回数指定なし無限ループを使うので、非常に注意が必要です。対象スプライトが見つからなかったらループから抜ける処理は必須です。もしこれを忘れてスクリプトの実行をしてしまうと、CTRL+ALT+DELも効かない、ALT+F4も効かない、電源コンセントを引っこ抜くしかないという悲惨な状況になります。もしテストするのが怖かったら「await 0」をrepeat~loopのどこかに入れましょう。これさえやっておけば、ALT+F4での強制終了だけは可能になります^^;

何故こんなことをしなければならないかというと、es_findはスプライト番号の若い方から順番に検索し、該当スプライトが見つかるとそこで処理が終わってしまう為です。そこで、処理の終わったスプライトの次の番号から再度検索をする必要があるのです。この一連の処理を行うには、repeat~loopの無限ループを使うのが一番効率がいいものと思われます。もっといい方法がある人は教えてください^^;

スクリプトを順番に見ていきましょう。まずes_findで存在する敵スプライトの検索をします。ここで活躍するのがes_typeで設定したtype値です。これは、当たり判定処理では必ず使うのにhtmlの命令別マニュアルには記載されていない(リファレンスを読みなさい、と書いてあります^^;)ちょっと都合の悪い奴です。ではまずリファレンスの説明から抜粋します。この説明文は非常にわかりやすいです。

・ スプライトtype値の設定について

スプライトのtype値は、ゲームなどで物体の識別をする時に有効に使うことができます。type値は、es_type命令で設定することができます。この値は、ユーザーの好きに設定することができる識別用の値となります。
設定できる値は、

1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768

の計16種類です。

es_set命令でスプライトを設定した直後は、type値は1になっています。この値は、衝突判定やスプライトの検索対象を指定するのに使用されます。スプライトが示す物体の種別を登録しておくのが主な利用法です。

たとえば、シューティングゲームで自機はtype1、敵はtype2、敵のミサイル
はtype4、自分のミサイルはtype8、といった感じでtype値をスプライトを出す時に設定しておきます。こうしておけば、自機が敵か、または敵ミサイルに衝突しているかを判定する場合にチェックがしやすくなります。

es_check命令では、複数のtype値を同時に判定できるので、この例で言うと自機のスプライトに対して、敵(type2)と敵ミサイル(type4)が衝突しているかを調べて自分の生死を決めることができます。

また、自分のミサイルが敵に衝突しているかを判定する場合には、まずes_find命令でtype8のスプライトだけを検索して、その1つ1つが、敵(type2)と衝突しているかを判定すればいいわけです。

このようにtype値をあらかじめ割り振っておくことで、スプライトの管理をスマートに行なうことが可能です。

いかがでしょうか?まさにtype値さまさまといった感じです(笑)。ところで、な~んでtype値ってこんな飛び飛びの数字なんだろうな~と思った人もいるんじゃないでしょうか?思わなかった人は読み飛ばしてください。これは2進法に直すとよくわかります。要するにコンピュータ的な処理で、なるべく容量を少なく識別をさせるには2進法が一番だからです。

                                                                                                                                     
10進法 2進法 16進法
1 %0000000000000001 $0001
2 %0000000000000010 $0002
4 %0000000000000100 $0004
8 %0000000000001000 $0008
16 %0000000000010000 $0010
32 %0000000000100000 $0020
64 %0000000001000000 $0040
128 %0000000010000000 $0080
256 %0000000100000000 $0100
512 %0000001000000000 $0200
1024 %0000010000000000 $0400
2048 %0000100000000000 $0800
4096 %0001000000000000 $1000
8192 %0010000000000000 $2000
16384 %0100000000000000 $4000
32768 %1000000000000000 $8000

type値がこのように割り振られているので、複数のtype値の判定や検索も可能なのです。例えばtype値8のスプライトとtype値8192のスプライトを検索する場合、こういった形になります。

                               
10進法 2進法 16進法
8 %0000000000001000 $0008
8192 %0010000000000000 $2000
8200 %0010000000001000 $2008

2進法の部分を見ると一番良くわかりますが、別々の位置がスイッチになってるのがよくわかると思います。要するにこうやってコンピュータは識別をしてるのです。

余談はこのくらいにして。

要するに、例えば自機のtype値を(1)、自機弾を(2)、敵機を(4)、敵弾を(8)といった具合に設定しておけば、es_findで検索 →es_checkで判定という処理ができる、ということです。原理はややこしいですが、処理は格段に楽になりますし、負荷も少なくできます。標準命令だ けでの当たり判定ルーチンを列記してみましょう。

;■ 敵移動
*enemymove
repeat emax
et.cnt++
if ef.cnt<1 : continue
if en.cnt=1 : gosub *enemy1
if en.cnt=2 : gosub *enemy2
if en.cnt=3 : gosub *enemy3
if ef.cnt<1 : continue
ent=cnt
repeat wmax
if wf.cnt<1 : continue
ddx=(ex.ent+25)-(wx.cnt+12) : if ddx<0 : ddx=-ddx
ddy=(ey.ent+25)-(wy.cnt+25) : if ddy<0 : ddy=-ddy
if (ddx<20)&(ddy<40) {
ef.ent-- : wf.cnt=0
if ef.ent>0 {
pos wx.cnt,wy.cnt
gcopy 2,175,25,25,25
snd 4
score++
}
else {
pos ex.ent,ey.ent
gcopy 2,200,0,50,50
snd 2
gosub *messageborn
}
}
loop
loop
return

このように二重ループを使って処理してましたね。仮に「emax=200」で「wmax=10」だとすると、ここだけで2000回のループを行うことにな ります。実際にはcontinueを使って処理飛ばしをしているので、全ての処理を2000回行っているわけではないのですが、やはり負荷は大きいです。 処理速度の減退の大きな理由になります。ところがスプライトのes_checkを使った処理の場合、確実に画面上に表示されてる対象スプライト数だけしか 処理を行いません。それどころか対象スプライトが無い場合は全く処理を行わないですむのです。せっかくDirectXを使って画面描写が速くなっても、そ れ以外の部分で処理が遅くなってては意味がありません。だからこのes_checkは非常に大きな意味があるし、使い応えもあるのです。

[改造記]スプライトの任意移動制御

さて、問題になってくるのがアルゴリズムを持って動くスプライトの制御です。まぁ問題、と言ってもそんなに大したことはありません。解剖記の敵の動かし方と基本は同じです。

	es_ini 1000, : es_screen 640,480,8,0

;~中略~

emax=1000 ;スプライトの最大数

;~中略~
;■ 配列定義
dim ef,emax ;敵固さ・生死
dim exv,emax ;敵x方向運動量
dim eyv,emax ;敵y方向運動量
dim et,emax ;敵出現からの時間

;~中略~

es_get ex,enemy,3 ; x座標取得
es_get ey,enemy,5 ; y座標取得
es_get en,enemy,12 ; キャラクタ番号取得
if en=31 : gosub *enemy1

;~中略~
;■ 敵1
*enemy1
eyv.enemy=4
dx=mx-ex : dy=my-ey
if dy<350 {
if dx<0 {
exv.enemy--
if exv.enemy<-25 : exv.enemy=-25
}
else {
exv.enemy++
if exv.enemy>25 : exv.enemy=25
}
}
if dx<0 : dx=-dx
if dx<50 : gosub *tamaborn

es_apos enemy,exv.enemy,eyv.enemy,100
return

まぁ要するに、配列変数を使って移動速度や配置からの時間なんかを一機一機に対して保持しておく、という形は変わらないです。ただ、es_getでスプライトの様々な情報が取得できるので、用意する配列変数は少なくて済みます。座標なんかは上記スクリプトのように簡単に取得できますしね。

後はes_aposを使ってx方向y方向の移動速度を変化させるだけです。基本的な部分は解剖記の敵の生成と移動アルゴリズム1となんら変わりありません。

ただし、注意しなければならないことがあります。それはスプライト数と用意する配列の数です。ちょっと工夫をしないと「配列の要素が大きすぎます」というエラーに悩ませられることになります(俺だけか?)。私なりのやり方なのですが、es_iniで宣言するスプライトの数だけ配列変数を用意しちゃいます。

;■ 敵生成
*enemyborn
rnd temp,7
if temp=0 : return
es_new enemy,600 ; 空きスプライト番号取得
if temp=1 : ef.enemy=1 : en=31
if temp=2 : ef.enemy=1 : en=31
if temp=3 : ef.enemy=1 : en=31
if temp=4 : ef.enemy=2 : en=32
if temp=5 : ef.enemy=2 : en=32
if temp=6 : ef.enemy=3 : en=33
; ef:硬さ en:キャラクタ番号
rnd ex,640 ; 初期出現位置x軸
et.enemy=0 ; タイマー初期化
es_set enemy,ex,-50,en ; スプライト登録
es_type enemy,8 ; 敵type値(8)
es_adir enemy,0,100 ; 初期移動処理
exv.enemy=0 : eyv.enemy=10
return

すると、上記スクリプトのように、スプライト登録をするときに何も考えずにそのまま空きスプライト番号を配列の要素として使えます。なので、これは敵に限らず全てのキャラで使えるように、汎用の配列として用意しておくのがいいのかもしれません。

[改造記]真っ直ぐ進むスプライト

敵や弾のように、随時登録するスプライトの場合、条件分岐でサブルーチンへ飛ぶ形で処理します。この前の項でも説明しましたが、es_setによる登録は一度行えば消える(消す)まで有効です。

;■ 自機弾発射
*weapon

es_new temp,501
if temp<504 {
es_set temp,mx+12,my,3
;↑自機弾スプライト設置(キャラクタ番号3)
es_type temp,4 ;自機弾type値(4)

es_adir temp,32,4000 ;移動方向指示
shottime=1 ;連射制御
}
return

まずes_newで未使用のスプライト番号を検索します。ここで指定する番号は前項で決めた表示の優先順位に従ってください。別に重ね合わせの順番なんか気にしないや~いという方は何も考えずに0でいいでしょう^^;

ただその場合「特定のキャラは画面上の表示数を制限する」といった作業が面倒になります。自機の弾なんかは画面上の表示数を決めておかないとゲーム性が崩 れます。上記スクリプトの場合、スプライト番号501~503は完全に自機弾専用になってます。こうすることで画面上に自機弾は3つ以上表示されないよう になってます。

設置してから後は移動を制御しない(ずっと直進する)キャラの場合、最初に一回だけes_adires_aimes_aposを行えば、あとはほったらかしでいいです。上記スクリプトの場合es_adirを使って画面上方向に直進するよう指示をしています。es_adirは下図のように下方向が0で、半時計回りに64段階の方向指定ができます。

es_adirの方向

つまりこの自機弾の場合32なので画面上方向に真っ直ぐ飛んでいくというわけです。これが例えば自機を狙う敵弾などの場合はこんな感じになります。

;■ 弾生成1
*tamaborn
es_new tama,0
if tama<500 {
es_set tama,ex+12,ey+12,4
;↑敵弾スプライト設置(キャラクタ番号4)
es_type tama,32 ;敵弾type値(32)
rnd dv,8 : dv+=level/2+2 ;弾速

es_aim tama,mmx,mmy,dv*100 ;移動方向
}
return

(mmx,mmy)は自機の座標です。たったこれだけの命令で狙い弾を撃たせることができます。めちゃめちゃ簡単でしょ?解剖記の敵弾の生成と移動のスクリプトと比較してみてください。はっきり言ってあの苦労はなんだったんだ?っていう感じです^^;

[改造記]スプライトの登録

es_sizeとes_patで登録したキャラクターは、es_setで 登録することで初めてスプライトとしての機能を果たします。一旦es_setで登録したスプライトは、自分で削除(es_killまたは es_clear)するか、移動してスプライト有効範囲(es_areaで設定)をはみ出すまで有効となります。それゆえes_setはキャラの生成時の みに使用し、後は他の命令で制御する形になります。毎フレームes_setで登録しなおす必要は全く無いので注意しましょう。

;■ スタート前準備
;~中略~

es_window 0,0,640,480 ;スプライト描写範囲設定
es_area -50,-50,640,480 ;スプライト有効範囲設定
es_cls ;一旦画面消去
es_sync ;消去した画面の描写
wait 100 ;最初だけ若干ウェイトをかまします

es_set 300,295,300,5 ;自機スプライト登録
es_type 300,1 ;自機Type値設定(1)

;■ メインルーチン
*main
es_cls 0,0,0 ;画面クリア
;~中略~・・・もろもろの処理

es_draw ;スプライト描写
;~中略~・・・スプライトより手前に描写したいものがある場合はここで処理
await 0
es_sync 32 ;画面描写とウェイト
goto *main

メインルーチンに入る前に、スプライトを描写する範囲と有効範囲を設定しておきます。これらは一度設定すれば以後はずっと有効になるため、前準備(メイン ルーチンなど永久ループに入る前)の段階で一度やれば二度とやる必要がありません。もちろんゲーム中に変更する必要がある場合は別ですが^^;

まずes_windowで スプライトを描写する範囲を決めます。例えば縦スクロールシューティングなどで、画面の縦横比を変えたい場合はここで設定します。あくまでこれはスプライ トの描写範囲の設定なので、es_copyなどを使えばこの範囲の外側にも描写はできます。例えばステータスウィンドウ(得点とかシールド残量などを表 示)のように、そこにスプライトが描写されると困るような範囲がある場合は使うといいでしょう。画面いっぱいに描写してもかまわないのであれば、設定する 必要はありません。

次にes_areaでスプライトの有 効範囲を決めます。これも前準備の段階で一度行えばオッケーです。es_windowを行うと、その範囲+上下左右128ピクセルが自動的にスプライトの 有効範囲に設定されます。なので、スプライト有効範囲を変更したい場合は必ずes_windowの後に行いましょう。このes_areaで指定された範囲 から外にスプライトがはみ出すと、そのスプライトは自動的に削除されます。スプライトの座標はその画像の左上になるので、見かけ上不合理が生じないように するには上記スクリプトのように、スプライト有効範囲を画面上側と左側に若干大きめにとる必要があります。下図を参考にしてみて下さい。

es_windowとes_areaの関係

シューティングゲームで言う自機のように最初から画面に現れてるキャラクターをスプライト登録する場合は、上記のスクリプトのようにメインルーチンに入る前に設置を行います。es_typeに関しては、当たり判定の部分で説明するのでここでは割愛させていただきます。

スプライトの重ね合わせはスプライト番号によって決まります。番号が若い方が手前に表示さ れるのです。例えばシューティングゲームで言うと、敵の弾が爆風などの後ろで描写されてると隠れて見えず、非常にプレイヤーにストレスがかかります(そう いうゲーム性だったらいいでしょうけど^^;)。なので、敵の弾は必ず自機のストライプ番号より少ない番号を選ぶようにしなければなりません。重ね合わせ の優先順位があるのならば、使用するスプライト番号の範囲を自分で決めておきましょう。例えばこんな感じです。

  1. 敵弾・・・0-500の範囲
  2. 自機・・・501
  3. 自機弾・・・502-510
  4. 爆風・・・511-550
  5. 敵機・・・551-650

スプライトの登録の際はこの優先順位がポイントとなります。自分の作るゲーム性に合わせていろいろ考えてみましょう。es_iniで初期化を行う際に、か なり多めにスプライト数を設定しておいて、使う範囲をわかりやすい番号で区切っておくというのも一つの手です。そうすれば、作ってる最中で最大数などを変 更したい場合にラクです。

そして、メインルーチン(永久ループになる部分)では、es_syncより前にes_drawを 行い、スプライトの描写をしましょう。これを忘れてるとスプライトだけが表示されないので「?」ってなことになります(私はサンプルv3.0を作ってる時 にやらかしました^^;)。スプライトより手前に描写したいものがある場合は、es_drawとes_syncの間で処理しましょう。例えばメッセージと か数字とか。そういった、スプライトに隠れては困るものはes_syncの直前に描写処理を行えばいいのです。

[改造記]スプライトを使う準備

それではいよいよ後半部分の解説に入ります。hspdx.dllのスプライト機能です。

まず最初に、スプライトの機能を使うと何ができるのか?という事を整理しておきましょう。単純に描写速度を速くする目的のみでDirectXを用いるのであれば、ここまでの講座内容だけで充分です。新出の命令の半分も使ってないですしね。覚えることも少なくて済みます。

しかし、スプライトの機能を使うことで、次のようなことが容易になります。

  • 当たり判定をDirectXの機能を使ってできる・・・アルゴリズムが簡単になる
  • キャラクタの管理ができる・・・新規のキャラクタ登録や削除が容易になる
  • 移動の制御ができる・・・例えば「弾を発射する」といったアルゴリズムが簡単になる
  • アニメーションができる・・・視覚効果を派手にするのが容易になる
  • 方角が調べられる・・・(※hspdxfix.dllのみ・・・スプライトじゃなくてもできます)

もうシューティングゲームを作ってください!と言っ てるのも同然の機能ですね。標準命令では苦労してた事が、スプライトを使うことによって非常にラクになります。「プログラミングの醍醐味はアルゴリズム だ!」という声も聞こえてきそうですが、私はできることならラクしたいです。ただし、厳密なこと(ex.正確な角度を求める)をやろうとするとどうしても 無理はでてくるので、そういう場合はdllを複数使うなりして対処しましょう。

さて、スプライトを使う場合最初にやらなければいけないのはキャラクタの登録です。使用する画像の大きさ当たり判定の範囲アニメーションのパターンなどは最初に設定しておきます。

;■ バッファへ画像の読みこみ
buffer 2,640,480,1
pos 0,0
picload "sampledx.bmp",1
es_buffer 0,0
if stat=1 : goto *dxerr3
;↑オフスクリーンバッファへの転送と成否判定


es_size 50,50,80,0
repeat 11 : es_pat cnt,50*cnt,0 : loop
;キャラクタ1・・・0-10

es_size 40,40,60,0
repeat 8 : es_pat 11+cnt,40*cnt,50,1 : loop
es_link 18,11
;キャラクタ2・・・11-18

まずes_sizeで切り抜いてくる画像の大きさと、当たり判定の大きさを決めます。例えば50x50の大きさのキャラクタで、当たり判定の範囲は40x40にしたいのなら、第3パラメータに80(%)を指定します。当たり判定の範囲は下図のようになります。

キャラクタ1の場合この図のように、中心から80%の範囲が当たり判定の範囲になります

スプライトを使う際に透過しない設定にすることはまずありえないので、第4パラメータには0を入れることになります。

そして次に、切り抜いてくる場所をes_patで 指定します。第1パラメータがキャラクタ番号になります。es_sizeが同じ設定の画像を連続で切り抜いてくるのなら、上記のスクリプトのように repeat~loopで一気に指定するとラクです。そうする為にも、最初にbufferへ読み込む画像は下図のようにつなげておくと便利です。

自機のロール画像です。このように使う順番で保存しておくと後がラクです

次に、自動でアニメーションさせる画像の場合の設定です。上記のスクリプトの場合、キャラクタ2は自動的にアニメーションさせるキャラクタです。このようにes_patの第4パラメータに アニメーションする早さを指定しておきます。これはes_syncで何回画面描写したら次のコマへ移るか?という指定になりますので、数を多くするとコマ 送りのようにになってしまいます。30FPSくらいの描写速度だと、2フレームに1回くらいならあまり不自然にはなりません。

連続したアニメーションの場合、es_linkで キャラクタ番号の何番から何番をつなげるかを指定します。アニメーションを途中で止める場合(設置直後はアニメーションするが、最後のコマでアニメーショ ンしなくなる)は、最後のコマのes_patの第4パラメータに0を指定します。0指定がなかったりes_linkでつなげるのを忘れると笑える現象が起 こりますので注意しましょう。全然違うキャラでアニメしてくれます^^;

                               
毎回描写ウェイトかけ過ぎes_linkの番号間違い
ウェイト1だとこのくらいコマ送り状態番号を間違えた

キャラクタの登録で最大のポイントになるのは、数が多くなると何番がどのキャラかわからなくなるということです。笑い事のようですが、真実です。上記のスクリプトのように、必ずコメントに「このキャラは何番から何番のキャラクタ番号」というのを書くことをお勧めします。私はこれで泣きました。キャラが多すぎて書いてても泣きました(笑)。

[改造記]解剖記のスクリプトを改造しよう

実はここまでの所さえ理解できていれば、DirectX対応に改造することは可能です。ただし、もちろんスプライトの機能は使えません^^;

というわけで「解剖記」のサンプルスクリプトを改造しちゃいましょう。当然スプライトの機能は使いません。否、まだ使えないでしょって^^;

 

; シューティングゲームサンプルv2.1
; by たかのん
; HSP v2.5 で作成されています
; 敵と自機の距離によって画面に倍数が表示されます。
; この倍数は、距離が近いほど大きくなります。

; v1.1との違い
; 画面描写をDirectXに対応
; ただし、スプライトの機能は使ってません

; ジョイスティックに対応


;■ DirectX 初期化&チェック
#include "hspdxfix.as"
#include "hspjsis.as"

es_ini : es_screen 640,480,8,0
if stat=1 : goto *dxerr1
if stat=2 : goto *dxerr2
goto *gamesetting

*dxerr1
dialog "DirectXの初期化に失敗しました。",1    : end
*dxerr2
dialog "スクリーンの初期化に失敗しました。",1 : end
*dxerr3
es_bye : wait 100
dialog "VRAMの容量が不足しています。",1       : end


*gamesetting
cls 4
highscore=5000
randomize
move=5 ;自機の移動速度を指定
smax=50 ;星の最大数
emax=50 ;敵の最大数
tmax=200 ;敵弾最大数
wmax=3 ;自機弾最大数
imax=10 ;倍数表示最大数
vmax=10 ;情報表示最大数
bmax=10 ;爆風最大数
cmax=50 ;カスリ効果最大数
level=1 ;難易度


;■ バッファへ画像の読みこみ
buffer 2,640,480,1
pos 0,0
picload "sample2.bmp",1

es_buffer 0,0
if stat=1 : goto *dxerr3
;↑オフスクリーンバッファへの転送と成否判定


;■ 効果音とBGMの読みこみ
sndload "Int01.mid",0,1
sndload "dmg.wav",1,0
sndload "exp.wav",2,0
sndload "shot.wav",3,0
sndload "kin.wav",4,0
snd 0


;■ 配列定義
*gamestart
dim sx,smax ;星のx軸座標
dim sy,smax ;星のy軸座標
dim sc,smax ;星の色
dim ss,smax ;星の移動速度

dim ex,emax ;敵x軸座標
dim ey,emax ;敵y軸座標
dim ef,emax ;敵固さ・生死
dim en,emax ;敵種類
dim exv,emax ;敵x方向運動量
dim eyv,emax ;敵y方向運動量
dim et,emax ;敵出現からの時間

dim tx,tmax ;敵弾x軸座標
dim ty,tmax ;敵弾y軸座標
dim tf,tmax ;敵弾on/off
dim txv,tmax ;敵弾x方向運動量
dim tyv,tmax ;敵弾y方向運動量

dim wx,wmax ;自機弾のx軸座標
dim wy,wmax ;自機弾のy軸座標
dim wf,wmax ;自機弾のOn/Off
dim wyv,wmax ;自機弾のy軸移動距離

dim ix,imax ;倍数x軸座標
dim iy,imax ;倍数y軸座標
dim iff,imax ;倍数表示時間
dim iv,imax ;倍数フォントサイズ
dim in,imax ;元得点

dim bx,bmax ;爆風x軸座標
dim by,bmax ;爆風y軸座標
dim bf,bmax ;爆風表示時間


 dim cx,cmax ;カスリ効果x軸座標
dim cy,cmax ;カスリ効果y軸座標
dim cxv,cmax ;カスリ効果x方向移動距離
dim cyv,cmax ;カスリ効果y方向移動距離
dim cf,cmax ;カスリ効果表示時間


;■ 星の準備
repeat smax
rnd sx.cnt,640
rnd sy.cnt,480
rnd sc.cnt,10
ss.cnt=10-sc.cnt
sc.cnt=sc.cnt*5
loop

;■ 平方根の準備
dim calc,900
repeat 900
temp=cnt : calc.cnt=temp*temp
loop

;■ スタート前準備
mx=295 : my=400 ;自機の初期位置を指定
mf=8 ;自機の初期シールドを指定
gameovertime=0
score=0
frame=0
highdst=0
gsel 0

es_cls
es_sync
wait 100

;■ メインルーチン
*main

es_cls bgcolor,0,0 : bgcolor=0
gosub *message ;ゲーム情報
gosub *haikei ;星の描写
gosub *jikimove ;自機移動
gosub *weapon ;自機弾
if frame\(25-level)=0 : gosub *enemyborn ;敵生成
gosub *enemymove ;敵移動
gosub *tekitamamove ;敵弾移動
if pat&128>0 : gosub *pausegame ;ポーズ
getkey returntop,123 : if returntop=1 : end ;強制終了(F12)
if mf<1 : mxv=0 : myv=0 : gameovertime++ : gosub *bomb
if gameovertime>100 : goto *gameover

await 0
es_sync 30
frame++
level=frame/500+1
if level>20 : level=20
goto *main


;■ 自機移動
*jikimove
if mf>0 : stick pat,79 : jstick 79

if stat!0 : pat=stat
;↑ジョイスティック入力判定
shottime--
if mf>0 : if (pat&64>0)&(shottime<0) : shot=1
xv=(pat>>2&1)-(pat&1)
yv=(pat>>3&1)-(pat>>1&1)
if mf>0 : mx=xv*move+mx : my=yv*move+my
if mx<0 : mx=0
if mx>590 : mx=590
if my<0 : my=0
if my>430 : my=430
pos mx,my

gmode 1,50,50 : es_copy 0,xv*50+50,0
return


;■ 背景描写
*haikei
repeat smax
sy.cnt=sy.cnt+ss.cnt
if sy.cnt>480 : sy.cnt=0 : rnd sx.cnt,640

pos sx.cnt,sy.cnt
gmode 1,5,5 : es_copy 0,400,sc.cnt
loop
return


;■ 自機弾発射
*weapon
repeat wmax
if (shot=1)&(wf.cnt<1) {
wf.cnt=1
wx.cnt=mx+12
wy.cnt=my
shot=0
shottime=1
; snd 3
;↑効果音がうるさいので切ってあります^^;
}
if wf.cnt<1 : continue
wy.cnt=wy.cnt-40
if wy.cnt<-50 : wf.cnt=0
pos wx.cnt,wy.cnt

gmode 1,25,50
es_copy 0,150,0
loop
return


;■ 敵生成
*enemyborn
rnd temp,7
if temp=0 : return
repeat emax
if ef.cnt>0 : continue
if temp=1 : en.cnt=1
if temp=2 : en.cnt=1
if temp=3 : en.cnt=1
if temp=4 : en.cnt=2
if temp=5 : en.cnt=2
if temp=6 : en.cnt=3
ef.cnt=en.cnt ; 固さ
rnd ex.cnt,640 ; 初期出現位置x軸
ey.cnt=-50 ; 初期出現位置y軸
exv.cnt=0 ; 初期移動量x軸
eyv.cnt=10 ; 初期移動量y軸
et.cnt=0
break
loop
return


;■ 敵移動
*enemymove
repeat emax
et.cnt++
if ef.cnt<1 : continue
if en.cnt=1 : gosub *enemy1
if en.cnt=2 : gosub *enemy2
if en.cnt=3 : gosub *enemy3
if ef.cnt<1 : continue
ent=cnt
repeat wmax
if wf.cnt<1 : continue
ddx=(ex.ent+25)-(wx.cnt+12)
if ddx<0 : ddx=-ddx
ddy=(ey.ent+25)-(wy.cnt+25)
if ddy<0 : ddy=-ddy
if (ddx<20)&(ddy<40) {
ef.ent-- : wf.cnt=0
if ef.ent>0 {
pos wx.cnt,wy.cnt

gmode 1,25,25
es_copy 0,175,25
snd 4
score++
}
else {
pos ex.ent,ey.ent

gmode 1,50,50
es_copy 0,200,0
snd 2
gosub *messageborn
}
}
loop
loop
return

;■ 敵1
*enemy1
dx=mx-ex.cnt
dy=my-ey.cnt
if dy<350 {
if dx<0 {
exv.cnt=exv.cnt-1
if exv.cnt<-25 : exv.cnt=-25
}
else {
exv.cnt=exv.cnt+1
if exv.cnt>25 : exv.cnt=25
}
}
if dx<0 : dx=-dx
if dx<50 : gosub *tamaborn
eyv.cnt=4
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
pos ex.cnt,ey.cnt

gmode 1,50,50 : es_copy 0,250,0
return


;■ 敵2
*enemy2
dx=mx-ex.cnt
if et.cnt<2 {
if dx<0 {
exv.cnt=-6
}
else {
exv.cnt=6
}
}
if et.cnt>25 : eyv.cnt-- : if eyv.cnt<-10 : eyv.cnt=-10
if ey.cnt>200 : gosub *tamaborn
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
pos ex.cnt,ey.cnt

gmode 1,50,50 : es_copy 0,300,0
return


■ 敵3
*enemy3
dx=mx-ex.cnt
if et.cnt<2 {
if dx<0 {
exv.cnt=-3
}
else {
exv.cnt=3
}
}
if ey.cnt>200 : eyv.cnt--
if et.cnt>20 : if ey.cnt<50 : eyv.cnt++
if ey.cnt>200 : gosub *tamaborn
if et.cnt>20 : if ey.cnt<50 : gosub *tamaborn
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
pos ex.cnt,ey.cnt

gmode 1,50,50 : es_copy 0,350,0
return


;■ 弾生成
*tamaborn
if frame\(8-(level/4))>0 : return
dx=mx-ex.cnt : px=dx : if dx<0 : dx=0-dx
dy=my-ey.cnt : py=dy : if dy<0 : dy=0-dy
dst=(dx*dx)+(dy*dy)
edx=ex.cnt+12 : edy=ey.cnt+12
repeat 900
if dst<calc.cnt : dst=cnt : break
loop
repeat tmax
if tf.cnt>0 : continue
tf.cnt=1
tx.cnt=edx : ty.cnt=edy
rnd ds,8 : ds=level/5+3+ds
txv.cnt=px*ds/dst
tyv.cnt=py*ds/dst
break
loop
return


;■ 敵弾移動 & 当り判定
*tekitamamove
repeat tmax
if tf.cnt<1 : continue
tx.cnt=tx.cnt+txv.cnt
ty.cnt=ty.cnt+tyv.cnt
if tx.cnt>640 : tf.cnt=0 : continue
if tx.cnt<-25 : tf.cnt=0 : continue
if ty.cnt>480 : tf.cnt=0 : continue
if ty.cnt<-25 : tf.cnt=0 : continue
dx=(mx+25)-(tx.cnt+12) : if dx<0 : dx=-dx
dy=(my+25)-(ty.cnt+12) : if dy<0 : dy=-dy
if (dx<5)&(dy<5) {
repeat tmax : tf.cnt-- : loop
pos tx.cnt,ty.cnt

gmode 1,25,25 : es_copy 0,175,25
bgcolor=128
snd 1
mf--
}
if (dx<30)&(dy<30) {
score++

ent=cnt
repeat cmax
if cf.cnt>0 : continue
cx.cnt=tx.ent+10
cy.cnt=ty.ent+10
rnd cxv.cnt,3 : cxv.cnt-1
rnd cyv.cnt,3 : cyv.cnt-1
cf.cnt=10
break
loop

}
pos tx.cnt,ty.cnt

gmode 1,25,25
es_copy 0,175,0
loop
return


;■ 倍率表示
*messageborn
dmy=my-ey.ent : if dmy<0 : dmy=-dmy
dstv=480-dmy/50
if highdst<dstv : highdst=dstv
dstlevel=level
dstcore=dstv*dstlevel
score=en.ent*100*dstcore+score
repeat imax
if iff.cnt>0 : continue
ix.cnt=ex.ent
iy.cnt=ey.ent
iv.cnt=dstv
iff.cnt=40
in.cnt=en.ent*100
il.cnt=dstlevel
break
loop
return

;■ ゲーム情報
*message

repeat cmax
if cf.cnt<1 : continue
cx.cnt=cx.cnt+cxv.cnt
cy.cnt=cy.cnt+cyv.cnt
gmode 1,5,5
cw=10-cf.cnt*5
pos cx.cnt,cy.cnt
es_copy 0,405,cw
cf.cnt--
loop

repeat imax
if iff.cnt<1 : continue
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
font "Impact",iv.cnt*5+12
pos ix.cnt,iy.cnt

es_fmes ""+in.cnt+" x "+iv.cnt
iy.cnt++
iff.cnt--
loop

font "MSゴシック",12,3
if highscore<score : highscore=score
color 0,255,255
pos 10,10

es_fmes "HIGHSCORE "+highscore

color 255,255,255
pos 10,30

es_fmes "SCORE "+score

color 0,255,0
pos 10,50

es_fmes "HIGH DISTANCE "+highdst

color 255,255,0
pos 10,70

es_fmes "LEVEL "+level

color 255,255,255
pos 10,460

es_fmes "SHIELD "
if mf<1 : return
repeat mf
color 255,cnt*32,0
pos cnt*10+60,460

es_fmes "■"
loop
return


;■ 自機爆風
*bomb
repeat bmax
bf.cnt--
if bf.cnt<1 {
rnd bf.cnt,10 : bf.cnt=bf.cnt+10
rnd bx.cnt,50 : bx.cnt=25-bx.cnt+mx
rnd by.cnt,50 : by.cnt=25-by.cnt+my
}
pos bx.cnt,by.cnt

gmode 1,50,50 : es_copy 0,200,0
rnd dbx,10 : dbx=dbx-5
bx.cnt=bx.cnt+dbx
by.cnt=by.cnt+10
loop
rnd effect,10
if effect=0 : snd 1 : bgcolor=128
if effect=1 : snd 2 : bgcolor=255
return


;■ ポーズ処理
*pausegame
es_cls
gosub *message ;ゲーム情報
font "MSゴシック",40,3
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
pos 250,200

es_fmes "PAUSE"
stick pat : stick pat,79 : jstick 79 : if stat!0 : pat=stat
if pat&128>0 : return
getkey returntop,123 : if returntop=1 : end ; 強制終了(F12)
es_sync 30
await 0
goto *pausegame


;■ ゲームオーバー
*gameover
es_cls
gosub *message ;ゲーム情報
font "MSゴシック",40,3
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
pos 200,200

es_fmes "GAME OVER"
pos 30,250
es_fmes "PUSH RETURN TO RESTART"
stick pat : stick pat,79 : jstick 79 : if stat!0 : pat=stat
if pat&32>0 : goto *gamestart
getkey returntop,123 : if returntop=1 : end ; 強制終了(F12)
es_sync 30
await 0
goto *gameover

hspdxfix.dll対応にする際に変更を加えた箇所はこの色になってます。大きく異なるのは基本設定の部分と、ポーズ&ゲームオーバーの処理です。その瞬間の画面のまま静止させるのが面倒だったので、ポーズ&ゲームオーバーのルーチンはその部分だけでループさせてます。画面上には文字しか表示されないです。もしやるとしたら、敵や自機などの表示だけをさせるルーチンを用意しましょう。試しに作ってみましたがやっぱり面倒でしたes_getbufを使って画面全部をbufferに保存して表示する・・・というやり方も試してみたんですが、こちらはうまくいかなかったです^^;

後はmes命令が全部es_fmesに変わってます。この命令は単純に置き換えできるので、エディタの置換機能で一気に変更しちゃいましょう。gcopyの部分も単純置換できるかな?

後は、カスリ効果を視認できるようにしたのと倍率表示を派手にしたのが変更点。もちろん改造元のスクリプトでやらかしてた部分(無駄な計算や、動かす前に表示しちゃってた事)も直してます^^;

[改造記]画面描写(es_copy)

さていよいよhspdx.dllを使う場合の画面描写です。実はスプライトの機能を使わなければ、変更点はgcopyの代わりにgmodeとes_copyを使うだけです。では「奮闘記」の配列を使って星の動きを制御するのだのスクリプト(若干異なります^^;)を改造してみましょう。やっと「改造記」の名に相応しい状況になってきました(笑)

改造前スクリプトと改造後スクリプトのダウンロード(lzh)
※hspdxfix.dllとhspdxfix.asはご自分でご用意ください

サンプルにはstars.bmpが添付されてます。こんな感じ。

小さくてわかりませんね(笑)左から順に、明るい色から暗い色まで1dotの点がうってあります。ワンブロック5x5の大きさでコピーするようにしています。まぁどんな大きさでもいいんですけど^^;

;DirectX 初期化&チェック
#include "hspdxfix.as"
es_ini : es_screen 640,480,8,0
if stat=1 : goto *dxerr1
if stat=2 : goto *dxerr2
goto *setting

*dxerr1
dialog "DirectXの初期化に失敗しました。",1 : end
*dxerr2
dialog "スクリーンの初期化に失敗しました。",1 : end
*dxerr3
es_bye : wait 100
dialog "VRAMの容量が不足しています。",1 : end


;初期設定
*setting
randomize
smax=100 ;星の最大数
dim sx,smax ;星のx軸座標
dim sy,smax ;星のy軸座標
dim sc,smax ;星の色
dim ss,smax ;星の移動速度
repeat smax
rnd sx.cnt,635
rnd sy.cnt,475
rnd sc.cnt,14
rnd ss.cnt,4 : ss.cnt++
loop

;バッファへ画像の読みこみ
buffer 2,640,480,1
picload "stars.bmp",1

es_buffer 0,0
if stat=1 : goto *dxerr3


;メインルーチンへ行く前の儀式(笑)
gsel 0

es_cls
es_sync
wait 100

;----------------------------------------------------------
;メインルーチン
*main
getkey returntop,123
if returntop=1 : end ;強制終了(F12)

es_cls 0,0,0
gosub *stars
await 0
es_sync 20

goto *main
;----------------------------------------------------------
;流れる星を表示するためのサブルーチン
*stars
repeat smax
pos sx.cnt,sy.cnt

gmode 1,5,5 : es_copy 0,sc.cnt*5,0
sy.cnt+=ss.cnt
if sy.cnt>480 : sy.cnt=-5 : rnd sx.cnt,640
loop
return

hspdxfix.dll対応にする際に変更を加えた箇所はこの色になってます。この色になってる部分が今回の改造のキモです。この*starsのルーチンをもし標準命令で書くとすると、こんな感じです。

;流れる星を表示するためのサブルーチン
*stars
repeat smax
pos sx.cnt,sy.cnt

gcopy 0,sc.cnt*5,0,5,5
sy.cnt+=ss.cnt
if sy.cnt>480 : sy.cnt=-5 : rnd sx.cnt,640
loop
return

このサンプルの場合gcopyをgmodeとes_copyに変更してますが、じつは標準命令の場合もgmodeでコピーサイズを指定しておけるので、実 質はgcopyをes_copyに差し替えるだけです。この程度のスクリプトなら、もろもろの準備をしなければならない分、DirectXを使うほうが面 倒だったりします^^;

stars.asとstarsdx.asをじっくり見比べてみてください。実行時の見た目は、chgdispで全画面表示をしてるので殆ど差が無いと思い ます。大きく変更されているのは今までの項で解説してきたDirectX対応にする為の準備部分だけのはずです。そしてこの部分はDirectX対応にす る限り、全く同じような形でスクリプトを組むこととなります。

[改造記]メインルーチン

では今度は、実際のメインルーチンで使う場合にどうするか?を説明しましょう。スプライトの機能を使わない限り基本的な部分はほとんど変わりないです。


gsel 0
es_cls
es_sync
wait 100

メインルーチンに入る前に、上記のように一旦画面クリアして一定時間待つ処理を入れましょう。あまり早く画面を切り替えてしまうと、画面にウィンドウの残骸が見えてしまいます。


;■ メインルーチン
*main
es_cls
; いろいろな処理
await 0
es_sync 30
goto *main

メインルーチン(主要な処理をループさせる部分です)は、上記のように「画面消去」→「画面更新とウェイト」という流れになります。そのどこでもいいの で、「await 0」を入れておきましょう。これは他のWindowsタスクに処理時間を渡すためです。これをやらないと結構やばい事になるので注意しましょう。

また、このように永久ループをさせる部分では、強制終了させるための処理も入れておきましょう。実際にDirectXの画面描写処理に入ると、CTRL+ALT+DELが効かなくなります。ALT+F4は効きますが、ソフト的に終了する手段を入れておいたほうが安全です。


;■ メインルーチン
*main
es_cls
; いろいろな処理
await 0
es_sync 30
getkey endgame,123 : if endgame=1 : end
goto *main

こんな感じです。

参考までに標準命令だけでやる場合のメインルーチンを併記しておきます。


;■ メインルーチン
*main
redraw 2
color 0,0,0
boxf 0,0,640,480
; いろいろな処理
await 30
redraw 1
goto *main

[改造記]使えなくなる命令

このようにHspをDirectX対応にすると、Hsp標準命令の画像描写関係のものは一切使えなくなります。否、標準命令の画像描写を行ってもDirectXの画面には反映されません。使ってもエラーが出るわけじゃないです。ただ単に見えないだけです^^;

でも見えないだけで実はちゃんと描写は行ってます。なので、buffer上にpsetやmes命令などで描写しておき、それをes_bufferに転送す れば他の画像と同じようにes_copyで画面に反映することが可能です。ただ、リアルタイムでこれをやっちゃうとめちゃめちゃ遅くなり、DirectX でやってる意味が無いです。なんで、使えなくなる、と考えておいて問題はないでしょう。

じゃ、使えなくなる(意味の無い)命令と、hspdx.dllで追加される命令と関係するものをまとめてみましょう。

  • プログラム制御命令関係・・・1つだけ使い方が変わります

    • await・・・画面更新速度はes_syncで一定にしますが、Windowsへタスクを渡すためにウェイト無しで使用します


  • 基本入出力制御命令関係・・・画面表示に絡むものは駄目です。

    • cls・・・代わりにes_clsを使います
    • dialog・・・es_byeでDirectXから通常画面に戻らないと見えません^^;
    • mes・・・代わりにes_meses_fmesを使います
    • print・・・代わりにes_meses_fmesを使います
    • text・・・試してません^^;
    • title


  • オブジェクト制御命令関係・・・1つを除き全滅です。

    • button
    • chkbox
    • clrobj
    • combox
    • input
    • listbox
    • mesbox
    • objmode
    • objprm
    • objsel
    • objsend
    • objsize・・・拡大縮小コピー(es_zoom)の際の大きさ指定に使います


  • 画面制御命令関係・・・ほぼ全滅。その代わりに使う命令があります。
    • bgscr
    • bmpsave・・・es_getbufでHspウィンドウに逆読み込みしてからならDirectXの描写画面を保存できます
    • boxf・・・代わりにes_boxfes_fillを使います
    • buffer・・・es_bufferの転送元として使います
    • chgdisp・・・解像度はes_screenで指定できます
    • gcopy・・・代わりにes_copyを使います
    • getpal・・・試してません^^;
    • gmode・・・es_copyのコピーサイズ指定に使います
    • gsel・・・Hspのウィンドウに対してしか意味が無いのでes_getbuf等の際には使うのでしょうか?
    • gzoom・・・代わりにes_zoomを使います
    • line
    • palcolor・・・試してません・・・が、多分使えます^^;
    • palcopy・・・es_bufferの際に自動的に設定されます
    • palette・・・試してませんが、es_palsetが代わりに使えるはずです
    • palfade・・・代わりにes_palfadeを使います
    • pget・・・試してません^^;
    • picload・・・es_bufferの転送元のbufferへの読み込みに使います
    • pos・・・健在です
    • pset
    • redraw・・・画面更新はes_syncで行います
    • screen・・・代わりにes_screenを使います
    • sysfont・・・試してません・・・が、多分使えます^^;
    • width・・・解像度がes_screenで決まるので使いません

私の場合、使えなくなって一番痛いのはlineですかね。「Aeolianation」のAUTO SHOTはDirectXだと再現できないのです(笑)。垂直か水平の線を引くのであれば、es_boxfなどで代用はできるのですが・・・。

[改造記]画像の準備

続いて使用画像の準備です。奮闘記にも登場したバッファローマン(をい)。DirectXを使う場合、通常のHspのバッファー以外にオフスクリーンバッファというのを用意することになります。言ってみれば悪魔超人のバッファローマンとキン肉マンの仲間になったバッファローマンの2種類がいるようなもんです(余計わかりづらい)。

通常のHspの画面描写
標準命令の場合

通常のHspの場合、上記のようにコピーする画像をbufferに入れておき、そこからscreenへgcopyで転送するという手法が一般的だと思います。これにredrawを組み合わせると高速な描写が可能になるというわけです。

hspdx.dllを用いた場合、DirectX専用のオフスクリーンバッファを作り、通常のバッファーからes_bufferで転送して、そこから画面上にコピーなどを行う形になります。

その際注意しなければならないのが、HspのDirectXは16bitカラーに対応していないという事です。つまりpicloadでbuffer→es_bufferで転送する元の画像は256色以内(本当は236色)に抑えておきましょう。「D-Pixed」等の256色お絵かきソフトを使って減色するのが一番いいかな?(※注_最新バージョンでは対応しているようです)

hspdx.dllの画面描写
hspdx.dllの場合


;■ バッファへ画像の読みこみ
buffer 2,640,480,1 ;バッファ作成
pos 0,0 : picload "sampledx.bmp",1 ;バッファへ読み込み
es_buffer 0,0 ;オフスクリーンバッファへの転送
if stat=1 : goto *dxerr3 ;成否判定

es_screenで初期化したサイズと、es_bufferで確保するサイズは同じ大きさになるので、解像度を320x240にした場合は、bufferのサイズは320x240以上にはできません。また、マニュアルにはオフスクリーンバッファは10枚取れると書いてありますが、実は8枚までしか取れないというバグがあります。これもhspdxfix.dllを使うことで解決できます。最新のバージョンでは64枚まで取れるようになってるので、画像をたくさん使う場合でも対応可能です。

こ のオフスクリーンバッファへの転送は、メインルーチンではやらないほうが無難だと思います。リアルタイムで画面描写してる最中に転送を行うと、やっぱりそ の瞬間ウェイトがかかってしまうはずです。シューティングゲームなら面と面の間にやるとか、初期設定の段階で全部用意しちゃっておく方がいいでしょう。私 の場合、途中での読み込みをやるとわけわからなくなるという理由でやってません^^;

[改造記]まずは初期設定

まず、HspでDirectXを使うのに必要なものを用意しましょう。

  1. Hspの標準セット(当たり前ですね^^;)
  2. DirectX3以上(DirectX5以上が推奨です)
  3. hspdx.dll(拡張dll本体)
  4. hspdx.as(ヘッダファイル)

これ以外にWindows95以上を搭載したパソコンも必要です。っつーかこれが無ければHsp標準命令でプログラミングすることさえできませんが^^;

hspdx.dllhspdx.ashsed2.exehsp2.exe等と同じフォルダに入れておきましょう。これ以外には、プログラム中に描写するBMPファイルなども同じフォルダに置いておく必要があります。まぁこのへんはHsp標準命令と同様です。

HspでDirectXを扱うのに、様々な命令が用意されています。それで何ができるか?ということを整理しておきましょう。大雑把に分類するとこんな感じです。

  • HspでDirectXを扱う為の準備系命令(es_iniやes_bufferなど)
  • 直接画面に描写をする命令(es_boxfやes_mesなど)
  • バッファから画像を転送する為の命令(es_copyやes_zoomなど)
  • スプライト関係の命令(es_newやes_setなど)

実はこれらの中で一番多いのはスプライト関係の命令だったりします。色々なことが制御できるので、やっぱり命令も色々あるわけです。

では最初にHspでDirectXを扱う為のおまじないをしましょう。これはスクリプトの先頭に記述する必要があります。


#include "hspdx.as"

これによって、hspdx.dllで扱うことができる諸命令が追加されます。お次に初期設定を行いましょう。


;■ DirectX 初期化&チェック
es_ini
es_screen 640,480,8,0
if stat=1 : goto *dxerr1
if stat=2 : goto *dxerr2
goto *gamestart

*dxerr1
dialog "DirectXの初期化に失敗しました。",1    : end
*dxerr2
dialog "スクリーンの初期化に失敗しました。",1 : end
*dxerr3
es_bye : wait 100
dialog "VRAMの容量が不足しています。",1       : end

まずes_iniでシステムの初期化を行います。スプライト数の上限(定義なしで512個)やキャラクタ定義数の上限(定義なしで1024個)を変更することもできます。

次にes_screenで 画面解像度などの指定を行います。VRAMの容量に左右されるようですが、320x240、640x480、800x600の3つのサイズが対応してるよ うです。実はプレステなどの家庭用ゲームでは320x240の解像度が限界なので、あれよりもっときめの細かい画面表現が可能ということになります。た だ、その分絵を描くのは大変ですし、容量も食います。自分が目指すクオリティによって解像度を決めましょう。

お次に初期化の成否チェックを行います。でも実はhspdx.dllのバグで、もし初期化に失敗してもこのダイアログは表示されません。何がいけないんだぁ~(T-T)と泣き叫びながらデバッグを行うことになります。なので「はじめに」でも書きましたが、Sinagawaさんによってデバッグされたhspdxfix.dllを使うことを強くお勧めします。そ の場合ウィンドウモードで起動して、Hspのデバッグウィンドウを随時参照するようにしましょう。ただ、ウィンドウモードではDirectXの強みである 画面描写がものすごく遅くなるので、デバッグする時以外はフルスクリーンモードに戻しましょう。es_screenの第4パラメータに3を指定するとウィンドウモードです。


;■ DirectX 初期化&チェック
;■ hspdxfix.dllでウィンドウモード起動する場合
es_ini
,,-1 : es_screen 640,480,8,3
if stat=1 : goto *dxerr1
if stat=2 : goto *dxerr2
goto *gamestart

*dxerr1
dialog "DirectXの初期化に失敗しました。",1    : end
*dxerr2
dialog "スクリーンの初期化に失敗しました。",1 : end
*dxerr3
es_bye : wait 100
dialog "VRAMの容量が不足しています。",1       : end

2箇所変更点がありますね。hspdx.dllの2つ目のバグで、パレット番号0が強制的に透明色となる。その上パレット番号0が#000000の黒以外の場合は透過しないと いうものがあります。私の場合、hspdx.dllを使う前からパレット番号0は黒にしていた為このバグには気がつきませんでした。非常にラッキーだった と言えます。もしパレット番号0に黒以外を指定してしまってた場合、パレットを変更するか、hspdxfix.dllを使い上記のようにes_iniで初 期化する時に第3パラメータに-1を入れておきましょう。これで解決。

[改造記]はじめに

ここではHspにDirectXの機能を付加することができる「hspdx.dll」についての解説を行います。予めお断りしておきますが、「奮闘記」や 「解剖記」を読んでも全然意味がわからない~という方は、秀和システムの「Hspプログラミング入門」等を通読して、基本的なことは押さえてからここの解 説に移って下さい。

Hspは非常に高速な言語です。ただ、リアルタイムで処理するプログラムになると、標準命令だけではどうしても限界があります。それは画面描写速度が 原因となる部分が殆どです。これは別にHspだけの問題ではありません。Windowsの限界なのです。「Aeolianation」を見ていただければ わかると思いますが、Hsp標準命令だけでもあれくらいはできます。実はウィンドウズサイズがそれほど大きくないので負荷が少ないという理由もあるのです が^^;

そこで登場したのがDirectXです。これは Windowsの弱点を補うべく、Mircosoftが開発した「高速な画面描画のためのコンポーネント」です。これは、それぞれのビデオカードごとにド ライバを用意することにより、直接ビデオカード上のハードウエアを介して画面描画を行なうことを可能に する、というものです(Hsp2.5添付のhspdx.txtより)。このDirectXを使うことによって、非常に高速な画面描写をすることが可能にな りました。

ただ本当は、普通にDirectXを使おうと思うとめちゃディープなWindowsの知識が必要だったりします(私もこのへんはまだ全然わかりません^^;)。ところがHsp2.5に添付されているhspdx.dllを使うことによって、このDirectXを比較的簡単に扱うことができるのです。命令一つでスプライト(ファミコンなんかで使われてた、キャラクタを簡単に制御する奴です)の機能を使えたり、命令一つでそのスプライト同士の衝突判定をすることが出来るのです。

ただ、DirectXを比較的簡単に扱うことができる、とは言ってもやっぱりいろいろ面倒です。また、実はこのhspdx.dllには若干のバグがあって(※注:バグがあるのはHsp2.5に添付されていたバージョンなので、Hsp2.6を使っている場合は問題ありません)、それが原因でデバッグが非常に難しい状態になってしまっています。そこでここではhspdx.dllの基本的な使い方と、Sinagawaさんによってデバッグされたhspdxfix.dll(※注:Hsp2.6には標準で添付されるようになりました)便利機能について解説をします。最終的には「解剖記」で解説しているサンプルスクリプトを、DirectX完全対応に改造するところまでやります。

※文中で打ち消し線が入っている箇所は、Hsp2.6が出たことにより記述が現実に即さなくなった部分です。

Divide by 0 Divide by 0
Hspdxfix.dllは進化を続け、現在ではv0.19cになっています。Direct3D対応になり、回転・半透明・加算合成などもできるようになってます。

[解剖記]アナログジョイスティックへの対応

いまさら・・・という感じですが、ジョイスティックを購入したので、その対応をやってみました。でも、Hspの標準命令にはジョイスティックの入力に関する命令が無いため、拡張dllを使う事になります。Miaさんの開発された、hspjsis.dllをダウンロードし、hsp2.exeなどと同じフォルダに入れましょう。

詳しくはhspjsis.dll添付のreadmeやサンプルを参考にして頂きたいのですが、この拡張dllを使う場合はスクリプトの先頭を次のようにして下さい。


#uselib "hspjsis.dll"
#func jstick jstick $0
#func jsticka jsticka $0
#func jassign jassign $0
#func jdset jdset $0

これで準備ができました。

実は、デジタルジョイスティックへの対応は非常に簡単です。というのは、stick命令とjstick命令はほぼ同じ機能なので、


stick (変数)

となってる部分に追加して、


jstick : (変数)=stat

とやればOKです。より具体的にすると、次のような形にした方がいいでしょう。


stick pat,79
jstick 79 : if stat!0 : pat=stat
xv=(pat>>2&1)-(pat&1)
yv=(pat>>3&1)-(pat>>1&1)

非トリガータイプキーの指定の仕方も全く同じです。

問題はアナログジョイスティックへの対応です。システム変数statに代入される値がstick命令と全く違うため、対応させるためにはキー入力の判定を別の方法でやらなければなりません。その対応版は以下の通りです。色がついてる部分が変更箇所になります。


;■ 自機移動-ジョイスティック対応後-
*jikimove
if mf>0 :
jsticka : pat=stat
shottime--
axv=pat>>16&255 : xv=0
if axv< 96 : xv=-1
if axv>160 : xv=1
ayv=pat>>8&255 : yv=0
if ayv< 96 : yv=-1
if ayv>160 : yv=1
pat=pat<<4
if mf>0 : if (pat&64>0)&(shottime<0) : shot=1
if mf>0 : mx=xv*move+mx : my=yv*move+my
if mx<0 : mx=0
if mx>590 : mx=590
if my<0 : my=0
if my>430 : my=430
pos mx,my : gcopy 2,xv*50+50,0,50,50
return

赤い部分が運動方向の算出式です。
(jstickaで得られる値)>>16&255が左右方向を表します。この値は、127を中心として、それ以下(0まで)は左、それ以上(255まで)は右を表します。
(jstickaで得られる値)>>8&255が上下方向です。この値も同じく、127を中心として、それ以下(0まで)は上、それ以上(255まで)は左を表します
ちゃんとしたアナログジョイスティックというのは、大きく傾けると速く、小さく傾けると遅く移動する、というような判定もできるようです。要は、127を 中心として256段階の判定ができるというわけ。私の持ってるジョイスティックの場合、左なら0、右なら255と、とってもデジタルな値しか返してくれま せんでしたけども^^;
それゆえここでは、127を中心として64の範囲を「動かない(xvまたはyvが0)」、それ以外の場合をそれぞれ上下左右に割り振っています。

ゴールドの部分がボタンの入力判定算出式です。
こうすれば、stick命令と同じ値が返されるようになります。具体的には、ボタン1がスペースキー(16)、ボタン2がEnterキー(32)、ボタン3がCtrlキー(64)、ボタン4がEscキー(128)です。


さて、問題はユーザーによって持ってるジョイスティックの種類が違うということです。デジタル入力の場合とアナログ入力の場合とで、入力判定の方法が異 なってくるため、同じルーチン内で処理するのは困難です。私の場合、コンフィグメニューでユーザーに「どちらを使うのか?」を選ばせるようにしてありま す。恐らく順々に入力判定を行えば、ユーザー側がデジタルアナログを意識することなく利用することが出来るようなスクリプトを組むことができると思います が、それは皆さんのほうで工夫してみてください。

参考までに、今までのキーボードからの入力のみの移動判定ルーチンを列記しておきます。


;■ 自機移動-ジョイスティック対応前-
*jikimove
if mf>0 : stick pat,79
shottime--
if mf>0 : if (pat&64>0)&(shottime<0) : shot=1
xv=(pat>>2&1)-(pat&1)
yv=(pat>>3&1)-(pat>>1&1)
if mf>0 : mx=xv*move+mx : my=yv*move+my
if mx<0 : mx=0
if mx>590 : mx=590
if my<0 : my=0
if my>430 : my=430
pos mx,my : gcopy 2,xv*50+50,0,50,50
return

[解剖記]全画面表示への改造

実は、全画面表示への改造は非常に簡単です。拍子抜けするくらい簡単です。スクリプトの先頭の当りをこのようにすればオッケーです。


;■ 初期設定
chgdisp 2 : mouse -1


;■ バッファへ画像の読みこみ
buffer 3,640,480,1 : picload "sample.bmp",1
bgscr 2,640,480,1  : palcopy 3 : gmode 2 : gsel 2,2

後はgcopyを行ってる部分を、バッファー2から3に直すだけ(gcopy 2,・・・となってるのを、gcopy 3,・・・にする)です。たったこれだけのことでなんだか市販ゲームみたいな雰囲気ですね(笑)

[解剖記]その他の画面効果

まず、倍率を表示する部分です。


;■ 倍率表示
*messageborn
dmy=my-ey.ent : if dmy<0 : dmy=-dmy
dstv=480-dmy/50
if highdst<dstv : highdst=dstv
dstlevel=level
dstcore=dstv*dstlevel
score=en.ent*100*dstcore+score
repeat imax
if iff.cnt>0 : continue
ix.cnt=ex.ent
iy.cnt=ey.ent
iv.cnt=dstv
iff.cnt=20
il.cnt=dstlevel
realscore=en.ent*100 ;・・・???
break
loop
return

倍率は、y軸座標の比較だけで行っています。そのため非常にシンプルです。自機に近いほど倍率が高いようにするための計算をし、あとはそれをスコアに加算するだけです。???の部分は何の処理をしてるのか私にもわからなくなってしまいました。realscoreという変数はここ以外には出てこないので、全く意味の無い行かもしれません^^;


続いてゲーム情報の表示をする部分です。ここにはなんのひねりもありません。画面上に倍率表示がしばらく残るような処理を行ってるだけです。まぁ特に解説する必要もないでしょう。


;■ ゲーム情報
*message
repeat imax
if iff.cnt<1 : continue
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
font "MSゴシック",iv.cnt*2+8,1
pos ix.cnt,iy.cnt
mes "× "+iv.cnt
iy.cnt++
iff.cnt--
loop

font "MSゴシック",12,3
if highscore<score : highscore=score
color 0,255,255
pos 10,10 : mes "HIGHSCORE "+highscore

color 255,255,255
pos 10,30 : mes "SCORE "+score

color 0,255,0
pos 10,50
mes "HIGH DISTANCE "+highdst

color 255,255,0
pos 10,70
mes "LEVEL "+level

color 255,255,255
pos 10,460
mes "SHIELD "
if mf<1 : return
repeat mf
color 255,cnt*32,0
pos cnt*10+60,460
mes "■"
loop
return


続いて、ポーズの処理です。Escが押されたらこのルーチンへ飛び、再度押されるまでメインルーチンには戻らないようにしてあります。


;■ ポーズ処理
*pausegame
redraw 2
font "MSゴシック",40,3
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
pos 250,200 : mes "PAUSE"
stick pat : if pat&128>0 : return
getkey returntop,121
 if returntop=1 : end ; 強制終了(F10)
await 30
redraw 1
goto *pausegame

例えば、ポーズ中も星は流れるようにする、といった処理を加えようと思ったら、もう一つメインルーチンを作るくらいのつもりでやらないとダメです。まぁサンプルではそこまで難しいことはやるつもりもなかったので、文字の色を変化させるだけに留めてあります。

一点だけ、メインルーチンにもある強制終了の処理で すが、ウィンドウモードで起動させてる限り特に必要はないのですが、全画面表示にしたり、DirectX対応にした場合などは必ず必要です。特に DirectX対応にした場合はCtrl+Alt+Delも効かなくなるので、無限ループになる部分には必ず入れるようにしましょう。

[解剖記]敵弾と自機の当り判定

続いて、敵弾と自機の当り判定です。基本は自機弾と敵機の当り判定と全く同じです。二重ループを使わない分、こっちの方が簡単かもしれません。ポイントは同じく、座標差(絶対値)を求め、その差によって判定するというところです。


;■ 敵弾移動 & 当り判定
*tekitamamove
repeat tmax
if tf.cnt<1 : continue
tx.cnt=tx.cnt+txv.cnt
ty.cnt=ty.cnt+tyv.cnt
if tx.cnt>640 : tf.cnt=0 : continue
if tx.cnt<-25 : tf.cnt=0 : continue
if ty.cnt>480 : tf.cnt=0 : continue
if ty.cnt<-25 : tf.cnt=0 : continue
pos tx.cnt,ty.cnt : gcopy 2,175,0,25,25
dx=(mx+25)-(tx.cnt+12) : if dx<0 : dx=-dx
dy=(my+25)-(ty.cnt+12) : if dy<0 : dy=-dy
if (dx>=0)&(dx<10)&(dy>=0)&(dy<10) {
repeat tmax : tf.cnt-- : loop
pos tx.cnt,ty.cnt
gcopy 2,175,25,25,25
bgcolor=128
snd 1
mf--
}
if (dx>=10)&(dx<40)&(dy>=10)&(dy<40) : score++
loop
return

これも計算式に無駄がありますね。自機弾と敵機の当り判定と全く同じです。絶対値を求めてるのだから、ゼロ以下になる事はありえませんね。こんな感じで充分です。

if (dx<10)&(dy<10)

自機と敵弾が当ったときの処理でちょっと変わってるのが、ダメージを受けたらその瞬間に敵弾が全て消えるという部分です。これによって、ゲーム難易度を落としています。非常に短い記述でこういうことが簡単にできるのです。例えば敵弾を全て消すフラッシュボムのようなものにも使えますね。

ダメージを受けたときの処理でもう一つのポイントが、画面を赤くフラッシュさせる処理です。これは、メインルーチンの部分で画面クリアをする色をダメージを受けたフレームだけ変更する事によって実現しています。メインルーチンのこの部分です。


;■ メインルーチン
*main
redraw 2
color bgcolor,0,0 : bgcolor=0
boxf 0,0,640,480
------------------------------------------------------------
goto *main

これによって、普段は(color 0,0,0)で画面更新を、ダメージを受けたときは(color 128,0,0)で画面更新を行うようになります。

もう一つ、画面効果を行わなかったので気づかない人も多いかと思うのですが、カスリボーナスの処理を 行っています。これも当り判定の応用で、一定範囲以下であれば得点を加算する、という処理だけをしています。例えば「Distance」の場合、カスった ら火花のようなものが飛ぶようになってるので、カスリボーナスの存在にも気づかれやすいです。が、このサンプルではあえてやらなかったので、実装してても 気づいてもらえなかったのではないでしょうか?プログラムの構造のところでも話をしましたが、プログラムの流れは

ユーザーからの入力その結果がどうなったかを判定画面に表示(音声の出力)

こういった形になっています。「入力」→「判定」部分は基本になってくるので、それほど違いを持たせることはできませんが、「表示(出力)」部分に関して は、力いっぱい凝ることもできますし、シンプルにしちゃうこともできます。あまり凝りすぎると処理速度の問題なんかも出てきてしまいますが、せっかく実装 している判定をユーザーに分かってもらえないというのも悲しいものです。サンプルの場合、わざと(面倒だったからじゃないです^^;)得点にだけ反映させ てます。が、こういう小さな部分に凝る事で、ゲーム自体の面白みが増す事もあるということを覚えておきましょう。

[解剖記]敵弾の生成と移動

さてお次は敵弾の生成です。ここのポイントは、如何にして自機を狙ってくる弾を撃たせるか?という部分です。Hspの命令に√を扱うものがあればそれほど問題にはならないし、sin・cos・tanを扱うものがあればもっと楽なんですが、残念ながら基本命令では四則演算しかできないため、ちょいとひねったことをやっています。


;■ 平方根の準備
dim calc,900
repeat 900
temp=cnt : calc.cnt=temp*temp
loop


;■ 弾生成
*tamaborn
if frame\(8-(level/4))>0 : return
dx=mx-ex.cnt : px=dx : if dx<0 : dx=0-dx
dy=my-ey.cnt : py=dy : if dy<0 : dy=0-dy
dst=(dx*dx)+(dy*dy)
edx=ex.cnt+12 : edy=ey.cnt+12
repeat 900
if dst<calc.cnt : dst=cnt : break
loop

repeat tmax
if tf.cnt>0 : continue
tf.cnt=1
tx.cnt=edx : ty.cnt=edy
rnd ds,8 : ds=level/5+3+ds
txv.cnt=px*ds/dst
tyv.cnt=py*ds/dst

break
loop
return

要は、最終的には弾の運動量(txv,tyv)が、ちゃんと自機の座標方向へ進むものであればいいわけです。その方向は(px,py)で決まってきます。 これは、自機座標から敵機座標を単純に引いたものです。例えば敵機が自機の右斜め前方にいる場合、弾の運動量はx方向はマイナス・y方向はプラスになりま す。

自機座標から敵機座標を引けば、どういう場合でもちゃーんと方向だけは算出できます。後はこれに弾の速さ(サンプルの場合ds)を掛けて、一定数で割ればいいだけです。問題は「割る数をどうやって出すか?」です。

例えば定数で割ると、自機と敵機が近いと遅い弾・自機と敵機が遠いと速い弾という妙な事になってしまいます。要は、自機と敵機の実距離で割ればいいのです。奮闘記の一番最後で二点間の距離の算出方法が解説してありますが、サンプルではそれを応用しています。

もっとシンプルにやるのであれば、xの差(dx)とyの差(dy)で大きいほうで割るという手もあります。Miaさんのページで はその方法が解説してあり、「Aeolianation」でもこの方法で敵弾の処理を行っています。処理速度と手間を考えると、この方法のほうが楽です が、斜めに飛んでくる弾が若干速くなるという欠点があります。いずれにしても、基本命令だけでやろうと思うと結構無理があります。拡張dllを使い、斜め の実距離を計算で出す(hepext.dllを使う)か、ソフト的にやらせる(hspdx.dll = DirectXの拡張dllを使う)方が全然ラクです^^;

そういえば、この「二点間の距離を求める」解説をした際に、もっと簡単なやり方があるよ~と教えてもらったことがありました。これを使ってやるという方法 もありますので、紹介させていただきます。実は私も最初はサンプルを作るときにこれを使おうと思ったのですが、うまくいかないので自分なりに開発したやり 方にしてしまったという経緯があります^^;


bits=10 ; 2のbits乗-1までの整数を平方根として求める。
x=573* ; xに√を求めたい値を代入

b=$400 ;1<<bits;10でないときはこれを使う

repeat bits
b=b>>1 : aa=ans|b : if aa*<=x : ans=aa
loop

mes "√"+x+"="+ans
stop

どなたか、これを使ってもっと簡単に狙い弾を撃たせるスクリプトを開発してくださいまし。私にはできませんでした。お願い~。

[解剖記]敵と自機弾の当り判定

さて、問題の当り判定です。これがしっかりしていないと、ゲームとして成立しませんね。

ここでのミソは2重ループを 使ってることです。つまり、敵一機一機について、弾一個づつ順番に当り判定をしているわけです。このサンプルでは敵が最大50(emax=50)で、自機 弾が最大3(wmax=3)なので、このルーチンでは合計50x3=150回の当り判定ループを行っていることになります。そして、その2重ループを行う 上で一番大切な処理が「 ent=cnt 」の部分です。


;■ 敵移動
*enemymove
repeat emax
et.cnt++
if ef.cnt<1 : continue
ent=cnt
repeat wmax
if wf.cnt<1 : continue
ddx=(ex.ent+25)-(wx.cnt+12)
if ddx<0 : ddx=-ddx
ddy=(ey.ent+25)-(wy.cnt+25)
if ddy<0 : ddy=-ddy
if (ddx>=0)&(ddx<20)&(ddy>=0)&(ddy<40) {
ef.ent-- : wf.cnt=0
if ef.ent>0 {
pos wx.cnt,wy.cnt
gcopy 2,175,25,25,25
snd 4
score++
}
else {
pos ex.
ent,ey.ent
gcopy 2,200,0,50,50
snd 2
gosub *messageborn
}
}
loop
if en.cnt=1 : gosub *enemy1
if en.cnt=2 : gosub *enemy2
if en.cnt=3 : gosub *enemy3
loop
return

普通に1重のループであれば、「 cnt 」というシステム変数を使えば「何番目の要素番号」というのは簡単に処理できます(奮闘記10.配列変数を参照)。ところが、2重のループになった場合、内側のループではそのシステム変数が内側だけのものになってしまいます。つまり、外側のループが何回目の ループ(要素番号何番か?)なのか?を記憶しておく変数を用意しなければ、内側のループでそれを使う事ができないのです。つまり、内側のループではent が敵、cntが自機弾の番号を意味しているわけです。

さて、問題の当り判定。これは座標の差(絶対値)によって判定します。その際敵機と自機弾の大きさの違いを考慮に入れて、計算式の中で調整を行っています。要は、中心座標の差(絶対値)を求めているわけです。敵機の大きさは50x50なので、中心座標は(ex+25、ey+25)になります。自機弾の大きさは25x50なので、だいたいの中心座標は(wx+12、wy+25)です。この差を求め、それが一定数以下ならばあたりという判定を行っているわけです。

ここで、お気づきの方もいるかと思いますが、この計算式には無駄があります。座標の差を絶対値で求めているので(ddx>=0)や(ddy>=0)という条件は不要です。無駄が無いようにするならばこの部分の計算式はこうなります。

if (ddx<20)&(ddy<40)

またしても半日で作った弊害が表面化してしまいました。まぁあんまり問題にはならないですが、皆さんがこういう処理を行うときは、こういった無駄な事はしないようにしましょう^^;

ちなみに、この数値を大きくしたり小さくしたりする事によって、当り判定の範囲を大きくしたり小さくしたりする事ができます。ゲームの難易度に大きくかかわってくる部分ですね。

後は、当りだった場合にどう処理をするかだけです。このサンプルでは敵の固さという概念がありますのでそれを減らし(ef.ent--)、自機弾を消し (wf.cnt=0)ます。この自機弾を消す処理を行わないと、貫通するレーザーのできあがりです。また、ef.entから減らす値を武器によって変えれ ば、威力の違いという概念も導入できます。「Aeolianation」ではLimitedモードだけしか出てきませんが、自動標準レーザーと誘導弾の威 力を著しく落とすために、この概念を導入しています。

後は、敵がまだ破壊できてない場合(ヒットした効果音と画像の表示)と、破壊した場合(爆発音と爆風の表示・得点のカウントと倍率表示の処理)の処理を行 うだけです。このあたりの処理をどうするかはアイデア次第です。例えば爆風も、もっと飛び散るような派手なものにするんだったらそれなりの処理を行わなけ ればなりませんし、例えば連続ヒットコンボのような概念を導入するのであれば、やはりもっと処理は複雑になってきます。ただ、基本は座標の差を求め、それ が一定数以下の場合は当り、という部分です。

[解剖記]敵の生成と移動アルゴリズム3

続いて敵3のアルゴリズムです。基本は今までの敵と全く同じです。問題はアイデアということになってきますね。「Aeolianation」で一番気に 入ってる敵は、3面で出てくる目の前に貼りつく奴だったりします。それ自体に攻撃力は無いものの、まるで盾のように自機の攻撃を防いでしまうという滅私奉 公ぶりが何とも言えませんね^^


■ 敵3
*enemy3
pos ex.cnt,ey.cnt : gcopy 2,350,0,50,50
dx=mx-ex.cnt
if et.cnt<2 {
if dx<0 {
exv.cnt=-3
}
else {
exv.cnt=3
}
}
if ey.cnt>200 : eyv.cnt--
if et.cnt>20 : if ey.cnt<50 : eyv.cnt++
if ey.cnt>200 : gosub *tamaborn
if et.cnt>20 : if ey.cnt<50 : gosub *tamaborn
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
return

左右の移動方向は敵2と同じように、生成直後に決まります。後は、上下にサインカーブを描いて飛んでいくだけです。カーブの頂点付近で弾を乱射するのも敵 2と同じです。敵2の場合はUカーブを描いてそのまま画面の上に消えてしまいますが、こいつの場合いつまでも画面に残ってるため、撃ちもらすとやっかいな ことになるのです。実は「Aeolianation」の1面のボスとほぼ同じ動きだったりします(画像も同じだけど)。「Aeolianation」の1 面のボスの場合、画面の左右の端にきたら、左右の運動量を反転させ、あたかも画面端でバウンドするような動きになっています。

ここまで3種類の敵のアルゴリズムを解説しましたが、いかがでしょう?生成されてからの時間であったり、自機との距離であったり、実際の座標であったりと、いろんな条件を使って動きを制御しているのがわかると思います。それほど難しくはないですよね?問題はアイデアです。

[解剖記]敵の生成と移動アルゴリズム2

続いて「敵2」のアルゴリズムです。敵を何種類も用意しようと思ったら、このようにその数だけサブルーチンを用意しなければなりません。この敵の動きがゲーム性を決める要素の一つでもあるので、いろいろと頭をひねりましょう。意外にこういう作業が楽しかったりします。


;■ 敵2
*enemy2
pos ex.cnt,ey.cnt : gcopy 2,300,0,50,50
dx=mx-ex.cnt
if et.cnt<2 {
if dx<0 {
exv.cnt=-6
}
else {
exv.cnt=6
}
}
if et.cnt>25 : eyv.cnt-- : if eyv.cnt<-10 : eyv.cnt=-10
if ey.cnt>200 : gosub *tamaborn
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
return;

まず出現してからすぐのタイミングで、左右どっちへ動くか決めます。この場合、自機の方向へ動くようにし、その方向は死ぬ(消える)まで変わりません。

続いて一定時間経過後、y軸の移動方向を変え、画面の上のほうに進むようにします。V型カーブの動きにしようと思ったら、ここでeyv.cntの値を一気に変えればいいのです。この敵はU型カーブの動きをするように、eyv.cntの値を徐々に変化させています。

弾を撃つタイミングは、y軸座標によって決めています。だいたいカーブの頂点当りで乱射するようになっています。

もっと複雑な動きをさせたい場合、et.cnt(生成からのフレーム数)が非常に意味を持ってきます。これをうまく使えば、一定時間経過後自爆して弾をば らまく「機雷」とか、一定距離を移動した後静止して弾を撃ち再度動く、といった動きも可能になってきます。「Aeolianation」ではボスも含めれ ば30種類以上の敵を用意してあります。変数の名前は違いますが、基本は同じなので参考にして下さい。

「Aeolianation」のソースを 見る上での注意としては、大きさの違う敵の場合座標の調整を行っていることです。というのは、自機の座標(mx,my)にしても敵機の座標(ex,ey) にしてもそこを左上として画像を表示するために、大きさの異なるもの同士だと本来当り判定を行いたい位置とはずれが出てくるのです。

このことについては、敵と自機弾の当り判定の中で詳しく説明をします。

[解剖記]敵の生成と移動アルゴリズム1

敵の移動や硬さなどを制御する要素には、

  1. 画面に表示できる最大数
  2. x軸座標
  3. y軸座標
  4. x軸の移動量
  5. y軸の移動量
  6. 画面上に表示されているかどうか

最低限この6つが必要となってきます。このうち「画面上に表示されているかどうか」の要素を使って、「敵の固さ」という概念も導入しています。つまり、自機弾が何発か当らなければ倒せない、という敵を用意する事もできるわけです。

また、このサンプルではこれらの要素のほかに、

  • 敵の種類
  • 出現からのフレーム数

といった要素も用意してあります。ゲームをやってみると分かりますが、敵は全部で三種類あり、三種類とも動きや固さ・弾の発射タイミングなどが異なります。これを処理するためにはこういった要素も不可欠でした。


emax=50 ;敵の最大数

;■ 配列定義
dim ex,emax ;敵x軸座標
dim ey,emax ;敵y軸座標
dim ef,emax ;敵固さ・生死
dim en,emax ;敵種類
dim exv,emax ;敵x方向運動量
dim eyv,emax ;敵y方向運動量
dim et,emax ;敵出現からの時間

;■ メインルーチン
*main
--------------------------
--
 if frame\(25-level)=0 : gosub *enemyborn ;敵生成
gosub *enemymove ;敵移動
-------------------------
--
frame++
level=frame/500+1
if level>20 : level=20
goto *main


;■ 敵生成
*enemyborn
rnd temp,7
if temp=0 : return ;出現しない
repeat emax
if ef.cnt>0 : continue
;↑未使用の要素番号を調べる
if temp=1 : en.cnt=1 ;敵1
if temp=2 : en.cnt=1 ;敵1
if temp=3 : en.cnt=1 ;敵1
if temp=4 : en.cnt=2 ;敵2
if temp=5 : en.cnt=2 ;敵2
if temp=6 : en.cnt=3 ;敵3

ef.cnt=en.cnt ;固さ
rnd ex.cnt,640 ;初期出現位置x軸
ey.cnt=-50 ;初期出現位置y軸
exv.cnt=0 ;初期移動量x軸
eyv.cnt=10 ;初期移動量y軸
et.cnt=0
;↑出現からのフレーム数リセット
break
loop
return

まず、メインルーチンでどういったタイミングで敵を生成するかの 制御を行っています。このサンプルではレベルが上がっていくと生成頻度が高くなるような形になっています。これを、決まったタイミングでいつも同じように 出現するようにしようと思うと、全く違った形の処理(「Aeolianation」ではそういう処理をおこなっています)をしなければなりません。

続いて「敵生成」のルーチンで、どの敵を出現させるのかを 決め、その固さや初期出現位置などを決めます。ここでは乱数(変数tempに代入した値)によってそれを決めていますが、弱い敵1を多く出現させ、強い敵 3はあまり出現させないようにしています。見たままですが、(敵1:敵2:敵3)=(3:2:1)という出現頻度になっています。


;■ 敵移動
*enemymove
repeat emax
et.cnt++
if ef.cnt<1 : continue
----------------------

if en.cnt=1 : gosub *enemy1
if en.cnt=2 : gosub *enemy2
if en.cnt=3 : gosub *enemy3
loop
return


;■ 敵1
*enemy1
pos ex.cnt,ey.cnt : gcopy 2,250,0,50,50
dx=mx-ex.cnt
dy=my-ey.cnt

if dy<350 {
if dx<0 {
exv.cnt=exv.cnt-1
if exv.cnt<-25 : exv.cnt=-25
}
else {
exv.cnt=exv.cnt+1
if exv.cnt>25 : exv.cnt=25
}
}
if dx<0 : dx=-dx
if dx<50 : gosub *tamaborn
eyv.cnt=4
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt

if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0

return

ここでは敵の動きと弾発射のタイミングを決めています。まず、自機との距離(x軸の差とy軸の差)を求め、y軸方向の距離が350未満の時には、自機に接近する方向にx軸の移動速度を変化させています。この移動速度を一気に変えると恐ろしい動きをすることになってしまうので、だんだん変化させ、一定以上の速度にはならないようにしています。

続いて、自機とのx軸方向の距離(差を求めてから絶対値を出す)を調べ、自機との距離が50未満の時には弾生成ルーチンへ飛ぶようになっています。

・・・そしてここでもまたやっちゃってるんですが、先に表示してから移動をさせてしまっています。これをやってしまうと本来の位置と画像の表示位置が微妙にずれるので、当り判定をした時に「画像は当ってないのに当った」ということが起こってしまうので、移動をしてから表示するようにしましょう。悪い例です^^;

最後に、画面外へ消えたときは消滅させる処 理を行っています。画面外から戻ってくるようにするには、ここの値を変えたり処理しないようにすればいいのです。ただ、最終的には消滅させるようにしなけ れば、画面外に消えた敵がいつまでも残り、emaxで決めた値を超えるとそれ以上敵が出てこないようになってしまうので気をつけましょう。

[解剖記]自機移動と弾の発射

自機移動に関しては「奮闘記」で細かく解説しているので、ここでは省略させて頂きます。

自機弾を制御する要素として、

  1. 画面に表示できる最大数
  2. x軸座標
  3. y軸座標
  4. 画面上に表示されているかどうか(発射されてるかどうか)

最低限この4つが必要となってきます。これに付け加え、

  • x軸の移動量
  • y軸の移動量

といった要素も必要な場合もあります。これは発射する弾によって移動速度が違ったり、斜めに飛ぶ弾を制御する場合には必須です。サンプルの場合、一定速度 でy軸方向にだけ飛ぶ弾なので、実は移動量の要素は必要が無いんですが、消しておくのを忘れたため残ってしまっています^^;

自機弾発射ルーチンでは、「弾の生成」、「弾の表示」、「弾の移動」などの処理を行っています。


wmax=3 ;自機弾最大数

dim wx,wmax ;自機弾のx軸座標
dim wy,wmax ;自機弾のy軸座標
dim wf,wmax ;自機弾のOn/Off
dim wyv,wmax ;自機弾のy軸移動距離
;↑このサンプルの場合必要なし^^;

;■ 自機移動
*jikimove
if mf>0 : stick pat,79
shottime-- ;(3)'
if mf>0 :
if (pat&64>0)&(shottime<0) : shot=1 ;(1)
xv=(pat>>2&1)-(pat&1)
yv=(pat>>3&1)-(pat>>1&1)
if mf>0 : mx=xv*move+mx : my=yv*move+my
if mx<0 : mx=0
if mx>590 : mx=590
if my<0 : my=0
if my>430 : my=430
pos mx,my : gcopy 2,xv*50+50,0,50,50
return

;■ 自機弾発射
*weapon
repeat wmax
if (shot=1)&(wf.cnt<1) { ;(2)
wf.cnt=1
wx.cnt=mx+12
wy.cnt=my-40
shot=0
shottime=1 ;(3)
snd 3
}
if wf.cnt<1 : continue ;(4)
pos wx.cnt,wy.cnt : gcopy 2,150,0,25,50
wy.cnt=wy.cnt-40 ;(5)
if wy.cnt<-50 : wf.cnt=0 ;(6)
loop
return

まずポイントになるのは shot 変数です。これによって弾を発射するか否かの判定を行っています(1)。ちなみに自機のシールド( 変数mf )がなくなってしまった場合はこの判定を行わないようにし、ゲームオーバー直前の自機が爆発している時には弾を撃てないようにしています。

次に repeat ~ loop 命令で、弾が発射できる場合( shot=1 )は使用されていない( = 画面に表示されていない = 変数wf が1ではない )要素番号を調べます(2)。そして、発射される位置を指定し、効果音を鳴らします(うるさいので切ってありますが^^;)。

次に、あまり連射できないようにするための制御を行います。(3)の変数shottime に代入する値を大きくすると、その分だけ連射ができなくなります。このサンプルの場合、前の弾を撃ってから2フレーム目に次の弾が撃てるようになります。毎フレーム shottime を減らす処理(3')を忘れないようにしましょう。

引き続いて、そのまま表示と移動の制御を行います。画面に表示されるべき弾( 変数wf =1 )を調べ(4)、gcopy 命令で画面に弾の画像をコピーします。

次に、弾の移動を 行います(5)。この処理は本当は、画面に弾を表示をする前に行ったほうがいいかもしれません。というのはこのサンプルの場合、画面に表示をした後に座標 を動かしているため、のちに行う当り判定の時、実際表示されている場所より先の場所でヒットしてしまうのです。画面の更新速度が速いのでほとんどわからな いかもしれませんが、実は弾がまだ表示されているのにヒットした効果が表示されています。移動してから表示すれば、実際の座標と表示されている座標が一致 するのでこのような現象は起きません。その場合、最初に表示する位置( my-40 )を若干調整しましょう。

最後に、画面の上端から見えなくなったときに消す処理を行います(6)。これを忘れてると次から弾が撃てなくなります^^;

試しに、wmaxの値や、shottimeの値、(5)でwy.cntから引く値などを変えてみて下さい。たくさん撃てるけど異様に遅い弾とか、全然連射ができないとか、いろんな調整ができます。自分が作ろうと思ってるゲームにあわせて調整しましょう。

[解剖記]プログラムの構造

部分部分の解説に移る前に、プログラムの構造の基本的な事をお話しておきましょう。

鑑賞系のものを除けば、ほとんどのプログラムは次のような流れで成り立っています。

ユーザーからの入力 その結果がどうなったかを判定 画面に表示(音声の出力)

この一連の流れを、シューティングゲームのようなリアルタイムで進行するプログラムの場合、秒間に何十回というすさまじい速さで更新していく事になりま す。よく「FPS」というのが表示されているのを見かけますが、これは「Frame Par Second」の略で、要するに「1秒間に何コマ書き換えをしているか?」という数字になります。一般的なTVアニメの場合、秒間16コマの書き換えを 行っています。このサンプルゲームの場合動作環境にもよりけりですが、秒間30コマ(30FPS)の書き換えを行うようになってます。この書き換え数が大 きければ大きいほど、滑らかな動きを表現する事ができます。

その一連の流れを制御しているのが、メインルーチンになります。


;■ メインルーチン
*main
redraw 2
color bgcolor,0,0 : bgcolor=0
boxf 0,0,640,480

gosub *message ;ゲーム情報
gosub *haikei ;星の描写
gosub *jikimove ;自機移動
gosub *weapon ;自機弾
if frame\(25-level)=0 :
gosub *enemyborn ;敵生成
gosub *enemymove ;敵移動
gosub *tekitamamove ;敵弾移動
if pat&128>0 :
gosub *pausegame ;ポーズ
getkey returntop,121 : if returntop=1 : end ;強制終了(F10)
if mf<1 : mxv=0 : myv=0 : gameovertime++ :
gosub *bomb
;↑ゲームオーバー寸前
if gameovertime>100 : goto *gameover ;ゲームオーバー
await 30
redraw 1

frame++
level=frame/500+1
if level>20 : level=20

goto *main

見て頂ければ分かると思いますが、このメインルーチンでは、画面の消去・再表示(赤い部分)以外は、ほとんどが gosub 命令でサブルーチンにジャンプするようになってます。ポイントとなるのは「どういう順番で画面に表示するか?」で す。例えば自機の移動より後に星の描写を行うと、自機の手前を星が流れるようになってしまいます。敵の移動より前に敵弾の移動を行うと、敵の影に弾が隠れ て見えないようになってしまいます。様々な処理をサブルーチン化するのは、デバッグをし易くする(どの部分でエラーになったのかがわかりやすい)というの と、画面表示の順番を簡単に制御できるという事が大きいと思います。

ちなみにこのメインルーチンでは、ダメージを受けた場合に画面を赤くする(bgcolor変数で制御)処理と、時間経過(frame変数)によってレベルが上がっていく(level変数)という処理F10を押すと強制終了するという処理も行っています。

[解剖記]これがサンプルのソースだっ

サンプルスクリプト・素材のダウンロード

さて、いよいよ始まりました「解剖記」。この解説は、ある程度HSPの命令に関しては覚えてる人を対象にしてます。だからいちいち命令に関しての解説はし ません。でも、この部分ではこういうことをやってるんだよ、みたいな解説をしていくつもりなんで、初めてHSPやる人が「シューティングってどうやって作 るんだろ?」って思ったときに見てもなるべくわかるようにはするつもりです。では能書きはここまでにして、サンプルのソースを見てみることにしましょう。


; シューティングゲームサンプル
; by たかのん
; HSP v2.5 で作成されています
; 敵と自機の距離によって画面に倍数が表示されます。
; この倍数は、距離が近いほど大きくなります。


;■ 初期設定
screen 0,640,480,1
title "Sample Shooting by たかのん"
highscore=5000
*gamestart
cls 4
randomize
move=5 ;自機の移動速度を指定
smax=50 ;星の最大数
emax=50 ;敵の最大数
tmax=200 ;敵弾最大数
wmax=3 ;自機弾最大数
imax=10 ;倍数表示最大数
vmax=10 ;情報表示最大数
bmax=10 ;爆風最大数
level=1 ;難易度


;■ 配列定義
dim sx,smax ;星のx軸座標
dim sy,smax ;星のy軸座標
dim sc,smax ;星の色
dim ss,smax ;星の移動速度

dim ex,emax ;敵x軸座標
dim ey,emax ;敵y軸座標
dim ef,emax ;敵固さ・生死
dim en,emax ;敵種類
dim exv,emax ;敵x方向運動量
dim eyv,emax ;敵y方向運動量
dim et,emax ;敵出現からの時間

dim tx,tmax ;敵弾x軸座標
dim ty,tmax ;敵弾y軸座標
dim tf,tmax ;敵弾on/off
dim txv,tmax ;敵弾x方向運動量
dim tyv,tmax ;敵弾y方向運動量

dim wx,wmax ;自機弾のx軸座標
dim wy,wmax ;自機弾のy軸座標
dim wf,wmax ;自機弾のOn/Off
dim wyv,wmax ;自機弾のy軸移動距離

dim ix,imax ;倍数x軸座標
dim iy,imax ;倍数y軸座標
dim iff,imax ;倍数表示時間
dim iv,imax ;倍数フォントサイズ

dim bx,bmax ;爆風x軸座標
dim by,bmax ;爆風y軸座標
dim bf,bmax ;爆風表示時間


;■ バッファへ画像の読みこみ
buffer 2,640,480,1
pos 0,0
picload "sample.bmp",1


;■ 効果音とBGMの読みこみ
sndload "Int01.mid",0,1
sndload "dmg.wav",1,0
sndload "exp.wav",2,0
sndload "shot.wav",3,0
sndload "kin.wav",4,0


;■ 星の準備
repeat smax
rnd sx.cnt,640
rnd sy.cnt,480
rnd sc.cnt,8
ss.cnt=sc.cnt+3
sc.cnt=sc.cnt*32+31
loop


;■ 平方根の準備
dim calc,900
repeat 900
temp=cnt : calc.cnt=temp*temp
loop


;■ スタート前準備
mx=295 : my=400 ;自機の初期位置を指定
mf=8 ;自機の初期シールドを指定
gameovertime=0
score=0
frame=0
highdst=0
snd 0
gsel 0,1
palcopy 2
gmode 2

;■ メインルーチン
*main
redraw 2
color bgcolor,0,0 : bgcolor=0
boxf 0,0,640,480
gosub *message ;ゲーム情報
gosub *haikei ;星の描写
gosub *jikimove ;自機移動
gosub *weapon ;自機弾
if frame\(25-level)=0 : gosub *enemyborn;敵生成
gosub *enemymove ;敵移動
gosub *tekitamamove ;敵弾移動
if pat&128>0 : gosub *pausegame ;ポーズ
getkey returntop,121
if returntop=1 : end ;強制終了(F10)
if mf<1 : mxv=0 : myv=0 : gameovertime++ : gosub *bomb
if gameovertime>100 : goto *gameover
await 30
redraw 1
frame++
level=frame/500+1
if level>20 : level=20
goto *main


;■ 自機移動
*jikimove
if mf>0 : stick pat,79
shottime--
if mf>0 : if (pat&64>0)&(shottime<0) : shot=1
xv=(pat>>2&1)-(pat&1)
yv=(pat>>3&1)-(pat>>1&1)
if mf>0 : mx=xv*move+mx : my=yv*move+my
if mx<0 : mx=0
if mx>590 : mx=590
if my<0 : my=0
if my>430 : my=430
pos mx,my : gcopy 2,xv*50+50,0,50,50
return


;■ 背景描写
*haikei
repeat smax
color sc.cnt,sc.cnt,sc.cnt
sy.cnt=sy.cnt+ss.cnt
if sy.cnt>480 : sy.cnt=0 : rnd sx.cnt,640
pset sx.cnt,sy.cnt
loop
return


;■ 自機弾発射
*weapon
repeat wmax
if (shot=1)&(wf.cnt<1) {
wf.cnt=1
wx.cnt=mx+12
wy.cnt=my-40
shot=0
shottime=1
; snd 3
;効果音がうるさいので切ってあります^^;
}
if wf.cnt<1 : continue
pos wx.cnt,wy.cnt : gcopy 2,150,0,25,50
wy.cnt=wy.cnt-40
if wy.cnt<-50 : wf.cnt=0
loop
return


;■ 敵生成
*enemyborn
rnd temp,7
if temp=0 : return
repeat emax
if ef.cnt>0 : continue
if temp=1 : en.cnt=1
if temp=2 : en.cnt=1
if temp=3 : en.cnt=1
if temp=4 : en.cnt=2
if temp=5 : en.cnt=2
if temp=6 : en.cnt=3
ef.cnt=en.cnt ; 固さ
rnd ex.cnt,640 ; 初期出現位置x軸
ey.cnt=-50 ; 初期出現位置y軸
exv.cnt=0 ; 初期移動量x軸
eyv.cnt=10 ; 初期移動量y軸
et.cnt=0
break
loop
return


;■ 敵移動
*enemymove
repeat emax
et.cnt++
if ef.cnt<1 : continue
ent=cnt
repeat wmax
if wf.cnt<1 : continue
ddx=(ex.ent+25)-(wx.cnt+12)
if ddx<0 : ddx=-ddx
ddy=(ey.ent+25)-(wy.cnt+25)
if ddy<0 : ddy=-ddy
if (ddx>=0)&(ddx<20)&(ddy>=0)&(ddy<40) {
ef.ent-- : wf.cnt=0
if ef.ent>0 {
pos wx.cnt,wy.cnt
gcopy 2,175,25,25,25
snd 4
score++
}
else {
pos ex.ent,ey.ent
gcopy 2,200,0,50,50
snd 2
gosub *messageborn
}
}
loop
if en.cnt=1 : gosub *enemy1
if en.cnt=2 : gosub *enemy2
if en.cnt=3 : gosub *enemy3
loop
return

;■ 敵1
*enemy1
pos ex.cnt,ey.cnt : gcopy 2,250,0,50,50
dx=mx-ex.cnt
dy=my-ey.cnt
if dy<350 {
if dx<0 {
exv.cnt=exv.cnt-1
if exv.cnt<-25 : exv.cnt=-25
}
else {
exv.cnt=exv.cnt+1
if exv.cnt>25 : exv.cnt=25
}
}
if dx<0 : dx=-dx
if dx<50 : gosub *tamaborn
eyv.cnt=4
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
return


;■ 敵2
*enemy2
pos ex.cnt,ey.cnt : gcopy 2,300,0,50,50
dx=mx-ex.cnt
if et.cnt<2 {
if dx<0 {
exv.cnt=-6
}
else {
exv.cnt=6
}
}
if et.cnt>25 : eyv.cnt-- : if eyv.cnt<-10 : eyv.cnt=-10
if ey.cnt>200 : gosub *tamaborn
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
return;


■ 敵3
*enemy3
pos ex.cnt,ey.cnt : gcopy 2,350,0,50,50
dx=mx-ex.cnt
if et.cnt<2 {
if dx<0 {
exv.cnt=-3
}
else {
exv.cnt=3
}
}
if ey.cnt>200 : eyv.cnt--
if et.cnt>20 : if ey.cnt<50 : eyv.cnt++
if ey.cnt>200 : gosub *tamaborn
if et.cnt>20 : if ey.cnt<50 : gosub *tamaborn
ex.cnt=ex.cnt+exv.cnt
ey.cnt=ey.cnt+eyv.cnt
if ex.cnt<-50 : ef.cnt=0
if ex.cnt>640 : ef.cnt=0
if ey.cnt<-50 : ef.cnt=0
if ey.cnt>480 : ef.cnt=0
return


;■ 弾生成
*tamaborn
if frame\(8-(level/4))>0 : return
dx=mx-ex.cnt : px=dx : if dx<0 : dx=0-dx
dy=my-ey.cnt : py=dy : if dy<0 : dy=0-dy
dst=(dx*dx)+(dy*dy)
edx=ex.cnt+12 : edy=ey.cnt+12
repeat 900
if dst<calc.cnt : dst=cnt : break
loop
repeat tmax
if tf.cnt>0 : continue
tf.cnt=1
tx.cnt=edx : ty.cnt=edy
rnd ds,8 : ds=level/5+3+ds
txv.cnt=px*ds/dst
tyv.cnt=py*ds/dst
break
loop
return


;■ 敵弾移動 & 当り判定
*tekitamamove
repeat tmax
if tf.cnt<1 : continue
tx.cnt=tx.cnt+txv.cnt
ty.cnt=ty.cnt+tyv.cnt
if tx.cnt>640 : tf.cnt=0 : continue
if tx.cnt<-25 : tf.cnt=0 : continue
if ty.cnt>480 : tf.cnt=0 : continue
if ty.cnt<-25 : tf.cnt=0 : continue
pos tx.cnt,ty.cnt : gcopy 2,175,0,25,25
dx=(mx+25)-(tx.cnt+12) : if dx<0 : dx=-dx
dy=(my+25)-(ty.cnt+12) : if dy<0 : dy=-dy
if (dx>=0)&(dx<10)&(dy>=0)&(dy<10) {
repeat tmax : tf.cnt-- : loop
pos tx.cnt,ty.cnt
gcopy 2,175,25,25,25
bgcolor=128
snd 1
mf--
}
if (dx>=10)&(dx<40)&(dy>=10)&(dy<40) : score++
loop
return


;■ 倍率表示
*messageborn
dmy=my-ey.ent : if dmy<0 : dmy=-dmy
dstv=480-dmy/50
if highdst<dstv : highdst=dstv
dstlevel=level
dstcore=dstv*dstlevel
score=en.ent*100*dstcore+score
repeat imax
if iff.cnt>0 : continue
ix.cnt=ex.ent
iy.cnt=ey.ent
iv.cnt=dstv
iff.cnt=20
il.cnt=dstlevel
realscore=en.ebt*100
break
loop
return

;■ ゲーム情報
*message
repeat imax
if iff.cnt<1 : continue
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
font "MSゴシック",iv.cnt*2+8,1
pos ix.cnt,iy.cnt
mes "× "+iv.cnt
iy.cnt++
iff.cnt--
loop

font "MSゴシック",12,3
if highscore<score : highscore=score
color 0,255,255
pos 10,10 : mes "HIGHSCORE "+highscore

color 255,255,255
pos 10,30 : mes "SCORE "+score

color 0,255,0
pos 10,50
mes "HIGH DISTANCE "+highdst

color 255,255,0
pos 10,70
mes "LEVEL "+level

color 255,255,255
pos 10,460
mes "SHIELD "
if mf<1 : return
repeat mf
color 255,cnt*32,0
pos cnt*10+60,460
mes "■"
loop
return


;■ 自機爆風
*bomb
repeat bmax
bf.cnt--
if bf.cnt<1 {
rnd bf.cnt,10 : bf.cnt=bf.cnt+10
rnd bx.cnt,50 : bx.cnt=25-bx.cnt+mx
rnd by.cnt,50 : by.cnt=25-by.cnt+my
}
pos bx.cnt,by.cnt
gcopy 2,200,0,50,50
rnd dbx,10 : dbx=dbx-5
bx.cnt=bx.cnt+dbx
by.cnt=by.cnt+10
loop
rnd effect,10
if effect=0 : snd 1 : bgcolor=128
if effect=1 : snd 2 : bgcolor=255
return


;■ ポーズ処理
*pausegame
redraw 2
font "MSゴシック",40,3
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
pos 250,200 : mes "PAUSE"
stick pat : if pat&128>0 : return
getkey returntop,121
if returntop=1 : end ; 強制終了(F10)
await 30
redraw 1
goto *pausegame


;■ ゲームオーバー
*gameover
redraw 2
font "MSゴシック",40,3
rnd icr,8 : icr=icr*32+31
rnd icg,8 : icg=icg*32+31
rnd icb,8 : icb=icb*32+31
color icr,icg,icb
pos 200,200 : mes "GAME OVER"
pos 30,250 : mes "PUSH RETURN TO RESTART"
stick pat
if pat&32>0 : sndoff : wait 10 : goto *gamestart
getkey returntop,121
if returntop=1 : end ; 強制終了(F10)
await 30
redraw 1
goto *gameover

簡単に、見やすいように作ったつもりですが、長いです。非常~に長いです。HSP詳しい人が見たら「無駄が多いなぁ・・・」と思われてしまうこと間違い無しです。でも、半日で作ったんだから許して。変数の整理なんか全然やってないんだから^^;

・・・さて、次章からは、これを解剖して解説していく事にします。

[奮闘記]2点間の距離を算出するのだ

だ~いぶ間があいてしまったんで、題材がすっとんじゃってますけど、あんま気にしないで下さい(^^ゞ

今作成中の『Distance』というシューティングゲームのメインコンセプトは「自機から近い位置で倒すほど得点倍率が高い」というもの。ということは、必然的に自機と敵との間の距離を算出しなきゃいけなくなってくる。ちょっと下の図を見ていただきたい。

要は、(mx,my)~(ex,ey)の距離を出したいのである。ここで数学の知識が必要となってくるわけだが、直角三角形の場合dxとdyの長さがわかってれば斜め辺の長さは求めることができる。それがいわゆる三平方の定理である(すっかり忘れてた^^;)。

2=dx2+dy2

つまり、(dx2+dy2)の平方根を求めれば、2点間の距離が算出できるというわけだ。しかし、これには致命的な問題点がある。HSP2.4には標準状態のままだと平方根を求める命令がないのである。もちろん拡張dllなんかでその命令を追加することはできるが、別にそこまでして正確な平方根を求めようと思わないのなら、基本命令だけでもなんとかできちゃうのである。もちろんけっこう値はいいかげんなんで、正確に求めたいなら拡張dllを使いましょう。


; 三平方の定理を利用した距離算定スクリプト

screen 0,640,480,1,50,0 : title "2点間のだいたいの距離"
mx=320 : my=240


; ■■■■■ 要素番号の2乗を格納
*start
dim y,900
repeat 900
x=cnt : y.cnt=x*x
loop

goto *main


; ■■■■■ メイン
*main
redraw 2
color 255,255,255 : boxf 0,0,640,480

gosub *dotmove
gosub *measure

redraw 1
await 16
goto *main


; ■■■■■ 距離の算出
*measure
dx=320-mx : if dx<0 : dx=-dx
dy=240-my : if dy<0 : dy=-dy
dst=(dx*dx)+(dy*dy)

repeat 900
if dst<y.cnt : dst=cnt : break
loop

return


; ■■■■■ 点の移動と画面描写
*dotmove
stick pat,79
if pat&128>0 : end
if pat&64>0 : move=4 : else : move=1
mxv=(pat>>2&1)-(pat&1)
myv=(pat>>3&1)-(pat>>1&1)
mx=mxv*move+mx : my=myv*move+my
if mx<0   : mx=0
if mx>640 : mx=640
if my<0   : my=0
if my>480 : my=480

color 192,192,192
pos 320,0 : line 320,480
pos 0,240 : line 640,240
color 0,0,0
pos 320,240 : line mx,my
boxf 319,239,321,241
boxf mx-1,my-1,mx+1,my+1
cx=mx-320 : cy=-my+240
pos 0,0  : color 0,0,0 : mes "座標:("+cx+","+cy+")"
pos 0,20 : mes "距離:"+dst+"pt"
return

※CTRLを押しながらだと、点が素早く動く。

このスクリプトのポイントは3点。

  1. あらかじめ2乗の値を配列に格納しておく
  2. 2点のx,y座標の差(dx,dyの絶対値)を求め、その2乗を足す( → dst )
  3. dstと格納しておいた配列とを比較し、配列がdstより大きくなったらその要素番号が距離になる

要は、用意しておいた値と比較するっていうところが今回のミソ。この方法だと正確な値を求めることはできないけど、だいたいの距離を把握したいのなら必要にして充分。拡張dll使って平方根求めると、特に必要のない(と言うよりむしろ邪魔)な小数点以下まで出てきちゃうしね。

ちなみに乗数を900個用意したのは、640x480の斜めの長さは約832だから。つまり、これだけ用意しておけば640x480のウィンドウをややは み出すような2点間の距離も求められるってわけ。今回のスクリプトの場合中心からの距離を求めてるから、本当は400個も用意しておけば充分。例えば10 段階程度で距離を求めたいなら、10個用意すれば充分なわけで、処理速度にもそれほど影響はない。

[奮闘記]実はそんなに難しいことではなかった、カーソルで動くわけ

再度確認の意味で、例の部分を書き出しましょ。


*JIKI
STICK PAT,15
XV=(PAT>>2&1)-(PAT&1)
YV=(PAT>>3&1)-(PAT>>1&1)

MX=XV*MOVE+MX : MY=YV*MOVE+MY
IF MX<0   : MX=O
IF MX>566 : MX=566
IF MY<0   : MY=0
IF MY>364 : MY=364
POS MX,MY : GMODE 2 : GCOPY 2,0,0,32,32
RETURN

※ MX , MY ・・・ 自機の座標
※ MOVE ・・・ 自機の移動速度

んで、STICK命令で返される値が次の通りね。

1 2 4 8

じゃ、これをそのまま2進法で表記してみましょ。

%0001 %0010 %0100 %1000

全部違う桁の数字が1になってるよね?要はこれがスイッチなわけ。一番右の桁 は「←」のスイッチ、右から2つ目の桁は「↑」のスイッチになってると。そう考えると、「←↑」同時押しの場合でも、ちゃんと異なった値が出てくるのって むしろ自然なことなんだよね。仮に日本語106キーボード全てのキーを全ての組み合わせで判定させようと思ったら、2進法で106桁あれば全部の組み合わ せが判定できるわけだ。

・・・ん?2進法で106桁???

2106=81129638414606681695789005144064

・・・コンピュータって偉いね^^;

[奮闘記]計算式と条件式(その1)

しばらく更新が停滞してたけど、手をつけてなかったわけじゃないです。結構思ったとおりにいろんなことができるようになったんで、製作に熱中してたのだ^^;

っつーわけで(どういうわけだ?)、今回のお題は「条件式と計算式」。数学嫌い~(T-T)などと言ってるわけにもいかないのだ。もちろん今までのプログラムでも、計算式とか条件式は使ってきた。でも、ちょっとした工夫で、式を短くしたり複雑なことができるようになるのだ。

例えば「変数Aに1づつ加えていき、Aが20になったら0に戻す、ということを繰り返す」てのを普通に書くとこんな感じになる。


*TEST
A=A+1
IF A=20 : A=0

GOTO *TEST

この2行、ちょっと工夫すれば1行で書けるのだ。嘘ではない。本当である。下を見てみなさい。


*TEST
A=A+1 : IF A=20 : A=0
GOTO *TEST

うーん・・・確かに1行になってる・・・ってをい(-_-;

これは単なる冗談。本当は、こういうふうに書く。


*TEST
A=(A+1)\20
GOTO *TEST

以前「よく使い方がわからん」って言ってた『』。そう、これは 割り算の余りを算出する記号だ。要するに20で割り切れる場合ってのは、余りが0なわけだよな。一番最初、Aが0の場合から順番に考えてみんさい。0に1 足して20で割った余りがAに代入されるから、A=1になるでしょ?次には1に1足して20で割るから余りは2。次には2に1足して20で割るから余りは 3、、、、、。嘘だと思ったら、この行の次に「PRINT A」って追加してみな。0~19の数字が順番に並ぶから。

え?これをどう使うかって?そりゃーあんた、考えてみなって(なんか偉そう^^;)。例えばあるループ中で、数回に一回だけ処理したいことがあったとしよ う。例えば「自機に弾を撃たせる条件」なんかを考えてみるとわかりやすいかな?あんまり連射ができないようにしたいなら、3~4回に一回だけ弾を撃たせる ようにすれば良いわけだ。そういう場合、今までだったらこういう感じに書いてたと思う。


*JIKI
STICK PAT,15
T=T+1 : IF T=4 :  SHOT=1 : T=0
XV=(PAT>>2&1)-(PAT&1)
YV=(PAT>>3&1)-(PAT>>1&1)
MX=XV*MOVE+MX : MY=YV*MOVE+MY
IF MX<0   : MX=O
IF MX>566 : MX=566
IF MY<0   : MY=0
IF MY>364 : MY=364
POS MX,MY : GMODE 2 : GCOPY 2,0,0,32,32
RETURN

んで、「SHOT=1」の場合だけ弾を撃たせるようにすればいいわけだ。でもこれを『¥』を使って書くとこうなる。


*JIKI
STICK PAT,15
T=(T+1)\4 : IF T=0 : SHOT=1
XV=(PAT>>2&1)-(PAT&1)
YV=(PAT>>3&1)-(PAT>>1&1)
MX=XV*MOVE+MX : MY=YV*MOVE+MY
IF MX<0   : MX=O
IF MX>566 : MX=566
IF MY<0   : MY=0
IF MY>364 : MY=364
POS MX,MY : GMODE 2 : GCOPY 2,0,0,32,32
RETURN

まぁあんまり短くなってないけど、Tを0に戻す手間は省けてるよね。これがもっと複雑なことになってくると、重宝するような気がしない?「T=0」っての を忘れてて、一回撃ったら次から二度と撃てないなんていうマヌケなことやっちゃうのを防ぐこともできるし(そういうのやるの俺だけ?^^;)

[奮闘記]ちゃんと自機がカーソルに従って動いた理由の解析

確認の意味で、謎だった部分を書き出してみましょ。「暗記しましょう」はいいんだけど、訳がわからないと悔しいもんね^^;


*JIKI
STICK PAT,15
XV=(PAT>>2&1)-(PAT&1)
YV=(PAT>>3&1)-(PAT>>1&1)

MX=XV*MOVE+MX : MY=YV*MOVE+MY
IF MX<0   : MX=O
IF MX>566 : MX=566
IF MY<0   : MY=0
IF MY>364 : MY=364
POS MX,MY : GMODE 2 : GCOPY 2,0,0,32,32
RETURN

※ MX , MY ・・・ 自機の座標
※ MOVE ・・・ 自機の移動速度

んで、STICK命令で返される値が次の通りね。

1 2 4 8

ここで注意なのは、同時に押された場合。←→が同時に押されてる場合、返される値は「 5 」になる。↑→なら「 6 」だね。論理演算は2進法で考えざるを得ないんで、全部の組み合わせを2進法になおして表記してみましょう。

- %0000 0
%0001 1
%0010 2
←↑ %0011 3
%0100 4
←→ %0101 5
↑→ %0110 6
←↑→ %0111 7
%1000 8
←↓ %1001 9
↑↓ %1010 10
←↑↓ %1011 11
→↓ %1100 12
←→↓ %1101 13
↑→↓ %1110 14
←↑→↓ %1111 15

見事に全部違う値が返されました。これはあくまで想像だけど、STICKの命令で返される値は、全部の組み合わせが違う数値になるんじゃないかな?こんなこと考える人って天才か変態かどっちかだと思う。あっしのような凡人にはとても真似できない^^;

閑話休題

さて、←↑が押された場合ってのを考えてみましょう。この場合「PAT=3」なので、この2行はこんな感じになる。


XV = (%0011)>>2 & %0001 - %0011 & %0001
YV = (%0011)>>3 & %0001 - (%0011)>>1 & %0001

XV = %0000 & %0001 - %0011 & %0001
YV = %0000 & %0001 - %0001 & %0001

XV = %0000 - %0001
YV = %0000 - %0001

はい、見事にXVもYVも-1になりました。はっきり言って、お見事!の 一言です。さすがに全部の組み合わせを試してみる気にはなれないけど、完璧に動いてるってことは、全ての組み合わせがちゃんとこういう感じになるってこと ですね。ちなみに←→っていうような、相反する組み合わせで押された場合でも、ちゃーんとx軸の運動量は0になります。つまり動かないっつーこと。こう やって論理演算で処理させてるから、キビキビとカーソルに反応するプログラムにできるんですね。CPUに優しく、操作する人にも優しく、プログラムを組む 人には厳しく・・・(爆)

[奮闘記]数式ってのはこういうものなのだ

今回・次回の解説には、OXさん・ToYさん・MIAさん・Ma_Tsさんなどの多大なご協力を頂きました。この場を借りて御礼申し上げます。深謝。

じゃぁ、マニュアルの「数式」のとこに書いてある順番に見てきましょう。なんかそのうち中学・高校で使ってた数学の教科書引っ張り出さなきゃいけなくなりそうだな^^;



10進整数 -2147483647  ~  2147483647
16進整数 $0  ~  $FFFFFFFF
2進整数 %0  ~  %111111...

10進数字→2進数字とか、16進数字→10進数字を暗算でやれって言われると「ごめんなさい」なんだけど、まぁ意味はわかる。要はこういうことでしょ?

2進法 10進法 16進法
%1 1 $1
%101101 45 $2D
%1110011 115 $72
%11111111 255 $FF

Windowsに標準で入ってる「電卓」を使いました。プログラマに関数電卓が必須だっての、よくわかるね・・・こんなの手計算じゃやっとれん^^;



文字コード(1バイト) 'A'

最初意味がよくわからんかったのだけど、やってみて納得。それぞれの文字が持ってるコード番号ってのがあって、それがそのまま入るらしい。例えば 'a'=97 、 'A'=65 っていう感じ。全角文字は調べられないのかな~と思ったら、できちゃった。 'あ'=-126 だった。入力した文字を調べて何か計算する(占いとか暗号とかに使えるかな?)ような使い方ができるのかな?



加減乗除算 + - * /

これは特に問題なし。ところでこれはHSPの特殊な仕様らしいんだけど、計算式は常に左から順番に処理されるん だって。乗除算の優先は無いそうな。優先させたかったら()をつけるってのは同じらしいんだけど。例えば「A=1+5*2」は「A=12」と同じ。数学的 に正しく計算すれば「A=11」だもんね。そういうふうにしたかったら「A=1+(5*2)」というような感じで記述しなきゃいけないそうだ。



論理演算(and) &
論理演算(or) |
論理演算(xor) ^

今まで一番わからなかった部分。要は2進法で考えなきゃいけないそうだ。

例1: 12&20
12 %01100
20 %10100
結果 %00100

ってことで、結果は「4」になる。これが信じられない人は、スクリプトエディタで下のように書いてみてくだされ。F5を押すと「4」と表示されるはずだぁ。


PRINT 12&20
STOP

つまり、「and」の計算は、2進法に直して、同じ位置のどちらにも「1」があれば、「1」になるということ。「論理積」と言うそうな。縦に見るとわかりやすいね。

例2: 12|20
12 %01100
20 %10100
結果 %11100

今度の結果は「28」。信じられない人は、上のプログラムの「&」を「|」に書き換えてF5を押してみなはれ。「or」の計算は、2進法に直して、同じ位置のどちらかに「1」があれば「1」になると。「論理和」と言うそうな。

例3: 12^20
12 %01100
20 %10100
結果 %11000

今度の結果は「24」。同じ位置の数字が違ったら結果は「1」になるそうな。「排他的論理和(xor)」と言って、あまり見かけないけど暗号化なんかに使えるらしい。

彼ら(笑)の使い道ってすぐには思いつかないんだけど、元々CPUの根っこでは2進法で計算してるんだから、人間様は苦労するけどコンピュータだと処理が速くなるそうな。得意な方法でやらせてあげましょうってことだね・・・。



割り算の余り \

こいつは簡単。例えば 4\3=1って感じだね。ただし、何に使うのかは見当がつかん^^;



条件式(同じ) =
条件式(小さい) <
条件式(以下) <=
条件式(以上) >=
条件式(大きい) >
条件式(≠) !

これらはほとんどIF文で使うやつだね。「IF x>600 : x=600」っていう感じだな。



左方向へビットシフト <<
右方向へビットシフト >>

これはマジで、きちんと勉強した人じゃなきゃわかんないよぉ。要は、「2のn乗を掛ける」ということらしい。左とか右ってなんのこっちゃ?って思ってたんだけど、要は2進法で考えるらしい。

例1: 50>>1
50 %00110010
結果 %00011001

これは「 50×2-1 」ということになるから、「 50/2 」と結果は同じ。2進法に直すと、見事に右側にずれてる。これも論理演算と同様、2進法で人間様は苦労するけどコンピュータは楽チンになるそうな。

例2: 50<<2
50 %00110010
結果 %11001000

これは「 50×22 」だから、「 50*4 」と結果は同じ。2進法で考えると、今度は左に2つずれてるのがわかるでしょ?


さぁここまで来れば、あの謎の2行の意味がわかってくるぞぉ。解析は次回。

[奮闘記]配列を使って星の動きを制御するのだ

実は、今までのやり方の応用で多重スクロール(速度の異なったスクロールを重ね合わせる)をやってみたのだが、めったくそ遅い(T-T)/これに加えて、敵キャラ表示したり当り判定の処理をやったり、、、なんてこと想像もしたくなかったので、別のやり方を模索することにした。なんでも、敵キャラのデータを管理するのには「配列変数」というのを使うと便利らしい。いずれやらなきゃならんのなら、単純に制御できるもので勉強してみようっつーことで、今回のお題が決定。


;初期設定
SCREEN 0,600,400,1
CLS 4
RANDOMIZE
MX=300 :MY=200 ;自機の初期位置を指定
MOVE=4 ;自機の移動速度を指定
SMAX=100 ;星の最大数

;配列定義
DIM X,SMAX ;星のx軸座標
DIM Y,SMAX ;星のy軸座標
DIM C,SMAX ;星の色
DIM S,SMAX ;星の移動速度

;バッファー2に自機の画像を描きこみ
BUFFER 2,600,400,1
POS 0,0 : PICLOAD "myship.bmp"

;星をランダムに表示
GSEL 0 : COLOR 0,0,0 : BOXF 0,0,600,400
REPEAT
SMAX
RND X.
CNT,600
RND Y.CNT,400
RND C.CNT,3 : C.CNT=(C.CNT+1)*64-1
RND S.CNT,4 : S.CNT=S.CNT+1
COLOR C.CNT,C.CNT,C.CNT
PSET X.CNT,Y.CNT
LOOP

;--------------
;メインルーチン
;--------------
*MAIN
REDRAW 2
COLOR 0,0,0 : BOXF 0,0,600,400
GOSUB *HAIKEI
GOSUB *JIKI
REDRAW 1
AWAIT 16
GOTO *MAIN


;流れる星を表示するためのサブルーチン
*HAIKEI
REPEAT SMAX
COLOR C.CNT,C.CNT,C.CNT
Y.CNT=Y.CNT+S.CNT
IF Y.CNT>400 :Y.CNT=0
PSET X.CNT,Y.CNT
LOOP
RETURN


;自機を動かすためのサブルーチン
*JIKI
STICK PAT,15
IF PAT=128 : END
XV=(PAT>>2&1)-(PAT&1)
YV=(PAT>>3&1)-(PAT>>1&1)

MX=XV*MOVE+MX : MY=YV*MOVE+MY
IF MX<0   : MX=O
IF MX>566 : MX=566
IF MY<0   : MY=0
IF MY>364 : MY=364
POS MX,MY : GMODE 2 : GCOPY 2,0,0,32,32
RETURN

ふむ、、、今までに比べると、なんか短くなったような気がする。特に失敗作(遅すぎる)の多重スクロールのプログラムなんて、この2倍ぐらいの長さだもんな(笑)

DIM (配列を割り当てる変数名),(要素の最大数)
指定した数の要素を持つ配列変数を作成
使うとき (変数名).(要素番号)

ちなみに変数名と要素番号の間は.(ピリオド)ね。

今までプログラムの本とか見ても、何に使うか一番わからなかったのがこの配列。でも仮に配列を使わないで、同じように星を100個制御しようと思うと、変 数をめちゃめちゃいっぱい作らなきゃいけなくなるということのようだ。今回の場合、星の配置・明るさ・移動速度をランダムに設定してるから、配列変数を使 わないと「;星をランダムに表示」のところはこんな感じになる。


;星をランダムに表示
GSEL 0 : COLOR 0,0,0 : BOXF 0,0,600,400
RND X0 ,600 : RND Y0 ,400 : RND C0 ,3 : RND S0 ,4
RND X1 ,600 : RND Y1 ,400 : RND C1 ,3 : RND S1 ,4
RND X2 ,600 : RND Y2 ,400 : RND C2 ,3 : RND S2 ,4
RND X3 ,600 : RND Y3 ,400 : RND C3 ,3 : RND S3 ,4


(中略)


RND X98,600 : RND Y98,400 : RND C98,3 : RND S98,4
RND X99,600 : RND Y99,400 : RND C99,3 : RND S99,4

こんなのやってられっか~~~(T-T)/
これを配列変数を使うことによって「n番目の要素」っていう感じに変数で処理ができるようになるというわけ。

んで、今回のミソ二つ目のシステム変数。一番上のプログラムで緑色になってるCNTってやつね。これはREPEAT~LOOPの中で、今は何回目のループなのか?っていうのを格納してるスグレモノ。正確に言うと「カウンター」を調べるものなので、一回目のループだとCNT=0になるらしい。だからX.CNTが意味するのは、Xのカウンター番目の要素ってことだな。要はREPEAT~LOOPを使うことによって、100行書く手間を省いてるというわけだ。これは便利。

さて、次のお題だけど、自機を操作するところで丸暗記^^;し た部分(青くなってる部分ね)、全く意味がわからなくて悔しかったんで、方々に尋ねてやっと理解できた。それを忘れないうちに脳味噌に叩き込んでやろうっ つーことで、ちょっと今までの奮闘記とは系統が異なるけど「解説」じみたことをやってみたいと思う。間違ってなきゃいいんだけど^^;

[奮闘記]流れる星空を背景に、自分が好きなように動かせるキャラを表示するのだ

よし、いよいよ自機を動かずぞ~!

と言っても、細かい計算なんかは先人の知恵を拝借した方が良さそうなんで、MIAさんのページに載ってた解説をそのまま利用させていただくことにしました。深謝。


SCREEN 0,600,400,1,100,50,600,400
CLS 4
RANDOMIZE
MX=300 :MY=200 ;自機の初期位置を指定
MOVE=2 ;自機の移動速度を指定

;バッファー3に星空の絵を描きこみ
BUFFER 3,600,400,1
COLOR 0,0,0 : BOXF 0,0,600,400
REPEAT 1000
RND X,600 : RND Y,400
RND R,7 : RR=(R+1)*32-1
RND G,7 : GG=(G+1)*32-1
RND B,7 : BB=(B+1)*32-1
COLOR RR,GG,BB
PSET X,Y
LOOP
BUFFER 4,600,400,1

;バッファー2に自機の画像を描きこみ
BUFFER 2,600,400,1
POS 0,0 : PICLOAD "myship.bmp"


;描画画面の準備
GSEL 0 : COLOR 0,0,0 : BOXF O,O,600,400
PALCOPY 3
POS 0,0 :GCOPY 3,0,0,600,400 ;星空を複写
POS MX,MY :GCOPY 2,0,0,32,32 ;自機画像を複写

;メインルーチン
*MAIN
REPEAT 400
REDRAW 0
GOSUB *HAIKEI
GOSUB *JIKI
REDRAW 1
STICK K : IF K=128 : END
AWAIT 16
LOOP
GOTO *MAIN

;流れる星を表示するためのサブルーチン
*HAIKEI
GMODE 1,600,400
GSEL 4
POS 0,0 : GCOPY 3,0,0,600,400 ;現在の画面を保存

GSEL 3
POS 0,0 : GCOPY 4,0,399,600,400
POS 0,1 : GCOPY 4,0,  0,600,399

GSEL 0
POS 0,0 : GCOPY 3,0,0,600,400
RETURN

;自機を動かすためのサブルーチン
*JIKI
STICK PAT,15
XV=(PAT>>2&1)-(PAT&1)
YV=(PAT>>3&1)-(PAT>>1&1)

MX=XV*MOVE+MX : MY=YV*MOVE+MY
IF MX<0   : MX=O
IF MX>566 : MX=566
IF MY<0   : MY=0
IF MY>364 : MY=364

POS MX,MY : GMODE 2 : GCOPY 2,0,0,32,32
RETURN

なんと今回は、新しい命令一つもなし。そのかわり、何やら難しげな条件判断が(赤い部分)。ここはMIAさん曰く、「こういうふうにやるもんだと暗記しちゃいましょう」とのこと。要は変数PATを調べて、自機の移動方向を決定するための公式らしい。で、次の行で自機の座標(MX,MY)を決定すると。移動速度をわざわざ変数(MOVE)にしたのは、後々スピードアップアイテムを登場させるため。なんて読みが深いんでしょ(笑)

あと、緑の部分の条件判断は、自機がウィンドウからはみださないようにするため。ウィンドウサイズが600×400なんで、右端と下端は若干少なめに設定してある。これは、カレントポジションを左上にした32×32のキャラを表示させてるから。

ちなみに最初に真っ黒な背景で動かしたら、自機のかけらが残っちゃったんだけど、今回の場合毎回背景の描きこみをしてるから、自動的に消えちゃうんだよね。なんか案ずるより生むが易しって感じ。無駄なことしないですむってのはいいよね。

[奮闘記]流れる星空を背景に、静止したキャラを表示するのだ

とりあえず早くキャラを動かしてみたいけど、慌てない慌てない。さっき作った流れる星空を背景に、まずは静止したキャラを表示させてみよう。ちなみに用意したキャラはこれ。

あれ?なんで縦向きなの?・・・実は横向きのキャラがうまく描けなかったのだ。だからそれにあわせてスクロール方向も縦方向に変えちゃったという^^;


SCREEN 0,600,400,1,100,50,600,400
CLS 4
RANDOMIZE

;バッファーに星空の絵を描きこみ
BUFFER 3,600,400,1
COLOR 0,0,0 : BOXF 0,0,600,400
REPEAT 1000
RND X,600 : RND Y,400
RND R,7 : RR=(R+1)*32-1
RND G,7 : GG=(G+1)*32-1
RND B,7 : BB=(B+1)*32-1
COLOR RR,GG,BB
PSET X,Y
LOOP
BUFFER 4,600,400,1 ;星空を保存するための画面を準備

;バッファーに自機の画像を描きこみ
BUFFER 2,600,400,1
POS 0,0 :
PICLOAD "myship.bmp"
MX=300 :MY=200 ;自機の初期位置を指定

;描画画面の準備
GSEL 0 : COLOR 0,0,0 : BOXF O,O,600,400
PALCOPY 3
POS 0,0 :GCOPY 3,0,0,600,400 ;星空を複写
POS MX,MY :GCOPY 2,0,0,32,32 ;自機画像を複写

;メインルーチン
*MAIN
REPEAT 400
REDRAW 0
GOSUB *HAIKEI
GOSUB *JIKI
REDRAW 1
STICK K : IF K=128 : STOP
AWAIT 16
LOOP
GOTO *MAIN

;流れる星を表示するためのサブルーチン
*HAIKEI
GMODE 1,600,400
GSEL 4
POS 0,0 : GCOPY 3,0,0,600,400 ;現在の画面を保存

GSEL 3
POS 0,0 : GCOPY 4,0,399,600,400
POS 0,1 : GCOPY 4,0,0,600,399

GSEL 0
POS 0,0 : GCOPY 3,0,0,600,400
RETURN


;自機を表示するためのサブルーチン
*JIKI
GMODE 2,32,32
POS MX,MY :GCOPY 2,0,0,32,32
RETURN
 

自機を表示する座標をわざわざ変数にしてるのは、今後のことを考えて。いずれは動かすわけだから、変数にしておくべきだもんね。今回のには意味ないけど^^;

しっかしだんだん長くなってきたなぁ~。でもこれだけ長くなっても、新しく出てきた命令は3つだけ。

PICLOAD "(読みこむ画像ファイル名)"
指定した画像を、カレントポジションに描画。

敵とか味方とか背景なんかを外部ファイルから読みこませるのはこれだね。今後活躍しそうな命令^^

GOSUB (ラベル名)
指定したフラグに移動し、RETURNで戻る。

RETURN
移動元のGOSUBへ戻る。

この二つも「REPEAT~LOOP」と同じように、必ずペアで用いるそうな。いわゆるサブルーチンジャンプって奴だな。このプログラムを最初に実行したとき、いきなり終わっちゃったんで「???」ってなったんだけど、単にRETURNを忘れてただけだったという^^;

今回はウィンドウを3枚も使っちゃったけど、バッファーを宣言するときにウィンドウサイズを大きくしておけば、2枚でできそうね。まぁ8枚使えるわけだから、今のうちはあんまり気にしなくても良いかもしれないけど、そのうち困るときが来そうだから注意しておこう。

さて、いよいよ次は、カーソルキーで自機を動かすプログラムだ!とりあえず最初の目標だったからね。感無量^^

[奮闘記]流れる星空を表示するのだ

※注:この項の内容ですが、星を流すやり方としては駄目な例です。もっといいやり方を「配列を使って星の動きを制御するのだ」で解説しています。命令の解説自体は間違ってないのですが・・・用法は真似しないで下さい(1枚絵を動かすやりかたとしてはさほど間違いではないのですが)。
(2004年5月7日追記)


今度は、RNDを使って描いた星空を、右から左にスクロールさせてみよう。GCOPYの応用技だな。


SCREEN 0,600,400,1,100,50,600,400
CLS 4
RANDOMIZE

;バッファーに星空の絵を描きこみ
BUFFER 2,600,400,1
COLOR 0,0,0 : BOXF 0,0,600,400
REPEAT 1000
RND X,600 : RND Y,400
RND R,7 : RR=(R+1)*32-1
RND G,7 : GG=(G+1)*32-1
RND B,7 : BB=(B+1)*32-1
COLOR RR,GG,BB
PSET X,Y
LOOP

;描画画面の準備
GSEL 0 : COLOR 0,0,0 : BOXF O,O,600,400
PALCOPY 2 : GMODE 1,600,400
POS 0,0 :GCOPY 2,0,0,600,400
;バッファーに描いた画面を複写

;星をスクロールさせる部分
*MAIN
GSEL 2 : POS 0,0
GCOPY 0,0,0,600,400
;現在の画面を保存
GSEL 0
REDRAW 0
;書き換える前に非表示
POS 0,0 : GCOPY 2,1,0,600,400
POS 599,0 : GCOPY 2,0,0,1,400

REDRAW 1
;書き換えた画面を表示
STICK K : IF K=128 : STOP
;ESCが押されたら停止
AWAIT 16
;ウィンドウズにタスクを渡す
GOTO *MAIN

新しい命令がいくつか出てきたぞ~。

SCREEN (ウィンドウID),
(初期化する横幅),(初期化する縦幅),
(初期化するモード)
(配置x軸),(配置y軸)
(ウィンドウの横幅),(ウィンドウの縦幅)
ウィンドウIDを指定したモード、配置、大きさで初期化

「BUFFER」の親戚だね。違いは実際表示する画面かどうかってとこ。「WIDTH」だと大きさと配置しか決められなかったけど、こっちは画面モードの切り替えができるってのが違いみたい。

PALCOPY (ウィンドウID)
別画面で使用されてるパレットを現在の画面に反映する。

2番のウィンドウで使った色を、そのまま0番ウィンドウでも使いますよ~っていう意味でつかってるんだけど、0番ウィンドウに対してのみ、この命令はやらなくてもいいみたい。

GMODE (コピーモード),
(コピーする横幅),(縦幅)
「GCOPY」で、どのような形でコピーをするかを指定する。

0だと普通にコピー(1670万色モードのときに使うのかな?)、1だと高速コピー、2だと高速コピー+(0,0,0)の色が透過するっていう感じらしい。2のモードはキャラの重ね合わせに使えそうだね。

REDRAW (描画モード),
(再描画する画面の左上x軸座標),(y軸座標)
(横幅),(縦幅)
描画モードを指定する。

0で描きこむと、画面上では変化せず、1にすると更新すると。つまり今回の場合、赤字になってる部分で星を流してる(コピーする座標をちょっとづつずらし て、流れてるように見せる)わけだけど、これを『見える』状態でやるとマヌケだから、『見えない』状態で描きこんでおいて、描き終わったら『見える』よう にするというわけ。キャラクタを動かすときなんかも、描き終わってから『見える』ようにしないと画面がチラチラしてしょうがないみたいね。

ここで納得。文字を動かすプログラムも、「描く」→「見えない部分で消す」→「位置を動かして描く」→「見えるようにする」ってやれば、きれいに動いたんだ。

STICK (代入先変数),(非トリガータイプキー指定)
(よく使われる)キーが押されてるか押されてないかをチェックし、押されてる場合はそのキーに対応する数値を変数へ代入。

キャラクタを動かすときなんかは「押してる間だけ動く」っていう感じにしたいわけだから、非トリガータイプキーとして指定するらしい。まぁ今回は「プログラムを停止したいときはESCキーを押す」っていうことをやりたかっただけだから、特に気にしなくてもいいみたい。

AWAIT (処理を待つ時間;単位1/10ms)
処理を一定時間中断する。

HSPの仕様なんだけど、こういう永久ループをさせるときは、必ずWAITかAWAITを入れなきゃだめなのだそうだ。でないと他の入力一切受け付けず、「応答なし」で止まっちゃう^^;

ちなみにAWAITは、前回中断した(AWAITした)時からの待ち時間ということになるそうな。つまり、ループの処理を常に一定に保つ効果があるわけだな。リアルタイムのゲームなんかには不可欠でしょ。ちなみに「 AWAIT 16 」だと、だいたい1秒間に60回くらいの処理速度になる。

さて、次はキャラクタを表示させて、その背景で星空をスクロールさせてみよう。いわゆる重ね合わせ処理って奴だな。

[奮闘記]星空を一気に表示するのだ

ついに出てきたバッファー

ウォーズマンも立ったままKOされるわけだ。ちが~う(T-T)/


CLS 4
WIDTH 600,400,100,50
RANDOMIZE

;バッファーに星空の絵を描きこみ
BUFFER 2,600,400,1
COLOR 0,0,0 : BOXF 0,0,600,400
REPEAT 1000
RND X,600 : RND Y,400
RND R,7 : RR=(R+1)*32-1
RND G,7 : GG=(G+1)*32-1
RND B,7 : BB=(B+1)*32-1
COLOR RR,GG,BB
PSET X,Y
LOOP

;バッファーからの読み出し
GSEL 0
GCOPY 2,0,0,600,400
STOP

今回新しく出てきた命令は3つ。ちなみに「 ; (セミコロン)」から後ろは「コメント」ってことで、命令とは判断されないそうだ。要はプログラムをわかりやすくするための注釈だな。忘れっぽいおいら は、こういうのを書いておかないと後で(゚◇゚)~になりそうだから、きっちり書いておくことにしよう。

BUFFER (ウィンドウID),
(ウィンドウの横幅),(ウィンドウの縦幅),
(画面モード)
仮想画面を使いますよ~っていう宣言をする。

仮想画面ってのは「メモリ上に仮想画面が作られるだけで、実際の画面には表示されません」ということらしい。要は画面には何も表示されないってことだな。

親 戚に「SCREEN」という命令があるらしいが、こっちは「仮想」じゃなくて実際の画面。HSPだと合計8枚までこういうウィンドウを制御できるらしい。 特に何も指定しなければ、0番のウィンドウを使うらしい。じゃ、今までやってきたことは、全部0番のウィンドウの上で行ってきたっつーことね。重ねあわせ の処理なんかで使うのかな?あ、ちなみに1番のウィンドウはシステム側で使うウィンドウなんで、自由に使うことはできないとのこと。だからここでは2番を使ってる。

あと、画面モードってのは、0だとフルカラー1670万色、1だと1670万色中256色のパレットモードになるらしい。1のが速いのかな?

GSEL (ウィンドウID),(スイッチ)
操作するウィンドウと、そのモードを指定する。

2番のウィンドウに書きこみが終わったら、0番の実際の画面に切り替える。要は「いまから0番にいろいろ操作します~」っていう宣言だな。スイッチっつーのは、表示したりしなかったりする場合に使うらしい。よくわからん^^;

GCOPY (コピー元ウィンドウID),
(始点x軸),(y軸),
(終点x軸),(y軸)
指定したウィンドウから、指定した大きさの画像をコピーし、GSELで指定されたウィンドウのカレントポジションにその画像を読みこむ。

あ、カレントポジションの指定するの忘れてた(゚◇゚)~

まぁそれでも問題なく出てくるんで良しとしましょう。でもこれを動かしたり~ってことになった場合は絶対忘れちゃ駄目ね^^;

ちなみに表示された星空はこんな感じ。

よし、次はこの星空をスクロールさせてやる~って無謀?^^;

[奮闘記]星空を描いてみるのだ

なんかこれはBASICでもやった記憶があるぞ。乱数を発生させて、適当に点をばら撒くってやつだな。けっこう神秘的な雰囲気になるんだよね、うん。早速やってみよう。


CLS 4
WIDTH 600,400,100,50
RANDOMIZE

REPEAT 1000
RND X,600 : RND Y,400
RND R,255 : RND G,255 : RND B,255
COLOR R,G,B
PSET X,Y

LOOP

STOP

なんかめちゃめちゃいっぱい新しい命令でてきたね^^;

CLS (0~4の数値)
画面をクリアする。0だと白、4だと黒、その間はグレー。

「星空」だから背景が黒じゃなきゃ不自然。っつーわけで、最初にウィンドウを黒でクリアすることにした。BOXFで塗りつぶしてもいいんだろうけどね・・・。

WIDTH (ウィンドウの横幅),(縦幅),
(ウィンドウを表示させるx軸の位置),(y軸の位置)
ウィンドウの大きさと位置を指定する。

乱数を発生する範囲を決めるため、ウィンドウの大きさも指定。そうしないと、星の無い空間とかできちゃうもんね。ウィンドウ位置は適当。特に意図無し。

RANDOMIZE (適当な数値)
乱数表を指定。数値が指定されて無い場合は、パソコンの内部時計を参照して適当な乱数表を選ぶ。

毎回同じパターンで星空を表示させたいなら、ここで数値を指定しておけばいいみたい。

RND (変数),(数値)
0から指定した数値までの間で発生させた乱数を、指定した変数に代入する。

今回のポイント。XとYに代入した値は点を打つ座標に、RとGとBに代入した値は点の色を決めるのに使ってる。「WIDTH」でウィンドウのサイズを決め ておいたから、Xの上限は600・Yの上限は400。また、色の明るさは255が最大だから、R,G,Bの上限も255。

REPEAT (数値)
「REPEAT」~「LOOP」の間を指定回数分繰り返す

LOOP -
「REPEAT」~「LOOP」の間を、REPEATで指定した回数分繰り返す。

この2つは必ずペアで用いるみたいね。ちなみにこのプログラムだと、赤い部分が1000回繰り返される。これによって星が1000個表示されるっつーわけだな。

PSET (x軸),(y軸)
指定した場所に1ドットの点を打つ。

まぁこれはそのまんまだね。点を打つ前に、色を指定。色もランダムに発生させてるからカラーな星空になってる。

今回は一発でできたんで「俺って天才!」とか思ってたんだけど、よ~く考えたらこれ(0,0,0)に近い色はほとんど見えないんだよな。黒は表示させず、色調も8段階くらいにしたいところだよね・・・。よし、やってみよう。


CLS 4
WIDTH 600,400,100,50
RANDOMIZE

REPEAT 1000
RND X,600 : RND Y,400
RND R,7 : RR=(R+1)*32-1
RND G,7 : GG=(G+1)*32-1
RND B,7 : BB=(B+1)*32-1

COLOR
RR,GG,BB
PSET X,Y
LOOP

STOP

大きく変えたのは赤い部分。まず、0~7の乱数を発生させる。これで「8段階」っつー条件はクリアできたわけだな。で、次にその乱数に1を加え(これに よって1~8の乱数が得られる)、それを32倍(最大256までの数字が得られるわけだな)し、そこから1引く(色の明るさは最大が255なんで)と。こ れで、31・63・95・128・159・191・223・255の8段階の色調で表示することができるようになったというわけだ。う~ん、完璧。でもな んか遅くなったような気が・・・^^;

よし、次はいよいよバッファーっつーやつを使ってみよう。トマホーク・ハリケーンを食らわないように注意しなきゃ。・・・それってバッファローマンじゃん(T-T)/

星が今回は1000個だからいいけど、100000個とかいったらけっこう時間かかるからね。仮想画面に描いてから一気に表示させてやろうというわけ。速くなるそうな。

[奮闘記]文字を動かしてみるのだ(その2)

どうやら一旦描いた文字を消さなかったから、ああいう感じになったらしい。軌跡がそのまま残っちゃってるわけだな。命令一覧表を見てたら、塗りつぶしの四角を描く命令があったから、それを使ってやってみよう。


*MAIN
COLOR 255,255,255
BOXF X,Y,X+100,Y+50
X=X+1
Y=Y+1
POS X,Y
COLOR 0,0,0
MES "動く~"
WAIT 2
IF Y<400 : GOTO *MAIN
STOP

おっとぉ、一気に3つ新しい命令が出てきたぞ。でもまだそんなに難しくないね。

COLOR (赤の明るさ),(緑の明るさ),(青の明るさ)
いろんな色を指定する。

BOXF (始点x軸の座標),(始点y軸),(終点x軸),(終点y軸)
指定した範囲を四角形に塗りつぶす。

WAIT (数値)
指定した数値分、そのまま待つ。

「動く~」の大きさが正確にはわからないんで、適当な大きさ(横100・縦50)の白い四角で消すことにした。ノーウェイトでやったら一瞬で終わっちゃっ たんで、ちょっと静止させることにした。これでF5を押すと、、、お見事!「動く~」の文字が左上から右下に動いていきました^^

しかし、これちょっと駄目プログラムだね。書き方的には間違ってないけど、「書いて→消す」っていうやり方のが見た感じわかりやすい。こういう感じのがいいかな?


*MAIN
X=X+1
Y=Y+1
POS X,Y
COLOR 0,0,0
MES "動く~"
WAIT 2
COLOR 255,255,255
BOXF X,Y,X+100,Y+50
IF Y<400 : GOTO *MAIN
STOP

うん、この方がわかりやすい。

でも、「動く~」の文字がチラチラするのがちょっと気になるなぁ。もっときれいに見せる方法無いものかな?それに、もし背景がついてたらそれまで消えちゃうわけだよね?うーん・・・これは後回しだな。なんかバッファーとかいう仮想画面に書きこんだのを読みこんだりするらしいが、、、まだそんな高度なことできまへん^^;

っつーわけで、なんの脈絡も無いが次回は「星空」を描いてみよう。シューティングっていえば宇宙空間だからね。

[奮闘記]文字を動かしてみるのだ(その1)

マニュアルにも載ってたけど、プログラムには「旗」を立てるらしい。なんのこっちゃ。なになに?「印をつけておくこと」・・・・・・・だったら印って言えよ~(ToT)/


*MAIN
X=X+1
Y=Y+1
POS X,Y
MES "動く~"
IF Y<400 : GOTO *MAIN
STOP

とりあえずこういう感じで使うらしい。1行目の「*MAIN」が旗(「フラグ」と言うそうな)。問題は7行目だな。この「IF」ってのが曲者だ。

IF (条件式) : (条件式が正しい場合の命令)
条件式が成立したときはすぐ後の「:」以下へ、条件式が成立しない場合は次の行へ移動。

つまり、上のプログラムで赤い部分を日本語にすると「もし変数Yが400より小さかったら、*MAINに戻ってね」っていうことらしい。日本語で書くと冗長だな。だから英語を使うのか?まぁいいや。

つ まりこのプログラムでは、カレントポジションをちょっとづつ増やして「動く~」の文字を右下へ移動させたかったのだ。でも延々とやられても困るから、条件 式を使って途中で止めるようにしたと。Y軸が400以上になったら繰り返しをやめて停止するっつーわけだな。おっと、一個命令を脳味噌にインプットするの を忘れてた。

GOTO (フラグの名前(ラベルと言う))
指定したラベルへ移動。

「IF」にも「GOTO」にも『移動』っつー言葉を使ったけど、要はプログラムの進行をいじる命令なわけだな。MULTIPLYで条件分岐したりとか、空 白チップで回路全体を循環させるようにしたりしたけど、そんな感じのことか。そういや昔BASICを触ってたころに「GOSUB~RETURN」っつーの があったぞ?「GOTO」を使うとプログラムがややこしくなるから、極力「GOSUB~RETURN」でサブルーチン化した方がいいっていう話しがあった ような・・・。まぁいい、そのうち出てくるだろ。多分。

ところで、上記のプログラムを試した人(もしくはプログラムに詳しい人)は気づいたと思うんだけど、これを実行すると妙なことになっちゃうのだ。ちょっと見てくれ。

こんな風に斜めに棒が表示されちゃうのだ。なんで?うーん・・・続きは次回。

[奮闘記]文章を表示させるのだ(その2)

よし、今回はウィンドウの好きな位置に文字を表示させるぞ。「ここから文字とか画像を表示させます」っていう位置のことをカレントポジションって言うんだって。それを指定しておいてから表示させればいいみたい。


POS 100,100
MES "いやっほぅ"
STOP

ウィンドウの左上が(0,0)で、x軸は右にいくほど大きく、y軸は下に行くほど大きい数値になるそうな。ちなみに「POS」のを指定しないと、カレントポジションは自動的に(0,0)になるんだと。だからさっきは左上に表示されたんだな。

POS (x軸),(y軸)
カレントポジションを指定した位置に移動。

この命令は非常~に重要らしいね。キャラを動かしたりするのは、これを使うらしい。とりあえず最初の目標が「カーソルキーに従って主人公が動くようにする」ことだから、活躍するんだろうな・・・。なんとなく原理はわかるぞ。

  1. どのキーが押されたかチェック
  2. そのキーに従って、カレントポジションを移動
  3. そこにキャラを表示

これをループさせればいいんだよな。なんかサブルーチンとか使うらしいが、今のところ気にしないでおこう。「万里の道も一歩から」って言うじゃないの。とりあえず、次はこの文字を動かしてやろう。

[奮闘記]文章を表示させるのだ(その1)

プログラミングの入門書を見ると、JavaだろうがCだろうが、何故か出てくる「Hello World」。プログラムの勉強ってのはここから始めなきゃいけないっていう決まりらしい(※注_嘘です)ので、とりあえずそれから始めてみよう。

まず「hsed2.exe」を起動して、こんな感じに打ってみた。


MES "Hello world!"
STOP

なんか行頭はTABを一個あけなきゃいけないらしい。「それが決まり」なんだって。
ここまで打てたらF5(コンパイル+実行)をぼちっと・・・。わーい、成功・・・みたい。真っ白なウィンドウが開いて、その左上に「Hello world!」って表示された。よーし、脳味噌にインプットだぁ!

MES "(文字列)"
""で囲まれた文字列を表示

STOP -
その状態で静止する

「MES」ってのは「message」の略だろうね。「言語」っていうくらいだから、英語とかと同じようなもんなんだろうな。コンピュータと会話するための言語。でもさぁ・・・ちょっと思ったんだけど日本語で命令できるような言語って無いんかい(-_-;日本人なら日本語を使え!日本語を!(またしてもにわか国粋主義者に変貌)

・・・・・・閑話休題

まずは、こういう命令を片っ端から覚えることだな。英単語の暗記みたいなもんだ。・・・って思ってたら、とある人から助言。よく使われてる(自分の目的に 合った)命令から覚えていけばいいんだと。確かにいっぱい命令あるみたいだもんな~。BASICを挫折したのは、実はこれが原因。アルファベット順に覚え ようとしていたのだ。確かに英語も辞書をアルファベット順に暗記しようと思ったら死ぬわな^^;

よーし、次はこの文字を、自分の好きな位置に表示させてみよう。左上にしか表示できないんじゃどうしようもないもんね。

[奮闘記]はじめに

「オリジナルゲームを作ってみたい!」

これは私が長年思ってたことだった。最初にチャレンジしたのは中学2年のころ。初めて所有したNECのPC8801FHというパソコンを使い、BASIC なる言語を勉強しようとして・・・2週間ほどで挫折した(^^;)「マイコンベーシックマガジン」という雑誌は愛読していたが、なにせ難しいことからやろ うとしすぎていた。また、今考えるとあのBASICってやつはわかりにくいことこの上ない。なにせ変数に自由な名前が使えない。だから他人の組んだプログ ラムを見ても、何をやってるのかすぐにはわからない。まるでMULTIPLYの回路解析みたいなもんである。

今 思えば、このとき挫折したのをきっかけに、文系の道へとすすんでいくことになったのだろう。実際高校2年くらいまでは、数学はむしろ得意教科だった。しか し、どこかでコンプレックスを持っていたのだろう。とにかく私の頭脳ではプログラムってやつは理解不能だったのである。

そして月日は流れ・・・

何の因果か、今ではパソコンに関わる仕事をしている。といってもインターネットの接続サポートとかアプリケーションの使い方のサポートなんかが中心だから、未だプログラムっていう奴が私にとって未知の領域であることには変わりない。

そ んなある日出会ったのが「MULTIPLY」というゲームであった。このHPを見てわかるとおり、私はこのゲームに没頭した。自分の思ったとおりの動きを する機体を作れたときの感動。「俺にもプログラムってできるんだ!」幼いころのコンプレックスは氷解し、憧れはやがて野望へと変わっていった。「こんな ゲームを作ってみたい!」日に日にその思いはつのるばかりであった。

しかし!!

私の前に立ちふさがったのは、今度は「C 言語」という奴であった。「C!!」ああ、なんという甘い響き。よく耳にはしていた。「SEXのことじゃないよ」なんていう冗談を目にすることもあった。 本屋で解説書を立ち読みしたこともあった。「オ、オブジェクト指向???」私は頭から煙が出るのを感じた。そう。だいたいプログラムの解説書というやつ は、全くのど素人を対象には書かれてないのである。羅列されるカタカナ言葉。「日本人なら日本語でしゃべりやがれ!べらんめぇ」などと、にわか国粋主義者 になってもいたしかたない。それが現実なのだ。

そんなある日耳にした「HSP」という言葉。「なになに?プログラムを勉強するんだったら比較的簡単なこういう言語から入っていった方がいい?」渡りに船だった。さっそくそのHSPというやつをダウンロード。HTMLで書かれた解説書を開いた・・・。

確 かになんとなくはわかる。しかしやっぱり「なんとなく」なのだ。これを使って何ができる?そういう部分になってくると、さっぱりわからない。思い余って、 同じHSPを使ってプログラムを組んでる人に相談してみた。・・・習うより慣れろなんだね。作っていくうちに覚えていくもんなんだ。確かに MULTIPLYでも、最初は稚拙な回路しか作れなかった。でも何体か作っていくうちに、強豪と称されるような機体を作れるようになっていった。初めから 大きい魚を狙い過ぎなんだね。よ~くわかった。Step by step.一歩づつ極めていってやろうじゃないの!!

このページは、そんな私がHSPを使ってゲームを作れるようになるまでを克明に記したドキュメントである。


上記は1999年6月16日に記述した文章です。当時から考えると、転職をしたりサイト内容変更したり何だかんだでかなり状況が変わってますが、「これからHspを取得してやるぞぉっ!」という私の決意がよく現れた文章なので、あえて内容は変更せずそのまま載せています。ぶっちゃけこの奮闘記自体も今読み返すと微笑ましいくらいの内容ですが(苦笑)誰にだって初心者の頃はあります。これからHspを勉強しよう!という方も、ご安心ください。私にもこんなころがあったんです。

« 2007年12月 | トップページ | 2008年2月 »