「えぐぜりにゃ〜」ソース解説2

前回はざっくりと移動関数を見ましたので、今回は細かく見ていきます。
(※バージョンは「exelinya_0214.zip」です)

movemychar

まずはプレイヤーの移動関数です。(498行目から)
 
プレイヤーの移動関数は、以下の処理を行っています。

  1. ライフの自動回復
  2. キー入力処理
  3. 移動
  4. 当たり判定
ライフの自動回復

501行目でライフの自動回復を行っています。

	// ライフ自動回復
	if(mychar_life < MYCHAR_LIFE_MAX){
		mychar_life++
	}

見たままですね。
ライフが最大値よりも小さければ、ライフを1つプラスしています。
 

キー入力処理

508行目でキー入力の判定をしています。

	if((stick_state | key_state) & 1){vx = -1.0 : mychar_gr_rotate -= MYCHAR_ROT}
	if((stick_state | key_state) & 2){vy = -1.0 : mychar_gr_rotate -= MYCHAR_ROT}
	if((stick_state | key_state) & 4){vx =  1.0 : mychar_gr_rotate += MYCHAR_ROT}
	if((stick_state | key_state) & 8){vy =  1.0 : mychar_gr_rotate += MYCHAR_ROT}

これも見たままで、上下左右の入力処理を行っています。
 
ポイントは次の部分ですね。(513行目)
分かりやすいようにコメントを入れておきます。

	if(vx != 0.0 | vy != 0.0){
		// キー入力があった場合
		// ①移動量を一定にする
		vr = sqrt(vx * vx + vy * vy)
		vx = vx / vr
		vy = vy / vr
		// ②キー入力に対する慣性をつける
		if(anchor_state == ANCHOR_STATE_STANDBY | anchor_state == ANCHOR_STATE_RETURN){
			// 何も掴んでいない
			mychar_ssx += vx * MYCHAR_SPEED1
			mychar_ssy += vy * MYCHAR_SPEED1
		}else{
			// 何か掴んでいる
			mychar_ssx += vx * MYCHAR_SPEED2
			mychar_ssy += vy * MYCHAR_SPEED2
		}
	}

①ではキー入力に対する移動量を一定にしています。
例えば斜めに移動があった場合、

vr = sqrt(1*1 + 1*1) ≒ 1.4
vx = vx / vr = 1 / 1.4 ≒ 0.7
vy = vy / vr = 1 / 1.4 ≒ 0.7
 
移動量 = sqrt(0.7*0.7 + 0.7*0.7) ≒ 1

となり、斜めの移動量が水平/垂直方向と同じになります。
 
なぜこのようなことをするのかというと、
移動量を一定にすると、「操作性が安定」するためです。
 
②では、キー入力に対して、慣性をあらわすパラメータssx/ssyに値を足しこんでいます。
MYCHAR_SPEED1は「5.0」で定義してあるので、何も掴んでいないときは「かなりの力」が、
MYCHAR_SPEED2は「0.2」で定義してあるので、何か掴んでいる「わずかな力」が、
それぞれ働くことになります。
 

移動

527行目の反動と減衰を見てみます。

	// 反動と減衰
	// ①座標の更新
	mychar_x += (mychar_sx + mychar_ssx)
	mychar_y += (mychar_sy + mychar_ssy)
	// ②慣性の減衰
	mychar_ssx *= MYCHAR_SPWEAKEN
	mychar_ssy *= MYCHAR_SPWEAKEN
	// ③移動量の減衰
	mychar_sx *= MYCHAR_REACTWEAKEN
	mychar_sy *= MYCHAR_REACTWEAKEN
	// ④回転角度の減衰
	mychar_gr_rotate *= MYCHAR_ROTWEAKEN

①では座標の更新を行っています。
ここでは単純に現在の座標に、「移動量」と「慣性」を足しこんでいます。
 
②では慣性の減衰をしています。
MYCHAR_SPWEAKENは「0.5」なので、急激に減速します。
しかし、このほんの少しだけ働く慣性が、プレイヤーを操作する感覚を心地よくさせています。
 
③では移動量の減衰をしています。
sx/syは、アンカーの反動や敵に衝突したときの反動です。
MYCHAR_REACTWEAKENは「0.85」と結構大き目の値を取っています。
 
