今回は操作キャラクターの回りを旋回する弾を発射する攻撃ユニットの作成をしていきたいと思います。
キャラクターを移動させてもキャラクターの回りをグルグル回り、ユニットの攻撃ボタンを押したらユニットが向いている方向(キャラクターからユニットを見た方向)に弾を飛ばすようにしたいと思います。
今回の機能を作成すると、
上のようになります。
シューティングゲームやアクションゲーム等で使えそうですね。(^^)v
攻撃ユニットの作成
それでは攻撃ユニットの作成をしていきましょう。
ユニットゲームオブジェクトの作成
今回はユニットをUnityのCubeで作成していきます。
ヒエラルキー上で右クリックから3D Object→Cubeを選択し、名前をUnitとします。
次にUnit用のマテリアルを作成します。
Assetsエリア内で右クリックからCreate→Materialを選択し、名前をUnitMaterialとします。
UnitMaterialを選択し、インスペクタのAlbedoの色を赤色にします。
UnitMaterialをヒエラルキー上のUnitにドラッグ&ドロップするか、UnitのインスペクタのMeshRendererのMaterialsのElement 0にUnitMaterialをドラッグ&ドロップします。
マテリアルが適用されるとUnitが赤色になります。
デフォルトのCubeだとユニットとしては大きいのでTransformのScaleを0.3にし少し小さくします。
また、ユニットが他のゲームオブジェクトと衝突すると困るのでUnitのBox Colliderコンポーネントを削除します(Box Colliderの右の歯車からRemove Component)。
これでUnitのインスペクタは
上のようになります。
これでユニットのゲームオブジェクトが出来ました。
ユニットがキャラクター回りを旋回するスクリプト
ユニットが出来たので、次は操作キャラクターの回りを旋回するようにスクリプトを作成していきます。
ユニットが操作キャラクターの回りをグルグル回るスクリプトは
の記事で作成したキャラクターの回りをカメラが回る機能と同じようにして作成します。
つまりカメラをユニットに置き換えて計算していくことになります。
ユニットの位置 = 操作キャラクターの位置 + 操作キャラクターの位置とユニットのある位置の角度 × 操作キャラクターからユニットまでのベクトル
という計算式を使います。
計算式を見ると分かり辛いですが、まず操作キャラクターの位置を中心として、そこからユニットがある位置を計算します。
ユニットの角度はUpdateメソッドが呼ばれる度に少しづつ角度を変化させます。
というわけでそれを元にスクリプトを作成しUnitに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RotateUnit : MonoBehaviour { // 旋回するターゲット [SerializeField] private Transform target; // 現在の角度 private float angle; // 回転するスピード [SerializeField] private float rotateSpeed = 180f; // ターゲットからの距離 [SerializeField] private Vector3 distanceFromTarget = new Vector3(0f, 1f, 2f); // Update is called once per frame void Update() { // ユニットの位置 = ターゲットの位置 + ターゲットから見たユニットの角度 × ターゲットからの距離 transform.position = target.position + Quaternion.Euler(0f, angle, 0f) * distanceFromTarget; // ユニット自身の角度 = ターゲットから見たユニットの方向の角度を計算しそれをユニットの角度に設定する transform.rotation = Quaternion.LookRotation(transform.position - new Vector3(target.position.x, transform.position.y, target.position.z), Vector3.up); // ユニットの角度を変更 angle += rotateSpeed * Time.deltaTime; // 角度を0~360度の間で繰り返す angle = Mathf.Repeat(angle, 360f); } } |
ユニットが旋回するターゲットはインスペクタで設定出来るようにしておきます。
angleはキャラクターから見たユニットの角度を入れます。
rotateSpeedはユニットが1秒間に回転する角度を指定します。
ターゲットからの距離はtargetに設定した操作キャラクターからユニットまでの距離をベクトルで指定し、ユニットの位置をインスペクタで設定出来るようにしています。
Updateメソッド内が実際にユニットの位置を作成している処理です。
ターゲットから見たユニットの角度はQuaternion.Eulerを使ってオイラー角を指定しますが、ターゲットを中心にY軸で回転させる為にY軸にangleを指定し、X軸とZ軸は0で固定にします。
ユニットの位置はこれで出来ましたがこの後、弾を飛ばす時にユニット自身の角度をキャラクターから見たユニットの方向に向けさせておく必要があります。
なので、ユニットの位置からターゲットの位置を引いてユニットの方向を求め、Quaternion.LookRotationを使ってVector3.up(Y軸)を軸にして角度を求め、それをユニット自身の角度とします。
その後angleを回転スピード分足します。
角度は0~360の間で収めたいのでMathf.Repeatを使っています。
これで操作キャラクターの回りをグルグル回るユニットが出来ました。
ユニット位置の計算を実際に試して理解する
ユニットの位置や角度の計算式が分かり辛い方は実際にゲームオブジェクトを設置して試してみるとわかりやすいかもしれません。
例えばCubeをヒエラルキー上に配置しスクリプトを取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RotateUnitTest : MonoBehaviour { [SerializeField] private Transform target; // Update is called once per frame void Update() { // ユニットをターゲットの位置にする transform.position = target.position; } } |
上のスクリプトではインスペクタで設定したターゲットの位置にCubeが来るだけです。
上の画像では右上のCubeが最初にCubeを配置した位置で、キャラクターをtargetにしているのでキャラクターの足元にあるのがこのスクリプトを設定したCubeの位置です。
次にY軸で45度回転しベクトルをかけてユニットの位置を計算する処理を足します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RotateUnitTest : MonoBehaviour { [SerializeField] private Transform target; // Update is called once per frame void Update() { // ユニットの位置をターゲットの位置からの角度にベクトルをかけて計算 transform.position = target.position + Quaternion.Euler(0f, 45f, 0f) * new Vector3(0f, 0f, Mathf.Sqrt(2)); } } |
Quaternion.Euler(0f, 45f, 0f)でY軸で45度回転しそれにnew Vector3(0f, 0f, Mathf.Sqrt(2))をかけることでその角度の前方に√2の長さの位置にターゲットの位置を足してユニットの位置を求めます。
前方にルート2としているのは三平方の定理で横と縦が1だと斜め方向の長さがルート2となるのでUnityのシーンビュー上で丁度スリットの真ん中にCubeがくるようにしたかった為です。
実行すると、
上のようになります。
上のCubeが元の位置でXYZが(1, 1, 1)の位置に置いてあります。
下のCubeがスクリプトで計算した位置でターゲットであるキャラクターの位置からY軸で45度回転し、その方向のベクトルにルート2の長さ分移動したものです。
ユニット自身の角度を実際に試して理解する
次はユニット自身の角度をキャラクターからユニットの位置の方向に向けるのを実際に試して確認してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RotateUnitTest : MonoBehaviour { [SerializeField] private Transform target; // Update is called once per frame void Update() { // ユニットの位置をターゲットの位置からの角度にベクトルをかけて計算 transform.position = target.position + Quaternion.Euler(0f, 45f, 0f) * new Vector3(0f, 0f, Mathf.Sqrt(2)); transform.rotation = Quaternion.LookRotation(-Vector3.forward, Vector3.up); } } |
上のスクリプトではVector3.forwardでワールド空間の前方(Z軸)の方向-を付けて反転させ、Vector3.up(Y軸)を軸にした角度をQuaternion.LookRotationで計算しています。
上のように上のCubeは元の角度なのでデフォルトのままワールドのZ軸と同じ方向を向き、下のCubeはスクリプト計算して角度をワールドのZ軸の反対側にしています。
ユニット自身の角度の計算が出来たところで、次はキャラクターからユニットの方向を求めその角度をユニットの角度に設定すればユニット自身の角度がそちらの方向を向いてくれます。
キャラクターからユニットの方向を求めるには
ユニットの位置 – キャラクターの位置
を計算すると求められますので、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RotateUnitTest : MonoBehaviour { [SerializeField] private Transform target; // Update is called once per frame void Update() { // ユニットの位置をターゲットの位置からの角度にベクトルをかけて計算 transform.position = target.position + Quaternion.Euler(0f, 45f, 0f) * new Vector3(0f, 0f, Mathf.Sqrt(2)); transform.rotation = Quaternion.LookRotation(transform.position - new Vector3(target.position.x, transform.position.y, target.position.z), Vector3.up); } } |
上のようになります。
ユニットとキャラクターのY軸の位置が違うと向く方向が変わってしまうので、キャラクターの位置のY軸をユニットのY軸と同じにして計算します。
上のようにユニット自身の前方(ローカルのZ軸)がキャラクターからユニットの方向になっているのがわかります。
ユニットが攻撃出来るようにするスクリプト
ユニットが操作キャラクターの回りをグルグル回るようになったので、次はユニットの攻撃ボタンを押したらユニットが向いている方向に弾を飛ばす処理を作成していきます。
RotateUnitスクリプトに追記していきます。
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 51 52 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RotateUnit : MonoBehaviour { // 旋回するターゲット [SerializeField] private Transform target; // 現在の角度 [SerializeField] private float angle; // 回転するスピード [SerializeField] private float rotateSpeed = 180f; // ターゲットからの距離 [SerializeField] private Vector3 distanceFromTarget = new Vector3(0f, 1f, 2f); // 弾のプレハブ [SerializeField] private GameObject bullet; // 弾を飛ばす力 [SerializeField] private float shotPower = 1000f; // Update is called once per frame void Update() { // ユニットの位置 = ターゲットの位置 + ターゲットから見たユニットの角度 × ターゲットからの距離 transform.position = target.position + Quaternion.Euler(0f, angle, 0f) * distanceFromTarget; // ユニット自身の角度 = ターゲットから見たユニットの方向の角度を計算しそれをユニットの角度に設定する transform.rotation = Quaternion.LookRotation(transform.position - new Vector3(target.position.x, transform.position.y, target.position.z), Vector3.up); // ユニットの角度を変更 angle += rotateSpeed * Time.deltaTime; // 角度を0~360度の間で繰り返す angle = Mathf.Repeat(angle, 360f); // ユニットから弾を発射する if (Input.GetButton("Fire1")) { // ユニットの位置と角度に弾のインスタンスを生成 var bulletIns = Instantiate<GameObject>(bullet, transform.position, transform.rotation); // 弾のRigidbodyに力を加えてユニットの向いている方向に飛ばす bulletIns.GetComponent<Rigidbody>().AddForce(transform.forward * shotPower, ForceMode.Force); // 5秒後に削除 Destroy(bulletIns, 5f); } } } |
弾のプレハブ、弾を飛ばす力をインスペクタで設定出来るようにします。
Updateメソッド内でFire1に設定したボタンを押した時に弾のプレハブからインスタンスを生成しています。
弾の生成位置はユニットの位置と角度にします。
弾のインスタンスに設定しているRigidbodyコンポーネントを取得しAddForceメソッドでユニットの前方(transform.forward)に力を加えて飛ばします。
ここら辺の処理は
の記事あたりと同じですね。
弾のプレハブの作成
弾のプレハブはヒエラルキー上に右クリックから3D Object→Sphereを選択し、名前をBulletとし、インスペクタのAdd ComponentからPhysics→Rigidbodyを取り付けUse Gravityのチェックを外し重力が働かないようにします。
またユニットのサイズに応じて出てくる弾のサイズも調整しておきます。
後はBulletをAssetsエリア内にドラッグ&ドロップしてプレハブにし、RotateUnitのbulletに設定します。
キャラクターを旋回する攻撃ユニットの完成
キャラクターの回りを旋回する攻撃ユニットが出来上がりましたので、ユニットを幾つか配置し設定を変えて試してみましょう。
上のようになりました。
上の例ではユニットを4つ配置しそれぞれのインスペクタでユニットのY軸の角度であるangleの初期値をインスペクタで変えてユニットが同じ位置にこないようにしています。
angleはそれぞれ0、90、180、270を設定しています。
終わりに
今回の攻撃ユニットはY軸で回転していますが、スクリプトの角度の部分を変えることで縦回転や不思議な回転にする事も出来ます。
今回の場合はターゲットのY軸の位置とユニットのY軸の位置を合わせて地面に平行に弾が飛ぶようにしていますが、Y軸を合わせないでも良いなら
1 2 3 4 5 | // 回転軸 [SerializeField] private Vector3 axisOfRotation = Vector3.up; |
上のように回転軸をインスペクタで設定出来るようにし、
1 2 3 4 5 6 | // ユニットの位置 = ターゲットの位置 + ターゲットから見たユニットの角度 × ターゲットからの距離 transform.position = target.position + Quaternion.Euler(axisOfRotation * angle) * distanceFromTarget; // ユニット自身の角度 = ターゲットから見たユニットの方向の角度を計算しそれをユニットの角度に設定する transform.rotation = Quaternion.LookRotation(transform.position - target.position, Vector3.up); |
上のように回転軸であるaxisOfRotationにangleをかける形にして、ユニット自身の角度を作る時にY軸の位置を合わせた計算をしないようにします。
上の例では一括でaxisOfRotationにangleをかけてますが、Vector3のXYZのそれぞれの角度を更新する事も出来ます。
こうすると色々面白い動きをするユニットが出来るかも?しれません。