今天介绍下 游戏中的sprite模块,也就是构建玩家及怪物的模块。有了这个模块,就可以在咱们的游戏里加入人物了。

想必用过css的朋友都知道sprite,一种将需要加载的图片拼接在一张图里以减少请求的css性能优化的方法。在游戏中的sprite,指游戏里有行为、有模样的对象。也就是人物或怪物。

源码见game-engine第598行。这里截取下目前需要的部分 加以说明。

// 精灵构造器
var Sprite= function (name,context,painter,behaviors) {
this.name=name;
this.painter=painter;
this.behaviors=behaviors || [];
} Sprite.prototype.paint=function (context) {
return this.painter.paint(context);
};
Sprite.prototype.update=function (sprite,time) {
if (this.exist) {
for (var i=0;i<this.behaviors.length;i++) {
this.behaviors[i].execute(sprite,time);
}
}
}

很简单,一个构造函数两个原型方法。这里为什么Sprite要以构造函数的形式呈现,因为在游戏中我们每刷新一个怪物相当于就是new了一个Sprite对象。

这里sprite接受4个参数  name,context,painter,behaviors

name:对象名称,是玩家还是怪物,是小怪还是boss?在这里说明

context:指向我们canvas绘画环境的2d context。如果还不了解canvas基本操作的朋友可以简单看看教学http://javascript.ruanyifeng.com/htmlapi/canvas.html

painter:顾名思义,是用来绘制sprite的样子的。跑动的僵尸,枪口喷射的火焰理论上都可以通过painter实现。painter有两种,一种比较简单,用来绘制几何图形。只需要在painter函数中用canvas绘制出几何图形,引擎会帮助你在画面中实现删除上一帧与绘制下一帧操作。

本文主要介绍复杂的一中:spriteSheet(精灵表)。类似与css中的sprite图,他能根据需要拉取需要的贴图,实现漂亮的动画效果。源码见game-engine 267行。

//源码现在不需要掌握,贴出来只是为了方便接下来讲解,只需了解构成即可
var SpriteSheetPainter=function (name,sprite,cells,spritesheet,interval,x,y,width,height) {
this.name=name;
this.sprite=sprite;
this.cells=cells || [];
this.cellIndex=0;
this.spritesheet=spritesheet;
this.x=x;
this.y=y;
this.width=width;
this.height=height;
this.isRunning=false;
this.lastTime=0;
this.interval=interval;
this.angle=angle;
}; SpriteSheetPainter.prototype={
advance:function (spriteSheets) {
this.isRunning=true;
if (this.cellIndex==this.cells.length-1) {
delete spriteSheets[this.name];
if (this.sprite.index) delete spriteSheets[this.name+this.sprite.index];
this.isRunning=false;
this.cellIndex=0;
} else {
this.cellIndex++;
}
},
paint:function (context) {
var sprite=this.sprite,
sLeft=sprite.left,
sTop=sprite.top;
context.save();
context.beginPath();
context.translate(sLeft,sTop);
context.rotate(this.sprite.angle);
for (var i=0;i<this.cells.length;i++) {
var cell=this.cells[this.cellIndex];
context.drawImage(this.spritesheet,cell.left,cell.top,cell.width,cell.height,this.x,this.y,this.width,this.height);
}
context.restore();
}
}

 看到这么多行代码别被吓到了,请听我分析分析。

总的来说也就是一个构造函数和2个原型方法。 SpriteSheet构造函数中只用关心以下两句:

		this.cells=cells || [];
this.cellIndex=0;

  cell是什么? 下图中每个动作就是一个cell。  cellIndex就代表每个动作的编号(索引)。

原型方法中paint方法就是根据当前的索引(每个索引对应的动作位置保存在data文件中,如下图就是僵尸死亡的动画每个索引对应的坐标),找到索引对应的坐标,再把图片中对应坐标的动画绘制在canvas中。这就是一帧动画的绘制。
zombieDieCell=[{left:0,top:1035,width:58,height:53},
{left:58,top:1035,width:58,height:53},
{left:116,top:1035,width:58,height:53},
{left:174,top:1035,width:58,height:53},
{left:232,top:1035,width:58,height:53},
{left:290,top:1035,width:58,height:53},
{left:348,top:1035,width:58,height:53},
{left:0,top:1088,width:58,height:53},
{left:58,top:1088,width:58,height:53},
                                      

绘制完毕一帧后,就该原型中的advance方法出场了,该方法会让索引值+1,如果索引值达到最大(如我的动画基本都是22帧,也就是22幅画面组成一个动作),就删除这个动画。 (为什么要删除?想想如果索引值达到22我又给他赋值为1,那倒地的僵尸不就又站起来重复倒地么?删除表示这个动作做完了)

就是这么简单。

到这里我们再梳理下本节思路,我们介绍了Sprite构造函数,该构造函数接受4个参数,其中一个是painter,接下来我们介绍了一种很好用的painter--spriteSheet。 

// 精灵构造器
var Sprite= function (name,context,painter,behaviors) {
this.name=name;
this.painter=painter;
this.behaviors=behaviors || [];
}

接下来介绍behaviors,其实思路和painter一模一样。 刚才是每帧动画的绘制,接下来就是每帧动画所代表的行为绘制(想想如果仅仅绘制动画而没有行为,那僵尸一巴掌拍你身上,看起来吓人但根本不掉血啊!因为根本没触发攻击的行为)

这里截取僵尸的行为,源代码见game-loop 220行。(为啥这次的行为构造器不像spriteSheet一样放在game-engine里而放在game-loop里?因为spritesheet是任何游戏都能复用的部分所以放在引擎中,而不是所有游戏里都有僵尸,所以僵尸的行为代码放在我的游戏主体里)

ZombieBehaviors=function  () {
this.interval=undefined;
this.angleTriggle=false;
this.sprite=undefined;
this.angle=undefined;
this.player=player;
} ZombieBehaviors.prototype.hangout=function () { };
ZombieBehaviors.prototype.advance=function () { };
ZombieBehaviors.prototype.stepBack=function () { };
ZombieBehaviors.prototype.hunt=function () { };
ZombieBehaviors.prototype.freeze=function () { }
}
ZombieBehaviors.prototype.execute=function (sprite) {
var random=Math.random();
this.player=player;
this.sprite=sprite;
this.advance();
this.stepBack();
this.hangout();
this.hunt();
this.freeze();
}

