アーマードコアVにでてくるようなロックオンシステムを実装していきます。
画面に描画されたロックオンサークル内に、敵を一定時間捕捉しているとロックオンしてくれるシステムですね。
こんな感じ↓(画像は拾いもん)
以下のようなロジックでロックオンを実装していく。
※最初の画面内に敵がいるかどうかの判定は不必要かもしれない
OnWillRenderObject関数は指定したカメラに映る位置にオブジェクトがいた場合に呼び出される関数。まずはここで自機の視野角内にいる敵に絞り込む。
しかし、OnWillRenderObject関数はカメラと対象オブジェクトの間にある他のオブジェクト有無を考慮しない。
つまりこのままでは壁の向こう側にいる敵すらロックオンしてしまう。
なので、Physics.Linecast関数を用いて間に他のオブジェクトが間にあるかの判定を行う。ここで全オブジェクトを判定にいれてしまうと弾が自機と敵の間を通っただけでロックオンが途切れかねない。なので有無判定を行うオブジェクトにはレイヤーでマスクをかけること。
上記の手順で実際にプレイヤーから見て画面上に移っている敵に絞り込まれるはず。後は絞りこんだ敵が画面上のどこに描画されているかをRectTransformUtility.WorldToScreenPoint関数を用いて取得する。
RectTransformUtility.WorldToScreenPoint関数は指定したワールド座標を画面座標に変換してくる関数です。
敵の画面座標が分かったのでこの画面座標がロックオンサークル内に収まっているかを判定する。
ロックオンサークル内に何フレーム捕捉し続けているかを計測し、目標時間まで到達したら晴れて無事ロックオン完了といった流れ。
各関数の細かい使用方法や注意点は、ぐぐれば個別に詳細がわかる記事があるのでそちらを参考にしてください。
上記を踏まえ、以下のように実装しました。
敵の画面内に入っているかの判定
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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UnitManagement : MonoBehaviour { //メインカメラに付いているタグ名 private const string MAIN_CAMERA_TAG_NAME = "MainCamera"; //カメラに表示されているか private bool _isRendered = false; void Start () { } void Update () { _isRendered = false; } public bool getIsRendered(){ return _isRendered; } //カメラに映ってる間に呼ばれる private void OnWillRenderObject(){ //メインカメラに映った時だけ_isRenderedを有効に if (Camera.current.tag == MAIN_CAMERA_TAG_NAME) { _isRendered = true; } } } |
ロックオンシステムの処理
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class LockOnSystem : MonoBehaviour { public GameObject enemyUnit; public GameObject Weapon; private GameObject target; private int elapsedTime = 0; private const int MAX_LOCK_ON_TIME = 3600; private bool isLockOn = false; public float lockOnCircle = 150; void Start () { } void Update () { lockOnProc (); //ロックオン完了までの時間を越えた場合ロックオン!! if (Weapon.GetComponent<Weapon> ().getLockOnTime() <= elapsedTime) { isLockOn = true; } else { isLockOn = false; } Debug.DrawLine (this.transform.position, enemyUnit.transform.position, Color.red); } /*ロックオン処理*/ private void lockOnProc(){ //敵がゲーム画面内にいる場合 if (enemyUnit.GetComponent<UnitManagement> ().getIsRendered ()) { //敵との間に障害物無い場合 if (Physics.Linecast (this.transform.position, enemyUnit.transform.position, LayerMask.GetMask ("Field")) == false ) { Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint (Camera.main, enemyUnit.transform.position); screenPoint.x = screenPoint.x - (Screen.width / 2); screenPoint.y = screenPoint.y - (Screen.height / 2); //ロックオンサークル内の場合 if (screenPoint.magnitude <= lockOnCircle) { if (elapsedTime < MAX_LOCK_ON_TIME) { elapsedTime++; } target = enemyUnit; return; //処理終了 } } } //敵がロックオンできない状態の場合 elapsedTime = 0; return; } public int getElapsedTime(){ return elapsedTime; } public GameObject getTarget(){ return target; } public bool getIsLockOn(){ return isLockOn; } } |
ロックオンマーカーは以下のように実装
あらかじめ以下のような画像を用意し、スクリプトイメージとしてインポート
白い長方形の辺部分の画像なので、背景白いからなんも見えないかも・・・
※インポート時にTexture TypeをSpriteにすること
これをUIのImageとして描画し、スクリプトで描画位置と色・表示非表示を処理している。
UIに関しては以下が参考になりました
http://www.metalbrage.com/UnityTutorials/uGUI/ImageElement.html
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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class RockMarker : MonoBehaviour { public GameObject lockOnSystem; // Use this for initialization void Start () { this.gameObject.GetComponent<Image> ().color = new Color (0f,1f,0f,0f); } // Update is called once per frame void Update () { /*ロックオンシステム*/ LockOnSystem l = lockOnSystem.GetComponent<LockOnSystem> (); //ロックオンマーカーの置くべき座標を取得 Vector2 position = RectTransformUtility.WorldToScreenPoint (Camera.main, l.getTarget ().transform.position); this.transform.position = new Vector3 (position.x, position.y, 0f); //ロックオンマーカーの状態判定 if (0 < l.getElapsedTime ()) { //ロックオンサークル内 if (l.getIsLockOn ()) { //ロックオン完了 this.gameObject.GetComponent<Image> ().color = new Color (0f, 1f, 0f, 1f);//緑 } else { //ロックオン途中 this.gameObject.GetComponent<Image> ().color = new Color (1f, 0f, 0f, 1f);//赤 } } else { //ロックオンサークル外 this.gameObject.GetComponent<Image> ().color = new Color (0f, 0f, 0f, 0f);//透明(非表示) } } } |
あとはロックオンしたら武器の向きを
transform.LookAt ()で敵に向けるだけ。
こんな感じになりました。
まだ偏差撃ちを実装していないので、ロックオンするとむしろ当たらない・・・
望み通りの動きになってはいるものの、カメラの視野角に収まっているかどうかの判定処理を敵オブジェクト自体に入れなきゃいけないのは歯がゆい。できればその辺の処理もロックオンシステムオブジェクトで処理させちゃいたい。
というより視野角に収まってる状態かどうかの判定なんていらないのかな・・・
それと、敵が一機だった場合のみのロジックになってるから複数の敵を考慮するロジックに修正する必要がある。武器の左右のロック速度が違った場合のロジックもいれないとね。
以上です。
コメント