html5 MMORPG Game研究 一 精灵

翅膀的初衷

发表于2013-10-12 10:33:25

  在此之前,建议安装最新版火狐浏览器或者Chrome,编辑器可以Dreamweaver,或者记事本也可以,如果有更专业的编辑则更好,只要具备这些条件,那么一切都已经准好了,我们可以开始神奇的html5之旅了!

  首先,我们建立一个基本的html5页面

	<!DOCTYPE html> 
<html>
<head>
<meta charset="utf-8">
<title>DEMO</title></head>
<body>
<canvas id="scene" width="800" height="600"></canvas>
</body>
</html>

 

  对比XHTML,它的头部申明变短了,不需要加入那长长的一串URL地址了,而且meta标签的申明也简化了!这 里我们就建立了一个基本的html5页面,使用UTF-8编码!而且我们还使用了一个canvas标签!html5通过canvas提供了相关图形处理接 口,我们通过它就可以处理各种各样的图形了,在canvas中我们可以绘制各种图形,比如矩形,圆行,直线,曲线等,同时出可以绘制各种现有图片!

下面的例子,我将在canvas(下称为画布)上绘制一张小图片(精灵)(在这里,我假定大家已经知道了帧与Sprite(精灵)的概念!如果对这两个概念不了解的自行查询)!

var ctx = document.getElementById("scene").getContext("2d");
var img = new Image();
img.onload=function(){
    ctx.drawImage(img,0,0,75,70,0,0,75,70);
}
img.src="dragon.gif";
显示结果:

   我们成功在画布上画出一个精灵,第一行通行getContext("2d")获得了一个CanvasRenderingContext2D对 象,然后创建了一个Image对象,并在Image加载完成的事件中,使用drawImage方法将我们的精灵绘制到了画布上!

  看起来很简单,好像也没有什么激动人心的,是的,只是一张小小的图片,不过一切复杂的事物不都是加简单的东西组成的吗?我们先来看一下drawImage方法的相关参数:

  drawImage有多个参数的重载!最多为九个,下面以以九个参数为例解释下各个参数的作用:drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh)

 

 

参数

参数

说明

参数1

image

图片

参数2

sx

图片裁剪的X位置

参数3

sy

图片裁剪的Y位置

参数4

sw

裁剪的宽度

参数5

sh

裁剪的高度

参数6

dx

画布的X位置

参数7

dy

画布的Y位置

参数8

dw

绘制到画布上的宽

参数9

dh

绘制到画布上的高

 

 

  所有方法如下:

void drawImage(in HTMLImageElement image, in float dx, in float dy, optional in float dw, in float dh); 
  void drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh); 
  void  drawImage(in  HTMLCanvasElement  image,  in  float  dx,  in  float dy, optional in float dw, in float dh); 
  void  drawImage(in  HTMLCanvasElement  image,  in  float  sx,  in  float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh); 
  void drawImage(in HTMLVideoElement image, in float dx, in float dy, optional in float dw, in float dh); 
  void drawImage(in HTMLVideoElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh);

  利用CanvasRenderingContext2D对象还可以进行像素级的操作,这个我将会在后期讲到,目前只要利用drawImage方法就可以做出很多有趣的东西来了!

 

  下面我们来做一个会动的精灵!其实每个动画,都是由一张一张的图片(帧)快速切换而得到的!下面,我们将利用这个原理,每隔一定时间,从下面的图片上截取一张小图片绘制到画布上,形成动画,让小红龙扇动翅膀!

  首先要用到一张大图:

 

var ctx = document.getElementById("scene").getContext("2d");
var img = new Image();
var x =0;
img.onload=function(){
    setInterval(function(){
        if(x>=9){
            x=0;
        }else{
            x++;
        }
        
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        ctx.drawImage(img,x * 75,0,75,70,n,0,75,70);
    },50)
}
img.src="dragon.gif";

在这里,使用了定时器,每隔50毫秒就会在图片截取一张75 * 70像素大小的小图并绘制出来,且每次向右移75像素,直到到最右端时重新从0开始,不停循环!小红龙就开始不停扇动翅膀了!

好,翅膀开始扇动起来,我们的小红龙也要展翅高翔了,下面,我就让小红龙在我们的控制下开始飞行!

