Unity3D打斗游戏开发(一)普通攻击敌人判断

发表于2017-05-24
评论1 8.4k浏览

    本篇文章要介绍的是关于打斗类(如: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打斗游戏开发系列:

Unity3D打斗游戏开发(二)异步加载场景

Unity3D打斗游戏开发(三)简单实现对象池

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引