开发flappy bird小游戏(附完整源码参考)

news/2025/3/12 23:20:58/文章来源:https://www.cnblogs.com/zezhou/p/18768892

小游戏玩法介绍

  • 用户通过点击屏幕让小鸟往上飞,控制小鸟穿过管道,每穿过1组水管加1分,直到碰到管道游戏结束。

鸟节点

  • 制作一个鸟节点,给它添加碰撞体和刚体组件。

  • 碰撞体组件:可以选用CircleCollider2D组件,通过碰撞体可以方便监听鸟和管道是否产生碰撞。

  • 刚体组件:选用动力学类型,通过力的传递让鸟实现往上飞行的效果。

鸟运动

  • 鸟的飞行效果实现思路:让鸟只往上运动,而管道持续往左移动,从而实现鸟往右上飞行的效果。

  • 监听屏幕触摸事件,给鸟刚体传递一个向上的力。

// 监听屏幕触摸,让小鸟往上运动
input.on(Input.EventType.TOUCH_START, this.touchBegin, this)
touchBegin(event: EventTouch){if (this.gameStatus === GameStatus.end || this.gameStatus === GameStatus.init) returnif (this.gameStatus === GameStatus.ready){this.startGame()}this.audioManager.playWing()// 通过力向上运动this.birdRd.applyForceToCenter(this.force, true)// 旋转动画this.beginTween = tween(this.birdNode).to(0, {angle: 0}).to(0.2, {angle: 40}).to(0.5, {angle: -20}).start()
}

管道生成

  • 管道的缺口是以屏幕高度尺寸得到一个随机值。

  • 管道分为上下两个管道,通过一个预制体管道实例两次,调整管道上下位置组成一个管道组,而下个管道组的位置是根据前一个管道组的位置进行偏移。

generatePipe(){const startIndex = this.pipeList.lengthfor (let i=0;i<this.pipeNum;i++){const pipeItem:PipeItemInterface = {index: startIndex + i,recordScore: false,pipeTopNode: null,pipeBottomNode: null}const randomLimit = this.screenHeight * 0.65const randomY = randomRangeInt(0, randomLimit) - randomLimit / 2this.createPipe(pipeItem, randomY)this.pipeList.push(pipeItem)}
}createPipe(pipeItem: PipeItemInterface, randomY: number){let previousPipeItem = nulllet previousPipePos: Vec3 = nullif (pipeItem.index > 0){previousPipeItem = this.pipeList[pipeItem.index-1]previousPipePos = previousPipeItem.pipeTopNode.getWorldPosition()}else{previousPipeItem = pipeItempreviousPipePos = new Vec3(this.pipeStartSpacing, 0, 0)}// 生成上管道pipeItem.pipeTopNode = instantiate(this.pipePrefab)pipeItem.pipeTopNode.getComponent(UITransform).setContentSize(this.pipeWidth, this.screenHeight)const pipeTopCollider: BoxCollider2D = pipeItem.pipeTopNode.getComponent(BoxCollider2D)pipeTopCollider.size = new Size(this.pipeWidth, this.screenHeight)pipeTopCollider.offset = new Vec2(0, this.screenHeight / 2)pipeItem.pipeTopNode.setWorldPosition(previousPipePos.x + this.pipeWidthSpacing,randomY + this.pipeHeightSpacing,0)// 生成下管道pipeItem.pipeBottomNode = instantiate(this.pipePrefab)pipeItem.pipeBottomNode.getComponent(UITransform).setContentSize(this.pipeWidth, this.screenHeight)const pipeBottomCollider: BoxCollider2D = pipeItem.pipeBottomNode.getComponent(BoxCollider2D)pipeBottomCollider.size = new Size(this.pipeWidth, this.screenHeight)pipeBottomCollider.offset = new Vec2(0, this.screenHeight / 2)pipeItem.pipeBottomNode.setWorldPosition(previousPipePos.x + this.pipeWidthSpacing,randomY - this.pipeHeightSpacing,0)pipeItem.pipeBottomNode.setScale(1, -1)this.pipesNode.addChild(pipeItem.pipeTopNode)this.pipesNode.addChild(pipeItem.pipeBottomNode)
}