drawImage方法的九个参数中,第一个参数是绘制对象,可以是图片,也可以是canvas,第二到五个参数是图片的裁剪参数,控制裁剪的位置与大小,第六 至九个参数控制绘制到画布的位置与大小!我们只要更改图片在画面的位置就可以让小红龙“飞行”起来了!即动态更改第6,第7个数值即可!

  不过先别急,在这处理这个过程前,还有涉及到一个方向的问题,在2D游戏中,一般主流有四个方向或者八个方向两种(横版的是二个方向的,最近也比较流行),我这里将以八个方向为例,演示如何控制小红龙的飞行!

  为了方便控制,我先定义一个对象字面量!

var Directions={
        North:6,
        NorthEast:7,
        East:0,
        SouthEast:1,
        South:2,
        SouthWest:3,
        West:4,
        NorthWest:5
    };

我在这里用0-7 来代表东南西北等八个方向!这里哪个方向代表哪个数字是有一定规律的,细心的朋友可能发现了,在素材图中,每一行均代表了一个方向!Directions 中每个方向的值对应每行的索引值,比如第一行,红龙的朝向为东,则Directions.East=0,第二行为东南,则 Directions.SouthEast=1,依此类推!

  然后定义一个对象来保存精灵的当前位置,一个对象来保存精灵当前朝向!

var currentPoint = {"X":0,"Y":0};
var direction=0;

 

  然后可以通过onclick事件来处理精灵的移动!另外,我通过canvas的onmousemove事件来获取当前的鼠标点击坐标,完整代码如下

var ctx = document.getElementById("scene").getContext("2d");
var ctxW = document.getElementById("scene").width;//画布宽
var ctxH = document.getElementById("scene").height;//画面高
var currentPoint = {"X":0,"Y":0};//精灵当前位置
var mousePoint = {"X":0,"Y":0};//鼠标相对位置
var direction=0;//当前朝向
var Directions={
        North:6,
        NorthEast:7,
        East:0,
        SouthEast:1,
        South:2,
        SouthWest:3,
        West:4,
        NorthWest:5
    };
    
