createjs进阶—sprite精灵图的使用(二)
今天这篇文章前端童鞋需要着重看,因为不会用到flash或者animateCC。
在讲之前大家先看一个demo:http://www.ajexoop.com/demo/spriteTest2/index2.html这个demo可不是死的动画,看下代码就可以知道,我只是在某个时间点让角色做出了动作,也就是如果加上控制器,你就可以控制屏幕里的角色,非常适用于游戏编程。
先从简单的讲起,这个demo的制作。
首先准备素材,sprite图,这个图里面的人都是对称,也就是说可以不用flash做出来。然后就是代码了,先写好sprite数据:
var spriteData = { images: ["images/woody_0.png","images/woody_1.png","images/woody_2.png"], frames: {width:80, height:80, regX: 40, regY:40}, animations: { stand:[0,3,"stand",0.3], walk:{ frames: [4,5,6,7,6,5], next: "walk", speed: 0.3 }, run:{ frames: [20,21,22,21], next: "run", speed: 0.3 }, somersault:{ frames: [58,59,69], next: "stand", speed: 0.3 }, attack1:[10,13,"stand",0.3], attack2:[14,17,"stand",0.3], attack3:{ frames: [8,9,19], next: "stand", speed: 0.3 }, jump:{ frames: [60,61,62], next: "jumpSky", speed: 0.3 }, jumpSky:{ frames: [62], speed: 0.3 }, crouch:{ frames: [61], next: "stand", speed: 0.3 }, runJump:{ frames: [112], speed: 0.3 } } };
大家可以从代码中看出来,代码里的images是这个sprite需要的素材,可以是多张,frames是截取的宽高,regX,regY是中心点,而animations是我给角色定义的一些动作,其中如果是数组,第一个参数代表起始帧,第二个参数代表结束帧,第三个参数代表结束后跳到哪个动作,第四个参数代表帧频。如果是对象那frames代表的是动作帧的顺序,next代表结束后跳转的动作,speed代表帧频。
接下来就是放在sprite中了,这个很简单:
var spriteSheet = new createjs.SpriteSheet(spriteData); var sprite = new createjs.Sprite(spriteSheet,"stand"); container.addChild(sprite); sprite.x = 200; sprite.y = 200; sprite.gotoAndPlay("run")
上面代码中sprite的第二个参数代表默认的动作,最后gotoAndPlay就是你要跳转的动作,如果做游戏的话,你就可以按下按钮后执行这个动作。
全部代码放出来:
var canvas,stage,container; canvas = document.getElementById("mainView"); function init() { stage = new createjs.Stage(canvas); createjs.Touch.enable(stage); var loader = new createjs.LoadQueue(false); loader.addEventListener("complete", loadCompleteHandler); loader.loadManifest([ {src:"images/woody_0.png", id:"woody_0"}, {src:"images/woody_1.png", id:"woody_1"}, {src:"images/woody_2.png", id:"woody_2"} ]); container = new createjs.Container(); stage.addChild(container); createjs.Ticker.setFPS(40); createjs.Ticker.addEventListener("tick", stageBreakHandler); } function loadCompleteHandler(event) { event.currentTarget.removeEventListener("complete",loadCompleteHandler); var spriteData = { images: ["images/woody_0.png","images/woody_1.png","images/woody_2.png"], frames: {width:80, height:80, regX: 40, regY:40}, animations: { stand:[0,3,"stand",0.3], walk:{ frames: [4,5,6,7,6,5], next: "walk", speed: 0.3 }, run:{ frames: [20,21,22,21], next: "run", speed: 0.3 }, somersault:{ frames: [58,59,69], next: "stand", speed: 0.3 }, attack1:[10,13,"stand",0.3], attack2:[14,17,"stand",0.3], attack3:{ frames: [8,9,19], next: "stand", speed: 0.3 }, jump:{ frames: [60,61,62], next: "jumpSky", speed: 0.3 }, jumpSky:{ frames: [62], speed: 0.3 }, crouch:{ frames: [61], next: "stand", speed: 0.3 }, runJump:{ frames: [112], speed: 0.3 } } }; var spriteSheet = new createjs.SpriteSheet(spriteData); var sprite = new createjs.Sprite(spriteSheet,"stand"); container.addChild(sprite); sprite.x = 200; sprite.y = 200; sprite.gotoAndPlay("run") } function stageBreakHandler(event) { stage.update(); }
代码运行之后就是这样:http://www.ajexoop.com/demo/spriteTest2/index1.html这样我们的demo就完成了么,并没有。一个角色的代码就那么多,一个游戏会有多少角色?代码会有多少冗余?所以我们需要一个比较好的封装,这时候就需要用到OOP。
》》》》》》》》》分割线(下面的内容可能有点难)》》》》》》》》》》
首先我们要有这样一个思想,所有角色都是角色吧,他们有共同的特性吧,那我们就需要创建一个BasePeople类,让所有的游戏角色继承与他。不管什么游戏角色都有跑跳攻击吧,那基类BasePeople就需要拥有这些方法,那么下面我先写个基类:
/** * 人物基类 所有人物角色均继承与此类 */ //BasePeople (function() { function BasePeople(){ this.Container_constructor(); this.walkSpeedX = 2; this.walkSpeedY = 0.5; this.runSpeedX = 5; this.runSpeedY = 0.5; this.jumpHeight = 30; this.runJumpHeight = 35; this.arrow = "right"; this.setSpriteData(); } var p = createjs.extend(BasePeople,createjs.Container); p.setSpriteData = function (){ } p.stand = function (){ this.animation.gotoAndPlay("stand"); }; p.move = function (x,y){ this.x +=x; this.y +=y; } p.startWalk = function (sx,sy){ this.changeStop(); this.animation.gotoAndPlay("walk"); var a ; if(sx > 0) { a = "right"; } else if(sx < 0) { a = "left"; } if(this.arrow != a) this.changeArrow(a); this.sx = sx; this.sy = sy; var _this = this; this.addEventListener("tick",this._walking = function (){_this.walking()}) } p.walking = function (){ this.move(this.sx,this.sy) } p.stopWalk = function (){ this.animation.gotoAndPlay("stand"); this.removeEventListener("tick",this._walking) } p.startRun = function (sx,sy){ this.changeStop(); this.animation.gotoAndPlay("run"); var a ; if(sx > 0) { a = "right"; } else if(sx < 0) { a = "left"; } if(this.arrow != a) this.changeArrow(a); this.sx = sx; this.sy = sy; var _this = this; this.addEventListener("tick",this._runing = function (){_this.runing()}) } p.runing = function (){ this.move(this.sx,this.sy) } p.stopRun = function (){ this.animation.gotoAndPlay("stand"); this.removeEventListener("tick",this._runing) } p.startDecelerate = function (){ this.decelerateTime = 0; this.changeStop(); this.animation.gotoAndPlay("somersault"); var _this = this; this.addEventListener("tick",this._decelerateing = function (){_this.decelerateing()}) } p.decelerateing = function (){ this.decelerateTime +=1; this.sx = this.sx*0.95; this.sy = this.sy*0.95; this.move(this.sx,this.sy); if( this.animation.currentFrame == 0) { this.stopDecelerate(); } } p.stopDecelerate = function (){ this.animation.gotoAndPlay("stand"); this.removeEventListener("tick",this._decelerateing) } p.changeArrow = function(arrow){ this.arrow = arrow; if(arrow == "left") { this.animation.scaleX = Math.abs( this.animation.scaleX) * -1; } else { this.animation.scaleX = Math.abs( this.animation.scaleX); } } p.startAttack = function (type){//攻击动作 1 2 是左勾拳和右勾拳随机出现 3是最后的浮空攻击 this.changeStop(); if(type == 3) { this.animation.gotoAndPlay("attack3"); } else { if(Math.random() > 0.5) { this.animation.gotoAndPlay("attack1"); } else { this.animation.gotoAndPlay("attack2"); } } this.removeEventListener("tick",this._attacking);//之后需要换成排队攻击 var _this = this; this.addEventListener("tick",this._attacking = function (){_this.attacking()}) } p.attacking = function (){ // this.move(-0.5,0); if(this.animation.currentFrame == 0) { this.stopAttack(); } } p.stopAttack = function (){ this.animation.gotoAndPlay("stand"); this.removeEventListener("tick",this._attacking); } p.jump = function (){ this.animation.gotoAndPlay("jump"); this.jumpNum = this.jumpHeight; this.jumpY = this.y; var _this = this; this.addEventListener("tick",this._jumping = function (){_this.jumping()}) } p.jumping = function (){ var list = this.data.animations.jump.frames; if( this.animation.currentFrame == list[list.length - 1]) { this.jumpNum -=3; this.move(0,-this.jumpNum); if(this.y >= this.jumpY) { this.y = this.jumpY; this.stopJump(); } } } p.stopJump = function(){ this.removeEventListener("tick",this._jumping); this.changeStop(); this.animation.gotoAndPlay("crouch"); } p.runJump = function (){ this.animation.gotoAndPlay("runJump"); this.jumpNum = this.runJumpHeight; this.jumpY = this.y; var _this = this; this.addEventListener("tick",this._runJumping = function (){_this.runJumping()}) } p.runJumping = function (){ this.jumpNum -=4; this.move(this.sx,-this.jumpNum); var list = this.data.animations.runJump.frames; if( this.animation.currentFrame == list[list.length - 1] && this.jumpNum < 20)//之后会改成有踢腿标识时开始踢 { this.animation.gotoAndPlay("runJumpAttack"); } if(this.y >= this.jumpY) { this.y = this.jumpY; this.stopRunJump(); } } p.stopRunJump = function (){ this.removeEventListener("tick",this._runJumping); this.changeStop(); this.animation.gotoAndPlay("crouch"); } p.changeStop = function (){//因需要切换动作而停止当前的动作侦听 this.removeEventListener("tick",this._walking); this.removeEventListener("tick",this._runing); this.removeEventListener("tick",this._decelerateing); } cls.BasePeople = createjs.promote(BasePeople, "Container"); }());
我们分析下上面的代码:
首先人物的一些动作我写成了方法,一些数值我写成了属性,那我要任务做动作的时候就非常简单了,比如 李小龙.开始攻击() xxx.startAttack()就可以了,要判断数值也很简单,xxx.hp就可以了。但是大家看到我这里并没有对sprite动画的实现,因为每个人的动画都不一样,所以动画的实现要在子类中,好的现在来写个子类。
/** * 角色Woody */ //Woody (function() { "use strict"; function Woody(){ this.BasePeople_constructor(); this.runSpeedX = 6; } var p = createjs.extend(Woody,cls.BasePeople); p.setSpriteData = function (){ if(this.animation) { if(this.animation.parent) { this.animation.parent.removeChild(this.animation); } } this.data = { images: ["images/woody_0.png","images/woody_1.png","images/woody_2.png"], frames: {width:80, height:80, regX: 40, regY:40}, animations: { stand:[0,3,"stand",0.3], walk:{ frames: [4,5,6,7,6,5], next: "walk", speed: 0.3 }, run:{ frames: [20,21,22,21], next: "run", speed: 0.3 }, somersault:{ frames: [58,59,69], next: "stand", speed: 0.3 }, attack1:[10,13,"stand",0.3], attack2:[14,17,"stand",0.3], attack3:{ frames: [8,9,19], next: "stand", speed: 0.3 }, jump:{ frames: [60,61,62], next: "jumpSky", speed: 0.3 }, jumpSky:{ frames: [62], speed: 0.3 }, crouch:{ frames: [61], next: "stand", speed: 0.3 }, runJump:{ frames: [112], speed: 0.3 }, runJumpAttack:{ frames: [107,108,109], speed: 0.3 }, guiqizhan:{ frames: [140,141,142,143,144,145,146,147,148,149,150,151], next: "stand", speed: 0.3 } } }; this.spriteSheet = new createjs.SpriteSheet(this.data); this.animation = new createjs.Sprite(this.spriteSheet, "stand"); this.addChild(this.animation); } p.startguiqizhan = function (){ this.changeStop(); this.animation.gotoAndPlay("guiqizhan"); var _this = this; this.addEventListener("tick",this._guiqizhaning = function (){_this.guiqizhaning()}); } p.guiqizhaning = function (){ if(this.animation.currentFrame == 144) { if(this.guiqizhan1 != 1) { var guiqizhan1 = new cls.Guiqizhan(); this.parent.addChild(guiqizhan1); guiqizhan1.x = this.x + (this.arrow == "left"?-30:30); guiqizhan1.y = this.y; guiqizhan1.scaleX = Math.abs(guiqizhan1.scaleX) * (this.arrow == "left"?-1:1); var num = (this.arrow == "left"?-10:10); guiqizhan1.startRun(num,0.5); this.guiqizhan1 = 1; } } else if(this.animation.currentFrame == 147) { if(this.guiqizhan2 != 1) { var guiqizhan2 = new cls.Guiqizhan(); this.parent.addChild(guiqizhan2); guiqizhan2.x = this.x + (this.arrow == "left"?-30:30); guiqizhan2.y = this.y; guiqizhan2.scaleX = Math.abs(guiqizhan2.scaleX) * (this.arrow == "left"?-1:1); var num = (this.arrow == "left"?-10:10); guiqizhan2.startRun(num,-0.5); this.guiqizhan2 = 1 } } else if(this.animation.currentFrame == 151) { this.stopguiqizhan(); this.guiqizhan1 = 0; this.guiqizhan2 = 0; } } p.stopguiqizhan = function (){ this.animation.gotoAndPlay("stand"); this.removeEventListener("tick",this._guiqizhaning); } cls.Woody = createjs.promote(Woody, "BasePeople"); }());
大家可以看到,我在子类中实现了sprite。细心的同学会发现我还额外显示了一些方法,这个呢是人物的技能,因为每个角色的技能都不一样所以我就把他放在子类中实现,那人物有基类,技能有吗,答案是有的,但是和人物有点不一样,因为技能分弹道类的技能,范围类的技能,近战技能等等,所以每种类型的技能都要做一个基类,我这里展示下弹道类技能的代码。
首先是基类:
/** * 弹道技能基类 所有弹道技能均继承与此类 */ //Barrage (function (){ "use strict"; function Barrage() { this.Container_constructor(); this.arrow = "right"; this.setSpriteData(); } var p = createjs.extend(Barrage,createjs.Container); p.move = function (x,y){ this.x +=x; this.y +=y; } p.setSpriteData = function (){ } p.startRun = function (sx,sy){ this.animation.gotoAndPlay("run"); this.sx = sx; this.sy = sy; var _this = this; this.addEventListener("tick",this._runing = function (){_this.runing()}) } p.runing = function (){ this.move(this.sx,this.sy); if(this.x > (1206 + 100)||this.x < -100||this.y < -100||this.y > 1206) { this.stopRun() } } p.stopRun = function (){ this.removeEventListener("tick",this._runing) if(this.parent) { this.parent.removeChild(this); } } p.startHit = function (){ this.changeStop(); this.animation.gotoAndPlay("hit"); var _this = this; this.addEventListener("tick",this._hitting = function (){_this.hitting()}) } p.hitting = function (){ var list = this.data.animations.hit.frames; if( this.animation.currentFrame == list[list.length - 1] ) { this.stopHit(); } } p.stopHit = function (){ this.removeEventListener("tick",this._hitting); if(this.parent) { this.parent.removeChild(this); } } p.changeStop = function (){//因需要切换动作而停止当前的动作侦听 this.removeEventListener("tick",this._runing); this.removeEventListener("tick",this._hitting) } cls.Barrage = createjs.promote(Barrage, "Container"); }())
然后是子类实现:
/** * 技能鬼气斩 */ //Guiqizhan (function (){ "use strict"; function Guiqizhan() { this.Barrage_constructor(); } var p = createjs.extend(Guiqizhan,cls.Barrage); p.setSpriteData = function () { if (this.animation) { if (this.animation.parent) { this.animation.parent.removeChild(this.animation); } } this.data = { images: ["images/guiqizhan.png"], frames: {width: 82, height: 83, regX: 41, regY: 41.5}, animations: { run: [0, 3, "run", 0.3], hit: [4, 7, "", 0.3], run2: [8, 11, "run2", 0.3] } }; this.spriteSheet = new createjs.SpriteSheet(this.data); this.animation = new createjs.Sprite(this.spriteSheet, "run"); this.addChild(this.animation); } p.run2 = function (sx,sy){ this.animation.gotoAndPlay("run2"); this.sx = sx; this.sy = sy; var _this = this; this.addEventListener("tick",this._runing = function (){_this.runing()}) } cls.Guiqizhan = createjs.promote(Guiqizhan, "Barrage"); }())
这样角色的一个技能就完成了,放在角色子类中实现,角色就可以使用技能。
当然一个真正的格斗游戏要做的还有很多,但是基本上都可以用OOP这种思维来完成,使代码更具有可读性,更容易维护,也少有有冗余的代码。今天讲的是sprite,游戏的其他方面知识就不赘述了。下面放出demo的链接:
百度网盘:http://pan.baidu.com/s/1bpA23iJ后话:
一个月没更新,实在是因为最近忙,等空下来我会恢复持续发博文,谢谢大家支持!
发表评论