【第26回】ゴルフゲームの進行を制御するUnityチュートリアル

【第26回】ゴルフゲームの進行を制御するUnityチュートリアル

前回のチュートリアルでは、ゴルフっぽいゲームで使うコースを作成しました。Prefabからオブジェクトをインスタンス化し、手動で配置するのって結構楽しい気がします。

今回は、前回作成しなかったゴールを設置するのと、ボタンの動きを少し変えてゲーム全体の進行を制御するようにしてみます。

前回のチュートリアルはこちらから。

 

今回の目的

コースのゴールを配置し、スタートからゴールまでの全体の進行を制御します。

ゴールに到達したら、UIテキストをアニメーションさせます。

プロジェクトの準備

前回のチュートリアルで作成したプロジェクトをそのまま使います。

このページに先にたどり着いた方は、チュートリアルの初回から追っていただけるといいかもしれません。

ゴール用オブジェクトの作成

何をもってゲームのゴールとするか、というのはどんなゲームでも考えないといけないことですね。

このゲームはゴルフっぽいゲームを目指しているので、ボールが到達するゴールを作成しないといけません。

分かりやすいのはゴール用オブジェクトを作ってしまうことなので、サクッとやっちゃいましょ。テクスチャは以下のものを使います。右クリックで保存してください。

ゴール用テクスチャ
ゴール用テクスチャ

 

このテクスチャをUnityの『Project』ウィンドウ内、『Textures』フォルダにインポートします。エクスプローラ(Win)またはFinder(Mac)からドラッグ&ドロップ。

インポート
素材をインポートするのだポッター

 

インポート設定は特にいじらなくて大丈夫です。

インポートできた
インポートできた

 

続いてマテリアルを作成します。『Materials』フォルダに移動して、右クリックまたは二本指タップでコンテキストメニューを開きます。[Create] -> [Material]を選択。

マテリアルの作成
マテリアルの作成

 

Materialの名前は[Goal]にしました。そのまんま。『Goal』マテリアルを選択し、Albedoの左にある丸ボタンからインポートした『goal』をセットします。

インポートしたテクスチャをセット
インポートしたテクスチャをセット

 

続いてゲームのシーン内にゴール用のオブジェクトを作成します。『Hierarchy』ウィンドウでコンテキストメニューを開き、[3D Object] -> [Cube]を選択します。

Cubeの作成
Cubeの作成

 

作成したオブジェクトは[Goal]と名付けました。この『Goal』オブジェクトを選択し、Transformコンポーネントの歯車アイコンから[Reset]を選択した後、PositionのXを[85]に、Yを[0]、Zを[0]にします。

ScaleはXが[5]、Yが[0.2]、Zが[5]としています。0.1m分地面に埋まっていますが、あまり地面から出すと側面の衝突でゴール扱いになるので、これを避けています。

GoalのTransform
GoalのTransform

 

Box Colliderでは物理特性マテリアルの『Bound』をセット。衝突する物体として使いたいので、Is Triggerにチェックを入れないように注意。

Box Collider
Box Collider

 

次にMesh RendererのMaterialsに『Goal』マテリアルをセットします。

Mesh Renderer
Mesh Renderer

 

また、ゴールはコースの一部なので、『CourseObject』オブジェクトの子オブジェクトにします。ドラッグ&ドロップで簡単にできます。

CourseObjectの子オブジェクトにする
CourseObjectの子オブジェクトにする

 

また、ゴールに乗ったことを検知するため、衝突相手を特定するタグを付けます。Unityのデフォルトのタグで[Finish]があるため、それを設定しましょ。

Finishタグ
Finishタグ

 

ここまで設定を行うと、以下のようにゴールが表示されました。

ゴールが表示された
ゴールが表示された

 

このオブジェクトの上で止まった時に、ステージクリアとなるようにしましょ。

 

ゴール時のUIテキストを作成

ゴールしたことが分かるように、UIテキストを使って表示してみます。

『Hierarchy』ウィンドウから『Canvas』オブジェクトを選択し、右クリックまたは二本指タップでコンテキストメニューを開きます。[UI] -> [Text]を選択してUIテキストを作成します。

テキストを作成
テキストを作成するのだポッター

 

作成されたオブジェクトは[GoalText]にリネームしました。Rect Transformコンポーネントでは、Widthを[800]に、Heightを[400]に変更します。大きめにババーンと表示しましょ。

GoalTextのRectTransform
GoalTextのRectTransform

 

続いてTextコンポーネントの設定です。Textフィールドには[GOAL!!]を入力しました。ゴールした時に表示されるので、お好きな文字列を入れるといい感じ。

Font Styleは[Bold]、Font Sizeは[192]、Alignmentはどちらも中央に。Colorは[FFFFFFFF](白色)にしました。

Textコンポーネント
Textコンポーネント

 

いつものようにOutlineコンポーネントを追加しますが、今回は3つOutlineコンポーネントを追加します。ゴールなので大盤振る舞いです。[Add Component] -> [UI] -> [Effects] -> [Outline]と選択してOutlineコンポーネントを3つアタッチします。

