Unityでインディゲーム道!

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

ゲーム製作を学んで一番感動したこと。なぜマリオは壁の中で立てるのか。[ 当たり判定 ]


 ゲーム製作やプログラミングを現在も勉強中ですが、それ以前は当然、単なるゲームプレイヤーでした。今では、それに違う視点も加わることで、ゲームというものを多面的に見れるようになり、また別の面白さを味わえるようになっています。


 その中で、特に感動したのが当たり判定についてです。プレイヤーキャラと敵キャラが当たってダメージを食らう処理とか、そもそも地面に立てたりできるのすら、すべて当たり判定のおかげです。
 考えてみれば、ゲームというのはコンピュータ上におけるプログラムによるもので、実体はありません。画面に見えているのものは単純に画像であって、それだけでは幽霊のように見えているけど、触ったりすることが出来ない状態です。

 実体があるかのように感じさせるために、当たり判定は根幹的機能を果たします。当たり判定に基づいてレスポンスがあることで、ゲームプレイにおける感触や実感というものに繋がりますし、また逆に当たり判定が適当だったり、厳密すぎるとストレスに感じることもあるわけです。

マリオの当たり判定

 子供の頃の思い出として、スーパーマリオでの体験があります。マリオはキノコを食べると巨大化できますが、そうすると1マス分の穴に潜れなくなります。しかし、走りながらタイミングよくしゃがむと、しゃがみ状態で穴に滑り込めます。
 面白いのは、その状態で立つ事ができるということです。そして、穴を出るまでニューっと押し出されます。この押し出される、というのがポイントです。共感してくれる人は多いと思うんですが、このニューというのがすごく好きで、マリオを遊ぶときは毎回やってた思います。

 現実においては、自分の身長よりも小さい穴の中で立つことはできないですよね、当然ですが。実体のある人間と壁はぶつかるからです。しかし、ゲームではそうではありません。当たり判定、とは言われますが、実は当たっていないんです。

当たり判定はなにするの?

 つまり、当たり判定とはゲーム上のオブジェクト同士が重なったら、『当たった』と判断し、重ならない状態まで押し戻すことで、実体があるかのように見せるのが本質だというのです。正直、こういう説明を目の前にして、最初は実感が湧きませんが、先ほど書いたマリオでの体験を思い返せば、至極納得がいきます。

 マリオが穴の中で立った瞬間、マリオと壁という2つのオブジェクトが重なり合い、その結果、動的オブジェクトであるマリオが壁の外まで押し出されるからです。こんな風に過去の体験の理由をその仕組みと共に理解が出来たのが、今までで一番感動した瞬間ですね。


 そもそも、キャラが地面に立つというのも、立っているのではなく、理論的には地面がキャラを押し返し続けている、ということになります。また、押し出す処理が必要ない場合は、重なった!という判断だけをするトリガーとして扱われます。これは敵味方のビームや空中を自由に浮遊してくる敵キャラなどがそうですね。

仕組みが分かればトラブルも!

 なんで、壁を取りぬけてしまうバグなどもこの仕組みが分かっていれば、何故起こるのか理解できます。つまり、重なっているという判断をする前に、フレームを跨いで壁を通りぬけてしまうと押し返せない、ということなのです。面白いですよね。ハイスピードなレースゲームなんかは、より速いレートで当たり判定を行うことで、すり抜けないようにするみたいですね。

 また、銃弾の玉をオブジェクトとして飛ばすとコレもまたすり抜けてしまうので、Unityでもそうですが、レイキャストを飛ばして、つまり空間上を線と線で結んで判断する、みたいな処理をするようです。

 また、当たり判定はある程度は大らかである方が、ゲームプレイ上いいようです。適当な当たり判定に憤慨する、ということは昔のゲームではよくありましたよね。

まとめ!

 というわけで、個人的に感動したことを交えつつ、当たり判定について書いてみました。Unityではコンポーネントとしてコライダーが準備されているので、ゲームオブジェクトにアタッチしてすぐ使えます。便利な世の中!

[参考文献]
ゲームの作り方 改訂版 Unityで覚える遊びのアルゴリズム on Amazon

リアルでの人助けと『ウィッチャー3』| 決断と見返り

f:id:miur-us:20180825231700p:plain 
 ひさびさの投稿になります。リハビリがてら、ちょっとした小話というか、ゲームに絡めた雑談を。


 昨日、スーパーに行った時の話。自転車を倒して買ったモノを散乱させたおばあさんに出くわしました。
 さすがに目の前だったので、知らんぷりも出来ません。自転車起こしたり、荷物拾うのを少しお手伝いしたんですけど、その時に思ったのが、あっこれ、ウィッチャーだとモンスターがおばあさんに化けてるか、山賊の仲間かもしれないやつだ!と。

 いや、すいません。悪気はないんです。でも、ウィッチャー3をやった人なら分かってくれると思います。そういう風にモノの考え方に影響を与えるゲームもないよな、って改めて考えさせられます。もう、三年ぐらい経ってるんですよね。

