Unity入門12

Tanks!③

(タンク)

  • 前回までで、タンクと弾の動き、エフェクト部分が出来ました。
  • 次は、一旦カメラ側の処理を作っておきたいと思います。

前回の確認

  • さて、このゲームのカメラは少し特殊な動きをしています。
  • 必ず【2台のタンクがカメラ内に映るように動く】という点と【タンク間の距離によってカメラの表示範囲を変える】という動きを自動で行っています。次はこの部分を作っていきます。
  • この部分の処理が、このゲームで一番ややこしいかと思うので、先にこの動きがどう作られているかを解説しておきます。
    1. 常にタンクとタンクの中間地点に動く】オブジェクト(A)を用意し、カメラはそれを中心にして映します(この時、カメラをAオブジェクトの子オブジェクトにすることで、同じ動きになるため、Aオブジェクトを映し続けれます)
    2. Aオブジェクトとタンクまでの距離によってカメラの表示範囲を変える。
  • この2点の処理を行います。

カメラを動かす

  • では、まず空オブジェクトを作ってください。名前は【CameraRig】(カメラリグ)にしておきます。
    • リグとは「仕組まれた」のような意味ですが、このオブジェクトが【常にタンクとタンクの中間地点に動く】オブジェクトになります
  • 次に、カメラ(Main Cameraオブジェクト)を、このCameraRig】に子オブジェクトにしてください。
    • 次へ

カメラを動かす

  • CameraRig】(カメラリグ)と、【MainCamera】のTransformは以下の状態にしておきましょう。
  • ここからは、スクリプトで処理を作っていますので、まずは、新しいファイルを作りましょう。名前は【CameraControl】にしておきます。
  • まずは以下の変数を用意しておきます。

カメラを動かす

  • ここまでで一旦保存しておきます。
  • 次に、カメラの動きを確認するためには、先にタンクを2台にしておきたいので、Tankオブジェクトの複製を作っておきます。
  • Tank】オブジェクトを右クリックして【Duplicate】を選択します。これでTankとまったく同じ中身の【Tank(1)】オブジェクトが出来ていると思います。
    • これは動作確認用のオブジェクトなので、名前はそのままで良いです。
  • このオブジェクトはTankと同じように動いてしまうので、これを防ぐために【Move】スクリプトのチェックを外しておいてください。これで動きは止められます。

カメラを動かす

  • 次にCameraControl】スクリプトを【CameraRig】オブジェクトにアタッチします。
  • Inspectorビューから、配列Tanksに【Tank】と【Tank(1)】を代入します。
    • 普通にドラッグして、Tanksの文字にマウスを重ねてドロップすると代入できます。
  • それ以外の変数も含めて以下のようになっていればOKです。

カメラを動かす

  • ここから、スクリプトをさらにイジっていきます。まずStart()メソッドを以下のようにします。
  • このコードで子オブジェクトの【MainCameraのCameraコンポーネント】が【mainCamera】変数に代入されることになります。
    • GetComponentInChildren()】は、子オブジェクトからコンポーネントを取得するメソッドです。
  • 次にタンクとタンクの中間地点に動く】部分を処理を作りましょう。
  • 独自メソッドを定義します。名前はMove()メソッドにしておきます。

カメラを動かす

  • Vector3.SmoothDamp】という新しいメソッドを使っています。
  • これは、【なめらかに移動させる】ためのメソッドで、引数は以下の意味です。
    • 第一引数:現在の位置 → 【transform.position】そのまんまです。
    • 第二引数:目的の位置 →【FindRigPosition()】※後で作るメソッドです。
    • 第三引数:現在速度 → 上で用意したVector型の変数。
    • 第四引数:到達までの時間 → 上で用意したfloat型の変数。
  • わかりにくいのが「第三引数」の部分です。
    • この部分はVector3.SmoothDamp】が呼びされる度に勝手に値が変化し、自動的に増減します。こちら側は、その箱となる変数だけを作っておいてあげてるだけです。
      • ref】という見慣れないものが付いていますが、これは【参照渡し】という方法で値を引数にする方法です。ちょっとややこしいので、もう少し後でやりたいと思います。
      • 今は「Vector3.SmoothDamp】の第三引数は、refをつけておかないといけない」とだけ理解しておいてください。
  • このMove()メソッドは、【FixedUpdate()】メソッド内で動かします。このスクリプトでは【Update()】メソッドは使わないので、変更して以下のようにしておきましょう。

