Egret 小游戏实战教程 跳一跳(搬运二)

摘要:
开始页面的逻辑由于开始页面比较单调,只有一个开始游戏的按钮,所以我们只需要在按钮上添加一个事件监听即可具体逻辑就是当触摸事件发生时,我们将把SceneGame添加到舞台中,同时把BeginScene从舞台中移除SceneBegin.ts代码如下:publicbeginBtn:eui.Button;privateinit(){//这里的once其实就是addEventListner的意思,只不过它只

开始页面的逻辑

由于开始页面比较单调,只有一个开始游戏的按钮,所以我们只需要在按钮上添加一个事件监听即可

具体逻辑就是当触摸事件发生时,我们将把 SceneGame添加到舞台中,同时把 BeginScene从舞台中移除

SceneBegin.ts 代码如下:

publicbeginBtn:eui.Button;
privateinit() {
    //这里的 once 其实就是 addEventListner 的意思,只不过它只监听一次
    this.beginBtn.once(egret.TouchEvent.TOUCH_TAP, this.start, this);
}
privatestart() {
    //在舞台中添加游戏场景
    this.parent.addChild(newSceneGame());
    //在舞台中移除初始场景
    this.parent.removeChild(this);
}

游戏页面的逻辑

现在我们点击开始按钮就能够跳到游戏界面,接下来就是高大上的游戏逻辑了,也是本文的精髓所在,撸起袖子加油敲吧!

变量声明

这里要在 SceneGame.ts 中声明一堆变量,具体可以去看文章末尾的源码,就不全部复制过来了

//SceneGame.ts 中主要的变量声明
//当前的盒子(最新出现的盒子,就是准备要跳过去的目标盒子)
privatecurrentBlock: eui.Image;
//下一个的盒子方向(1向右,-1向左)
public direction: number = 1;
//tanθ 角度值(可自己微调),和 direction 配合计算出下一个盒子的坐标
public tanAngle: number = 0.556047197640118;
//随机盒子的最大最小(水平)距离
private minDistance = 220;
private maxDistance = 320;
//跳的距离(也就是根据你按压时间算出来的),这里指的是水平方向上的距离
public jumpDistance: number = 0;
//左侧跳跃点(固定的,可自己微调)
private leftOrigin = { "x": 180, "y": 350};
//右侧跳跃点(固定的,可自己微调)
private rightOrigin = { "x": 505, "y": 350 };

初始化界面

首先我们要看下游戏的初始界面长什么样,才知道 init函数里面写什么:

Egret 小游戏实战教程 跳一跳(搬运二)第1张

从上图中可以看出我们要做的就是生成方块,然后设置小人和方块的位置即可

要注意第一个方块和小人的位置是固定的,在左下方;默认第一次起跳的方向是右边,第二个方块也是在右边

为什么要固定初始位置呢,个人觉得主要还是方便吧,省的你再去判断计算,下文会再解释一波

你也可以看一下正版的微信跳一跳小游戏,它的第一个位置和方向也是固定的

ok,我们先简要看下一个方块是如何生成并添加到舞台上(重复的东西我们一般会写成一个类或者方法,这里写的是方法)

其实就是贴图,好比我们用 new Image()一样,再设置 src等属性即可,具体请看下面代码:

//创建一个方块
privatecreateBlock(): eui.Image {✌
    //随机背景图
    let n = Math.floor(Math.random() * this.blockSourceNames.length);
    //实例化并添加到舞台中
    let blockNode = neweui.Image();
    blockNode.source = this.blockSourceNames[n];
    this.blockPanel.addChild(blockNode);
    //设置方块的锚点(之前说过的不是图片的中心点,而是图中盒子的中心点)
    blockNode.anchorOffsetX = 222;
    blockNode.anchorOffsetY = 78;
    blockNode.touchEnabled = false;
    //把新创建的方块添加进入 blockArr 里,统一管理
    this.blockArr.push(blockNode);
    returnblockNode;
}
//添加一个方块并设置 xy 值
privateaddBlock() {
    //创建一个方块
    let blockNode = this.createBlock();
    //随机水平位置(在最大最小值之间的一个数,毕竟屏幕就那么大)
    let distance = this.minDistance + Math.random() * (this.maxDistance - this.minDistance);
    if (this.direction > 0) { //向右跳
        blockNode.x = this.currentBlock.x +distance;
        blockNode.y = this.currentBlock.y - distance * this.tanAngle;
    } else { //向左跳
        blockNode.x = this.currentBlock.x -distance;
        blockNode.y = this.currentBlock.y - distance * this.tanAngle;
    }
    this.currentBlock =blockNode;
}

ok,现在我们知道了怎么创建方块,接下来就看看 init函数里面的代码吧,瞅瞅初始化的时候都做了啥:

//SceneGame.ts
privateinit() {
    //所有盒子资源
    this.blockSourceNames = ["block1_png", "block2_png", "block3_png"];
    //加载按下和跳跃的声音
    this.pushVoice = RES.getRes('push_mp3');
    this.jumpVoice = RES.getRes('jump_mp3');
    //初始化场景(方块和小人)
    this.initBlock();
    //添加触摸事件
    this.blockPanel.touchEnabled = true;
    this.blockPanel.addEventListener(egret.TouchEvent.TOUCH_BEGIN, this.onTapDown, this);
    this.blockPanel.addEventListener(egret.TouchEvent.TOUCH_END, this.onTapUp, this);
    //心跳计时器(目的:计算按的时长,推算出跳的距离)
    egret.startTick(this.computeDistance, this);
}
privateinitBlock() {
    //初始化第一个方块,并设置相关的属性(主要就是在舞台中的位置也就是xy值)
    this.currentBlock = this.createBlock();
    this.currentBlock.x = this.leftOrigin.x;
    this.currentBlock.y = this.stage.stageHeight - this.leftOrigin.y;
    this.blockPanel.addChild(this.currentBlock);
    //初始化小人(小人的锚点在底部的中间)
    this.player.y = this.currentBlock.y;
    this.player.x = this.currentBlock.x;
    this.player.anchorOffsetX = this.player.width / 2;
    this.player.anchorOffsetY = this.player.height - 20;
    this.blockPanel.addChild(this.player);
    //初始化得分
    this.score = 0;
    this.scoreLabel.text = this.score.toString();
    this.blockPanel.addChild(this.scoreLabel);
    //初始化方向
    this.direction = 1;
    //添加下一个盒子
    this.addBlock();
}

上面的代码注释应该都写得挺清楚了,我们主要讲一下其中的难点egret.startTick(this.computeDistance, this)

startTick 这个 api 将会以 60 帧速率来调用this.computDistance这个方法,不明白?没关系,假想成 setInterval就好了。

this.computDistance 这个方法的主要目的就是通过按压时间来计算出跳跃的水平距离,看下面的代码应该不难理解:

//SceneGame.ts
//这个函数需要返回布尔值(规定),具体还不是很清楚它的作用,但不影响我们写代码
privatecomputeDistance(timeStamp:number):boolean {
    //timeStamp 是一个自增的时间(执行到当前所逝去的时间,比如0,500,1000...,单位 ms)
    let now =timeStamp;
    let time = this.time;
    let pass = now -time;
    pass /= 1000;
    if (this.isReadyJump) {
        //通过按压时间(就是 s = vt)来计算出跳的距离(这里指的是水平位移)
        this.jumpDistance += 300 * pass; //300 是调试出来的参数,可自行更改
}
    this.time =now;
    return true;
}

其实上面的内容都是铺垫,下面才正式开始写游戏部分的逻辑,深吸一口气,心态要稳,车还是要继续开的

起跳前

也就是当我们触摸界面的时候,需要做什么呢,先在脑海中回忆一下。。。。

没错,就两件事情,播放一下按下的音效,然后为了逼真一点,给小人加上 y 轴上的形变即可(简单到爆),代码如下:

//SceneGame.ts
privateonTapDown() {
    //播放按下音效,参数为(从哪里开始播放,播放次数)
    this.pushSoundChannel = this.pushVoice.play(0, 1);
    //使小人变矮做出积蓄能量的效果,就是缩放Y轴
    egret.Tween.get(this.player).to({scaleY: 0.5}, 3000);
    //起跳的标记
    this.isReadyJump = true;
}

起跳时

也就是当我们手指离开界面的时候,总共要做以下几件事情:

1、将舞台置为不可点击状态;

2、切换声音;

3、通过按压时间来计算跳跃的水平距离;

4、小人沿曲线起跳并旋转;

先上代码再解释:

//SceneGame.ts
privateonTapUp() {
    if (!this.isReadyJump) return;
    if (!this.targetPos) this.targetPos = new egret.Point(); //point 就是个点,有 xy 值等
    
    //一松手小人就该起跳,此时应先禁止点击屏幕,并切换声音
    this.blockPanel.touchEnabled = false;
    this.pushSoundChannel.stop();
    this.jumpVoice.play(0, 1);
    
    //清除所有动画
egret.Tween.removeAllTweens();
    this.isReadyJump = false;
    
    //计算落点坐标
    this.targetPos.x = this.player.x + this.direction * this.jumpDistance;
    this.targetPos.y = this.player.y + this.direction * this.jumpDistance * (this.currentBlock.y - this.player.y) / (this.currentBlock.x - this.player.x);
    
    //执行跳跃动画
    egret.Tween.get(this).to({ factor: 1 }, 400).call(() => { //这表示贝塞尔曲线,在 400 毫秒内,this 的 factor 属性将会缓慢趋近1这个值,这里的 factor 就是曲线中的 t 属性,它是从 0 到 1 的闭区间。
        this.player.scaleY = 1;
        this.jumpDistance = 0;
        //判断跳跃是否成功
        this.checkResult();
    });
    //执行小人空翻动画,先处理旋转中心点
    this.player.anchorOffsetY = this.player.height / 2;
    egret.Tween.get(this.player)
        .to({ rotation: this.direction > 0 ? 360 : -360 }, 200)
        .call(() => { this.player.rotation = 0})
        .call(() => { this.player.anchorOffsetY = this.player.height - 20; });
}
//添加 factor 的 set、get 方法
public getfactor():number {
    return 0;
}
//这里的 getter 使 factor 属性从 0 开始,结合刚才 tween 中传入的 1,使其符合公式中的 t 的取值区间。
//而重点是这里的 setter,里面的 player 对象是我们要应用二次贝塞尔曲线的显示对象,而在 setter 中给 player 对象的 xy 属性赋值的公式正是之前列出的二次贝塞尔曲线公式。
public setfactor(t:number) {
    //仅仅是个公式
    this.player.x = (1 - t) * (1 - t) * this.player.x + 2 * t * (1 - t) * (this.player.x + this.targetPos.x) / 2 + t * t * (this.targetPos.x);
    this.player.y = (1 - t) * (1 - t) * this.player.y + 2 * t * (1 - t) * (this.targetPos.y - 300) + t * t * (this.targetPos.y);
}

比较难理解的应该是小人怎么沿贝塞尔曲线(这种令人迷茫的数学名词)运动了,这里我们就小小剖析一下。

看下 egret.Tween.get(this).to({factor: 1}, 400)里面的 factor,这是什么意思呢?

factor 是一个属性,你就当做是个变量吧,它的初始值为 0,我们用egret.Tween 这个缓动函数让 factor的值在 400 ms 内从 0 变成 1

factor 的值改变了,根据 public set factor(t:number) {},小人的坐标也将跟着改变,于是小人就动起来了,好好体会一下

也许你又会问,那小人的坐标为什么那样写呢?其实说白了,这就是一个公式,啥公式呢,如下图:

Egret 小游戏实战教程 跳一跳(搬运二)第2张

这公式又是啥呢,就是下面这个:

Egret 小游戏实战教程 跳一跳(搬运二)第3张

是不是稍微熟悉了点呢,等等,P0,P1,P2 又是啥?就是三个坐标点啦,我们看下面这幅图会好理解点:

Egret 小游戏实战教程 跳一跳(搬运二)第4张

由上图可以看出 P0 是小人的坐标,P2 是目标点的坐标,P1 则是二者中间上方的某一个点(可自己修改),然后带入上述公式即可

如果你还是一头雾水,没关系,不重要,你只要知道若是我们已知三个点(起点,中间点和终点),带入上述公式,就能画出一条贝塞尔曲线就行

再次好好体会一下,有余力的话可以百度了解一下贝塞尔曲线

要是实在理解不了呢,也不打紧,你就不要用它,直接把小人平移到终点就好,不要什么跳跃效果了,就像下面这样:

egret.Tween.get(this.player).to({ x: this.targetPos.x, y: this.targetPos.y }, 400).call(() => {})

看到这里真是不容易啊,跨过了一个坎,得给自己鼓个掌

起跳后

马不停蹄,同志们请继续加油,黎明就在眼前,fighting

现在,小人已经跳到目标方块上了,此时我们需要判断一下,小人落地的位置是不是在允许误差范围内,以此来判断成功和失败,先来看下下面这张图:

Egret 小游戏实战教程 跳一跳(搬运二)第5张

我们知道了小人和方块的位置,就可以求出二者的误差是多少(就是求斜边),如果小于一定范围我们就认为此次跳跃是成功的,代码如下:

//SceneGame.ts
privatecheckResult() {
    //实际误差
    let err = Math.pow(this.player.x - this.currentBlock.x, 2) + Math.pow(this.player.y - this.currentBlock.y, 2)
    //允许的最大误差
    const MAX_ERR_LEN = 90 * 90;
    if (err <= MAX_ERR_LEN) { //跳跃成功
        //更新分数
        this.score++;
        this.scoreLabel.text = this.score.toString();
        //要跳动的方向
        this.direction = Math.random() > 0.5 ? 1 : -1;
        //当前方块要移动到相应跳跃点的距离
let blockX, blockY;
        blockX = this.direction > 0 ? this.leftOrigin.x : this.rightOrigin.x;
        blockY = this.stage.stageHeight / 2 + this.currentBlock.height;
        //小人要移动到的点
        let diffX = this.currentBlock.x -blockX;
        let diffY = this.currentBlock.y -blockY;
        let playerX, playerY;
        playerX = this.player.x -diffX;
        playerY = this.player.y -diffY;
        //更新页面,更新所有方块位置
        this.updateAll(diffX, diffY);
        //更新小人的位置
        egret.Tween.get(this.player).to({
            x: playerX,
            y: playerY
        }, 800).call(() =>{
            //开始创建下一个方块
            this.addBlock();
            //让屏幕重新可点;
            this.blockPanel.touchEnabled = true;
        })
    } else { //跳跃失败
        this.restartBtn.addEventListener(egret.TouchEvent.TOUCH_TAP, this.reset, this);
        this.overPanel.visible = true;
        this.overScoreLabel.text = this.score.toString();
    }
}
privateupdateAll(x, y) {
    egret.Tween.removeAllTweens();
    for (var i: number = this.blockArr.length - 1; i >= 0; i--) {
        var blockNode = this.blockArr[i];
        //盒子的中心点(不是图片的中心点)在屏幕左侧 或者在 屏幕右侧 或者在 屏幕下方
        if (blockNode.x + blockNode.width - 222 < 0 || blockNode.x - this.stage.stageWidth - 222 > 0 || blockNode.y - this.stage.stageHeight - 78 > 0) {
        //方块超出屏幕,从显示列表中移除
        if (blockNode) this.blockPanel.removeChild(blockNode);
        this.blockArr.splice(i, 1);
        } else{
        //没有超出屏幕的话,则移动
        egret.Tween.get(blockNode).to({
            x: blockNode.x -x,
            y: blockNode.y -y
        }, 800)
        }
    }
}

先说下跳跃失败的情况,很显然,我们需要更新分数到结束界面中,并显示结束界面

同时还要在结束界面中的按钮上添加事件监听,对于重置也就是把各种变量重新初始化一遍,这个比较简单,就不细说了

再来看下跳跃成功的情况,我们需要做的有更新分数,随机下一个方向,移动所有的方块和小人,创建下一个方块

这步的难点就在于如何移动画面,所以又要好好装 13 一番

我们先这样想,所有的东西一起移动有个共同的地方就是:大家都会往左或往右移动一样的距离,往上或往下移动一样的距离

所以我们只需要知道其中一个方块怎么移,往 x 轴移动多少,往 y 轴移动多少,其他方块和小人也跟着移动一样的距离不就可以了?又可以好好体会一番

So 现在我们的首要任务就是知道其中一个方块怎么移,先来看看下面这张图:

Egret 小游戏实战教程 跳一跳(搬运二)第6张

上图的意思是:我们小人始终是在这两个固定点(可自己微调位置)的其中一个位置起跳的,这很重要

开头也说到了,为什么一开始的方块会固定在左边,其实放右边也可以,但必须是这两个固定点的其中一个

这样我们移动方块的时候才有一个参考点,如上图中的右图所示,小人跳完后所在的那个方块需要移动到其中一个跳跃点上

因为下一个方向向左,所以我们将把小人所在的方块移动到右边的跳跃点上。这样一来,事情就变得简单了

小人所在的方块和右侧跳跃点的坐标值都有了,一减就可以算出 diffY和 diffX,然后对方块数组进行一个 for 循环,都移动同样的距离即可,小人也是一样的

移动完后再生成下一个方块,就大功告成了,能看到这里实属不易,给读者们几个大大的掌声,真的优秀!

运行游戏

我们打开 Egret Launcher,点击发布设置,如下图:

Egret 小游戏实战教程 跳一跳(搬运二)第7张

选择微信小游戏,并点击设为默认发布,输入自己的 AppID(自己在小程序官网上注册一个,很快的),再写个名称,点击确定即可:

Egret 小游戏实战教程 跳一跳(搬运二)第8张

然后会弹出如下一个弹窗:

Egret 小游戏实战教程 跳一跳(搬运二)第9张

我们点击使用微信开发者工具打开(前提是你要安装微信开发者工具),就可以预览自己写的游戏效果了,就像下面这样。耶耶耶!

Egret 小游戏实战教程 跳一跳(搬运二)第10张

这里补充一点,就是玩的时候你可能会觉得卡,没关系,我们只要改一下游戏的默认帧率就行,就像下面这样:

Egret 小游戏实战教程 跳一跳(搬运二)第11张

结语

至此,要多粗糙有多粗糙的跳一跳小游戏就可以跑起来了

还等啥,赶紧动手玩玩吧,玩自己写的游戏,感觉还真是非(废)一般呢

当然,问题还是有很多的,比如适配啊,有些手机尺寸容易出现黑边(就很丑)

或者细节不到位啊(自己可以试着改改);功能不够完善啊(自己试着加下)

但这些都不重要,重要的是游戏思路,思路才是最值钱的,人与人之间的差别又体现出来了

跳一跳源码地址:GitHub

原文:尤水就下

*** | 以上内容仅为学习参考、学习笔记使用 | ***

免责声明:文章转载自《Egret 小游戏实战教程 跳一跳(搬运二)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇订单服务,使用feign调用服务Yii框架常见问题汇总下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

egret 入门学习笔记之问题解决

一、super()的用处 MaskLayer继承了eui.Component, constructor是类的入口 super()代表执行父类的构造函数 二、人物动画 我设计的效果是:人物随着手指触摸屏幕任何一点而走动。 但是当我使用 var boy2 = egret.Tween.get(mc2); boy2.to({x:e.stageX,y:e.st...

egret 自动播放音乐问题:ios 自动播放音乐失效

Egret Engine 2D 官方文档可见,音频有三种加载方式: 1.通过Sound加装音频 2.通过 res 加装音频 3.通过 URLLoader 加装音频 每种方式我都尝试了一下,测试发现这三种方式用PC浏览器(chrome)打开都可以正常自动播放。 然而我用自己手机(iphone8)测试(主要使用微信、safari和QQ打开测试)的时候,发现问题...

egret 发布ios记录

根据官方文档http://developer.egret.com/cn/github/egret-docs/Native/native/hybrid/hybrid/index.html 将现有的项目发布成Hybird。 发布成功之后,使用xcode打开xcworkspace工程。 然后点击运行按钮。成功调用启动了iphone模拟器,但是内容确实一片空白,即...

iOS 14 egret H5游戏卡顿问题分析和部分解决办法

    现象 总体而言,iOS 14 渲染性能变差,可以从以下几个测试看出。 测试1:简单demo,使用egret引擎显示3000个图(都是同一个100*100 png 纹理),逐帧做旋转。(博客园视频播放可能有问题,视频地址:https://github.com/kenkozheng/kenkozheng.github.com/blob/master...