シンプルなアクションゲームを作ってみようの第18回です。
今回は大砲を作成し、大砲から弾を飛ばしてプレイヤーに当たったらダメージを与える機能を作成していきます。
前回はプレイヤーキャラクターのHPを管理するアセットの作成とHPをUIに表示する機能を作成しました。

シンプルなアクションゲームを作ってみようの他の記事は

シンプルなアクションゲームを作るのを通してUnityの使い方を学ぶカテゴリです。
から参照出来ます。
大砲を作成する
今回作成する大砲はUnityのプリミティブな図形のCubeを使って作成します。
大砲のゲームオブジェクトを作成する
まずは大砲の形状を作成していきます。
Environmentゲームオブジェクトを選択した状態で右クリックからCreate Emptyを選択し、名前をCannonとします。
Cannonは空のゲームオブジェクトなので大砲のベースとするゲームオブジェクトとして使用します。
次に大砲の土台を作成します。
Cannonを選択した状態で右クリックから3D Object→Cubeを選択し、名前をGunMountとします。
GunMountを選択し、インスペクタのTransformの値を変更します。
シーンビューでは以下のようになりました。
Cannonの位置がプレイヤーキャラクターと被っているので、Cannonの位置をずらし見やすい場所に移動させます。
CannonのインスペクタのTransformのXは-3.38にしました(シーンビューでCannonのX軸をドラッグしてもOK)。
とりあえず見やすければ問題ないです。
次に砲台部分のゲームオブジェクトを作ります。
ヒエラルキーでCannonを選択した状態で右クリックから3D Object→Cubeを選択し、名前をBarrelとします。
Barrelゲームオブジェクトを選択し、インスペクタのTransformの値を変更します。
シーンビューで確認すると以下のようになりました。
これで大砲の形状が出来たように思いますが、大砲の砲身部分であるBarrelは回転してキャラクターの方向を向いて弾を発射出来る機能を取り付ける予定です。
しかし、砲身であるBarrelはCubeゲームオブジェクトから作成したのでBarrelを回転するとBarrelの中心がCubeの真ん中にあるため中央で回転してしまいます。
Barrelを選択し、Pivot表示にしてシーンビューを見るとわかりやすくなります。
つまりスクリプトで回転処理をさせようと思うと上のシーンビューの赤い四角の部分を中心に回転してしまいます。
そこでBarrelの根本部分から回転出来るようにちょっとした工夫を加えたいと思います。
砲身の基点のゲームオブジェクトの作成
砲身であるBarrelが根本で回転するように、Barrelの根本の部分に空のゲームオブジェクトを作成し、その空のゲームオブジェクトの子要素にBarrelを移動して、空のゲームオブジェクト自体を回転させれば子であるBarrelも相対的に回転するので砲身の根元から回転しているように見えます。
そこでBarrelの根元のゲームオブジェクトを作成します。
Cannonを選択した状態で右クリックからCreate Emptyを選択し、名前をBasePointOfTheBarrelとします。
BasePointOfTheBarrelの位置は砲身であるBarrelの根本の位置に移動させます。
またHandle LotationがLocalであることを確認し、ローカル軸の青い矢印(そのゲームオブジェクトの前方)が砲身の先を向くように回転させます。
わたくしの場合はBasePointOfTheBarrelのTransformを以下のようにしました。
これでBarrelの根本であるBasePointOfTheBarrelが出来ました。
次にBarrelをドラッグしてBasePointOfTheBarrelの子に移動させます。
弾を飛ばす位置の作成
次は砲身から弾を飛ばす時の、弾を発射する位置を作成します。
Barrelゲームオブジェクトを選択した状態で右クリックからCreate Emptyを選択し、名前をLaunchPositionとします。
次にHandle PositionをLocalにし、LaunchPositionの移動と回転を行います。
上のような位置に移動させ、ローカル軸の青い矢印(そのゲームオブジェクトの前方)が弾が飛んでいく方向に向けます。
変更したLaunchPositionのTransformは以下のようになりました。
位置は弾のインスタンスの大きさによっては変更する必要が出てくるかもしれません。
砲身を回転させるスクリプトの作成
次に砲身を回転させるスクリプトを作成していきます。
ここでは砲身と言っていますが、実際には砲身の根本であるBasePointOfTheBarrelを回転させます。
Assets/Scriptsフォルダに新しくCannonRotationScriptという名前のスクリプトを作成し、BasePointOfTheBarrelゲームオブジェクトに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class CannonRotationScript : MonoBehaviour { // 砲身を回転させるかどうか [SerializeField] private bool rotatable; // 回転スピード [SerializeField] private float rotationSpeed = 2f; private Transform player; private Rigidbody rigidBody; // 砲身が向く角度 private Quaternion viewingQuaternion; // Start is called before the first frame update void Start() { player = GameObject.FindWithTag("Player").transform; rigidBody = GetComponent<Rigidbody>(); viewingQuaternion = Quaternion.identity; } // Update is called once per frame void Update() { if(rotatable) { // 徐々に砲身をプレイヤーの方向に向ける為の処理 viewingQuaternion = Quaternion.Slerp(rigidBody.rotation, Quaternion.LookRotation(player.position - rigidBody.position + Vector3.up * 0.8f), rotationSpeed * Time.deltaTime); } } private void FixedUpdate() { // 砲身の根元を回転させる if(rotatable) { rigidBody.MoveRotation(viewingQuaternion); } } } |
rotatableは砲身を回転させるかどうかで、インスペクタでオン・オフを切り替えられます。
rotationSpeedは砲身の回転スピードです。
playerはプレイヤーキャラクターのTransformを入れます。
rigidBodyはBasePointOfTheBarrelに取り付けるRigidbodyを入れます。
viewingQuaternionは砲身を向ける角度を入れます。
StartメソッドでGameObject.FindWithTagメソッドを使ってPlayerタグを取り付けられたプレイヤーキャラクターを探し、そのTransformを取得してplayerに入れます。
rigidbodyを自身から取得します。
viewingQuaternionには最初Quaternion.identityを入れます。
Quaternion.identityは親の軸の回転に合わせるか、ルートの親(一番の親)である場合はワールド軸の回転していない状態と同じです。
UpdateメソッドではQuaternion.Slerpメソッドを使って第1引数のQuaternion値を徐々に第2引数のQuaternion値に変更します。
値は第3引数で指定した値で補間されます。
第3引数の値は0~1の間の数値に変換され0であれば第1引数のQuaternion、1であれば第2引数のQuaternionと一致します。
その間の数値であれば第1引数と第2引数の間のQuaternionが得られるという感じになります。
今回の場合は第1引数のrigidBody.rotationの値がプレイヤーの方向を向くように変化していきますので、第3引数は回転スピードのrotationSpeedとTime.deltaTimeを掛けたものを指定しています。
rotationSpeedを大きくしても最大で1にしかならないので1の場合はプレイヤーキャラクタの方向を完全に向いた回転を得られます。
仮に毎回Updateメソッドが0.02秒毎に呼ばれるとしたらrotationSpeedを2とした場合に
1 2 3 | viewingQuaternion = Quaternion.Slerp(rigidBody.rotation, Quaternion.LookRotation(player.position - rigidBody.position + Vector3.up * 0.8f), 0.04f); |
となるのでrigidBody.rotationとQuaternion.LookRotation(player.position – rigidBody.position + Vector3.up * 0.8f)の間の0~1の割合の0.04の回転を得られるという感じになります。
イメージとしては以下のようになります。
現在の砲身の回転はどんどん更新されていきますが、現在の回転と目的の回転の割合の0.04の部分の回転を取得している感じです。
Quaternion.LookRotationは引数で指定した方向の回転を得られます。
ここではプレイヤーの位置から砲身の位置を引いてプレイヤーの方向を指定していますので、プレイヤーの方向の回転が得られます。
Vector3.upはnew Vector3(0f, 1f, 0f)と同じでそれに0.8fを掛けて補正値としています。
これはplayer.positionはキャラクターの足元が基点となっているので、キャラクターのお腹の辺りに砲身を向ける為に補正値を加えています。
FixedUpdateメソッドでは計算した回転にRigidbodyのMoveRotationメソッドを使って回転させています。
これで砲身を回転させるスクリプトが出来ました。
砲身はスクリプトからRigidbodyを介して回転させますのでRigidbodyコンポーネントを取り付けます。
BasePointOfTheBarrelゲームオブジェクトのインスペクタのAdd ComponentからPhysics→Rigidbodyを選択し取り付けます。
CannonRotationScriptのRotatableにチェックを入れてみます。
RigidbodyのIsKinematicにチェックを入れてスクリプトから操作するようにします。
弾のプレハブの作成
次は砲身から飛ばす弾のプレハブを作成します。
ヒエラルキー上で右クリックから3D Object→Sphereを選択し、名前をBulletとします。
Bulletゲームオブジェクトを選択し、インスペクタで設定をしていきます。
まずはAdd ComponentからPhysics→Rigidbodyを選択し取り付けます。
TransformのScaleのXYZを全て0.2にします。
RigidbodyのMassを0.1にし、Use Gravityのチェックを外し重力を働かせないようにします。
次にAssets/Scriptsフォルダに新しくBulletScriptスクリプトを作成し、Bulletゲームオブジェクトに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class BulletScript : MonoBehaviour { [SerializeField] private int attackPower = 1; private GameManager gameManager; // Start is called before the first frame update void Start() { gameManager = GameObject.Find("GameManager").GetComponent<GameManager>(); // 弾が発生してから10秒後に消す Destroy(gameObject, 10f); } private void OnCollisionEnter(Collision collision) { if (gameManager.GameOver) { Destroy(gameObject); return; } // 衝突した相手がプレイヤーならダメージを与え、さらに力を加えて飛ばす if (collision.gameObject.CompareTag("Player")) { collision.gameObject.GetComponent<PlayerController>().TakeDamage(attackPower); collision.gameObject.GetComponent<Rigidbody>().AddForce(transform.forward * 5f, ForceMode.VelocityChange); } // 何らかのゲームオブジェクトに衝突したら消す Destroy(gameObject); } } |
attackPowerは与えるダメージです。
gameManagerはGameManagerを入れます。
StartメソッドではGameManagerゲームオブジェクトを探してGameManagerスクリプトを取得しgameManagerに入れています。
1 2 3 | Destory(gameObject, 10f); |
で弾が登場してから(Startメソッドが呼ばれてから)Destoryメソッドを使って自身のゲームオブジェクトを10秒後に消します。
これは弾が登場してからずっと残っていても無駄で処理も遅くなっていくからです。
OnCollisionEnterメソッドは弾のコライダに他のコライダが衝突した時に呼ばれるメソッドです。
GameManagerのGameOverプロパティがtrueの時はゲームオブジェクトを削除してreturnでその後の処理をしません。
その後衝突した相手のCollision型のcollisionのゲームオブジェクトのタグがPlayerだった時にそのゲームオブジェクトからPlayerControllerスクリプトを取得し、TakeDamegeメソッドを呼び出してダメージを与えます。
その後、相手のRigidbodyコンポーネントを取得し、AddForceメソッドでtransform.forwardで弾の前方を取得し、その方向に力を加えています。
オプションとしてForceMode.VelocityChangeを指定し、質量を無視してRigidbodyに即座に力を加えています。
この処理は弾がプレイヤーに当たった時にプレイヤーを少し力で押す感じにしたい為に入れました。
最後に、何らかのコライダと衝突した場合は弾を消したいのでDestoryメソッドで自身のゲームオブジェクトを消しています。
つまり、弾は何らかのゲームオブジェクトと衝突したら消えますが、衝突しなくても最終的に10秒後には消えます。
これで弾のゲームオブジェクトが出来上がりました。
弾をプレハブにする
弾のゲームオブジェクトが完成しましたが、大砲から弾を継続的に発射する予定です。
なので、あらかじめたくさんの弾をヒエラルキー上に配置して飛ばすよりも、弾のゲームオブジェクトをプレハブにしておき、スクリプトから弾のプレハブをインスタンス化し、大量生産出来るようにした方が簡単です。
Assetsフォルダに新しくPrefabsフォルダを作成し、ヒエラルキーのBulletゲームオブジェクトをドラッグ&ドロップします。
これでAssets/PrefabsフォルダにBulletプレハブが出来ました。
ヒエラルキーにあるBulletは使わないので削除してください。
定期的に弾を飛ばすスクリプトの作成
飛ばす弾のプレハブが出来たので次は定期的にBulletプレハブからインスタンスを生成し飛ばすスクリプトを作成していきます。
Assets/Scriptsフォルダに新しくFireBulletScriptを作成しヒエラルキーのCannonの子要素のLaunchPositionゲームオブジェクトにドラッグ&ドロップして取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class FireBulletScript : MonoBehaviour { // ゲームマネージャー private GameManager gameManager; // 弾のプレハブ [SerializeField] private GameObject bullet; // 弾を発射する間隔秒数 [SerializeField] private float fireInterval = 2f; // 弾に加える力 [SerializeField] private float power = 10f; // 経過時間 private float nowTime; // Start is called before the first frame update void Start() { // ゲームマネージャーの取得 gameManager = GameObject.Find("GameManager").GetComponent<GameManager>(); } // Update is called once per frame void Update() { // ゲームオーバー時は以降何もしない if (gameManager.GameOver) { return; } // 一定時間ごとに弾を発射 nowTime += Time.deltaTime; if (nowTime >= fireInterval) { Fire(); nowTime = 0f; } } void Fire() { // 弾プレハブをインスタンス化 var ins = Instantiate(bullet, transform.position, transform.rotation); // 弾のRigidbodyに質量を無視して力を加える ins.GetComponent<Rigidbody>().AddForce(transform.forward * power, ForceMode.Force); } } |
gameManagerはGameManagerを入れます。
bulletは先ほど作ったBulletプレハブをインスペクタで設定します。
fireIntervalは次の弾を発射するまでの間隔時間です。
powerは弾に加える力です。
nowTimeは経過時間を入れます。
StartメソッドではGameManagerゲームオブジェクトを探しGameManagerスクリプトを取得しています。
Updateメソッドでは最初にゲームオーバーであればreturnでそれ以降の処理をしません。
nowTimeにTime.deltaTimeを足していき、経過時間を計算します。
経過時間がfireIntervalの時間以上になったらFireメソッドを呼んで弾を飛ばします。
弾を飛ばしたら経過時間を0にします。
FireメソッドではInstantiateメソッドを使ってbulletを自身の位置(LaunchPositionゲームオブジェクトの位置)と回転に設定してインスタンス化しそれをins変数に入れています。
ins変数からGetComponentを使ってRigidbodyを取得し、AddForceメソッドを使ってtransform.forwardでLaunchPositionゲームオブジェクトの前方(青い矢印の方向)に力を加えています。
ForceMode.Forceオプションは質量を利用して継続的に力を加えるモードです。
LaunchPositionゲームオブジェクトのFireBulletScriptのBulletにBulletプレハブを設定します。
これで大砲の機能が出来ました。
大砲をプレハブ化する
大砲の機能が出来ましたがゲームステージにいくつか大砲があることも考えられます。
ヒエラルキー上で大砲をCtrl+Dキーで複製してもいいですが、どうせならプレハブにしておきましょう。
Assets/PrefabsフォルダにCannonゲームオブジェクトをドラッグ&ドロップしてプレハブにします。
今後大砲を追加したい場合はAssets/PrefabsフォルダのCannonプレハブをヒエラルキーやシーンビューにドラッグ&ドロップすることで配置出来ます。
今回は3つの大砲を配置し、以下のようなTransformにしました。
またCannonとCannon(2)の子要素のBasePointOfTheBarrelのCannonRotationScriptのRotatableのチェックは外して回転しないようにします。
シーンビューで見ると以下のような位置に配置されています。
実行して確認してみる
機能が出来たので実行して確認してみましょう。
上のようになりました。
大砲から定期的に弾が発射されていますね、また右の大砲の砲身はキャラクターの方向を向いて打ってきます。
終わりに
今回は弾を発射する大砲を作成しました。
ゲームっぽくなってきましたね。(^_^)v
次回はプレイヤーキャラクターにジャンプ機能を取り付けます。