Unity 3D\2D手机游戏开发:从学习到产品(第3版)
上QQ阅读APP看书,第一时间看更新

3.6 交互

当前的敌人虽然会攻击主角,但并没有造成实际伤害,主角暂时也不能攻击敌人。下面我们将分别为主角和敌人添加处理逻辑的代码,使其具备攻击对方的能力。

3.6.1 主角的射击

步骤 01 打开脚本Player.cs,添加OnDamage函数,该函数的作用是减少主角的生命,并更新UI界面的显示,当生命值小于0时,取消鼠标锁定。

            public void OnDamage(int damage)
            {
                m_life -= damage;

                // 更新UI
                GameManager.Instance.SetLife(m_life);

                // 取消锁定鼠标光标
                if (m_life <= 0)
                  Screen.lockCursor = false;
            }

步骤 02 在Player.cs中添加几个新的属性:

            // 枪口transform
           Transform m_muzzlepoint;

           // 射击时,射线能射到的碰撞层
           public LayerMask m_layer;

           // 射中目标后的粒子效果
           public Transform m_fx;

           // 射击音效
           public AudioClip m_audio;

           // 射击间隔时间计时器
           float m_shootTimer = 0;

步骤 03 在Player.cs的Start函数中添加下面的代码,获取枪口的位置。武器模型的枪口有一个名为muzzlepoint的空游戏体,它是在3D建模软件中加进去的,用来标识枪口的位置。注意,查找它的时候,因为其处于较深的层级,所以层级之间的名字要使用“/”来分隔。此外,也可以使用GameObject.GetComponentInChildren一次获得所有的子物体,然后通过名字查找。

            m_muzzlepoint = m_camTransform.FindChild("M16/weapon/muzzlepoint").transform;

步骤 04 在Player.cs的Update函数中添加下面的代码实现射击功能。这里主要是使用Physics.Raycast射出一条射线,如果射线与敌人相碰撞,则使敌人减少一定的生命值。

                // 更新射击间隔时间
                m_shootTimer -= Time.deltaTime;

                // 鼠标左键射击
                if (Input.GetMouseButton(0) && m_shootTimer<=0)
                {
                    m_shootTimer = 0.1f;
                    this.GetComponent<AudioSource>().PlayOneShot(m_audio);
                    // 减少弹药,更新弹药UI
                    GameManager.Instance.SetAmmo(1);
                    // RaycastHit用来保存射线的探测结果
                    RaycastHit info;

                    // 从muzzlepoint的位置,向摄像机面向的正方向射出一根射线
                    // 射线只能与m_layer所指定的层碰撞
                    bool hit = Physics.Raycast(m_muzzlepoint.position,
                      m_camTransform.TransformDirection(Vector3.forward), out info, 100, m_layer);
                    if (hit)
                    {
                      // 如果射中了Tag为enemy的游戏体
                      if (info.transform.tag.CompareTo("enemy") == 0)
                      {
                          Enemy enemy = info.transform.GetComponent<Enemy>();
                          // 敌人减少生命
                          enemy.OnDamage(1);
                      }
                      // 在射中的地方释放一个粒子效果
                      Instantiate(m_fx, info.point, info.transform.rotation);
                    }
                }

步骤 05 添加两个碰撞层:enemy和level。将enemy层指定给敌人,level层指定给场景模型,然后创建一个Tag,命名为enemy,指定给敌人,如图3-16所示。

图3-16 添加碰撞层

步骤 06 在场景中选择主角的游戏体,为其添加Audio Source组件。然后在Player脚本组件中将Layer设为enemy和level,这样主角在射击时,其射线可以击中敌人和场景。在Project窗口中找到FX.Prefab,将其作为射击时击中目标的特效,在rawdata\Sound Pack中找到shot.WAV作为射击的音效,如图3-17所示。

图3-17 设置Player

步骤 07 创建一个新的脚本AutoDestroy.cs,将其指定给射击特效FX.Prefab,这个脚本的作用是在一定时间内自动销毁游戏体。

        public class AutoDestroy : MonoBehaviour {
            public float m_timer = 1.0f;
            void Start () {
              // 通常可采用缓存的方式避免在游戏运行中使用Instantiate和Destroy,
              Destroy(this.gameObject, m_timer);
            }
        }

3.6.2 敌人的进攻与死亡

接下来,我们继续修改敌人的脚本,主要是添加攻击逻辑和死亡状态。

步骤 01 选择敌人,在菜单栏中选择【Component】→【Physics】→【Capsule Collider】为其添加碰撞体,如图3-18所示。

图3-18 添加碰撞体

如果需要精确地测试碰撞,比如判断射击到敌人的头或脚等部位,在建模时需要专门创建一个底面的模型用于测试碰撞,将其作为骨骼的子物体导出,在Unity中将其显示功能关闭,设为Mesh类型的碰撞体即可,其过程在第2章中介绍过。

步骤 02 打开enemy.cs脚本,添加OnDamage函数更新敌人的伤害,当生命值为0时,敌人进入死亡状态。

            public void OnDamage(int damage)
            {
                m_life -= damage;
                // 如果生命值为0,播放死亡动画
                if (m_life <= 0)
                {
                  m_ani.SetBool("death", true);
                  // 停止寻路
                  m_agent.ResetPath();
                }
            }

步骤 03 在Update函数中添加死亡状态,当敌人的死亡动画播完,更新UI界面并销毁自身。

                  // 如果处于死亡且不是过渡状态
                 if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.death")
                                      &&! m_ani.IsInTransition(0))
                 {
                    m_ani.SetBool("death", false);
                    // 当播放完成死亡动画
                    if (stateInfo.normalizedTime >= 1.0f)
                    {
                        // 加分
                        GameManager.Instance.SetScore(100);
                        // 销毁自身
                        Destroy(this.gameObject);
                    }
                 }

步骤 04 在Update函数中修改攻击状态,执行主角的OnDamage函数更新主角的生命值。

                if (stateInfo.fullPathHash == Animator.StringToHash("Base Layer.attack")
                                    && ! m_ani.IsInTransition(0))
                 {
                    // 面向主角
                    RotateTo();
                    m_ani.SetBool("attack", false);
                    // 如果动画播完,重新进入待机状态
                    if (stateInfo.normalizedTime >= 1.0f)
                    {
                        m_ani.SetBool("idle", true);

                        // 重置计时器 待机2秒
                        m_timer = 2;
                        m_player.OnDamage(1); // 添加对主角的伤害功能
                    }
                 }

运行游戏,如果主角距离敌人过近,则有被消灭的可能。单击鼠标左键,可以向敌人开火消灭敌人,在子弹击中的地方还会有粒子效果出现,如图3-19所示。

图3-19 射击效果