今天介绍下 游戏中的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. MINA系列学习-IoBuffer

    在阅读IoBuffer源码之前,我们先看Mina对IoBuffer的描述:A byte buffer used by MINA applications. This is a replacement ...

  2. chVsprintf

    #if LINUX_SYSTEM int chVsprintf(LPSTR buffer, int nCount, LPCSTR format, va_list argptr) { return vs ...

  3. 获取本机内存使用信息、DataTable占用内存空间

    相当于windows系统中的任务管理器,功能是通过系统的API实现的本机的监视,代码如下 using System;using System.Collections.Generic;using Sys ...

  4. JavaScript 详说事件机制之冒泡、捕获、传播、委托

    DOM事件流(event  flow )存在三个阶段:事件捕获阶段.处于目标阶段.事件冒泡阶段. 事件捕获(event  capturing):通俗的理解就是,当鼠标点击或者触发dom事件时,浏览器会 ...

  5. 什么时候用Model,什么时候用Entity?[转载知乎-备忘]

    在建立一个实体类的时候,究竟是用Model还是用Entity?比如MVC中,Model存了数据实体,但是他被称为Model,而在EF中,Entity也是存放数据实体,却被称作Entity,这两者有何区 ...

  6. 对Linux(Unix)的基础知识归纳

    前言,不论是原生APP(Android&IOS),还是大型架构级基础环境(.NET&J2EE,或LAMP阵营等), 基本都不可避免的涉及到Linux(Unix),故还是觉得有必要把自己 ...

  7. 谷歌Cartographer学习(1)-快速安装测试

    谷歌自己提供了安装方法,但是安装比较繁琐,我做了一定的修改,代码放到个人github上,https://github.com/hitcm/. ros下面的安装非常快捷,只需要catkin_make即可 ...

  8. 中国能用的NTP服务器地址

    133.100.11.8 prefer210.72.145.44203.117.180.36131.107.1.10time.asia.apple.com64.236.96.53130.149.17. ...

  9. [Notes] AWS Automation using script and AWS CLI

    (c) 2014 Amazon Web Services, Inc. and its afflialtes, All rights reserved. The content in this file ...

  10. 使用 xsd.exe 命令工具将 xsd 架构生成 类(CS) 文件

    vs自带命令行工具 命令:xsd  xml文件路径 C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC>xsd d:Scheme.xml ...