团体战斗核心技术:如何防止团体进攻中角色堆叠在一起

最近比较流行团体战斗的游戏,比如肉鸽+SLG,肉鸽+割草等类型。这里面团体战斗的时候,如何防止战斗中的角色堆叠是其中的一个重点,第一次做这类游戏的朋友感觉很难,实际上非常简单,今天就直接告诉大家如何做到,而不是”装神弄鬼“的来渲染”各种系统与技术多么NB“。先看效果:

1: 为什么不用"物理引擎"与"RVO"

很多人的第一反应就是为什么不用"物理引擎","动态壁障"来处理,这里主要是和这类游戏的需求有关系。因为在 ”走位“的过程中允许角色物体之间可以相互穿透,主角不会被怪物"卡住"。所以就不能使用"物理引擎"与"动态壁障"。需要自己编写规则来控制角色的团体战斗与移动,同时也需要自己制定规则来提供机制防止怪物之间的堆叠。

对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀

要满足这样的需求,那么寻路导航,战斗队形,与堆叠处理都需要自己编写代码来写规则。

2: 出现"堆叠"的常见情况与处理规则

堆叠: 每个角色都有一个互斥半径。当两个角色的距离小于角色1的互斥半径 + 角色2的互斥半径时,这时我们称角色A与角色B堆叠。

图片

如果自己控制角色的移动,战斗我们就要分析出来出现堆叠的常见的处理情况与处理规则:

1: 同类队友更容易堆叠,异类(自己与敌人)基于攻击范围等距离控制,不容易堆叠在一起。如:角色与怪物走进了自己的攻击区域,则发起攻击,不会继续前进,自然就通过攻击来将"异类"的堆叠避免,如果有必要,也可以把同类堆叠处理改成全类防堆叠处理。算法不变。只是运算规模会增加。

2: 枚举战斗中的需要处理”堆叠“的重要的状态,Idle, Attack, Run。这里我们把这些状态分成两类,"动态角色(Idle || Attack)"与静态角色(Run)", 于是整个战斗场景中的堆叠就分成了3种情况:

(1)两个静态的角色堆叠在一起。为了让他们保持安全距离,处理时,就沿着两人连线的方向,相互推开即可,直到他们恢复安全距离(A的互斥半径+B的互斥半径之和)。

图片

(2)两个动态的角色堆叠在一起,如果这两个角色是不同方向的移动,那么分开只是时间问题,无需要做任何的处理。如果两个角色的方向相同,这个时候就会发生堆叠在一起来行走。处理方式如下: 让其中一个角色A回退回到上一次前进的结果(视觉上,角色A停止不前进),让另外一个角色B正常行走,等他们拉开距离后恢复。比如A, B两个角色同向向右移动,当A,B发现彼此"堆叠"后,A迭代完移动,然后堆叠处理模块再把A往回迭代,这样A就不动,B正常移动,这样多次处理后,A与B保持安全距离了,则A继续移动。这样便分开了两个"同向堆叠移动"的物体。如何判断同向堆叠,规则为"当两个角色的速度的方向的夹角小于5度"的时候视为"同向"。

图片

(3)一个静态的角色与一个动态的角色堆叠在一起,这种情况不用处理,交给时间,它们两个自然会分开。

图片

3: 结构与算法

1: "堆叠处理"发生的时期:

"堆叠模块"在每次迭代的最后的逻辑处理。如下图的代码接口,战斗迭代的时候,堆叠发生在寻路导航,AI处理,技能释放等迭代的最后,最后再调用堆叠模块"来"修正堆叠"现象。

图片

