【Unity】TankBattle联机坦克大战(五)坦克的核心功能(下)

发布时间:2026/8/28 17:45:18
【Unity】TankBattle联机坦克大战(五)坦克的核心功能(下) 更新日期2026年8月27日。项目源码获取源码。索引坦克的核心功能八、坦克移动碰撞检测1.获取坦克前方地块①.坦克完全处于道路中间②.坦克处于道路两边③.识别坦克位置2.与地块进行碰撞检测①.砖墙②.钢板③.海水④.玩家老巢3.碰撞后处理①.坦克完全处于道路中间②.坦克处于道路两边4.半边地块的碰撞检测5.限制向上移动坦克的核心功能本篇我们介绍坦克的核心功能实现在上一篇的基础上整个关卡地图已经创建完成下一步便是创建坦克单位实现游戏真正的核心玩法。内容接上篇【Unity】TankBattle联机坦克大战四坦克的核心功能上。八、坦克移动碰撞检测由于帧同步所必须的确定性所以碰撞检测不能基于Unity的物理系统来实现我们必须实现一套简单的碰撞检测逻辑并保证其在不同设备、不同环境下保持确定性。1.获取坦克前方地块当坦克进行移动时碰撞检测的本质也即是判断其能否抵达即将移动到的下一位置而我们的坦克只能向前移动向后移动时会调转车头转变为向前移动所以移动前首先需要获取即将移动到的下一位置的地块也即是坦克前方的地块。注意后续均以坦克向上移动为例。//坦克前方位置坦克中心坐标 坦克尺寸的一半inttankFrontPosRealPos.yTB_UILevel.BlockSizeHalf;获取坦克前方地块要根据坦克所处位置分两种情况进行处理1.坦克完全处于道路中间获取坦克前方的一个地块。2.坦克处于道路两边获取坦克前方的两个地块一个地块各碰到一半。①.坦克完全处于道路中间这种情况处理起来比较简单直接以坦克当前坐标为参数计算出前方地块坐标再以坐标获取地块即可//获取其前方地块TB_Blockfront_uILevel.GetBlockByPos(pos.x,tankFrontPos);这里要先实现一个能以坐标获取地块的通用方法/// summary/// 获取一个位置所在的地块/// /summarypublicTB_BlockGetBlockByPos(intposX,intposY){intx(posX/BlockSize)-1;if(posX%BlockSize0)x;inty(posY/BlockSize)-1;if(posY%BlockSize0)y;xMath.Clamp(x,0,Rows-1);yMath.Clamp(y,0,Cols-1);return_blocks[x,y];}②.坦克处于道路两边当坦克处于道路两边时其位置左右横跨了两个地块所以其前方的地块也应该分为左右两个//获取其前方左侧、右侧地块TB_BlockfrontLeft_uILevel.GetBlockByPos(pos.x-TB_UILevel.BlockSizeHalf,tankFrontPos);TB_BlockfrontRight_uILevel.GetBlockByPos(pos.xTB_UILevel.BlockSizeHalf,tankFrontPos);③.识别坦克位置如何识别坦克的位置是前两种中的哪一种只需如下的简单判断即可//坦克横坐标向上移动时除以地块尺寸能够除尽则在垂直方向上完全处于道路中间if(pos.x%TB_UILevel.BlockSize0){}//否则处于道路两边else{}2.与地块进行碰撞检测获取到了坦克前方的地块接下来只需要进行碰撞检测即可首先我们依然得分类型进行处理。①.砖墙由于砖墙可拆分为4行*4列16个组成片所以碰撞检测最为精细首先要检测最下面的一行行号0组成片是否与坦克产生碰撞因为坦克是向上移动所以最下面的一行理应最先碰到。某行砖墙组成片能够达成碰撞对坦克产生阻挡排斥效果的条件如下1.该行砖墙组成片未被完全破坏比如还剩下两边的一块也能产生阻挡效果这在原版里经常被用作挡路机关2.坦克前方y坐标高于该行砖墙组成片的低坐标向上移动挡路机关满足如上两个条件则表明坦克碰到了该行砖墙组成片应该被阻挡排斥代码实现如下//!IsDestroyRow(0)行号0的整行组成片未被完全破坏//frontY pieceY坦克前方y坐标高于该行组成片的低坐标if(!IsDestroyRow(0)frontYpieceY){//阻挡排斥将该行组成片低坐标赋予坦克前方y坐标实现将坦克挡在后面的效果frontYpieceY;}检测某一行的组成片是否完全被销毁/// summary/// 指定一行的组成片是否完全被销毁仅针对砖墙或钢板/// /summary/// param namerow行号/parampublicboolIsDestroyRow(introw){if(TypeBlockType.BrickWall){if(row0row4){return_brickWallPieces[0,row].IsDestroy_brickWallPieces[1,row].IsDestroy_brickWallPieces[2,row].IsDestroy_brickWallPieces[3,row].IsDestroy;}}elseif(TypeBlockType.SteelPlate){if(row0row2){return_steelPlatePieces[0,row].IsDestroy_steelPlatePieces[1,row].IsDestroy;}}returnfalse;}如果第一行砖墙组成片未产生碰撞则依次判断后面三行//判断第一行if(!IsDestroyRow(0)frontYpieceY){}//判断第二行第二行的低坐标 第一行低坐标 地块尺寸的四分之一elseif(!IsDestroyRow(1)frontY(pieceYTB_UILevel.BlockSizeQuarter)){}//判断第三行第三行的低坐标 第一行低坐标 地块尺寸的二分之一elseif(!IsDestroyRow(2)frontY(pieceYTB_UILevel.BlockSizeHalf)){}//判断第四行第四行的低坐标 第一行低坐标 地块尺寸的四分之三elseif(!IsDestroyRow(3)frontY(pieceYTB_UILevel.BlockSizeHalfTB_UILevel.BlockSizeQuarter)){}完事后这一块砖墙的碰撞检测才算完成。②.钢板钢板与砖墙的碰撞检测类似只不过钢板的组成片为2行*2列4个所以只需检测两行//判断第一行if(!IsDestroyRow(0)frontYpieceY){}//判断第二行第二行的低坐标 第一行低坐标 地块尺寸的二分之一elseif(!IsDestroyRow(1)frontY(pieceYTB_UILevel.BlockSizeHalf)){}③.海水海水也能产生阻挡效果其是整个地块生效所以处理极其简单//如果碰撞到的地块为海水elseif(TypeBlockType.Seawater){//则直接阻挡排斥将该地块低坐标赋予坦克前方y坐标frontYpieceY;}④.玩家老巢玩家老巢也能产生阻挡效果其是整个地块生效所以处理方式与海水一致//如果碰撞到的地块为玩家老巢elseif(TypeBlockType.Lair){frontYpieceY;}3.碰撞后处理碰撞后的处理也应分两种情况。①.坦克完全处于道路中间这种情况只需将碰撞产生的排斥坐标赋予坦克即可//获取其前方地块TB_Blockfront_uILevel.GetBlockByPos(pos.x,tankFrontPos);//与地块碰撞检测如果产生碰撞则返回排斥后坐标pos.yfront.TankCollideBlockUp(tankFrontPos,0);②.坦克处于道路两边这种情况需要判断坦克前方的两个地块以其中的排斥坐标的较小值作用到坦克上因为我们处理的是向上移动较小的坐标意味着越靠下所以其越先阻挡坦克的前进//获取其前方左侧、右侧地块TB_BlockfrontLeft_uILevel.GetBlockByPos(pos.x-TB_UILevel.BlockSizeHalf,tankFrontPos);TB_BlockfrontRight_uILevel.GetBlockByPos(pos.xTB_UILevel.BlockSizeHalf,tankFrontPos);//先判断左侧地块intleftPosfrontLeft.TankCollideBlockUp(tankFrontPos,1);//再判断右侧地块intrightPosfrontRight.TankCollideBlockUp(tankFrontPos,-1);//左侧和右侧取最小值pos.yMathf.Min(leftPos,rightPos);如下图左侧地块的排斥坐标较小其产生的阻挡效果才生效4.半边地块的碰撞检测如上坦克处于道路两边时需要检测地块的一半左半边或右半边是否产生碰撞所以我们必须为碰撞检测方法增加一个参数pos/// summary/// 坦克向上移动时碰撞到地块/// /summary/// param namey坦克前方位置的y坐标/param/// param namepos碰撞位置-1碰到左半边0碰到整个地块1碰到右半边/param/// returns被地块排斥后的新y坐标/returnspublicintTankCollideBlockUp(inty,intpos)当仅检测半边地块的碰撞时也只需检测半边组成片是否被销毁//!IsDestroyRowHalf(0, -1)行号0的左半行组成片未被完全破坏if(!IsDestroyRowHalf(0,-1)frontYpieceY){frontYpieceY;}5.限制向上移动基于上文的代码基础我们将其统一为了一个限制坦克向上移动的方法/// summary/// 限制坦克向上移动碰到砖墙、钢板、海水等/// /summaryprivateVector2IntLimitMoveUp(Vector2Intpos){//坦克前方位置inttankFrontPospos.yTB_UILevel.BlockSizeHalf;pos.ytankFrontPos;//坦克位置在垂直方向上处于两个地块之间if(pos.x%TB_UILevel.BlockSize0){//获取其前方左侧、右侧地块TB_BlockfrontLeft_uILevel.GetBlockByPos(pos.x-TB_UILevel.BlockSizeHalf,tankFrontPos);TB_BlockfrontRight_uILevel.GetBlockByPos(pos.xTB_UILevel.BlockSizeHalf,tankFrontPos);//先判断左侧地块intleftPosfrontLeft.TankCollideBlockUp(tankFrontPos,1);//再判断右侧地块intrightPosfrontRight.TankCollideBlockUp(tankFrontPos,-1);//左侧和右侧取最小值pos.yMathf.Min(leftPos,rightPos);}//坦克位置在垂直方向上处于一个地块上else{//获取其前方地块TB_Blockfront_uILevel.GetBlockByPos(pos.x,tankFrontPos);//碰到整个地块pos.yfront.TankCollideBlockUp(tankFrontPos,0);}//坦克位置回到中心pos.y-TB_UILevel.BlockSizeHalf;returnpos;}同理向下移动、向左移动、向右移动等都需要完全按照本文的碰撞检测流程走一遍只是坐标与方向上存在不同原理是一致的所以在这里我就不再赘述了。最终根据坦克朝向的不同调用不同的方法即可//限制移动碰到砖墙、钢板、海水等switch(Dir){caseDirection.Up:RealPosLimitMoveUp(RealPos);break;caseDirection.Down:RealPosLimitMoveDown(RealPos);break;caseDirection.Left:RealPosLimitMoveLeft(RealPos);break;caseDirection.Right:RealPosLimitMoveRight(RealPos);break;}