Unityでインディゲーム道!

プログラム、Unity初心者がインディゲーム制作を目指して日々思うことなどを書き綴ります。

"Cuphead" Unity製の注目インディゲームの初感レビュー!!

いよいよ期待のインディゲーム、Cupheadが発売されました!

 ぶっ続けで3時間ほどプレイし、第一、第二エリアをクリアするかしないかの所までいったので、ひとまずその初感をザックリと書いておきたいと思います。

f:id:miur-us:20171003211414g:plain

以下全ての画像は実際のゲーム画面のスクショです。

いやー、おもしろい!!あと良い意味で難しい! 

 やる前に想定していたゲーム性とは少し違ったデザインでしたが、なぜそうしたかを考察すると、インディだからこその割りきりがあり、そこがプレイ面にも良い影響をあたえていると思いました。そこらへんの考察は後ほど。では順にまとめていきます!

※ゲーム製作者的観点の考察を多く含みます! 

 

どんなゲーム?

ガンシューティング2Dアクションです。主人公であるカップヘッドは悪魔の経営するカジノで魂を賭けたギャンブルに負け、見逃してもらう代わりにカジノへの借金滞納者の魂を回収するように命じられます。(ヘビーだ・・・)

 

 自分はPVを見た感じ、メタルスラッグのようなゲームなのかなー?と思っていました。つまり、ステージを進んでいって、ボスを倒して・・・という構成です。また同じく2Dガンアクションの名作、魂斗羅を思い浮かべた人もいるようです。(実はやったこと無いです、恥ずかしながら。ミニスーファミでやりますぜ!)

f:id:miur-us:20171003211529j:plain

 しかし、実際には通常ステージはあるにせよ、それは決して本筋ではなく、マップ各地に偏在する、ボス(つまり滞納者)を訪ねて直接対決を繰り返していく、という言わば、ボスラッシュ的な構成だったのです。(各ステージに一体なので直後に連続ではありません。が実質的にボス戦が続きます。)

 

 ある意味、予想を裏切られた形ではありますが、しかし、やってみるとその絶妙な難易度で死んで、覚える、そしてボスを打ち負かすという明確かつシンプルな目標と素晴らしい映像、アップテンポなスイングジャズが融合した、かなりの高揚感を味わえるゲームになっています。

  

個性的なゲーム

 まずヴィジュアル、グラフィックから唯一無二の個性を放っていますよね。PVの時点でスゴいな!という印象でしたが、実際やるとさらに良いです。というか、ゲームとしても気が抜けないゲームなので、その映像に気を取られてしまうとクリアできないです。アニメーションに見とれている暇はありません(笑)

 

 1930年代のカトゥーンアニメ調のグラは群を抜いたクオリティです。そこは説明する必要は無いでしょう!問題は、じゃあゲームとしてはどうなのか?ということになります。見た目も大事だけど中身も大事ですから!

 

 インディゲームの場合、どうしても『~っぽい』とか『~の影響うけてるなー』という印象を持つことが良くも悪くも多くあります。繰り返しますが、自分はメタルスラッグっぽい感じなのかな?と想像してました。

 

 しかし、実際のゲームとしてのデザインは、スーパー・マリオブラザーズを初めとした往年の2Dゲームからの影響をもちろん感じさせながらも、Cupheadとしての独自性をきちんと打ち出せている、と感じました。つまりボスを倒すということに重きをおいたゲーム性が、CupHeadらしさを生んでいるのではないか?ということです。

f:id:miur-us:20171003211610j:plain

ドラゴン、そして宙に浮かぶ足場。これはまさしくロックマンのメカドラゴン!

今のところ、一番好きなボスかもしれません。

 

ゲームデザインについて

  いわゆる伝統的な2Dアクションというのは、マリオしかりロックマンしかり、障害物レースのように様々な仕掛け、ザコ敵を避けながら(あるいは倒して)ステージを進み、中ボスを倒していき、各ステージを走破して、最終的にラスボスを倒す、というような構成になっています。つまり、アスレチックを攻略していくようなゲームです。

 

f:id:miur-us:20171003211715j:plain

 しかし、CupHeadというゲームは明らかにボス戦そのものをゲーム体験の主軸においています。Cupheadにはコインがありますが、その数は限られており、通常ステージの中に置かれています。しかし、通常ステージは少なく、1つのエリアにそれぞれ2ステージずつしかありませんでした。(この後のエリアでは増えるかもしれません。)ボス戦ステージの方が数が多いのです。

f:id:miur-us:20171003211854j:plain

  コインは主人公キャラの新たな技を買うために存在しており、それが必要ないのであれば、別に通常ステージをクリアする必要もない、という扱いです。初見プレイでは間違いなく、買っていかないとツライです。チャージショットがお気に入り。(一方、マリオでのコインの扱いは100枚集めれば一機増えるという救済措置的な存在)

 

f:id:miur-us:20171003212357j:plain

  これってなかなか画期的ではないでしょうか?ボスは与えたダメージによって、基本第三段階まで変化します。(上写真はスライムの第二段階)これはよくあるパターンですよね。親切なことにプレイヤーが負けると、どこまで行ったかをリトライ画面で示してくれます。 難しいけど何度も死んで覚えていき、馴れると遅くとも三分以内に倒せる、という短い時間に密度の高いゲーム体験が出来るというデザインになっています。

f:id:miur-us:20171003211805j:plain


 思ったのは、ダークソウルシリーズのような良い意味での死にゲーです。しかし、このゲームには、ダクソにもあった探検要素はほとんどなく、ボス戦の死んで覚えて打開する、という部分を凝縮した、割り切ったゲームであると言えます。

 

 つまり、このゲームは伝統的な2Dアクションではなく、各地に潜む個性豊かなキャラクター達に殴り込みをかけて、その命を奪うという非常にシンプルなゲームです。もちろんボスを倒すのには一筋縄ではいきませんので物足りなさは一切無いです。

 

 あとやりこみ要素がきちんと用意されています。各ステージにはスコアが設定されており、プレイによってFからA(A以上もあるらしいです。)までの成績がもらえます。ただクリアするだけでも大変なのですが、よりやりこみたい人への準備も怠っておりません! 繰り返し遊べる良いゲームだと思います。

 

インディならではの割り切り

  インディゲーム開発というのは、資金面においても、作業人数においても、そう工数を多く取れるものではありません。アニメに関しては、手描きということで手は掛かっているとはいえ、ゲーム全体における一貫したレベルデザインというのは、インディにとって困難な作業です。

 

 なので、ボス戦という通常のゲームであれば、合間の要所にあるモノをゲームのど真ん中に据えることで、レベルデザインの負担を押さえているのではないのかと思います。(おそらくステージ構成よりもボスの攻撃パターンを考えるほうが負担は少ない?)とは言っても、アニメ的演出は各ボスごとに相当凝っているので、手を抜いているというわけではありません。そちらの方を優先した、ということだと思います。

 

  これはリソースの限られたインディならではの割り切りだと思います。しかし、大事なことは、その割り切りがゲーム性そのものにおいても、良い影響を与え、個性を獲得しているということです。

  