カメラを動かす

  • これで常にMove()メソッドが呼び出されるようになりました。
  • 後は、まだ作っていないFindRigPosition()】を作ります。
    • ※​【Vector3.SmoothDamp】の第二引数に指定したメソッドです。
  • さてVector3.SmoothDamp】の第二引数は【目的の座標】を指定する必要がありましたね。
  • つまり【FindRigPosition()】は、【目的の座標を返す】メソッドとして作る必要があります。

カメラを動かす

  • 少し整理すると今回の場合、【目的の座標】とはTankTank(1)の中間地点の座標】です。
  • 常にこの位置に【CameraRig】オブジェクトを動かすことによって、その子オブジェクトであるカメラの中心を【TankとTank(1)の中間地点】にすることが出来るのです。
  • では、【TankとTank(1)の中間地点の座標】を取得するにはどうすれば良いかと言うと、以下の計算で求められます。
    • Tank】の座標 +Tank(1)】の座標 / 2
  • ​簡単ですね。【オブジェクトとオブジェクトの中心座標は、そのオブジェクトの座標を足しあわし、その個数分割る】だけということです。
  • ただ、今回の場合はY軸に関して動かしたくないので、上記の計算後に【CameraRIg】の初期位置(0)で上書きしておく必要はあります。
    • ※地形などによって、タンクに微妙な高さの違いがあるため、上記の計算上、y座標は0にならないので。まぁ見た目にはわからない誤差レベルだけど。
  • なので、より正確に言うとFindRigPosition()】メソッドはTankとTank(1)の中間地点の座標(Y軸以外)】を返すメソッドにすれば良いということですね。
    • ​次へ。
  • 下の黒枠を記入しFindRigPosition()】メソッドを作ってください。
    • ヒント:配列に入れたTankの座標は【tanks[0].position】で指定できます。
    • Vector3 rigPos = Vector3.zero;】は、新しいVector型の変数を作っています。この変数が最終的にTankとTank(1)の真ん中の座標(Y軸以外)】になります。
    • rigPos.y = transform.position.y;】はY軸だけ【CameraRIg】の初期位置(0)に直している処理です。

課題①

  • 左の動画のように、【2台のタンクの中間地点】がカメラの中心になればOKです。
    • ※まだカメラサイズを変える処理は作っていないので、離れるすぎるとタンクが消えます。
  • 続いては、カメラのサイズを自動で変更し、タンクが画面から消えないようにする処理を作ります。
  • 以下のように新しいメソッドとして、Zoom()メソッドを作ってください。ちょっと見にくいですが…

カメラを動かす

  • mainCamera.orthographicSize】とは、【Camera】コンポーネントの【size】サイズのことです。
  • そこに代入する内容に、今回は【Mathf.SmoothDamp】というメソッドを使っていますが、これは先ほど使ったVector3.SmoothDamp】と基本的に同じです。
  • 唯一の違いとしては、【Vector3.SmoothDamp】が【現在の位置】【目的の位置】など【座標(Vector3)】の変化の時に使いましたが、【Mathf.SmoothDamp】は【現在の数値】【目的の数値】などの【数値(float)】の変化の時に使います。つまり引数は以下の意味になります。
    • 第一引数:現在の数値 → 【transform.position】そのまんまです。
    • 第二引数:目的の数値 →【FindCameraSize()】※後で作るメソッドです。
    • 第三引数:現在速度 → 上で用意したfloat型の変数。
    • 第四引数:到達までの時間 → 上で用意したfloat型の変数。※Move()メソッドの時と同じ。
  • 第四引数で、Move()メソッドと同じ【focusTime】変数を使っていることで、カメラの移動とサイズ変化は、同じタイミングで終わることになります。
  • では、Move()メソッドと同じようにこちらも【FixedUpdate()】で呼び出すようにしましょう。

カメラを動かす

  • 後は、まだ作っていないFindCameraSize()】メソッドを作るだけです。
  • 少し整理しておくと、このメソッドは【すべてのタンクがちょうど良い大きさで映るサイズ】を返す、ということをしたいわけです。
  • 【すべてのタンクがちょうど良い大きさで映るサイズ】というのは【CameraRIgオブジェクトから、それぞれのタンクまでの距離の中で、一番長い距離 + 画面余白分の距離】がカメラサイズになります。
    • ただ、一番長い距離といっても、Move()メソッドの処理で、CameraRIgオブジェクトは、タンクの中間地点に移動しますので、結局はどのタンクまでの距離も同じになります。
    • なので、実際は【どれか1つのタンクまでの距離 + 画面余白分の距離】でも、ほぼ同じ動くになりますが、タイムラグで微妙な違いが出るはずですので、今回はすべてのタンクとの距離を測ってから処理させましょう。
      • ​次へ。
  • では、以下のように【FindCameraSize()】メソッドを作ってください。
    • ※「CameraRigオブジェクトの目的の座標」の部分は、後で適切なコードを入力してもらうためなので、空白でOKです。

カメラを動かす

  • 新しいメソッドがいくつか出てきてますね。
  • まず、メソッド内の1行め、【transform.InverseTransformPoint()】とは【ワールド座標をローカル座標に変更する】メソッドです。
    • 前に【ローカル座標とは親オブジェクトへの相対座標】と言いましたが、このようにスクリプトから変更する場合は、そのスクリプトがアタッチされているオブジェクト、つまりCameraRIg】オブジェクトに対しての相対座標となります。
    • Move()メソッドで「CameraRigオブジェクトの目的の座標」に移動し続けてますので、目的座標に到着すれば、目的座標と【CameraRIg】が同じになり、ローカル座標はX:0, Y:0, Z:0になります。移動途中だけは数値は変わります。これを【rigLocalPos】という変数に代入しているということです。次へ。
  • 次の行でもtransform.InverseTransformPoint()】が使われていますね。
  • ココでは引数に【tanks[0].position】、つまり配列にいれたTankオブジェクトの座標を、ローカル座標に変換して【tankLocalPos】という変数に代入しています。
    • このローカル座標もCameraRIg】オブジェクトからの相対座標という意味になります。
  • ​その次の行【Vector3 rigToTank = tankLocalPos - rigLocalPos;】は、【タンクまでの相対座標から、目的の位置までの相対座標を引いて】、それを【rigToTank】に代入する処理です。
    • ​これは移動途中の座標の誤差をなくすためだけです。この式で求められた数値が【目的の位置】から【タンク座標】までの距離(座標)になるわけです。
  • ​次に【float size = 0f;】で【size】変数を作り、その下の行で【Mathf.Max()】というのを代入していますね。この【Mathf.Max()】とは、【引数を比較し、大きい方を返す】メソッドです。
    • その引数の中で使っているMathf.Abs】は、【引数の絶対値を返す】メソッドです。
      • 絶対値とは負の符号をムシした数、つまり「-5」なら「5」など、マイナスを取り除いてくれます。(プラスの場合はそのままです)
    • つまり【Mathf.Max(Mathf.Abs(rigToTank.y), Mathf.Abs(rigToTank.x) / camera.aspect);】の部分は、【(目的の位置から)タンクまでのヨコ軸距離と、タテ軸距離を比較し、遠い方を返す】という動きになります。
      • それをsize変数に代入して、最終的なカメラサイズとしているわけです。
      • 尚、【mainCamera】というのはアスペクト比(画面ヨコ幅÷タテ幅)というものです。ヨコの判定をアスペクト比の割合分、短くさせてから比較しています。こうすることで、アスペクト比の違う画面でも見え方を均等にしています。

カメラを動かす

  • この時点で【tanks[0]】のタンク(Tankオブジェクト)との距離で、一番長い軸の距離がわかりましたが、もう一台のタンクTank(1)オブジェクト)に関してはまだ出来ていません。
  • なので、tanks[1]】でも同じことをしなくてはなりません。つまり以下の感じになります。
    • ​※全体的に、ちょっとムダが多いコードですが、後で直します。

