声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢!
楼主记忆力不好,最近刚好用了一下createJs框架,怕以后一段时间没用后会忘记,所以在此做个记录,或许以后用得着。
createJs网上的中文教程挺少的,以前UC有个Xcanvas的论坛有createJs的详细教程,但是随着XCanvas团队的解散,那个网站也关闭了。。网上的大部分都是非常基础的教程,有点千遍一律的感觉。所以楼主就去把createJs下载下来,硬着头皮看英文文档了。凭着楼主这英语六级只考了三百多分的渣渣来说,看起来很费力啊,不过还是勉强摸索出了大概的用法。所以现在就是学了多少就记录多少,之后或许也会不定期更新一下该框架的新的学习心得。毕竟对自己以后还是有帮助的。
希望本文能帮到那些想学createJs的新手。因为楼主也是刚学的,所以本文或许有不正确之处,因此本文仅当参考,若有不正之处欢迎斧正。
闲话说到这,直接进入主题。
楼主用createJs写了个简单的跑酷游戏DEMO,就拿它做例子吧。 看DEMO戳我。
createJs的由来,基础什么的就不说了,就直接说createJs的用法吧。
首先到createJs官网下载,createJs分成easelJs(图形动画)、preloadJs(文件加载)、soundJs(音频控制)以及tweenJs(补间动画)四部分,大家下载的时候,建议下载两个文件,一个是压缩版文件,用于项目中的引用,再下载个源码文件,用于查看用法、API、demo等。因为楼主目前只用了easelJs和preloadJs,所以暂时就只说这两个,其实就这两个已经非常够用了。
接下来开始分析代码:
首先引入js文件
<script src="easeljs-0.7.1.min.js"></script> <script src="preloadjs-0.4.1.min.js"></script>
然后进行舞台初始化操作:
function init(){ stage = new createjs.Stage("cas"); C_W = stage.canvas.width; C_H = stage.canvas.height; var manifest = [ {src:"image/man.png" , id:"man"}, {src:"image/ground.png" , id:"ground"}, {src:"image/bg.png" , id:"bg"}, {src:"image/high.jpg" , id:"high"}, {src:"image/coins.png" , id:"coin"} ] loader = new createjs.LoadQueue(false); loader.addEventListener("complete" , handleComplete); loader.loadManifest(manifest); drawLoading(); }
上面就用到了preloadJs中的方法,实例化一个loader,把需要加载的图片文件放在manifest里面,进行加载,加载完成后调用回调handleCompelete函数:
function handleComplete(){ //当图片素材load完后执行该方法 var manImage = loader.getResult("man"), lowground = loader.getResult("ground"), highground = loader.getResult("high"), bgImage = loader.getResult("bg"), coins = loader.getResult("coin"); sky = new createjs.Shape(); sky.graphics.bf(bgImage).drawRect(0,0,C_W,C_H); sky.setTransform(0, 0, 1 , C_H/bgImage.height); stage.addChild(sky); man = createMan(200,326,manImage); //该框为判定角色的判定区域 kuang = new createjs.Shape(); kuang.graphics.beginStroke("rgba(255,0,0,0.5)").drawRect(0 , 0 , man.size().w , man.picsize().h*1.5); // stage.addChild(kuang); mapHandle(lowground , highground , coins); createjs.Ticker.timingMode = createjs.Ticker.RAF;//设置循环方法,可以是requestAnimationFrame或者是setTimeout createjs.Ticker.setFPS(30);//舞台帧率控制 createjs.Ticker.addEventListener("tick", tick);//绑定舞台每一帧的逻辑发生函数 window.addEventListener("keydown" , function(event){ event = event||window.event; if(event.keyCode===32&&man.jumpNum<man.jumpMax){ man.jump(); } }) }
获得加载完成后端的图片数据就直接用loader.getResult就可以获取了,跑酷游戏需要一个背景,所以,我们实例化一个sky,然后进行位图绘制,bf方法是beginBitmapFill的缩写,该方法就是开始绘制位图,后面的drawRect是位图的绘制区域,区域当然是整个画布啦,所以就是drawRect(0,0,C_W,C_H)。实例化出来sky后就直接添加到舞台stage里面就行了。接下来是实例化一个角色,createMan方法后面有说,是自己封装的。
然后进行舞台循环设置,上面有注释了,就不说了。
舞台设置中,mapHandle是地图数据的初始化:
var mapIndex = 0, //地图序列 Mix = 0, //地图数组的索引 allStones = [], //存放所有的石头 allCoins = [], //所有金币 showSt = []; //存放显示出来的石头 function mapHandle(lowground , highground , coins){ //初始化地图 allStones.length = 0; var stoneImage = {"A":lowground , "B":highground},kind = null; for(var i=0;i<30;i++){ //把需要用到的石头预先放入容器中准备好 switch(i){ case 0:kind="A";break; case 10:kind="B";break; case 20:kind="C";break; } var st = createStone(C_W , kind , stoneImage); allStones.push(st) } for(var i=0;i<10;i++){ //把需要用到的金币预先放入容器中 var coin = createCoin(coins); allCoins.push(coin); } Mix = Math.floor(Math.random()*mapData.length); //随机地图序列 for(var i=0;i<8;i++){ setStone(false) } } function setStone(remove){ //添加陆地的石头 var arg = mapData[Mix].charAt(mapIndex), coarg = coinCode[Mix].charAt(mapIndex), cc = null; if(coarg==="#"){ for(var i=0;i<allCoins.length;i++){ if(!allCoins[i].shape.visible){ cc = allCoins[i]; cc.shape.visible = true; break; } } } for(var z=0;z<allStones.length;z++){ if(!allStones[z].shape.visible&&allStones[z].kind===arg){ var st = allStones[z]; st.shape.visible = true; st.shape.x = showSt.length===0?0:showSt[showSt.length-1].shape.x+showSt[showSt.length-1].w; if(cc){ cc.shape.x = showSt.length===0?allStones[z].w/2-cc.size().w/2:showSt[showSt.length-1].shape.x+showSt[showSt.length-1].w+allStones[z].w/2-cc.size().w/2; cc.shape.y = arg==="C"? C_H-loader.getResult("high").height-50 : allStones[z].shape.y-cc.size().h/2-50; } if(remove) showSt.shift(); showSt.push(st); break; } } mapIndex++; if(mapIndex>=mapData[Mix].length){ Mix = Math.floor(Math.random()*mapData.length) mapIndex=0; } }
下面是人物模块的封装
(function(w){ var FRAME_RATE = 13, //精灵表播放速度 SCALE_X = 1.5, //X轴缩放 SCALE_Y = 1.5, //Y轴缩放 GRAVITY = 3, //重力加速度 JUMP_SPEED = 2.6, //垂直速度 WIDTH = 40, HEIGHT = 96, PICWIDTH = 64, PICHEIGHT = 64, PROPORTION = 150/1; //游戏与实际的距离比例 var Man = function(x , y , img){ this.x = x; this.y = y; this.endy = y; this.vx = 0.5; this.vy = 0; this.ground = []; this.state = "run"; this.jumpNum = 0; this.jumpMax = 1; this.init(img); } Man.prototype = { constructors:Man, init:function(img){ var manSpriteSheet = new createjs.SpriteSheet({ //实例化精灵表绘制器 "images":[img], "frames":{"regX":0,"height":PICWIDTH,"count":45,"regY":1,"width":PICHEIGHT}, "animations":{ "run":{ frames:[21,20,19,18,17,16,15,14,13,12], //精灵表每一帧的位置 next:"run", //当精灵表循环完后的下一步动作 speed:1, //精灵表播放速度 }, "jump":{ frames:[34,35,36,37,38,39,40,41,42,43], next:"run", speed:1, }, "die":{ frames:[8,7,6,5,4,3,2,1,0], next:"die", speed:1, } } }); this.sprite = new createjs.Sprite(manSpriteSheet , this.state); //实例化精灵 this.sprite.framerate = FRAME_RATE; //精灵表绘制速率 this.sprite.setTransform(this.x, this.y, SCALE_X, SCALE_Y); //设置精灵的位置 stage.addChild(this.sprite); //添加到舞台 }, update:function(){ var sprite = this.sprite; var time = createjs.Ticker.getInterval()/1000; //获取当前帧与上一帧的时间间隔 if(this.state==="run"){ if(sprite.x<this.x){ sprite.x +=this.vx; }else { sprite.x = this.x } } if(this.endy>sprite.y||this.state==="jump"){ //角色的动作处理 var nexty = sprite.y+time*this.vy*PROPORTION; this.vy += time*GRAVITY; sprite.y += time*this.vy*PROPORTION; if(Math.abs(sprite.y-this.endy)<10&&this.vy>0){ this.state = "run"; sprite.y=this.endy; this.vy = 0; } } if(sprite.x+(PICWIDTH*SCALE_X-WIDTH)/2<0||sprite.y>C_H+200){ this.die(); createjs.Ticker.reset(); alert("you are Die!"); } switch(this.state){ case "run": this.jumpNum = 0; break; case "die": if(sprite.currentFrame===0){ sprite.paused = true; } break; } }, run:function(){ this.sprite.gotoAndPlay("run") }, jump:function(){ this.vy = -JUMP_SPEED; this.state = "jump"; this.sprite.gotoAndPlay("jump"); //让精灵表播放特定的动画 this.jumpNum++; }, die:function(){ this.state = "die"; this.sprite.gotoAndPlay("die") }, size:function(){ return { w:WIDTH, h:HEIGHT } }, picsize:function(){ return { w:PICWIDTH, h:PICHEIGHT } } } w.createMan = function(x , y , img){ return new Man(x , y , img) }; })(window)
人物模块封装就是简单的在createJs的封装之上进行进一步的封装,封装很简单,就是用createJs实例化一个精灵类,再绑定精灵表,上面的代码中也有注释,基本上都说的很明白了。
下面贴出封装的石头以及金币模块,简单说下背景的循环,预先实例化一堆石头和金币,然后移动响应的石头,当石头移动到超出舞台区域时,把他的visible属性置为false,再重新添加一个石头在最后的位置进行新的一次移动。金币也一样。地图数据则是通过预先定义好的字符串来实现。
(function(w){ var SPEED = 4, COIN_STAY_X = 20, COIN_STAY_Y = 20, COIN_STAY_WIDTH = 30, COIN_STAY_HEIGHT = 30, COIN_SCALE_X = 0.08, COIN_SCALE_Y = 0.08; //地上的石头类 var Stone = function(x,kind,allImage){ this.x = x; this.kind = kind; this.allImage = allImage; this.init(); } var sp = Stone.prototype; sp.init=function(){ this.shape = new createjs.Shape(); if(this.kind!=="C"){ this.h = this.allImage[this.kind].height; this.w = this.allImage[this.kind].width*2; this.y = C_H - this.h; this.shape.graphics.beginBitmapFill(this.allImage[this.kind]).drawRect(0, 0, this.w, this.h); this.shape.setTransform(this.x, this.y, 1, 1); }else { this.h = -1000; this.w = 170; this.y = C_H - this.h; this.shape.graphics.beginFill("#000").drawRect(0, 0, this.w, this.h); this.shape.setTransform(this.x, this.y, 1, 1); } this.shape.visible = false; this.shape.cache(0 , 0 , this.w , this.h); stage.addChild(this.shape); } sp.update=function(){ this.shape.x -= SPEED; } //金币类 var Coin = function(image){ this.sizeX = COIN_SCALE_X; this.sizeY = COIN_SCALE_Y; this.isget = false; this.init = function(){ this.shape = new createjs.Shape(); this.shape.graphics.beginBitmapFill(image).drawRect(0, 0, image.width, image.height); this.shape.setTransform(0, 0, COIN_SCALE_X, COIN_SCALE_Y); this.shape.visible = false; stage.addChild(this.shape); } this.init(); this.update = function(){ if(this.isget){ this.sizeX = this.sizeX + ((COIN_STAY_WIDTH/image.width) - this.sizeX)*0.1; this.sizeY = this.sizeY + ((COIN_STAY_HEIGHT/image.height) - this.sizeY)*0.1; this.shape.setTransform( this.shape.x + (COIN_STAY_X - this.shape.x)*0.1, this.shape.y + (COIN_STAY_Y - this.shape.y)*0.1, this.sizeX, this.sizeY ); if(Math.abs(this.shape.x-COIN_STAY_X)<0.5&&Math.abs(this.shape.y-COIN_STAY_Y)<0.5){ this.shape.visible = false; this.isget = false; this.sizeX = COIN_SCALE_X; this.sizeY = COIN_SCALE_Y; this.shape.setTransform(0,0,this.sizeX,this.sizeY); } } else{ this.shape.x -= SPEED; if(this.shape.x<-image.width*COIN_SCALE_X){ this.shape.visible = false; } } } this.size = function(){ return { w:image.width*COIN_SCALE_X, h:image.height*COIN_SCALE_Y } } } w.createCoin = function(image){ return new Coin(image) } w.createStone = function(x,kind,allImage){ return new Stone(x,kind,allImage); } })(window)
封装方法跟上面的人物模块封装差不多,不过人物是用精灵类,石头金币则是用形状类了。就是通过位图的绘制,来绘制位图的图片,原理都一样。
最后是舞台逐帧处理的tick方法:
function tick(event){ //舞台逐帧逻辑处理函数 man.update(); kuang.x = man.sprite.x+(man.picsize().w*1.5-man.size().w)/2; //参考框 kuang.y = man.sprite.y; man.ground.length=0; var cg = stoneHandle(); if(man.ground[0]&&!cg) { man.ground.sort(function(a,b){return b.h-a.h}); man.endy = man.ground[0].y-man.picsize().h*1.5; } allCoins.forEach(function(cc , index){ if(cc.shape.visible){ if( Math.abs((kuang.x+man.size().w/2) - (cc.shape.x+cc.size().w/2)) <= (man.size().w+cc.size().w)/2&& Math.abs((kuang.y+man.size().h/2) - (cc.shape.y+cc.size().h/2)) <= (man.size().h+cc.size().h)/2&& !cc.isget ){ cc.isget = true; countCoin.innerHTML = parseInt(countCoin.innerHTML)+1 } cc.update(); } }) document.getElementById("showFPS").innerHTML = man.endy stage.update(event) }
在每一帧的处理,就像自己写游戏一样啦,就是把舞台里的所有对象逐个进行逻辑运算,进行相应处理。
基本上createJs的用法还是相对比较简单并且强大的。比自己去造轮子能省很多功夫。
源码地址:https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Game-demo/runningMan