Unityやるじゃん!

 間違いなく、Unity製のゲームの中での現時点でのトップクラスの作品に入ると思います。世界観の大切さだけでなく、ゲーム性をも両立させる、という言わばインディにおける最大の課題というものをクリアしているように思えるからです。

 

 もはや、Unityは単に手軽にゲームを作るための汎用ゲームエンジンではなくなりつつあるのではないでしょうか?もちろん、そういう用途でも今後使われるとは思いますが、これだけのゲームが作れるのですから!!

  

 間違いなく多くのUnityユーザーの指針となるゲームだと思います!

 

 

神ゲー"じゃんけん"でプログラミングを考える!!

神ゲーの1つである、ジャンケンをプログラミングで再現することで、プログラミングの最初から終わりまでの一連の流れをステップごとに追って説明していきます。それによりプログラミングとはなんだろうということを考察する内容です。

 ジャンケンは分かりやすいゲームではありますが、それをちゃんとプログラミングするとなると、初心者にとってそれほど簡単ではありません。 

f:id:miur-us:20171001211150g:plain

 完成したプログラムはこんな感じです。コンソールアプリになります。コンソールはとっつきにくいかもしれませんが、馴れてしまえば純粋にコードにだけ集中できるので練習にはふさわしいフォーマットだと思います。

 文字だけとは言え、最低限のジャンケンとしての体裁は整っています。こんなんでも、ちゃんとするにはそれなりに考えないとだめですからね。逆に人間はコレだけのことを『なんとなく』できるわけで人間もなかなかやるじゃん!という気になります。

 

 以前に書いた"プログラミングにおける5つの手順"に沿って、順を追って各工程ごとに書いていくことで、プログラミングという作業の流れというものも考えていきたいと思います。 それも本稿における重要な狙いです。

 

どんな構造なのか?

その前に、まず最初に概略について。

 たかがジャンケンですが、コンソールアプリとはいえ150行ほどになりました。コードは一応載せますが、分かりやすさのためにも先にプログラムの全体図を示したいと思います。 一枚の図にやるべきことをまとめています。 

f:id:miur-us:20171001223107p:plain 

 この図では、おおよその流れと各々の動作とそのための解決策をざっくりと書き表しています。ある意味では、リアルなジャンケンもこの図のような処理をしていることになります。いろんな機能を加えようとすれば、いくらでもできるとは思いますが(例えば、アスキーアートで手の形を表す、など)とりあえず最低限の機能のみのプログラムにしました。では5つの手順に沿って、考えていきたいと思います。

 

要件定義

まずはどんな設計、ルールにするかを考えます。(第一工程)

  • プログラム上のNPCとジャンケン勝負。
  • ベーシックに先に三回勝った方が勝ち。

 シンプルにコレだけになります。せめてwindowsアプリにしてグラフィカルにやった方がいいとは思いますが、プログラミングすることが目的だし、めんどくさいのでコンソール・アプリでの開発としました。

 

ファクタリング

次の第二工程は、"じゃんけん"というものの分析になります。

 ジャンケンは一対一、あるいは複数人が同時に『手の形』を出し合い、その優劣によって勝敗を決めるゲームです。手の形はそれぞれグー、チョキ、パーでいわゆる三すくみの関係(絶対的な強者がいない)になっています。

同じ手が出た場合は、『あいこ』となり、勝敗つかずのやり直しになります。

 

道具の選定 其の一

 分析も終わったので、その結果に基づき、ジャンケンをプログラム上で再現するために必要な道具を考えます。

 

 まずは互いの勝敗数を数えるための変数が必要です。単純な数を数えるだけなのでintタイプが二個あれば十分です。

int playerScore = enemyScore = 0;

 次は重要な『手の形』をどう表現するか?そして、二つの手をどう比較するかの問題です。プログラミングですので、つまりは数字に置き換わるわけですが、数字自体に意味はなく、自動的に振り分けられるような番号であり、プログラム中は変化することは無いので、(定数)つまりenum(列挙体)がちょうど良いということになります。

enum HandForm {

Goo = 1,

Choki = 2,

Pah = 3

}

 という風にかけます。数字を直接的に割り振ることもできますが、そうすると人間側がいちいち「1はなんだっけ?」という風に、その都度思い出さないといけなくなる可読性の低いコードになってしまいます。なので、列挙体を使って言葉に置き換えた方がより分かりやすいコードになります。

 

そして、勝敗の判定方法です。

まずはプレイヤーとNPCの手の形を記憶するオブジェクトが必要です。

HandForm playerHand, enemyHand

このように列挙体変数を作り、そこにそれぞれデータを格納します。

 

 この変数をintにキャスト変換し、その数字を使ってswitch文で勝負の判定を行います。それについては、後ほど説明します。

  

道具の選定 其の二

次は実際の勝負を何回行うか?という問題です。つまりループ処理になります。

 今回のゲームはどちらかが勝つまで続きます。ここで問題なのが、じゃんけんはあいこがあるので、何回勝負が続くかわからないということです。

つまり、どちらかが三回勝つまで勝負は延々と続くことになります。

 

 回数が決まってない、条件がtrueな限り処理が実行される、ということでwhile文がこの場合に適しています。条件は・・・

"どちらの勝ち星も共に2以下の限り"

とします。

while( playScore < 3 && enemyScore < 3 ){

//ジャンケン勝負の処理

}

 上記のように、プレイヤーの勝ちが3より小さい、"かつ" 敵の勝ち星も3より少ない、という風に書き表せます。これはつまり、どちらが3回勝った時点"false"偽となるのでループは終了するということになります。

 

道具の選定 其の三

 ところで、敵の手はどうやって決める?という問題がありました。普通に考えればランダムに出すのがゲームとしては親切でしょう。

 

 C#のライブラリにはRandomクラスがあるので、それをつかって1から3までの乱数を出します。それによって、列挙体から手を選ぶという方式になります。 

Random rnd = new Random();

 

 rndはインスタンスです。手の形に振り分けられた数字は1から3なので、rnd.Next(1, 4)とします。これにより、1から3までの整数がランダムに生成されます。

で、今回入力された整数にしたがって、手の形を返す関数を作りました。それに乱数を入力します。

enemyHand = DecisionHand(rnd.Next(1, 4));

これでランダムにNPCの手を決めたことになります。

 

ロジックを組み上げる

 概略図で示したようなロジックを考える作業です。コードを見ながらの方が説明しやすいので、コードは随時示していきながら、どういう風にロジックを組み上げていくかを考えていきます。コードの冒頭はコチラです。enumを最初に書いてます。

f:id:miur-us:20171001220332p:plain

 

まずは、敵の手を決めます。それについては具体的な内容は先に書いたとおりです。

 

 次にプレイヤーの手を決めます。数字キーを押してもらい、そこから整数を取得して、手を決定するための関数に入力します。

f:id:miur-us:20171001213837p:plain

 それがその関数です。HandForm列挙体を返します。(行数からも分かるとおり、一番下に記述しています。)switch文で入力された数字に基づいて対応した手の形を返す、という構造です。

 ちなみに数字のキーボードに振り分けられた数字は1のキーで49、2のキーで50ということになっているので、数値化する際は48を引かないとダメです。

