那么,我们今晚要完成的任务是能在画布上显示图片,在开始之前我们可能需要做一些准备,那就是我们的图片资源,这也是最困扰的,下面我介绍我常去的游戏资源网站,
这个网站目前来说基本够用了,有了这些你可能还需要一个软件,FLASH CS6,它可以帮助我们生成动画纹理集和配置文件,好了,一切准备就绪了,让我们开始吧.
先来第一步,我们新建一个画布,你可以在你的入口html代码块先申请一个画布,并且设置好属于你自己的画布名称,还有画布的宽度和高度,通常我们会有一个框架JS,框架JS是不包含游戏逻辑的,它可能是一个通用的动画块,而Main则是我们游戏的入口和启动类,我们可以在Main里写测试代码,html的代码看起来是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<
head
>
<
meta
http-equiv
=
"Access-Control-Allow-Origin"
content
=
"*"
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=utf-8"
/>
<
title
>HTML5 2D游戏引擎研发系列 第二章 <
Canvas
技术篇-画布技术></
title
>
<
script
type
=
"text/javascript"
LANGUAGE
=
"JavaScript"
src
=
"http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"
></
script
>
<
script
type
=
"text/javascript"
LANGUAGE
=
"JavaScript"
src
=
"DisplayObject.js"
></
script
>
<
script
type
=
"text/javascript"
LANGUAGE
=
"JavaScript"
src
=
"Main.js"
></
script
>
<
style
>*{margin: 0px;padding: 0px;}</
style
>
</
head
>
<
body
>
<
canvas
id
=
"myCanvas"
width
=
"1024"
height
=
"768"
></
canvas
>
</
body
>
</
html
>
|
我们知道基于Canvas去绘制动画是很繁琐的事情,而在游戏开发中我们可能不需要更改它的绘制逻辑,所以我们需要一个动画类MovieClip去管理我们的图像或者动画,而我们的游戏逻辑则是在外部去对做变换控制而已,所以我们需要把绘图逻辑封装起来,所以我们也需要一个场景管理器去管理他们,当我们正式开发游戏时的逻辑应该只是把资源传入动画对象,我们不用关系它是如何被显示出来的,我们只关系它的位置比例,角度等等,所以他们的逻辑块看起来应该是这样的:
所以当我们编写好这个框架之后,我们所要做的事情就是读取资源,创建场景,创建动画对象,再操作动画对象,我们新建一个DisplayObject.js,加入它的第一个代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//画布
var
canvas;
//上下文
var
context;
//场景宽度
var
stageWidth=1024;
//场景高度
var
stageHeight=768;
//显示列表
var
displayObjectList =
new
Array();
//创建画布上下文
$(document).ready(
function
(){
canvas=$(
"#myCanvas"
);
context=canvas.get(0).getContext(
"2d"
);
});
|
这里非常简单,当页面初始化完毕后我们获取我们的画布,然后获取画布的类型,并返回这个画布的控制句柄,之后我们对画布的所有操作都是这个句柄了.
第二步,创建场景控制器.
现在我们需要创建一个场景管理器去管理我们的动画对象现在它至少包含三个功能,添加和删除显示对象,还有一个无限循环的心跳函数来调用动画函数的绘制函数,现在它看起来是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
//场景管理器
function
Stage2D()
{
//初始化启动计时器
this
.init=
function
()
{
setTimeout(paint,0);
}
//添加子对象
this
.addChild=
function
(child)
{
if
(indexOf(child)==-1)
{
displayObjectList.push(child);
}
else
{
displayObjectList.splice(child,1);
displayObjectList.push(child);
}
}
//删除子对象
this
.removeChild=
function
(child)
{
if
(indexOf(child)!=-1)
{
displayObjectList.splice(child,1);
}
}
}
|
为了防止重复添加和删除空对象,我们加入了一个indexOf函数判断当前填入的对象是否在当前的显示列表中,所以我们还需要一个显示列表:
1
2
|
//显示列表
var
displayObjectList =
new
Array();
|
下面是查询函数
1
2
3
4
5
6
7
8
9
10
|
//查询显示对象是否在数组中
function
indexOf(Object)
{
for
(
var
i = 0;i<displayObjectList.length;i++){
if
(displayObjectList[i] == Object){
return
i;
}
}
return
-1;
}
|
查询函数的意思是检测数组中对象等于当前比较的对象如果是则返回当前的数组下标,否则返回-1,然后是重绘函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//重绘核心
function
paint()
{
//清理画面
context.clearRect(0,0,stageWidth,stageHeight);
//重置画布的透明度
context.globalAlpha=1;
//循环遍历显示对象
for
(
var
i=0;i<displayObjectList.length;i++)
{
//调用显示对象的paint重绘方法
displayObjectList[i].paint();
}
//设置计时器延迟为0
setTimeout(paint,0);
}
|
现在整个游戏的绘制核心都是在这里了,其实它是一个递归函数,这部分的逻辑是先清理上一帧的内容,然后重置画布句柄的属性,现在我们只用到了透明度,所以只重置了透明度,后面的循环则是遍历调用显示对象的重绘方法,所以可以看出来我们绘制的东西每一帧都是新的,这很符合画布的逻辑嘛,最后一步是设置计时器调用本函数方法,这样就可以让我们的游戏永久的运行下去,现在最后一步让我们创建动画对象,我们将会赋予它一些基本的操作属性,XY位移,角度,缩放和透明度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
//动画类
function
MovieClip2D(img,data)
{
//动画类的X坐标
this
.x=0;
//动画类的Y坐标
this
.y=0;
//动画类的角度
this
.rotation=0;
//动画类的X轴比例
this
.scaleX=1;
//动画类的Y比例
this
.scaleY=1;
//动画类的可见性
this
.visible=
true
;
//动画类的透明度
this
.alpha=1;
//动画类的重绘函数
this
.paint=
function
()
{
if
(
this
.visible)
{
//保存画布句柄状态
context.save();
//更改画布句柄的透明度,从这以后绘制的图像都会依据这个透明度
context.globalAlpha=
this
.alpha;
//设置画布句柄的位置,实际上是设置的图像的位置
context.translate(
this
.x,
this
.y);
//设置画布旋转角度实际上是设置了图像的角度,参数是弧度,所以我们必须把角度转换为弧度
context.rotate(
this
.rotation*Math.PI/180);
//设置画布句柄的比例,实际上是设置了图像的比例
context.scale(
this
.scaleX,
this
.scaleY);
//画布句柄设置好一切属性之后,开始绘制图像
context.drawImage(img,0,0,img.width,img.height,-img.width/2,-img.height/2,img.width,img.height);
//最后返回画布句柄的状态,因为画布句柄是唯一的,它的状态也是唯一的,当我们使用之后方便其他地方使用所以
//需要返回上一次保存的状态,就好像什么事情都没有发生过
context.restore();
}
}
}
|
实际上这不是一个面向对象的过程,而是一个过程式的,我们只有唯一的画布句柄使用,每个独立对象的属性改变都是通过画布句柄改变的,这里唯一难理解的地方就是drawImage,实际上它就是一个切片的方法,第一个是图像对象,0,0表示开始切片的位置,我们现在需要显示完整的图像,所以切片坐标不做任何偏移,然后是切片的宽度和高度,当然我们也选择了图片本身的宽度和高度,之后的参数可以理解为图像的旋转中心点,现在默认为图像的左上角也就是0,0所以我们需要把它移动到图像的中心点去,所以公式就是负的图片宽度和高度的一半,canvas的坐标系上左为负,下右为正,现在万事具备,让我们新建一个Main.js,然后开始使用他们吧.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//创建图像对象
var
image=
new
Image();
//输入载图图像的地址
image.src=
"haizei.jpg"
;
//图片载入完毕开始创建游戏场景
image.onload =
function
()
{
init();
}
//定义场景管理器
var
stage2d;
//初始化函数
function
init () {
//创建场景管理器
stage2d=
new
Stage2D();
//启用场景逻辑
stage2d.init();
//创建一个动画对象,参数为加载好的图像
var
mc=
new
MovieClip2D(image);
//将动画对象添加到场景上
stage2d.addChild(mc);
}
|
现在使用起来就很方便了,等待资源加载完毕后,我们开始创建场景,然后启用场景的逻辑,之后我们的游戏开发主要是针对动画对象(MovieClip)的操作,所以我们不再关心它是如何被显示出来了,只关心它的游戏逻辑,现在我们试试创建多个动画对象并且改变他们的属性.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var
mc2=
new
MovieClip2D(image);
mc2.x=300;
mc2.y=300;
stage2d.addChild(mc2);
var
mc3=
new
MovieClip2D(image);
mc3.x=600;
mc3.y=300;
mc3.scaleX=.5;
mc3.scaleY=.5;
stage2d.addChild(mc3);
var
mc4=
new
MovieClip2D(image);
mc4.x=800;
mc4.y=300;
mc4.scaleX=.5;
mc4.scaleY=.5;
mc4.rotation=45;
stage2d.addChild(mc4);
|
看了这么久的代码,现在让我们看看我们究竟画的是什么.
怎么样,有开始准备做游戏的蠢蠢欲动的心了吗,实际上我们把绘图的底层逻辑封装了起来,而在游戏逻辑中我们已经不再关心他们的存在,而只需要创建和使用,之后的章节都会基于现在的基础慢慢完善,所以最好现在开始准备好他们吧,虽然现在他们看起来是非常基础的内容.
这一章就到这里结束了,下一章开始会在这一章的代码基础之上开始我们的动画之旅,敬请期待