这就是僵尸的行为,中间具体的每个行为因为比较长我删掉了。大家从名字可以看出这些行为代表了僵尸被击退、被眩晕在原地、追踪玩家、闲逛。最后一个execute每次执行都会把僵尸所有行为都执行一次。在各个行为内部会检测当前是否适合执行该行为。

接下来我来简单演示下sprite怎么用。

首先我们定义好zombiePainter,源码见game-loop 326行。定义了painer名字叫“zombie”,他所用的cell数据来源于“zombie,monsterType[type].cell”。 所用的cell文件来源于“pics.getImage("images/sprite.png")”。 后面的参数定义了这个动画的大小,左右平移(用来和碰撞检测体积吻合)。

zombie.painter=new SpriteSheetPainter("zombie",zombie,monsterType[type].cell,pics.getImage("images/sprite.png"),monsterType[type].interval,monsterType[type].x,monsterType[type].y,monsterType[type].width,monsterType[type].height);

 接下来我们定义zombieBehaviors,这个构造函数告诉引擎僵尸都有哪些各式各样的行为。

至此,我们就完成了一个僵尸的创建,不仅有头有脸,而且有智商。

到这里还有最后一个问题,我们现在已经知道painter用来绘制一帧的画面,behavior用来显示当前帧僵尸的行为。但是为了让僵尸动起来,每一帧我们都必须调用他的painter和behavior,难道要我们每一帧都手动调用?

这,就是Sprite构造函数原型上两个方法做的事情了。

Sprite.prototype.paint=function  (context) {
return this.painter.paint(context);
};
Sprite.prototype.update=function (sprite,time) {
if (this.exist) {
for (var i=0;i<this.behaviors.length;i++) {
this.behaviors[i].execute(sprite,time);
}
}
}

构造函数上有个paint和update方法,这两个方法会去调用我们传入的painter的paint方法 已经behaviors的execute方法。 还记得么,paint方法会绘制一帧的画面,execute方法会执行一遍僵尸所有的行为。

所以,我们可以通过Sprite的这两个方法来更新动画以及行为,那又是谁来调用这两个方法呢?上一章我们已经讲过了——游戏引擎。

       this.tick(time);
this.clearScreen();
this.startAnimate(time);
this.paintUnderSprites();
this.updateSprites(time);
this.paintSprites();
this.paintOverSprites();
this.endAnimate();

还记得吗,游戏引擎每一帧都会将上面所有函数都调用一遍。其中

   this.updateSprites(time);
this.paintSprites();

就是调用游戏内所有Sprite的paint方法以及update方法。  paint方法又会去调用painter的paint方法, update方法会去调用behavior的execute方法。

这里是这两个函数的源码:

