createjs进阶—sprite精灵图的使用(二)


今天这篇文章前端童鞋需要着重看,因为不会用到flash或者animateCC。

在讲之前大家先看一个demo:

http://www.ajexoop.com/demo/spriteTest2/index2.html

这个demo可不是死的动画,看下代码就可以知道,我只是在某个时间点让角色做出了动作,也就是如果加上控制器,你就可以控制屏幕里的角色,非常适用于游戏编程。

QQ图片20160714151239

先从简单的讲起,这个demo的制作。

首先准备素材,sprite图,这个图里面的人都是对称,也就是说可以不用flash做出来。

woody_0

然后就是代码了,先写好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

后话:
一个月没更新,实在是因为最近忙,等空下来我会恢复持续发博文,谢谢大家支持!


上一篇 下一篇