「えぐぜりにゃ〜」ソース解説2
前回はざっくりと移動関数を見ましたので、今回は細かく見ていきます。
(※バージョンは「exelinya_0214.zip」です)
movemychar
まずはプレイヤーの移動関数です。(498行目から)
プレイヤーの移動関数は、以下の処理を行っています。
- ライフの自動回復
- キー入力処理
- 移動
- 当たり判定
ライフの自動回復
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 }
そのまま、、、ですね。
当たり判定
最後に、アンカーの当たり判定の部分を、簡単に説明しておきます。
フローは以下のようになっています。
- 敵の配列(enemy)を全部チェック
- 当たり判定をし、当たっていたら以下を実行。そうでなければ1に戻る
- 発射状態なら掴む。1に戻る
- スタンバイ状態なら、はじかれる。1に戻る
大体こんな感じです。
とりあえず、きょうはここまでですー。