news 2026/9/7 17:15:22

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Unity】TankBattle联机坦克大战(五)坦克的核心功能(下)

更新日期:2026年8月27日。
项目源码:获取源码。

索引

  • 坦克的核心功能
    • 八、坦克移动碰撞检测
      • 1.获取坦克前方地块
        • ①.坦克完全处于道路中间
        • ②.坦克处于道路两边
        • ③.识别坦克位置
      • 2.与地块进行碰撞检测
        • ①.砖墙
        • ②.钢板
        • ③.海水
        • ④.玩家老巢
      • 3.碰撞后处理
        • ①.坦克完全处于道路中间
        • ②.坦克处于道路两边
      • 4.半边地块的碰撞检测
      • 5.限制向上移动

坦克的核心功能

本篇我们介绍坦克的核心功能实现,在上一篇的基础上,整个关卡地图已经创建完成,下一步便是创建坦克单位,实现游戏真正的核心玩法。

内容接上篇:【Unity】TankBattle联机坦克大战(四)坦克的核心功能(上)。

八、坦克移动碰撞检测

由于帧同步所必须的确定性,所以碰撞检测不能基于Unity的物理系统来实现,我们必须实现一套简单的碰撞检测逻辑,并保证其在不同设备、不同环境下保持确定性。

1.获取坦克前方地块

当坦克进行移动时,碰撞检测的本质也即是判断其能否抵达即将移动到的下一位置,而我们的坦克只能向前移动(向后移动时会调转车头,转变为向前移动),所以移动前,首先需要获取即将移动到的下一位置的地块,也即是坦克前方的地块。

注意:后续均以坦克向上移动为例。

//坦克前方位置(坦克中心坐标 + 坦克尺寸的一半)inttankFrontPos=RealPos.y+TB_UILevel.BlockSizeHalf;

获取坦克前方地块要根据坦克所处位置分两种情况进行处理:

  • 1.坦克完全处于道路中间:获取坦克前方的一个地块。
  • 2.坦克处于道路两边:获取坦克前方的两个地块(一个地块各碰到一半)。
①.坦克完全处于道路中间

这种情况处理起来比较简单,直接以坦克当前坐标为参数,计算出前方地块坐标,再以坐标获取地块即可:

//获取其前方地块TB_Blockfront=_uILevel.GetBlockByPos(pos.x,tankFrontPos);

这里要先实现一个能以坐标获取地块的通用方法:

/// <summary>/// 获取一个位置所在的地块/// </summary>publicTB_BlockGetBlockByPos(intposX,intposY){intx=(posX/BlockSize)-1;if(posX%BlockSize>0)x++;inty=(posY/BlockSize)-1;if(posY%BlockSize>0)y++;x=Math.Clamp(x,0,Rows-1);y=Math.Clamp(y,0,Cols-1);return_blocks[x,y];}
②.坦克处于道路两边

当坦克处于道路两边时,其位置左右横跨了两个地块,所以其前方的地块也应该分为左右两个:

//获取其前方左侧、右侧地块TB_BlockfrontLeft=_uILevel.GetBlockByPos(pos.x-TB_UILevel.BlockSizeHalf,tankFrontPos);TB_BlockfrontRight=_uILevel.GetBlockByPos(pos.x+TB_UILevel.BlockSizeHalf,tankFrontPos);
③.识别坦克位置

如何识别坦克的位置是前两种中的哪一种?只需如下的简单判断即可:

//坦克横坐标(向上移动时)除以地块尺寸能够除尽,则在垂直方向上完全处于道路中间if(pos.x%TB_UILevel.BlockSize==0){}//否则处于道路两边else{}

2.与地块进行碰撞检测

获取到了坦克前方的地块,接下来只需要进行碰撞检测即可,首先,我们依然得分类型进行处理。

①.砖墙

由于砖墙可拆分为4行*4列=16个组成片,所以碰撞检测最为精细,首先要检测最下面的一行(行号0)组成片是否与坦克产生碰撞(因为坦克是向上移动,所以最下面的一行理应最先碰到)。

