HELLO,大家好,我是麒麟子。作为Cocos社区高产用户,今天又给大家带来了一个看起来很酷,但实际上大多数人用不到的DEMO。

不知道大家是否记得梦幻西游、问道、英雄无敌、仙剑奇侠传、神仙道、神曲OL。

不知道大家是否最近在玩自走棋(哎哟,不错哟,最近特别火)

然而,他们从技术上讲,没有本质的区别,他们的战斗都是回合制。 

回合制的游戏就像棋牌一样,每一个回合,同一时间,只有一个人能操作。 等这个人表演完。再交给下一个。

DEMO内容:


这个DEMO展示了一个战斗场景,玩家可以控制一个英雄去攻击目标。 英雄有6个技能,当玩家点击某个技能的时候,英雄会冲到目标跟前,翻云覆云一番,再退回自己的位置。

有在线演示可以看哦:https://qilinzi.ukylin.net/?lesson=08
<https://qilinzi.ukylin.net/?lesson=08>

当然,还有,源码:https://gitee.com/qilinzi/qlz_ccc_tips
<https://gitee.com/qilinzi/qlz_ccc_tips>

一、动作与特效


在这个例子中,英雄的动作和特效是在同一张图上的。显然,这样的方式不适合像传奇这样的MMORPG。 但是对于不换装的游戏,是完全没有问题的。还能省下不少DrawCall。

值得注意的是,我们在这个例子中,并没有使用Cocos
Creator中的Animation来编辑英雄动画。 因为一个游戏有上百上千种动画,如果一个个手工编辑的话,是要死人的。 SO。。。 我们自己手写了一个。 请看大屏幕。
//动画信息配置 var AnimConfig = { } AnimConfig['0001'] = {
'attack':{frames:8,fps:8}, 'attacked':{frames:1,fps:8},
'combat_idle':{frames:4,fps:8}, 'idle':{frames:4,fps:8},
'ride_idle':{frames:4,fps:8}, 'ride_run':{frames:8,fps:8},
'run':{frames:8,fps:8}, 'rush':{frames:1,fps:8}, 'spell1':{frames:8,fps:8},
'spell2':{frames:8,fps:8}, 'spell3':{frames:14,fps:8},
'spell4':{frames:8,fps:8}, 'spell5':{frames:4,fps:8},
'spell6':{frames:10,fps:8}, } AnimConfig['0003'] = { 'attack':{frames:8,fps:8},
'attacked':{frames:1,fps:8}, 'combat_idle':{frames:4,fps:8},
'idle':{frames:4,fps:8}, 'ride_idle':{frames:4,fps:8},
'ride_run':{frames:8,fps:8}, 'run':{frames:8,fps:8}, 'rush':{frames:1,fps:8},
'spell1':{frames:8,fps:8}, 'spell2':{frames:8,fps:8},
'spell3':{frames:14,fps:8}, 'spell4':{frames:8,fps:8},
'spell5':{frames:4,fps:8}, 'spell6':{frames:10,fps:8}, } export default class
NewClass { public static getRoleInfo(roleId){ return AnimConfig[roleId]; } }
上面的类用于配置我们对应角色的动画,每一个动画,有动画名,帧数,帧率 三个属性。 每一个角色都有一个编码,如0001,0003。
import AnimConfig from './08_anim_config'; const {ccclass, property} =
cc._decorator; @ccclass export default class NewClass extends cc.Component {
@property roleId:string = '0001'; @property defaultAnim:string = 'idle'; //
LIFE-CYCLE CALLBACKS: private _spriteFrames:Array<cc.SpriteFrame> = []; private
_lastStartTime = 0; private getAnimInfo(animName:string):any{ var info =
AnimConfig.getRoleInfo(this.roleId); return info[animName]; } onLoad () { }
playAnim(animName:string){ var aniInfo = this.getAnimInfo(animName);
if(!aniInfo){ return null; } this.defaultAnim = animName; this._lastStartTime =
Date.now(); var folder = 'roles/' + this.roleId + '/'; var arr = []; for(var i
= 0; i < aniInfo.frames; ++i){ var url = folder + animName + '/frame' + i;
arr.push(url); } cc.loader.loadResArray(arr,cc.SpriteFrame,function(err,arr){
this._spriteFrames = arr; }.bind(this)); } start () {
this.playAnim(this.defaultAnim); } update (dt) { if(!this._lastStartTime ||
!this._spriteFrames.length){ return; } var fps =
this.getAnimInfo(this.defaultAnim).fps; var index = Math.floor((Date.now() -
this._lastStartTime) / 1000 * fps); if(index > this._spriteFrames.length){
this.playAnim('combat_idle'); return; } index %= this._spriteFrames.length;
this.node.getComponent(cc.Sprite).spriteFrame = this._spriteFrames[index]; } }