f:id:miur-us:20171001214237p:plain 

 その後は、実際に手の形を互いが見せ合って勝負するのですが、何もせずに画面上に表示されると、入力した瞬間結果が見えるのでジャンケンらしくなりません。

 『じゃん!けん!ぽん!』というコールとリズムがなによりもジャンケンらしさを生みます。なのでThread.Sleepを使って、リズムを表現しています。コール部を再現するためのものが、上部写真のしたの方のコードになります。

Thread.Sleepを使うにはusing System.Threadが必要です。)

 

f:id:miur-us:20171001214919p:plain

 その後、瞬時に互いの手を表示させますが、色が着いていた方が見やすいので、Console.ForegoudundColor = ConsoleColor.Redという風に文字の色を変えます。

 見やすいインターフェイスというのは、実際のUnityでゲームを作る上でも大事なわけで、こういう細かな気配りの重要性を改めて感じます。

 

そして、いよいよ勝負の判定です。

 二人の場合、その手の組み合わせは9通りになります。なので見易さからいってもswitich文を二つ使って羅列するのがいいのかなと判断しました。

f:id:miur-us:20171001215840p:plain

 ここはもう愚直に書いているという感じで、本当ならもう少し短く書ける工夫があるのかなと思いますが、ひとまず。結果の判定文とスコアの処理をしています。アイコの場合は、スコアは動かさず無効試合となります。

 

 ここまでがいわゆる1ゲームの流れで、while文の中身になります。前述したようにどちらかが三回勝つまではこの流れが延々と繰り返されます。最初に貼ったGIFは自分が上手いことストレート勝ちしていますが、普通はけっこう続きます。

 

ループを抜けたら、最終的な勝者を判断して、勝者を称えます。

f:id:miur-us:20171001220813p:plain

  ループを抜けた時点では、どちらかのスコアが3を超えたという判断しか行っていません。なのでプレイヤーが勝ったかどうかを判断し、勝者の宣言をします。

 

コードを書く!

 実際のプログラミングの5つの手順の最後は、実際にコードを書く!ということになりますが、前項にてコードがあった方が分かりやすいのでそっちで出しちゃってますので、ここは省略します。

 

 要はコードを書くということは、コレまで書いてきたような作業を経て、初めて取り掛かれることであり、つまりはプログラミングという作業全体のうちの一部に過ぎないと言うことです。

 

 これは本ブログでは度々書いていることですが、今回考察したプログラミングはどういう作業で、どんなことを考えないといけないのか?ということは市販の本にはほぼ書かれていないことです。なんでなんですかね?やっぱちゃんと説明するのが大変だからでしょうか?自分を含めた初心者が望む説明ってこういうことだと思うんですが。

 

拡張性について

 というわけで、じゃんけんプログラムを作ってみました。がやろうと思えば、いろんな拡張機能が追加できそうです。

 例えば、今回のNPCは完全ランダムですが、プレイヤーが入力した後に自分の手を決めるという仕組みにすれば絶対勝てないゲームに出来ます。(ずるい!)

 

 それは現実的ではないですが、ほぼ完全ランダムゆえそれに気づくと確立論的な戦術をプレイヤーが立てやすくなってしまうので、場合によっては手の出し方を少し偏らせることでプレイヤーを惑わす、そんなAIも作れるかもしれません。

 

 まずそもそも文字だけじゃつまらん!という場合にはGUIアプリを作った方がいいですし、じゃんけんと言えども試せることはいろいろある!ということになります。

 

まとめ!

 以上でジャンケンを題材にプログラミングの一連の作業、流れを考察してみる!ということをやってみました。やっぱ丁寧にやるとなると、ジャンケンですらかなりの文量になりますね。でも、実際こうした記事を書いてみると、プログラミングをするにはどういう風に考えないといけないかを改めて考えることが出来てよかったです。

 

 

なぜプログラミングが分からなかったのかを振り返る!<入門への道 >

そこそこプログラミング出来るようになった現在から、右も左もわからなかった頃、入門者時代を振り返り、なぜプログラミングは分からないのか?ということについての根本的原因を明らかにするのが本エントリの狙いです。一般的な観点(非プログラマ的観点)からの考察になります。特に具体的なコードは載せませんが、前提とする言語はC#としています。

※原稿用紙15枚分ほどで長いッ!!。

 

暗号だから読めない

 英語を使った人工言語だから、やはり英語力はそれなりに必要なのではないか?というエントリを以前書いたことがあります。 

 単純な単語力以上に、英語をリアルなスピードで使える力(ちから)、タイプしていく力がより大事なのではないか?という仮説です。もちろん英語が出来なくても、プログラミングは出来るんですが、日本人にとっての大きな壁はそこにある気がします。つまり多くの日本人にとって英語は暗号みたいなもんだし、"二重の暗号"になってしまうのではないか?ということです。

 

 で暗号というのはつまり"コード"です、プログラム・コードのコードと同じ"code"
暗号における"コード"というのは、つまり言葉の意味を入れ替える、というタイプの暗号を指します。普段使っている言葉が、違う場所では違う意味で使われているとしたら混乱しますよね?白が黒という意味だったら大変なことになってします。

 しかしプログラミングでは、そんなことがたくさんあるわけです。

 

現実社会(リアル)とは違う意味

 最も代表的な例が、" = "(イコール)でしょう。本来ならば、左辺と右辺は"等しい"という意味で、数学的事実を述べるための記号です。

a = b ;  //現実社会においてはaとbは等しい、という意味だが・・・

 しかし、プログラミングにおいては左辺にある"変数"に対して、左辺の"変数"の値を割り当てる、アサイン(asign)する処理を表す記号と成ります。つまり、ここではbの値をaに割り当てていることになり、aとbは等しい、という意味ではないのです。(処理の結果、等しくはなるわけですが)

 

 もう、この時点でワケわかんないですよね?しかし、そういう決まりなんです。だから納得してください!と言われます。でも、そういう決まりなんですって言われて直ぐ納得できる人は少ないわけで。ここがプログラミング入門者にとってキツイ。


 そしてこの"変数"自体も違う意味ですからね。メモリの一部分に名前をつけて、プログラム内でデータの保存に使えるように確保したものであり、数学における変数とは厳密には違います。プログラミングにおいて"変数"は何かを入れる箱のように例えられますが、そのような性質から単に"変数"という言葉を割り振られただけです。"関数"なども同じです。(コンピュータ上のモノを数学用語を使って抽象化した結果?)

 

 何かに例えることによって、分かりにくいコンピュータ上の機構を人間に分かりやすいようにしよう!ということは多くありますが、そのたとえ話を本気にしすぎてしまうとかえって分かりにくくなってしまうのではないかと思います。

 

 このような普段使われている言葉、記号がプログラミングにおいては違う意味、概念として使われている、というのが混乱の元です。

 自然言語プログラミング言語は近づけようとはしてますが違うものです。(言語によって、どれくらい近いかは変わる。)なので、最初はその意味を理解して覚えないといけないわけですが、ダブル・シンキングが必要になります。2 + 2 = 5なのです。

 

