ではガンシューティングの機能を作成しましたが、この場合物理的な弾を飛ばすのではなくスクリプトで銃の向いている方向と距離の間に敵がいたらダメージを与えていました。
しかし、銃に限らず何か物を遠くに飛ばす時に視覚的に何かを飛ばし、その飛ばしたものが敵に当たったら敵にダメージを与えるという風にしたい場合もあります。
今回は、
で作成したFPS風のシューティング機能を改造して銃口から弾を飛ばしてその弾が敵に当たった時に敵を消すようにしてみます。
FPSの銃から弾を飛ばすだけでなく、通常のアクションゲームで主人公が持っている武器から、または主人公が石を投げる、といった時にも適用出来ます。
何らかの物を飛ばす位置を、銃口、武器、手と変更するだけなので色々な場面に適用出来ると思います。
それでは「Unityのアクションゲームで照準をマウス操作で動かす」で作った簡易FPSを改造していきます。
弾が出る位置を銃に設定
まずは銃の子要素に銃口に当たるMuzzleを作成します。
上のように銃の子要素に右クリック→3D Object→Cube、またはSphereを作成しMuzzleと名前を付けます。
Muzzleは上のように銃口の位置とサイズに合わせます。
Muzzleの青矢印の方向を銃口の先になるように調整します。(弾はこちらの方向に飛ばすようにします)
Muzzleは銃の先に置いてください(弾が銃とぶつかってしまい変な方向に飛ぶ可能性がある為)
青矢印の方向を合わせる場合は上のようにLocalにしておく必要があります。
次にAssault_Rifle_A3_v2にAddForceBulletスクリプトを作成し、取りつけます。
前回Scriptという名前の空オブジェクトに設定していましたが、実際に弾を飛ばすのは銃になるので、銃に設定し直します。
AddForceBulletスクリプトは後で作成します。
飛ばす弾のプレハブを作成する
次に銃から発射する弾を作成していきます。
ヒエラルキー上で右クリック→3D Object→Sphereを選択します。
名前をBulletとします。
Sphere ColliderのIsTriggerをチェックする場合は物理的な衝突ではなく敵と接触したかどうかで判断させます。
チェックをしない場合は物理的な衝突で判断します。この場合は壁などと衝突すると跳ね返るようになります。
ここら辺はゲームの内容や飛ばす物によって変更します。
両方で試してみてお好みの方法を選択してください。
弾が敵に当たった時の処理スクリプトBulletAttackの作成
BulletにはインスペクタのAdd ComponentからPhysics→Rigidbodyを取りつけ、新しいスクリプトBulletAttackを作成し取りつけます。
RigidbodyのUse Gravityにチェックを入れると弾が銃から飛び出した後に重力が働きます。
今回は銃から飛び出る弾なのでチェックを外し飛び出した先にまっすぐ飛ぶようにします。
BulletAttackは弾が敵と接触した時に敵を消す為の処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using UnityEngine; using System.Collections; public class BulletAttack : MonoBehaviour { // コライダのIsTriggerのチェックを外し物理的な衝突をさせる場合 void OnCollisionEnter(Collision col) { if(col.gameObject.tag == "Enemy") { Destroy(gameObject); Destroy(col.gameObject); } } /* // コライダのIsTriggerのチェックを入れ物理的な衝突をしない(突き抜ける)場合 void OnTriggerEnter(Collider col) { if(col.tag == "Enemy") { Destroy(col.gameObject); } } */ } |
BulletのSphere ColliderのIsTriggerのチェックの有無で発生するイベントが変わるので二つの方法を記述しています。
IsTriggerのチェックを入れてない場合は衝突判定をするのでOnCollisionEnterメソッドを使用します。
IsTriggerのチェックを入れている場合はOnTriggerEnterメソッドを使用します。
Sphere Colliderのチェックの有無でOnCollisionEnterとOnTriggerEnterのどちらかをコメント化してください。
そうしないとコンソールにエラーが表示されます。
Collisionの場合は弾と敵を削除しています。
Triggerの場合は敵のみ削除しているので、その先に敵がいた場合はその敵も消えます。
この違いは特に理由はないのでCollisionで弾を消さない仕様にしても問題ありません。
これで弾の作成が終わったのでBulletをAssetsフォルダにドラッグ&ドロップしてプレハブ化してください。
プレハブ化したらヒエラルキーのBulletは削除します。
銃から弾を飛ばす処理をAddForceBulletに書く
次に銃から弾を飛ばすスクリプトAddForceBulletに処理を記述していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | using UnityEngine; using System.Collections; public class AddForceBullet : MonoBehaviour { // カーソルに使用するテクスチャ [SerializeField] private Texture2D cursor; // 弾のゲームオブジェクト [SerializeField] private GameObject bulletPrefab; // 銃口 [SerializeField] private Transform muzzle; // 弾を飛ばす力 [SerializeField] private float bulletPower = 500f; void Start () { // カーソルを自前のカーソルに変更 Cursor.SetCursor(cursor, new Vector2(cursor.width / 2, cursor.height / 2), CursorMode.ForceSoftware); } void Update () { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); transform.rotation = Quaternion.LookRotation(ray.direction); RaycastHit hit; if (Physics.Raycast (ray, out hit, 1f, LayerMask.GetMask ("Gun"))) { Cursor.visible = false; } else { Cursor.visible = true; } // マウスの左クリックで撃つ if(Input.GetButtonDown("Fire1")) { Shot(); } } // 敵を撃つ void Shot() { var bulletInstance = Instantiate<GameObject>(bulletPrefab, muzzle.position, muzzle.rotation); bulletInstance.GetComponent<Rigidbody>().AddForce(bulletInstance.transform.forward * bulletPower); Destroy(bulletInstance, 5f); } } |
インスペクタで弾のゲームオブジェクトbulletPrefab、銃口のTransformのMuzzle、弾を飛ばす力のbulletPowerを設定します。
bulletPrefabにはBulletのプレハブ、Muzzleには銃の子要素のMuzzle、bulletPowerには500を設定します。
bulletPowerは弾が飛んでいく力なので自由に設定してください。
マウスの左クリックをした時にShotメソッドが呼ばれ、Bulletをインスタンス化します。
Bulletの位置はMuzzleの位置と角度に設定し、BulletのRigidbodyコンポーネントを取得しAddForceでBulletの向きにbulletPowerの力を加えて飛ばします。
Destroyで5秒後には自動で消えるようにします。
でないとBulletインスタンスであふれかえり処理速度が落ちます。
上が銃のインスペクタになります。
敵にEnemyタグを設定し弾が衝突した時に識別出来るようにしておきます。
それではUnityの実行ボタンを押して確認してみます。
上のように銃から弾が飛び出し敵に当たったら敵が消えるようになりました。
また、坂の辺りに弾が当たるとコライダのIsTriggerにチェックを入れていないので跳ね返っているのがわかります。
しかし少し気になる所があります。
照準に弾が飛んでいっていない?
それは照準の方向に弾が飛んでおらずずれてしまっています。
実はこれ、照準の方向に飛んでいるんですが、飛んでいないように見えてしまっているんです。
『Unityのアクションゲームで照準をマウス操作で動かす』記事で作成した機能でマウスカーソルを照準にして動かし、クリック操作で敵を消しましたが、
この機能をそのまま利用するとおかしくなります。
あくまで照準を使いたい!という方はMuzzleの子要素に照準のゲームオブジェクトを設置しZ軸の位置を100辺りに設定します。
そうすると銃口の先に照準のゲームオブジェクトが表示されるので、照準の方向に弾が飛んでいるように見えます。
ですが照準が物理的に存在しているので、銃口と照準の間に敵や壁など他のゲームオブジェクトが入ると照準が見えなくなります。
これの対処は後で行います。
また弾が飛んでいく方向の途中に照準があるのでマウス操作で動く銃の場合照準の位置はある程度制限されてしまいます。
Muzzleの子要素に照準であるTargetを設置し、マウスの照準との違いを確認してみます。
奥にあるのがマウスカーソルを照準にしたもので、手前にあるのがMuzzleの子要素に設置したゲームオブジェクトの照準です。
表示されている位置が違うので混乱しますね・・・(^_^;)
上がDebug.DrawRayを使用して視覚的に弾が飛んでいく場所を表示したものです。
Muzzleの子要素の照準だけに弾がちゃんと飛んでいるように見えていましたが、ちゃんとマウスカーソルの照準にも飛んでいています。
ただこの線自体を表示しない限りは実際の弾を飛ばす場合にマウスカーソルの照準は使えません。
MuzzleにLineRendererコンポーネントを設置して実際の線を表示するのもいいかもしれません。
実際の物理的な弾を飛ばす場合はMuzzleの子要素にTargetのゲームオブジェクトを設置するか、
マウスカーソルを非表示にし、照準も設置しない方法もあります。
Muzzleの子要素にTargetゲームオブジェクトを設置した場合は先ほど言及したとおり、
Muzzleと子要素のTargetゲームオブジェクトの間に敵がいる場合に照準であるTargetが見えなくなります。
これの対処をしていきます。
照準をMuzzleの子要素にした時見えなくなる時の対処法
MainCameraの子要素にヒエラルキーで右クリック→Cameraで新しいカメラを作成します。
レイヤーに新しくTargetを作りMuzzleの子要素であるTargetオブジェクトのレイヤーをTargetにします。
MainCameraのインスペクタでCullingMaskでTargetのチェックを外します。
子要素のCameraのClear FlagsをDepth Onlyにし、Culling MaskをTargetのみにします。
これで子要素のCameraのみ照準のTargetを表示するので、MuzzleとTargetの間に敵がいても常に表示されるようになります。
またTargetはMuzzleの子要素なので銃の向きが変わると銃の向きに角度が変わってしまいます。
その為Targetには以下のスクリプトを追加し常にMainCameraの向きを向くようにします。
1 2 3 4 5 | void Update () { transform.rotation = Camera.main.transform.rotation; } |
上の画像のようにMuzzleの子要素のTargetが敵の前に表示されています。
実際には上のように照準(Target)は敵より後ろ側にありますが、カメラを作り設定をした事でMainCameraの子要素のCameraで常に照準が表示されるようになりました。
これでMuzzleの子要素に照準(Target)を置いても問題は少なくなりました。
照準を消して物理的な弾だけで狙いを定める
次は照準自体は消してしまうやり方です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | using UnityEngine; using System.Collections; public class AddForceBullet : MonoBehaviour { // 弾のゲームオブジェクト [SerializeField] private GameObject bulletPrefab; // 銃口 [SerializeField] private Transform muzzle; // 弾を飛ばす力 [SerializeField] private float bulletPower = 500f; void Start () { Cursor.visible = false; } void Update () { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); transform.rotation = Quaternion.LookRotation(ray.direction); // マウスの左クリックで撃つ if (Input.GetButtonDown ("Fire1")) { Shot (); } else if (Input.GetButtonDown ("Fire2")) { Cursor.visible = !Cursor.visible; } } // 敵を撃つ void Shot() { var bulletInstance = Instantiate<GameObject>(bulletPrefab, muzzle.position, muzzle.rotation); bulletInstance.GetComponent<Rigidbody>().AddForce(bulletInstance.transform.forward * bulletPower); Destroy(bulletInstance, 5f); } } |
Cursor.visibleにfalseを代入するとカーソルが非表示になります。
ゲーム中はいいんですが、いざ実行を止めようと思った時にカーソルが見えないと不便なので、右クリックした時にカーソルを表示するようにしています。
Escキーを押してもカーソルが表示されます。
上のようにマウスカーソルが消えました。
ただ弾が小さい為、敵に当てようと思うと結構大変です。
これでUnityのアクションゲームで物理的な弾を飛ばして敵との当たり判定をする事が出来るようになりました。
マウスで照準を動かす機能を改造して作ったので面倒な事になりました・・・(^_^;)
ゲームオブジェクトをインスタンス化して動かす処理は色々な所に応用出来るかと思います。