Unity3D打斗游戏开发(一)普通攻击敌人判断
本篇文章要介绍的是关于打斗类(如:ARPG类,并非格斗类)游戏的攻击判定问题,通常这类游戏都会有普通攻击和技能攻击等攻击方式(可参考王者荣耀),下面将分别介绍下。
一、普通攻击
普通攻击的流程就是主角靠近敌人,播放攻击动画,调用敌人受伤害计算方法(+被击动画、特效等);这一过程有几点需要注意的,
(1)调用受伤害计算方法时机:因为播放攻击动画会有1s左右的时间,可以在播放动画同时启动一个协程来帮助调用受伤害计算方法;
(2)攻击成功触发条件:通过点击攻击按钮可以触发攻击技能,但不代表能打出攻击伤害,主角和敌人必须满足一些条件才行;通常我们的做法是判断距离和方向。通俗来说,比如主角要攻击前面的敌人,从这就可以提取两点信息了,就是两者需满足一定的距离和角度了。具体方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // 方式1:通过主角和场景中的所有敌人比较 private void AtkCondition1( float _range, float _angle) { // 搜索所有敌人列表(在动态创建敌人时生成的) // 列表存储的并非敌人的GameObject而是自定义的Enemy类 // Enemy类的一个变量mGameObject则用来存储实例出来的敌人实例 foreach (var go in GameManager.GetInstance.gMonsterDict) { // 敌人的坐标向量减去Player的坐标向量的长度(使用magnitude) float tempDis1 = (go.Value.mGameObject.transform.position - mGameObject.transform.position).magnitude; // 敌人向量减去Player向量就能得到Player指向敌人的一个向量 Vector3 v3 = go.Value.mGameObject.transform.position - mGameObject.transform.position; // 求出Player指向敌人和Player指向正前方两向量的夹角,其实就是Player和敌人的夹角(不分左右) float angle = Vector3.Angle( v3, mGameObject.transform.forward); if (tempDis1 < _range && angle < _angle) { // 距离和角度条件都满足了 } } } // 方式2:通过主角和射线检测到的敌人比较 private void AtkCondition2( float _range, float _angle) { // 球形射线检测周围怪物,不用循环所有怪物类列表,无法获取“Enemy”类 Collider[] colliderArr = Physics.OverlapSphere(mGameObject.transform.position, _range, LayerMask.GetMask( "Enemy" )); for ( int i = 0; i < colliderArr.Length; i++) { Vector3 v3 = colliderArr[i].gameObject.transform.position - mGameObject.transform.position; float angle = Vector3.Angle(v3, mGameObject.transform.forward); if (angle < _angle) { // 距离和角度条件都满足了 } } } |
上面两种方式主要针对两种不同方式,第一种方式是因为我的主角、敌人等对象是不挂任何脚本的,所有的模型对象都是动态生成,模型对象只是对应类(Player类和Enemy类等)中的一个变量而已,所以需要循环查找Enemy类列表来获取对应的其中一个类实例,这样就不单能获取GameObject了,而是可以获取Enemy类中的任意公开数据(变量、方法等);但这种方式也有个小问题,举个例子:故事背景是军队打仗,双方各100人;那么使用方式1就是双方各有100个类实例,而每个类实例都包含这个判断方法,是循环对方类实例列表(大小100),那么双方加起来就是(2*100*100)的计算量了,当然手游的话不应该这么极端同时出现这么多模型的。即使是一个主角加100的情况下,主角类作这样的判断也是浪费的,因为一般主角旁边最多几个敌人的,不应该每次都查找所有敌人啊。
所以第二种方式就是这种情况,只判断身旁的敌人,通过主角发射一定长度的环形射线检测周围敌人(类似球形触发器检测敌人是否进入触发器),直接获取射线检测到的敌人数组列表,再将其和主角作夹角对比,从而得到判断结果。因为碰撞检测都是直接得到碰撞对象GameObject的,比较适合对象上挂载脚本的方式(获取数据方便),但是对于我那种方式来说,我如果要通过一个GameObject获取其所属的类实例,只能循环查找类实例列表一个个判断了,那么就又变回第一种方式了,所以说这两种方式应该按实际情况去使用。
技术扩展
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 计算目标是否在指定扇形攻击区域内 private bool CalculateDistance() { float distance = (mGameObject.transform.position - Target.transform.position).magnitude; Vector3 mfrd = mGameObject.transform.forward; Vector3 tV3 = Target.transform.position - mGameObject.transform.position; // mfrd.normalized(归一化):方向不变,长度归一,用在只关心方向忽略大小情况下(毕竟以单位1计算比使用float类型数计算方便快速嘛) // Vector3.Dot(点乘):余弦值;Mathf.Acos()反余弦值(弧度形式表现) // Mathf.Rad2Deg:弧度转度;Mathf.Deg2Rad:度转弧度 float deg = Mathf.Acos(Vector3.Dot(mfrd.normalized, tV3.normalized)) * Mathf.Rad2Deg; // 一半扇形区域 if (distance < 2f && deg < 120 * 0.5){ return true ; } return false ; } |
1.2 点乘虽然可以判断两坐标点的夹角,但范围是[0,180],也就是说不分左右的,那么在实现移动转身时就无法区分顺时针转身或者逆时针转身了(只能指定一个转身方向)。
1 2 | // 借用上面变量作叉乘计算 Vector3 t = Vector3.Cross(mfrd.normalized, tV3.normalized); // 叉乘结果为一个Vector3向量 |
关于叉乘结果t,有三种结果:如果t.y>0 目标点在主角右方,主角顺时针转身;如果t.y<0 目标点在主角左方,主角逆时针转身;t.y=0,两者平行(夹角0或180)。PS:为方便记忆,可使用左手定则,如果y>0,左手拇指朝上,四指为转身方向,否则相反。
当然也可以使用下面方法自带区分顺、逆方向旋转
1 2 | Quaternion rotation = Quaternion.LookRotation(TargetPoint - mGameObject.transform.position); mGameObject.transform.rotation = Quaternion.Slerp(mGameObject.transform.rotation, rotation, Time.deltaTime * 10f); |
Unity3D打斗游戏开发系列: