自機を動かす(2/2)

 2.自機を動かす

(1)自機の座標を増減する
 ここまでできれば、もう簡単です。あとはukey,dkey,lkey,rkeyの値に応じて、自機の座標(playerx,playery)を増減させればよいのです。ここでは、1度に4ドット動かすことにします。
  if ukey=1 { playery=playery-4 }
  if dkey=1 { playery=playery+4 }
  if lkey=1 { playerx=playerx-4 }
  if rkey=1 { playerx=playerx+4 }
 パソコンの画面は、y座標は下がプラス方向、上がマイナス方向です。
 左と上が両方とも押されていたら、playery=playery-4とplayerx=playerx-4が行われ、左斜め上に動きます。たまたま上と下が同時に押された場合、playery=playery-4とplayery=playery+4が行われ、相殺されます。

(2)画面の端では動かさない
 画面の端に来たら、もうそれ以上動けないようにしなければなりません。例えば自機が左端にいる時(playerxが0の時)、playerx=playerx-4を行うとマイナスの値になってしまいますね。こういう場合は強制的にplayerxを0にします。
  if playerx<0 { playerx=0 }
  if playerx>608 { playerx=608 }
  if playery<0 { playery=0 }
  if playery>448 { playery=448 }
 画面のサイズが640×480ですから、右端のx座標は639、下端のy座標は479です。(playerx,playery)は自機の左上隅の座標で、自機のサイズが32×32ですから、右端にいる時playerxは608、下端にいる時playeryは448です。

(3)変更後の座標に自機の絵を描く
 あとは変更後の座標に自機を描くだけですが、ここで、アニメーションをさせたいと思います。アニメーションとは、キャラクタが動いている方向に傾いたり、くるくると回ったり、羽ばたいたりすることです。毎回同じ絵を描くのではなく、その都度違う絵を描くことです。
 自機が動いている方向によって、右に傾けたり左に傾けたりします。
  絵のファイル
 samplecg.bmpに右に傾いた絵と左に傾いた絵を加えました。それぞれのサイズは32×32、左上隅の座標は(0,0)、(32,0)、(64,0)です。

 ddsetrectでコピー元の絵を指定する時に、右に動いている時には右に傾いた絵を、左に動いている時には左に傾いた絵を指定します。どちらでもない時は傾いていない絵を指定します。ここで、「右に動いている」とは右斜め上、右斜め下も含みます。
 右に動いていることを判別するにはどうすればいいでしょうか。rkeyが1の時ですね。本当にそれだけでしょうか? たまたま右キーと左キーをいっしょに押してしまった場合、右に傾いた絵を描くのはおかしいですね。つまり、rkeyが1かつlkeyが0の時です。すると、自機を描く処理はこうなります。
  ddpos playerx,playery
  ddsetrect 0,0,32,32
  if (rkey=1)&(lkey=0) { ddsetrect 32,0,32,32 }
  if (lkey=1)&(rkey=0) { ddsetrect 64,0,32,32 }
  ddgcopy 1
 先にどちらにも傾いていない絵を指定し、右に動いていれば右に傾いた絵に指定し直しています。左も同様です。


 以上をふまえて、付加的な処理を加えた今回の部分は以下の通りです。

	digetjoynum
	njoy=stat	; つながっているジョイパッドの数

	ddsetrenewaltiming 60	; 画面の更新回数を60回/秒にする。

	; メインループ
*Main_Loop
	await 0
	gosub *Clear_Screen

	; 自機の移動
	gosub *In_Key
	if ukey=1 { playery=playery-4 }
	if dkey=1 { playery=playery+4 }
	if lkey=1 { playerx=playerx-4 }
	if rkey=1 { playerx=playerx+4 }
	if playerx<0 { playerx=0 }
	if playerx>608 { playerx=608 }
	if playery<0 { playery=0 }
	if playery>448 { playery=448 }

	; 自機を描画
	ddpos playerx,playery
	ddsetrect 0,0,32,32
	if (rkey=1)&(lkey=0) { ddsetrect 32,0,32,32 }
	if (lkey=1)&(rkey=0) { ddsetrect 64,0,32,32 }
	ddgcopy 1
	ddredraw

	goto *Main_Loop

	; キー、ジョイパッドの入力
*In_Key
	ckey=0
	ukey=0
	dkey=0
	lkey=0
	rkey=0

	digetkeystate key,0
	if key&256!0 { ckey=1 }
	if key&1!0 { ukey=1 }
	if key&2!0 { dkey=1 }
	if key&4!0 { lkey=1 }
	if key&8!0 { rkey=1 }

	digetkeystate key,1
	if key&16777216!0 { ukey=1 }
	if key&2!0 { dkey=1 }
	if key&64!0 { lkey=1 }
	if key&1048576!0 { rkey=1 }

	if njoy>0 {
		digetjoystate key,0
		if key&16!0 { ckey=1 }
		if key&1!0 { ukey=1 }
		if key&2!0 { dkey=1 }
		if key&4!0 { lkey=1 }
		if key&8!0 { rkey=1 }
	}

	return