情けは自分のためならず

 ウィッチャーというのは、あくまで職業の一つであって、世間一般から忌避されています。主人公のゲラルトは決して"勇者様"ではない。なにより、物騒で陰惨な中世ヨーロッパな世界観なので、いかにして自分の身を守るか、ということが求められます。

 ウィッチャーでは、そうした自己防衛への意識もロールプレイに含められています。自分の身は自分で守れ。他人に構っている余裕はあるのか?ということです。


 ゲーム内の用意されているミニクエストは、基本的には人助けです。しかし、多くのクエストに共通しているのは、金銭や情報を得るためのビジネスだということ。そして、純粋な正義も悪もないし、善かれと思ってやったことが必ずしも良い結果をもたらすとは限らない、ということです。

 ここがウィッチャーの肝かもしれません。プレイヤーは決断を迫られますが、それか正しいのかどうかは、どんな選択にしろ明確に判断することが難しい。痛い目にあったり、裏目に出たりする。そういう構造になっています。

見返りを求めない

 で、話をおばあさんに戻しますけど、声をかける前に瞬間的にフッとそのようなことが思い浮かんだんです。
 
 でも、実際今回はものを拾うくらいなので全然いいんですけど、人助けやったつもりが善くない結果をもたらしたり、自分がひどい怪我をしてしまったり、っていうのは全然ありえますよね。
 だから、ウィッチャーでの経験を活かして、少しだけ考えてから行動する、というのもいいのかもしれませんね。とは、言ってもほぼ瞬間的におばあさんに大丈夫ですか?って声をかけたんですけどね。善いことしたつもりではいるんですけど、それが裏目に出ることもあるかもしれない、という意識はどこかで持っていた方がいいなと思いました。


 もし誰かを瞬間的に助けようとして、その結果、自分が取り返してのつかない怪我をしてしまったりした時、納得できるのでしょうか、それとも、後悔するのでしょうか。

 ウィッチャー3からは、それでも自分の信じた道を進め、自分の決断を受け止めろ、と教わりました。

 以上です。

カメラを揺らして、画面上の敵を振動で倒すブロックを作る!| Unity 2D ポリモーフィズム 継承

下から叩くと画面を揺らし、画面上にいる敵キャラを全部倒す、というブロックをUnity2Dで再現してみました!一気に敵を一掃できる爽快感溢れるアクションになりますが、これを上手くやるためにはカメラを揺らすだけでなく、ポリモーフィズム継承を上手く活用する必要があります。 

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

 

敵キャラをまとめて処理

 敵キャラといっても、様々なキャラがいます。マリオで言えばクリボー、ノコノコをはじめとした多くのキャラクタがおり、多様な動きをしています。

 クラスを横断する一括処理を実現させるにはポリモーフィズムを使います。いろいろな敵キャラを"ひとまとめ"にし、一括して処理する必要があるからです。

 

 つまり、"衝撃波で攻撃する"という処理を各キャラに行うわけですが、、ひとつのアクション対してのそれぞれ違ったリアクションを取らせる、ということになります。それをシンプルにコーディングするには、継承ポリモーフィズムによる抽象化が必要です。

 

継承とポリモーフィズム

 継承とはコードの量を減らすためのアイディアで、ポリモーフィズム複雑な処理を人間的な感覚でシンプルに書き表すためのテクニックです。

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

 敵キャラクターという概念があり、そこから様々な具体的なキャラクターに派生します。各々のキャラクタは違いがあるといえど、基本的なパラメータ、動作を共通しています。体力があるかもしれませんし、移動するでしょうし、プレイヤーへの攻撃動作もあるかもしれません。そうした共通部分をスーパークラス、基底クラスで定義します。

 それを継承した各々の具体的なキャラの独自性の定義は、そのキャラのクラスですれば良いわけです。そうすれば、各敵キャラに共通する部分は省略することが出来ます。

 

 ポリモーフィズムとは、要は共有されたメソッドの中身をそのクラスにあった内容にする、実装を変えるということです。つまり、移動する、Move()というメソッドがあったとして、あるキャラは歩いて移動するかもしれませんし、他のキャラは飛び跳ねる、または飛ぶ。魚系のキャラはそれが泳ぐ、ということになるかもしれません。今回の例だと、ボール型と花型の敵は動かないですが。

 

 また、"プレイヤーに踏まれる"という動作などについては、クリボーはペッタンコになってしまいますし、ノコノコは殻にひっこみます。トゲゾーは何も起きず逆にマリオがやられてしまいます。つまり、同じ動作に対して、違った反応、結果が定義されていることになります。

 ポリモーフィズム多態性)とは、違った形態を持たせるということです。

 

