Unityによるベクトルの回転と射出パターンの制御
Unityによるベクトルの回転の概要
シューティングゲームの作成中に自機狙いの射出パターンを作った。
当初は正確に自機に向かって射出するパターンであり、射出頻度の割には弾数が多いように感じなかった。(次の動画)当ゲームはスマホでリリースする事を想定しているため、必要以上の弾を射出せず弾数が多いように見せかける必要がある。その為の工夫として射出方向を決定するベクトルにランダム性を持たせた。その際ベクトルを回転させる必要があったので報告する。
目次
- 行列によるベクトルの回転方法
- Unityの機能(Quaternion.Euler)を使ったベクトルの回転方法
- 以前の方法との比較
- シューティングゲームへの適用
- まとめ
- パッケージ
行列によるベクトルの回転方法
ベクトルの回転は今までも何度かしているため、現在の自分の所持する技能・設備でも実装可能である事は確認できている(用途は異なる)。
以前は以下のような回転行列を用いた。
以前作成したもの
実装例(以前書いたもの)
private void FixedUpdate()
{
float nowTime=Time.time;
float passTime=nowTime-inTime;
v.x =-3f;
v.y = Mathf.Cos((passTime * OMEGA)+initialPhase) * A;
Vector2 vGlobal;
vGlobal.x = v.x * Mathf.Cos(rad)- v.y * Mathf.Sin(rad);
vGlobal.y=v.x*Mathf.Sin(rad) +v.y*Mathf.Cos(rad);
this.GetComponent().velocity = vGlobal;
}
Unityの機能(Quaternion.Euler)を使ったベクトルの回転方法
以前と同様の方法でベクトルを回転させても良いが、Unityの機能で回転させる事は可能か調べたところ、回転させる方法がある事が分かった(下式)。
行列を用いた方法と比べるとコードの記述量は非常にスッキリしており、1行でベクトルの回転を実装することが出来る。
ここでQuaternion.Eulerの第一~第三引数はそれぞれ、X軸Y軸Z軸周りの回転角である。
この場合Z軸周りにrndだけ回転させている。
内部的には四元数という複素数を拡張したようなものを利用しているようで、掛け算の順序に気を配る必要がある。
Unityの場合、上式の右辺の掛け算の順を逆にするとエラーが出る(コンパイル不能になる)ため間違えることはなさそうである。
以前の方法との比較
以前の行列を用いた方法と比較すると、コード量が減って扱いやすくなったと感じる。
しかもUnityに初めから実装されている機能であるため、関数として書き出す必要もない。
シューティングゲームへの適用
今回は、敵の弾の射出パターンとしてベクトルの回転を実装した。
当ゲームはスマホでリリースする事を想定しているため、PCと異なり多くの負荷をかけるわけにはいかない。そこで、弾の射出パターンを工夫し、正確に自機を狙った射出から少しだけ射出角を変更することで、画面上に弾が散らばっている状態にする。こうすることでより多くの弾が飛んでいるという印象をプレーヤーに与えることが出来ると考えた。
実装した結果、同一量の弾の射出量だが、ランダム性の大きい方が多くの弾が飛んでいるように見えると分かった(冒頭の動画)。
これを利用し、以前までに作った部分もできるだけ弾数を減らすようにしたい。
まとめ
- シューティングゲームで敵の弾の射出パターンを作成した
- Quaternion.Eulerで回転を記述可能と分かった。
Unity(2022.3.201f)
Script:今回作成したショットを打ってくる敵
public class EnermyChase : MonoBehaviour
{
int hp = 2;
Vector3 v;
static GameObject player;
static GameObject camera;
public GameObject[] gate;
public GameObject bulletPref;
public GameObject explosionPref;
const float speed = 3f;
const float bulletSpeed = 6.0f;
Rigidbody rb;
int loopCnt;
public WarningAlart.AlartPosition pos;
public enum ChaseType
{
XY,//X座標Y座標の両方の方向に移動させる
X,//X方向のみ移動させる
Y//Y方向のみ移動させる
}
public ChaseType chaseType;
// Start is called before the first frame update
void Start()
{
loopCnt = 0;
if(player == null) {
player = GameObject.FindGameObjectWithTag("MyShip");
}
if(camera == null)
{
camera = GameObject.FindGameObjectWithTag("MainCamera");
}
transform.SetParent(camera.transform);
v = player.transform.position - transform.position;
switch (chaseType)
{
case ChaseType.X:
v=new Vector3(v.x,0,0);
break;
case ChaseType.Y:
v=new Vector3(0,v.y,0);
break;
}
v=v.normalized*speed;
rb = GetComponent();
rb.velocity = v;
}
// Update is called once per frame
void Update()
{
float rad=Mathf.Atan2(v.y,v.x);
transform.rotation= Quaternion.Euler(-rad*Mathf.Rad2Deg,90,-90);
if (hp <= 0)
{
Instantiate(explosionPref, transform.position, Quaternion.identity);
Destroy(gameObject);
}
}
private void FixedUpdate()
{
if(loopCnt==0) {
WarningAlart.ActivateAlart(pos);
}
if (loopCnt % 40 == 0)
{
Vector3 bulletVelocity;
for (int i = 0; i < gate.Length; i++)
{
float rnd = Random.Range(-10f, 10f);
GameObject bullet = Instantiate(bulletPref, gate[i].transform.position, Quaternion.identity);
bulletVelocity = Quaternion.Euler(0, 0, rnd)*(player.transform.position - gate[i].transform.position);//ここでショット方向の制御をしている
bulletVelocity = new Vector3(bulletVelocity.x, bulletVelocity.y, 0).normalized*bulletSpeed;
bullet.GetComponent().velocity = bulletVelocity;
bullet.transform.SetParent(camera.transform);
}
}
loopCnt++;
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "NormalBullet")
{
hp--;
}else if (other.gameObject.tag == "ChargeShot")
{
ChargeShot cs = other.GetComponent();
if (cs != null)
{
hp -= cs.damege;
}
}
}
}