2: 找出战斗场景中的"堆叠对"。

    private FindLegionNearPairUpdate(): void {        // 找出entity靠的太近的entity对;        FightWorldMgr.Instance.ForEachTypeEntity(EntityType.Legion, (e: CharactorEntity2D)=> {            var target = EntityCtrlUtils.FindOneOverlapEntityInWorld(e, EntityType.Legion);            if(target) {                // 排除掉重复的, A, B, B, A这种情况;                if(!this.IsPairAlreadyExsit(this.legionEntitiesPair, e, target)) {                    var pair = new NearPairEntitys();                    pair.entityA = e;                    pair.entityB = target;                    this.legionEntitiesPair.push(pair);                }            }            return 1;         });    }
    public static FindOneOverlapEntityInWorld(entity: CharactorEntity2D, entityType: number): CharactorEntity2D {        var target = null;        FightWorldMgr.Instance.ForEachTypeEntity(entityType, (e: CharactorEntity2D)=>{            if(entity === e) { // 同一个entity,排除;                return 1;            }            // 判断两个物体的距离            let dis = Vec3.squaredDistance(entity.uTransform.pos, e.uTransform.pos);            let minLen = entity.uInfo.charactorData.MutexR + e.uInfo.charactorData.MutexR;            // console.log("yes it is", minLen, dis, entity.uTransform.pos.y, e.uTransform.pos.y);            if(dis <= (minLen * minLen)) {                target = e;                return -1;            }               // end            return 1;        });        return target;    }

3: 处理堆叠对,分成"静态类堆叠"处理与"动态类堆叠"处理:

step1: 找出场景中的"堆叠对"

step2: 处理每个"对谍对",排除掉不用处理的情况;

    update(dt: number): void {        // 军团        // this.legionEntitiesPair = [];        this.legionEntitiesPair.length = 0;        this.FindLegionNearPairUpdate();        // console.log(this.legionEntitiesPair.length);        this.HandleNearMutexPair(this.legionEntitiesPair, dt);        // 怪物        // this.monsterEntitiesPair = [];        // this.monsterEntitiesPair.length = 0;        // this.FindMonsterNearPairUpdate();        // this.HandleNearMutexPair(this.monsterEntitiesPair, dt);    }
    private HandleNearMutexPair(mutexPairSet: Array<NearPairEntitys>, dt: number): void {        for(var i = 0; i < mutexPairSet.length; i ++) {            var pairData = mutexPairSet[i];            // 处理静态            if((pairData.entityA.uStatus.status === CharactorStatus.Idle || pairData.entityA.uStatus.status === CharactorStatus.Attack1 || pairData.entityA.uStatus.status === CharactorStatus.Attack2) &&                 (pairData.entityB.uStatus.status === CharactorStatus.Idle || pairData.entityB.uStatus.status === CharactorStatus.Attack1 || pairData.entityA.uStatus.status === CharactorStatus.Attack2)) {                this.ProcessNearMutexWithStationary(pairData, dt);            }            // 处理动态            else if(pairData.entityA.uStatus.status === CharactorStatus.Run &&                 pairData.entityB.uStatus.status === CharactorStatus.Run) {                this.ProcessNearMutexWithDynamic(pairData, dt);            }        }    }
    // 静止状态下的距离互斥处理    private ProcessNearMutexWithStationary(pairData: NearPairEntitys, dt: number): void {                var dirAB = v3(0, 0, 0);        Vec3.subtract(dirAB, pairData.entityB.uTransform.pos, pairData.entityA.uTransform.pos);        dirAB = dirAB.normalize();                var len = this.mutexSpeed * dt;        var offsetX = len * dirAB.x;        var offsetZ = len * dirAB.z;                // entityA 反方向移动        pairData.entityA.uTransform.pos.x -= offsetX;        pairData.entityA.uTransform.pos.z -= offsetZ;        // end        // entityB 反向移动        pairData.entityB.uTransform.pos.x += offsetX;        pairData.entityB.uTransform.pos.z += offsetZ;        // end        // 后续考虑攻击围绕着目标来移动,暂时不处理        console.log("ProcessNearMutexWithStationary", len, offsetX, offsetZ);        // end    }
    // 两个角色在一条先上行走,这样看上去就重叠了,需要处理;    private ProcessNearMutexWithDynamic(pairData: NearPairEntitys, dt: number): void {        console.log("ProcessNearMutexWithDynamic");        var rA = Math.atan2(pairData.entityA.uAStarNav.vz, pairData.entityA.uAStarNav.vx);        var rB = Math.atan2(pairData.entityB.uAStarNav.vz, pairData.entityB.uAStarNav.vx);        if(Math.abs(rA - rB) > (5 * Math.PI) / 180) { // 速度的方向超过了5度,那么就不用管;            return;        }        // 改变一个其中一个,停一停,等一等,反方向回退        pairData.entityA.uTransform.pos.x -= pairData.entityA.uAStarNav.vx * dt;        pairData.entityA.uTransform.pos.z -= pairData.entityA.uAStarNav.vz * dt;        pairData.entityA.uAStarNav.passedTime -= dt;        // end    }

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值