CocosCreator制作flappybird CocosCreator经典入门项目之flappybird
冏尼 人气:0开发环境
CocosCreator v2.3.1
node.js v10.16.0
vscode 1.46.1
游戏引擎概念
可以理解为一套已经编写好的代码,它封装了对底层接口的使用,是游戏开发的核心功能提供者。
一般分为6各部分:
- 图像渲染:控制电脑对游戏绘画的绘画操作,直接影响游戏质量
- 音频UI:提供音频特效,以及游戏UI部分,让游戏与用户交互更好
- 设备输入:键盘、鼠标、陀螺仪等
- 脚本引擎:提供脚本接口,为游戏开发者提供“笔墨”
- 网络引擎:数据交互模块,用服务器为客户端提供交互
- 物理引擎(高级):模拟现实的物理效果(重力加速度、物体间的碰撞等)。
关于Cocos Creator
项目结构
ProjectName(项目文件夹)
├──assets 资源文件夹----------用来放置游戏中所有的本地资源、脚本和第三方库文件
├──library 资源库----------------这里文件的结构和资源格式将被处理成最终游戏发布时需要的形式
├──local 本地设置-------------存放项目本机删的配置信息(编辑器面板布局、窗口大小、位置等)
├──packages 扩展插件文件夹—存放项目的自定义扩展插件
├──settings 项目设置-------------保存项目的相关设置,如构建发布菜单里的包名、场景和平台选择
├──temp 临时文件夹----------用于缓存CocosCreator在本地的临时文件
└──project.json 验证文件-------------作为验证CocosCreator项目合法性的标志
下面开始进入真正的项目上手
配置代码编辑环境
Visual Studio Code (以下简称 VS Code)是微软新推出的轻量化跨平台 IDE,支持 Windows、Mac、Linux 平台,安装和配置非常简单。使用 VS Code 管理和编辑项目脚本代码,可以轻松实现语法高亮、智能代码提示等功能。
安装 Cocos Creator API 适配插件
该操作会将 Cocos Creator API 适配插件安装到 VS Code 全局的插件文件夹中,安装成功后在 控制台 会显示绿色的提示:VS Code extension installed to ...
。这个插件的主要功能是为 VS Code 编辑状态下注入符合 Cocos Creator 组件脚本使用习惯的语法提示。
在项目中生成智能提示数据
如果希望在代码编写过程中自动提示 Cocos Creator 引擎 API,需要通过菜单生成 API 智能提示数据并自动放进项目路径下。
选择主菜单的 开发者 -> VS Code 工作流 -> 更新 VS Code 智能提示数据。该操作会将根据引擎 API 生成的 creator.d.ts
数据文件复制到项目根目录下(注意是在 assets
目录外面),操作成功时会在 控制台 显示绿色提示:API data generated and copied to ...
使用 VS Code 激活脚本编译
使用外部文本编辑器修改项目脚本后,要重新激活 Cocos Creator 窗口才能触发脚本编译,我们在新版本的 Creator 中增加了一个预览服务器的 API,可以通过向特定地址发送请求来激活编辑器的编译。
新建项目
1.新建一个空白项目
2.资源管理器
然后你需要在资源管理中创建项目中最重要场景、脚本文件,以及导入游戏所需要的纹理(图片资源)。这里的文件夹不是默认创建的,你需要手动创建,便于管理你的项目。需要说明的是resources是CocosCreator(以下简述cocos)中特殊的资源文件夹,所有需要通过cc.loader.loadRes动态加载(后续会提到这个方法)的资源,都必须放置在resources文件夹和它的子文件夹下。如果一份资源仅仅是被resources中的其他资源所依赖,而不需要直接被cc.loader.loadRes调用,就不需要放在resources文件夹里。
3.场景
在scenes中新建场景,scenes–右键–新建scenes场景,在 Cocos Creator 中,游戏场景(Scene) 是开发时组织游戏内容的中心,也是呈现给玩家所有游戏内容的载体。游戏场景中一般会包括以下内容:
- 场景图像和文字(Sprite,Label)
- 角色
- 以组件形式附加在场景节点上的游戏逻辑脚本
当玩家运行游戏时,就会载入游戏场景,游戏场景加载后就会自动运行所包含组件的游戏脚本,实现各种各样开发者设置的逻辑功能。所以除了资源以外,游戏场景是一切内容创作的基础。现在,让我们来新建一个场景。入门项目flappybird只需要新建一个场景,你完成这个项目后的效果大致是这个亚子。
4.场景编辑器、层级管理器、属性检查器
双击你所创建的bird场景,cocos就会在 场景编辑器 和 层级管理器 中打开这个场景。打开场景后, 层级管理器 中会显示当前场景中的所有节点和它们的层级关系。我们刚刚新建的场景中只有一个名叫 Canvas
的节点,Canvas
可以被称为画布节点或渲染根节点,点击选中 Canvas
,可以在 属性检查器 中看到他的属性。
从资源包里面的texture目录下将名为的sky背景图片拖到Canvas中,作为游戏背景。调整Canvas和sky的size尺寸大小。sky的size至少要大于Canvas,不然你制作的游戏可能会有很大的黑边。然后用相似的方法把bird0(其他两张是为了配合作出简易帧动画,模拟小鸟的飞行)、pipe1(下管道)、pipe2(上管道)添加到Canvas下。上下管道为一组,我复制了4组,一共5组。通过脚本控制背景和每组管道向左移动来达到小鸟持续向前飞行的效果。
5.节点绑定
需要注意,Canvas下的元素都是以node节点的形式来被管理的。在script中新建脚本文件—game.js,将其拖入Canvas中,或者直接绑定到Canvas上。确保在加载场景时脚本被一并加载。
6.生命周期回调
Cocos Creator 为组件脚本提供了生命周期的回调函数。用户只要定义特定的回调函数,Creator 就会在特定的时期自动执行相关脚本,用户不需要手工调用它们。
目前提供给用户的生命周期回调函数主要有:
- onLoad
onLoad
回调会在节点首次激活时触发,比如所在的场景被载入,或者所在节点被激活的情况下。而且onLoad
总是会在任何start
方法调用前执行,通常我们会在onLoad
阶段去做一些初始化相关的操作。 - start
start
回调函数会在组件第一次激活前,也就是第一次执行update
之前触发。start
通常用于初始化一些需要经常修改的数据,这些数据可能在 update 时会发生改变。 - update 游戏开发的一个关键点是在每一帧渲染前更新物体的行为,状态和方位。这些更新操作通常都放在
update
回调中。以下四个回调函数在此项目中不会用到 - lateUpdate
- onDestroy
- onEnable
- onDisable
主要代码
game.js
cc.Class({ extends: cc.Component, properties: { skyNode: cc.Node,//定义天空节点 pipeNode: cc.Node,//定义管道节点 birdNode: cc.Node,//定义小鸟节点 clickLayerNode: cc.Node,//定义监听节点 监听鼠标点击事件 scoreNode: cc.Node,//定义得分节点 总得分节点 buttonNode: cc.Node,//定义按钮节点 开始游戏按钮 numberNode: cc.Node,//定义数字节点 加分combo overNode: cc.Node,//定义游戏结束节点 结束按钮 spriteFrame: {//定义精灵框架节点, default: [],//数组类型,将会绑定bird0、bird1、bird2三张图片精灵,通过在update()方法中不断更换,形成动画 type: cc.SpriteFrame //图片精灵类型 }, clip: {//定义音效节点 default: [],//同样为数组类型,便于绑定多个资源。后续学习,可尝试使用动态加载 type: cc.AudioClip //音频类型 } }, onClickButton() {//设置点击按钮方法 this.num = 0;//将num重置为0 this.sign = true;//设置控制游戏是否继续的标识符为真 this.buttonNode.active = false;//让按钮节点不可见 this.overNode.active = false;//让控制“游戏结束”文本的overNode节点不可见 this.birdNode.y = 50;//点击按钮后小鸟的位置归位 this.power = 0;//将力量因素变为0,防止小鸟复活后下落太快 this.scoreNode.getComponent(cc.Label).string = "" + this.num;//将分数节点的string值变为0,this.scoreNode.getComponent(cc.Label).string let list = this.pipeNode.children;//用一个list存储金属管道的子节点(.children) for (let i = 0; i < list.length; i++) {//设置一个循环,终止条件是i小于list的长度 let child = list[i];//let 一个child变量用于在循环中存储每个list[i] child.x += 1000;//将管道节点的x右移1000 } cc.audioEngine.playMusic(this.clip[0], true); }, onClickBagButton(event, data) { //定义背包按钮方法 // cc.log(event, data); if (data == "bag") {//判断传过来的参数等于某event的CustomEventData this.showBag(); // 调用显示背包函数 } }, showBag() { //定义显示背包函数 if (this.bagPrefab == null) { //如果资源加载没有成功 setTimeout(() => { //设置延时0.5s后继续调用显示bag方法 this.showBag(); }, 500); return; } //资源加载完成 let node = null; //定义一个node赋值为空 if(this.isOpen){ //判断背包是否打开,this.isOpen初始值为false node = cc.find("Canvas/panelBag"); node.active = true; } else{ node = cc.instantiate(this.bagPrefab); //加载具体的预置资源并赋值给node cc.find("Canvas").addChild(node); //将node节点添加到Canvas幕布下 } node.opacity = 0;//设置node节点的透明度为0; node.scale = 0.1;//设置node节点的初始缩放为0.1; let ac = cc.spawn( //封装并行的动画并赋值给ac cc.fadeIn(0.5), //0.5s的速度淡入 cc.scaleTo(0.5,1),//0.5s的速度缩放到1 ); node.runAction(ac); //用runAction函数执行封装好的ac this.isOpen = true;//将背包打开参数赋值true }, gameOver() { //设置游戏结束方法 this.sign = false;//游戏结束,将游戏继续标识符变为false this.checkStill = false;//检查游戏是否进行参数变为false this.buttonNode.active = true;//游戏结束,让开始按钮this.buttonNode为可见 this.overNode.active = true;//游戏结束,让“游戏结束”文本的overNode节点可见 cc.audioEngine.stopMusic(this.clip[0]); //游戏结束停止背景音乐 }, addScore() { //设置加分方法 this.numberNode.opacity = 255;//让分数节点numberNode的.opacity元素(透明度)为255 this.num++;//让num值++ this.scoreNode.getComponent(cc.Label).string = "" + this.num;//让分数节点的string元素=空的的字符串“” 加num this.numberNode.y = this.birdNode.y;//让加分combo节点numberNode的y和小鸟节点的y相等 this.numberNode.runAction(//让加分combo节点numberNode渐入渐出runAction,spawn,fadeOut,moveBy cc.spawn( cc.fadeOut(0.5), cc.moveBy(0.5, cc.v2(0, 50)) ) ) cc.audioEngine.playEffect(this.clip[2]); //加分音乐 }, // LIFE-CYCLE CALLBACKS: onLoad() { // cc.director.getCollisionManager().enabled = true; //打开碰撞管理系统cc.director.getCollisionManager(). this.bagPrefab = null;//定义bagPrefab变量赋值为空 // cc.loader.loadRes(路径,资源类型,回调函数);error如果错误,打印错误信息,data为加载成功的资源 cc.loader.loadRes("prefab/panelBag", cc.Prefab, (error, data) => { if (error) {//判断如果错误信息不为空 cc.log(error);//打印错误信息 return; } this.bagPrefab = data; //将加载到的资源赋值给this.bafPrefab }); }, start() { this.isOpen = false;//定义背包是否打开变量并置为false; this.num = 0;//将num参数变为0,防止游戏再开始时,得分继承上局的分数 this.scoreNode.getComponent(cc.Label).string = "" + 0;//设置分数初始值为0 this.speed = 5;//设置相对位移速度为5 this.power = 0;//设置力量参数为0 this.checkStill = true;//检查游戏是否进行参数变为true this.curFrame = 0;//定义一个变量用于循环skinnode列表 this.sign = false;//定义一个标识符控制游戏是否开始,初始值为false this.checkState = false;// false-非碰撞检测状态 true-碰撞检测状态 this.up = 0;// this.clickLayerNode.on(cc.Node.EventType.TOUCH_START, () => {//谁.on(cc.Node.EventType类型.事件,执行匿名方法() =>) this.power = 4;//将初始的力量参数直接由0置为4,确保每次监听到点击小鸟有比较明显的上升 this.up++; cc.audioEngine.playEffect(this.clip[1]); }) cc.audioEngine.playMusic(this.clip[0], true); }, update(dt) { if (!this.sign) {//设置让游戏暂停的标识符 return; } cc.log(2); this.skyNode.x -= this.speed;//通过控制天空节点的x值来控制背景移动 this.birdNode.y += this.power + this.up;//通过小鸟节点的y值和初始的力量参数来控制小鸟自由下落的速度 this.power -= 0.2;//通过让power小幅度值渐变达到让小鸟平滑移动 this.birdNode.angle = this.speed * this.power;//通过力量和速度参数控制小鸟上升和下降的角度 if (this.skyNode.x < -1200) {//判断当背景和起点相隔1200像素时,让背景节点skyNode.x归位=0(无限循环背景) this.skyNode.x = 0; } //node.children即管道节点的子节点 let list = this.pipeNode.children;//用一个list存储金属管道的子节点(.children) let checkNode = null;//定义检查管道节点变量并赋值为null for (let i = 0; i < list.length; i++) {//用循环赋值所有管道节点 并赋予速度 let child = list[i];//let一个child变量来存储list中的每个元素 child.x -= this.speed;//控制管道节点的x-=this.speed // cc.log(child); if (child.x < -600) { child.x = 600;//判断child出界< -600时让child归位到600 child.y = (Math.random() - 0.5) * 200;//通过Math.random()(其值在0-1之间)函数来控制管道通过口随机出现,缓和值为200 } let dis = Math.abs(this.birdNode.x - child.x)//let一个变量dis用于计算小鸟和管道的x的差值(Math.abs取绝对值) let width = (this.birdNode.width + child.children[0].width) / 2;//定义width变量用于存储小鸟和管道碰撞的临界值 if (dis < width) {//判断 dis <= width,就给检查管道节点变量赋值child checkNode = child; } } if(this.birdNode.y + this.birdNode.height / 2 > cc.winSize.height / 2 //判断小鸟的头部大于屏幕的上边缘或者小鸟的底部小于屏幕的下边缘 || this.birdNode.y - this.birdNode.height / 2 < -cc.winSize.height / 2){ this.gameOver();//执行游戏结束方法 } if(checkNode) {//直接判断checkNode,如果未被赋值即为空 this.checkState = true;//状态从无到有,将检查状态变量checkState赋值为true //判断矩形小鸟的上边框(.y + height/2)大于等于通道上边框(checkNode.y+100)或者小鸟的下边框小于等于通道 if (this.birdNode.y + this.birdNode.height / 2 > checkNode.y + 100 || this.birdNode.y - this.birdNode.height / 2 < checkNode.y) { this.gameOver();//游戏结束 } }else{ if (this.checkState && this.checkStill) {//判断检查状态变量和检查游戏是否进行变量是否都为true(&&) this.addScore();//调用加分函数 } this.checkStill = true; //将检查游戏是否进行变量赋值true this.checkState = false;//将检查状态变量赋值为false this.up = 0; } this.curFrame++;//小鸟帧率变量自增 if (this.curFrame > 2) {//判断如果帧率变量大于2,将帧率变量变为0; this.curFrame = 0; } if (this.birdNode.y) {//如果力量参数大于0 //让小鸟节点的Sprite组件的spriteFrame参数随帧率参数变化 this.birdNode.getComponent(cc.Sprite).spriteFrame = this.spriteFrame[this.curFrame]; } } })
总结
上述关于小鸟、钢管是否碰撞的检测是通过数学计算的方法实现的。不过我的项目文件中还保留了使用cocos碰撞组件的版本,具体表现为每个pipe节点、entrance节点身上的BoxCollider组件,以及bird.js。另外还有我学习制作的一个背包,主要通过预置(cc.prefab)节点实现了背包窗口以及在窗口中动态加载道具图片(sprite)。本人也在学习阶段,有问题欢迎评论区交流。笔者后续还准备分享一些其他项目!包括单机和局域网联机版本的对比项目;可能还会用到一些优化策略如:资源管理器、网络通讯管理器、信号槽等,不足之处希望各位大佬轻喷。
加载全部内容