Unityでカメラのレンズの中心からマウスクリックした位置に弾を飛ばす

今回はカメラのレンズの中心からマウスクリックした位置に弾を飛ばす機能を作成します。

以前作成した

Unityのガンシューティングゲームなどで銃から弾を飛ばす場合にスクリプトで敵と接触したか判断するのではなく、物理的な弾を飛ばしその弾が当たったかどうかで判断させます

では銃口の先に弾を飛ばすという処理をやりましたが、それを少し変更する感じですね。

↑のような機能を作成していきます。

カメラのレンズが始まる位置から弾を飛ばすようにしています。

↑のサンプルでは重力を働かせていませんが、重力を働かせた場合のサンプルも作成していきます。

スポンサーリンク

飛ばす弾の作成

まずは飛ばす弾のプレハブを作成していきます。

ヒエラルキー上で右クリック→3D Object→Sphereを選択し、名前をBulletとします。

飛ばす弾のプレハブを作成

↑のようにScaleの調整をし、Rigidbodyコンポーネントを取り付けてUse Gravityのチェックを外し重力を働かせないようにします。

BulletTestスクリプトを作成し取り付けます。

BulletTestスクリプトはこの後作成します。

弾に力を加えるBulletTestスクリプト

BulletTestは弾が存在している間はRigidbodyに力を加えて飛ばす処理を記述します。

Awakeで自身に設定されているRigidbodyコンポーネントを取得します

OnEnableメソッドはゲームオブジェクトがアクティブになった時に呼ばれるメソッドです。

Camera.main.ScreenPointToRay(Input.mousePosition)でカメラからマウスの位置までレイを飛ばしそれをrayに保持しておきます。

弾のゲームオブジェクトがアクティブになってからdeleteTimeで指定した時間が経ったらDestroyで弾のゲームオブジェクトを削除します。

OnCollisionEnterは弾に設定しているコライダが他のゲームオブジェクトと衝突した時に呼ばれるので、その中でEnemyタグを設定したゲームオブジェクトと衝突した時は、

弾自身のゲームオブジェクトと当たった相手のゲームオブジェクトを削除しています。

FixedUpdateメソッドは固定フレームレートで呼ばれるメソッドで剛体(Rigidbody等の物理的な挙動)を使う時はこの中で処理をするといいみたいです。

そんなわけでray.directionでカメラの位置からマウスの位置の方向を取得し、そこに力を加えています。

FixedUpdateが呼ばれる度にAddForceで弾に力を加えているので、RigidbodyのUse Gravityにチェックを入れても効果の程が解り辛いです。

そんな時はFixedUpdateで毎回力を加えるのではなく最初に強い力を加えてFixedUpdate処理をしないようにします。

そこら辺の処理は最後にやりたいと思います。

これで弾が出来たのでAssetsフォルダにドラッグ&ドロップしてプレハブにします。

敵のゲームオブジェクトの作成

次に敵のゲームオブジェクトを作成します。

今回は弾が物理的に当たるようにし、OnCollisionEnterで判定しているので、衝突対象も物理的に当たるようにします。

ヒエラルキー上で右クリック→3D Object→Cubeを選択し名前をEnemyとします。

インスペクタのTagに新しくEnemyタグを作成し設定します。

敵に見立ててCubeを配置する

上のTagの部分をEnemyにします。

出来たらCtrl+Dキーでコピーして位置を変えたりしていくつか設置しておきます。

MainCameraに弾を発射するスクリプトを取り付ける

次にカメラから弾のプレハブをインスタンス化して弾を発射させるので、新しくShotTestスクリプトを作成し取り付けます。

インスペクタでbulletに弾のプレハブを設定します。

offsetはカメラレンズからのオフセット値で、カメラレンズと同じ位置から弾を飛ばすと弾のゲームオブジェクトの中身が表示されてしまうので、それを回避する為に設定します。

今回はZ軸の値のみ変更する為、floatにしてますが、Vector3型にして上下左右のオフセット値を指定する事も出来ます。

Input.GetButton(“Fire1”)でマウスの左ボタンを押している間はメインカメラのビューポート(カメラの表示領域の横と縦を0~1の割合で現したもの)の中心+オフセット値を取得し、

Instantiateで弾の位置をこのレンズの中心+オフセット値を指定します。

ShotTestは

ShotTestスクリプトの設定

↑のように設定しました。

弾の衝突設定を変更する

ShotTestスクリプトでInput.GetButtonで処理をする為、ボタンを押している間ずっと弾をインスタンス化しています。

その為、弾同士がぶつかって跳ね返ってしまいます。

そこで、弾同士は衝突判定をしないように設定します。

UnityメニューのEdit→Project Settings→Physicsを選択します。

弾同士が衝突しないようにする

↑のようにPhysicsのLayer Collision MatrixでBullet同士のチェックを外し、お互いが衝突しないようにします。

弾を撃ってからのフリーズ時間を作る

現時点では弾はUpdateの度に実行されていますが、あまりに弾が連続して発射されても困るので1回弾を発射してから次の弾を発射するまでに一定時間が必要となるようにします。

ShotTestに処理を追加します。

弾を飛ばしてからの時間(elapsedTime)が待ち時間(waitTime)を超えていなければその後の処理をしないようにします。

こうすることで弾を飛ばす間隔が広くなります。

弾に重力を働かせる

弾に取り付けたRigidbodyのUse Gravityにチェックを入れ弾に重力を働かせるようにして確認してみます。

しかしBulletTestスクリプトのFixedUpdateメソッドで毎回力を加えているので重力の効果が解り辛いです。

