实现人形角色的攀爬

news/2024/12/21 23:01:43/文章来源:https://www.cnblogs.com/OwlCat/p/18403126

在Unity实现角色攀爬

前言

开放世界类型的游戏近年也热门起来了,自由攀爬也成了这一类游戏的一大特色。攀爬给了玩家更多探索路径的选择,也让地图设计有了更多思路。这次,我们就来尝试在Unity中制作一个人形角色的攀爬。

image

注:攀爬是一个角色完整动作系统的一部分,本文暂且抛开其它动作,也不涉及动画,仅针对攀爬逻辑的实现这一点。

主要实现

首先,我们要意识到,游戏中的攀爬行为已经与物理系统没有太大关系了。在攀爬时,角色实际上进入了一种“悬浮”状态,然后贴着墙面运动。攀爬系统要做好,就在于如何能让角色贴着墙面运动。

或许说到这,你脑海里已经想到了很多千奇百怪的攀爬面,但其实,任何攀爬面只要能抓住其法线,一切都好解决很多了:

  1. 先写一个辅助函数,将向量投影在某一法线所属平面(得到的就是那条深蓝色向量):

    image
    /// <summary>
    /// 获取向量在某一平面的投影
    /// </summary>
    /// <param name="vector">要投影的向量</param>
    /// <param name="planeNormal">平面的法线(需归一化)</param>
    /// <returns>在平面的归一化投影向量</returns>
    public static Vector3 GetProjectOnPlane(Vector3 vector, Vector3 planeNormal)
    {return (vector - planeNormal * Vector3.Dot(vector, planeNormal)).normalized;
    }
    
  2. 如果能获取攀爬面的法线,我们就可以将角色的运动方向转化为在攀爬面上的运动方向:

    var newXAxis = GetProjectOnPlane(xAxis, contactNormal);
    var newZAxis = GetProjectOnPlane(zAxis, contactNormal);
    

那现在的问题就在于如何准确获取攀爬面的法线?或许你会想到用射线检测,但遇到内角与外角的情况又该如何检测呢:

image image

难不成要换成其它形状的碰撞检测?其实没必要这么麻烦,我们完全可以利用角色自身的碰撞体接触来判断。大多数情况下,人形角色都是使用Capsule Collider(胶囊体碰撞盒),能较“均匀”地触碰接触面,比如在接触到内直角的情况下,将所有接触点的法线累加再求平均,得到的平均法线向量是接近45度,这是胶囊体曲面性质导致的。