テキストだからこそ想像力が必要

 なので最初はその暗号の読み方、つまり文法を学んでいくことになります。しかし、ただ暗号の意味を分かるようになってもコードは読めません。なぜか?小説を読むように想像力を働かせないといけないからです。

 

 要は表層的な記述を読んだだけではコードを読んだとはいえない、ということです。そのコードは何がしたいのか?それを実現するためにどんな手段を採用しているか?(ソリューション、アルゴリズムを読み取らなければいけません。で他人のコードを読むことが大事とは言え、もちろん最初は文法も理解し切れてないし、そういう背後にある考えや論理を読む、ということも初心者には難しいわけです。

 

 視覚的にプログラミングしていくソフトもあるでしょうが、基本的にはプログラム・コードはテキスト主体で、文字の集合体です。小説は文字だけですが、私達はそれを読むことで、様々な世界や物語を頭の中に思い浮かべることが出来ます。

 

 コードも同様に単なる文字の集まりであっても、確かにコンピュータ上には何らかの世界が展開されており、なんらかの仕事がされています。それは画面に文字を映す、という具体的なものかもしれませんし、大きな仕事を達成するための細かい、人間にはよくわからない仕事かもしれません。それらを想像する力が、最初はないから読めません。

  

 目には見えなくても、確かに存在し、直接的には人間には分かりづらいことであっても、具体的な何らかの処理をしているわけです。そのことを感じ取れるように、想像できるように知識を付けていくことが求められます。プログラミングの文法というのはあくまで書式上のルールでしかなく、逸れも大事ですが、物事の仕組みや論理的な思考の方が重要だと思います。

 

コンピュータと人間のギャップ

 プログラミングというのは、人間が手作業でやろうとすると面倒くさい、時間がかかる、あるいは事実上不可能な仕事をやってもらうための手段です。ザックリ言ってしまえ、コンピュータの計算能力を利用して、現実世界における問題を解決するための機械への命令文を書くことがプログラミングです。

 

 iPhoneSiriみたく話してやってもらうのが理想ですが、現時点では不可能です。しかしプログラミングを始めたばかり、あるいはやったことがない人は、このことが理解できません。なぜならプログラミングという作業と普段コンピュータを使ってやっていることが、結びつかないからです。コンピュータと人間がいろんな意味でどれほどかけ離れた存在か?ということがよく分かってないから、ということになります。

 

 昨今はディープラーニング・ブームで人工知能への熱が高まり、また先述のSiriなどもあり、どんどん人工知能が日常のものになってきつつあります。人口知能によって仕事が奪われる!のもいよいよ現実になるか?みたい話題もよく見聞きするようになりました。

 


 そうした流れの中で見て取れるように、世間的にコンピュータは人間に相対する存在であり、超える存在だと捉えられている、と考えることが出来ます。これは、ある意味では正しいし、しかし過剰評価の部分もあります。

 

 人間の友達、あるいは敵としての存在するコンピュータ、AIは古いSFから現代のアニメまで、よくモチーフにされます。しかし、そのようなレベルの人工知能を実現するには、まだまだ十数年の時間がかかるようです。オバマ元大統領が任期末期にGoogleの中の人からそのようなテーマについて聞いたようなので、実際そうなのでしょう。

 

 人間は何にしても、擬人化して捉えるような部分があります。特に日本人はそこらへんの気質は強いのかもしれません。人工知能を過剰に擬人化して、人間のようにとらえてしまうのは、少なくとも現時点では、コンピュータというものへの理解を妨げます。

 

ではコンピュータとはどんなもので、人間とどう違うのか?

そしてコンピュータを利用するとはどういうことか?

 

 利用に関して、たとえ話を石油による火力発電で考えてみたいと思います。火力発電は石油そのもの自体をエネルギー化するのではなく、石油を燃やして湯を沸かしてタービンを回して発電しています。実はコンピュータによる仕事もこのような転用に近いです。コンピュータの莫大な計算を計算以外(つまり電卓以外)の仕事で使う、ということで、コンピュータが主体的に何かやっているわけではないんです。

 

使う言葉も考え方も違う

 コンピュータの言葉は01であり、考え方は全て計算です。これが全てであり、だとすると、やはり人間とは明らかに異なった存在であると分かります。しかし、最初はこの事実を受け入れるのは以外と難しいです。

 なぜならワタシ達現代人は日々の生活の中で、当たり前にコンピュータを使いこなしている(ように思わされている)からです。あらゆることをスマフォでやっています。なので、プログラミングを学び始めて直面する様々な課題とそうした普段使っているコンピュータとのギャップがとてつもなく大きいものなっています。 

 Hello World!プログラムは普段使っているようなソフトを自分で作ってみたい!という人にとっては退屈すぎますが、データ(この場合は文字列)を"入力"し、ライブラリにある標準関数を使い、"出力"するというプログラミングにおける基本的なことを短いながらに網羅しているプログラムであり、実は凄いプログラムなんですが、どうしても「なんだ!この詰まらないプログラムは!」となってしまいます。

 

 そして、どんなコンピュータであれ全て0と1に構成され、計算によってあらゆるソフトは動いています。あんだけいろいろ出来てんじゃん!?0と1だけであんないろいろ出来るのはおかしくない?みたいに思うのが普通です。それもやはり障壁となるギャップのひとつです。プログラミングを学ぶ上では、まずそうした手軽にコンピュータを使うということと自分でプログラミングするということの落差を呑み込めるようになるのが大事なのかもしれません。

 

 数字で表せるモノであれば、最終的にそれらは0と1だけで表すことができ、コンピュータ上で取り扱うことが出来るわけです。(10進法から2進法への変換)なにより、現代のコンピュータはアホみたいに莫大な計算力があります。一秒間に何十億回という計算が出来ます。コンピュータに暗算勝負を仕掛ける人はいないでしょう。

 

 数字で表すことが出来て、かつ計算することが出来る問題であれば基本的にはコンピュータは解決することが出来るということになります。しかし、そうでなければ、そもそもコンピュータがそうした問題、仕事を理解することすら出来ないのです。

 つまり計算によって答えが出せる問題ならば、0と1で構成された計算式による命令文をコンピュータに実行してもらうことで解決できる、ということになります。

 

プログラミングの目的

 さて、実際のプログラミングにおいて、問題なのは数字で表す、つまりデータ化や、そうしたデータをどう組み合わせて、どう計算するか?を考えるのは人間ということです。"上手いやり方"を人間がコンピュータに教えないといけません。

(そうしなくて済むようになるかも?というのがディープ・ラーニングらしい。)

 

 プログラミングは単にコードを書くだけでなく、そうした領域の問題解決も含むわけです。と同時に、既存のコードにはそうした部分が含まれているのであり、コードを読むということは、コードの背後にある概念を理解する、ということになります。

 つまり、目の前にあるコードというのは結果の産物であり、それに至るまでには様々な工程を経ているということです。だから、コードを読むというのは難しいのです。

 

絵が書けます。音楽が聴けます、作れます。映像を見れます、作れます。

夢のようなことを実現するための処理をコンピュータはしてくれています。

 

 しかし、どう処理するかの命令は人間が書いています。人間のやって欲しいことというのは、コンピュータには基本的に理解しがたいことであり、コンピュータに分かるような形で命令を書かなければいけません。

 

 つまり、人間とコンピュータのギャップを埋める、というのがプログラミングの本質だということになります。なので、そこをまず理解することがプログラミング入門の一歩の気がしますし、それなりの時間が掛かるのかなと思います。

  

じゃあ、どうするべきか?

注意点と対策をまとめます。

 

 プログラム・コードは一種の暗号であり、人間側の都合とコンピュータ側の都合が交じり合っているので、惑わされない。分かりにくくて当然と考える。

 コンピュータ上でことを人間が分かりやすいように何かに例えることが多いが、あくまで例え話なので本気にしない

 コードを読むというのは、表層的に読んでも意味がなく、何がしたいか?どう解決しているか?を理解する必要があり、その分野に文法は直接的には関係ない

 むしろ、理想論的にはコードを書き始めた時点で、コンピュータにどう指示するか?という問題解決の手段は見つかっており、その解決法を文法に沿って翻訳したものがコードと呼ばれるものに過ぎない。(現実的には試行錯誤が必須。)

 hello world!は詰まらないプログラムだけど、そこで諦めない。

 やはり、それなりに時間が必要なので焦らず自分で考える力を付ける。

 文法を覚えきるより、簡単なプログラムを自分で書いていく方がいい。

 コンピュータと人間の違いを理解し、その差を埋めるように"お願い"するということを意識する。

 

あくまで個人的な見解も入ってますが、こんな感じでしょうか。

 

最後に

 という感じで未熟者がいろいろ講釈たれたわけですが、とにかく言いたいのは、

プログラミングおもしろいから、諦めずに頑張ろうぜ!ということ。

まぁ仕事としてやってないからってこともあるのかもしれませんが、でも馴れるまでが大変だったとはいえ、単純にいろいろできるようになるとやっぱ楽しいんですよね。

 

 もちろんプログラミングは手段であり、それを使って何をするかが大事なんですが、それ自体がおもしろいなぁって。

 

 Unityなんかは特にプログラミングできなくてもゲーム作れるよ!という触れ込みではあるんですけど、逆にプログラミングできると何でもできますからね。なんかプログラミングをネガティブなものとして捉えるのはもったいないと思います。

いや、実際まともな動作にするには大変ではあるんですけどね。

 

  

プログラミングってこういうことだよね?というチャート図を描いてみた。

結局、プログラミングとはどういう作業なんだろう?ということを一覧できるような図を描いてみよう、というのは以前から思っていました。

 

プログラミングを一概に説明するのは無理でしょう。でも初心者の頃にそういうのが欲しかったんですよね。大体でいいので、プログラミングという作業の概要をなんとなく掴めるような図です。

 

これをチャート図といっていいのか分かりませんが、一応上から下へと順に工程を説明するような形にはなっているので、いいのかなー?

 

f:id:miur-us:20170913013402p:plain

かなりでかくなっちゃいました。

こんな感じで改善の余地アリアリですが、とりあえず自分が今理解している範囲でのプログラミングという作業の俯瞰図として描けたと思います。

まぁごちゃごちゃしてます。あとオブジェクト指向を念頭に置いた図になっています。

 

これを具体的にゲーム制作に当てはめた図も描くと、より分かりやすくなりますかね。

それも近々。

 

この図を描いて改めて思ったことをまとめると、人間とコンピュータのギャップを如何に埋めるか?という一言に尽きるのかもしれません。

そして、やはりコードを書くというのはプログラミングの中でも一部分に過ぎないということです。もっと物事の本質を掴むチカラが大事なんだなーと。

 

話は変わりますが、コードを書けるの当たり前で、おもしろいゲームを作れるかは別問題である!ということを最近痛感してきています。

コードによる処理も大事なんだけど、やっぱグラフィック面が大事なんですよね。音とか曲はまだ自分で何とか出来るんですけども。 

まぁ、そのためのアセットストアですけども!^_^;

 

最後、少し話がズレましたが、こんな感じで楽しくやってます。

 

 

C#で"FizzBuzz問題"に挑む!Main内で五行達成。

FizzBuzzとは英語圏における言葉遊びで、複数人で数字を順に言っていくわけですが、3の倍数の時は"fizz"5の倍数の時は"buzz"3と5の公倍数の時には"fizzbuzz"と言うのがルールになっています。

FizzBuzz問題とは、この遊びをプログラミングで再現するというものです。その人が本当にプログラミングが出来るかどうかを試すのにちょうどいい"簡単な問題"とされるもので、面接などで出題されるそうです。あるいはいろんな条件を課して、力試しとして取り組むこともあるようです。

 

きっかけはこちらの記事です。"fizzbuzz"という遊びはなんとなく聞いたことがあった気がしますが、プログラミングでそういうものがあるのは初めて知ったので、興味が出ました。ググってみた所、会社内でコレを使ってコンテスト開いた!というブログ記事なんてのもあったので、それを軽く読んでみたり。

 

どちらの記事もPythonでのコードです。ほぼ経験ナシのPythonとは言え、答えのコードを軽くは見てしまってはいるんですが、せっかくなのである条件を付け加えて、C#でこのfizzbuzz問題のコードを書いてみることにしました。

以下はどのような試行錯誤を経て、最終的なコードへと至ったかの思案の推移をまとめたドキュメントであり、なるべくどのように思考したかを順を追って逐一説明する形で文章化してあります。

 

条件とは?

目標としては可読性や見た目を損なわず、できる限り行の少ないコードにする!ことに設定しました。

1から100までの数字をコンソール画面に表示し、ルール通りに特定の数字の時にはfizzやらbuzzを変わりに表示する、というコードです。

いわゆるウィキペディアなどにあるような模範的なコードではなく、C#として使えるものは全部使って出来るかぎり短いコードにするということです。

 

試験ではないので制限時間もないし、コード実行して結果を見つつ改善していく、ということも特に禁止してません。あくまで自分で考えて、コードを書くという試みです。

 

問題の分割!

さて、この問題は大きく分けて2つの個々の問題に分割することが出来ます。

  • 繰り返し処理
  • 条件分岐

まずは1から100までを繰り返し画面に映す、という問題です。

これはループ文、イテレーションによって実現できます。whileかforのどちらかを選ぶか、ということについては、自分はwhile文の中でincrement(インクリメント)すれば短くできるかも?ということでwhile文を選びました。しかし、それは誤りで普通にfor文の方を使った方が簡単に短く出来るので最終版ではforを選択しました。

 

カウント変数、イテレータをそのままConsole.WriteLineを使って繰り返し画面に表示すれば、ひとまず1から100まで画面に映す、という問題は解決できます。

for(int i =1; i <= 100; i++){

Console.WriteLine( i );

}

 

次に解決すべき問題は、数字を判断して、3の倍数の時はfizzを、5の時はbuzzという風に数字の代わりに文字列を映す、という問題です。

  • 3の倍数            Fizz
  • 5の倍数            Buzz
  • 3と5の公倍数   FizzBuzz
  • それ以外の数   カウント変数を出力 

これは素直にif文でそれぞれ条件判断すればいいわけですが、自分の課題はなるべく行数を少なくすることです。なので三項演算子を使うことにしました。

 

三項演算子とは

(条件式) ? trueの時の値 : falseの時の値

というもの場合によってはif文よりシンプルに(あるいは読みづらく)条件判定が書き表わせられるというものです。

 

これを使えばConsole.WriteLine()のカッコ内、引数として三項演算子の式を入れることが出来ます。これによって行数を少なく出来ます。後で説明しますが、これは一回挫折します。しかし、最終的には三項演算子を使うというアイデアを実現することが出来ました。見当違いではなかったということになります。

Console.WriteLine( a > b ? a : b  );

//aの方が大きいならaがそうでないならbが表示される。

というわけで、この問題を解決するための準備は整いました。

 

解決に向けて その1

自分はwhile文を選んでしまったので、まずwhileの条件式の中でincrement出来るかの確認から始めました。一回試したかったというのもあります。

カウント変数は文字数を減らす意味でもnumberの頭文字のnとしました。

int n = 0;

while( n++ < 100){

Console.WriteLine(n);

}

です。

インクリメントは前置き後置きがあって、演算子や比較子を実行する前に増やすか後に増やすか選ぶことが出来ます。

どちらでも問題はないようです。後置きだと100との比較後にincrementが実行され、その後に、内部の処理が実行される、という流れになります。

ちなみにVisual StudioはConsole.ReadLine()などで受付待ちさせないと、処理終了後すぐ画面が閉じてしまうので付け足していますが、除外して行を数えています。

f:id:miur-us:20170822233900p:plain

f:id:miur-us:20170822233815p:plain

というわけでひとまずは最初の問題は解決です。しかし先ほど書いたように、最終的にはwhile文は捨てて、for文を使います。

 

解決に向けて その2

三項演算子です。それぞれの倍数は%(剰余算)を使って表現出来ます。倍数はつまり、その数で割り切れるということであり、%の答えが0になります。

6 % 3 == 0 (6を3で割った余りはゼロ)

 

で最初に思いついたアイディアを形にしたのが、この二行になります。

f:id:miur-us:20170822234305p:plain

キーポイントはConsole.Writeです。これは改行されません。つまり、3,5の倍数の時は続けてbuzzが表示されることで、Console.WriteLine("fizzbuzz")を省略出来ます。これは我ながら良いアイデアだと思いました。つまり3,5の公倍数の時には"FizzBuzz"を出力する!という個別の処理文を書く必要がなくなるので省スペースになります。 

f:id:miur-us:20170822234238p:plain

これを実行すると、Fizzの後ろに3が出力されてしまっていますが、それなりに上手くいっているように思えます。しかし、どうしても3の倍数を出力しない方法が、この時点では思い浮かびませんでした。

 


なので、諦めて素直にif文を使ったコードにすることにしました。

continue文で、その時点でループを強制終了して次のループへ移行します。

f:id:miur-us:20170822234441p:plain

3と5の公倍数を表すのに素直な表現は

if( n % 3== 0 && n % 5 == 0 ){}

になりますが、これは圧縮出来ます。

if( n%3 + n%5 == 0 ){}

どちらも0であるならば、その和も0になるのだから、これでも条件としてはオッケーなはずです。この後は素直にそれぞれの条件を判断し、相応しい数字、文字列を移すようにします。というわけで、完成かと思いましたが、最後の文が三項演算子で省略できると閃きました。上の写真の最後の文は元々は

if( n % 5 == 0 ) Console.WriteLine( "Buzz" );

else Console.WriteLine( n );

でした。

つまり、ここをConsole.WriteLine( n % 5 == 0 ? "buzz" : n.ToString() );としました。

三項演算子の場合は、nをToStringを使って文字列に変換することが求められます。

まさしく最初に思いついたアイデアをそれなりに活かすことが出来て満足でした。

f:id:miur-us:20170822234836p:plain

 

まだ終わってない!!

というわけで、できたー!と満足していたんですが、やはり三項演算子を活用した更に短いコードを諦めきれず、そしてやはりfor文の方が短く済むということで、再度挑戦しました。改めてというか、出来た後一時間後くらいなんですけども。

for ( int i = 1; i <= 100; i++ ){

//判断し、画面に映す処理

}

とりあえず、最初の改善案はfor文に変更することです。こちらの方が分かりやすい!

そして、最初のアイディアの大きな問題はfizzが打ち出された後、そのまま流れでカウント変数がプリントされてしまうことでした。

 

しかし、continueを使って次のループに飛ばす、という技を第二案で使っていたので、これを使えばいけるだろうと考えました。

 

何かもう一行、条件が必要です。fizzの条件を再確認しましょう。3の倍数であり、5の倍数出ない時です。つまり、複数の条件ですから、三項演算子は使えません。そうだ!別にif文使えばいいだけじゃん!と思いつきました。

f:id:miur-us:20170823001506p:plain

三項演算子の間にif文を書いてはいけない、なんていうルールはないです。自分自身の思い込みによって、視野が狭くなっていました。

if( i % 3 == 0 && i % 5 != 0){ Console.WriteLine(); continue;}

fizzは改行されませんが、Console.WriteLine()を実行し改行処理。continue;でイテレーションを飛ばします。これでcontinueも一個に削減。

以上で、FizzBuzz問題のコードをMain内で五行にすることが出来ました。 

 

まとめ!

今回の試みを経験したことで、やはりアイディアは大事だし、一回ダメでもいろいろ試してみると道は開ける、ということを強く実感しました。結果的に最初の三項演算子を使ってコードを圧縮できるかも!?という算段は外れてはいなかったので。

いろいろ勉強になりました! 

応用編として、%を使わない、とかいろんな縛りプレイがあるみたいですね。

そこらへんもいつか挑戦してみたいと思います。

 


と思ったら、記事を書いている間に案を思いつきました。

割り切れる、というのは余りがない、あるいは割った商の小数点以下がないということなので、%(剰余算)を使う代わりに次のように書き表せます。

もし i / 3.0f - i / 3 == 0 なら

3.0fつまりfloatにしないと、商が切り捨てられた整数になってしまいます。逆にi / 3とすることで、小数点以上のみに出来るのでこれを引けば、少数点以下を表せます。

もしiが1の場合、1.0 ÷ 3.0 = 0.33333...で1 ÷ 3は少数点以下切捨てで0でよって、少数点以下が存在するので(0.333... - 0 = 0.333...)1は3の倍数ではない、という風に判断することができます。

iが6の時は、(6.0 ÷ 3.0 = 2) - ( 6÷ 3 ) = 0.0よって、3の倍数である!となります。

あとは先ほどの三項演算子の条件式の中身と取り替えるだけです。

f:id:miur-us:20170822235029p:plain

というわけでFizzBuzz問題の研究を終わります。やっぱ自分で考えて解くと爽快感がありますね。

 

UnityでGetComponentを使って"参照"を得るということ。Unityにおけるオブジェクト指向とは、的な。

Unityでのスクリプト作成、コーディングにおいて重要であり、かつ基本的なメソッドである"GetComponent"について、まとめてみます。

そして、そもそも"参照(リファレンス)を得る"ということはどういうことか?という基本的な部分についても書いてみたいと思います。

 

GetComponentオブジェクトの参照を得るためのメソッド

 

オブジェクトとは

まず"オブジェクト"とは"クラス"の変数です。"クラス"は設計図のようなモノであり、そこから作られた実体、みたいな説明がありますが、結局は"クラス"は複数の変数、メソッドを保有できるデータ・タイプ(いわゆる"型")なワケで、そのタイプのデータを格納するための変数が"オブジェクト"です。

変数とは少し違うんだぞ!という区別をするために"オブジェクト"あるいは"インスタンス"といっているのであって、何らかのデータを格納するための器のようなものという性質は同じです。私達人間も様々なデータを内部に保有しています。

 

Unityでコードのコンポーネント"スクリプト"と呼んでいますが、要は"クラス化された、あるひとつのコード"であるということに注意が必要です。つまり、プログラム全体のコードの一部分です。またUnityのスクリプト上で、"スクリプト"を含む他のコンポーネントのオブジェクトを作るということは、他のクラスのオブジェクトを作るということにもなります。(ここは後で詳しく説明します。)

 

変数である以上、宣言して使うわけですが、それだけでは"中身"は入っていません。

int year = 2017;

このように値を代入しなければいけません。

 

オブジェクト指向とは"クラス"で分割し、そこからオブジェクトを作り、オブジェクトを中心にプログラムを動かしていくことで、現実世界の概念や自然言語に近づける、というものです。当然、オブジェクトには中身が必要です。

問題はUnityにおけるオブジェクトに入れる中身とは何か?ということです。つまり、それは実際のゲームオブジェクトにアタッチされているコンポーネントになります。

(もちろんプログラミングにおける一般的な意味でのオブジェクトも作られます。)

 

Unityにおける"コンポーネント"

直訳すれば『部品』となりますが、Unityでは何かしらの機能のまとまり、ゲームオブジェクトに機能を持たせるための部品になります。

要は物理演算のための"Rigidbody"や当たり判定の"コライダー"、音を出すための"オーディオ・ソース"、などのUnityで(比較的)手軽にゲームを作ることの出来る、有難い部品の数々です。

と同時にプログラミング的にこれらは全て"クラス"でもあります。つまり、スクリプト上でUnityのコンポーネントを動すには、そのコンポーネントの"クラス"からオブジェクトを作る必要があるということです。

 

そして、上記で書いたようにオブジェクトには中身が必要です。繰り返しますが、それがゲームオブジェクトに実際にアタッチされているコンポーネントになるわけです。

つまり、その中身がなければ、

Rigidbody rb; // Rigidbodyのオブジェクト、インスタンス

rb.AddForce( Vector3.up * jumpPower ); 

とコードを書いても、Unity上では何も起こらないわけです。ここでいう"rb"というRigidbodyのオブジェクトに中身を入れる必要があります。

 

つまり、オブジェクトにアタッチされているコンポーネント"参照"(リファレンス)を得る必要があります。(例えばプレイヤーのゲームオブジェクトにアタッチされているRigidbodyのことになります。)

 

そもそも"参照"とは?

"参照する"とは単純に言えば、結びついている、関連しているということです。具体的な行為としては、何らかの本を参考にするみたいなことです。より詳しく言うと、"どこ"にある"どの本"の"何ページ目"の"何行目"を明示した上で、参考にする、というのが"参照する"ということです。

プログラミングにおいても同様で、より正確な所在地を指し示しつつ、参考にしている、引用している、ということになります。

 

プログラミングにおける"参照"(リファレンス)

少しプログラミングそのものに関する話です。C#では変数のデータの格納の仕方には二種類あります。値そのものを格納するデータ・タイプ、実際の値は格納せず、データ群が置かれたメモリ上のアドレスを格納するリファレンス・タイプの二つです。

 

オブジェクトはリファレンスタイプになります。つまり、オブジェクトは正確には該当する中身の"ある場所"を格納しているということになります。オブジェクトはメモリの住所を指し示している、とも言えます。どこにあるか分かっているからこそ、データやメソッドを動かすことができるということです。

 

GetComponentの働き

というわけで、いよいよ本題です。つまりGetComponentはUnity上の該当するコンポーネントどこにあるかをオブジェクトに示す(返す)ためのメソッドです。

スクリプト上のオブジェクトが真の実体を持つには、オブジェクトがUnity上のコンポーネントを指し示すことが出来なければいけないのです。

先ほどの例で言えば、

Rigidbody rb;

 

void Start(){

        rb = GetComponent<Rigidbody>();

}

 

これはRigidbodyのオブジェクトを作り、このスクリプトがアタッチされているゲームオブジェクトに同じくアタッチされているRigidbodyコンポーネントの場所を"rb"に指し示す、という内容になります。

注意としては、GetComponentの効力は、基本そのスクリプトがアタッチされているコンポーネントに限る、ということです。(ChildやParentは指定すれば可)

上の例が、プレイヤーキャラにアタッチされた"PlayerController"スクリプトだとすると、そのプレイヤーキャラにアタッチされたRigidbodyを呼び出していることになるのです。つまりrbというオブジェクトが、当コンポーネントの参照を得て、このコード上において実体をもった、ということになります。

 

アタッチされたゲームオブジェクト以外の参照

しかし、それだけでは参照を得るのに限界があります。例えば、敵キャラにアタッチされた"EnemyContoroller"等の他のスクリプト(他の"クラス")の参照を得ることはできないのでしょうか?でなければ、"PlayerController"側から、敵キャラを動かすことができないことになります。(攻撃したら吹っ飛ばす、みたいな)

 

スクリプトの中で、そのスクリプトのオブジェクトを作り、きちんと"参照"を得れば可能です。それにはいくつかの方法があります。

 

publicとして宣言して、インスペクター上にドラッグする。

public Rigidbody playerRb;

これは便利ですが、実際にシーンにおかれているゲームオブジェクトである必要があります。例えば、必ずシーンに存在するプレイヤーを参照する際に便利かもしれません。しかしドラッグし忘れるとnull(参照ないですよ!)エラーが発生します。

 

GameObject.FindWithTag()で引っ張ってくる。

タグの設定をする必要がありますが、確実な方法です。基本的にプレイヤーには、Playerタグを設定しておけば、FindWithTagでそのプレイヤーのゲームオブジェクトの参照を得られます。つまり、一回、ゲームオブジェクトのオブジェクトにそれを移して、そのオブジェクトからGetComponentする、という形になります。

GameObject playerObject = GameObject.FindWithTag("Player");

if(playerObject != null)

           rb = playerObject.GetComponent<Rigidbody>(); 

 

・接触したコライダーから参照を得る。

これはダイナミックな方法です。OnCollisionEnterなどで検出したコライダーからGetComponentするという方法です。応用するとPhysic.OverlapSphere等で検出したコライダーからを与えて、バクダンの爆発力を与えて吹き飛ばす、なんてこともできます。rigidbodyの場合は、collider.attachedRigidbodyから直接アクセスも可能です。

  

まとめ!

というわけで、Unity上で何かする際に必須な各コンポーネントスクリプト上でコントロールするためには、その実際のコンポーネントの参照を得る必要があり、そのためにGetComponentは必要である、という話でした。

基本ではありますが、大事な部分ではあると思うし、Unity上のC#を使う上での特徴的な所だと思うので、自分なりにまとめてみました。

   

 

下から叩くとアイテムが出るブロックの再現。二重コライダーの活用。

今回はUnity上で、マリオなどのアクションゲームで目にする"下から叩くとアイテムを出すブロック"を再現してみたいと思います。Unity2Dになりますので、3Dとの違いにご注意を。 

 

f:id:miur-us:20170813163431g:plain

完成版のGIFです。こんな感じでニュッと出るようにしました。明らかに敵っぽい目つきですが、アイテムのフラワーです。

Unityはcollider(コライダー)が準備されているので、それを各ゲームオブジェクトにアタッチすれば、簡単に"当たり判定"を付けることができます。

今回は2Dになるので、BoxCollider2Dという感じで後ろに"2D"がつきます。付け忘れると動かないので注意しましょう。

 

 で、ボックスにとりあえずコライダーを付ければ、プレイヤーキャラとの単純な衝突は検出できるようになるので、アイテムボックスのスクリプト側にOnCollisionEnter2Dを書いて、そこに"アイテムを飛び出させるコード"を書けば何とかなりそうです。

 

下からだけに反応させる!

しかし!このままでは、ボックスのどの位置にプレイヤーが当たってもアイテムが飛び出してしまいます。下から当たった時にだけアイテムが出るようにするにはどうすればいいか?自分が思いついたのは"二重コライダー"です。

つまり、ブロックにコライダーを二個設置することで、プレイヤーがブロックの下に当たったかを検出するという方法です。通常のコライダー以外にトリガーモードのコライダーを下側に設置します。

 

f:id:miur-us:20170813170020p:plain

 少し見えにくいですが、下に少しだけ飛び出している、コライダーがあるのが分かるかと思います。(緑の四角)

要はこのコライダーでプレイヤーが下にいることを確認しつつ、メインのコライダーに当たったか?という判定を行うわけです。そのために下のコライダーはトリガーモードでの使用、ということになります。 

 

 

 f:id:miur-us:20170811224644p:plain

Diamondというのは、赤い模様の部分なので無視してください。

"UnderTrigger"というのがそれです。コライダーのみアタッチされています。ヒエラルキー上では、ItemBoxのチャイルドに置かれています。

f:id:miur-us:20170811224835p:plain

IsTriggerにチェックをいれると、トリガーモードにできます。これにより単純にここに重なっているかどうかを検出できるようになります。"当たり"はなくなります。

↓アイテムボックス本体のインスペクターはコチラです。

f:id:miur-us:20170811225100p:plain

Box Collider 2Dがアタッチされています。これが単純にブロックとしての当たり判定になります。最終的なプレイヤーとの衝突はここが検出します。

下にある、Item Box Controllerが実際のスクリプトになります。

 

コードを確認してみる!

f:id:miur-us:20170811225350p:plain

f:id:miur-us:20170811225415p:plain

各変数から説明していきます。

"content"

まさしく箱の中身でアイテムです。今回はフラワーですが、Unityのインスペクター上でアイテムのプレファブをドラッグすれば、いろんなアイテムに入れ替えられます。

悩んだんですが、Startメソッド、つまりシーンがロードされた時点でプレファブをInstantiateしています。でSetActiveでfalseにして隠しておきます。

"movePoint"

アイテムの飛び出す位置です。本来はアイテムごとに飛び出る位置を調整する必要があるでしょうが、ここでは省略しています。Startメソッドで座標を指定しています。

ニュッと出すためにVector2.Lerpを使いますが、きちんと動くためにここで宣言しておく必要があります。(フィールドというやつです。)

"IsOpened"

これは開いたかどうか?当然最初は開いてないのでfalseです。プレイヤーがきちんと下にあたったらtrueにします。

"IsActive"

動かすのにVector2.Lerpを使うと移動し終えるまでUpdateでループさせる必要があるんですよね。で移動し終わった後もコードが実行され続けてパフォーマンス的にまずいと思うので、アイテムがきちんと出たらコレをfalseにして、つまりボックスの動作がオフになった、ということにしてコードを実行されなくするためのフラグになります。

 

"sr"

これはSpriteRendererのオブジェクトです。叩き終わった後、もうアイテムが入ってないよ!ってことを示すために色を変えたいんですが、その際に使います。

"m_Colliders"

このコードでもっとも重要な複数のコライダーを扱うためにBoxCollider2Dのオブジェクトを配列として宣言します。Startメソッド内でGetComponentsInChildrenを使って、ボックス本体のと下側の二つの参照を得ています。なので・・・

m_Colliders[0]はItemBoxのコライダー、m_Collider[1]は下部のトリガーをそれぞれ指すことになります。

"playerCollider"

プレイヤー自身のコライダも必要なので、オブジェクトを作り、StartメソッドでFindWityhTagを使い、参照を得ています。

 

実際の処理の流れ

というわけで準備は整いました!

プレイヤーがブロック下にぶつかると、どういう流れで処理が進んでいくかを、実際に説明していきたいと思います。

まずはOnCollisionEnter2Dが作動します。二つの条件を判定します。

  • 下部トリガーがプレイヤーのコライダーを検出しているか?
  • 本体に当たったゲームオブジェクトのタグが"Player"か?

最初の判定はm_Colliders[1].IsTouching(playerCollider)という文で行います。IsTouchingはコライダのメソッドで、引数として入力されたコライダが自分と触れ合っているかを判定し、触れていればtrueを返します。二番目は公式チュートリアルでも出てくるCompareTagで判定。

col.gameObject.CompareTag("Player")でプレイヤーのタグをチェックします。

 

どちらもtrueとなったら以下の文が実行されます。

content.gameObject.SetActive(true);

IsOpened = true;

StartCoroutine(WaitSwitchOff);

SetActiveでアクティブにし、IsOpenedをtrueにすることで移動の処理が始まります。

StartCoroutineIEnumratorを使って、WaitForSecondを含むメソッドを実行する際に必要ですが、コレによりアイテムが出た後、IsActiveがfalseになるようにしています。一秒もあれば移動も終わるので、1.0f秒待機、としています。

f:id:miur-us:20170811235740p:plain

さて!ではアイテムが飛び出るための処理です!

Updateメソッド上にあります。この処理自体をメソッド化してもよかったのですが、これだけなので、そのままにしています。

 if( IsOpend && IsActive) { //移動の処理 }

まずは箱がオープンされたか?アイテムボックスは仕事を終えてないか?という条件判定です。これについては、上記で説明したとおりです。

 

content.transform.position = Vector2.Lerp( ~ 以下略 ;

これはつまり、フレームごとにアイテム自身の位置目標地点を0.3fの割合で線形補間していく、というもので、要は単純に移動させるのではない、ニュッという感じの動きになります。生々しい動きになるわけです。

sr.color = Color.Lerp(sr.color, Color.gray, 0.4f);

については、正直ブロックを叩いた時点で実行していいですね。

ボックスの色を変える実行文です。

 

そして、先ほど言ったように移動し終わったころには、WaiSwitchOffメソッド内の

IsActive = false;

が実行され、移動の処理は行われなくなります。

if(IsOpend && IsActive)の部分がfalse(偽)になるからです。

 

まとめ!

以上で、プレイヤーが下から叩いたかを判定し、アイテムを出して、ボックス上部に移動させる、という動作の実現についてのレポートになります。

まだ改善点、付け加える部分はありますが、プレイヤーをジャンプさせてブロックを叩いて、その勢いのままにアイテムが飛び出してくる、という手ごたえのあるレスポンスは再現できたのかなと思います。

 

というわけで、Unity上でのまともな実践を扱ったエントリはこれが初になるんですかね?ネタはいろいろあるので、ドンドンやって生きたいと思います!!