Unity上での継承

 Unityで継承する際には、UpdateなどのUnity固有のメソッドも各クラスに継承させる必要があります。この時、protectedを使います。親子関係にあるクラス間のみアクセス可能、となります。そして、継承クラスのUpdate内で、base.Update()とすることで、基底クラスのUpdateを実行します。

 

↓このクラスで定義した、プレイヤーがブロックを叩いた時の処理は継承されたクラス内のUpdate内にbase.Update()と記述することで引き継がれます。

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

 衝撃波の出るブロックは原型となるBoxControllerクラスから派生しています。アイテムが出るボックスもありますし、いろいろあるボックスのうちのひとつです。

 どんなボックスでもプレイヤーがしたからジャンプで叩くと飛び跳ねるという動作を共有しています。継承してしまえば、各クラスごとにこのコードを書く必要がなくなります。繰り返しますが、継承とは余分な、重複するコードを無くし、読まなければいけないコードの量を減らすためのテクニックです。

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

↓はShockBoxControllerスクリプトですが、base.Update()があります。

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

BoxController内のvirtual Updateでの処理が実行されることになります。

 

Unity上でのポリモーフィズム

  基底クラスでvirtualとして定義したメソッドを継承クラスでoverrideすることで、そのクラス独自の動作を出来るようにします。今回で言えば、ブロックを叩いた後に、敵がやられるわけですが、"どうやられるか"という反応をそれぞれ違ったものにするということです。

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

 あるキャラは吹っ飛ぶかもしれませんし、あるキャラはバラバラになるかもしれません。まったく効かず、微動だにしないキャラもいるかもしれません。今回、衝撃波によるダメージを"DamagedDeath"というメソッドとして定義していますが、させたい動作をそれぞれコーディングすればいいわけです。 

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

 Enemyクラスでは中身がありませんが、地を這う敵キャラは"飛び跳ねて下に落ちる"という処理をしています。花の敵キャラは"逆さまになって、まっすぐ下に落ちる"ようにしています。

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

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

死亡判定されると、まず回転させて、下に移動させるという処理で、動きを作っています。

 

まとめて処理する!(アップキャスト)

  さて、では各クラスでoverrideとして定義した"DamagedDeath"メソッドをどう活用すれば良いか!それには各キャラをおおざっぱに"敵キャラ"として認知する必要があります。

 UnityにはPhysics2Dに、ある地点の周辺のコライダーを集めるOverlapBoxAllメソッドがあります。つまり、これを使って画面上の敵キャラのコライダーを集めます。

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

 このコライダーを使って、ボックスのクラス内にEnemyクラスのインスタンスを作ります。つまり、いろんな敵キャラを具体的な各々のクラスのインスタンスとして、生成するのではなく、漠然とした"敵キャラ"インスタンスとして生成するということで、柔軟性を持たせます。これこそ"抽象化"になります。詳細を無視して、概念的に物事を捉えるということです。

 

 つまり、様々なキャラをひとまとめに出来、大らかな同タイプのオブジェクト群として扱えるようになります。

enemy.DamagedDeath();

 なので、そうして集めた"敵キャラ"の集合に対して、ボックスは「衝撃波出すので、各々リアクション取ってね!」とお願いします。つまり、"DamagedDeath"が実行されます。つまり、各クラスで実装された異なる処理がなされるということになります。

 

カメラを揺らす!

 今回の件で更に重要なのは、視覚効果であるカメラの揺れです。これは何にでも使えるアイディアですよね。爆弾が爆発した時にも使えそうですし、いろいろ使い道があります。注意としては、やりすぎるとプレイヤーが酔ってしまうということですね。特に縦揺れはキツイ。

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

変数など。動く幅、スピードなどの変数を宣言。

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

 やることは多いので、揺れを起動するメソッドをShockActivationとして定義。叩いた時点でのカメラの位置の検出、ブロックを光らせるなど、様々なヘルパーメソッドを実行します。

 

 これには動く足場の件で学んだことを生かしています。まぁ、サイン波に沿ってカメラを一定時間揺らす、というだけのことなんですけどね。

 ブロックを叩いた瞬間のカメラの位置を中央"Center"として、そこから左右に揺れ動く、という処理にしています。叩いた瞬間を最大の揺れにして、時間経過ごとに揺れとスピードが弱まっていく、という処理にしています。それぞれ、speed, widthにしていますが、毎フレームごとにdeltaTime分を引いていく、という処理です。

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

