cocos creator 笔记——做一个弹幕游戏
本帖最后由 flakedzz 于 2016-11-17 16:27 编辑才开始接触ccc,前几天囫囵的看了一遍文档,又把文档自带的游戏做了一下。
这几天在考虑做循环滚动的2D背景
想法:ABC三个图片,游戏窗口显示中间,三张图片依次向下匀速滚动,当图片接触到特定坐标的时候,将图片坐标改变。
三张图片的运动类似:ABC→CAB→BCA→ABC(重复)
代码已经写出来了,把图片做成了Prefab,似乎不做成Prefab直接在场景中摆上去也可以,效果不太理想,图片与图片之间偶尔会有重叠或者空隙
猜想应该是向下运动的时候不一定会正好接触到特定坐标。
图片上挂的脚本如下
cc.Class({
extends: cc.Component,
properties: {
// foo: {
// default: null, // The default value will be used only when the component attaching
// to a node for the first time
// url: cc.Texture2D,// optional, default is typeof default
// serializable: true, // optional, default is true
// visible: true, // optional, default is true
// displayName: 'Foo', // optional
// readonly: false, // optional, default is false
// },
// ...
moveSpeed:0
},
// use this for initialization
onLoad: function () {
},
// getPos:function(){
// this.posX=this.picScoller.node.width;
// this.posY=this.picScoller.node.height;
// var moveAction=this.scollPic();
// this.node.runAction(moveAction);
// //this.node.destroy();
// },
// scollPic:function(){
// var moveDown=cc.moveBy(0.5,cc.p(this.posX*0,-this.posY*2));
// var moveUp=cc.moveBy(0.5,cc.p(this.posX*0,this.posY*2));
// return cc.repeatForever(cc.sequence(moveDown, moveUp));
// },
moveDown:function(dt){
this.node.y -= this.moveSpeed * dt;
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
//背景向下滚动
if(this.node.y<=-this.picScoller.posY)
this.node.y=this.picScoller.posY;
this.moveDown(dt);
},
});
注释掉的就别看了,失败的尝试。
接下来想试试直接在scene里放置结点看看效果,也许会解决重叠和空隙的问题。(重叠和空隙也可能不是代码问题,说不定我手残坐标选的不对?)
------------------
解决了,问题大概也能想到,
if(this.node.y<=-this.picScoller.posY)
this.node.y=this.picScoller.posY;之前是当坐标小于blabla的时候直接给这个结点的坐标赋值
如果这个坐标等于blabla的话,这样是没问题的
但是update并不能保证每一个像素都经过
所以很大概率是小于这个坐标而不是等于
修改之后的代码
if(this.node.y<=-960)
this.node.y+=1280;640是默认分辨率的宽度
1280正好可以把最下面的图片提到最上面
目前分辨率是写死的,如果做自动适应的话需要在Canvas下挂一个读当前分辨率的脚本来知道应该+多少像素,懒,以后再写这个。
---------------------------------------然后……试着写一下控制角色移动,再然后试着做一下敌机,再再然后是敌机factory。(饭要一口一口吃
似乎一个STG的原型就出来了
2016年11月16日21:53:18
好的,现在把角色移动给写出来了。
现在的想法是做一个能够8方向移动的角色就好(键盘操控,暂时不想加手柄输入)
在角色结点添加一个监听器监听键盘输入(keyUp&keyDown)
当监听到keyDown的时候判断是哪个键,如果是w按下,就把角色的spriteFrame(可以理解成显示的图片)变更为正常状态,然后把runToUp赋值为真
以此类推,把wasd四个按键的按下事件都做好。
keyUp就是把四个控制方向的布尔型变量按照对应方向赋值为假(四个变量 runToUp,runToDown,runToLeft,runToRight)
然后在update函数里通过这四个变量的真假来控制角色移动的方向
代码如下:
cc.Class({
extends: cc.Component,
properties: {
// foo: {
// default: null, // The default value will be used only when the component attaching
// to a node for the first time
// url: cc.Texture2D,// optional, default is typeof default
// serializable: true, // optional, default is true
// visible: true, // optional, default is true
// displayName: 'Foo', // optional
// readonly: false, // optional, default is false
// },
// ...
idle:{
default:null,
type:cc.SpriteFrame
},
left:{
default:null,
type:cc.SpriteFrame
},
right:{
default:null,
type:cc.SpriteFrame
},
moveSpeed:0,
},
// use this for initialization
onLoad: function () {
this.sprite=this.getComponent(cc.Sprite);
//sprite.spriteFrame.setTexture(cc.url.raw('ninganyi.jpg'));
this.setInputControl();
},
setInputControl:function(){
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN,
this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP,
this.onKeyUp, this);
},
onKeyDown:function(event){
//self=this;
//switch(event.keyCode){
// case cc.KEY.a:
// this.sprite.spriteFrame=this.left;
// this.enabled=false;
// this.enabled=true;
// this.runToLeft=true;
// break;
// case cc.KEY.d:
// this.sprite.spriteFrame=this.right;
// this.enabled=false;
// this.enabled=true;
// this.runToRight=true;
// break;
// case cc.KEY.w:
// this.sprite.spriteFrame=this.idle;
// this.enabled=false;
// this.enabled=true;
// this.runToUp=true;
// break;
// case cc.KEY.s:
// this.sprite.spriteFrame=this.idle;
// this.enabled=false;
// this.enabled=true;
// this.runToDown=true;
// break;
// }
if(event.keyCode==cc.KEY.a){
this.sprite.spriteFrame=this.left;
this.enabled=false;
this.enabled=true;
this.runToLeft=true;
}
if(event.keyCode==cc.KEY.d){
this.sprite.spriteFrame=this.right;
this.enabled=false;
this.enabled=true;
this.runToRight=true;
}
if(event.keyCode==cc.KEY.w){
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;
this.runToUp=true;
}
if(event.keyCode==cc.KEY.s){
this.sprite.spriteFrame=this.left;
this.enabled=false;
this.enabled=true;
this.runToDown=true;
}
},
onKeyUp:function(event){
//switch(event.keyCode){
// case cc.KEY.a:
// this.sprite.spriteFrame=this.idle;
// this.enabled=false;
// this.enabled=true;
// this.runToLeft=false;
// break;
// case cc.KEY.d:
// this.sprite.spriteFrame=this.idle;
// this.enabled=false;
// this.enabled=true;
// this.runToRight=false;
// case cc.KEY.w:
// this.runToUp=false;
// case cc.KEY.s:
// this.runToDown=false;
// }
if(event.keyCode==cc.KEY.a){
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;
this.runToLeft=false;
}
if(event.keyCode==cc.KEY.d){
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;
this.runToRight=false;
}
if(event.keyCode==cc.KEY.w){
this.runToUp=false;
}
if(event.keyCode==cc.KEY.s){
this.runToDown=false;
}
},
// setInputControl:function(){
// var self=this;
// cc.eventManager.addListener({
// event:cc.EventListener.KEYBOARD,
// onKeyPressed:function(keyCode,event){
// switch(keyCode){
// case cc.KEY.a:
// self.sprite.spriteFrame=self.left;
// self.enabled=false;
// self.enabled=true;
// self.runToLeft=true;
// break;
// case cc.KEY.d:
// self.sprite.spriteFrame=self.right;
// self.enabled=false;
// self.enabled=true;
// self.runToRight=true;
// break;
// case cc.KEY.w:
// self.sprite.spriteFrame=self.idle;
// self.enabled=false;
// self.enabled=true;
// self.runToUp=true;
// break;
// case cc.KEY.s:
// self.sprite.spriteFrame=self.idle;
// self.enabled=false;
// self.enabled=true;
// self.runToDown=true;
// break;
// }
// },
// onKeyReleased:function(keyCode,event){
// switch(keyCode){
// case cc.KEY.a:
// self.runToLeft=false;
// break;
// case cc.KEY.d:
// self.runToRight=false;
// case cc.KEY.w:
// self.runToUp=false;
// case cc.KEY.s:
// self.runToDown=false;
// }
// }
// },self.node);
// },
// called every frame, uncomment this function to activate update callback
update: function (dt) {
//this.SpriteFrame=this.left;
if(this.runToLeft){
this.node.x -=this.moveSpeed*dt;
}
if(this.runToRight){
this.node.x+=this.moveSpeed*dt;
}
if(this.runToUp){
this.node.y+=this.moveSpeed*dt;
}
if(this.runToDown){
this.node.y-=this.moveSpeed*dt;
}
},
});
同样的,注释掉的不用看,是另一种尝试,不过效果不太好。(才发现,代码缩进怎么没了?)
这里着重讲一下下面这段代码:
this.sprite=this.getComponent(cc.Sprite);
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;一行一行解读:
获取当前结点的Sprite类型的组件,赋值给全局变量sprite
将idle赋值给sprite的spriteFrame属性(idle是spriteFrame类型,可以看上面那段代码的properties区域)
禁用当前结点
启用当前结点
我之前在替换精灵的图片的时候只写了前两行,没有效果。
加上后面两行之后效果正常,我也不太懂这是为什么,如果有谁知道请务必给我讲解一下。猜测是更换图片之后需要刷新一下?而后两行代码起到了刷新的作用?
顺便附上用到的角色图片
是从rpgmaker里截出来的
角色的移动大概就这样了。之后试着做一下敌机和自机的攻击。
---------------------------------
2016年11月17日14:59:55
感觉弹幕游戏中,向左向右移动的时候角色脸也跟着变好像有点奇怪,先不改了,等代码写完再说素材的事。
顺便加了慢速移动的代码,按住shift的时候移动速度降低50%,松开恢复。
代码很简单,不贴了
从文档里找到了KEY的枚举值,把wasd换成了上下左右,更正常了。
下面是目前进度
右侧黑条预计用来做计分板,现在还没开工。
左侧的滚动图片是自己用PS做的
用起来效果还可以
------------------------------------------
2016年11月17日15:09:48
接下来准备做自机的武器,还在考虑要怎么升级。要不先做敌机?(好的)
好了,敌人画好了。
……………
至少大家知道它是敌人了,对吧------------------------------
2016年11月17日16:26:31
设置好了碰撞体还有碰撞分组,边界处理还是没做,懒癌发作,休息一会儿
------------------------------
本帖最后由 flakedzz 于 2016-11-19 18:03 编辑
2016年11月18日21:07:12
今天感冒了,一行没写,罪过2016年11月19日17:59:58
休息了快两天,继续做。
先来想一下敌机自机的武器和自机武器的升级
击毁一架敌机掉落P点?
直接得分的话感觉太容易了一点,做个击毁敌机会掉落P点的小功能好了。
当敌机被击毁,会在一定范围内随机选取几个坐标生成P点对象。
和自机接触后自机获得得分。
这个放后面做。
那先做自机的弹幕好了。
把得分暴露出来,用来在没有收集P点的时候可以手动调整数值来测试弹幕的武器等级。
ccc这引擎的界面和东方正作的好像啊
{:39:}
最近看了一篇基于unity2d的制作打飞机教程,挺有趣
http://space.bilibili.com/32264724/#!/video/0//1 坡忒头 发表于 2016-11-22 19:27
ccc这引擎的界面和东方正作的好像啊
最近看了一篇基于unity2d的制作打飞机教程,挺有趣
之前做过unity的,但是感觉太臃肿,这次顺便学学ccc怎么用,就用这个做了 2016年11月26日19:44:22
摸了几天鱼,今天着手写了自机弹幕的发射。
大概思路如下:
创建一个精灵节点,调整好材质之后,挂上去一个让它自动向前移动和在几秒后销毁的脚本。然后把这个精灵节点设置成Prefab
然后在触发按键事件之后按照一定速率重复生成该Prefab。【注意:这种方式有重大缺陷,重复的创建销毁对象,对性能的影响非常大。这里只做演示,完成后会改成用数组存储弹幕缓存的方式来进行优化】要注意的是,生成新的对象Prefab之后,将其加入到角色的子节点。这样设置坐标的时候就可以以角色本身的坐标系来进行。
这样就完成了子弹的发射。
如果要完成子弹和敌机的碰撞功能,需要在子弹节点和敌机上添加碰撞体。然后将碰撞进行分组。我设置的是playerBullet碰撞enemy。
这样当带有这两个标签的,带有碰撞体的节点相互碰撞,就会触发 onCollisionEnter()onCollisionStay()onCollisionExit()三个方法。
需要注意的是,碰撞管理默认情况下是关闭的,所以需要用代码手动启用。
子弹脚本如下:
cc.Class({
extends: cc.Component,
properties: {
// foo: {
// default: null, // The default value will be used only when the component attaching
// to a node for the first time
// url: cc.Texture2D,// optional, default is typeof default
// serializable: true, // optional, default is true
// visible: true, // optional, default is true
// displayName: 'Foo', // optional
// readonly: false, // optional, default is false
// },
// ...
shootSpeed:0,
},
onCollisionEnter: function (other, self) {
console.log('on collision enter');
self.node.destroy();
},
onCollisionStay: function (other, self) {
console.log('on collision stay');
},
onCollisionExit: function (other, self) {
console.log('on collision exit');
other.node.destroy();
},
// use this for initialization
onLoad: function () {
this.counter=0;
var manager = cc.director.getCollisionManager();
manager.enabled = true;
},
move:function(dt){
this.node.y+=this.shootSpeed*dt;
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
this.counter++;
this.move(dt);
//if(this.node.y>this.game.height*0.5||this.node.y<0-game.height*0.5||this.node.x<0-this.game.width*0.5||this.node.x>this.game.width*0.174)
if(this.counter>=100)
this.node.destroy();
},
});
添加了发射功能的角色脚本如下:
cc.Class({
extends: cc.Component,
properties: {
// foo: {
// default: null, // The default value will be used only when the component attaching
// to a node for the first time
// url: cc.Texture2D,// optional, default is typeof default
// serializable: true, // optional, default is true
// visible: true, // optional, default is true
// displayName: 'Foo', // optional
// readonly: false, // optional, default is false
// },
// ...
idle:{
default:null,
type:cc.SpriteFrame
},
left:{
default:null,
type:cc.SpriteFrame
},
right:{
default:null,
type:cc.SpriteFrame
},
normalBullet:{
default:null,
type:cc.Prefab
},
game:{
default:null,
type:cc.Node
},
moveSpeed:0,
fireSpeed:0,//普通攻击速率
specialBomb:0,//特殊攻击可用次数
weaponLevel:0,//武器等级
weaponPosX:0,
weaponPosY:0,
},
// use this for initialization
onLoad: function () {
this.counter=0;
this.sprite=this.getComponent(cc.Sprite);
this.setInputControl();
var manager = cc.director.getCollisionManager();
manager.enabled = true;
},
cannonControl:function(){//直接在自机前方生成子弹
},
setInputControl:function(){
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN,
this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP,
this.onKeyUp, this);
},
onKeyDown:function(event){
if(event.keyCode==cc.KEY.z){
this.cannonFire=true;
}
if(event.keyCode==cc.KEY.left){
this.sprite.spriteFrame=this.left;
this.enabled=false;
this.enabled=true;
this.runToLeft=true;
}
if(event.keyCode==cc.KEY.right){
this.sprite.spriteFrame=this.right;
this.enabled=false;
this.enabled=true;
this.runToRight=true;
}
if(event.keyCode==cc.KEY.up){
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;
this.runToUp=true;
}
if(event.keyCode==cc.KEY.down){
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;
this.runToDown=true;
}
if(event.keyCode==cc.KEY.shift){
this.moveSpeed=this.moveSpeed*0.5;
}
},
onKeyUp:function(event){
if(event.keyCode==cc.KEY.left){
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;
this.runToLeft=false;
}
if(event.keyCode==cc.KEY.right){
this.sprite.spriteFrame=this.idle;
this.enabled=false;
this.enabled=true;
this.runToRight=false;
}
if(event.keyCode==cc.KEY.up){
this.runToUp=false;
}
if(event.keyCode==cc.KEY.down){
this.runToDown=false;
}
if(event.keyCode==cc.KEY.shift){
this.moveSpeed=this.moveSpeed*2;
}
if(event.keyCode==cc.KEY.z){
this.cannonFire=false;
}
},
normalAttackLv1:function(dt){
if(this.counter>=this.fireSpeed){
var bullet=cc.instantiate(this.normalBullet);
this.node.addChild(bullet);
bullet.setPosition(0,0);
bullet.getComponent("NormalBulletScript").game=this.game;
this.counter=0;
}
else{
this.counter++;
}
//cc.log(this.node.x+"x"+this.node.y);
},
normalAttackLv2:function(dt){
},
normalAttackLv3:function(dt){
},
boardCheck:function(){
var screenWidth=this.game.width;
var screenHeight=this.game.height;
if(this.node.x<0-0.5*screenWidth)
this.node.x=0-0.5*screenWidth;
if(this.node.x>0.174*screenWidth)
this.node.x=0.174*screenWidth;
if(this.node.y<0-0.5*screenHeight)
this.node.y=0-0.5*screenHeight;
if(this.node.y>0.5*screenHeight)
this.node.y=0.5*screenHeight;
},
moveControl:function(dt){
if(this.cannonFire){
this.normalAttackLv1(dt);
}
if(this.runToLeft){
this.node.x -=this.moveSpeed*dt;
}
if(this.runToRight){
this.node.x+=this.moveSpeed*dt;
}
if(this.runToUp){
this.node.y+=this.moveSpeed*dt;
}
if(this.runToDown){
this.node.y-=this.moveSpeed*dt;
}
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
if(this.cannonFire){
this.normalAttackLv1(dt);
}
this.moveControl(dt);
this.boardCheck();
},
});
我的命名不规范,还请不要学习我。
如果仔细看代码的话会发现我创建了三个类似的攻击函数,并只实现了一个。
剩下的两个是为了给拾取道具增强火力做出的预留。【并非最终版本,设计随时可能会变更】
接下来要实现至少一种的敌人和敌人的弹幕设计。
敌人和敌人的弹幕设计都做好之后,会进行第一阶段的优化。
将弹幕以及敌人进行缓存处理。
这次更新就到这里。
一般这种js的大项目我都是建议用TypeScript,有静态类型检查和完整的面向对象,写完以后再转成js( tinyAdapter 发表于 2016-11-28 15:21
一般这种js的大项目我都是建议用TypeScript,有静态类型检查和完整的面向对象,写完以后再转成js(
也不算大型了,js没有类是挺困扰,不过ccc用js u3d用C#,已经习惯了 2016年12月12日22:22:03
A了A了,我要摸鱼去了。更日志哪有摸鱼爽啊。 楼楼好厉害……Orz(仰望 谢谢dalao
萌新正在努力肝。。。。。。
页:
[1]
2