今天尝试使用cocoscreator制作个小游戏,其实在这之前已经把creator文档里的第一个小游戏,摘星星敲了2、3遍了,因为只是兴趣,平时主要web开发用不到,正好现在赋闲在家,翻出来再瞅瞅,看看这次能不能自己输出点什么小玩意。
跳过产品吹毕环节,直接https://www.cocos.com/creator官网下载最新的ide并安装,刚刚更新了2.4.0,更新的内容对新手没啥影响,略;
其实有没有这张图都很清楚,大概有哪些东西干嘛用的一目了然;第一次接触creator文档上准备的demo还是很方便的,直接下载摘星星,所有资源都准备好了,只需要简单写一点脚本,往场景里一丢就ok了;
依照文档,创建主游戏场景,游戏场景是玩家看到的游戏内容的载体;
在层级管理器中打开canvas,在右侧属性检查器中可以看到各种属性,就和调试工具一样可以直接在里面修改需要的值;
不了解的属性直接hover就可以看到相应的说明;
退拽资源中的背景放到canvas中,然后通过场景编辑修改素材达到想要的状态;
快捷键 | 用途 |
---|---|
w | 移动工具,通过拖拽修改position属性 |
e | 旋转工具,通过拖拽修改rotation属性 |
r | 缩放工具,通过拖拽修改scale属性 |
t | 变换工具,通过拖拽矩形框修改属性 |
使用同样的方式将背景、地面、角色都拖入画布,调整位置就可以开始撸脚本代码了;
在资源管理器中右键创建第一个脚本,命名为player,这里可以选择脚本语言js或者ts,需要注意的是这里的文件名也就是组件名,并且区分大小写;
打开刚刚创建的脚本,这里可以关联喜好的编辑器或者使用creator提供的文本编辑器;
脚本中已经有一些默认创建的代码,首先是一个全局的cc.class方法,cc就是cocos是引擎的主要命名空间,所有类、方法、属性都仍在这里定义
cc.Class({
extends: cc.Component,
properties: {
},
// onLoad () {},
start () {
},
// update (dt) {},
});
在play脚本中的properties中添加角色的属性
properties: {
// 主角跳跃高度
jumpHeight: 0,
// 主角跳跃持续时间
jumpDuration: 0,
// 最大移动速度
maxMoveSpeed: 0,
// 加速度
accel: 0,
}
Cocos Creator 规定一个节点具有的所有属性都需要写在 properties 代码块中,这些属性将规定主角的移动方式,在代码中我们不需要关心这些数值是多少,因为我们之后会直接在 属性检查器 中设置这些数值。以后在游戏制作过程中,我们可以将需要随时调整的属性都放在 properties 中。
切回creator,这时可以通过在属性检查器中通过添加组件按钮进行绑定;关联完成后就可以在属性检查器中看到刚刚我们创建的属性并设置了;
除了基础属性外,在添加动作,让角色可以动起来;相关api参看动作系统api
实例和参数
cc.moveBy = function (duration, deltaPos, deltaY) {
return new cc.MoveBy(duration, deltaPos, deltaY);
};
duration 持续时间 number
deltaPos 坐标位置,num或vec2类型对象
deltaY Y坐标 number
返回一个actionInterval类型对象,表示时间间隔动作的类,详细参看ActionInterval类型
setJumpAction: function () {
/*
跳跃上升
cc.v2创建vec2类,表示2D向量和坐标
easing缓动函数类,提供缓动效果
EaseCubicOut 是按三次函数缓动退出的动作
*/
var jumpUp = cc.moveBy(
this.jumpDuration,//这里的时间就是刚刚在角色中定义的跳跃时间
cc.v2(0,this.jumpHeight)//这里的运动坐标是一个vec类型的2d坐标包含了Y坐标所以第三个参数省略
).easing(cc.easeCubicActionOut());
// 下落
var jumpDown = cc.moveBy(this.jumpDuration, cc.v2(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
/*
不断重复
sequence按定义顺序执行
*/
return cc.repeatForever(cc.sequence(jumpUp, jumpDown));
},
定义好这些之后就可以在player的脚本中使用了,可以通过按钮事件触发也可以简单一点直接丢在生命周期onload中
生命周期 | 说明 |
---|---|
onLoad | 是在脚本初始化阶段。执行一次。 |
start | 会在组件激活前,执行一次,在update执行之前 |
update | 在组件进行更新时执行,帧计时器会一直执行函数中的操作 |
lateupdate | 在所有组件更新完后执行 |
onEnable | 会在enabled属性从false到true和active从false到ture时执行。执行顺序是在onLoad之后,start之前 |
onDisable | 会在enabled属性从true到false和active从true到false时执行。 |
//player
onLoad () {
// 初始化跳跃动作
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
},
完成这些切换回编辑器,在编辑器正中央预览游戏按钮,在浏览器中查看,就可以看到角色已经在蹦上了;
傻蹦跶可不像话,接下来添加对元素的控制,在keydown时打开位移加速度的开关,在keyup时关闭;
onKeyDown (event) {
switch(event.keyCode) {
case cc.macro.KEY.a:
this.accLeft = true;
break;
case cc.macro.KEY.d:
this.accRight = true;
break;
}
},
onKeyUp (event) {
switch(event.keyCode) {
case cc.macro.KEY.a:
this.accLeft = false;
break;
case cc.macro.KEY.d:
this.accRight = false;
break;
}
},
键盘事件有了,还要在onload中进行初始化监听
onLoad: function () {
// 初始化跳跃动作
this.jumpAction = this.setJumpAction();
this.node.runAction(this.jumpAction);
// 加速度方向开关
this.accLeft = false;
this.accRight = false;
// 主角当前水平方向速度
this.xSpeed = 0;
// 初始化键盘输入监听
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);
},
最后再修改update方法,添加加速度、速度、和当前位置
update: function (dt) {
// 根据当前加速度方向每帧更新速度
if (this.accLeft) {
this.xSpeed -= this.accel * dt;
} else if (this.accRight) {
this.xSpeed += this.accel * dt;
}
// 限制主角的速度不能超过最大值
if ( Math.abs(this.xSpeed) > this.maxMoveSpeed ) {
// if speed reach limit, use max speed with current direction
this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
}
// 根据当前速度更新主角的位置
this.node.x += this.xSpeed * dt;
},
完成后再次启动预览,这时史莱姆已经可以听话的往左右蹦跶了
现在已经拥有一个可以控制的蹦来蹦去的史莱姆了,接下来要为其添加目标,在场景中添加一些不断出现的星星,供玩家收集;
仍然是重复上边制作角色的过程,拖拽元素到场景,为场景元素添加脚本,在脚本中添加属性索引
properties: {
// 星星和主角之间的距离小于这个数值时,就会完成收集
pickRadius: 0,
},
由于这个元素是需要重复生成的节点,可以将其保存为Prefab(预制)资源,预制资源
方法也简单,只需要将绑定过脚本的元素从图层管理器中拖拽回资源管理器assets目录下就可以完成转换了;完整预制资源保存后层级管理器中的相应元素就没有用了,直接移除就好了;
星星的生成是游戏主逻辑的一部分,主逻辑还包含计分、gameover、replay等等相关逻辑,创建game脚本,作为游戏控制的主逻辑
properties: {
// 分数属性的完整写法
score: {
default: 0,//指定默认值
displayName: "Score (player)",//属性名
tooltip: "The score of player",//hover时的提示
},
// 这个属性引用了星星预制资源
starPrefab: {
default: null,
type: cc.Prefab
},
// 星星产生后消失时间的随机范围
maxStarDuration: 0,
minStarDuration: 0,
// 地面节点,用于确定星星生成的高度
ground: {
default: null,
type: cc.Node
},
// player 节点,用于获取主角弹跳的高度,和控制主角行动开关
player: {
default: null,
type: cc.Node
}
},
设置一个完整属性包含一下常用参数
索引 | 说明 |
---|---|
default | 设置属性的默认值,这个默认值仅在组件第一次添加到节点上时才会用到 |
type | 限定属性的数据类型,详见 CCClass 进阶参考:type 参数 |
visible | 设为 false 则不在属性检查器面板中显示该属性 |
serializable | 设为 false 则不序列化(保存)该属性 |
displayName | 在属性检查器面板中显示成指定名字 |
tooltip | 在属性检查器面板中添加属性的 tooltip |
完成脚本编辑后,将脚本添加到canvas节点上,接着把刚刚制作的预制资源star添加到game组件的starPrefab属性中,进行属性引用,同样的将层级管理器ground 和 player 节点到 Canvas 节点 Game 组件中相对应名字的属性,完成节点引用
继续修改game主逻辑,让星星在场景的随机位置出现
onLoad: function () {
// 获取地平面的 y 轴坐标
this.groundY = this.ground.y + this.ground.height/2;
// 生成一个新的星星
this.spawnNewStar();
},
spawnNewStar: function() {
/*
使用给定的模板在场景中生成一个新节点
instantiate克隆指定的任意类型的对象或者从 Prefab 实例化出新节点
*/
var newStar = cc.instantiate(this.starPrefab);
// 将新增的节点添加到 Canvas 节点下面
this.node.addChild(newStar);
/*
为星星设置一个随机位置
setPosition设置节点在父节点坐标系中的位置,可以传入num或vec2
*/
newStar.setPosition(this.getNewStarPosition());
},
getNewStarPosition: function () {
var randX = 0;
// 根据地平面位置和主角跳跃高度,随机得到一个星星的 y 坐标
var randY = this.groundY + Math.random() * this.player.getComponent('player').jumpHeight + 50;
// 根据屏幕宽度,随机得到一个星星 x 坐标
var maxX = this.node.width/2;
randX = (Math.random() - 0.5) * 2 * maxX;
// 返回星星坐标
return cc.v2(randX, randY);
},
修改完成后可以刷新预览,看看星星是否随机出现在场景中‘
现在要添加史莱姆收集星星的行为逻辑了,这里的难点在于,星星和主角的碰撞事件,需要随时获得史莱姆的位置;通过之前做了两件事
// Game.js
spawnNewStar: function() {
// ...
// 在星星组件上暂存 Game 对象的引用
newStar.getComponent('star').game = this;
},
保存game,打开star脚本,利用game组件中引用的player节点来拍短距离
getPlayerDistance: function () {
// 根据 player 节点位置判断距离
var playerPos = this.game.player.getPosition();
// 根据两点位置计算两点之间距离
var dist = this.node.position.sub(playerPos).mag();
return dist;
},
onPicked: function() {
// 当星星被收集时,调用 Game 脚本中的接口,生成一个新的星星
this.game.spawnNewStar();
// 然后销毁当前星星节点
this.node.destroy();
},
保存后继续预览,碰撞如果有异样,说明数值设置有问题,进行一下微调;
现在几乎完成了所有游戏动作,之后为我们的行为添加计分机制,首先需要创建一个label节点进行分数的展示;
在 层级管理器 中选中 Canvas 节点,右键点击并选择菜单中的 创建新节点 -> 创建渲染节点 -> Label(文字),之后在场景编辑器中调整到想要的位置,或者直接在属性检查器中使用数值进行设置,最后将位图字体资源拖拽到组件的font属性中,替换文字字体;
在game脚本中,为properties添加分数显示的引用属性,并在onload中进行初始化,最后在添加更新分数展示的文字的方法
onLoad: function () {
this.score = 0;
},
properties:{
scoreDisplay: {
default: null,
type: cc.Label
}
},
gainScore: function () {
this.score += 1;
// 更新 scoreDisplay Label 的文字
this.scoreDisplay.string = 'Score: ' + this.score;
},
完成脚本编辑后,将层级管理器中的score节点关联到game组件的scoreDisplay属性中;
在收集星星的事件中,调用刚刚创建的gainScore方法
// Star.js
onPicked: function() {
// 当星星被收集时,调用 Game 脚本中的接口,生成一个新的星星
this.game.spawnNewStar();
// 调用 Game 脚本的得分方法
this.game.gainScore();
// 然后销毁当前星星节点
this.node.destroy();
},
现在的星星是一直存在与场景中的,需要为星星添加计时消失的逻辑,在game的onload方法中加入计时需要变量的声明;
onLoad: function () {
// ...
// 初始化计时器
this.timer = 0;
this.starDuration = 0;
},
然后在spawNewStar中加入重置计时器的逻辑
spawnNewStar: function() {
// ...
// 重置计时器,根据消失时间范围随机取一个值
this.starDuration = this.minStarDuration + Math.random() * (this.maxStarDuration - this.minStarDuration);
this.timer = 0;
},
在 update 方法中加入计时器更新和判断超过时限的逻辑
update: function (dt) {
// 每帧更新计时器,超过限度还没有生成新的星星
// 就会调用游戏失败逻辑
if (this.timer > this.starDuration) {
this.gameOver();
return;
}
this.timer += dt;
},
最后在gainscore后边加入喜闻乐见的gameover方法
gameOver: function () {
this.player.stopAllActions(); //停止 player 节点的跳跃动作
cc.director.loadScene('game');
}
现在星星已经会随着时间流失消失了,但是突然消失太灵异了,所以再添加些效果,在star脚本中加入淡出效果
update: function() {
// ...
// 根据 Game 脚本中的计时器更新星星的透明度
var opacityRatio = 1 - this.game.timer/this.game.starDuration;
var minOpacity = 50;
this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity));
}
首先是跳跃时的音效,打开player属性,添加引用声音文件资源的jumpAudio属性
properties: {
// ...
// 跳跃音效资源
jumpAudio: {
default: null,
type: cc.AudioClip
},
},
之后在setJumpAction方法,插入音效播放的毁掉,并通过添加playJumpSound方法来播放声音
setJumpAction: function () {
// 跳跃上升
var jumpUp = cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
// 下落
var jumpDown = cc.moveBy(this.jumpDuration, cc.v2(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
// 添加一个回调函数,用于在动作结束时调用我们定义的其他方法
var callback = cc.callFunc(this.playJumpSound, this);
// 不断重复,而且每次完成落地动作后调用回调来播放声音
return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback));
},
playJumpSound: function () {
// 调用声音引擎播放声音
cc.audioEngine.playEffect(this.jumpAudio, false);
},
之后同样的添加得分音效,至此就完成了demo小游戏的全部制作;
作为初识上手,这个简单的demo完整的展示了开发一个小游戏的基本思路和流程,当然如果想要把自己的idea实现出来还是要多翻翻api,更多的了解才行;没注意的话,最简单的学习方法还是在这个demo的基础上舔砖加瓦,看看能健壮到什么地步,比如添加开始、暂停、重开的按钮,触屏交互事件,tips弹窗,设计关卡,场景过渡之类的,总之学海无涯回头是岸。