cam.transform.position = center +

           new Vector3("横揺れ", "縦揺れ", 0);

 縦揺れも加えていますが、揺れの周期も幅も横揺れよりも弱くしています。

 

より、リアルに。

 揺れている!という感じを更に感じさせるにはどうすればいいのか。プレイヤーが一定時間動けなくなるのがいいなと思いました。実際、そういうゲームは多いですよね、地面が揺れてるとプレイヤーは動けなくなる、みたいな。ロックマンのガッツマンとかもそうでしたね。

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

 これは単純にブロック側のスクリプトがプレイヤーキャラ操作のスクリプトを一定時間、非アクティブにすることで実現しています。コレをやると、画面が揺れている間、プレイヤは動けなくなるので、あぁスゴイ揺れなんだなーという演出になります。FindGameObjectWithTagでプレイヤーとカメラのオブジェクトを探して、GetComponentから直接、オンオフにするのが楽ですが、少し長いのでStopPlayer()というヘルパーメソッドを作りました。

 

より、白熱した体験を!

 今回の動作を再現して思ったのは、やっぱゲームに爽快感を与えるには、こういう派手な仕掛けが必要なんだな!ということです。このアイディアの初出って、初代マリオブラザーズなんでしょうか?POWブロックを再現しました。

 こういう見た目が派手なものが大事ですよね、加えて敵キャラを一掃出来るのが楽しい。やっぱ、ゲームはやってて楽しくないと意味ないですよ!!

 

 

スーパードンキーコングでのバナナ、星の動きを再現してみた。

スーパードンキーコング・シリーズと言えば、レア社任天堂による、SFCの傑作2Dアクションゲーム三部作です。今回はその"バナナ"や☆の獲得後の動きを再現してみました。 

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

 本シリーズのバナナは言わば、マリオにおけるコインのような存在で、100本集めると一機増えるというコレクトアイテムになっています。今回再現するのは、プレイヤーがバナナを回収した後に起きる動きです。

 

 プレイヤーがバナナに触ると、バナナが画面の左上、つまり獲得数が表示されている部分に向かって飛んでいき、表示と重なると数が更新されるというものです。 

  • プレイヤーがバナナに触れる。
  • バナナが左上に飛んでいく。
  • アイコン表示部と重なると、消える。
  • スコアが更新される。 

 マリオなどでは、コインが獲得後は消えてしまうことを考えると(獲得アニメはありますが)、特徴的な演出であると言えます。とても面白みのある動きです。

 

再現に向けて

 まずは見た目から、です。画像を用意します。しかし、バナナはめんどくさいので星にすることにしました。本作中のミニゲームでは星を集めるので、特に問題はないはずです。右の画像の通り。

 

 

コーディングで押さえるべきところ

 この動きは気の利いたものですが、やろうとしていること自体は単純です。プレイヤーが触れたら、目標となる地点へ星を動かせばいいだけです。

 

 上記のリストから、各段階のコードでの処理をまとめると・・・

  • トリガーでプレイヤーを検知する。
  • 検知後、フラグをオンにする。
  • 画面上の左上部に移動させる続ける。
  • アイコンとの距離を測る。
  • 一定の距離になったら星を消す。
  • 獲得数に基づき、スコア表示を更新する。

 注意点としては、スクリーン上の位置とゲーム中、つまりシーン中(ゲームワールド内)の位置は異なるということです。プレイヤーが移動し続けるのであれば、画面上の位置とゲーム内の座標はズレ続けます。つまり、GUIのアイコンはスクリーン上に、星はゲーム内のアイテムとして、違う次元の存在なのでズレを考慮しないとダメだということです。プログラミング的には、星の移動中はアイコンの位置を更新し続けないとダメだということになります。

 

 今回、対象となるスクリプトは星自体にアタッチする"CollectableStar"とゲーム全体を管理する"GameController"の二つになります。前者は当然、星の動きを管理するもので、後者は星の獲得スコアを管理します。

 

星の動き

 まずは画像をインポートします。Spriteフォルダで"Import New Asset"を実行し、星の画像を選択し取り込みます。

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

 シーン内に引っ張って、コライダーをアタッチします。Physics 2D >> Circle Colliderを選択し、大きさ(半径)を適当に調整します。シビアすぎてもダメなので、大らかな大きさに設定してます。

 

 で、スクリプトである"CollectableStar"をアタッチします。ドンキーにおける星は、その場で回転しているので、それも再現します。

