Unity-BattleStar丨11. 且听风吟:音乐与音效

2024-08-14 16:39:11 浏览数 (1)

本章目标

完成 Unity-BattleStar的Audio系统

最终效果展示:视频地址

一、导入资源文件

文件下载:地址

1、导入Package

2、运行_Scenes里面的BattleStar_GameScene场景,观察

二、概要

1、BGM位于玩家对象,即摄像头下,Play On Awake、Loop

2、在同一个Audio Source—Audio Clip上动态切换音乐,需:

Assets新建Resources文件夹,将音乐放入其中,代码使用Resources.Load方法,动态更换Audio Clip

3、3D音效:

a、Audio Source组件—Spatial Blend设置为1开启3D音效

b、3D Sound Settings—Volume Rolloff设置为Custome Rolloff等

c、3D Sound Settings—Doppler Level设置为0避免Audio Source快速移动,Audio Listener听到的失真

三、注意事项

1、 一个场景只能有一个Audio Listener

2、用代码切换动画时,我们要注意Unity Animation默认播放动画应该空,否则即使写了改变播放动画,也不会执行我们写的程序,Unity会执行默认动画的播放

3、关于机器人不射击的原因:

原代码发射射线检测玩家是用的如下代码

代码语言:javascript复制
Physics.Raycast((transform.localPosition   new Vector3(0, 1.3f, 0)), transform.forward, out hit, 10);

我们通过在Scene视图会发现,某些机器人Z向(即前向)坐标轴并不是指向身体正前方,因此当机器人面对玩家时,往往射线检测的方向为另一方向,造成无法检测到玩家的现象

我们调整方向代码,改为如下所示。方向Z轴加上1.3是因为要跟起点等高,避免射线向上或向下倾斜

代码语言:javascript复制
Physics.Raycast((transform.localPosition   new Vector3(0, 1.3f, 0)),(playerTransform.position-(transform.position new Vector3(0,1.3f,0))), out hit, 15);

四、Audio系统控制策略

1、Assets新建Resources文件夹,将Packages里Audios的音频压缩包解压到里面去

2、删除GunWithHand的默认播放动画

3、给WeaponMainMesh、HealthPackage和每个Robot添加AudioSource组件,并设置为3D音效模式

4、我们分别给这几个C#脚本设置:

Gun:

1)、当我们击中Robot时,Robot会调用BulletHit的音效,若没击中,则在Gun代码中调用GunFire音效

2)、当更换弹药时,播放ReloadBullet音效,更改动画播放速度,使之与声音相匹配

AnimationState.speed调整动画播放速度

代码语言:javascript复制
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour
{
    public Animation anim;

    void Start()
    {
        // Walk at double speed
        anim["Walk"].speed = 2.0f;
    }
}

3)、可在没子弹时开火,此时只播放FireWithoutBullet音效

GunModelTrigger:当捡到枪支时,播放GetGun音效

HealthPackage:当捡到血包时,播放HealthPackage音效

Player:当玩家受伤时,播放PlayerGetHurt音效

Robot:当机器人射击时,播放RobotHit音效

五、代码展示

PS:有的代码执行完毕后就要销毁自身物体,我们可使其先GetComponent<MeshRenderer>().enabled = false;隐藏显示,Invoke()一段时间执行完我们想要的命令后再进行销毁

我们仅将最复杂的Gun、Robot代码展示出来,其余代码读者根据本文描述自行思考

Gun