各コンポーネントの設定は以下の通り。

Outlineコンポーネント Effect Color Effect Distance X Effect Distance Y
# 471F00A8 4 -4
真ん中 # FFA40080 8 -8
# FFE3E340 12 -12
Outlineの三連星
Outlineの三連星

 

テキストオブジェクトの設定が終わると、以下のように表示されます。メインのテキストが中心にある白の部分で、こげ茶、オレンジ、薄いピンクの順にOutlineが表示されています。正直、ここまでやるなら画像にしちゃった方が楽かも。

Unityの機能でも多少装飾が可能ですよーという紹介のためにこの形にしてみました。

GOALテキスト
GOALテキスト

 

このゴールテキストはゴールした時に表示したいので、Rect Transformコンポーネント内、PositionのXを[1500]にしておきます。画面に映らなくなるので、ゴールした時にスライドインするようにしてみましょ。

実は『Scene』ウィンドウでも見える
実は『Scene』ウィンドウでも見える

 

スクリプトの編集

今の状態では、ボールを1回打ち上げるとそれっきりになってしまいます。

ゴールまで続けて打って行きたいので、以下の方針でスクリプトを編集します。

  • 何回も続けてボールを打てるようにする
  • ボールを打つ向きを前後にしたい
  • 落下した時は、初期位置ではなく最後に打った場所に戻す
  • ゴールオブジェクトの上で止まったらコースクリア
  • ゴールしたら画面にUIテキストを表示

これに沿って編集したのが以下のもの。編集の対象はSphereBooster.csで、スクリプトの全文は『GitHub』にあります。

ここでは変更点を中心に解説します。

 

いやもうメンバ変数がえらいこっちゃ。全部は載せきれないので更新箇所だけ。

まずは発射ボタンのオブジェクト、ゴール用UIテキストオブジェクトへの参照を用意。

次に、initPositionのフィールドを削除します。というのは、ボール落下時は最後にボールを打った位置に戻したいためです。距離の測定も最後にボールを打った位置を基準にするので、初期位置であるinitPositionは不要となりました。

最後にボールを打った位置については、メンバ変数の下の方でprePositionとして格納しています。

ボールの射出角度はMinAngleを0度に、MaxAngleを180度にすることで、前後にボールを打つことが可能になります。MaxAngleが90度のままだと、ゴールを飛び越えてしまったら詰みになっちゃうんですよねぇ。

あとはフラグやタグを追加しています。

 

 

Start()では、メンバ変数でinitPositionを削除したのに連動して、initPositionをセットしていた行を削除しました。

新たに追加したのは、boostButtonのButtonコンポーネントへの参照です。

 

 

Update()は変更なしで、FixedUpdate()を更新しました。

ゴールしたかどうか、『Sphere』オブジェクトの状態を確認するCheckSphereState()を呼ぶようにしています。こちらは新たに追加したメソッドで、詳細は後述します。

ボタンが押されたかどうかの判定後は、元々飛行中フラグでボタンの動作を切り替えていましたが、ここからは発射だけを行うようにします。

そのため、ボタンが押された後にcanButtonPressをfalseにし、それをboostButtonのinteractableにセットすることで、ボールの飛行中は発射させないようにしています。canButtonPressはキーボードからの入力を拾うかどうかのフラグにもしています。

発射だけになったので、飛行中フラグのisFlyingはtrueのみをセットするようにしました。

 

 

StopFlying()では、距離のリセットやガイドの表示などを行なっていましたが、それらの処理をReadyToBoost()にまとめました。

なお、ボールを戻す処理については、初期位置initPositionではなく、ボール発射時の位置であるprePositionをセットするようにしています。

 

 

BoostSphere()の変更点は、ボールが発射される時の位置を記憶するようにした点のみです。

 

 

CheckDistance()ではinitPositionを使って計算していた部分をprePositionに変更しました。2箇所あるので注意。

 

 

続いて落下判定についてです。

ここでは元々、ボタン押されたよフラグをtrueにしていたのですが、ボタンの動作を変えたため明示的にStopFlying()を呼ぶようにしました。

 

 

ここは今回のキモ。新たに、OnCollisionEnter(Collision other)とOnCollisionExit(Collision other)を追加しています。

これは衝突したフレーム/衝突から離れたフレームに呼ばれるメソッドです。上のOnTriggerEnter(Collider other)と似ていますね。

各種Colliderコンポーネントで『Is Trigger』にチェックを入れたかどうかで呼ぶメソッドが変わるんです。

『Is Trigger』がtrueならOnTriggerほにゃららを呼び、『Is Trigger』がfalseならOnCollisionほにゃららを呼びます。

引数がCollider型だったりCollision型だったりで紛らわしいことこの上ないですね。

ここでやっていることは単純で、『Sphere』オブジェクトの衝突相手がゴールオブジェクトだったらisTouchingGoalのフラグをtrueにし、ゴールオブジェクトから離れたらフラグをfalseにしています。

 

 

