みこしの活動日記

みこしの日々の活動を記録していきます。

【Unity】ソースコード解説 "Touch The Balls!"

f:id:quietMikoshi:20190307231528p:plain
"Touch The Balls!"のアイコン

unityroom.com

私が初めてUnityで作った3Dゲーム「Touch The Balls!」の解説記事を書いていこうと思います。上にリンクを貼ってありますので、良かったら遊んでみてください!(PCでの起動をおすすめします) 

ソースコードの構成は

  • ゲーム全体を統括する(GameManager.cs)

  • Stage1~5のギミックを決めるのにそれぞれ1つずつ(Gimmick1~5.cs)

  • プレイヤ、ボールーの挙動を決める(PlayerController.cs, PointBallController.cs)

  • 触れると吹き飛ばされる敵、動く床・壁の実装(EnemyController.cs, FloorController.cs, RefrectorController.cs)

  • タイトル画面から各ステージへシーンを遷移する(ChangeScene.cs)

の合計12本となっています。ソースコードはすべてC#で書いてあります。

   

GameManager.cs  

ではまずゲーム全体を統括するGameManager.csを見ていきます。

9行目はテキスト、10行目は効果音のゲームオブジェクトをpublicで格納するための変数を宣言しています。

f:id:quietMikoshi:20190304111350p:plain
GameManager

そして上図のようにGameManagerのインスペクター画面からアセットを入れます。

12~17行目はGameManager.csのソースコード内でのみ使う変数をprivateで宣言しています。17行目のAudioSource型の変数は10行目で格納した音源を鳴らすときに使います。13,14行目の変数にstaticをつけているのは他のクラスからもアクセスできるようにしておくためです。 

続いてStartメソッドの中に入っていきます。Startメソッドでは初期化を行います。

20行目でflagをfalseにしているのはステージ上のギミックがまだ作動しないようにしておくためです。ゲーム開始直後にカウントダウンが始まるようにしているのですが、カウントダウンが終了してからflagをtrueにしてギミックが作動するようにします。

21行目は"CountdownCoroutine"という名前のコルーチンを始めるという指示です。コルーチンが始まるとUnity内の他の処理が一旦停止し、コルーチン内の動作が全て終了したのちに再びUnityが動き始めます。コルーチンを使って他の動作をいったん止めてカウントダウンを行います。さて93~114行目にCountdownCoroutineメソッドがあります。 

まず94行目でSetActiveメソッドを使ってCountdowntextをtrueにしています。カウントダウンを表示するためのテキストは、普段は非アクティブしておいてカウントダウンを行う時だけアクティブにします。96~109行目の流れは 

"3"を表示→1秒待つ→効果音を鳴らしながら"2"を表示→1秒待つ→効果音を鳴らしながら"1"を表示→1秒待つ→効果音を鳴らしながら"GO"を表示→1秒待つ 

となっています。PlayOneShotメソッドは引数にとった音を一回だけ鳴らします。また、yield returnでコルーチンをいったん中断し1秒間待つ処理を挟んでいます。 

最後に111,112行目でカウントダウンのテキストを空白・非アクティブにしてコルーチンは終了です。 

コルーチンが終了するとStartメソッドに戻ります。26行目のInvokeメソッドは第2引数で指定した秒数後に第1引数で指定したメソッドを実行するメソッドです。88~90行目にMoveToTitleメソッドが書いてありますが、これはタイトル画面へとシーンを遷移させるメソッドです。これを53秒後に実行するのは、カウントダウンの3秒・ゲームの制限時間である45秒が終了してからさらに5秒後にシーンを遷移させるためです。 

そして29行目でまたコルーチンを呼んでいますが、DelayMethod(117~120行目)は第1引数で指定した秒数後に第2引数で指定したメソッドを実行するコルーチンです。書き方が違うだけで機能はInvokeメソッドと全く同じです。29~32行目の書き方はラムダ式と呼ばれていて短く書けます。 

では続いてUpdateメソッドに移ります。Updateメソッドは39~46行目は画面に表示させる得点の処理を行っています。そもそもGameManager.csにはscoreとdispayscoreという二つの得点を表す変数が用意されていますが、これは画面に表示させる得点が10点刻みで変化するのを実装するためです。得点が変化したらまずscoreを更新し、displayscoreがscoreと一致していなかったら10刻みで更新していきます。 

54行目のTime.deltaTimeは1フレームの長さを表していて、これをtimeから引き続けて制限時間を減らしていきます。 

72~90行目のメソッドはコメントにある通りです。 

   

PlayerController.cs