管道移动

  • 先定义一个管道每秒移动速度,然后在帧回调函数中,让管道持续往左移动。

  • 每秒移动速度 * 前一帧间隔时间 = 要移动的像素值,让管道减去每帧要移动的像素值,达到往左持续移动效果。

  • 为了优化游戏,不能存在过多节点,要让管道出了屏幕范围时进行销毁。

update(deltaTime: number) {if (this.gameStatus === GameStatus.running){this.pipeList.forEach((pipeItem:PipeItemInterface)=>{// 管道移动pipeItem.pipeTopNode.getWorldPosition(this.tempVec3).subtract3f(this.pipeMoveSpeed * deltaTime, 0, 0)pipeItem.pipeTopNode.setWorldPosition(this.tempVec3)pipeItem.pipeBottomNode.getWorldPosition(this.tempVec3).subtract3f(this.pipeMoveSpeed * deltaTime, 0, 0)pipeItem.pipeBottomNode.setWorldPosition(this.tempVec3)// 销毁管道数据if (this.tempVec3.x < -this.pipeWidth){pipeItem.pipeTopNode.destroy()pipeItem.pipeBottomNode.destroy()this.pipeList.splice(this.pipeList.findIndex(item=>item===pipeItem), 1)}})}
}

得分

  • 在帧回调函数中,在管道往左移动后,判断鸟的x位置如果大于管道组x位置时,进行得分累计。

  • 并且可以根据得分逻辑,来生成新的管道组。

update(deltaTime: number) {if (this.gameStatus === GameStatus.running){this.pipeList.forEach((pipeItem:PipeItemInterface)=>{// 管道移动pipeItem.pipeTopNode.getWorldPosition(this.tempVec3).subtract3f(this.pipeMoveSpeed * deltaTime, 0, 0)pipeItem.pipeTopNode.setWorldPosition(this.tempVec3)pipeItem.pipeBottomNode.getWorldPosition(this.tempVec3).subtract3f(this.pipeMoveSpeed * deltaTime, 0, 0)pipeItem.pipeBottomNode.setWorldPosition(this.tempVec3)// 判断穿过管道记录成绩(每个管道只会执行一次)if (!pipeItem.recordScore && this.tempVec3.x <= this.birdX){pipeItem.recordScore = truethis.score += 1this.audioManager.playScore()// 判断生成新的管道(只剩x组管道时)if (this.pipesNode.children.length / 2 <= 3){this.generatePipe()}}// 销毁管道数据if (this.tempVec3.x < -this.pipeWidth){pipeItem.pipeTopNode.destroy()pipeItem.pipeBottomNode.destroy()this.pipeList.splice(this.pipeList.findIndex(item=>item===pipeItem), 1)}})}
}

游戏结束

  • 通过碰撞组件监听鸟和管道的碰撞,当鸟和管道碰撞时游戏结束。

  • 当鸟碰到管道时,让鸟顺时针旋转到-40,实现撞到墙后的效果,由于鸟是动力学会一直往下掉落,所以还需在x秒后改为静态类型让鸟停在屏幕外。

// 监听小鸟的碰撞
const birdCollider = this.birdNode.getComponent(Collider2D)
birdCollider.on(Contact2DType.BEGIN_CONTACT, this.onBirdColliderBegin, this)
onBirdColliderBegin(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null){// 小鸟碰到管道、空气墙,游戏结束if (this.gameStatus === GameStatus.running && (otherCollider.node.name === this.pipePrefab.name)){this.gameOver()}
}gameOver(){this.setGameStauts(GameStatus.end)this.audioManager.playDie()// 死亡动画this.beginTween.stop()tween(this.birdNode).to(0.1, {angle: -40}).start()// 不让鸟节点一直下落this.scheduleOnce(()=>{if (this.gameStatus === GameStatus.end){this.birdRd.type = ERigidBody2DType.Static}}, 3)
}

空气墙

  • 由于鸟是往上运动,而管道的高度是有限的,当疯狂点击屏幕可以让鸟绕过管道进行得分,因此可以在鸟的上下方加上空气墙,让鸟碰到空气墙时也游戏结束。

  • 创建空气墙节点,添加盒子碰撞组件,并在鸟的碰撞回调里判断碰撞东西是否是空气墙。

onBirdColliderBegin(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null){// 小鸟碰到管道、空气墙,游戏结束if (this.gameStatus === GameStatus.running && (otherCollider.node.name === this.pipePrefab.name || otherCollider.node.name === this.topAirWallNode.name ||otherCollider.node.name === this.bottomAirWallNode.name)){this.gameOver()}
}

背景缓缓移动

  • 为了让显示效果更好一些,可以让背景以极慢速度缓缓朝左边移动。

  • 需要创建两个相同的背景节点,让图片无缝排列在一起,在帧回调函数里让它俩持续往左移动,当第一个图片过了屏幕时,给它移动到第二个图片后面,再次整体持续移动,实现无缝背景图片移动。

update(deltaTime: number) {if (this.gameStatus === GameStatus.running){this.pipeList.forEach((pipeItem:PipeItemInterface)=>{// 管道移动pipeItem.pipeTopNode.getWorldPosition(this.tempVec3).subtract3f(this.pipeMoveSpeed * deltaTime, 0, 0)pipeItem.pipeTopNode.setWorldPosition(this.tempVec3)pipeItem.pipeBottomNode.getWorldPosition(this.tempVec3).subtract3f(this.pipeMoveSpeed * deltaTime, 0, 0)pipeItem.pipeBottomNode.setWorldPosition(this.tempVec3)// 判断穿过管道记录成绩(每个管道只会执行一次)if (!pipeItem.recordScore && this.tempVec3.x <= this.birdX){pipeItem.recordScore = truethis.score += 1this.audioManager.playScore()// 判断生成新的管道(只剩x组管道时)if (this.pipesNode.children.length / 2 <= 3){this.generatePipe()}}// 销毁管道数据if (this.tempVec3.x < -this.pipeWidth){pipeItem.pipeTopNode.destroy()pipeItem.pipeBottomNode.destroy()this.pipeList.splice(this.pipeList.findIndex(item=>item===pipeItem), 1)}})// 第一背景切换if (this.bgNode.children[0].getWorldPosition().x <= -this.bgWidth){this.bgNode.children[0].setWorldPosition(this.bgNode.children[this.bgNode.children.length-1].getWorldPosition().add3f(this.bgWidth, 0, 0))this.bgNode.insertChild(this.bgNode.children[0], this.bgNode.children.length)}// 背景持续往左移动for (let i=0;i<this.bgNode.children.length;i++){const node = this.bgNode.children[i]node.setWorldPosition(node.getWorldPosition().subtract3f(this.bgMoveSpeed * deltaTime, 0, 0))}}
}

获取完整小游戏源码

  • 以上内容是讲述了flappy bird小游戏核心逻辑实现,你如果是为了给别人玩、为了上架小游戏平台,那么就需要使用一个游戏引擎去开发小游戏。

  • Cocos商城 里,搜索 zezhou222 ,获取对应小游戏源码学习!

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

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

相关文章

毒王正传:初出江湖

忽见江湖风云变, 协议如雪落满天。 十方少侠争破译, 哪知此物带瘟烟。楔子混沌起微澜忽见江湖风云变,协议如雪落满天。十方少侠争破译,哪知此物带瘟烟。话说那日产品大殿突发英雄帖:"今有通天协议问世,三日接入者赏鸡腿三筐!"众客户端少侠摩拳擦掌,却不知正堕…

uniapp 开发微信小程序自定义背景图与导航栏

uniapp 开发微信小程序自定义背景图与导航栏 1、page.json中使用custom自定义导航栏{"path": "pages/store/store","style": {"navigationBarTitleText": "店铺详情","navigationStyle": "custom" //自…

Redis--Lesson06--Redis进阶2

一.Redis持久化之RDB Redis的RDB持久化机制是通过快照(snapshot)的形式将存储在内存中的数据以一定的时间间隔保存到硬盘上。以下是RDB持久化的具体流程: 触发条件:RDB文件可以通过配置文件设置自动触发(例如,根据时间或修改次数),也可以手动通过命令SAVE或BGSAVE来触发。…

Spring Security-web安全框架

进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条等,下边拿微信来举例子说明认证相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码登录微信的过程就是认证。Spring Security 1. 基本概念 1.1 …

SpringBoot文件上传到数据库

SpringBoot文件上传到数据库 首先导入了相应的jar包 <!--thymeleaf--> <dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf-spring5</artifactId> </dependency> <dependency><groupId>org.thymeleaf.…

供应链系统中的 “计划单、订单、通知单,入库 / 出库单” 的区别

在供应链管理中,各种单据是业务流程的核心纽带,但它们之间的区别和联系常常让新手感到困惑。本文从采购和销售两大业务场景出发,详细拆解了“计划单、订单、通知单、入库/出库单”等常见单据的定义、作用及相互关系。在供应链系统中,经常会听到一些名字相近,但是意思可能略…

20232209实验一《Python程序设计》实验报告

20232209 2024-2025-2 《Python程序设计》实验一报告 课程:《Python程序设计》 班级: 2322 姓名: 吴易阳 学号:20232209 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 1.实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能;(编写书中的程序…

20242909王天宇_网络攻防实践第2次作业

20242909王天宇《网络攻防实践》第2次作业 1.实验内容 学习内容总结 本次实验内容涵盖了网络信息查询、网络安全扫描和个人隐私保护等多个方面,旨在通过实践掌握网络信息获取、漏洞分析及隐私保护的基本技能。主要学习内容如下:DNS与IP信息查询:通过 nslookup 等工具,学习如…

如何给海淘电子产品挑一根「合格」电源线——IEC 320解读

转载声明:https://sspai.com/post/72699 网站:少数派 作者:Levinson喜欢海淘的朋友们,你是否碰到过「买来的数码产品或家用小电器自带的电源插头不是中国大陆标准,不得不自己再配一根国标线却不知道该搜什么关键词」的尴尬时刻呢?实际上这些电源线都是有统一标准的,这个…

IEC 320解读——如何给海淘电子产品挑一根「合格」电源线

喜欢海淘的朋友们,你是否碰到过「买来的数码产品或家用小电器自带的电源插头不是中国大陆标准,不得不自己再配一根国标线却不知道该搜什么关键词」的尴尬时刻呢?实际上这些电源线都是有统一标准的,这个标准被称为 IEC 60320,所有的电源线都可以通过这个标准找到唯一的那一…

Netty基础—3.基础网络协议

大纲 1.网络基础的相关问题总结 2.七层模型和四层模型 3.物理层(网线 + 光缆 + 01电信号) 4.数据链路层(以太网协议 + 网卡mac地址) 5.网络层(IP协议 + 子网划分 + 路由器) 6.传输层(TCP和UDP协议 + Socket + 端口) 7.应用层(HTTP协议 + SMTP协议) 8.浏览器请求一个域名会发生什…

4, 表单

复选框 复选框组 将一组复选框或单选按钮组合成一组并排放置的Bootstrap按钮bootstrap.min.cssjquery.min.jsbootstrap.min.jsdiv.btn-group[data-toggle=buttons]label.btn.btn-default.activeinput[type=checkbox]{Option 1}label.btn.btn-defaultinput[type=checkbox]{Opti…