private void OnCollisionEnter (Collision collision) 
{sensor.EvaluateCollision(collision);
}
private void OnCollisionStay(Collision collision)
{sensor.EvaluateCollision(collision);
}/// <summary>
/// 在OnCollisionEnter和OnCollisionStay中调用,用于获取接触到的碰撞有效信息
/// </summary>
/// <param name="other">接触的碰撞体</param>
public void EvaluateCollision(Collision collision)
{int layer = collision.gameObject.layer;for(int i = 0; i < collision.contactCount; ++i) //检查接触点类型并记录对应类型的法线{Vector3 normal = collision.GetContact(i).normal;float upDot = Vector3.Dot(upAxis, normal);//如果当前可以攀爬、攀爬面层级为可攀爬层级、攀爬面的倾斜角度未超过最大攀爬角度if(isAllowedClimb && ((1<<layer) & climbMask) != 0 && upDot >= minClimbDot){++climbContactCnt; //统计接触点数量,便于后续求平均climbNormal += normal; //累加攀爬法线,便于后续求平均lastClimbNormal = normal;}}
}/// <summary>
/// 检测攀爬并更新、归一化攀爬法线,攀爬时调用
/// </summary>
/// <returns>true为可攀爬,false为不可攀爬</returns>
public bool CheckClimb()
{if(IsClimbing){if(climbContactCnt > 1){climbNormal.Normalize();//如果处于裂缝中(四周攀爬面法线和为地面),就取检测到的最后一个面为攀爬面var upDot = Vector3.Dot(upAxis, climbNormal);if(upDot >= minGroundDot){climbNormal = lastClimbNormal;}}contactNormal = climbNormal;return true;}return false;
}

这里提两点:

  1. 为什么upDot >= minClimbDot可以用来判断是否更倾斜?
    在往期文章中,我有提到过:假设法线的长度都为1,可以发现当地面越来越陡峭时,法线在竖直方向上的投影,也就是它的cos值会越来越小,直到地面完全垂直(变成墙壁)时,这个值会变成0。所以,只要事先将「可攀爬的最大角度」的cos值算出,我们就能将角度的比较转为数值的比较。

    [SerializeField, Range(90, 180), Tooltip("最大攀爬角度")]
    private float maxClimbAngle = 140f;minClimbDot = Mathf.Cos(maxClimbAngle * Mathf.Deg2Rad);
    
    image
  2. 为什么要记录lastClimbNormal
    这是为了防止类似下图这种情况,角色接触面法线的平均为Vector3.zero,此时角色将爬不动,故将「最后接触到的那条法线」作为攀爬面法线。

    image

上述这些就已经能接近内角的问题了,但外角还需要一点额外处理——挤压,攀爬时向角色持续施加一个沿着法线向墙面的力:

float maxClimbAcceleration = 40f;//攀爬时的加速度
//用于攀爬外墙角时贴紧墙面
Velocity -= contactNormal * (0.9f * maxClimbAcceleration * Time.fixedDeltaTime);

取90%的攀爬运动的加速度作为这个力的大小,可以保证挤压力不让角色动弹不得。现在,内外角的攀爬就没有太大问题了(红色的是接触面法线):

image

额外调整

然而,事情并没有结束。攀爬时我们通常还会让角色始终面向攀爬面,这就需要我们在攀爬时适时旋转角色,这也不困难:

public void ClimbForward() //旋转以面向攀爬墙面
{if(sensor.climbNormal != Vector3.zero){var forwardQ = Quaternion.LookRotation(-climbNormal, upAxis);transform.rotation = forwardQ;}
}

而一旦这么做了,那么当你尝试攀爬以下形状的面,会发现角色频频抽搐、退出攀爬状态:

image

为什么会这样?我们来分析下这个过程:

image
  1. 角色沿着墙面向上爬,一切正常
  2. 当角色顶部接触到上斜面时,计算出的法线发生了改变,角色也要进行旋转以面向新法线
  3. 问题就发生在这里,角色是胶囊体,在旋转后可能就与墙面冲突了。而且旋转后接触墙面的区域也变了,法线又发生了变化,而法线一旦变化,角色又得旋转,而一旦旋转后……

不难看出,罪魁祸首其实是胶囊体(攀爬的实现逻辑不改变的话(。・ω・。))!胶囊体横向旋转时势必会影响接触区域,导致计算出的法线变化,从而带来一系列问题。除非你的角色是个球形的,这样,随便旋转都不会影响接触区域了……

image

是啊!不妨仅在攀爬时将角色的碰撞体进行「变形」,从胶囊体变为球体:

private void SetClimbCollider(bool isClimbing)
{if(isClimbing)//正在攀爬时,将胶囊体的高度设为0,就变球体了{playerCollider.height = 0;playerCollider.radius = climbColliderRadius;playerCollider.center = climbColliderCenter;}else //退出攀爬时,将参数还原以变回原本的胶囊体{playerCollider.height = colliderHeight;playerCollider.radius = colliderRadius;playerCollider.center = colliderCenter;}
}

除了将胶囊体高度设为0,我们还适度增加了胶囊体半径,以及将中心偏移,通常是改成能覆盖角色上半身的情况 攀爬时,腿真的不重要了

image

万事大吉了吗?还差一步,仍是旋转的问题。

image

一般的人形角色的根物体位置只是角色底部中心处,平时涉及的运动也是围绕这个点进行的,但如今我们想要角色能绕调整后的球形碰撞体的球心旋转,因为只是绕根位置旋转,很可能让角色失去碰撞接触(示意图中,将角色模型简化成了棒棒糖形状):

image

这就相当于将根物体位置改为球心了呀,这还有点麻烦,毕竟会干扰原本的运动逻辑。除非有什么巧妙的旋转策略,鄙人倒是有个想法,定然不是最好的,大家如果自己有思路,也可以跳过这段,代码如下:

image
//绕攀爬时的球心(攀爬时胶囊体会变成球体)旋转,以面向攀爬墙面
public void ClimbForward() 
{/*总体思路: 先绕根位置旋转以调整面朝的方向,但旋转点并不是球心只是这样旋转的话,必定会让球心偏离原来位置,所以要让playerTransform补回那段距离,以让球心回到旋转前的位置这样就实现了:既朝向了攀爬法线,球心位置又不改变 = 绕球心旋转*/if(sensor.climbNormal != Vector3.zero){var originCenter = playerTransform.position + playerTransform.up * playerCollider.center.y;var forwardQ = Quaternion.LookRotation(-sensor.climbNormal, sensor.upAxis);playerTransform.rotation = forwardQ;var newCenter = playerTransform.position + playerTransform.up * playerCollider.center.y;playerTransform.position += originCenter - newCenter;}
}

尾声

有关攀爬的核心实现大概就这些了,再次强调一遍,这是完整动作系统的一部分,我从自己实现的一个项目中剥离出来的,显得不太完整(因为完整的会涉及很多为了配合其它动作而设的一些变量,有点喧宾夺主了),本身只是一个思路分享,大伙有更多想法也可以分享出来呀~当然,有不满之处也可指出(。・・)ノ

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/794078.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LVDS(FPGA)

差分输入时钟缓冲器(IBUFDS)点击查看代码// IBUFDS: Differential Input Buffer// 7 Series// Xilinx HDL Language Template, version 2024.1IBUFDS #(.DIFF_TERM("FALSE"), // Differential Termination.IBUF_LOW_PWR("TRUE"), // L…

51nod 石子分配

可以发现步数限制把数轴变为了环。环之间不可以交换,环内相邻两点可以交换,然后我们只需要对每个环操作,最后累加。 对于环上的每个石子堆,我们需要将其石子数调整到均值 \(avg\)。因此,我们首先计算每个堆石子相对于 \(avg\) 的偏差,即 \(nowa[i] - avg\)。 因为相邻节点…

马哥教育C10网络安全课第四周作业2024_0831

网络安全C10-2024.8.31 作业: 1、安装burp并实现抓取HTTP站点的数据包(HTTPS站点暂时不要求) (1)安装Java 1.8.144; 设定操作系统环境变量 - JAVA_HOME jdk文件夹的绝对路径 例: JAVA_HOME=C:\Program Files\Java\jdk1.8.0_144 - CLASSPATH CLASSJPATH=.;%JAVA_HOME%\lib…

十年来(2015-2024)植物/农学领域有哪些学者戴上了杰青帽子?

2024年杰青建****议资助项目申请人名单(部分名单)(注:该表只是统计现公布的名单,欢迎留言补充) 2023年杰青建议资助项目申请人名单2022年杰青建议资助项目申请人名单2020年杰青建议资助项目申请人名单2019杰青建议资助项目申请人名单2018杰青建议资助项目申请人名单2017杰…

Nature Comm. | CoPheScan:一种考虑连锁不平衡的全表型组关联分析

分享一篇最近发表在NC的一篇文章:CoPheScan: phenome-wide association studies accounting for linkage disequilibrium。文章介绍了一种新的贝叶斯方法CoPheScan(Coloc adapted Phenome-wide Scan),用于在考虑连锁不平衡(LD)的情况下进行表型范围关联研究(Phenome-wid…

智能农业和精准农业中的计算机视觉:技术与应用

计算机视觉智能系统融合了计算机视觉、人工智能和机器学习技术,使机器能够模仿人类的视觉和认知能力做出决策,已广泛融入现代人类生活的各个层面。自 20 世纪后期以来,自动化的视觉系统已革新了各多个行业。鉴于世界人口的增长、可耕地的减少以及劳动力的短缺等问题,农业从…

Plant Com | 上海师范大学生科院解析杂草稻近代野化的进化机制

2024年8月19日,上海师范大学生命科学学院的研究团队在_Plant Communications在线发表了题为“Landrace introgression contributed to the recent feralization of weedy rice in East China”的论文。研究揭示了我国江浙沪地区稻田中的杂草稻都为近代野化起源,并均衍生于籼型…

PBJ | 钱前院士综述:野生稻——开启水稻育种的未来

PBJ近日在线发表了中国农业科学院和崖州湾国家实验室合作的题为“Wild rice: unlocking the future of rice breeding”的综述论文,文章系统梳理了稻属各物种间的系统进化关系、分类及地理分布,栽培稻育种中已鉴定的野生稻来源基因,分析了野生稻耐生物胁迫、非生物胁迫基因的…

图解:14个国家超级计算中心

相信大家都听过计算机、数据中心,超级计算机,但是超级计算中心是什么呢?听起来不明觉厉,但其实超级计算中心就是将多个超级计算机放在一个数据中心内。国家超级计算中心是由国家科技部批准成立的数据计算机构,是科技部下属事业单位。那么超级计算中心有什么作用呢?当然是…

综述 | 杂种优势形成机制和预测方法及其在猪生产中的应用与展望

杂种优势是指不同品种或品系间的杂交代在生长、发育、繁殖和抗病抗逆等性状优于亲代的现象。杂交能有效提高后代的生长发育性能、繁殖性能和抗病性,因此被广泛应用于农业生产。然而,现有杂种优势理论只能解释部分杂种优势现象;并且由于杂种优势理论不完善,现有的杂种优势预…

全球7大家禽育种公司的前世今生

我国白羽肉鸡的祖代全部是从国外引进的品种,属于快大型肉鸡,其生长速度快、产肉量多,适合工业化生产,作为肉鸡屠宰加工企业的主要原料,白羽肉鸡是我国肉鸡产品主导品种,目前我国白羽肉鸡主要包括艾拔益加(AA+)、罗斯(Ross)308、科宝(Cobb)、海波罗(Hybro)和哈伯德…