PlayerController.csのUpdateメソッドの中ではプレイヤーがステージ外に落ちたときにステージ中央に戻すという実装をしています。24,25行目ではプレーヤーの座標を指定しており、プレイヤーがこの座標にいたらif文が実行されます。まず26行目ではプレイヤーの移動速度を0にしています。これは速度がついた状態でステージ外に落ちると、その速度が保存された状態でステージ上に復帰し、場合によると無限にステージ外へ落下し続けるかもしれないからです。次に27行目では復帰先の座標を指定しています。そして28行目では、ステージ外に落ちたペナルティとしてscoreを引いています。最後29行目で、ステージ外に落ちたことを表す効果音を鳴らします。 

続いてOnCollisionEnterメソッドに移ります。このメソッドは、引数にとったオブジェクト(この場合はcol)に衝突した瞬間に呼び出されます。ただし引数の型がCollisionなので、衝突する方と衝突される方の両方にRigidBodyColliderコンポーネントをつけておかなければなりません。このメソッド内では、赤ボール・黄ボール・敵に衝突したそれぞれの場合を実装しています。衝突したオブジェクトの種類は「タグ」で見分けています(if文の条件指定でtagを使っています)。 

f:id:quietMikoshi:20190308182015p:plain
タグの設定
 

赤ボールに衝突したらPointBallController.csのGetRedPointBallメソッドを実行、黄ボールに衝突したらPointBallControllerのGetYellowPointBallメソッドを実行、敵に衝突したらEnemyController.csのTouchEnemyメソッドを実行します。

   

PointBallController.cs

9,10行目はそれぞれ赤ボール、黄ボールをとったときに取得する点数です。 

Updateメソッド内ではステージ外に落ちたボールをDestroyメソッドを使って削除しています。 

GetRedPointBallメソッドGetYellowPointBallメソッドではプレイヤーがボールに触れた瞬間に点数が加算されボールが消えるようになっています。 

   

Gimmick1.cs

ステージ1~5に共通することですが、5行目で赤いボール・黄色のボールのプレハブを格納します。また、ステージ1では触れるとプレイヤーが吹き飛ばされる敵を実装するので、6行目で敵のプレハブを格納しています。7行目もステージ1~5に共通していますが、得点をResult変数に格納しています。シーンが遷移してもResult変数の値が保持されるようにstaticをつけるのを忘れずに。 

9行目のred_or_yellow変数は、この値が0以上2以下だったら黄色ボールを作り、3だったら赤いボールを作るという基準を設けるために宣言しておきます。 

さてStartメソッドに入ります。16行目でGameManager.csにあるflagを取得しています。GameManager.csとGimmick1.csを同じオブジェクトにくっつけているので、このような記述になります。 

f:id:quietMikoshi:20190305175318p:plain
GameManager.csとGimmick1.csを同じオブジェクトにくっつける
 

20~23行目で敵の出現位置を初期化しています。図にすると、下図の赤い線分1本の上のどこかランダムに1つずつ敵が出現します。22行目のInstantiateメソッドは引数を3つとる場合、第1引数がオブジェクトそのもの、第2引数がオブジェクトを出現させる座標、第3引数がオブジェクトをどれだけ回転させた状態で出現させるかを指定します。 

f:id:quietMikoshi:20190305181316p:plain
敵の出現場所
 

Updateメソッドに行きます。31~38行目がボールの生成処理です。まずTime.deltaTimeを使ってdeltaを増やし続け、deltaがSPAN(=1.0f)を超えたらボールを1個生成します。「もし超えたら」の処理が34行目のif文です。if文の中では、まずdeltaを0に戻し、次にボールの色をランダムで決め、最後にCreatePointBallメソッドでボールを生成します。CreatePointBallメソッドでは、出現座標をランダムで決めてからInstantiateメソッドでボールを出現させています。

   

EnemyController.cs 

 

敵はflagがtrueのときだけ動くようにするので、Updateメソッドの中でif文を使って実装しています。if文の中で使っているtransform.positionはこのスクリプトをくっつけたオブジェクトの座標を取得します。+=や-=で座標を更新し続けて、敵が等速で動いているように実装します。26,27行目では敵の座標が一定値を超えたらLeftOrRightの値を変更して、敵の進行方向を逆向きにしています。 

TouchEnemyメソッドに移ります。34行目のdirectionは敵から見たプレイヤーのベクトルを表しています(Normalizeでベクトルの大きさを1にしています)。このdirectionの方向にプレイヤーを吹き飛ばします。35行目ではプレイヤーが山なりの軌道を描くようにy成分をいじっています。最後に36行目では実際にプレイヤーに力を加えています。 

   

Gimmick2.cs

ステージ2では床が上下に動くという仕掛けになっているので、7行目で床のプレハブを格納する変数を宣言しています。そしてStartメソッドの中にある21行目のfor文で床の初期位置を決めています。床は縦3×横3という形状なのでfor文で実装するとコンパクトになるだろうと思って書きました。 