カメラを動かす

  • 追加した1行目、2行目はtanks[0]】で使った変数を、tanks[1]】で再利用しているだけです。
  • 3行目も似ていますが、第一引数にsize変数を追加していることが重要です。この時点でのsizeには【tanks[0]】タテヨコ軸の長い方の距離が入っていますので、それも比較しておく必要があります。
    • Mathf.Max()】はこのように3つ以上の引数を指定しても、一番大きいものを返してくれます。
      • ​次へ。

カメラを動かす

  • 後やらなければいけないことは以下です。
    • size変数にmargin(余白)分をプラスする】→これがないと、タンクが画面端に来てしまい、非常に見にくくなります。
    • size変数がminSize(最小カメラサイズ)より小さい場合は、minSizesizeにする】→これがないと、タンクどうしが近づいた時に画面がアップしすぎます。
    • もちろん最終的な結果のsize変数を返す処理も必要ですね。次へ。
  • 黒枠を記入し、FindCameraSize()】メソッドを完成させてください。

課題②

  • 左の動画のようになればOKです。
  • さて、これでカメラの「移動・サイズ変更」に関してはOKですが、もう少しだけ工夫をしてみたいと思います。
  • 現在は【タンクが2台の時だけ】しか対応できないコードです。これを【タンクが何台あっても】対応できるコードに変更したいと思います。
  • まずはタンクをもう一台増やして(Tankオブジェクトの方を右クリックしてDuplicateしておきましょう)、出来たTank(2)Tanks[]配列に代入しておきましょう。
    • これも動かないようにしておいた方が確認しやすいです。
  • この3台の状態で、動かしてみると以下のように、Tank(2)(右のタンク)をムシしてカメラが動くかと思います。

カメラを動かす

  • ちゃんとすべてのタンクが画面に映るように、カメラが「移動・サイズ変更」出来るようにしてみましょう。​次へ。
  • FindRigPosition()】と、【FindCameraSize()】メソッドを一部変更し、タンクが3台以上の場合でも、ちゃんと映るようにカメラの「移動・サイズ変更」処理を作ってください。
    • ヒント&条件:それぞれのメソッドの中で、必ず【for文】と【tanks.Length(配列の要素数を返すメソッド)】を使って実装してください。
      • ※以下の動画のようになればOKですが【FindCameraSize()】に関しては、間違っていても変化がわかりづらいので、完成すれば必ず見せてください。

課題③

  • さて、これでカメラの動きはバッチリですね。
  • 次に、タンクの体力を用意し、それぞれがダメージを与えられるようにしましょう。
  • まずは体力です。このゲームでは体力ゲージがありますので、まずはそれを用意します。
    • ため撃ちの矢印と同じように、これもUIの【Slider】で表示させています。
  • まずはTankオブジェクトのCanvasを右クリックし、【UI】→【Slider】を追加してください。
    • 名前は【Slider(1)】になると思いますが、HealthSlider】に変更しておいてください。
      • また、ため撃ち矢印の作った【Slider】も【ShellSlider】に変更しておきましょう。
    • HealthSlider】の中の【Handle Slide Area】は使いませんの削除しておきます。今、下の状態でになっていると思います。

タンクの体力

  • ここで、以前に設定した【Canvas】と【ShellSlider】、その中の【Fill Area】とFill】の数値を確認しておいてください※画像と同じ場合はそのままでOKです。
    • ※【Fill AreaFill】は次のページにあります。

ちょっと確認

Canvas

ShellSlider

Fill Area (ShellSliderの子オブジェクト)

Fill (ShellSliderの孫オブジェクト)

ちょっと確認

  • では、ここからはHealthSlider】から設定していきます。それぞれのコンポーネントを以下のようにしてください。※細かい項目も変更し忘れないように注意。
  • ここでの変更場所はほぼ【ShellSlider】と同じです。

タンクの体力

  • 次は【Background】です。ここは【ShellSlider】では削除していましたが、今回は使います。

