玩法分析
玩家操作一个反映迟钝却蹦跳不停地小怪物去触碰不断出现的星星,难以驾驭的加速度将给玩家带来很大挑战,和您的小伙伴比一比看谁能拿到更多地星星吧!
创建项目
启动CocosCreator创建项目,在资源管理器中分类创建资源文件夹以保存不同的资源。
资源文件夹 | 描述 |
---|---|
texture | 存放纹理资源 |
audio | 存放音效资源 |
scene | 存放游戏场景文件 |
script | 存放游戏逻辑脚本文件 |
prefab | 存放预设文件 |
将项目所使用的音效和纹理分别存放到对应文件夹并规范化命名。
创建场景
游戏场景(Scene)是开发时组织游戏内容的中心,也是呈现给玩家游戏内容的载体。游戏场景中一般会包含以下内容:场景图片、文字、角色、以组件形式附加在场景节点上的游戏逻辑脚本等。当玩家运行游戏时,首先会加载游戏场景,场景加载后会自动运行所包含组件的游戏脚本,以实现各种开发者设置的逻辑。
创建游戏主场景
$ vim assets/scene/main.fire
双击主场景会在场景编辑器和层级管理器(节点树)中打开,打开场景后层级管理器会显示当前场景中所有节点及其层级关系。刚新建的场景中默认只有一个名为Canvas的根节点,Canvas可以被称为画布节点或渲染根节点。默认状态下,画布节点在场景编辑器中以紫色矩形框显示区域范围。
选中Canvas在右侧属性检查器的Canvas
组件属性中设置画布适配选项。
画布属性 | 描述 |
---|---|
DesignResolution | 游戏设计分辨率,即游戏界面的宽度和高度。 |
FitWidth | 宽度自适应,用于在不同尺寸屏幕上运行时,按高度优先来缩放画布以适配。 |
FitHeight | 高度自适应,用于在不同尺寸屏幕上运行时,按高度优先来缩放画布以适配。 |
由于提供多分辨率适配一般会将场景中所有负责图像显示的节点都存放在Canvas下面,这样当作父节点的Canvas的缩放属性scale
改变时,所有子节点图像也会跟着一起缩放以适应不同屏幕大小。
设置创景图像
添加场景背景图,将资源管理器中texture纹理目录下的background图像拖拽到层级管理器Canvas节点下,即添加一个以background
为贴图资源的子节点。节点会自动以贴图资源的文件名来命名,编辑场景后及时使用快捷键Ctrl+S
保存。
修改背景尺寸
为了使背景图片覆盖整个屏幕,选中背景节点后点击主窗口左上角工具栏第四个按钮矩形变形工具,使用矩形变形工具可任意修改图像节点的尺寸。
为了使背景图能够覆盖主流手机整个屏幕,在背景节点的属性检查器的Node节点属性组中设置size
宽高为1360 x 760。
添加地面
游戏主角在画面上是在地面上跳跃,因此需添加地面。添加方式和背景雷同。需要注意的是在层级管理器中显示在下方的节点渲染顺序是会上方节点的后面,也就是说下方节点会在上方节点之后绘制。从层级管理器上可以发现,层级管理器最下方的节点在场景编辑器中会显示在最前方。另外,子节点永远会显示在父节点前方。因此可以通过调整节点层级顺序来控制图层显示顺序。
设置地面宽高为1960x250,并沿着背景底边对齐。
添加主角
添加主角到层级管理器,确保位于最下层显示最前面。设置主角属性,修改锚点位置,默认任何节点的锚点会在节点中心即(0.5, 0.5)
位置,也就是说节点中心点所在位置就是该节点的位置。
主角跳跃时需要控制主角的底部位置来模拟在地面上跳跃的效果,所以需要将主角的锚点在底边中心即(0, 0.5)
。
到此为止,场景搭配完毕。
主角脚本
Cocos Creator开发游戏的核心理念是让内容生产和功能开发流畅地并行协作
创建驱动主角行动的脚本
$ vim assets/script/player.js
Creator中脚本名称就是组件的名称,命名大小写敏感,若组件名称大小写错误会导致无法通过名称使用组件。
cc.Class({
});
使用全局方法cc.Class()
声明CocosCreator中的类
-
cc
是Cocos引擎的简称也是Cocos引擎的主要命名空间,引擎代码中所有的类、函数、属性、常量都在cc
这个命名空间下。 -
Class()
方法是cc
模块下的一个方法,用于声明CocosCreator中的类。 -
Class()
方法的参数是一个原型对象,在原型对象中以键值对形式设定所需参数类型,即可创建出所需的类。
创建组件类的结构
cc.Class({
extends:cc.Component,
properties:{}
});
自定义组件类继承自cc.Component
类,实现继承后能够挂载到到场景的节点上,提供控制节点的各种功能。
添加组件属性
properties: {
jumpHeight:0,//主角跳跃高度,单位像素。
jumpDuration:0,//主角跳跃持续时长,单位为秒。
maxSpeed:0,//最大移动速度,单位像素每秒。
accel:0,//移动加速度,单位像素每秒。
jumpAudio:{type:cc.AudioClip, default:null},//跳跃音效
},
CocosCreator规定节点自定义属性必须写在properties
代码块中,自定义属性规定主角的移动方式主要包括高度、时间、速度、加速度这四大属性,具体的属性值可使用属性检查器设置,换句话说,这种随时需要调整的属性都可以放到properties
属性中。
为节点添加脚本组件
在层级管理器中选中主角节点后,在属性检查器下点击添加组件,选择用户脚本组件,选择player脚本组件,即可为主角节点添加player组件。
添加组件成功后即可在属性检查器中出现player组件以及自定义的属性,设置主角跳跃高度为200像素,起跳到最高点所需时间0.6秒,最大水平移动速度为400像素每秒,水平加速度250像素每秒。
实现玩家垂直跳跃
onLoad(){
//初始化跳跃动作并在当前节点上执行该动作
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
},
//设置主角跳跃动作
setJumpAction(){
//执行向上跳跃到最高点
const jumpUp = cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
//从最高点向下跳跃到原点
const jumpDown = cc.moveBy(this.jumpDuration, cc.v2(0, -this.jumpHeight)).easing(cc.easeCubicActionOut());
//上下跳跃时播放跳跃音效
const callback = cc.callFunc(this.playJumpAudio, this);
//循环执行
return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback));
}
onLoad
生命周期回调函数会在场景加载后立即自动执行,一般会将初始化相关的操作和逻辑放入其中。这里首先讲循环跳跃动作传递给jumpAction
变量,然后调用组件挂载的节点的runAction
方法,让主角节点循环跳跃。
当组件加载后脚本首先会执行onLoad
生命周期回调函数,在onLoad()
回调函数中实现主角跳跃动作,需要了解CocosCreator的动作系统(Action),CocosCreator中的动作简单来说就是节点的位移、缩放和旋转。主角上下跳跃实际上可以分解为两个动作向上移动和向下移动,然后无限重复这两个动作。这里需要实现移动和重复两个操作,需使用cc.moveBy()
和cc.repeatForever()
方法。
移动到指定的距离
cc.moveBy(duration, deltaPos, deltaY):ActionInterval
参数 | 类型 | 描述 |
---|---|---|
duration | Number | 移动持续秒数 |
deltaPos | Vec2/Number | 目标坐标点或X轴坐标值 |
deltaY | number | Y轴坐标值 |
cc.moveBy()
方法用于在规定时间内移动指定的一段距离,duration
参数是移动消耗的时长,deltaPos
参数是一个Vec2类型(2D向量和坐标)的对象,也可以是一个Number类型的X轴坐标值。当传入的是一个Number类型是即为X轴坐标,此时就需要填写第三个参数deltaY。需要注意的是这里的坐标值是相当于节点当前的坐标位置,而非整个坐标系的绝对位置。
cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight))
cc.moveBy(this.jumpDuration, 0, this.jumpHeight)
cc.moveBy()
方法返回值是一个ActionInterval
类型的对象,ActionInterval
表示时间间隔动作类,即动作在一定时间内完成。时间间隔类ActionInterval
具有easing()
方法,可以让时间间隔动作呈现一种缓动运动,easing()
方法的参数是一个缓动对象,同时也返回一个ActionInterval
时间间隔类对象。
cc.moveBy(this.jumpDuration, 0, this.jumpHeight).easing(cc.easeCubicActionOut());
cc.easeCubicActionOut()
方法构建的缓动对象是以EaseCubicInOut按三次函数缓动进入并退出的动作。
重复上下跳动
cc.repeatForever()
函数用于永远地重复某个动作,有限次数内重复一个动作则使用repeat
函数,由于cc.repeatForever
重复执行的动作不会停止,因此不能被添加到cc.sequence
或cc.spawn
函数中。
cc.repeatForever(action:FiniteTimeAction):ActionInterval
按顺序执行动作
cc.sequence()
函数用于按顺序执行动作,创建的动作将按顺序依次运行。
cc.sequence(actionOrActionArray:FiniteTimeAction|FiniteTimeAction[], tempArray:FiniteTimeAction):ActionInterval
执行回调函数
cc.callFunc(selector:function, selectorTarget:object, data:any):ActionInterval
实现玩家移动控制
键盘按下开启加速,键盘抬起关闭加速。
- 键盘按下开启向左或向右加速
- 键盘松开关闭向左或向右加速
为了让玩家能够通过键盘按键控制主角按水平移动,可为主角添加键盘输入,使用键盘A键和D键控制跳跃的方向。
玩家水平左右移动的过程中存在加速度,当开启方向加速度开关时向左或向右移动时会添加加速度,当关闭方向加速开关时向左或向右移动时会取消加速度。
onLoad(){
//初始化水平加速度
this.xSpeed = 0;
//初始化方向加速开关
this.accLeft = false;
this.accRight = false;
//监听键盘按下事件以实现玩家水平移动
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
onDestroy(){
//取消监听
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
//键盘按键按下 启用方向加速度开关
onKeyDown(evt){
switch(evt.keyCode){
case cc.macro.KEY.a: this.accLeft = true; break;
case cc.macro.KEY.d: this.accRight = true; break;
}
}
//键盘按键抬起 关闭方向加速度开关
onKeyUp(evt){
switch(evt){
case cc.macro.KEY.a: this.accLeft = false; break;
case cc.macro.KEY.d: this.accRight = false; break;
}
}
Cocos通过systemEvent
来监听系统全局事件,通过向systemEvent
注册键盘响应函数,在函数中通过switch
判断键盘上的A和D键是否被按下或松开,若松下就执行对应的操作。
设置主角速度和位置
- 向左加速移动
- 向右加速移动
游戏中根据键盘输入获得加速度方向后,需要每帧在update
方法中计算主角的速度和位置。
游戏开发的一个关键点是在每一帧渲染前更新物体的行为、状态和方位,这些更新操作通常都会放在update
这个生命周期回调函数中。
update(dt){
//根据加速度方向设置每帧更新速度
if(this.accLeft){
this.xSpeed -= this.accel * dt;//向左加速移动
}else if(this.accRight){
this.xSpeed += this.accel * dt;//向右加速移动
}
//限制主角速度不能超过最大值
if(Math.abs(this.xSpeed) > this.maxSpeed){
this.xSpeed = this.maxSpeed * this.xSpeed / Math.abs(this.xSpeed);
}
//根据当前速度更新主角的X坐标位置
this.node.x += this.xSpeed * dt;
}
制作星星
星星是主角的目标,会不断在场景中消失又出现。玩家需引导主角去触碰星星以收集分数。当主角触碰到星星时便会显示,然后随机位置重新出现。
星星在场景中属于重复出现又消失的节点,对于这种在节点树上重复生成的节点,应采用预制资源prefab
,作为动态生成节点时所使用的模板。
- 星星随机地出现在画布范围内
- 星星与主角的距离一旦满足收集范围则
制作预制资源
从资源管理器中将星星纹理拖到场景中,无需修改星星节点的任何属性,只需要为其添加一个自定义的脚本组件后,在将星星节点拖放到资源管理器,此时会发现资源管理器会自动生成星星预设资源。
星星脚本组件
星星脚本组件主要实现的操作包括
- 每一帧都判断星星和主角的距离是否达到收集半径,若满足则发生收集行为。
- 计算星星与主角之间的距离
- 星星被收集时计算得分并销毁
- 星星收集时会出现渐隐的视觉效果
这些效果的实现仅仅靠单独的星星组件脚本是不行的,需要和其他组件配置实现。
$ vim assets/script/star.js
cc.Class({
extends: cc.Component,
properties: {
//星星和主角之间的距离小于这个数值时会完成收集
pickRadius:0,
}
}
星星组件添加属性
星星组件只需要一个属性用来控制主角距离星星多远时完成收集,在properties
中添加pickRadius
自定义属性。
为节点添加组件
将star.js
脚本组件添加到star
节点中,设置收集半径pickRadius
属性值为60像素。
删除星星节点
将星星节点从层级管理器拖拽到资源管理器中的prefab
文件夹下,自动生成star.prefab
预设资源文件。接下来,删除层级管理器中的star
节点。后续修改星星组件可直接双击star.prefab
预设资源文件即可。
游戏控制
目前准备工作已经做完,首先创建了搭建了场景,完成了美术UI设计,接着为主角添加了player.js
控制脚本组件,为星星创建了预设资源star.prefab
文件。下面的中心是实现游戏主逻辑,首先添加名为game.js
的脚本组件作为游戏主逻辑脚本,将game.js
脚本组件添加到场景的渲染节点Canvas
中,接着在将地面节点ground
、玩家节点player
、星星节点star
作为自定义属性添加到脚本中,在依次实现生成星星、计算得分、游戏结束功能。
$ vim assets/script/player.js
cc.Class({
extends: cc.Component,
properties: {
//星星产生后消失时间的随机范围
minStarDuration:0,
maxStarDuration:0,
//星星预制资源
star:{type:cc.Prefab, default:null},
//地面节点用于确定星星生成的高度
ground:{type:cc.Node, default:null},
//玩家节点用于获取主角弹跳的高度和控制主角行动开关
player:{type:cc.Node, default:null},
//得分文本
scoreLabel:{type:cc.Label, default:null},
//得分音效
scoreAudio:{type:cc.AudioClip, default:null}
}
}
属性检查器自定义属性参数
例如:在脚本中为自定义属性除了设置type和default默认值外,还可设置其他参数。
star:{
type:cc.Prefab,
default:null
},
属性参数 | 描述 |
---|---|
type | 限定属性的数据类型 |
default | 设置属性的默认值,默认值在组件第一次添加到节点是才会使用。 |
visible | 布尔值,是否在属性检查器面板中显示。 |
serializable | 布尔值,是否序列化保存属性。 |
displayName | 在属性检查器面板中显示的最终名字 |
tooltip | 在属性检查器面板中添加属性提示 |
为场景渲染节点Canvas
添加game.js
游戏主逻辑控制脚本组件
将自定义属性中的节点分别设置(拖拽)到属性检查器面板对应位置
任意位置随机生成星星
场景加载后会初始化游戏主逻辑控制脚本game.js
并执行onLoad
生命周期回调函数,此时需要随机位置生成星星,生成星星的位置是有一定区域限制的,从坐标的角度上看,星星的最高点不能超过主角的弹跳最高点,最低点不得超过地面,左右侧不得超过屏幕。
如何使用预设资源生成星星,需要使用给定模板在场景中生成节点,并将该节点添加到Canvas节点下,最后为其设置一个随机的位置。
onLoad(){
//随机生成星星
this.spawnStar();
},
//随机生成星星
spawnStar(){
//使用给定预设资源模板在场景中生成一个新的节点
const start = cc.instantiate(this.starPrefab);
//将新增节点添加到画布节点下
this.node.addChild(star);
//为新节点设置随机位置
star.setPosition(this.getPosition());
}
这里涉及到两个问题,第一个是如何使用预设资源即模板生成节点,第二个如何设置随机位置。
使用预设资源生成节点
const star = cc.instantiate(this.starPrefab);
cc.instantiate
方法的作用是克隆指定的任意类型的对象,或从Prefab
预设资源实例化出新节点,返回值为Node
节点或Object
对象。
为星星设置随机坐标位置
节点X轴位置可根据屏幕宽度随机计算得到,节点y轴坐标可根据地面高度和主角跳跃的高度随机计算得到。
//为星星设置随机坐标位置
getPosition(){
const x = this.node.width / 2 * (Math.random() - 0.5);
const y = this.groundY + this.player.getComponent("player").jumpHeight * Math.random() + 50;
return cc.v2(x, y);
}
获取地面最高点Y坐标值
this.ground.y
是地面锚点的Y坐标,锚点默认位于地面的中心位置,获取高点顶点位置还需为加上地面高度的一半。
onLoad(){
this.groundY = this.ground.y + this.ground.height / 2;
}
获取玩家的最高点Y坐标值
this.groundY + this.player.getComponent("player").jumpHeight
计算星星的随机Y轴坐标值
const y = this.groundY + this.player.getComponent("player").jumpHeight * Math.random();
添加主角碰触收集星星的行为
星星每帧获取主角节点的坐标,通过判断它们之间的距离是否小于可收集距离来执行收集行为,这里的问题是,如何在星星中获取主角节点的引用呢?由于game脚本组件中保存了player玩家节点属性即主角节点的引用,而且星星是在game脚本中动态生成的,因此只要在game脚本生成star节点实例时,将game组件的实例传入到star脚本并保存起来即可,之后即可在star脚本中通过this.game.player
访问主角节点。
spawnStar(){
//...
//在星星组件上暂存game对象的引用
star.getComponent("star").game = this;
}
在star脚本组件中每帧判断星星和主角的距离
$ vim assets/script/star.js
update(dt){
//每帧判断星星与主角的距离来执行收集行为
if(this.getDistance() < this.pickedRadis){
this.onPicked();
return;
}
},
//获取星星和主角之间的距离
getDistance(){
//获取主角的坐标
const playerPosition = this.game.player.getPosition();
//极端两点之间的距离
return this.node.position.sub(playerPosition).mag();
}
计算两个cc.Vec2
矢量坐标的距离时,首先对两个坐标做减法,然后求距离。这里使用到两个函数cc.Vec2.sub()
和cc.Vec2.mag()
方法。
-
cc.Vec2.sub
方法用于坐标向量减法同时返回新的向量 -
cc.Vec2.mag
方法用于计算向量的长度
实现星星收集
当星星被收集时调用game脚本中的接口生成一个星星,然后在销毁当前星星节点,最后计算得分。
onPicked(){
//生成新的星星
this.game.spawnStar();
//销毁当前星星
this.node.destroy();
//计算得分
this.game.gainScore();
}
计算得分并显示
成功收集星星时会增加得分奖励的逻辑和显示
场景中添加得分节点
游戏开始得分为0,每次成功收集积分加1,显示积分,首先需要在场景中添加一个Label节点,并添加到画布节点下,设置Label节点位于最下层并命名为score,设置Label节点属性的位置和文字大小。最后将Label节点添加到game游戏主逻辑脚本属性的score属性中。
游戏主逻辑脚本中实现得分逻辑
$ vim assets/script/game.js
properties(){
//得分文本显示节点
scoreLabel:{type:cc.Label, default:null},
//得分音效资源
scoreAudio:{type:cc.AudioClip, default:null}
},
onLoad(){
//初始化得分值
this.score = 0;
},
//计算得分
gainScore(){
//计算得分
this.score += 1;
//更新显示
this.scoreLabel.string = this.score + "";
//播放音效
cc.audioEngine.playEffect(this.scoreAudio, false);
}
失败判断
得分可以带来成就感和补偿感能增加兴趣,但得分再多不能失败,游戏的挑战和刺激性就不够,因为没有成就感,游戏带来体验之一是成就感,这种成就感是通过不断地失败而后成功,才能极大的给玩家带来某种幸福感。如何让玩家在收集星星的过程中失败呢?星星是不断出现和消失的,在出现和消失的时间间隙完成收集则成功加分,否则失败退出。
为星星添加计时消失的逻辑
设置标准计时器
vim assets/script/game.js
onLoad(){
//初始化计时器
this.timer = 0;
},
update(dt){
//更新计时器
this.timer += dt;
},
//生成星星
spawnStar(){
//重置计时器
this.timer= 0;
}
设置星星消失时间范围
vim assets/script/game.js
properties:{
//定义星星产生后消失的时间范围
minStarDuration:0,
maxStarDuration:0,
}
onLoad(){
//初始化星星消失时间
this.starDuration = 0;
}
//创建星星
spawnStar(){
//随机设置星星消失时间
this.starDuration = this.minStarDuration + Math.random() * (this.maxStarDuration - this.minStarDuration);
}
游戏结束判断
vim assets/script/game.js
update(dt){
//游戏失败判断
if(this.timer > this.starDuration){
//游戏失败
this.gameOver();
return;
}
//更新计时器
this.timer += dt;
}
//游戏失败
gameOver(){
//停止玩家行为
this.player.stopAllActions();
//重新加载场景
cc.director.loadScene("main");
}
为星星增加渐隐效果
$ vim assets/script/star.js
properties(){
//透明度范围
minOpacity:50,
maxOpacity:255
},
update(dt){
//根据计时器更新透明度
const radio = 1 - this.game.timer / this.game.starDuration;
this.node.opacity = this.minOpacity + Math.floor(radio * (this.maxOpacity - this.minOpacity));
}