代码语言:javascript复制
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class Gun : MonoBehaviour
{
    public Text bulletNumberText;

    //枪支Animation组件
    Animation gunAnimation;

    //主摄像机,用于Raycast射线检测
    Camera mainCamera;

    //开火粒子特效
    public ParticleSystem gunParticle;

    //开火声音
    AudioSource gunAudio;

    //子弹数量属性
    int gunBulletNumber;
    public int GunBulletNumber
    {
        set
        {
            gunBulletNumber = value;
            bulletNumberText.text = gunBulletNumber.ToString();
        }
        get
        {
            return gunBulletNumber;
        }
    }

    //控制开火属性。在没换弹完成前不允许开火。因此设置布尔变量,开完火后立即将允许开枪的变量设置为false,在换弹动画完成前不允许开火
    bool activeFire;
    public bool ActiveFire
    {
        set
        {
            activeFire = value;
        }
        get
        {
            return activeFire;
        }
    }

    private void Start()
    {
        gunAnimation = transform.GetComponent<Animation>();
        mainCamera = GameObject.Find("FirstPersonCharacter").GetComponent<Camera>();
        gunAudio = GetComponent<AudioSource>();

        GunBulletNumber = 25;
        ActiveFire = true;

        StartCoroutine(GunFire());
        StartCoroutine(GunReLoad());
    }

    IEnumerator GunFire()
    {
        while (true)
        {
            if (Input.GetMouseButton(0))
            {
                if (activeFire)
                {
                    Fire();
                }
            }
            yield return null;    //一帧怎么能执行两次开火动画呢?两次开火之间要有一个时间差。但这儿即使不隔一帧也没关系,因为我们已设置了开火一次后延迟换弹时间才能进行下一次开火 
        }
    }

    void Fire()
    {
        if (GunBulletNumber > 0)
        {
            activeFire = false;
            
            //发射射线检测是否打中机器人,是则调用机器人减血等动画
            Vector3 point = new Vector3(mainCamera.pixelWidth / 2, mainCamera.pixelHeight / 2, 0);
            Ray ray = mainCamera.ScreenPointToRay(point);
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit))
            {
                if (hit.transform.name.Contains("Robot"))
                {
                    ChangeGunAnimation("Fire01");
                    gunParticle.Play();
                    GunBulletNumber--;
                    //RobotGetHurt方法内有播放击中Robot的音效
                    hit.transform.GetComponent<Robot>().RobotGetHurt();
                    Invoke("ResumeFire", gunAnimation.GetClip("Fire01").length);
                }
                else
                {
                    ChangeGunAnimation("Fire01");
                    gunParticle.Play();
                    gunAudio.clip = (AudioClip)Resources.Load("GunFire");
                    GunBulletNumber--;
                    gunAudio.Play();  //若没击中机器人,但击中了某碰撞器,播放开火声音
                    Invoke("ResumeFire", gunAnimation.GetClip("Fire01").length);
                }  
            }
            else
            {
                ChangeGunAnimation("Fire01");
                gunParticle.Play();
                gunAudio.clip = (AudioClip)Resources.Load("GunFire");
                GunBulletNumber--;
                Invoke("ResumeFire", gunAnimation.GetClip("Fire01").length);
                gunAudio.Play();  //若什么都没击中,也播放开火声音
            }
        }
        else
        {
            activeFire = false;
            ChangeGunAnimation("Idle04");
            gunAnimation.Play();
            gunAudio.clip = (AudioClip)Resources.Load("FireWithoutBullet");
            gunAudio.Play();
            Invoke("ResumeFire", gunAnimation.GetClip("Idle04").length);
        }     
    }

    void ResumeFire()
    {
        ActiveFire = true;
    }

    void ChangeGunAnimation(string gunAnimationName)
    {
        if (!gunAnimation.isPlaying)
            gunAnimation.Play(gunAnimationName);   //Animation的名字是string类型,Animation组件会直接调用内部这个名字的Animation动画
    }

    IEnumerator GunReLoad()
    {
        while (true)
        {
            if (Input.GetKeyDown(KeyCode.R))
                ReLoadBullet();
            yield return null;
        }
    }

    void ReLoadBullet()
    {
        if (GunBulletNumber < 25)
        {
            if (!gunAnimation.isPlaying)
            {
                if (activeFire == true)
                {
                    activeFire = false;
                    StartCoroutine(GunReloadAnimation());
                }
            }
        }
    }

    IEnumerator GunReloadAnimation()
    {
        if (!gunAnimation.isPlaying)
        {
            gunAnimation["Reload01"].speed = 1.6f;
            gunAnimation.Play("Reload01");
            gunAudio.clip = (AudioClip)Resources.Load("ReloadBullet");
            Invoke("gunReload", 0.5f);
            yield return new WaitForSeconds(0.406f);
            GunBulletNumber = 25;
            ActiveFire = true;
        }
    }

    void gunReload()
    {
        gunAudio.Play();
    }
}