そこで力を加えるのは最初に1回だけにする事にしましょう。

BulletTestスクリプトを修正します。

FixedUpdateメソッドを削除し、OnEnableメソッド内で力を1回加えます。

力を加えるのが1回だけになったので、BulletTestで設定するpowerを上げます。

RigidbodyのUserGravityにチェックをいれ力を加えるのは1回

それでは確認してみましょう。

↑のようになりました。

弾に与える力の調整をする事で、放物線を描いて飛ぶ弾にすればちょっとした的当てゲームの作成も出来そうですね。(^^)/

終わりに

今回の処理では弾を飛ばす度に弾のプレハブをインスタンス化していますが、弾をあらかじめインスタンス化しておき、それを再利用(プール)する方法もあります。

毎回弾をインスタンス化したり削除したりといった事をしないので、メモリの読み書きの節約やガベージコレクションの発生が抑えられます(らしいです)。

それは次回以降やってみたいと思います。

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます

コメント

  1. しなしな より:

    FPSゲームを作成していて,カメラの真ん中にレティクルを配置しているんですけど.
    そのレティクルからメインカメラが向いている方に弾を飛ばしたくてこのサイトを活用さしてもらっています.
    弾の制御で
    void OnEnable(){
    //メインカメラの位置取得
    cameraposi = GameObject.FindWithTag(“MainCamera”).transform.position;
    //カメラの真ん中にレイを飛ばす
    ray = Camera.main.ScreenPointToRay(cameraposi);

    //弾を発射してから一定時間経つと消えるように
    Destroy(this.gameObject,deleteTime);
    }
    としてみたのですがうまくいきませんでした.
    どうすればよいでしょうか.

    • カメラの中心位置からカメラの向いている方向に弾を飛ばしたいということですね?

      Camera.main.ScreenPointToRay()メソッドは、カメラから引数で与えた位置までのRayを取得するので、カメラの中心から向いている方向のRay型のデータを得るには

      等とすればカメラの位置からカメラの前方×距離の位置までのRayが得られます。

      弾自身に力を加えるスクリプトを設定しないのであれば、カメラ自体にスクリプトを取り付けそこでRigidbodyを取り付けた弾に力を加えるという方が簡単かもしれません。

      カメラにスクリプトを取り付けたので、弾のプレハブをインスタンス化する時にカメラの位置と角度を指定し、その弾に設定しているRigidbodyを取得し、カメラの前方に力を加えています。

      • しなしな より:

        2つ目に提案してもらった,maincameraの方のスクリプトを書いたんですが,オンラインゲームを想定していて,登場してくる人は同じ”Player”タグで,bulletが”player”タグにぶつかったら”Player”を消去するという物なんですけど,弾をtransform.pisitionで取得した場所に生成して飛ばすと生成した自分に当たって自分が消えてしまいます.
        transform.positionを取得してx,z座標に0.5ずつ足して生成すれば,自分は消えずに弾はまっすぐ飛ぶのですが,真ん中からは生成されずキャラクターの向きによって生成場所が異なってしまいました.
        キャラクターの向いている方向の生成場所を定める方法とかはありますか?

        • この記事中の「MainCameraに弾を発射するスクリプトを取り付ける」の項目にあるようにカメラの中心+カメラの描画の最初の位置+補正値であれば特別に位置を調整する必要はありません。

          カメラの位置(transform.position)を使って弾の発射位置を変更したい場合は

          https://gametukurikata.com/basic/transformproperty

          上の記事の「Vector3とTransformのStatic変数について」の項目にあるように、指定したゲームオブジェクトの前方(transform.forward)を使うと実現出来ます。

          今回の場合のtransform.forwardはカメラの前方を表していますので、例えばカメラの前方0.5m前から弾を発射したい場合は

          でカメラの前方0.5mの位置が取得出来ますので、この位置に弾をインスタンス化してみてください。

          • しなしな より:

            確かに!forwardに距離を掛ければよかったんですね!
            こちらのサイトは丁寧な説明とコメントの返信が早くて助かってます.
            これからも参考にさせていただきます.

          • しなしな より:

            あとひとつ前からの疑問があって,例えば経過時間(自分の場合はFPSなので何分かたったら自動的に終了とをしたいです)を取得したいときにdeltaTimeだとそれぞれのPCの性能によって変わってきちゃうじゃないですか?
            PCに依存しない正確な時間の取得の仕方って何かありますかね?DataTimeとかで取得してもなんか年とかまでいくと処理がめんどくさそうで…

          • deltaTimeはUnityマニュアルで

            最後のフレームを完了するのに要した時間

            でUpdate1回の処理時間が計算出来るのでそれを足していくので、オフラインのゲームであれば個人のPCの処理速度の違いはあっても計算は出来てると思いますが、deltaTime以外ではコルーチンを使って時間経過で何かを実行する事も出来ます。

            DateTimeを使うと標準時から計算出来そうですが、実際に作っていないのでなんとも言えません。

            それともオフラインではなくネットワークゲームでのイベント処理の時間をローカルプレイヤー全部で同期したいということですかね?

            ネットワークゲームの場合はサーバーにデータを保存して出来るゲームの場合はサーバーの時間を元にしてイベント発生の処理をしているんじゃないですかね?

            UNET等のリアルタイム型のゲームの場合はホストの経過時間に合わせるとか、「ホストの経過時間+ローカルからホストまでの通信時間」を計算して時間を同期させるとか。

            そこら辺は専門的な話になるのでわたくしにはちょっとわかりませんが・・・・(^_^;)

  2. 名前 より:

    とても参考になりました