transform.Rotate()

 これを使うと、ゲームオブジェクトを回転させることが出来ます。ベクターの各軸、X, Y, Z軸それぞれ回転数をfloatで指定して、Update関数内で使用します。見たまま時計回りにするので、回転させるのはZ軸になります。Rotateは実は負担が大きい、とのことで、それなりに対策した方が良いらしいですが、ここでは省略します。

 

スコアの管理

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

 本筋に移る前に、スコア関連について。星獲得スコアは、単純に数を数えるだけなのでintタイプの変数"score"になります。星側のスクリプトから直接、そのフィールド(クラス内に直接宣言された変数)を操作するのは良くないことなので、このスコアを管理する"GameController"内部にスコアを加算し、表示を更新するUpdateScoreなる関数を作り、星側のスクリプトが外から、その関数を呼び出すという形にしました。内容はシンプルで、スコアを加算してTextの中身をそれに合わせて更新する、という処理をするだけです。

 

テキストについて

 表示については、Textを使います。using UnityEngine.Textを書く必要があります。星の画像を表示するので、それはimageです。どちらもCanvas上に表示させます。

 

星を動かそう!

  さて、ではいよいよ本編です。星のコライダーはトリガー・モードで使用します。つまり、プレイヤーが触れたかどうかが分かればいいだけですので。 

 

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

 プレイヤーの検知はおなじみ、OnTriggerEnter2Dを使います。2Dの時は、あらゆるものの後ろに2Dが付くことに注意です。"Tag"タグを使って判定するので、この場合Unity上でプレイヤーキャラに"Player"タグを設定しておく必要があります。

 

 触ったかどうかのフラグは"IsCollected"のbool値で管理します。集めたか?ということですね。OnTriggerEnter2Dでプレイヤーが触れたら判定し、これをtrueにします。trueになった時点から、移動のための処理が始まります。(Update()内部)

 

 

スターにアタッチするスクリプト

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

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

 Update内に移動のメソッドを書いておき、フラグが立ったらこれが実行されるようにします。つまりif文です。IsCollectedがtrueになったら、移動メソッドが実行されるようになり、星が動き始めるという算段です。

 

 問題は前述の通り、星がどこに向けて移動していくかということです。

 

 画面上の左上部分になりますが、そもそも我々が見ているものはスクリーンであり、スクリーンはゲーム内世界の一部を映し出したものに過ぎません。つまり、星やバナナからしてみれば、画面上の位置は分からないわけで、画面上の位置をシーン内の座標に変換しないとダメです。 

 スクリーンに映し出されているものは、カメラを通したものなのです。CameraクラスにおけるViewportToWorldを使い、変換します。

 

 スクリーン上の座標は0.0fから1.0fまでのfloatのx, y座標として表されます。つまり、今回の"左上"はVector(0.3f, 0.95f)です。今回のようなゲームは常にプレイヤーが動き続けるので、こうした座標の変換、計算を常に行う必要があります。IsCollectedがTrueになった瞬間からアイコンに届くまでの間、Updateメソッド内で移動の処理が毎フレームされ続けます。

 

 

 そして、指定の位置に近づいたらスコア加算のメソッドを実行します。厳密に言えば、GameControllerUpdateScoreメソッドを呼び出し、自分のゲームオブジェクトを非アクティブにすることで表示を消す、という処理です。

gameObject.SetActive(false);

 Destroyでもいいかと思いますが、Destroyは負担も大きいし、星自体の数も多いので、SetActiveを使うことにしました。画面から消える、という効果は同じなので。

 

  ちなみに星の点滅というか、色の変化の処理も書いてますが、それについては今回の本筋とは無関係なので省略します。

 

注意!

 今回のコードですと、画面の大きさの変化に対応できません。アイコンの座標を定数で表しているからです。なので、解像度が変わればズレてしまいます。なので、本当は画面の大きさから計算して、相対的なアイコンの位置を計算して割り出すという処理が必要なのですが、今回は省略しています。

 

まとめ!

  ということで、自分も大好きなゲームの動きを一部だけではあっても、再現することは楽しいなと思いました。こういうちょっとした演出が、ゲームに生命観を与えるのだなーと、また1つ勉強になりましたね。今後も名作ゲームの再現に挑戦していきたいと思います。あとSwitchでトロピカルフリーズやりたいなぁ。

  

 

オブジェクト指向とは単なる分割、縮小、連携のための手段だ!

 オブジェクト指向は思想のように捉えることは混乱を生みます。エレガントな手段!なんてものではなく、タイトルの通り、それなりの規模のプログラミングに対する妥当で、現実的な手段ではないのでしょうか。オブジェクト指向の本質を理解した気がするので、考えをまとめたいと思います!

 

