物事には自然な順序があり、それはもちろんプログラムにもあるだろう、という部分から考えてみました。コードの自然な流れの感覚を身につける、ということについて。
プログラミングにおける基本原理。
プログラミングにおける基本要素としては、変数、関数であったり、配列やfor文などの繰り返し文、if文など条件判定文といった、いわゆるコードを構成する基礎部品があって、まずそれらを覚えることが大事だと思います。
しかし、スルーされてしまいがちなことがあると思います。プログラミングというのは本質的には英語の文章、命令文なので、それを考えれば当然といえば当然なのですが・・・
上から下へ、左から右へ読まれる、実行される。
ということです。
当たり前すぎるというか文章としての大前提(より細かく言えば英文法)なわけですが、これをしっかりと意識するようになってから、プログラミングを自分で書こうとするときに楽になった気がします。要はコードの自然な流れを意識する、ということであり、読む上でも書く上でも大事なのではないかと。
山に雨が降り、それが川となって流れ出し、ときには湖になったり、いくつかの流れに別れてしまうこともあるが、最終的には全て海へと流れこむ、というのが自然の摂理であり、プログラミングの流れもこのような性質を持っていると思います。
もちろん繰り返し文(for Statement, while Sstatement)に関しては、この法則は当てはまらず、川の流れをくみ上げて、上に行ったり来たりという人為的な動きがあるわけで、そこらへんが大きな挫折ポイントの一つではないかとは思います。しかし基本的にはやはり上から下へ、です。関数、メソッドを呼び出す、という事は川が分かれるように、横道に逸れて、処理が終われば、またメインの川に合流する、というようなイメージで考えています。
また今となってはGUI(グラフィカル・ユーザー・インターフェイス)主体のプログラミング、アプリケーションづくりがほとんどなわけで、そうなると処理の流れは必ずしも上から下に流れている、という例え方は正しくないのかもしれません。
むしろ飛び飛びでしょう。GUIで操作する上では、操作する人が特定の順序に従って動くわけでもないし、実際の処理も各イベントハンドラーを跨ぐことが当たり前なので、このような上から下へというような意識は持ちにくいんだと思います。そうはいっても、少なくとも狭い部分にはおいては、上から下にが原則なので、無視は出来ないことだと思います。
あえて今、CUIをやることの意味。
CUI(キャラクタ・ユーザー・インターフェイス)visual studioでいえばコンソールアプリでもなければあまり意識することではないのかもしれません。それでももちろん関数に飛んだりはしますが。自分はGUI世代であるので、いまさらCUIなんてとは思いましたが、この原則を身につけるのにはCUIの方がいいのかなーと。static void mainの中は明らかに上から下に流れていきますからね。簡単な数当てゲームを作ったりしたので、いずれこのブログでそのことを書こうと思います。
とにかく上流から下流へと川が流れるようなイメージも持つことで、プログラミングにおける自然な流れ、力学的なものがわかりやすくなった気がします。結局はコンピュータ様にご理解いただけるように、厳密で順序だてられた命令文を書くのだ!位の気持ちじゃないといかんのかなと。
コンピュータへの指示を明確に順序付ける、ということを意識する、そういう思考法になれるということが大切なような気がします。
Unityの場合であれば、collisionやtriggerの関数において、条件処理を書くときに複雑になりますので、そうした上から下への流れを意識してコードを書くことがより大切かなと思います。(プレイヤーにどう反応するか?壁などに当たったらどう反応するか?)
returnで早めに返して、流れを断ち切って関数を終わらせたり、あるいはどの条件にも引っかからなかった、その他大勢の処理を下の方に書いたり。
実際のコードで流れを見る。
具体例を挙げましょう。
以下のコードはマリオっぽいゲームのモノです。敵のダメージ判定の接触に関する部分の簡易版となります。上から踏むと倒せるが横に当たるとダメージを受ける、という雑魚キャラのトリガーに関するコードです。プレイヤーの体力がゼロになって、次に敵に当たるとゲームオーバーという設定になってます。親切設計です。
大まかな流れとしては、
・地面、見えない壁の接触は無視する。(returnではじき出されます。)
・プレイヤーに触れた際の条件によっておきること
・プレイヤーに触れたら必ずおきること
除外すべきことを先に書く、この場合はまず触れたのが障害物かプレイヤーか?特定の条件でおきることは真ん中に、この場合はプレイヤーの残り体力。
そのどちらの条件に当てはまろうが、結果としてすべての時に必ず起きることは下の方に書く、という段階に沿って書かれています。
void OnTriggerEnter2D(Collider2D col)
{
分岐点1-1
//地面、見えない壁の接触は無視する。
if(col.gameObject.tag == "Boundary" || col.gameObject.CompareTag("Ground"))
{
return;
}
分岐点1-2
//プレイヤーに接触時の処理、プレイヤーに踏まれて倒されたら発生しない。
else if ((col.tag=="Player" )
{
分岐点2-1
if (PlayerController.playerHP > 0)
{
//プレイヤーの体力が0以上の場合
PlayerController.playerHP -= damage;
PlayerController.playerHP = Mathf.Max(0, PlayerController.playerHP);
gameControll.HPslider.value = PlayerController.playerHP;
}
分岐点2-2
else
{
//プレイヤーの体力がゼロの時。死亡エフェクトを発動させる。
playerControll.DeathByDamage();
}
終着点(1-2を通ればここに辿り着く)
//何であれプレイヤーに触れたらおきること。爆発させて、表示を消す。
Instantiate(explodeFX, transform.position, transform.rotation);
Destroy(rb.gameObject);
}
}
ちょっとした図を描いてみました!
1-2を通ったものだけが関数の中に入れられ、なににせよ必ず終着点にたどり着く!ということです。分岐したものがまた最後に合流する、という流れですね!
まさに川の流れのように。
お後がよろしいようで。