【超簡単】Unityで下に飛ばしたRayからレイヤー検知する処理『足音対応』

※ Qiita から移行した記事となりますので、情報が古い可能性があります。

Unity 2D で下方向に飛ばした Ray から床を検知し、地面の種類によって足音を変え、音声が再生される間隔も調整する処理を作成したので共有します。

Ray を飛ばす処理の作成で参考にしたもの

元となるコード自体は、Unity アセットストアにて公式が提供している “Lost Crypt – 2D Sample Project” というサンプルプロジェクト内で書かれていたものです。

Get the Lost Crypt - 2D Sample Project package from Unity Te…

こちらの内容を参考に自分なりに編集しました。
(公式が良いプロジェクト出してくれるのは本当にありがたい……!)

判定したい地面レイヤーを設定

今回は下記のように設定しました。

地面を判定する処理について

大きく分けてコライダーで判定する方法と Ray で判定する方法の 2 つがあります。

コライダー

Ray

コライダーのイベント関数 (OnCollisionEnter とか) で判定すると、壁に接触した時にも地面と同じ接触判定が出るため、今回は Ray で地面の検知をします。

// 地面の種類
public enum GroundType
{
    Ground_Grass,  // 草
    Ground_Stone,  // 石
    // 一部省略
    Airborne       // 空中
}

private GroundType groundType = GroundType.Airborne;

// 取得するレイヤー情報
private int ground_Grass;
private int ground_Stone;

[SerializeField]
private float rayDistance;

private void Start()
{
    ground_Grass = LayerMask.GetMask(nameof(GroundType.Ground_Grass));
    ground_Stone = LayerMask.GetMask(nameof(GroundType.Ground_Stone));
}

private void UpdateGrounding()
{
    if (GroundRayHit(ground_Grass))
    {
        groundType = GroundType.Ground_Grass;
    }
    else if (GroundRayHit(ground_Stone))
    {
        groundType = GroundType.Ground_Stone;
    }
    else
    {
        groundType = GroundType.Airborne;
    }

    // 指定したレイヤーが地面にヒットしたかどうか
    bool GroundRayHit(int layer)
    {
        var pos = transform.position;

    // Unityエディタでのみ起動
    #if UNITY_EDITOR
        // シーンビューにRayを視覚化
        // 引数(開始地点, 終了地点, Rayの色)
        Debug.DrawLine(pos, pos + (Vector2.down * rayDistance), Color.red);
    #endif

        // 引数(Rayの開始地点, Rayが向かう方向, Rayの判定距離, 判定するレイヤー)
        return Physics2D.Raycast(pos, Vector2.down, rayDistance, layer);
    }
}

音声側の処理を設定

まず、フィールドに下記のような項目を追加します。

[SerializeField]
private AudioSource _audioSource;

[Header("Audio Clips")]
[SerializeField]
private AudioClip[] _stepsGrass,
                    _stepsStone;

[Header("Steps Interval")]
[SerializeField]
private float _stepsInterval;

private float _stepsTimer;

次にインスペクターから必要な音声をアタッチします。

メソッドも追加します。

// 接地中の地面から足音を再生
private void AudioSteps(GroundType groundType, float speedNormalized)
{
    // 空中だった場合は処理を返す
    if (groundType == GroundType.Airborne) return;

    // 経過時間 * プレイヤーの移動速度をタイマーに加算
    _stepsTimer += Time.deltaTime * speedNormalized;

    // 設定した時間を超えた場合
    if (_stepsInterval <= _stepsTimer)
    {
       // 現在接地中の地面からランダムな着地音を返す
       _audioSource.PlayOneShot(SelectedRandomSFX(groundType));
       // 音が鳴った後はタイマーをリセット
       _stepsTimer = 0.0f;
    }
}

// 接触している地面に応じて地面の接触音をとる
private AudioClip SelectedRandomSFX(GroundType groundType)
{
    // 地面のタイプを判定
    // 判定したものを配列に代入
    AudioClip[] steps = groundType switch
    {
        GroundType.Ground_Grass => _stepsGrass,    // 草
        GroundType.Ground_Stone => _stepsStone,    // 石
                              _ => null
    };

    // 足音の配列の中からランダムに1つを取得
    return steps[Random.Range(0, steps.Length)];
}

自分の場合ジャンプから着地した時にも同一の音声を再生する為メソッドを分けていますが、着地音を別に用意する場合は AudioSteps 内に直接 SelectedRandomSFX の内容を記述してもいいです。
また、音声をあまり多く用意出来ないという方や、容量の都合上多く使いたくないという方は、AudioSource の pitch や volume を指定の範囲でランダムに変更するという処理を挟むと、足音が少し違って聞こえるようになるのでオススメです。

実際に使用する

ここまで書いた全ての内容を統合し、実際に使用してみます。

private float walkSpeed = 1.0f;
private float dashSpeed => walkSpeed * 2;

private Rigitbody rb;

private void Start()
{
    rb = GetComponent<Rigitbody>();
}

private void Update()
{
    var speed = walkSpeed;
    if (Input.GetKeyDown(KeyCode.Shift))
    {
        speed = dashSpeed;
    }

    rb.velocity = new Vector2(speed, rb.velocity.y);

    // 横移動の速さに疾走スピードを割る(疾走中だった場合は1になる)
    // 今回の場合,歩いている時の速度は走っている時の速度の半分に設定しているので,音の再生速度も半分
    float horizontalSpeedNormalized = Mathf.Abs(rb.velocity.x) / dashSpeed;
    AudioSteps(groundType, horizontalSpeedNormalized);
}

以上です。
音声側の処理を記述する場所を変更するなどして使用してください。

SNS もやってるよ!
>IT で繋がるコミュニティ

IT で繋がるコミュニティ

IT の普及に伴い、他人同士のコミュニケーションは気軽になりましたが、技術的な質問となると話は別だと私は考えています。本コミュニティでは、そういった問題へのアプローチとして、プログラミング・インフラストラクチャ問わず多種多様な分野での有識者を募り、お互いが得意分野を補完することを目的としています。どなたでも参加可能です。

CTR IMG