それ以外の部分はほとんどGimmick1.csと同じです。ただしCreatePointBallメソッド内でボールの発生位置のy座標が13.0fになっています。これは、床が上下に移動するために、Gimmick1.csと同じくy座標を0に設定すると床の下側にボールが発生する場合があり、そのままボールが下に落ちていくだけになるのを防ぐためです。床は最大でもy座標が10までしか上がらないように設定してあるのでボールのy座標を13にしてあります。  

   

FloorController.cs

FloorController.csの実装は、数値や移動方向が変わっただけでEnemyController.csの実装と同じです。 

   

Gimmick3.cs

Gimmick3.csがGimmick1.csと違うのは、CreatePointBallメソッドの中でAddForceメソッドを使ってボールに対して発生時に力を加えているところです。AddForceメソッドは45行目と52行目にありますが、使う際にはGetComponentRigidBodyにアクセスする必要があります。 

   

Gimmick4.cs

7行目で床のプレハブを格納する変数を宣言しています。また12,13行目にPlaneSpanplanedeltaという変数が増えています。これはステージ4の床が消える仕掛けで使います。

さて床が消えたり生まれたりする仕掛けを作るのでUpdateメソッドを工夫しなければなりません。31~36行目は今までと同じです。40~46行目ではランダムな位置の床を縮小少しだけ縮小させています。Time.deltaTimeを使っているのはボールを発生させる実装と同じ原理です。まず43行目でposに小さくする床の位置を格納し、44行目でその床の色を緑に変えます。最後に45行目でその床を少しだけ小さくします。 

49~53行目では、大きさが0になっている床を元の大きさに戻し、色も水色に戻しています。 

58~60行目では、少しだけ小さくなった床を小さくし続けています。58行目の1.732という値が鍵なのですが、45行目で少しだけ小さくされた床はtransform.localScale.magnitudeの値がわずかに1.732を下回ります。この1.732という数字が大きすぎるとすべての床が小さくなってしまいますし、逆に小さすぎると一つも床が小さくなくなります。そして61~63行目では、ある程度小さくなった床の大きさを0にして見えなくします。大きさが0になった床は49~53行目の実装で復活するのでしたね。 

   

Gimmick5.cs

ステージ5では動く壁と回転するキューブを実装します。 

壁は一定間隔で発生するようにするので、今までと同じくTime.deltaTimeを使って37~41行目で実装しています。CreateWallメソッド内では発生させる座標をランダムに決めてからInstantiateメソッドで実際に発生させています。 

キューブの回転はtransform.Rotateを使えば44行目のように書けます。 

   

WallController.cs

WallController.csでは今までに解説してきたことしか使っていませんので、説明は大丈夫でしょう。 

   

ChangeScene.cs

9~13行目では各種アセットをpublicで格納しています。Unity側では下のようになっています。 

f:id:quietMikoshi:20190305143231p:plain
ChangeScene

Updateメソッドの29~33行目ではStage1~5の得点を一時的にResult[0]~[4]に格納しています。ここで、Gimmick1~5.csではResult変数はstaticにしておく必要があります。 

さて今ChangeScene.csのResult配列には現時点でのStage1~5の得点が入っていますが、このResult配列には今までの各ステージの最高得点が入っていなければなりません。そこで使うのがPlayerPrefsクラスのメソッドです。ここではその中のGetIntメソッドSetIntメソッドを使います。 

まず37行目では、Result[i]にResult[i]自身と"Maxi"というキーにおさめてある数(何もおさめられていなかったら0が返される)のうち大きな方を格納しています。これでResult[i]が常に今までの最高得点に更新され続けます。次に38行目では"Maxi"というキーに更新済みのResult[i]を格納しています。最後に39行目でResult[i]をテキストに出力します。 

48~86行目はStage1~5のボタンを押すと各ステージに遷移するときに使うメソッドです。ボタンを押すと効果音が鳴り、2秒後にシーンを遷移させるという実装になっています。  

f:id:quietMikoshi:20190305145506p:plain
ステージを遷移させるときに押すボタン
 

上図はStage1に遷移するのに使うボタンですが、この赤い枠内にChangeSceneManager(ChangeScene.csをつけた空オブジェクト)を入れ、青い枠内でChangeScene.SelectStage1を選択すればOKです。ただしSelectStage1メソッドはpublicにしておかなければなりません。この作業をStage1~5に施します。 

        

以上になります。今後もUnityで何か作ったら解説記事を書こうと思っているので、ご要望等あったらコメントに書いてもらえると嬉しいです!読んでくださってありがとうございました!