3.6 交互 当前的敌人虽然会攻击主角,但并没有造成实际伤害,主角暂时也不能攻击敌人。下面我们将分别为主角和敌人添加处理逻辑的代码,使其具备攻击对方的能力。 3.6.1 主角的射击 打开脚本Player.cs,添加OnDamage函数,该函数的作用是减少主角的生命,并更新UI界面的显示,当生命小于零时,取消鼠标锁定。 public void OnDamage(int damage) { m_life -= damage; // 更新UI GameManager.Instance.SetLife(m_life); // 如果生命为0,取消鼠标锁定 if (m_life <= 0) Screen.lockCursor = false; } 在Player.cs中添加几个新的属性: //枪口transform Transform m_muzzlepoint; // 射击时,射线能射到的碰撞层 public LayerMask m_layer; // 射中目标后的粒子效果 public Transform m_fx; // 射击音效 public AudioClip m_audio; // 射击间隔时间计时器 float m_shootTimer = 0; 在Player.cs的Start函数中添加下面的代码,获取枪口的位置。武器模型的枪口有一个叫muzzlepoint的空游戏体,它是在3D建模软件中加进去的,用来标识枪口的位置。注意查找它的时候,因为它处于较深的层级,层级之间的名字要使用“/”来分隔。 m_muzzlepoint = m_camTransform.FindChild("M16/weapon/muzzlepoint").transform; 在Player.cs的Update函数中添加下面的代码实现射击功能。这里主要是使用Physics.Raycast射出一条射线,如果射线与敌人相碰撞,则使敌人减少一定生命。 // 更新射击间隔时间 m_shootTimer -= Time.deltaTime; // 鼠标左键射击 if (Input.GetMouseButton(0) && m_shootTimer<=0) { m_shootTimer = 0.1f; //射击音效 this.audio.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); } } 添加两个碰撞层,enemy和level,将enemy层指定给敌人,将level层指定给场景模型。然后再创建一个Tag,命名为enemy,指定给敌人,如图3-17所示。 图3-17 设置Tag和Layer 在场景中选择主角的游戏体,为其添加Audio Source组件。然后在Player脚本组件中将Layer设为enemy和level,这样主角射击时,其射线可以击中敌人和场景。在Project窗口找到FX.prefab,将其作为射击时击中目标的特效,在RawdataSound Pack中找到shot.WAV作为射击的音效,如图3-18所示。 图3-18 设置Player 创建一个新的脚本AutoDestroy.cs,将其指定给射击特效FX.prefab,这个脚本的作用是在一定时间内自动销毁游戏体。 using UnityEngine; using System.Collections; public class AutoDestroy : MonoBehaviour { public float m_timer = 1.0f; void Update () { m_timer -= Time.deltaTime; if (m_timer <= 0) Destroy(this.gameObject); } } 3.6.2 敌人的进攻与死亡 接下来我们继续修改敌人的脚本,主要是添加攻击逻辑和死亡状态。 选择敌人,在菜单栏选择【Component】→【Physics】→【Capsule Collider】为其添加碰撞体,如图3-19所示。 图3-19 设置敌人的碰撞体 如果需要精确地测试碰撞,比如判断射击到敌人的头或脚之类,在建模时需要专门创建一个低面的模型用于测试碰撞,将其作为骨骼的子物体导出,在Unity中将其显示功能关闭,设为Mesh类型的碰撞体即可。 打开enemy.cs脚本,添加OnDamage函数更新敌人的伤害,当生命值为零时,敌人进入死亡状态。 public void OnDamage (int damage) { m_life -= damage; // 如果生命为0,进入死亡状态 if (m_life <= 0) { m_ani.SetBool("death", true); } } 在Update函数中添加死亡状态,当敌人的死亡动画播完,更新UI界面,并销毁自身。 if (stateInfo.nameHash == Animator.StringToHash("Base Layer.death") && !m_ani.IsInTransition(0)) { // 当播放完成死亡动画 if (stateInfo.normalizedTime >= 1.0f) { // 加分 GameManager.Instance.SetScore(100); // 销毁自身 Destroy(this.gameObject); } } 在Update函数中修改攻击状态,执行主角的OnDamage函数更新主角的生命值。 if (stateInfo.nameHash == 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); m_timer = 2; // 更新主角的生命 m_player. OnDamage (1); } } 运行游戏,如果主角距离敌人过近,则有被消灭的可能。按鼠标左键,可以向敌人开火消灭敌人,在子弹击中的地方还会有粒子效果出现,如图3-20所示。 图3-20 射击效果