在上面的代码中,playAnim被调用的时候,我们首先获取到动画的信息,然后使用cc.loader.loadResArray来加载动画所需要使用到的图片。待加载完毕后,放入对象变量中缓存。

update里,我们根据时间计算出当前帧。 

通过这样的方式,我们就可以基于配置和文件命名规则来实现大量的角色动画和NPC动画。 减少动画编辑的工作量。

二、攻击过程


回合制游戏,最大的特点就是玩家不需要控制角色位置。 所以,近身攻击是需要系统主动移动位置到目标跟前的。 攻击完毕后,又要移回来。 我们在这里,使用了cc.Action组合来做。先欣赏一下代码。
onCastSpell(event){ var animName = event.target.__meta; if(this._isSpelling){
return; } this._isSpelling = true; var arr = []; //切换成冲刺动画,并移动到目标跟前
arr.push(cc.spawn(cc.moveTo(0.3,this.target.node.position.sub(cc.v2(120,0))),cc.callFunc(function(){
this.hero.playAnim('rush'); },this)) ); //播放攻击动画
arr.push(cc.callFunc(function(){ this.hero.playAnim(animName); },this)); var
animInfo = AnimConfig.getRoleInfo(this.hero.roleId)[animName]; var playTime =
animInfo.frames / animInfo.fps; //等待攻击完成 arr.push(cc.delayTime(0.5 +
playTime)); //移回原来位置 arr.push(cc.moveTo(0.1,this.hero.node.position));
arr.push(cc.callFunc(function(){ this._isSpelling = false; },this)); var act =
cc.sequence(arr); this.hero.node.runAction(act); }
1、冲刺到目标面前

为了实现冲刺到目标面前,我们需要在切换动画的同时,移动角色位置。 Cocos
Creator提供了cc.spawn,构建出能够同时执行多个Action的组合Action。cc.callFunc我们理解为自定义Action,你可以在回调函数里编写你期望的逻辑。

2、攻击

我们只需要简单的使用cc.callFunc写一个自定义Action,进行攻击动作的播放即可。

3、等待播放完成

由于我们自己写的动画播放类,还未处理播放完成事件,所以在这里,我们使用了一个cc.delayTime来做等待,等待的时间,是根据动画帧率*帧数 + 一个固定值

4、回到原来位置

这个用cc.moveTo即可实现

5、把它们串起来


我们把所有的Action放入一个数组中,再使用cc.sequence,即可以构建出一个按顺序执行的cc.Action。 并把这个Action交给目标节点执行即可

6、其它

大家会看到一个this._isSpelling的变量,和这个相关的代码,均是为了防止用户在攻击过程中,多次点击出现BUG。

三、技能图标与池

技能图标的实现相当简单,就是把他们放到了一个Layout里面,只不过,我们是动态添加的节点。 在这里,麒麟子使用了一套常用的辅助函数
public static removeItemToPool(listRoot){ for(var i = 0; i <
listRoot.childrenCount; ++i){ listRoot.children[i].active = false; } }; public
static addItemFromPool(listRoot){ for(var i = 0; i < listRoot.childrenCount;
++i){ var child = listRoot.children[i]; if(child.active == false){ child.active
= true; return child; } } var newChild = cc.instantiate(listRoot.children[0]);
listRoot.addChild(newChild); return newChild; };
removeItemToPool 会把所有的layout子节点标记为active = false; 

addItemFromPool 会从layout子节点中选择一个active为false的节点,若没有可用的,则复制0号节点来用。 


上面这个套路,麒麟子用了很久了,既简单明了,又能够控制对象池。 大家在做背包,或者其它大量子元素界面的时候可以试试。可以很任性的随意刷新,完全不用担心效率和内存问题。

四、结束语

有在线演示:https://qilinzi.ukylin.net/?lesson=08
<https://qilinzi.ukylin.net/?lesson=08>

源码:https://gitee.com/qilinzi/qlz_ccc_tips
<https://gitee.com/qilinzi/qlz_ccc_tips>

大家朋什么不明白的,或者想了解但麒麟子没有写的。在本博客中留言即可,也可以直接在交流群里@麒麟子,或者私聊麒麟子。 谢谢大家的支持!

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信