updateSprites: function  (time) {
for (var i=0;i<this.sprites.length;i++) {
var sprite=this.sprites[i];
if (sprite!==undefined) sprite.update(sprite,time);
}
},
paintSprites: function (time) {
for (var i=0;i<this.sprites.length;i++) {
var sprite=this.sprites[i];
if (sprite!==undefined) sprite.paint(this.context);
}

这下, 你懂了么,一层调用一层,一层包裹一层形成大大小小的模块,最后再由引擎调用。这么做看似复杂。但是如果游戏画面出了问题,我们从引擎出发,顺着引擎的

updateSprites和paintSprites找问题,没有问题继续往下一层Sprite找问题,再没问题就找Sprite的Painter和Behaviors。这样思路是很清晰的。

好了,现在我们有了满屏乱晃的僵尸,是时候召唤我们的英雄了,下一篇将告诉你如何控制你的英雄奔跑,射击。
 

用canvas制作酷炫射击游戏--part3的更多相关文章

  1. 用canvas制作酷炫射击游戏--part1

    好久没写博客了,因为过年后一直在学游戏制作方面的知识.学得差不多后又花了3个月时间做了个作品出来,现在正拿着这个作品找工作. 作品地址:https://betasu.github.io/Crimonl ...

  2. 用canvas制作酷炫射击游戏--part2

    今天这一部分主要讲游戏的实现原理与游戏循环的代码实现. 先说原理,大家都看过动画吧.在我看来,游戏就是玩家能人为控制动画剧情发展方向的动画.所以,我们的游戏引擎其实说白了就是个动画引擎再加上鼠标事件. ...

  3. html5+Canvas实现酷炫的小游戏

    最近除了做业务,也在尝试学习h5和移动端,在这个过程中,学到了很多,利用h5和canvas做了一个爱心鱼的小游戏.点这里去玩一下 PS: 貌似有点闪屏,亲测多刷新两下就好了==.代码在本地跑都不会闪, ...

  4. 怎样用HTML5 Canvas制作一个简单的游戏

    原文连接: How To Make A Simple HTML5 Canvas Game 自从我制作了一些HTML5游戏(例如Crypt Run)后,我收到了很多建议,要求我写一篇关于怎样利用HTML ...

  5. [译]怎样用HTML5 Canvas制作一个简单的游戏

    这是我翻译自LostDecadeGames主页的一篇文章,原文地址:How To Make A Simple HTML5 Canvas Game. 下面是正文: 自从我制作了一些HTML5游戏(例如C ...

  6. canvas实现酷炫气泡效果

    canvas实现动画主要是靠设置定时器(setinterval())和定时清除画布里的元素实现,canvas动画上手很简单,今天可以自己动手来实现一个酷炫气泡效果. 气泡炸裂效果(类似水面波纹) 代码 ...

  7. 教你使用Python制作酷炫二维码

    这篇文章讲的是如何利用python制作狂拽酷炫吊炸天的二维码,非常有趣哦! 可能你见过的二维码大多长这样: 普普通通,平平凡凡,没什么特色... 但,如果二维码长这样呢! 或者 这样! 是不是炒鸡好看 ...

  8. Unity3D游戏开发从零单排(三) - 极速创建狂拽酷炫的游戏地形

    提要 在Unity工作流程内,地形是一个必不可少的重要元素.不论是游戏或虚拟现实都会使用到各种类型的地形效果,在这个教学中我们须要了解到地形的制作基本概念与,当中对于Unity的地形操作部分须要大量的 ...

  9. 腾讯AlloyTeam正式发布pasition - 制作酷炫Path过渡动画

    pasition Pasition - Path Transition with little JS code, render to anywhere - 超小尺寸的Path过渡动画类库 Github ...

随机推荐

  1. PL/SQL安装部署配置(配图解)

    PL/SQL安装部署配置 下载好安装包之后,双击exe程序 双击安装程序,出现如下页面 点击[NEXT],出现如下界面 选择[I Accept...],点击[NEXT],出现如下界面 选择安装路径,点 ...

  2. 安装一些包管理的记录 win10

    我大php的composer 国内镜像包http://pkg.phpcomposer.com/  还是全局的爽些: omposer config -g repo.packagist composer ...

  3. SpringMVC+Freemarker+JSTL支持

    前提: 网页编程中,我的思路是,通用的模块不仅仅只有后台代码,前端页面也可以独立为模块. 这个和asp.net中的UserController很像 比如有个人员基本信息的展示界面,需要在多个界面中嵌入 ...

  4. js script中引用其他script

    在需要引用目标js中引用其他js依赖项 可以使用这个方法直接在js顶部加入这一行即可 document.write("<script type='text/javascript' sr ...

  5. MD5编码的内存泄露

    MD5CryptoServiceProvider 如果多次使用会产生内存溢出,如下这样调用几百万次就会出现内存 溢出. public static string MD5Encode(string so ...

  6. bzoj 1051 (强连通) 受欢迎的牛

    题目:这里 题意: Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这 种关系是具有传递性的,如果A认为B受欢迎,B认为 ...

  7. myeclipse的web project中使用dom4j报错java.lang.ClassNotFoundException: org.dom4j.Document

    在学习微信公众平台开发的过程中,参考了微信公众平台java开发详解(工程代码+解析)中的xml解析方法,是使用dom4j来解析的. 对于java中解析xml的方法,又参考了java解析xml的几种方式 ...

  8. FreeMarker标签与使用

    模板技术在现代的软件开发中有着重要的地位,而目前最流行的两种模板技术恐怕要算freemarker和velocity了,webwork2.2对两者都有不错的支持,也就是说在webwork2中你可以随意选 ...

  9. [linux]windows无法访问samba的安全性问题(关闭selinux)

    背景 在某一天重启了虚拟机的 linux 之后,我的 windows 在连接上 samba 之后,点击某些文件夹的时候,会出现没有权限打开的情况.这问题折腾了我一度重新配置了好几次 samba 的配置 ...

  10. rbx1 package 下载安装过程

    学习INSTALLING THE ROS-BY-EXAMPLE CODE,是书中第五章的内容,如果我们按照上一篇教程执行过了,就可以直接进入第五章,安装一个叫rbx1的包.这个包里面包括了本书中用到的 ...