【第26回】ゴルフゲームの進行を制御するUnityチュートリアル
- 2018.05.02
- Unityチュートリアル
- Unity, チュートリアル
前回のチュートリアルでは、ゴルフっぽいゲームで使うコースを作成しました。Prefabからオブジェクトをインスタンス化し、手動で配置するのって結構楽しい気がします。
今回は、前回作成しなかったゴールを設置するのと、ボタンの動きを少し変えてゲーム全体の進行を制御するようにしてみます。
前回のチュートリアルはこちらから。
今回の目的
コースのゴールを配置し、スタートからゴールまでの全体の進行を制御します。
ゴールに到達したら、UIテキストをアニメーションさせます。
プロジェクトの準備
前回のチュートリアルで作成したプロジェクトをそのまま使います。
このページに先にたどり着いた方は、チュートリアルの初回から追っていただけるといいかもしれません。
ゴール用オブジェクトの作成
何をもってゲームのゴールとするか、というのはどんなゲームでも考えないといけないことですね。
このゲームはゴルフっぽいゲームを目指しているので、ボールが到達するゴールを作成しないといけません。
分かりやすいのはゴール用オブジェクトを作ってしまうことなので、サクッとやっちゃいましょ。テクスチャは以下のものを使います。右クリックで保存してください。
このテクスチャをUnityの『Project』ウィンドウ内、『Textures』フォルダにインポートします。エクスプローラ(Win)またはFinder(Mac)からドラッグ&ドロップ。
インポート設定は特にいじらなくて大丈夫です。
続いてマテリアルを作成します。『Materials』フォルダに移動して、右クリックまたは二本指タップでコンテキストメニューを開きます。[Create] -> [Material]を選択。
Materialの名前は[Goal]にしました。そのまんま。『Goal』マテリアルを選択し、Albedoの左にある丸ボタンからインポートした『goal』をセットします。
続いてゲームのシーン内にゴール用のオブジェクトを作成します。『Hierarchy』ウィンドウでコンテキストメニューを開き、[3D Object] -> [Cube]を選択します。
作成したオブジェクトは[Goal]と名付けました。この『Goal』オブジェクトを選択し、Transformコンポーネントの歯車アイコンから[Reset]を選択した後、PositionのXを[85]に、Yを[0]、Zを[0]にします。
ScaleはXが[5]、Yが[0.2]、Zが[5]としています。0.1m分地面に埋まっていますが、あまり地面から出すと側面の衝突でゴール扱いになるので、これを避けています。
Box Colliderでは物理特性マテリアルの『Bound』をセット。衝突する物体として使いたいので、Is Triggerにチェックを入れないように注意。
次にMesh RendererのMaterialsに『Goal』マテリアルをセットします。
また、ゴールはコースの一部なので、『CourseObject』オブジェクトの子オブジェクトにします。ドラッグ&ドロップで簡単にできます。
また、ゴールに乗ったことを検知するため、衝突相手を特定するタグを付けます。Unityのデフォルトのタグで[Finish]があるため、それを設定しましょ。
ここまで設定を行うと、以下のようにゴールが表示されました。
このオブジェクトの上で止まった時に、ステージクリアとなるようにしましょ。
ゴール時のUIテキストを作成
ゴールしたことが分かるように、UIテキストを使って表示してみます。
『Hierarchy』ウィンドウから『Canvas』オブジェクトを選択し、右クリックまたは二本指タップでコンテキストメニューを開きます。[UI] -> [Text]を選択してUIテキストを作成します。
作成されたオブジェクトは[GoalText]にリネームしました。Rect Transformコンポーネントでは、Widthを[800]に、Heightを[400]に変更します。大きめにババーンと表示しましょ。
続いてTextコンポーネントの設定です。Textフィールドには[GOAL!!]を入力しました。ゴールした時に表示されるので、お好きな文字列を入れるといい感じ。
Font Styleは[Bold]、Font Sizeは[192]、Alignmentはどちらも中央に。Colorは[FFFFFFFF](白色)にしました。
いつものように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が表示されています。正直、ここまでやるなら画像にしちゃった方が楽かも。
Unityの機能でも多少装飾が可能ですよーという紹介のためにこの形にしてみました。
このゴールテキストはゴールした時に表示したいので、Rect Transformコンポーネント内、PositionのXを[1500]にしておきます。画面に映らなくなるので、ゴールした時にスライドインするようにしてみましょ。
スクリプトの編集
今の状態では、ボールを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』オブジェクトをセットします。
動作確認
んじゃ確認しましょ。
GIFにしてみたら容量がえらいことになったので、YouTubeに上げています。
中身についてですが、ボールを一度発射した後、停止した時に再度ボールを発射できるようになっています。発射する向きも後ろまで選べるようになっていますし、ゴールの上で止まったらアニメーションします。
うんうん、良さそう。
あとは細かいところですが、ボールが止まるまでの時間を短くするのと、落下時に少し演出を加えてみましょ。
まとめ
今回は盛りだくさんで、ゴールの作成とその動作を実装しました。
スタートからゴールまでの制御もできるようになったので、細かいところを修正して完成にしましょ。
次回はボールが止まるまでの時間を早送りできるようにするのと、落下時の演出を加えます。
ゲーム開発の攻略チャートを作りました!
-
前の記事
【第25回】ゴルフっぽいゲームのコースを作るUnityチュートリアル 2018.04.30
-
次の記事
【第27回】timeScaleを使ってボールを加速させるUnityチュートリアル 2018.05.03
コメントを書く