【Unity小技巧】3D人物移动脚步和跳跃下落音效控制

简介: 【Unity小技巧】3D人物移动脚步和跳跃下落音效控制

单脚步声

public AudioClip walkingSound;
public AudioClip runningSound;

//移动音效
public void MoveSound()
{
    // 如果在地面上并且移动长度大于0.9
    if (isGround && moveDirection.sqrMagnitude > 0.9f)
    {
        audioSource.clip = isRun ? runningSound : walkingSound;
        if (!audioSource.isPlaying) audioSource.Play();
    }
    else
    {
        if (audioSource.isPlaying) audioSource.Pause();
    }
}

多脚步声,跳跃落地音效

[SerializeField] private float m_StepInterval; // 脚步声间隔

[SerializeField] private AudioClip[] m_FootstepSounds; // 脚步声音效数组
[SerializeField] private AudioClip m_JumpSound; // 跳跃声音效
[SerializeField] private AudioClip m_LandSound; // 落地声音效

private void Update()
{
    if (!m_Jump)
    {
        m_Jump = Input.GetButtonDown("Jump"); // 检测跳跃输入
    }

  ProgressStepCycle(speed); // 更新步行声音

    //判断角色是否落地,这个判断非常巧妙,假设角色起跳,m_PreviouslyGrounded和m_CharacterController.isGrounded肯定都是false,
    //执行到下一步时角色到达地上,m_CharacterController.isGrounded变为true,因为m_PreviouslyGrounded还未执行,所以还是false,及此时角色刚落地
    if (!m_PreviouslyGrounded && m_CharacterController.isGrounded)
    {
        PlayLandingSound(); // 播放落地声音
    }

if (m_CharacterController.isGrounded)
    {
        if (m_Jump)
        {
            PlayJumpSound(); // 播放跳跃声音
        }
    }

    m_PreviouslyGrounded = m_CharacterController.isGrounded;
}
private void PlayLandingSound()
{
    m_AudioSource.clip = m_LandSound; // 设置落地声音
    m_AudioSource.Play();
    //播放落地声音后延迟0.5s再播放脚步声
    m_NextStep = m_StepCycle + .5f;
}

private void PlayJumpSound()
{
    m_AudioSource.clip = m_JumpSound; // 设置跳跃声音
    m_AudioSource.Play();
}

private void ProgressStepCycle(float speed)
{
    //判断角色是否在移动。
    if (m_CharacterController.velocity.sqrMagnitude > 0 && (m_Input.x != 0 || m_Input.y != 0))
    {
        //如果角色在移动,根据角色当前的速度和行走/奔跑状态来更新步行声音循环计数器 m_StepCycle 的值。
        m_StepCycle += (m_CharacterController.velocity.magnitude + (speed * (m_IsWalking ? 1f : m_RunstepLenghten))) * Time.fixedDeltaTime;
    }

    //然后,检查是否到达了下一个播放脚步声音的时间点。如果没有到达,就直接返回
    if (!(m_StepCycle > m_NextStep))
    {
        return;
    }
    //如果到达了下一个播放脚步声音的时间点,就更新 m_NextStep 的值
    m_NextStep = m_StepCycle + m_StepInterval;
    PlayFootStepAudio(); // 播放脚步声音
}

private void PlayFootStepAudio()
{
    if (!m_CharacterController.isGrounded)
    {
        return;
    }
    int n = UnityEngine.Random.Range(1, m_FootstepSounds.Length);
    m_AudioSource.clip = m_FootstepSounds[n]; // 随机选择一个脚步声音
    m_AudioSource.PlayOneShot(m_AudioSource.clip);
    
    //播放过的脚步声放置第一位,避免连续播放相同的脚步声
    m_FootstepSounds[n] = m_FootstepSounds[0];
    m_FootstepSounds[0] = m_AudioSource.clip;
}

播放不同材质的多脚步声

获取人物移动状态

public class PlayerState : Singleton<PlayerState>
{
  [Header("判断人物状态")]
    public bool isMove;//是否在移动
    public bool isWalking;//是否正在行走
    public bool isRuning; //是否正在奔跑
    public bool isCrouching; //是否正在下蹲
    
  private void Update()
    {
        isMove = MovementScript.Instance.isGround && MovementScript.Instance.moveDirection.sqrMagnitude > 0;
        isRuning = MovementScript.Instance.isRun && isMove;
        isCrouching = MovementScript.Instance.isCrouching;
        isWalking = !isRuning && !isCrouching && isMove;
     }
}

脚步声控制

using System.Collections.Generic;
using UnityEngine;

//脚步声
public class FootStepSound : MonoBehaviour
{
    public RaycastHit hit;  // 射线检测结果
    public GameObject RayGo;  // 射线起点对象
    public AudioClip[] clipsMetal, clipsTree, clipsGrass, clipsDirt, clipsWater;  // 不同类型表面的声音剪辑数组
    public float dist = 2;  // 射线的长度
    private string tagProv;  // 上一次射线检测到的地面标签

    private int go = 0;  // 控制播放声音的变量,0表示重新播放,1表示继续播放
    public AudioSource AS;  // 声音源组件

    //该属性控制音频的播放速度,1.0 表示正常速度,小于 1.0 表示慢速播放,大于 1.0 表示快速播放。
    public float PitchRun, PitchWalk, PitchCrouch;

    // 创建一个列表用于存储之前选择过的音效索引
    private List<int> playedIndexes = new List<int>();

