生成落脚点
-
第一个落脚点生成在原点位置,之后的落脚点生成位置则是在前一个落脚点位置基础上进行偏移。
-
落脚点的角度在30或150度随机生成,30度表示在角色右边,150度则是在角色左边。
-
下一个落脚点距离上一个落脚点的距离是随机的,例如定义在90至200像素值之间随机数值。
typescript
generateFoothold(first = false){
// 生成随机落脚点
const footholdKey = Object.keys(this.gameConfig['footholds'])[randomRangeInt(0, Object.keys(this.gameConfig['footholds']).length)]
const footholdNode: Node = instantiate(this.prefabUrlMap[this.gameConfig['footholds'][footholdKey]['prefabUrl']])
const imgNode = footholdNode.getChildByPath('Img')
// 为了让动画看着流畅
const imgInitPos = imgNode.getPosition()
imgNode.getComponent(UIOpacity).opacity = 0
imgNode.setPosition(imgNode.getPosition().add3f(0, 80, 0))
// 往前插入节点,让覆盖顺序正确
this.footholdsNode.insertChild(footholdNode, 0)
if (first){
footholdNode.setWorldPosition(Vec3.ZERO)
}else{
const deg = this.gameConfig['config']['footholdDeg'][randomRangeInt(0, Object.keys(this.gameConfig['config']['footholdDeg']).length)]
const distance = randomRange(this.gameConfig['config']['footholdMinDistance'], this.gameConfig['config']['footholdMaxDistance'])
const direction = MyUtil.degreesToVector(deg)
// 新的落脚点位置需要在上一个落脚地位置基础上
footholdNode.setWorldPosition(this.nowFootholdNode.getWorldPosition().add3f(
direction.x * distance,
direction.y * distance,
0
))
}
// 生成动画
tween(imgNode).parallel(
tween(imgNode.getComponent(UIOpacity)).to(0.2, {opacity: 255}),
tween(imgNode).to(0.2, {position: imgInitPos})
).start()
return footholdNode
}
角色
- 角色设置在原点位置,和第一落脚点位置相同。
角色蓄力移动位置
-
定义最大跳跃距离值如900,定义最多蓄力时间如3秒,表示屏幕长按3秒让角色移动到900像素值,实现蓄力过多飞出屏幕的情况。
-
监听屏幕触摸事件:记录触摸开始时间。
-
监听屏幕触摸结束事件:获取当前时间 - 屏幕开始时间 = 蓄力时间;蓄力时间 / 最多蓄力时间 * 最大跳跃距离 = 蓄力移动的距离;角色当前位置 + 蓄力移动距离 * 下个落脚点中心点方向 = 目标位置。
typescript
const touchMaxTime = this.gameConfig['config']['touchMaxTime']
const ratio = this.pressTime < touchMaxTime ? this.pressTime / touchMaxTime : 1
const distance = this.gameConfig['config']['jumpMaxDistance'] * ratio
const rolePos = this.roleNode.getWorldPosition()
// 计算方向需要朝着下一个落脚点中心位置(为了跳的离中心越近得2分)
const direction = this.nextFootholdNode.getWorldPosition().subtract(rolePos).normalize()
const targetPos = new Vec3(
rolePos.x + direction.x * distance,
rolePos.y + direction.y * distance,
0
)
角色跳跃动画
- 有了目标位置后,通过游戏引擎的tween这个api函数可实现动画,让角色从当前位置往目标位置跳跃效果。
typescript
const jumpAniRunTime = ratio < 0.1 ? 0.25 : 0.35
const upTween = tween(this.roleImgNode)
.to(jumpAniRunTime / 2, {position: this.roleImgInitPos.clone().add3f(0, 700, 0).multiplyScalar(ratio)})
.to(jumpAniRunTime / 2, {position: this.roleImgInitPos})
-
上面这段代码表示:跳跃动画的时长根据蓄力时间比例来决定,如果是蓄力很短跳跃动画时长是0.25秒,否则是0.35秒完成跳跃动画;在中途让角色往上移动x像素模拟跳跃过程。
-
角色除了跳跃动画外,同时还有前空翻动画,要根据跳台在角色的左边还是右边,来决定角色是顺指针还是逆时针旋转,接着通过tween的parallel来同时执行两个动画,即可实现跳跃动画。
跳跃后是成功还是失败
-
当跳跃动画完成后,进入跳跃成功还是失败的逻辑,由于角色每次是朝着落脚点中心处的方向进行跳跃,所以可以通过角色距离落脚点中心的距离进行判断。
-
要依次定义各种类型落脚点的半径大小,角色位置 - 落脚点位置 = 落脚点到角色的向量,通过计算向量的模拿到落脚点到角色的距离,判断是否在落脚地定义的半径大小范围内,就知道是否跳跃成功了。
typescript
const footholdConfig = this.gameConfig['footholds'][this.nextFootholdNode.name]
const nowDistance = Vec3.distance(this.roleNode.getWorldPosition(), this.nowFootholdNode.getWorldPosition())
const nowFootHoldRadius = this.gameConfig['footholds'][this.nowFootholdNode.name]['radius']
const nextDistance = Vec3.distance(this.roleNode.getWorldPosition(), this.nextFootholdNode.getWorldPosition())
const nextFootHoldRadius = this.gameConfig['footholds'][this.nextFootholdNode.name]['radius']
// 跳到下一个
if (nextDistance <= nextFootHoldRadius){
...
// 生成下一个落脚点
this.nowFootholdNode = this.nextFootholdNode
this.nextFootholdNode = this.generateFoothold()
// 视角
this.viewUpdate().then(()=>{
...
})
}
// 原地挪
else if (nowDistance <= nowFootHoldRadius){
...
}
// 掉落,游戏结束
else{
...
}
- 注意:此方式判断,要求落脚点的形状是个圆形或正方形。
视角移动
-
角色跳跃完成后,要移动摄像头的位置,让摄像头移动在角色的上方,让角色在视角的中间偏下方显示。
-
由于小游戏里不只有游戏元素还有UI元素显示,所以当只有一个Canvas时,移动摄像头会导致UI显示出现问题,因此可以使用两个Canvas进行覆盖显示,把UI元素抽离到另一个Canvas里,覆盖在游戏Canvas上方。
typescript
viewUpdate(){
const direction = this.nextFootholdNode.getWorldPosition().subtract(this.nowFootholdNode.getWorldPosition()).normalize()
return new Promise((resolve, reject)=>{
tween(this.gameCameraNode.getWorldPosition()).to(
0.4,
// 相机移动是相反的运动(镜头往上移动,相对物体是显示向下运动)
this.roleNode.getWorldPosition().add3f(0, 20, 0).add(direction.clone().multiplyScalar(80)),
{
easing: "quadOut",
onUpdate : (target: Vec3, ratio:number)=>{
// 实时修改摄像机、背景位置
this.gameCameraNode.setWorldPosition(target)
this.bgNode.setWorldPosition(target)
},
onComplete: (target?: Vec3) => {
resolve(0)
},
}
).start()
})
}
得分,精准跳跃
-
跳跃成功后,获取落脚点配置里的自定义分数,进行得分累计,或只是简单的+1分。
-
跳跃成功后有落脚点到角色的距离,判断此距离如果小于3,那么就是精准跳跃,获取落脚点配置里的自定义精准跳跃分数进行得分累计。
其他动画
-
蓄力动画:在触摸开始的回调里,还需让角色开始持续压扁,同时跳台也被压扁,使用tween实现动画,动画时间为最大蓄力时间,并且在触摸结束回调里还需停止动画。
-
蓄力回弹动画:在触摸结束回调里,和跳跃动画同时执行,让角色和跳台恢复压扁前的样子。
-
...
蓄力粒子效果
-
在屏幕按压进行跳跃蓄力时,角色那会有许多圆白粒朝角色脚底汇聚。
-
可以使用游戏引擎里的ParticleSystem2D组件去实现,界面配置好想要的粒子效果后,在触摸开始回调里只需播放粒子效果,在触摸结束回调里停止粒子效果即可。
得分动画,精准跳跃动画
-
跳跃成功后,角色头顶会冒出一个分数,如+1效果。
-
制作一个得分预制体,在跳跃成功后实例它并修改分数UI,接着使用tween来实现分数缓缓向上移动的效果。
-
精准跳跃则是在分数基础上增加了一个圆环扩散效果。圆环图片放在角色节点上方,当精准跳跃后,让它显示并使用tween来实现慢慢放大再消失效果即可。
获取完整小游戏源码
-
以上内容是讲述了2D版本的跳一跳如何实现,你如果是为了给别人玩、为了上架小游戏平台,那么就需要使用一个游戏引擎去开发小游戏。
-
在 Cocos商城 里,搜索 zezhou222 ,获取对应小游戏源码学习!