タンクの体力

  • Image】コンポーネントの【Source Image】に入れている【Health Wheel】は、【Sprites】フォルダにあります。
  • その下の【Color】は白のままですが、色の透過率だけ変更しています。カラーウィンドウのAの項目です。
  • その下の【Image Type】を【Simple】変更しています。これは画像の拡大縮小時や表示方法に関連する項目で、Simple】は、ただただ単純に画像を表示するだけです。特に拡大縮小しないと思いますが、一応【Simple】にしておきます。
  • 次は【Fill Area】です。以下のようにしておきましょう。

タンクの体力

  • 最後は【Fill】です。以下のようにしておきましょう。

タンクの体力

  • Source Image】で使われている【Health Wheel】は【Background】と同じです。
  • Image Type】は【Filled】に変わっています。これは、画面表示に割合を変更できます。
    • 下の【Fill Method】で、基準となる表示方法、【Fill Origin】は表示方向、【Fill Amount】は値(0で非表示、1ですべて表示)となります。
      • 上記の場合は【Radial360】(画像の中心から円状)に表示、【Left】から、【0】(非表示)となります。
    • ちょっと文章だとわかりにくいので、実際にFill Method】を切り替え、【Fill Amount】の通知を変化させてみてください。※最終的には上の状態にもどしてください。
  • これで、以下のような状態になっていると思います。
  • 今、表示されているグレーの丸画像(Health Wheel)は【Background】の部分です。同じく丸画像を貼り付けている【Fill】は【Fill Amount】が0のため非表示ですね。

タンクの体力

  • このゲームでは、Fill】の丸画像が体力ゲージを表していており、ダメージをくらうごとに減っていきます。また減っていくごとに、色がからに変化していきます。
    • この部分は、もちろんスクリプトで処理させます。
  • 尚、Fill】オブジェクトは【HealthSlider】オブジェクトと連動しているため【HealthSlider】の【Value】項目を変化させれば、【Fill】オブジェクトの【Fill Amount】も変化します。
  • ​スクリプトでは基本的に【HealthSlider】オブジェクトを動かすことで、体力ゲージを変化させます。
  • では、体力を管理する新しいスクリプトを作ります。名前は【TankHealth】にしておきます。
    • Slider】を使いますので【using UnityEngine.UI;】も必要になります。
  • 次に、以下の変数を用意しましょう。

タンクの体力

  • 上記が出来たら、Tankオブジェクトにアタッチし、以下のようにしておきましょう。
    • ※【Fill】は【HealthSlider】の子オブジェクトの【Fill】なので注意。
  • まず、ゲームスタート時に【現在の体力の設定】と、それを【体力ゲージとして画面に反映させる】部分を作ります。
  • 以下のようにしてください。

タンクの体力

  • 1行目が【初期体力の設定】です。初期体力(startHealth)を現在の体力(currentHealth)に代入しています。
  • 2行目では【SetHealthUI()】というメソッドを呼び出しています。これは今から作るメソッドで【体力ゲージとして画面に反映させる】処理をさせたいと思います。
  • 別のメソッドにしているわけは【体力ゲージとして画面に反映させる】処理は、ダメージを受けるたびに行いたいからです。つまり、何度呼び出す必要があるので、独自メソッドでまとめるということですね。次へ。
  • では、SetHealthUI()】メソッドを作りましょう。これは以下のようにしてください。

タンクの体力

  • 1行目は【HealthSlider】オブジェクトの【Value】の値を、現在の体力にしています。
    • こうすることで【Fill】の【Fill Amount】の値も変わります。
  • 2行目は新しいメソッド【Color.Lerp()】というのが出てきています。このメソッドは、第一引数(赤色)と第二引数(緑色)の間で、第三引数(現在の体力÷初期の体力)の割合の値(色)を計算してくれます。
  • 例えば、ノーダメージ状態(体力100)であればに、体力が0になればに、その途中(残り体力50など)であれば、その中間の色になります。
    • ただし、体力ゲージは体力が減るごとに短くなっていきますので、完全に(体力0)の場合は表示されません。
    • ※数学的にいうと【Lerp】とは「線形補間」という計算方式です。これは色だけじゃなく別の型(Vector.Lerp())などでも使えます。
  • その【Color.Lerp()】で計算した色を【fillImage.color】に代入することで、実際の画面上の色が変わります。
  • 試しに動かしてみましょう。下のようになっていると思います。

タンクの体力

  • 現在は体力満タンなので緑色ですね。まだ攻撃くらってもダメージはありません。
  • 次は、ダメージの処理を作っていきたいと思います。
  • ちょっとここで休憩して、少し前に出てきた【ref】(レフ)の説明をしておきます。
    • ref】とは「変数の値を参照渡しする」という意味と言いましたね。
  • この【参照渡し】とは【変数に値を代入するのではなく、値がある場所の情報(住所)を代入する】という意味になります。
  • ​逆に今まで何度も使ってきた「通常の変数」は、【値のコピーを代入する】方法です。これを【値渡し】と言います

【値渡し】と【参照渡し】

【値渡し】での代入(a = b)

変数b

(aの場所)

変数a

(100)

【参照渡し】での代入(a = ref b)

変数a

(100)

変数b

(100)

両方とも100という数字が入る

変数bには数字はなく、参照場所(変数aの場所)だけが入っている。

  • 参照渡し】と【値渡し】の違いは、実はめちゃくちゃカンタンです。下の図を見てください。
    • 変数aには100という数字が代入されているとします。これを変数bに渡す(代入)するケースを例に、参照渡し】と【値渡し】の違いをみてみましょう。
  • では、【参照渡し】された変数bは、普通の変数とどう違うのでしょう?実は、変数を使う際はほとんど同じように使えます。
  • 例えば【a + 200】(aは数字の100が入っている)は当然300ですが、【b + 200】(bはaの場所が入っている)の場合も300になります。
  • このように、【参照場所】だけが入った変数でも、演算の際は参照先の値(つまりは変数aの値)を使って計算がされます。
  • この参照渡しが影響してくるのは、【参照している変数の値が変わった時】です。
    • 例えば、変数a(100)が変化すると、それを参照している変数bも変化するということですね。
    • 値渡し】は元の変数が変わっても、影響はありません。
      • 参照渡し】と【値渡し】は、この点が唯一の違いです。

【値渡し】と【参照渡し】

【値渡し】での代入(a = b)

変数a

(100)

【参照渡し】での代入(a = ref b)

変数b

(100)

変数a

(100)

200

を代入

変数a

(200)

変数b

(100)

変数b

(aの場所※200)

変数a

(100)

変数a

(100)

200

を代入

変数a

(200)

変数b

(aの場所)

変数b

(100)

  • ただ、先ほど例に出したような【a = ref b】というコードは、まだUnityまたはpaiza.ioなどでは使えません。
    • ※C#のバージョン7以降から。まだ対応が進んでいないため。
  • 現時点で使えるのは、【引数の中でだけ】です。つまり【Vector3.SmoothDamp(transform.position, FindRigPosition(), ref velocity, focusTime);】などにしか使えません。
  • また、以前にやったレイキャスト(カメラからの光線)を飛ばし、判定を取る処理で【out x】とみたいな書き方が出てきましたが、この【out】も参照渡しの意味になります(少し特殊な参照渡し)。
    • これは「戻り値以外にも値を返したい時」などに使いますが、同じようなもんです。
  • 上記のようにUnityでは、特定のメソッドで【ref】や【out】をつけなければいけない引数がありますが、それは、「そのメソッドの内部で【参照渡し】の処理が使われているから」とだけ考えておけばOKです。
  • 自分でref】などを使ったメソッドを作ることはほぼありません(個人的には1度もない)ので、あまり気にしなくてもいいでしょう。
    • ​※参照渡しが必要になるようなメソッドは、そもそも作り方がおかしい、という意見もあります。
  • ※ただし、【値】なのか?と【参照】なのか?の違いは、実はいろんなところで必要となる考え方です。この違いはしっかり理解しておいてください。

ref(参照渡し)

お疲れ様でした

  • 今回でカメラの動き、体力ゲージができました。
  • 次回はダメージ判定をつくっていきましょう。
  • Tank!④」に続く。

Tanks!③

By kinocode

Tanks!③

  • 1,363