むしろ補助的な機能

 オブジェクト指向に振り回されがちな初心者プログラマですが、自分も最初は掴み所がなくて困惑しました。クラスは設計図のようなもの・・・ようなものってなんだよ!?みたいな。

 Unityでやることを含め、最初の言語としてC#を選択する人が少くないと思いますが、C#オブジェクト指向の言語です。そのためオブジェクト志向な考え方にどっぷり浸かることになります。
 つまり、オブジェクト指向がプログラミングにおける本分のように錯覚してしまうわけですね、自分も最初そう思いましたし、同時に初歩的なこととオブジェクト指向的なもののが噛み合ってこないので、そこで要領を掴めなかったりしました。
 
 それもそのはずで、オブジェクト指向はむしろ補助的な機能に他ならないわけで、実は初心者はそれほど気にすべきことでもない、といってしまっていいと思います。

コーディングにおける二つの目的とは?

 プログラミングには、いろんな機能やテクニックがありますが、それらは主に二つの目的のために存在していると思われます。つまり・・・
可読性のための分割 & 読む量を減らすための縮小、の二つです。

 例えば、関数やメソッドというのはこの2つが組み合わさったもの、と説明できます。
 複数行の処理を関数化することで、他のコードと切り離し、区別できるようにし、読みやすくします。また、その処理が繰り返し使われる場合、関数を呼び出せばよくなるので、総合的なコードの量は減ります。関数の再利用というものですね!

 For文なども繰り返し処理を効率的に書き表すためのテクニックですし、縮小のための道具と言えそうです。

クラスも単なる道具である。

 というわけで、クラスもこの分割&縮小のための道具に他なりません。オブジェクト指向は現実世界に近い形でコードを書けるエレガントな方法だー!みたいなのは結果的にそう解釈できるに過ぎず、本来の目的は大きなプログラムを分割して、読みやすく扱いやすくし、また大きくなりすぎないようにするためのものなはずです。

 カプセル化とはコードを意味のある塊にまとめる、ということであり、機能や分野ごとにまとめるというのはその方がわかりやすいから、ということになります。
 難しく考える必要はなく、ただ単に分けて管理しよう、別々に考えよう、というものに過ぎないはずで、言葉遊びに付き合う必要はありません。
 
 継承、ポリモーフィズムはコードの量を減らすためのものです。継承は関連あるクラスの重複部をひとまとめにするためのもの、ポリモーフィズムは各クラスをひとまとめにし、いろんな動作を共通のメソッドで表現し、やはりコードを少なく、簡潔にするためのテクニックです。

自分でデータ・タイプを作るための器

 分割&縮小はトラブル回避のための消極的な目的ではありますが、積極的にクラスを使う目的もあります。それはデータ・タイプを自分で作れるということです。

 データ・タイプとは、いわゆるInt, float, char, bool などのデータの形式や種類を表すものですが、基本的なタイプは単純なデータしか扱えません。

 世の中にある様々なモノは複雑な構造、概念によって形成されており、そうした単純なタイプでは表現不可能です。正確に言えば、多くの単純なタイプが組み合わさって、初めて高度て複雑な表現ができます。
 要するにクラスとは、そうした数多くのデータタイプをひとまとめにし、抽象化するための入れ物になるわけです。抽象化とは、細かい事柄をざっくりと捉えることで、高度な理解、表現をするための手段です。

 また、クラスはメソッドも含めることができるので、振る舞いも表すことができ、クラスから作られるモノ(インスタンス)に主体性を与えることができます。これはクラスによる分業の面から見て、とても重要です。

 これにより複雑な構造を持つ、人間を含めた生き物などをコンピュータ上でデータとして、扱えるようになる、ということになります。
 つまり、高度で複雑なデータを表すタイプを自分で定義できるということです。

連携という、ネック

 分けた、ということはバラバラになったコード群が一つの仕事を成し遂げるために連携しなければならないということになります。どう連携するかの管理のためのアイデアがアクセス修飾子です。つまり、公開、非公開にすることでクラス間の連携を制限するというものです。

 連携はむしろクラスを利用するために支払わなければならないコストのようになります。なぜなら、上手く連携をするような構造になっていないと、かえってコードが複雑になってしまうからです。
 プログラミング言語が人間のためのものなのに、読みにくくなってしまうのと同じように、クラス分けのせいで、プログラム全体の構造が複雑になって、把握しにくくなるのは本末転倒です。
 しかし、クラス設計とは単純なコーディング以上に、物事の本質を見極める能力が求められ、難しいわけです。
 ここらへんがオブジェクト指向が必要以上に叩かれる原因なのではないでしょうか。しかし、オブジェクト指向は単なる方法論であり、使わなければならないなら慎重に使わざるを得ず、他にいい方法があるなら、その方法を使えばいいだけです。