某行砖墙组成片能够达成碰撞(对坦克产生阻挡排斥效果)的条件如下:

  • 1.该行砖墙组成片未被完全破坏;(比如还剩下两边的一块,也能产生阻挡效果,这在原版里经常被用作挡路机关
  • 2.坦克前方y坐标高于该行砖墙组成片的低坐标(向上移动);

挡路机关:

满足如上两个条件,则表明坦克碰到了该行砖墙组成片,应该被阻挡排斥,代码实现如下:

//!IsDestroyRow(0):行号0的整行组成片未被完全破坏//frontY > pieceY:坦克前方y坐标高于该行组成片的低坐标if(!IsDestroyRow(0)&&frontY>pieceY){//阻挡排斥:将该行组成片低坐标,赋予坦克前方y坐标,实现将坦克挡在后面的效果frontY=pieceY;}

检测某一行的组成片是否完全被销毁:

/// <summary>/// 指定一行的组成片是否完全被销毁(仅针对砖墙或钢板)/// </summary>/// <param name="row">行号</param>publicboolIsDestroyRow(introw){if(Type==BlockType.BrickWall){if(row>=0&&row<4){return_brickWallPieces[0,row].IsDestroy&&_brickWallPieces[1,row].IsDestroy&&_brickWallPieces[2,row].IsDestroy&&_brickWallPieces[3,row].IsDestroy;}}elseif(Type==BlockType.SteelPlate){if(row>=0&&row<2){return_steelPlatePieces[0,row].IsDestroy&&_steelPlatePieces[1,row].IsDestroy;}}returnfalse;}

如果第一行砖墙组成片未产生碰撞,则依次判断后面三行:

//判断第一行if(!IsDestroyRow(0)&&frontY>pieceY){}//判断第二行(第二行的低坐标 = 第一行低坐标 + 地块尺寸的四分之一)elseif(!IsDestroyRow(1)&&frontY>(pieceY+TB_UILevel.BlockSizeQuarter)){}//判断第三行(第三行的低坐标 = 第一行低坐标 + 地块尺寸的二分之一)elseif(!IsDestroyRow(2)&&frontY>(pieceY+TB_UILevel.BlockSizeHalf)){}//判断第四行(第四行的低坐标 = 第一行低坐标 + 地块尺寸的四分之三)elseif(!IsDestroyRow(3)&&frontY>(pieceY+TB_UILevel.BlockSizeHalf+TB_UILevel.BlockSizeQuarter)){}

完事后,这一块砖墙的碰撞检测才算完成。

②.钢板

钢板与砖墙的碰撞检测类似,只不过钢板的组成片为2行*2列=4个,所以只需检测两行:

//判断第一行if(!IsDestroyRow(0)&&frontY>pieceY){}//判断第二行(第二行的低坐标 = 第一行低坐标 + 地块尺寸的二分之一)elseif(!IsDestroyRow(1)&&frontY>(pieceY+TB_UILevel.BlockSizeHalf)){}
③.海水

海水也能产生阻挡效果,其是整个地块生效,所以处理极其简单:

//如果碰撞到的地块为海水elseif(Type==BlockType.Seawater){//则直接阻挡排斥:将该地块低坐标,赋予坦克前方y坐标frontY=pieceY;}
④.玩家老巢

玩家老巢也能产生阻挡效果,其是整个地块生效,所以处理方式与海水一致:

//如果碰撞到的地块为玩家老巢elseif(Type==BlockType.Lair){frontY=pieceY;}

3.碰撞后处理

碰撞后的处理也应分两种情况。

①.坦克完全处于道路中间

这种情况只需将碰撞产生的排斥坐标赋予坦克即可:

//获取其前方地块TB_Blockfront=_uILevel.GetBlockByPos(pos.x,tankFrontPos);//与地块碰撞检测,如果产生碰撞,则返回排斥后坐标pos.y=front.TankCollideBlockUp(tankFrontPos,0);
②.坦克处于道路两边

这种情况需要判断坦克前方的两个地块,以其中的排斥坐标较小值作用到坦克上(因为我们处理的是向上移动,较小的坐标意味着越靠下,所以其越先阻挡坦克的前进):

//获取其前方左侧、右侧地块TB_BlockfrontLeft=_uILevel.GetBlockByPos(pos.x-TB_UILevel.BlockSizeHalf,tankFrontPos);TB_BlockfrontRight=_uILevel.GetBlockByPos(pos.x+TB_UILevel.BlockSizeHalf,tankFrontPos);//先判断左侧地块intleftPos=frontLeft.TankCollideBlockUp(tankFrontPos,1);//再判断右侧地块intrightPos=frontRight.TankCollideBlockUp(tankFrontPos,-1);//左侧和右侧取最小值pos.y=Mathf.Min(leftPos,rightPos);

如下图,左侧地块的排斥坐标较小,其产生的阻挡效果才生效:

4.半边地块的碰撞检测

如上,坦克处于道路两边时需要检测地块的一半(左半边或右半边)是否产生碰撞,所以,我们必须为碰撞检测方法增加一个参数pos

/// <summary>/// 坦克向上移动时碰撞到地块/// </summary>/// <param name="y">坦克前方位置的y坐标</param>/// <param name="pos">碰撞位置(-1:碰到左半边,0:碰到整个地块,1:碰到右半边)</param>/// <returns>被地块排斥后的新y坐标</returns>publicintTankCollideBlockUp(inty,intpos)

当仅检测半边地块的碰撞时,也只需检测半边组成片是否被销毁:

//!IsDestroyRowHalf(0, -1):行号0的左半行组成片未被完全破坏if(!IsDestroyRowHalf(0,-1)&&frontY>pieceY){frontY=pieceY;}

5.限制向上移动

基于上文的代码基础,我们将其统一为了一个限制坦克向上移动的方法:

/// <summary>/// 限制坦克向上移动(碰到砖墙、钢板、海水等)/// </summary>privateVector2IntLimitMoveUp(Vector2Intpos){//坦克前方位置inttankFrontPos=pos.y+TB_UILevel.BlockSizeHalf;pos.y=tankFrontPos;//坦克位置在垂直方向上处于两个地块之间if(pos.x%TB_UILevel.BlockSize==0){//获取其前方左侧、右侧地块TB_BlockfrontLeft=_uILevel.GetBlockByPos(pos.x-TB_UILevel.BlockSizeHalf,tankFrontPos);TB_BlockfrontRight=_uILevel.GetBlockByPos(pos.x+TB_UILevel.BlockSizeHalf,tankFrontPos);//先判断左侧地块intleftPos=frontLeft.TankCollideBlockUp(tankFrontPos,1);//再判断右侧地块intrightPos=frontRight.TankCollideBlockUp(tankFrontPos,-1);//左侧和右侧取最小值pos.y=Mathf.Min(leftPos,rightPos);}//坦克位置在垂直方向上处于一个地块上else{//获取其前方地块TB_Blockfront=_uILevel.GetBlockByPos(pos.x,tankFrontPos);//碰到整个地块pos.y=front.TankCollideBlockUp(tankFrontPos,0);}//坦克位置回到中心pos.y-=TB_UILevel.BlockSizeHalf;returnpos;}

同理,向下移动向左移动向右移动等都需要完全按照本文的碰撞检测流程走一遍,只是坐标与方向上存在不同,原理是一致的,所以在这里我就不再赘述了。

最终,根据坦克朝向的不同,调用不同的方法即可:

//限制移动(碰到砖墙、钢板、海水等)switch(Dir){caseDirection.Up:RealPos=LimitMoveUp(RealPos);break;caseDirection.Down:RealPos=LimitMoveDown(RealPos);break;caseDirection.Left:RealPos=LimitMoveLeft(RealPos);break;caseDirection.Right:RealPos=LimitMoveRight(RealPos);break;}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 19:21:35

MySQL 安装配置(完整教程)

文章目录* 一、MySQL 简介* 二、下载 MySQL* 三、安装 MySQL* 四、配置环境变量* 五、配置 MySQL* * 5.1 初始化 MySQL * 5.2 启动 MySQL 服务* 六、修改 MySQL 密码* 七、卸载 MySQL* 八、结语一、MySQL 简介----------MySQL 是一款广泛使用的开源关系型数据库管理系统&#x…

作者头像 李华
网站建设 2026/9/2 13:51:04

中国人口出生率再次回落,新生男孩仍比女孩多

百度首页 设备学院 中国人口出生率再次回落,新生男孩仍比女孩多 第一财经 2026-08-28 14:56第一财经官方账号 已关注 中国“老龄少子化”趋势进一步深化。 据最新官方数据,2025年中国生育率回落,与之并存的则是65岁及以上高龄老人占比的同比增幅扩大。为此,政府有关…

作者头像 李华
网站建设 2026/9/2 13:51:09

为什么要在 markdownToHtml 出口统一压掉标签间空白

为什么要在 markdownToHtml 出口统一压掉标签间空白 先说结论&#xff1a;如果多个平台共用同一条 Markdown 转 HTML 渲染链&#xff0c;最稳的修法通常不是在单个平台适配器里补丁&#xff0c;而是把结构噪声在共享出口一次清干净。 这次 OmniPost 修的就是一个很典型的共享…

作者头像 李华
网站建设 2026/9/2 13:50:03

为什么 TCP 挥手需要有 TIME_WAIT 状态?

TIME_WAIT 是 TCP 四次挥手过程中&#xff0c;主动关闭连接的一方进入的状态。它的存在至关重要&#xff0c;主要为了解决以下几个核心问题&#xff1a;一、TIME_WAIT 状态的核心作用 1. 确保最后的 ACK 能够到达&#xff08;防止旧连接数据混淆&#xff09; 主动关闭方发送最后…

作者头像 李华
网站建设 2026/9/2 13:48:56

Dify分隔符功能

✅ Dify分隔符功能&#x1f3af; 问题理解当使用Dify上传CSV时&#xff0c;如果使用\n\n或.pdf等常见字符作为分段标识符&#xff0c;会导致chunk被错误切割&#xff0c;破坏元数据的完整性。&#x1f527; 解决方案在每个chunk的末尾添加一个唯一的、不会出现在正文中的特殊分…

作者头像 李华
网站建设 2026/9/2 18:27:53

模型可解释性评测实战:从静态数据到演化数据的完整方案

模型可解释性最近成了大模型应用落地时绕不开的话题&#xff0c;但很多人把注意力放在“怎么生成解释”上&#xff0c;忽略了更关键的问题&#xff1a;你怎么知道这个解释是可靠的&#xff1f; 一个典型的场景是&#xff1a;团队给风控模型接了 SHAP 解释&#xff0c;业务人员…

作者头像 李华