var timer;//定时器
//获取鼠标相对坐标
document.getElementById("scene").onmousemove=function(e){
    e = e || window.event;
    if(e.pageX || e.pageY){
        mousePoint.X=parseInt(e.pageX);
        mousePoint.Y = parseInt(e.pageY);
    }else{
        mousePoint.X=parseInt(e.clientX + document.body.scrollLeft - document.body.clientLeft);
        mousePoint.Y=parseInt(e.clientY + document.body.scrollTop  - document.body.clientTop);
    }
    var boundingClient=document.getElementById("scene").getBoundingClientRect();
    mousePoint.X-=parseInt(boundingClient.left+document.documentElement.scrollLeft);
    mousePoint.Y-=parseInt(boundingClient.top+document.documentElement.scrollTop);
};
//获取两个坐标点的距离
function GetDistance(x,y){
    return Math.sqrt(Math.pow((x.X - y.X), 2) + Math.pow((x.Y - y.Y), 2));
};
//获取精灵朝向
function GetDirection(current,target){
    var n = (target.Y - current.Y) / (target.X - current.X);
    if (Math.abs(n) >= Math.tan(Math.PI * 3 / 8) && target.Y <= current.Y) {
        return Directions.North;
    } else if (Math.abs(n) > Math.tan(Math.PI / 8) && Math.abs(n) < Math.tan(Math.PI * 3 / 8) && target.X > current.X && target.Y < current.Y) {
        return Directions.NorthEast;
    } else if (Math.abs(n) <= Math.tan(Math.PI / 8) && target.X >= current.X) {
        return Directions.East;
    } else if (Math.abs(n) > Math.tan(Math.PI / 8) && Math.abs(n) < Math.tan(Math.PI * 3 / 8) && target.X > current.X && target.Y > current.Y) {
        return Directions.SouthEast;
    } else if (Math.abs(n) >= Math.tan(Math.PI * 3 / 8) && target.Y >= current.Y) {
        return Directions.South;
    } else if (Math.abs(n) > Math.tan(Math.PI / 8) && Math.abs(n) < Math.tan(Math.PI * 3 / 8) && target.X < current.X && target.Y > current.Y) {
        return Directions.SouthWest;
    } else if (Math.abs(n) <= Math.tan(Math.PI / 8) && target.X <= current.X) {
        return Directions.West;
    } else if (Math.abs(n) > Math.tan(Math.PI / 8) && Math.abs(n) < Math.tan(Math.PI * 3 / 8) && target.X < current.X && target.Y < current.Y) {
        return Directions.NorthWest;
    } else {
        return 0;
    }
};
//判断精灵是否到达指定坐标
function RatherPoint(p1,p2){
    switch(direction){
        case Directions.North:
            return p1.Y<=p2.Y;
        case Directions.NorthEast:
            return p1.X>=p2.X||p1.Y<=p2.Y;
        case Directions.East:
            return p1.X>=p2.X;
        case Directions.SouthEast:
            return p1.X>=p2.X||p1.Y>=p2.Y;
        case Directions.South:
            return p1.Y>=p2.Y;
        case Directions.SouthWest:
            return p1.X<=p2.X||p1.Y>=p2.Y;
        case Directions.West:
            return p1.X<=p2.X;
        case Directions.NorthWest:
            return p1.X<=p2.X || p1.Y<=p2.Y;
    };
    return true;
};
//获取每次移动步长
function GetMovePoint(toPoint){
    var m=0,n=0,t=1;
    var speed=10;//移动速度
    switch(direction){
        case Directions.North:
            m=0;
            n=-speed;
            break;
        case Directions.NorthEast:
            t = GetDistance(currentPoint,toPoint);
            m=speed * (toPoint.X-currentPoint.X)/t;
            n=-speed * (currentPoint.Y - toPoint.Y)/t;
            break;
        case Directions.East:
            m=speed;
            n=0;
            break;
        case Directions.SouthEast://alert("Directions.SouthEast"+toPoint.X);
            t = GetDistance(currentPoint,toPoint);//alert(t);
            m=speed * (toPoint.X-currentPoint.X)/t;
            n=speed * (toPoint.Y-currentPoint.Y)/t;
            break;
        case Directions.South:
            m=0;
            n=speed;
            break;
        case Directions.SouthWest:
            t = GetDistance(currentPoint,toPoint);
            m=-speed * (currentPoint.X-toPoint.X)/t;
            n=speed * (toPoint.Y-currentPoint.Y)/t;
            break;
        case Directions.West:
            m=-speed;
            n=0;
            break;
        case Directions.NorthWest:
            t = GetDistance(currentPoint,toPoint);
            m=-speed * (currentPoint.X-toPoint.X)/t;
            n=-speed * (currentPoint.Y-toPoint.Y)/t;
            break;
    };
    return {"X":m,"Y":n};
}
//鼠标点击事件 
document.getElementById("scene").onclick=function(){
    clearInterval(timer);//清除定时器
    var tp = {"X":mousePoint.X,"Y":mousePoint.Y};//鼠标点击坐标,引用值不能直接用mousePoint,不然当鼠标在点击后移动时,目的坐标会变更
    direction = GetDirection(currentPoint,tp);//设置精灵朝向
    var movePoint = GetMovePoint(tp);//获取移动步长
    timer=setInterval(function(){//判断是否到达目标
        if(!RatherPoint(currentPoint,tp)){
            currentPoint.X+=movePoint.X;
            currentPoint.Y+=movePoint.Y;
            //碰撞检测
            if(currentPoint.X>ctxW){
                currentPoint=ctxW;
                clearInterval(timer);
            }
            if(currentPoint.X<0){
                currentPoint=0;
                clearInterval(timer);
            }
            if(currentPoint.Y>ctxH){
                currentPoint=ctxH;
                clearInterval(timer);
            }
            if(currentPoint.Y<0){
                currentPoint=0;
                clearInterval(timer);
            }
        }else{
            clearInterval(timer);
        }
    },100);
}

var index=0;
var img = new Image();
img.onload=function(){
    setInterval(function(){
        if(index>=9){
            index=0;
        }else{
            index++;
        }
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        ctx.drawImage(img,index * 75,direction * 70 ,75,70,currentPoint.X,currentPoint.Y,75,70);
    },50);
}
img.src="dragon.gif";

GetDistance, GetDirection这两个方法参考了深蓝的右手的相关博文,有兴趣的朋友可以去看下他写的Silverlight游戏开发系列教程,地址是:http://www.cnblogs.com/alamiye010

演示地址:http://www.jiniannet.com/html5
本文同步发表于博客园


原创作品,转载请注明作者:翅膀的初衷 出自:http://www.jiniannet.com

注:原域名iis0已弃用,启用新域名www.jiniannet.com