; メインループ
 自機の移動、描画は、このプログラムのメインとなるループです。このようなループを一般的にメインループと呼びます。

ddsetrenewaltiming 60
 ddsetrenewaltiming 60は、画面の更新がちょうど1秒間で60回になるように時間調整を行います。
  自機の移動→描画→ddredraw(画面の更新)
 これで1回のループとなります。このループの処理時間が1/60秒になるように待ち時間を入れるということです。
  自機の移動→描画→待ち時間→ddredraw(画面の更新)
  (待ち時間がddredrawの前に入るのか後に入るのかは分かりません。)
 パソコンの性能によって、プログラムの実行速度は変わります。ユーザによってゲームのスピードが違うことになります。この命令を行うことによって、どのパソコンでも1秒間で60回の画面更新となります。(60fpsと表されます。)

await 0
 メインループにはawait 0を入れます。正直、理由は分かりません。0秒待つって、どういう事? HMM . DLLのReadmeに、入れるように書いてあります。前に、長時間ループする処理にはawait 16を入れよと書きましたが、その代わりをしているのがddsetrenewaltiming 60とawait 0の組み合わせなのだろうと思います。(ちなみに、1/60秒は約16ミリ秒です。)

gosub *Clear_Screen
 毎回画面全体を消して描き直しています。このようなことをしても、現在のパソコンでは十分な速度が得られます。一昔前は考えられなかった事のようです。


[プログラム全体と絵をダウンロード]
※スタートボタンが押されるのを待つ処理も、今回作ったサブルーチンを呼ぶように書き換えています。

ひとくちメモ:
・マルチステートメントとif文
 1行に":"(コロン)で区切って複数の命令を書くことができます。これをマルチステートメントといいます。
  x=100 : y=200
 これくらいだったら、使うと見やすくなるかと思いますが、一般にマルチステートメントは多用すると分かりづらくなると言われています。if文もマルチステートメントを使って書くことができます。
  if 条件式 : 命令
 ちょっと、分かりづらいです。ちなみにこれは
  if 条件式 { 命令 }
 と同じ意味です。これは、"if 条件式"だけで一つの独立した命令文であると言っているようなものです。普通、そういう考え方をしません。"if 条件式 { …… }"で1つの塊なのです。特に、
  if 条件式 : 命令1 : 命令2 : 命令3
 なんていう書き方は、とても分かりづらいと思います。これはHSPの古い書式です。また、HSPのヘルプにも、"{","}"を使った書き方と":"を使って1行におさめる書き方を混在した場合、動作の保証ができない旨が記述されていますので、"if 条件式 : 命令"という書き方はしない方がいいと思います。
 
・べき乗計算について
「"^"はべき乗です」と書きましたが、これは一般的な表現であって、HSPにはべき乗計算を行う演算子はありません。(HSPでは"^"は排他的論理和です。)例えばnの4乗を行いたい場合には、
  n*n*n*n
 と書きます。2のべき乗に関してだけ、"<<"という演算子を使ってもっとコンパクトに書くことができます。例えば2の4乗は
  1<<4
 と書くことができます。これはビットシフトという演算子で、"1<<m"は1を左にm回ビットシフトします。ビットシフトとは、各ビットを隣にずらすことです。1は2進数では、
  0……000001
 と表されます。なお、HSPでは整数は32ビットなので、0は31個並びます。4回ビットを左にずらすと
  0……010000
 となります。これは16、つまり2の4乗です。"1<<m"は2のm乗です。プログラム中に16777216というぱっと見て2の何乗だか分からない数が出てきますが、この数字の代わりに"(1<<24)"と書いても同じです。
 
・なぜ途中でdigetkeystate k,0からdigetkeystate key,0に変えたのか?
 変数は、プログラム全体を通して一つの用途に使うものだけとは限りません。その場限りの、一時的な用途でしか使わない変数は、同じ変数名がいろいろな場所で、別の用途で使われます。iやjやkといった変数名は、そういう一時的な役割で使うことが多いです。サブルーチンの中でそういった変数名を使っていると、メインルーチンの中で別の用途で使っていた場合、値をサブルーチンの中で変えてしまうかもしれません。サブルーチンの中ではいかにもあちこちで使いそうな変数名を使うなとは言いませんが、メインルーチンで使っている変数の値をおかしくしてしまわないよう、注意して下さい。
 悪い例
 変数iをメインルーチンとサブルーチンとで、別の用途で使っています。サブルーチン中のiを別の変数名にすれば正しく動作します。

    ; 1の2乗+2の2乗+……+10の2乗を計算し、変数answerに代入する
    answer=0
    i=1
*calc
    number=i
    gosub *nijyou
    answer=answer+square
    i=i+1
    if i<=10 { goto *calc }
    mes "答は"    ; mes命令は引数の内容を画面に書きます。
    mes answer
    mes "です。"
    stop

    ; numberの2乗を計算するサブルーチン
*nijyou
    i=number
    i=i*number
    square=i
    return
 

[目次へ][前へ][次へ] inserted by FC2 system