超能陆战队
注:美术资源来源于网络
作者:
是烧麦啊未生畏死
游戏类型:动作类
* 所用到的 IDE :
Cocos Creator v2.0.10,主要用于视觉还原、主逻辑开发、跨端调试和编译。个人觉得这是超给力的游戏开发工具,把 component 的机制直接可视化,集成各种物理引擎、粒子引擎、UI 组件等功能,大大节省了游戏UI以及部分特效逻辑的研发成本。但小游戏版本的编译速度还是很慢啊。Visual Stuido Code,用于代码开发。这好像是最近最流行的代码撰写工具了,免费且稳定啊!Google Chrome,web版游戏调试。有些时候一些性能调优可以放在 chrome 上面,它有非常专业的调试工具。游戏的主要玩法:
摇杆控制人物躲避怪物并且用子弹或者技能杀死他们
* 实现功能:
摇杆控制人物移动长按子弹键一直发射子弹限制人物移动区域骨骼动画的切换人物机甲的子弹反弹选择关卡弹夹掉落炸弹怪索敌移动和爆炸蜜蜂怪尾刺功能蜘蛛怪移动和攻击锯齿怪移动和碰撞判断魔龙怪两种攻击方式机器人boss攻击冰冻道具功能实现护盾道具功能实现轰炸道具功能实现奖励箱功能实现欢呼通告功能实现角色怪物视角朝向功能实现是烧麦啊 实现模块
1.摇杆功能的实现
先在摇杆底图上挂载一个脚本,在他的面板属性上添加一个节点,在设置他的最远距离限制,然后将我们的摇杆拖到这个面板属性上的节点上
cc.Class({extends: ponent,properties: {Rocker: cc.Node,Max_r: 60,},start() {this.qishi = cc.v2(0, 0) this.Rocker.on(cc.Node.EventType.TOUCH_START, function (e) {var w_pos = e.getLocation(); //先获取触摸节点的坐标 var pos = this.node.convertToNodeSpaceAR(w_pos); //转换为在 父节点下的坐标if (pos.mag() > 60) {pos.normalizeSelf();pos = cc.v2(pos.x * 60, pos.y * 60);}this.Rocker.setPosition(pos);cc.director.emit("遥杆", pos.x, pos.y)}, this);
这是摇杆的代码,先设置一个起始位置,就是(0,0)
先注册一个触摸事件,在触摸时和移动时都先获取触摸节点的坐标,然后转换为父节点下的坐标,如果距离超过了限制距离 就将它归一化在乘60 如果没超过限制距离的话就直接赋值给他的position然后发射“摇杆”事件将参数传去给人物,人物就可以根据摇杆的方向去移动了
手指抬起时,位置复位到起始位置。
2.长按发射键的实现
长按子弹键发射子弹的功能实现我使用的是图片代替Button,然后在精灵上注册一个触摸事件,当你按下时就发射子弹事件,就能做到一直连发子弹,而如果使用Button的话只能一发一发的发射子弹下面是我实现的代码
this.qiang1.on(cc.Node.EventType.TOUCH_START, function () {cc.director.emit("开火1");this.qiang1.scale = cc.v2(0.9, 0.9);this.schedule(this.shoot1, 0.2);}, this)
当你按下子弹建的同时要缩放图片,这里是接受开火事件的代码块,先获取子弹的Label组件,然后读取他的值是否为0 当子弹数量为0时return出去不执行下面代码,如果不为零则获取主角身上的动画组件,让他播放相对应的枪械动画,并且设置动画播放模式为一直循环
cc.director.on("开火1", function () {let zidan1 = this.attack1.getComponent(cc.Label)if (zidan1.string == 0) {return;}var anim = this.node.getComponent(cc.Animation);let animState = anim.play('Pler clip');animState.wrapMode = cc.WrapMode.Loop;anim.playAdditive('zidan1');this.yidalipao(this.obj1, 0);this.danjiasc();
3.人物限制移动区域
人物限制的方法我是在摇杆移动赋值给人物前每帧进行判断,判断人物是否超出边界,如果左边到达边界则只加上下移动的值只需要做if的判断就行了
超能战队
4.人物的骨骼动画切换
先获取人物的骨骼动画组件然后给他注册骨骼动画播放完一次后的监听事件
setCpmpleteListener()下面是具体代码
onLoad() {var ske = this.node.getComponent(sp.Skeleton);this.ske = ske;},start() {let all = cc.find("Canvas");let acc = cc.find("Canvas/yaogan");all.pauseSystemEvents(true);this.ske.setAnimation(0, "ruchang", false);this.ske.setCompleteListener((trackEntry) => {if (trackEntry.animation.name == "ruchang") {this.ske.setAnimation(0, "zhongjian", false);} else if (trackEntry.animation.name == "zhongjian") {this.ske.setAnimation(0, "jiewei", false);} else if (trackEntry.animation.name == "jiewei") {acc.resumeSystemEvents(true);//恢复所有触摸点击事件this.scheduleOnce(() => {this.node.destroy();cc.director.emit("hudun1");cc.director.emit("nuosijj");}, 0.5)}})},
获取当前骨骼的动画的名字,然后进行骨骼动画的排序播放,我是设定了播放时屏蔽了所有的触摸点击事件,然后播放完后恢复,并且发射护盾,以及诺斯机甲的事件,别的地方监听后作出护盾和子弹导弹的发射
5.人物机甲导弹触壁反弹
人物机甲的子弹反弹功能实现,先在诺斯机甲上设立几个空节点作为她的发射点和计算向量的点
然后通过我们设立的2个空节点获取一条直线
let fashedian = this.toWorldPosition(this.node.getChildByName("2")); //发射点let paoguan = this.toWorldPosition(this.node.getChildByName("1")) //炮管 2个点得到一条直线计算弧度角度和方向let fashedian1 = this.toWorldPosition(this.node.getChildByName("4")); //发射点let it = cc.instantiate(this.jijiazidan);//实例化子弹it.parent = cc.find("Canvas");//父节点this.shuzu.push(it)if(this.zy == true ){it.position = fashedian1 }else{it.position = fashedian; //导弹出生点为发射点} let isx = fashedian.x -paoguan.x; let isy = fashedian.y - paoguan.y;
然后生成子弹的预制体。并且放到一个空数组中
然后调用接口转换得到角度,根据子弹的角度去判断他是往什么方向上去运行
然后就能得到他相撞后的那个位置(就举一个方向为例子)
let lastAngle = cc.misc.degreesToRadians(-it.rotation); //将之前赋值过得it的roattion转换得到幅度let tanValue = Math.tan(lastAngle);let py = -cc.winSize.height / 2;//已知的一个点的x坐标 -已知的一个点的y坐标-相撞后的y坐标(因为是上边界所以可以得到Y坐标 就是屏幕高度除以2)在调用Math.tan方法传入这条直线的幅度let px = it.x - (it.y - py) / tanValue;return cc.v2(px, py);
得到位置后直接调用moveTo方法让子弹移动到那里就行,这里要注意子弹反弹时的旋转角度
这里是反弹的具体代码
this.startPos = it.position;this.endPos = destlet distance = this.startPos.sub(this.endPos).mag() / 500//顺序动作先执行移动移动执行完后在调用自身var seq = cc.sequence(cc.moveTo(distance, dest),cc.callFunc(function () {if (it.curDir == "leftUp") {if (it.rotation < 0) {it.rotation = -180 - it.rotation;} else {it.rotation = - it.rotation;}} else if (it.curDir == "rightDown") {if (it.rotation < 0) {it.rotation = - it.rotation;} else {it.rotation = 180 - it.rotation;}}else if(it.curDir == "leftDown"){if (it.rotation < 0) {it.rotation = - it.rotation;} else {it.rotation = 180 - it.rotation;}}else{if (it.rotation < 0) {it.rotation = -180- it.rotation;} else {it.rotation = - it.rotation;}}this.movingAction(it);}, this)); it.runAction(seq);
反弹后我是根据子弹的旋转角度和他现在要飞行的方向来计算他现在的角度应该是多少
举个例子现在他反弹后飞行的角度是左上角,只有2种情况是会反弹左上角的,
一种是碰到右边墙壁此时子弹的角度是负的,要往左上角只需要将它当前角度转正就可以了,
还有一种情况是碰到下面的墙壁往左上角弹,此时子弹没有反弹时的角度是正的,180减去她的角度在转换为负数就可以得到反弹后的角度
然后递归调用函数直到子弹发生碰撞或者变身时间结束
超能战队
6.选择关卡的功能实现
使用的是Scrollview组件设置好大小,然后使用图片拼接
cc.director.on("zuobiao",function(x){this.node.getComponent(cc.ScrollView).scrollToPercentHorizontal((x.x-50)/3200);},this);
进入场景时将人物小头像的左边通过事件的方式传到函数里,然后调用Scrollview组件的scrollToPercentHorizontal方法就可以设置你
组件移动的百分比,就能达到下面的效果,进入选关场景时人物头像在中间不用从最左侧开始滑
7.弹夹掉落
弹夹的生成我是使用得预制体的方式去实现的,然后给弹夹添加一个自动义动画让它旋转起来,
而弹夹掉落的曲线我用的方法是贝塞尔曲线,下面是具体的代码。
let a = Math.floor(Math.random() * 200) // 随机生成是为了让它的曲线稍有不同不至于很单调的一条曲线let b = Math.floor(Math.random() * 50) + 350;let d = Math.floor(Math.random() * 10)if (d % 4 === 0) { //是为了弹夹弹飞的方向不同let itc = cc.bezierTo(1.2, [cc.v2(xsj.x + a, xsj.y + 300),cc.v2(xsj.x + a + 50, xsj.y - b),cc.v2(xsj.x + a, xsj.y - b),]);it.runAction(itc)} else {let cad = cc.bezierTo(1.2, [cc.v2(xsj.x - a, xsj.y + 300),cc.v2(xsj.x - a - 50, xsj.y - b),cc.v2(xsj.x - a, xsj.y - b),]);it.runAction(cad)}this.scheduleOnce(() => {it.destroy();}, 1) //延迟一秒后删除
未生畏死 实现模块:
AI生成随机在半个地图外生成AI,然后让AI移动到地图内。
bornPosition() {//出生位置let it = cc.find("Canvas/AiPanrent");//怪物父节点 置(0,0);this.node.parent = it;let posX = Math.random() * (cc.winSize.width + this.node.width) * 2 - (cc.winSize.width + this.node.width);//x,ylet posY = Math.random() * (cc.winSize.height + this.node.height) * 2 - (cc.winSize.height + this.node.height);if ((posX > (-cc.winSize.width) / 2 && posX < (cc.winSize.width) / 2) || (posY > (-cc.winSize.height) / 2 && posY < (cc.winSize.height) / 2)) {//在地图内则递归刷新位置this.bornPosition();//出生位置if (this.node.x < -cc.winSize.width / 2 - this.node.width / 2) {this.node.x = -cc.winSize.width / 2 - this.node.width / 2;}.......}else {//不在地图内则确定位置this.node.x = posX;this.node.y = posY;// return cc.v2(this.node.x, this.node.y);}},
AI
炸弹怪。保持速度向玩家移动,移动到一定距离内,快速飞行到玩家处爆炸,给予玩家定值伤害。
if (this.AiState == AiAllState.Walk) {//行走状态let target = this.targetPosition();//目标位置let isx = target.x - this.node.x;//x,y插值let isy = target.y - this.node.y;let dis = cc.v2(isx, isy).normalizeSelf();this.node.x += dis.x * this.speed;this.node.y += dis.y * this.speed;if (cc.v2(isx, isy).mag() < 95) {this.node.removeComponent(cc.PolygonCollider);let moveto = cc.moveBy(0.1, cc.v2(isx, isy));this.node.runAction(moveto);this.AiState = AiAllState.SelfDetonate;//变更为自爆状态}
超能战队
蜜蜂怪
每隔一段事件进入攻击状态,尾刺对着玩家,获取玩家位置为目标点,飞刺到目标点。如果尾刺碰到玩家则给予玩家一定伤害。
if (this.AiState == AiAllState.Walk) {//行走状态let target = this.targetPosition();//目标位置let isx = target.x - this.node.x;//x,y插值let isy = target.y - this.node.y;let dis = cc.v2(isx, isy).normalizeSelf();this.node.x += dis.x * this.speed;this.node.y += dis.y * this.speed;let newTime = new Date().getTime();if ((newTime - this.time) >= this.AttackGap * 1000) {this.attackPlayer(target);//攻击玩家this.AiState = AiAllState.AttackStart;//变更为开始攻击状态this.time = newTime;}}else if (this.AiState == AiAllState.AttackStart) {//开始攻击状态// this.AiState = AiAllState.AttackTime;//变为攻击中状态let newTime = new Date().getTime();if ((newTime - this.time) >= 1.2 * 1000) {this.time = newTime;this.AiState = AiAllState.Walk;//变更为行走状态this.node.getComponent(cc.Animation).play("monster2_1");//播放动画}}
超能战队
蜘蛛怪
初始状态,会向下突出蜘蛛丝,然后向下移动。
如果到达蜘蛛丝的两端会待机一段时间,然后向相反方向移动。
如果玩家碰到蜘蛛,则蜘蛛会吐出蜘蛛网,玩家被网住,一段时间不能移动。
if (this.AiState == AiAllState.Walk) {//移动状态let ySub1 = cc.winSize.height / 2 - 50;//上限let ySub2 = cc.find("Canvas/budong UI/" + this._line.name).y;//下限this.node.y += this.speed;if (this.speed > 0) {if (this.node.y > ySub1) {this.AiState = AiAllState.Stand;//站立状态}} else {if (this.node.y < ySub2) {this.AiState = AiAllState.Stand;//站立状态}}}else if (this.AiState == AiAllState.Stand) {//站立状态this.AiState = AiAllState.Standby;//待机状态this.scheduleOnce(() => {//站立3秒this.speed = -this.speed;this.AiState = AiAllState.Walk;this.node.rotation += 180;}, 3)}
超能战队
锯齿怪
随机一个目标点,、转圈圈移动到目标点。
如果碰到玩家,则造成伤害。
if(this.AiState==AiAllState.Walk){this.AiState=AiAllState.Standby;let it =this.targetPosition() ;//目标位置let isx=it.x-this.node.x;let isy=it.y-this.node.y;let v=cc.v2(isx,isy).normalizeSelf();let moveBy = cc.moveBy(8, cc.v2(v.x*cc.winSize.width*1.5, v.y*cc.winSize.width*1.5));this.node.runAction(cc.sequence(moveBy,cc.removeSelf()));}
魔龙怪
每隔一段时间攻击一次,攻击方式随机。
攻击方式一:丢炸弹,用贝塞尔曲线算了一个随机曲线。如果炸弹碰到玩家则爆炸,给予玩家一定伤害。炸弹自然落到终点也会爆炸。
3.攻击方式二:发散弹。喷射类似魂斗罗里面散弹的东西。如果散弹碰到玩家则给予玩家一定伤害,散弹飞到地图外销毁。
attack1(num) {//攻击方式1//激光 发射夹角90°num = 6;let destance = 1000;let startAngel = 225;let maxAngel = 60;for (let i = 0; i < num; i++) {let zidan = cc.instantiate(this.zidan1);zidan.parent = cc.find("Canvas/EffectPanrent");let angel = startAngel - i * maxAngel / (num - 1);let rad = cc.misc.degreesToRadians(angel);let desty = Math.sin(rad) * destance;let destx = Math.cos(rad) * destance;zidan.rotation = 360 - angel;zidan.position = this.node.position;zidan.runAction(cc.sequence(cc.moveBy(3, cc.v2(destx, desty)),cc.removeSelf()));}},attack2(num) {//攻击方式2// 炸弹for (let i = 0; i < num; i++) {let zidan = cc.instantiate(this.zidan2);zidan.position = this.node;zidan.parent = cc.find("Canvas/EffectPanrent");let randomX = Math.random() * (cc.winSize.width - this.node.width) - (cc.winSize.width - this.node.width) / 2;let bezierTo = cc.bezierTo(3.1, [cc.v2(this.node.x, this.node.y),cc.v2(this.node.x - cc.winSize.width / 7, cc.winSize.height / 2),cc.v2(randomX, -cc.winSize.height * 0.4)]);zidan.runAction(cc.sequence(cc.spawn(bezierTo,cc.rotateBy(3, 360),),cc.callFunc(() => {cc.director.emit("hit",zidan,this.zidan1Effect);//炸弹爆炸特效zidan.destroy();})),)}},
道具
冰冻使用冰冻道具,能冻结所有怪物一定时间。引入暂停状态,ai不做任何处理,冻结结束后,恢复。
this.pauseAll(this.node.parent.parent);//暂停this.scheduleOnce(() => {this.resumeAll(this.node.parent.parent);//恢复动作this.node.destroy();}, 3);护盾使用护盾后,会生成护盾,保护玩家免受伤害一段时间。护盾会将碰到它的怪物击退。就一个collider处理。this.node.x = 10000;this.startTime = 0;this.allTime = 0;this.hd1 = false;cc.director.on("hudun", () => {cc.director.emit("护盾");if (this.allTime == 0) {this.allTime = 5;this.startTime = new Date().getTime();this.node.x = 0;}else {this.allTime += 3;}});
导弹
使用后召唤导弹,轰炸地图。所有怪物暂定,播放导弹轰炸动画,结束后恢复正常。
all.pauseSystemEvents(true);//暂停所有触摸点击事件this.pauseAll(all);//遍历子节点cc.loader.loadRes("sounds/hedan",cc.AudioClip,(err,clip)=>{cc.audioEngine.pauseMusic();cc.audioEngine.playEffect(clip,false);cc.audioEngine.setEffectsVolume(0.8);})this.scheduleOnce(() => {this.bg.destroy();this.xian.destroy();this.daodan.destroy();this.hongzha.x = 0;cc.loader.loadRes("sounds/hebao",cc.AudioClip,(err,clip)=>{cc.audioEngine.playEffect(clip,false);cc.audioEngine.setEffectsVolume(0.8);})let it = _hongzha.setAnimation(0, "hongzha", false);if (it) {// 注册动画的结束回调_hongzha.setCompleteListener((trackEntry) => {if (trackEntry.animation.name == "hongzha") {cc.audioEngine.resumeMusic();this.resumeAll(all);//遍历子节点all.resumeSystemEvents(true);//恢复所有触摸点击事件this.node.destroy();}})}}, 3);
超能战队
其他
喝彩生成一个喝彩数组,杀死一定数量敌人则播放喝彩动画。
玩家视角
始终朝向最近的AI。实现:遍历AI数组,找到最近的AI,计算角度,旋转。
奖励箱
杀死AI会掉落奖励箱,箱子会向左或右移动。玩家可拾取。
打开后,可能是道具、武器、或者一无所获。