まとめ

 というわけで、クラスは分割や縮小、連携、そして、自分でデータ・タイプを定義するためのもの、という説明を書いてみました。
 少し、長くなりましたが、かなり分かりやすく書けたかと思います。自分で言うのもなんですが。

 ポリモーフィズムに関しては、Unity上の実践含めて、また改めて記事を書きたいと思います。画面中の敵を全部・・・みたいな内容です!

マリオオデッセイという名のロードムービー | ゲームクリア後感想

 少し遅いかもですが、マリオオデッセイをクリアしたので、その感想をまとめたいと思います。
 パワームーンを500個以上集め、「長い旅の終着点」までクリアしたので、一通りは遊んだと言えると思います。

インタラクティブ・ムービーとしてのマリオ

 ムービーゲーをマリオ64で否定したのが任天堂だったと思いますが、本作は思いの外、観るということに意識を置いたゲームになっていると思います。オープニングを始めとした各ムービーは、昨今の洋ゲーを意識した出来で、かっこよかったですね。
 
 というか、単純にクリアだけを目指した場合、つまりメインストーリーのゲーム体験はかなり映画的な流れになっていると思います。

クッパを追いかけて、各国を飛び回り、戦ったり、打ち落とされたり、でも這い上がってまた頑張ったり・・・(二回目の撃墜は少し冗長かな、とは思いましたが)

アクションゲームの集大成

 20年ほど前ですね、マリオ64を死ぬほどやった少年もオッサンになりました。この長い年月の間にゲームは様々な進化を遂げ、一時期ゲームを離れた時もありましたが、その変容はそれなりに見てきたつもりです。
 マリオ64革新、驚きとするなら、本作は洗練、成熟と言えると思います。この20年のゲームの進化を内含した一作と言うべきかもしれません。

 マリオの集大成というよりは、ここ20年くらいのアクションゲームの集大成なのかなと。いろんな国を飛び回り、旅をするというコンセプトは、また多様なゲームを取り入れる、という風にも解釈できます。


 個人的にクッパ城が好きなんですが、そこでのキツツキっぽい敵のツックンにキャプチャした時の壁にくちばしを刺すアクションって忍者っぽいんですよ。それもあって、がんばれゴエモンっぽいんですよね。

 また、黄金のリングとジェット花による高速ダッシュセガソニックっぽい・・・

 メカハナちゃんはメトロイドっぽさもあるし・・・。そもそもマリオがモーフボール出来るようのなってる!

 なんというか、キャプチャのなんでもアリさも合わせて、アクション・ゲームの集大成と感じました。
 64以降の3Dマリオをベースに様々なゲームを破綻しないように乗っけているという感じでしょうか。

マリオの冒険は続く

 なんで、あまりマリオ最終章という気はしなかったですね、やりたい放題やったオデッセイの後に、マリオにどんな新たな冒険があるのかが楽しみです。
 というか、オデッセイのエンジンで64をリメイクして欲しい!!!

唯一の欠点

1つだけ文句言わせてください。
ゲームの止め時がない!!

 オデッセイに限りませんが、今どきのゲームはどこでもセーブでき、いつでも中断できます。今どきセーブポイントでしかセーブできないのは時代遅れ!というのは正しいかと思いますが、しかし止めるタイミングがない!
 特にオデッセイは一つ一つのステージはそこまで長くないし、楽しいし、いくらでも出来てしまうんですよね。
 だから、ケツが痛くなったら止めるみたいな感じでした。昔はゲームオーバーになったら止める、みたいに出来たんですけどねー

ゲームにおける統一感

 なんでもありに、なってしまうと結果的にゲームとしての統一感を取るのは難しくなると思います。

 最初は結構戸惑ったり、特にブルータスのデザインに関しては最初はどうだろうと思ったりもしたんですが、まぁ慣れましたし、最後の方は愛着沸きましたね。

 というかゲームとしての面白さが全てを許してしまうというか。結果的に良く出来たゲームだなぁと。成熟したゲームを体現した出来ですね。

バルーンファインドが何気によい!

 いわゆるエンド・コンテンツのようなモノだと思うんですが、バルーンファインドが意外と、というかかなり面白かったですね。
 ほどよい、緩いオンライン要素で遊びやすく、みんな思いもよらない所に隠してるので、奥深さもありやす。

まとめ!

 頑張った自分に感動するのがゲーム、というのは宮本茂氏の言葉らしいですが、確かに長い旅の終着点をクリアした際には、少し涙が潤んでしまいました。
 少し駆け足気味にプレイしたのが勿体無いくらいでしたね。まだ更に遊べるので、ゆっくりやっていきたいと思います。