そのため、

  • アンカーは慎重に操作しなければならない(反動が大きいため)
  • 敵に連続でぶつかると、ピンボールのようにはじかれる

という、「戦略性」と「感覚の面白さ」を生み出しています。
 
④の回転角度の減衰はそのままですね。
これのおかげで、ふにゃふにゃとプレイヤーの画像が動いて、
妙な面白さをかもし出しています。
 
537〜540行目では、キャラが画面外に出ないように調整をしています。

	if(mychar_x <         32.0) : mychar_x = 32.0
	if(mychar_x > 640.0 - 32.0) : mychar_x = 640.0 - 32.0
	if(mychar_y <         32.0) : mychar_y = 32.0
	if(mychar_y > 480.0 - 32.0) : mychar_y = 480.0 - 32.0

 

当たり判定

544行目から589行目までは、プレイヤーvs敵の当たり判定を行っています。
とりあえず、551行目までをみてみます。

	// 敵とのコリジョン-----------------------
	repeat ENEMY_MAX
		// ①存在しない場合は何も処理をしない。
		if(enemy_type(cnt) == ENEMY_TYPE_NULL) : continue
		// ②円による当たり判定
		// 敵からプレイヤーへの距離
		vxx = enemy_x(cnt) - mychar_x
		vyy = enemy_y(cnt) - mychar_y
		// ぶつかる距離
		sz = MYCHAR_SIZE + enemy_spec_size(enemy_type(cnt))
		if(vxx * vxx + vyy * vyy < sz * sz){
			// 食らった

①ですが、「えぐぜりにゃ〜」のように敵を「配列」で管理する場合は、
必ず存在チェックが必要になります。
if文で囲んでもいいですが、continueを使う方がスッキリ書けるので、
この書き方がオススメです。
 
②は分かる人にはすぐ分かる「円」による当たり判定ですね。
分からない人は、呪文と思って丸暗記してしまってください(´∀`;
一応こちらが参考になるかもしれません。
http://www5f.biglobe.ne.jp/~kenmo/program/collision/2d/2d.html
 
552行目から557行目です。

			if(mychar_dam_timer < 1){
				// ①ダメージタイマーがない場合
				mychar_life -= enemy_spec_dam(enemy_type(cnt))
			}else{
				// ②ダメージタイマーが残っている場合
				mychar_life -= int(double(enemy_spec_dam(enemy_type(cnt))) * MYCHAR_DAMAGE)
			}
			// ③ダメージタイマー設定
			mychar_dam_timer = MYCHAR_DAM_TIME

①ダメージタイマーがない場合
そのまま、ごっそり体力を奪われてしまいます。
 
②ダメージタイマーが残っている場合
MYCHAR_DAMAGEは「0.1」なので、少しだけ体力を奪われます。
連続で衝突したときに即死しないようにするための配慮ですね。
 
③ダメージタイマー設定
これはそのままですね。ダメージタイマーをセットします。
 
559行目から573行目です。

			// ①反発力を求める
			vxx = vxx / sqrt(sz)
			vyy = vyy / sqrt(sz)
			// ②移動量設定
			mychar_sx = -MYCHAR_DAM_SP * vxx
			mychar_sy = -MYCHAR_DAM_SP * vyy

			if(anchor_state == ANCHOR_STATE_GRAB){
				// ③掴んでるのは投げる
				anchor_state = ANCHOR_STATE_RETURN
				// 敵を飛ばす移動量を求める
				vxx = anchor_sx / 2.0 + ANCHOR_RELEASE_SP * cos(anchor_rotate)
				vyy = anchor_sy / 2.0 + ANCHOR_RELEASE_SP * sin(anchor_rotate)
				// 掴んだ敵は投げられた敵になる
				addthrenemy grab_enemy_x , grab_enemy_y , vxx , vyy , grab_enemy_rotate , grab_enemy_type , grab_enemy_life
			}

①では、敵にぶつかったときの反発力を求めています。

	vxx = vxx / sqrt(sz)

sqrt(sz)により、敵のサイズによって移動量を変えています。
(サイズが大きいほど移動量が小さくなる)
 
そのあと②で移動量を設定しています。

mychar_sx = -MYCHAR_DAM_SP * vxx

MYCHAR_DAM_SPには「3.0」が入っているので、結構大きい反動が発生します。
vxx,vyyは「プレイヤーから敵へのベクトル」なので、
マイナスを掛けて力を反転させています。
 
③はそのままですね。
ダメージを受けたときは、敵を放します。
そして、敵を飛ばす移動量を求め、敵を「掴んだ敵」から「投げられた敵」に変化させます。
(※ただ、ANCHOR_RELEASE_SPはどこにも定義されていなかったのが気になりますが…)
 
 
最後の部分です(575行目〜587行目)

			// 敵とプレイヤーの中間の座標を求める
			xxx = (enemy_x(cnt) + mychar_x) / 2.0
			yyy = (enemy_y(cnt) + mychar_y) / 2.0

			// 敵にダメージ
			enemy_life(cnt) -= 2
			if(enemy_life(cnt) < 1){
				// 敵破壊エフェクト
				destroypertics enemy_x(cnt) , enemy_y(cnt) , enemy_sx(cnt) , enemy_sy(cnt) , enemy_type(cnt)
				enemy_type(cnt) = ENEMY_TYPE_NULL
			}else{
				// ダメージエフェクト
				addeffect  xxx , yyy , 0.0 , 0.0 , 0.0 , 96.0 , EFFECT_TYPE_PERTIC1
				addpertics xxx , yyy , 64.0 , 32.0 , 0.0 , 0.0 , 15
			}
			// ダメージ音
			ds_play SND_MISS

まあ、そのままなので、コメントを見てください、、(´∀`ヾ
 
敵の破壊やエフェクトの生成を行っています。
 
 

moveanchor

アンカーの移動関数です。
 
596〜599行目では画面外に出ないようにチェックしています。

	if(anchor_x <   0.0) : anchor_x =   0.0
	if(anchor_x > 640.0) : anchor_x = 640.0
	if(anchor_y <   0.0) : anchor_y =   0.0
	if(anchor_y > 480.0) : anchor_y = 480.0

 
602〜605行目では、キー入力に対応する移動量を設定しています。

	if((stick_state | key_state) & 1){vx = -1.0}
	if((stick_state | key_state) & 2){vy = -1.0}
	if((stick_state | key_state) & 4){vx =  1.0}
	if((stick_state | key_state) & 8){vy =  1.0}

 
607〜739行目では、アンカーの「4つの状態」に応じた更新処理を行っています。
 

スタンバイ

まずはスタンバイ状態から見ていきます。
607〜640行目がスタンバイ状態の更新処理です。
 
610〜619行目では、移動量に対応したアンカーの回転を行なっています。

		// 移動してる場合はアンカーをそちらに向ける
		if((vx != 0.0 | vy != 0.0) & ((stick_state | key_state) & 32) == 0){
			// ①回転するラジアンを取得
			theta = atan(vy,vx)
			if(absf(mychar_anc_rotate - theta) > MATH_PI){
				// ②「右回り」したら-π〜πを超える
				if(mychar_anc_rotate > theta){
					// プラス方向に振り切る
					mychar_anc_rotate -= MATH_PI * 2.0
				}else{
					// マイナス方向に振り切る
					mychar_anc_rotate += MATH_PI * 2.0
				}
			}
			// ③「アンカーの角度:入力角度」を「0.82:0.12」の比率で按分
			mychar_anc_rotate = mychar_anc_rotate * (1.0 - ANCHOR_ROT_SP) + theta * ANCHOR_ROT_SP
		}

①では、移動量に応じたラジアンを取得しています。
ラジアン」ってなんやねん!
と思った方はこちらが参考になるかもしれません。
初心者のためのフラッシュレベルアップ講座
 
ようは、0〜360°という範囲が、0〜2πという範囲に置き換わっただけです。
 
②では、③の演算結果が「不自然」にならないかチェックしています。
ということで、先に③を見ておきます。
 
③では、「現在のアンカーの角度:キー入力角度」を「0.82:0.12」の比率で按分することにより、

  • 現在向いている方向が、動かしたい方向から「遠い」場合、「速く」そっちを向くようにする。
  • 現在向いている方向が、動かしたい方向から「近い」場合、「遅く」そっちを向くようにする。

という処理をしています。
 
これにより、微妙な角度調整を行えるようになっています。
 
そして、この場合は足しこんでいるので「左回り」をするようにしています。
 
ただ、単純に「左回り」をすると、
現在方向が「-0.785」、移動方向が「3.14」の場合、
このような不自然な動きをしてしまいます。

この場合「右回り」をしてくれると自然な動きになりますよね。
 
そのための準備が②です。
 
はてさて、②では何をやっているのかというと、、、、。
③で動かしたい方向は、「左回り」ですよね。

			if(absf(mychar_anc_rotate - theta) > MATH_PI){

その前に「右回り」してみたら、180°を超えちゃいました!
というチェックをしています。
 
反対回りしたら、180°を超えるということは、「右回り」の方が近い!
ということです。
そこで、現在向いている方向の意味を「反転」させています。

				if(mychar_anc_rotate > theta){
					// プラス方向に振り切る
					mychar_anc_rotate -= MATH_PI * 2.0
				}else{
					// マイナス方向に振り切る
					mychar_anc_rotate += MATH_PI * 2.0
				}

正直kenmoは文系で頭悪いので、これを理解するのに、
紙に円を何回も書いたり、計算したりで、3時間以上かかってしまいました、、、。
 
うーん、数学力が足りないですね。
 
 
…それはさておき、次です。(622〜626行目)

		// 移動量0
		anchor_sx = 0.0
		anchor_sy = 0.0
		// 自機から40の位置にアンカーを配置
		anchor_x = mychar_x + 40.0 * cos(mychar_anc_rotate)
		anchor_y = mychar_y + 40.0 * sin(mychar_anc_rotate)
		// プレイヤーと同じ方向を見る
		anchor_rotate = mychar_anc_rotate

そのままですね〜。
 
最後にアンカーの発射処理です。

		// 射出
		if(((stick_state | key_state) & 16) == 16 & ((ostick_state | okey_state) & 16) == 0){
			anchor_state = ANCHOR_STATE_THROW
			// プレイヤーの向いている方向に飛ばす
			anchor_sx = ANCHOR_SHOT_SP * cos(mychar_anc_rotate)
			anchor_sy = ANCHOR_SHOT_SP * sin(mychar_anc_rotate)
			// プレイヤーに反動をつける
			mychar_sx = -anchor_sx * ANCHOR_REACT_FORCE
			mychar_sy = -anchor_sy * ANCHOR_REACT_FORCE

			ds_play SND_ANCHOR_SHOT
			return
		}

これもそのままですー。
 
 
 

発射

発射処理を見ていきます。(641〜666行目)
 
643〜644行目は、移動量を座標に反映しています。

		anchor_x += anchor_sx
		anchor_y += anchor_sy

 
646〜656行目では、プレイヤーがアンカーを引っ張る力を計算・反映しています。

		// ①アンカーからプレイヤーまでの距離
		vxx = mychar_x - anchor_x
		vyy = mychar_y - anchor_y
		if(vx != 0.0 | vy != 0.0){
			// ②プレイヤーがアンカーを引っ張る力を足しこむ
			anchor_sx += vx * ANCHOR_SHOT_CONTROL
			anchor_sy += vy * ANCHOR_SHOT_CONTROL
			// 正規化
			rr = sqrt(anchor_sx * anchor_sx + anchor_sy * anchor_sy)
			anchor_sx = anchor_sx * ANCHOR_SHOT_SP / rr
			anchor_sy = anchor_sy * ANCHOR_SHOT_SP / rr
			// アンカーの向きを修正
			anchor_rotate = atan(-vyy , -vxx)
		}

①で距離を出しておいて、②で「ANCHOR_SHOT_CONTROL」(0.5)を掛けて足しこみ、
正規化して、「ANCHOR_SHOT_SP」(16.0)の速度に設定しています。
 
最後に回収のチェックをします。(658〜664行目)

		// 発射をキャンセルして回収
		if(((stick_state | key_state) & 16) == 0){
			anchor_state = ANCHOR_STATE_RETURN
		}

		// 伸びきった場合、回収
		if(sqrt(vxx * vxx + vyy * vyy) > ANCHOR_LENGTH_LIMIT) : anchor_state = ANCHOR_STATE_RETURN
		// 画面外に出た場合、回収
		if(anchor_x > 640.0 | anchor_x < 0.0) : anchor_state = ANCHOR_STATE_RETURN
		if(anchor_y > 480.0 | anchor_y < 0.0) : anchor_state = ANCHOR_STATE_RETURN

 
 
 

掴み

掴み状態です。

		if(((stick_state | key_state) & 32) == 0){
			// ボタン2を離している場合、普通に動く
			anchor_sx += ANCHOR_GRAB_SP * vx 
			anchor_sy += ANCHOR_GRAB_SP * vy
			anchor_sx *= ANCHOR_GRAB_SPWEAKEN
			anchor_sy *= ANCHOR_GRAB_SPWEAKEN
		}else{
			// ボタン2を押している場合、ゆっくり動く
			anchor_sx += vx * MYCHAR_SPEED2
			anchor_sy += vy * MYCHAR_SPEED2
			anchor_sx *= MYCHAR_SPWEAKEN
			anchor_sy *= MYCHAR_SPWEAKEN
		}

定数はそれぞれ、

  • ANCHOR_GRAB_SP(1.6)
  • ANCHOR_GRAB_SPWEAKEN(0.9)
  • MYCHAR_SPEED2(0.2)
  • MYCHAR_SPWEAKEN(0.5)

と定義されています。
 
ここでも、「移動量の更新」→「減衰」というパターンを使って、
面白い動きを作り出しています。
 
681〜682行目では座標の更新をしています。

		anchor_x += anchor_sx
		anchor_y += anchor_sy

です。
 
684〜696行目では、アンカーの「近づきすぎ」「離れすぎ」のチェックをしています。

		// ①アンカーからプレイヤーまでの距離
		vxx = mychar_x - anchor_x
		vyy = mychar_y - anchor_y
		// 正規化
		rr = sqrt(vxx * vxx + vyy * vyy)
		if(rr < 20.0){
			// ②近づきすぎなので、自機から「20」離す
			anchor_x = mychar_x - 20.0 * vxx / rr
			anchor_y = mychar_y - 20.0 * vyy / rr
			// 減衰
			anchor_sx *= 0.8
			anchor_sy *= 0.8
		}
		if(rr > ANCHOR_LENGTH_LIMIT){
			// ③伸びきったら、それ以上伸びないようにする
			anchor_x = mychar_x - ANCHOR_LENGTH_LIMIT * vxx / rr
			anchor_y = mychar_y - ANCHOR_LENGTH_LIMIT * vyy / rr
		}

 
 
 

回収

718〜799行目が回収の処理です。

		// アンカーからプレイヤーの距離を求める
		vxx = mychar_x - anchor_x
		vyy = mychar_y - anchor_y
		// 正規化
		rr = sqrt(vxx * vxx + vyy * vyy)
		// 速度算出・設定
		anchor_sx = ANCHOR_RETURN_SP * vxx / rr
		anchor_sy = ANCHOR_RETURN_SP * vyy / rr
		// アンカーの方向調整
		anchor_rotate = atan(-vyy , -vxx)
		// 座標の更新
		anchor_x += anchor_sx
		anchor_y += anchor_sy

		if(rr < 32.0) {
			// 回収完了→スタンバイ状態へ
			mychar_sx = anchor_sx * ANCHOR_REACT_FORCE
			mychar_sy = anchor_sy * ANCHOR_REACT_FORCE
			mychar_anc_rotate = atan(-vyy , -vxx)
			anchor_state = ANCHOR_STATE_STANDBY

			ds_play SND_ANCHOR_RETURN
		}

そのまま、、、ですね。
 
 
 

当たり判定

最後に、アンカーの当たり判定の部分を、簡単に説明しておきます。
フローは以下のようになっています。

  1. 敵の配列(enemy)を全部チェック
  2. 当たり判定をし、当たっていたら以下を実行。そうでなければ1に戻る
  3. 発射状態なら掴む。1に戻る
  4. スタンバイ状態なら、はじかれる。1に戻る

大体こんな感じです。
 
 
 
 
とりあえず、きょうはここまでですー。