【开发分享】竖版动作跑酷——《峭壁逃亡》

#【开发分享】竖版动作跑酷——《峭壁逃亡》

大家好!本篇主要分享竖版跑酷动作类游戏——《峭壁逃亡》的开发,游戏模拟了悬崖攀登的过程,游戏的目标是尽可能攀到最高的高度,玩法如上面动画所示:

玩家角色会以一只手作为轴心旋转,当另一只手接近上方石头时,看准时机触屏,就可以使角色抓住新的石头,如果点击时游戏角色的手距离石头不够近,角色会掉下悬崖,游戏失败。

同时玩家不能在一个石头上停留太长时间,因为在下方还有水位追赶,如果水位淹到头的位置,同样视为游戏失败。

在项目实际开发时,经过分析归纳了以下两个主要的问题:

  1. 如何生成无尽关卡数据和处理画面表现层

  2. 角色抓取石头和表现细节方面的问题:

    角色坠崖,落水,溺亡…等

一. 生成无尽关卡

既然是无尽关卡,和所有无尽跑酷游戏一样,关卡数据都是在游戏过程中分阶段生成的,换一种说法,就是

根据游戏角色当前所处的位置,预先生成下一可视范围的数据,防止玩着玩着关卡就断片了…

不过在说明关卡数据生成前,我们先确定一个更基础的问题:

在游戏角色攀爬过程中,到底是让角色运动?还是让关卡背景运动?

稍微思考一下,好像不管是哪种情况,都需要去处理拼接背景在视觉上铺满屏幕的问题。

这里我们采用了一种方法规避背景拼接问题:

1,将关卡分为背景和前景,背景只有一张图,始终不移动

2,前景节点上挂实时生成的石头及其它关卡装饰物,并且游戏角色也在前景节点下移动

3,滚屏时,只需要移动前景节点

定下这种节点结构后,滚屏的实现就很方便了,代码如下

//滚屏(在玩家抓取新的石头后调用)
let oldPos = Game.instance.stonesNode.parent.getPosition();
let newXPos = 330 - Game.instance.levelStones[i].xpos;
let newYPos = AppMain.instance.screenHalfHeight - Game.instance.levelStones[i].ypos;
                    
let moveAction = cc.moveTo(0.3, cc.v2(newXPos, newYPos));
Game.instance.stonesNode.parent.runAction(moveAction);                    

接下来说明一下关卡生成的做法,为了让关卡的前景丰富一些,除了用于攀爬必要的石头,我们还准备了图腾,斑点,条纹,植物这几种装饰物,关卡算法的核心是

做一个比可视范围稍大的矩形区域,回收已经超出矩形区下方的场景节点,同时从对象池中取空闲节点生成上方区域

这里我们结合了对象池使用,直接用的cc引擎提供的NodePool, 这样不管玩家攀到天荒地老,内存中的节点数量都是固定的



二. 角色

游戏角色为了制作不同状态的动画动作,采用DragonBones制作,图片素材也会省


两张角色图在压缩后仅15K

角色抓取石头判断:

角色两只手的Bone是在角色Local空间下,因此我们始终需要转换到World空间,再和目标石头做距离判断

//LHand的世界坐标
lHandWorldPos: 
{
	visible: false,
    type: cc.Vec2,
    get: function ()
    {
                
    	let localPoint = cc.v2(this.lHandBone.global.x, -this.lHandBone.global.y);
        return this.armature.display.convertToWorldSpace(localPoint);
               
    }
 },
     
 //RHand 同理...

只要角色的HandBone和石头在同一坐标系下,那么抓取判断就好计算了,无非是两个cc.Vec2 的distance

let dis = lHandPos.sub(cc.v2(Game.instance.levelStones[i].xpos + Game.instance.stonesNode.parent.x,  Game.instance.levelStones[i].ypos + Game.instance.stonesNode.parent.y)).mag();                
             
if (dis <= holdDis)
{
     //完美抓取判断
     if (dis <= Game.instance.perfectHoldDis)
     {                        
         Game.instance.perfectHold();
     }
     else
         //普通抓取
         
     // 角色状态变化,动作切换,滚屏等其它处理...
}
else
{
	//抓取失败,GameOver      
}

水模拟:

水的模拟只需要两张不同波浪素材


场景中拼接如下,注意设置Sprite 的Pivot为底对齐,这样水位上涨只需要改变节点Height,

然后在Update中采用Position衔接方式模拟水体是无缝波动的

update(dt)
{
	if (this.waveDir === -1) 
    {
    	this.node.x -= this.waveSpeed * dt;
        if (this.node.x <= -AppMain.instance.designResolutionWidth)
        	this.node.x = 0;
    }
    else {
        this.node.x += this.waveSpeed * dt;
        if (this.node.x >= 0)
            this.node.x = -AppMain.instance.designResolutionWidth;
    }

    if (AppMain.instance.gameStarted) {
        for (let i = 0; i < this.node.childrenCount; i++) {
            this.node.children[i].height += this.riseSpeed * dt;
        }

        let sceneNode = Game.instance.stonesNode.parent;
        this.node.y = sceneNode.y;
    }

},

作者简介:

肖尧,从事游戏 前端/后端/3D引擎开发多年

前盛大锦天主程,前成都网龙研发负责人,高级架构师

现任休闲游戏公司H5技术总监,未来将持续专注于基于H5的泛娱乐/教育/传媒/工具等产品的研究与开发。

微信/QQ : 1611471

10赞

做得真好,謝謝分享~

顶一个

效果还可以,就是玩法抄的太明显了,国内都喜欢找热门游戏抄吗。

mark

请问一下,您的龙骨压缩是怎么做的:grinning:

单纯的压缩png图吧

这样做,并不能大幅度的降低draw call吧

你好,文中提到的压缩,主要是指使用龙骨的关键帧动画相对于序列帧动画更省资源
使用了龙骨后,又把PNG纹理压缩了一遍

mark