    void Update()
    {
        if (PlayerState.Instance.isRuning)
            AS.pitch = PitchRun;  //则设置为奔跑音调
        else if(PlayerState.Instance.isWalking){
            AS.pitch = PitchWalk;
        }else{
            AS.pitch = PitchCrouch;
        }
            

        if (PlayerState.Instance.isMove)
        {
            if (Physics.Raycast(RayGo.transform.position, Vector3.down, out hit, dist))  // 向下发射射线检测地面
            {
                if (hit.collider)
                {
                    if (hit.collider.tag != tagProv) go = 0;
                    tagProv = hit.collider.tag;

                    switch (hit.collider.tag)  // 根据地面的标签选择对应的声音类型
                    {
                        case "Metal":
                            PlayRandomSound(clipsMetal);  // 播放金属声音
                            break;
                        case "Tree":
                            PlayRandomSound(clipsTree);  // 播放树木声音
                            break;
                        case "Grass":
                            PlayRandomSound(clipsGrass);  // 播放草地声音
                            break;
                        case "Dirt":
                            PlayRandomSound(clipsDirt);  // 播放土地声音
                            break;
                        case "Water":
                            PlayRandomSound(clipsWater);  // 播放水声音
                            break;
                        default:
                            StopSound();  // 停止播放声音
                            break;
                    }
                }
            }
        }
        else
        {
            StopSound();  // 停止播放声音
        }
    }

    // 从给定的声音剪辑数组中随机播放一个声音
    void PlayRandomSound(AudioClip[] clips)
    {
        if (AS.isPlaying)
        {
            // AS.Stop();
        }
        if (go == 0)  // 如果需要重新设置音频剪辑
        {
            AS.clip = null;
            go = 1;
        }
        if (!AS.isPlaying)  // 如果当前没有正在播放的声音
        {
            
            // AS.clip = clips[Random.Range(0, clips.Length)];  // 随机选择一个声音剪辑
            int randomIndex = GetUniqueRandomIndex(clips.Length);  // 获取一个未播放过的随机索引
            AS.clip = clips[randomIndex];  // 根据索引选择一个声音剪辑
            AS.Play();  // 播放声音
        }
    }

    // 获取一个未播放过的随机索引
    int GetUniqueRandomIndex(int arrayLength)
    {
        int randomIndex;
        do
        {
            randomIndex = Random.Range(0, arrayLength);  // 生成一个随机索引
        } while (playedIndexes.Contains(randomIndex));  // 循环判断该索引是否已经播放过

        playedIndexes.Add(randomIndex);  // 将新的索引添加到已播放列表中
        if (playedIndexes.Count >= arrayLength)
        {
            playedIndexes.Clear();  // 如果已播放列表包含所有索引,则清空列表,重新开始播放
        }

        return randomIndex;
    }

    // 停止播放声音并重置go变量
    void StopSound()
    {
        AS.Stop();  // 停止播放声音
        go = 0;  // 重置go变量
    }
}

解释:

go 的这个逻辑保证了只有在需要重新设置音频剪辑时才会执行,避免了声音的混叠和中断。如果去除这个逻辑,可能会导致声音播放不正常。


AS.pitch是用来控制音频的音调(pitch)的属性。该属性控制音频的播放速度,1.0 表示正常速度,小于 1.0 表示慢速播放,大于 1.0 表示快速播放。

通过改变音调,可以实现音频的加速或减速播放。较高的音调会使音频听起来更快,而较低的音调则会使音频听起来更慢。


大致参数配置

目录
相关文章
|
2天前
|
图形学 开发者
【Unity小技巧】unity移动物体的探究——使用8个不同方法
【Unity小技巧】unity移动物体的探究——使用8个不同方法
10 1
|
2天前
|
图形学
【unity小技巧】Unity中实现一个战斗连击连招系统,可以动态添加减少连击连招段数功能
【unity小技巧】Unity中实现一个战斗连击连招系统,可以动态添加减少连击连招段数功能
5 0
|
2天前
|
存储 图形学
【unity小技巧】unity事件系统创建通用的对象交互的功能
【unity小技巧】unity事件系统创建通用的对象交互的功能
6 0
|
2天前
|
图形学
【unity小技巧】unity通过代码进行更改后处理效果
【unity小技巧】unity通过代码进行更改后处理效果
6 0
|
2天前
|
图形学
【unity小技巧】unity3D寻路指示轨迹预测
【unity小技巧】unity3D寻路指示轨迹预测
7 0
|
2天前
|
图形学
【推荐100个unity插件之19】武器拖尾特效插件——Pocket RPG Weapon Trails(2d 3d通用)
【推荐100个unity插件之19】武器拖尾特效插件——Pocket RPG Weapon Trails(2d 3d通用)
7 0
|
2天前
|
图形学
【unity小技巧】unity读excel配置表操作,excel转txt文本,并读取txt文本内容,实例说明
【unity小技巧】unity读excel配置表操作,excel转txt文本,并读取txt文本内容,实例说明
8 0
|
2天前
|
编解码 算法 图形学
【unity小技巧】减少Unity中的构建打包大小
【unity小技巧】减少Unity中的构建打包大小
5 0
|
2天前
|
图形学
【unity实战】3D水系统,游泳,潜水,钓鱼功能实现
【unity实战】3D水系统,游泳,潜水,钓鱼功能实现
3 0
|
2天前
|
存储 JSON 关系型数据库
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
11 2