用Unity重现《空洞骑士》的苦痛之路(1)——人物控制篇
本篇难度:★★★☆☆
大家好。受苦受难的虫子们啊,我又来继续了。
紧接上一期的内容,这期主要讲解人物控制的代码。
代码逻辑块相对复杂,并且代码较多,本期将会挑选重点进行讲解,代码不会完全贴出(太长了),所以大部分方法都不是完整的,细节方面还请下载文章末尾的工程,打开查看。
本期相对于上篇难度巨幅提高,如果说上篇是normal那这篇就是very hard。食用时请注意别噎着。
移动核心逻辑(难)
为了让游戏中玩家的移动能够完全被开发者掌握,本工程并没有使用Unity的物理引擎来进行移动操作,而是手动模拟相应的物理特性并进行移动。
在2D游戏中,玩家的移动总是能够分解成2个方向上的位移。我们移动的逻辑实现也是这样。
首先获取到这一帧的移动速度,然后计算出2个方向上的位移,再计算出下一个位置是否能够进行移动。如果无法移动,就需要对下一帧该方向的移动逻辑进行修正后再实施移动。原理如下图:
其中关于位置修正,我们还得手动打Box射线来检测是否碰撞,并根据结果进行相应的操作。原理如下:
请注意:由于我们是先移动X轴,在移动Y轴,并没有进行线性的移动,会在特定情况下舍弃部分位移(即位置出现偏差)。但是由于我们的移动是每一帧进行的,此处的误差实际上可以忽略,强迫症患者可以考虑在这个方法基础上进行修改。
代码实现需要获取到当前帧的速度,并且根据速度计算出这帧玩家给个方向的位移,用于下面的计算。然后根据移动的方向,分开处理。
先处理左右方向的位移,然后使用box射线检测,判断下一帧是否会碰到碰撞体或者陷阱,如果是墙壁(碰撞体),还需要通过碰撞点的位置进行修正,达到满意的移动效果。同时根据需求,更新对应的动画状态。代码如下:
public Vector3 moveSpeed; //每一帧的移动速度
public Vector2 boxSize; //玩家碰撞盒的大小
public void CheckNextMove()
{
Vector3 moveDistance = moveSpeed * Time.deltaTime;//当前帧的移动位移
if (moveSpeed.x!= 0)//当左右速度有值时
{
RaycastHit2D lRHit2D = Physics2D.BoxCast(transform.position, boxSize, 0, Vector2.right * moveSpeed.x, 5.0f, playerLayerMask);
if (lRHit2D.collider != null)//如果当前方向上有碰撞体
{
float tempXVaule = (float)Math.Round(lRHit2D.point.x, 1); //取X轴方向的数值,并保留1位小数精度。防止由于精度产生鬼畜行为
Vector3 colliderPoint = new Vector3(tempXVaule, transform.position.y); //重新构建射线的碰撞点
float tempDistance = Vector3.Distance(colliderPoint, transform.position); //计算玩家与碰撞点的位置
if (tempDistance > (boxSize.x * 0.5f + distance)) //如果距离大于 碰撞盒子的高度的一半+最小地面距离
{
transform.position += new Vector3(moveDistance.x, 0, 0); //说明此时还能进行正常移动,不需要进行修正
}
else//如果距离小于 根据方向进行位移修正
{
float tempX = 0;//新的X轴的位置
if (moveSpeed.x> 0)
{
tempX = tempXVaule - boxSize.x * 0.5f - distance + 0.05f; //多加上0.05f的修正距离,防止出现由于精度问题产生的鬼畜行为
}
else
{
tempX = tempXVaule + boxSize.x * 0.5f + distance - 0.05f;
}
transform.position = new Vector3(tempX, transform.position.y, 0);//修改玩家的位置
}
}
else
{
transform.position += new Vector3(moveDistance.x, 0, 0);
}
}
}
同理,上下也是一样的。只不过需要考虑受到重力的情况。在不进行上下移动的时候,加上判断是否在地面的功能,用来决定是否添加重力。这可以通过朝地面打射线的方式进行判断。
代码如下:
public bool CheckIsGround()
{
RaycastHit2D hit2D = Physics2D.BoxCast(transform.position, boxSize, 0, Vector2.down,5f, playerLayerMask);
if (hit2D.collider != null)
{
float tempDistance = Vector3.Distance(transform.position, hit2D.point);
if (tempDistance > (boxSize.y * 0.5f + distance))//如果距离大于 碰撞盒子的高度的一半+最小地面距离
{
return false;
}
else
{
return true;
}
}
else
{
return false;
}
}
左右移动
完成了上面主要移动核心逻辑、并在Update函数调用后,接下来的内容就要稍微简单一些。
由于实际的移动是由别的函数来进行实现,那么左右移动函数就只需要根据玩家的按键输入,来更新玩家的速度即可。如下:
public void LRMove()
{
float h = Input.GetAxis("Horizontal");
moveSpeed.x = h * speed;//更新左右轴上的速度
//接下来更新各种动画状态
}
跳跃
在《空洞骑士》中,跳跃是根据按键的蓄力时长,来控制跳跃高度。如果我们需要实现这个功能,就需要在KeyDown事件中触发跳跃,key事件中进行蓄力,KeyUp事件中停止跳跃蓄力,跳跃状态取消,并更新动画。
代码如下:
public float jumpTime; //跳跃的最大蓄力时间
float timeJump; //跳跃当前的蓄力时间
public void Jump()
{
if (Input.GetKeyDown(KeyCode.Space))
{
jumpState = true; //进入跳跃状态
moveSpeed.y += jumpPower;//初始添加向上的力
timeJump = 0;//蓄力时间清零
}
else if (Input.GetKey(KeyCode.Space) && jumpCount<=2 && jumpState)
{
timeJump += Time.deltaTime;//蓄力时间增加
if (timeJump < jumpTime)
{
moveSpeed.y += jumpPower;//蓄力
}
}
else if (Input.GetKeyUp(KeyCode.Space))
{
jumpState = false;//退出跳跃状态
timeJump = 0;//蓄力时间清零
}
}
二段跳实现的方法原理也是一样,只是需要在KeyDown事件中,区分当前是二段跳还是一段跳,并分别进行蓄力操作即可。
暗影冲刺
在冲刺状态下,玩家不受到重力,且此时不会接受按键输入。于是我们需要声明2个变量分别用来控制是否获取按键输入,以及应用重力。并在冲刺开始的时候关闭对应的开关,给玩家一个固定的移动速度,在冲刺结束后重新打开对应的开关,速度回滚。
实现的代码如下:
public bool gravityEnable; //重力开关
public bool inputEnable; //接受输入开关 true 游戏接受按键输入 false不接受按键输入
public void SprintFunc()
{
if (Input.GetKeyDown(KeyCode.J) && isCanSprint)
{
StartCoroutine(SprintMove(sprintTime));//冲刺协程
}
}
IEnumerator SprintMove(float time)
{
inputEnable = false;
gravityEnable = false;//关闭按键输入,以及不在应用重力
moveSpeed.y = 0;//Y轴速度清零
isCanSprint = false;
if (nowDir == PlayDir.Left)
{
moveSpeed.x = 15*-1;
}
else
{
moveSpeed.x = 15;
}//根据方向施加速度
yield return new WaitForSeconds(time);//延迟time秒后执行
inputEnable = true;
gravityEnable = true;
isCanSprint = true;//状态回滚
}
超级冲刺原理同上,但是需要考虑到需要蓄力,并且该状态结束是由按键与移动进行控制的。所以我们在实现超级冲刺的时候,需要用两种方式来结束。
代码就不贴出了,欢迎参考工程食用。
爬墙切换
原版游戏里,在空中时碰到特定墙壁会进入到爬墙的状态。
条件是下一帧需要碰到墙壁,且距离地面有一定的高度才行。代码如下:
public void EnterClimpFunc(Vector3 rayPoint) //移动检测到下一帧碰到墙壁时调用
{
//设定碰到墙 且 从碰撞点往下 玩家碰撞盒子高度内 没有碰撞体 就可进入碰撞状态。
RaycastHit2D hit2D = Physics2D.BoxCast(rayPoint, boxSize, 0, Vector2.down, boxSize.y, playerLayerMask);
if (hit2D.collider != null)
{
Debug.Log("无法进入爬墙状态 "+ hit2D.collider.name);
}
else
{
playAnimator.SetTrigger("IsClimb");//动画切换
isClimb = true;
isCanSprint = true; //爬墙状态,冲刺重置
}
}
有3种方式可以退出爬墙状态:
1.玩家自己受重力下落,超出墙壁的范围。
2.玩家按下跳跃键退出。
3.玩家按下反方向移动键退出。
这里只提一下跳跃退出实现的原理。比较简单,就不贴出代码了。在按下跳跃键后朝着墙壁的反方向施加一个朝上的力,就可使玩家离开碰撞体,退出爬墙状态。
攻击交互
在《空洞骑士》中,攻击碰到特定的物体时,会有后座力的效果,而且会刷新玩家身上特定的技能状态。实现逻辑就是在攻击的时候,进行射线检测,并根据碰撞体的标签,以及攻击的方向,来决定状态的刷新,以及是否施加后坐力的效果。
在代码中的实现:
public void AttackFunc()
{
if (Input.GetKeyDown(KeyCode.K))//按下攻击键
{
CheckAckInteractive((int)nowDir);
}
}
public void CheckAckInteractive(int dir) //参数为攻击的方向
{
float distance = 1.8f; //射线的检测长度
RaycastHit2D hit2D = new RaycastHit2D();
Vector2 raySize = new Vector2(boxSize.x + 0.5f, boxSize.y); //扩大检测X轴范围
switch (dir)
{
case 1:
hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.left, distance, playerLayerMask);
break;
case 2:
hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.right, distance, playerLayerMask);
break;
case 3:
hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.up, distance, playerLayerMask);
break;
case 4:
hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.down, distance, playerLayerMask);
break;
}
if (hit2D.collider!=null)
{
if (hit2D.collider.gameObject.CompareTag("Trap")) //如果是陷阱就有后坐力
{
StartCoroutine(InteractiveMove(dir, 10)); //开启协程 施加后座力效果,并刷新状态
}
}
}
结语
完成了本期文章的内容后,我们的主角在动作方面已经完全满足了这个项目的要求。
接下来需要完善的就是发挥自己的创意,搭建快乐地图啦。而且在本期文章中,考虑到有的童鞋可能动画切换这块的想法跟我不一致,我有意删减了动画切换相关的代码。若是需要参考的话,欢迎点击文章末尾的下载链接下载后进行查看。(PS:我使用的是2017.4.17f1版本)
工程下载链接:https://pan.baidu.com/share/init?surl=wJTQmSup2EOOdGBYVmYvVw
提取码:etvn
相关链接,很(mai)重(mai)要(mai)
空洞骑士购买链接:http://link.zhihu.com/target=https%3A//store.steampowered.com/app/367520/Hollow_Knight/
用Unity重现《空洞骑士》的苦痛之路(1)——人物控制篇相关推荐
- 用Unity重现《空洞骑士》的苦痛之路(2)——地图篇
前言: 大家好.虫虫大冒险(?)新的一期又双叒叕来了. 本期文章主要讲解<空洞骑士中>的地图搭建相关的内容,并会写一个简单的相机脚本.除此之外,还会涉及到如何制作背景.产生异于玩家移动的动 ...
- 用Unity重现《空洞骑士》的苦痛之路(3)——地图篇
前言: 大家好.虫虫大冒险(?)新的一期又双叒叕来了. 本期文章主要讲解<空洞骑士中>的地图搭建相关的内容,并会写一个简单的相机脚本.除此之外,还会涉及到如何制作背景.产生异于玩家移动的动 ...
- 【Unity项目】仿《空洞骑士》项目
PS:本系列笔记将会记录我此次在北京学习Unity开发的总体过程,方便后期写总结,笔记为随缘更. 笔记内容均为 自己理解,不保证每个都对 本次笔记为记录大概2–3周左右小组项目的每日进度完成情况. 为 ...
- 【Unity从零开始制作空洞骑士】①制作人物的移动跳跃转向以及初始的动画制作
事情的起因: 首先我之前在b站的时候突然发现有个大佬说复刻了空洞骑士,点进去一看发现很多场景都福源道非常详细,当时我除了觉得大佬很强的同时也想自己试一下,而且当时对玩家血条设计等都很模糊,就想着问up ...
- 【Unity2D】实现有个性的复杂的血条UI(以空洞骑士的为例)
学习目标: 特别感谢b站两位up主的技术指导:改个名字好贵l哦的个人空间_哔哩哔哩_Bilibili改个名字好贵l哦,以后准备游戏开发的蒟蒻大学生;改个名字好贵l哦的主页.动态.视频.专栏.频道.收藏 ...
- 《空洞骑士》:我们为什么深爱这款玩起来看着像是自虐的游戏
距离<空洞骑士>在PC端发售已经过去整整三年的时间.被游戏和dlc折磨无数遍的玩家们,自去年看到续作<空洞骑士:丝之歌>的宣传片起,每天都在以近乎哭嚎的方式在社交网络上问什么时 ...
- 空洞骑士:简单开场场景搭建
进入这个篇章,我们就离开了新手保护区了,更广阔更高深的内容会越来越多,推掉重做的东西也会更多,我自己都不敢保证能做完QAQ(小声BB). 开场图片的渐入渐出 首先创建一个新的场景,并将其拖到Build ...
- 聊聊游戏:《空洞骑士》为什么好玩
之前闲聊崔轶提议增加"游戏"话题,刚好我最近沉迷了一段时间<空洞骑士>,就请崔轶过来深入聊聊<空洞骑士>为什么这么好玩. 如果你玩过<空洞骑士> ...
- 对空洞骑士核心系统的分析
我以空洞骑士这款游戏作为我的一个分析案例.这个游戏的核心驱动在我看来有三条,分别为挑战与探索,以及辅助的养成驱动,整个游戏的系统都是围绕着这三条驱动来构建的,该游戏的系统在我看来核心可以分为三个1.地 ...
最新文章
- Asp.net 后台添加CSS、JS、Meta标签
- 用git从github网站上下载代码的方法
- MySQL(1)数据库介绍,配置MySQL的tab补全
- IntelliJ IDEA中快捷键大全+出现的问题
- 一个顶级程序员要多久才能独自写完Win10代码?
- python自动化办公 51cto_聊聊 Python 办公自动化之一 Excel
- OpenStack运维(二):OpenStack计算节点的故障和维护
- 小米线刷一直显示flashing_小米空调质量怎么样 小米空调一晚上多少电 看完这篇你就有数了...
- oppok1刷原生android10,OPPOK1系统升级最新ColorOSV6刷机包(完整固件rom下载ColorOSV6安卓9.0)...
- 如何用 Python 画一个纸飞机?| 原力计划
- LOJ 2312(洛谷 3733) 「HAOI2017」八纵八横——线段树分治+线性基+bitset
- java+opencv+intellij idea实现人脸识别
- vlfeat matlab怎么用,VLFeat在matlab中的使用
- java 二叉树运用场景_java二叉树有什么作用?有哪些实际应用?
- 隐藏窗口任务栏图标的三种方法
- 2020抖音最新版去水印方法
- DDR MC DFI PHY
- 运维的升级打怪之路v2版
- 来自 Serenity 的 Java 8 的一些使用技巧
- MuMu模拟器进行adb操作
热门文章
- 橙光古风背景素材下载
- caffe搭建----Visual Studio 2015+CUDA8.0+CUDNN5配置Caffe-windows(BLVC)
- Python网络爬虫实战:根据天猫胸罩销售数据分析中国女性胸部大小分布
- 分子模拟的理论与实践_化工热力学领域最新译著适合工业应用的热力学模型:从经典与高级混合规则到缔合理论正式出版...
- 前端动态生成数组/获取数组对象中的最值
- QT控件通过setProperty设置属性显示内容
- android viewholder
- C语言输入月份,输出月份的英文名
- SIMATIC Net介绍
- C# 上传 下载 源代码