概要
こんな感じのリコイルを作ります。
どんなリコイルパターンになるか自由に設定でき、滑らかな視点移動にしました。
前提として以前に書いた記事の方法2の視点移動Scriptを使っています。
仕組み
回転ベクトルについて
以前のコードに新しく変数を追加します。
[SerializeField] private float _returnSpeed = 1;
[SerializeField] private float _snappiness = 6;
private Vector3 _recoilTargetRotation;
private Vector3 _currentRecoilRotation;
private Vector3 _returnTarget;
リコイルが入力されたとき_recoilTargetRotation
の値を変え、それに伴って_currentRecoilRotation
が変動します。プレイヤーの視点に反映されるのは_currentRecoilRotation
です。
_returnTarget
はリコイルによって跳ね上がった視点が戻ってくる場所です。
この3つのベクトルによりリコイルの入力で瞬間的に_recoilTargetRotation
の値が変わるが、_snappiness
の速度で跳ね上がり、_returnSpeed
のスピードで元の視点に戻ります。
/// <summary>指定したリコイルを設定する</summary>
public void Recoil(Vector2 recoil)
{
_recoilTargetRotation += new Vector3(-recoil.y, recoil.x, 0);
}
/// <summary>リコイルを反映させる</summary>
void ReflectsRecoil()
{
_recoilTargetRotation = Vector3.Slerp(_recoilTargetRotation, _returnTarget,
_returnSpeed * Time.fixedDeltaTime);
_currentRecoilRotation = Vector3.Slerp(_currentRecoilRotation, _recoilTargetRotation,
_snappiness * Time.fixedDeltaTime);
}
Recoil
メソッドでリコイルの入力を受け付けています。入力時に直感的にわかりやすいので引数をVector2、XY反転でやっていますが好みで。
ReflectsRecoil
で各ベクトルの動きを計算しています。Vectr3.Slerp
を使うことで2つのベクトルの差が大きいほど速い視点移動となります。つまり一定の縦リコイルを入力し続けても、次第にリコイルは小さくなるということです。
加減速の計算について詳しくないのでReflectsRecoil
はFixedUpdateで実行しています。
ベクトルを反映させる
以前の記事のコードのLookの一部を書き換えます
// 頭、体の向きの適用
- _head.transform.localRotation = Quaternion.Euler(_xRotation, 0, 0);
- _body.transform.localRotation = Quaternion.Euler(0, _yRotation, 0);
+ _head.transform.localRotation =
+ Quaternion.Euler(_xRotation + _currentRecoilRotation.x, 0, 0);
+ _body.transform.localRotation =
+ Quaternion.Euler(0, _yRotation + _currentRecoilRotation.y, 0);
ベクトルの入力にリコイルのベクトルを追加しているだけです。
次は_returnTarget
について
このままだとリコイル制御でマウスを動かしたとき、その分だけリコイルが終わったときに視点が動いてしまいます。なので、リコイル制御したときに戻ってくる場所を更新する必要がある。
+ if (mouseInput.magnitude != 0)
+ {
+ _returnTarget = _currentRecoilRotation;
+ }
リコイルの入力
一定間隔で射撃でき、最初は一定のリコイルパターンがあるが最後のほうはランダムなリコイルになる、よくあるようなリコイルを再現します。射撃をやめればリコイルパターンはリセットされます。
リコイルテストコード
using UnityEngine;
public class RecoilTest : MonoBehaviour
{
[SerializeField] private POVController _povController;
[SerializeField] private GameObject _bulletMarkObject;
[SerializeField] private float _fireRate = 0.12f;
[SerializeField] private Vector2[] _recoilPattern;
[SerializeField] private Vector2 _randomRecoil;
private float _fireTimer;
private int _recoilPatternIndex;
private void Update()
{
if (_fireTimer < _fireRate)
{
_fireTimer += Time.deltaTime;
}
if (Input.GetButton("Fire1"))
{
if (_fireTimer >= _fireRate)
{
_fireTimer = 0;
if (_recoilPatternIndex < _recoilPattern.Length) // パターンが設定されていればそれに従う
{
_povController.Recoil(_recoilPattern[_recoilPatternIndex]);
_recoilPatternIndex++;
}
else // 一定以降はランダムとか
{
_povController.Recoil(new Vector2(Random.Range(-_randomRecoil.x, _randomRecoil.x), _randomRecoil.y));
}
// 着弾地点が分かるようにオブジェクト生成
Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out RaycastHit hit);
Destroy(Instantiate(_bulletMarkObject, hit.point, Quaternion.identity), 5);
}
}
else
{
_recoilPatternIndex = 0;
}
}
}