CheckInput()ではキーボードのBキーが押された時、同時にcanButtonPressフラグも確認するようにしています。

canButtonPressは画面に表示している発射ボタンと連動させているので、キーボードからの入力でも同じ動作となるようにしています。

 

CheckSphereState()はFixedUpdate()から読んでいるメソッドで、ゴールで止まったのか、別の場所で止まったのかを確認しています。

ゴールで止まった場合は、ゴールテキストがスライドインするアニメーションを開始させ、hasReachedGoalフラグをtrueにしています。このフラグがtrueの時には処理を行わないので、ゴール処理を1回だけ実施するようにしています。

アニメーションの処理を実装したメソッドはStartCoroutineで呼んでいます。コルーチンについてはやっぱりUnityのマニュアルを見るのが一番なのですが、ざっくり言えば時間の経過を伴う処理を実装するための機能です。

例えば処理を一定時間停止させるウェイトとか、徐々にオブジェクトを動かしたりだとか。Update()を使ったりして呼ぶと、同じフレーム内で処理を完結させちゃうから、途中を描画してくれないんですよね。そこでコルーチンを使って、ちょっとずつオブジェクトを移動させて、アニメーションしているように表現することができます。

ゴール以外では、ボールを発射するための準備を行うようにしています。ReadyToBoost()はこの次に説明します。

 

ReadyToBoost()は元々StopFlying()の中で行なっていた処理が多く含まれています。ボール落下時と、コース内での停止時では処理が若干異なるため、共通化できる部分をReadyToBoost()として切り出しています。

この処理の最後にcanButtonPressをtrueにして、ボタンを押せるようにしています。

 

コルーチンはIEnumeratorを使って書きます。

最初にRectTransformコンポーネントの参照をキャッシュし、whileの中でゴールテキストの位置のセットに使います。

initPosでゴールテキストの初期位置を保持し、targerPosでは移動先の位置を保持します。

initPosにlocalPositionをセットしているのは、このオブジェクトで設定したPositionを使って欲しいためです。通常のpositionを使ってしまうと、親オブジェクトの位置まで考慮されるためずれてしまいますからね(1敗)

finishTimeは処理完了の予定時刻。Time.timeで現在時刻を取得し、引数で渡された秒数を足した時刻にしています。whileの中でこのfinishTimeを見ており、finishTimeを超えていたら、最後にtargetPosをセットして処理終了となります。

rateは、引数で渡された秒数に対して、どれだけ時間が経過したかの割合を表しています。Clamp01は0と1の間に値を制限するメソッドで、この後のVector3.Lerpの引数で渡す時に必要になります。

Vector3.Lerpは線形補間を行うメソッドで、今回の場合だと、ゴールテキストの初期位置とターゲットの位置を直線で結び、その直線上の位置を割合に応じて返すもの。徐々にオブジェクトを移動させるのにぴったりです。

yieldを使った場合、その次のフレームで処理を再開します。ここではnullを返していますが、WaitForSecondsなどを使うと、指定した秒数だけ処理を待つこともできます。

この辺りの処理はテラシュールブログさんの記事を参考にさせてもらっています。

 

今回の変更点は以上です。スクリプトの全文は『GitHub』にあります。

オブジェクト参照の設定

スクリプトを変更したら、忘れずにオブジェクト参照をセットします。『Sphere』オブジェクトを選択し、BoostButtonObjectに『Button』オブジェクトをセット。GoalTextObjectには『GoalText』オブジェクトをセットします。

SphereBoosterのオブジェクト参照
SphereBoosterのオブジェクト参照

 

動作確認

んじゃ確認しましょ。

 

GIFにしてみたら容量がえらいことになったので、YouTubeに上げています。

中身についてですが、ボールを一度発射した後、停止した時に再度ボールを発射できるようになっています。発射する向きも後ろまで選べるようになっていますし、ゴールの上で止まったらアニメーションします。

うんうん、良さそう。

 

あとは細かいところですが、ボールが止まるまでの時間を短くするのと、落下時に少し演出を加えてみましょ。

まとめ

今回は盛りだくさんで、ゴールの作成とその動作を実装しました。

スタートからゴールまでの制御もできるようになったので、細かいところを修正して完成にしましょ。

次回はボールが止まるまでの時間を早送りできるようにするのと、落下時の演出を加えます。

     

ゲーム開発の攻略チャートを作りました!

CTA-IMAGE

「ゲームを作ってみたいけど、何から手を付けていいか分からない!」


そんなお悩みをお持ちの方向けに、todoがアプリをリリースした経験を中心に、ゲーム作りの手順や考慮すべき点をまとめたe-bookを作成しました。ゲーム作りはそれ自体がゲームのように楽しいプロセスなので、「攻略チャート」と名付けています。


ゲームを作り始めた時にぶつかる壁である「何をしたら良いのか分からない」という悩みを吹き飛ばしましょう!