设为首页收藏本站
查看: 908|回复: 0

Cha11丨且听风吟:音乐与音效

[复制链接]

该用户从未签到

17

主题

19

帖子

447

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
447
橙子va 发表于 2018-5-30 23:30:26 9080 | 显示全部楼层 |阅读模式
本章目标

完成 Unity-BattleStar的Audio系统

最终效果展示:视频地址



一、导入资源文件

文件下载:微信公众号vrlife—新书推荐—新书配套资源下载

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会执行默认动画的播放

0528-01.png

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

原代码发射射线控制策略是检测机器人坐标Z向是否存在玩家

  1. Physics.Raycast((transform.localPosition + new Vector3(0, 1.3f, 0)), transform.forward, out hit, 10);
复制代码

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

若我们想要机器人射线朝向玩家方向,便完成如下代码。方向Z轴加上1.3是因为要跟起点等高,避免射线向上或向下倾斜

  1. Physics.Raycast((transform.localPosition + new Vector3(0, 1.3f, 0)),(playerTransform.position-(transform.position+new Vector3(0,1.3f,0))), out hit, 15);
复制代码
0529-01.png



四、Audio系统控制策略

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


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


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


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

1、Gun:

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

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

AnimationState.speed调整动画播放速度

  1. using UnityEngine;
  2. using System.Collections;

  3. public class ExampleScript : MonoBehaviour
  4. {
  5.     public Animation anim;

  6.     void Start()
  7.     {
  8.         // Walk at double speed
  9.         anim["Walk"].speed = 2.0f;
  10.     }
  11. }
复制代码

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


2、GunModelTrigger:

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


3、HealthPackage:

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


4、Player:

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


5、Robot:

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



五、代码展示

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

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

Gun
  1. using System.Collections;
  2. using UnityEngine;
  3. using UnityEngine.UI;

  4. public class Gun : MonoBehaviour
  5. {
  6.     public Text bulletNumberText;

  7.     //枪支Animation组件
  8.     Animation gunAnimation;

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

  11.     //开火粒子特效
  12.     public ParticleSystem gunParticle;

  13.     //开火声音
  14.     AudioSource gunAudio;

  15.     //子弹数量属性
  16.     int gunBulletNumber;
  17.     public int GunBulletNumber
  18.     {
  19.         set
  20.         {
  21.             gunBulletNumber = value;
  22.             bulletNumberText.text = gunBulletNumber.ToString();
  23.         }
  24.         get
  25.         {
  26.             return gunBulletNumber;
  27.         }
  28.     }

  29.     //控制开火属性。在没换弹完成前不允许开火。因此设置布尔变量,开完火后立即将允许开枪的变量设置为false,在换弹动画完成前不允许开火
  30.     bool activeFire;
  31.     public bool ActiveFire
  32.     {
  33.         set
  34.         {
  35.             activeFire = value;
  36.         }
  37.         get
  38.         {
  39.             return activeFire;
  40.         }
  41.     }

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

  47.         GunBulletNumber = 25;
  48.         ActiveFire = true;

  49.         StartCoroutine(GunFire());
  50.         StartCoroutine(GunReLoad());
  51.     }

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

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

  76.             if (Physics.Raycast(ray, out hit))
  77.             {
  78.                 if (hit.transform.name.Contains("Robot"))
  79.                 {
  80.                     ChangeGunAnimation("Fire01");
  81.                     gunParticle.Play();
  82.                     GunBulletNumber--;
  83.                     //RobotGetHurt方法内有播放击中Robot的音效
  84.                     hit.transform.GetComponent<Robot>().RobotGetHurt();
  85.                     Invoke("ResumeFire", gunAnimation.GetClip("Fire01").length);
  86.                 }
  87.                 else
  88.                 {
  89.                     ChangeGunAnimation("Fire01");
  90.                     gunParticle.Play();
  91.                     gunAudio.clip = (AudioClip)Resources.Load("GunFire");
  92.                     GunBulletNumber--;
  93.                     gunAudio.Play();  //若没击中机器人,但击中了某碰撞器,播放开火声音
  94.                     Invoke("ResumeFire", gunAnimation.GetClip("Fire01").length);
  95.                 }  
  96.             }
  97.             else
  98.             {
  99.                 ChangeGunAnimation("Fire01");
  100.                 gunParticle.Play();
  101.                 gunAudio.clip = (AudioClip)Resources.Load("GunFire");
  102.                 GunBulletNumber--;
  103.                 Invoke("ResumeFire", gunAnimation.GetClip("Fire01").length);
  104.                 gunAudio.Play();  //若什么都没击中,也播放开火声音
  105.             }
  106.         }
  107.         else
  108.         {
  109.             activeFire = false;
  110.             ChangeGunAnimation("Idle04");
  111.             gunAnimation.Play();
  112.             gunAudio.clip = (AudioClip)Resources.Load("FireWithoutBullet");
  113.             gunAudio.Play();
  114.             Invoke("ResumeFire", gunAnimation.GetClip("Idle04").length);
  115.         }     
  116.     }

  117.     void ResumeFire()
  118.     {
  119.         ActiveFire = true;
  120.     }

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

  126.     IEnumerator GunReLoad()
  127.     {
  128.         while (true)
  129.         {
  130.             if (Input.GetKeyDown(KeyCode.R))
  131.                 ReLoadBullet();
  132.             yield return null;
  133.         }
  134.     }

  135.     void ReLoadBullet()
  136.     {
  137.         if (GunBulletNumber < 25)
  138.         {
  139.             if (!gunAnimation.isPlaying)
  140.             {
  141.                 if (activeFire == true)
  142.                 {
  143.                     activeFire = false;
  144.                     StartCoroutine(GunReloadAnimation());
  145.                 }
  146.             }
  147.         }
  148.     }

  149.     IEnumerator GunReloadAnimation()
  150.     {
  151.         if (!gunAnimation.isPlaying)
  152.         {
  153.             gunAnimation["Reload01"].speed = 1.6f;
  154.             gunAnimation.Play("Reload01");
  155.             gunAudio.clip = (AudioClip)Resources.Load("ReloadBullet");
  156.             Invoke("gunReload", 0.5f);
  157.             yield return new WaitForSeconds(0.406f);
  158.             GunBulletNumber = 25;
  159.             ActiveFire = true;
  160.         }
  161.     }

  162.     void gunReload()
  163.     {
  164.         gunAudio.Play();
  165.     }
  166. }