Robot

代码语言:javascript复制
using System.Collections;
using UnityEngine;
using UnityEngine.AI;

public class Robot : MonoBehaviour
{
    //玩家Transform组件
    [SerializeField] private Transform playerTransform;

    [SerializeField] private GameObject robotBullet;

    [SerializeField] private Transform healthImage;

    private Transform firePos;

    //用于控制机器人攻击间隔的布尔值
    private bool activeAttack;
    AudioSource AS;

    //机器人生命值
    private float robotHealth;
    public float RobotHealth
    {
        get
        {
            return robotHealth;
        }

        set
        {
            robotHealth = value;

            healthImage.localScale = new Vector3(value / 5, 1, 1);

            if (robotHealth == 0)
            {
                RobotDie();
            }
        }
    }

    //Unity Start方法,详情可查询官方文档
    void Start()
    {
        firePos = transform.Find("FirePos").transform;

        activeAttack = true;

        RobotHealth = 5;

        AS = GetComponent<AudioSource>();

        StartCoroutine(RobotNavigation());
    }

    //机器人寻路的逻辑判定
    private IEnumerator RobotNavigation()
    {
        while (GetComponent<NavMeshAgent>().enabled && RobotHealth > 0)
        {
            if (Vector3.Distance(playerTransform.position, transform.position) <= 10)
            {
                StopNavigation();
                RaycastHit hit;

                transform.LookAt(playerTransform);
                Physics.Raycast((transform.localPosition   new Vector3(0, 1.3f, 0)),(playerTransform.position-(transform.position new Vector3(0,1.3f,0))), out hit, 15);
                if (hit.transform.name == "FPSController")
                {
                    if (activeAttack)
                    {
                        //动画切换到Attack
                        GetComponent<Animator>().SetTrigger("Attack");
                        AS.clip = (AudioClip)Resources.Load("RobotHit");
                        InstantiateBullet();
                        AS.Play();

                        //避免短间隔重复伤害
                        activeAttack = false;

                        //2S后允许下一次进攻
                        Invoke("AttackPlayer", 2f);
                    }
                }
            }
            else if (10 < Vector3.Distance(playerTransform.position, transform.position) && Vector3.Distance(playerTransform.position, transform.position) < 30)
            {
                //机器人以玩家为目标进行寻路
                GetComponent<NavMeshAgent>().destination = playerTransform.position;

                //机器人保持面向玩家
                transform.LookAt(playerTransform);

                //继续寻路
                GetComponent<NavMeshAgent>().isStopped = false;

                //切换动画到Walk
                GetComponent<Animator>().SetBool("Walk", true);
            }
            else
            {
                StopNavigation();
            }

            yield return new WaitForEndOfFrame();
        }
    }

    private void InstantiateBullet()
    {
        //生成子弹飞向玩家
        Instantiate(robotBullet, firePos.position, firePos.rotation);
    }

    private void StopNavigation()
    {
        GetComponent<NavMeshAgent>().isStopped = true;
        GetComponent<Animator>().SetBool("Walk", false);
    }

    //NPC受到伤害
    public void RobotGetHurt()
    {
        RobotHealth -= 1;
        AS.clip = (AudioClip)Resources.Load("BulletHit");
        AS.Play();
    }

    //允许攻击
    private void AttackPlayer()
    {
        activeAttack = true;
    }

    //机器人死亡
    private void RobotDie()
    {
        //关闭碰撞体
        GetComponent<CapsuleCollider>().enabled = false;

        //关闭NavMeshAgent组件
        GetComponent<NavMeshAgent>().enabled = false;

        //机器人播放死亡动画
        GetComponent<Animator>().SetTrigger("Dead");

        //两秒后销毁机器人
        Invoke("DestroyRobot", 2);
    }

    //摧毁机器人
    private void DestroyRobot()
    {
        Destroy(gameObject);
    }
}

0 人点赞