関数とは再利用よりも抽象化するためのもの?

 関数といえば、プログラミングにおいては、なんらかの仕事をする「機械」のようなものであり、C#上だとメソッドとも言われます。

 関数とはコードを部品化、あるいはモジュール化して再利用しやすくするためのものだ、というような説明がなされます。それはその通りなのですが、初心者からすれば、コードを書いた経験もないので、再利用による恩恵が実感できず、結果的に関数というものの存在意義が掴みづらいのではないのか?と思います。(自分がそうでした。)

 久々の投稿になりますが、リハビリも兼ねて、今回はプログラミングの基本的な部分である「関数」について、書いてみたいと思います。

数学の関数とは違う?

 まず名前がややこしいですよね。数学における関数というのは、y = 2xみたいなもので、一定の処理をする計算式です。
 プログラミングは究極的には、計算の集合ではあるので、一定の仕事をするコードを関数というのは間違ってはいないと思います。しかし、文脈上のニュアンスが異なるということを、初心者は気をつけるべきです。
 では本題に入っていきます。

名前をつけることの重さ

 名は体をなす。という言葉があります。名前というものはプログラミングに限らず、実際、とても重要なものです。名前にこそ魂が宿る、という考えもありますよね。

 と同時に、いかなる名前を付けるべきか?という問題も存在します。そのモノの本質を表すような名前をつけなければいけないからです。

 プログラミングにおいても、この「名前付け」こそ、もっとも重要で困難な作業であったりします。可読性や、コードの働きへの認識に大きな影響を与えるからです。

「処理」に名前をつける。

 プログラムはなんらかの問題を解決するための「処理」の集合体とも言えます。複数の「処理」を積み重ねることにより、大きな「処理」をしている、ということです。(実際には、クラスも分割の単位にいれるべきですが、ここでは省略します。)

 小さな処理もまたより細かい「処理」により、構成されています。もっとも小さな処理の一単位を一行のコード、実行文とします。

 つまり、関数とは一定の処理をする、複数行のコードに対して、名前を与えて、まとめあげるためのものだということです。一言で言うならば、ある処理を抽象化するもの、ということになります。

抽象化とは?

 プログラミングにおける抽象化とは、人間の考え方、認識の仕方に近づける、ということです。
 つまりは、物事を漠然と捉える、余計な細かいことは気にしない、ということになります。

 プログラミング言語というのは、人間のためのものであり、人間が読みやすいように書かれるべきですが、それは人間側の都合です。

 コンピュータ側の都合で、コードは必ずしも人間に分かりやすいものではないかもしれません。例えば、次のようなコードがあります。

int numMAX = array[0];
for(int i = 1; i < array.Length; i++)
if( array[i] > numMAX)
numMAX = array[i];

 これは整数の配列(集合)の最大値を見つけるための処理です。つまり、やっていることをそのまま名前にして、関数化してしまえばいいんです。
つまり・・・

int FindMAXinArray(int[] array){
int numMAX = array[0];
for(int i = 1; i < array.Length; i++){
if( array[i] > numMAX)
numMAX = array[i];
}
return numMAX;
}

FindMAXinArray(配列の最大値を見つける)、というストレートな名前ですが、これで具体的にどんな細かい処理をやっているかを無視して、どんな目的のことをやっているかにだけ注目できます。

 つまり、人間の認識、思考能力には限界があるので、目の前に出される情報量を制限しよう、というのが抽象化であり、そのための手段に関数があるということになります。(より高度な抽象化のためのツールとして、「クラス」があります。)
 細かいことを意識から外すことで、より高度なことが出来るのが人間、ということになるかと思います。(細かいことを正確に早く出来るのがコンピュータ)

再利用よりも抽象化

 再利用という考え方は大事ですが、再利用というのは関数化したことによる結果論であり、再利用を前提としなくとも、関数化した方がよい場合もあります。
 自身の経験談としても、抽象化するために関数を作る、という風に意識を変えていった頃から、コードをより良く書けるようになった記憶があります。つまり、必要な処理がどんなものかを考え、先にその処理の名前を考える、というプロセスです。

 再利用するかもしれないし、するかもしれない。たくさん再利用するかもしれないし、二回しかしないかもしれない。そんなあやふやなメリットを強調するよりも、こっちの方がより分かりやすいのではないかと思います。(もちろんコード全体に散らばる重複部分を関数化するというのも、立派な再利用の恩恵といえますが。これはリファクタリングの範疇ですかね?)

まとめ!

 というわけで、今回は関数というものについて書きました。何らかの処理に対して、適切な名前を授ける、という意識で関数を作る、という考え方の方が初心者にとっても、分かりやすいんではないか?という主張でした。