复制代码


Robot
  1. using System.Collections;
  2. using UnityEngine;
  3. using UnityEngine.AI;

  4. public class Robot : MonoBehaviour
  5. {
  6.     //玩家Transform组件
  7.     [SerializeField] private Transform playerTransform;

  8.     [SerializeField] private GameObject robotBullet;

  9.     [SerializeField] private Transform healthImage;

  10.     private Transform firePos;

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

  14.     //机器人生命值
  15.     private float robotHealth;
  16.     public float RobotHealth
  17.     {
  18.         get
  19.         {
  20.             return robotHealth;
  21.         }

  22.         set
  23.         {
  24.             robotHealth = value;

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

  26.             if (robotHealth == 0)
  27.             {
  28.                 RobotDie();
  29.             }
  30.         }
  31.     }

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

  36.         activeAttack = true;

  37.         RobotHealth = 5;

  38.         AS = GetComponent<AudioSource>();

  39.         StartCoroutine(RobotNavigation());
  40.     }

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

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

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

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

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

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

  76.                 //切换动画到Walk
  77.                 GetComponent<Animator>().SetBool("Walk", true);
  78.             }
  79.             else
  80.             {
  81.                 StopNavigation();
  82.             }

  83.             yield return new WaitForEndOfFrame();
  84.         }
  85.     }

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

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

  96.     //NPC受到伤害
  97.     public void RobotGetHurt()
  98.     {
  99.         RobotHealth -= 1;
  100.         AS.clip = (AudioClip)Resources.Load("BulletHit");
  101.         AS.Play();
  102.     }

  103.     //允许攻击
  104.     private void AttackPlayer()
  105.     {
  106.         activeAttack = true;
  107.     }

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

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

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

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

  120.     //摧毁机器人
  121.     private void DestroyRobot()
  122.     {
  123.         Destroy(gameObject);
  124.     }
  125. }
复制代码



回复

使用道具 举报

0条回复
跳转到指定楼层

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|赛隆网 ( 粤ICP备16067842号 )  

Copyright 2013 -̳ Ȩ All Rights Reserved.

Powered by Cylonspace ; All Rights Reserved.

QQ|Archiver|手机版|小黑屋|赛隆网 ( 粤ICP备16067842号 )  

GMT+8, 2018-10-21 20:26 , Processed in 0.132992 second(s), 27 queries.

快速回复 返回顶部 返回列表