html5 MMORPG Game研究 二 封装

翅膀的初衷

发表于2013-10-12 10:47:18

    在之前的文章中,我们实现了一个基本的游戏精灵--一条红色的飞行的小飞龙,在这一节中,我将对这些方法进行封装,以方便进行下一步的开发!

    首先了解下什么叫立即调用的函数:

(function(){
//你的代码
})();

顾名思义,立即调用的函数即函数定义完后会立即调用或执行自己,在这里,它相当于

function 函数名(){
//你的代码;

函数名();
}

  封装在里面的代码,外部是无法访问的,这样能确保不会因为外面的同名变量而产生不可预料的异常!网上一些 JS库都是这种方式封装的,包括jQuery!在这里我们建一个立即调用函数并将之前用到的几个方法,封装进去,然后通过window.j2d来访问里面的方法!

function(window){
var j2d={
    //canves context对象
    Context:undefined,
    //坐标
    Point:function(x,y){
        if(isNaN(x)){
            x=0;
        }
        if(isNaN(y)){
            y=0;
        }
        return {"X":x,"Y":y};
    },
    //每秒帧数
    FPS:10,
    //精灵容器
    Containet:new Array(),
    //八个方向
    Directions:{
        North:0,
        NorthEast:1,
        East:2,
        SouthEast:3,
        South:4,
        SouthWest:5,
        West:6,
        NorthWest:7
    },
    //获取两点之间距离
    GetDistance:function(x,y) {
        return Math.sqrt(Math.pow((x.X - y.X), 2) + Math.pow((x.Y - y.Y), 2));
    },
    
    //计算当前坐标与目标点之间的正切值以获取朝向
    GetDirection:function(current,target){
        //...略
    },
    
    //是否到达指定坐标
    
    RatherPoint:function(direction,p1,p2){
        //...略
    }
    //
};
window.j2d=j2d;
})(window);

    封装好后,我们可以直接通用j2d来调用相关方法了!不过在这里要先改进一下计算移动步长GetMovePoint这个方法!

    如果精灵是横向或者纵向移动,每次移动步长直接是当前移动速度即可,如果是斜向移动,则需要计算目标点的长度,并根据对应的比例获取x坐标长度与y坐标长度!

    根据勾股定理:两条直角边的长度的平方之和等于斜边的平方,则得出获取两个坐标点的距离为Math.sqrt(Math.pow((point2.X - point1.X), 2) + Math.pow((point2.Y - point1.Y), 2));

    因为同号两数相乘得正数,异号两数相乘得负数,所以这里得出的结果永远是正数!再根据 斜边/长(高) = 速度/x步长(y步长) (两者比例一致的,斜边上的移动步长即为当前速度)就能得出对应的x轴移动步长或者y轴移动步长了!优化后方法如下(方法名调整为 GetMoveStep):

GetMoveStep:function(point1,point2,speed){
    var length = j2d.GetDistance(point1,point2);
    var p = j2d.Point(0,0);
    if(length==0){
        return p;
    }
    p.X= (point2.X - point1.X) * speed / length;
    p.Y= (point2.Y - point1.Y) * speed / length;
    return p;
}


  下面我将实现精灵对象的基类封装,每个精灵都有一个Draw方法,用来绘制精灵对象,一个Update方法,用来实现相关逻辑处理

ObjectEntity:function(){
        this.DrawObject=undefined;//绘制对象
        this.ID=undefined;//编号
        this.Name=undefined;//名字
        this.Offset={"X":0,"Y":0};//偏移量
        this.Containet=new Array();//子对象集合
        this.Width=0;//
        this.Height=0;//
        this.Direction=0;//朝向
        this.DrawPoint={"X":0,"Y":0};//绘制起始坐标
        this.Coordinate={"X":0,"Y":0};//当前所在坐标
        this.Draw=function(){
            if(j2d.Context&&this.DrawObject){
                var point = this.Coordinate;
                var w = this.Width;
                var h = this.Height;
                j2d.Context.drawImage(this.DrawObject,//绘制对象
                                      parseInt(this.DrawPoint.X),//
                                      parseInt(this.DrawPoint.Y),//
                                      parseInt(w),//裁剪尺寸
                                      parseInt(h),//裁剪的尺寸
                                      parseInt(point.X-this.Offset.X),//
                                      parseInt(point.Y-this.Offset.Y),
                                      parseInt(w),//绘制大小。
                                      parseInt(h) //绘制大小。
                                 );
                for(var n=0;n<this.Containet.length;n++){//执行子集合里面的Draw方法
                    if(this.Containet[n]&&this.Containet[n].Draw){
                        this.Containet[n].Draw(gameTime);
                    }
                }
            }
        }
    }


  然后,我们再定义一个地图对象,继承自ObjectEntity,其实javascript中并不存在类,继承等东西,但是我们可以通过一些方法来实现继承等功能,实现继承有多种方法,这里使用call来实现(其它还有原型继承等,更多请自行搜索了解)!

 

Map:function(){
        j2d.ObjectEntity.call(this);
        this.Update=function(gameTime){
            for(var n=0;n<this.Containet.length;n++){
                if(this.Containet[n]){
                    if(this.Containet[n].Update){
                        this.Containet[n].Update(gameTime);
                    }
                }
            }
        };
    }

 

  Map里面只新增一个Update方法,遍历子集合里面的所有对象并运行其Update方法(注:随着后面更多的功能增加,会有更多的方法增减,但是目前为了方便理解与突出重点,用不到的方法暂时不放出来)

  OK,地图定义完了,再定义一个Sprite类,在之前的演示中,我们的精灵只有一个动行,即飞行,但是这远远不够的,可能我还要有站立,行 走,攻击,死亡,打坐,受伤,施法...等很多动作,在这里我不想把这些动作写死,我打算在实际开发中动态添加,在这里我定义一个 AddAnimation方法,来用添加动作动画,再定义一个SetAnimation,来设置当前动作。但是精灵的基本图片要求不变,即每个方向为一 行!其实在一般的游戏中,精灵图片一般都是单张单张分开的,但是为了减少HTTP请求,我将所有图片都拼合到一张图片里面了,加载的时候慢点,但是不需要 频繁的去请求,智者见智,仁者见仁,如果不想一次加载的,可以自己更改相关方法,但是原理还是一致的!

Sprite:function(){
        j2d.ObjectEntity.call(this);
        this.Speed=100;//速度
        var action="",//当前动作
        list,//动作列表
        index=0,//当前动作的帧索引
        timeStep=0;//距上次更新时的时间步长
        this.AddAnimation=function(key,value){
            if(!list){
                list=new Object();
            }
            list[key]=value;
            return true;
        };
        
        this.SetAnimation=function(key){
            if(action!=key){
                index=0;
                action=key;
                timeStep=3600000;//这里将时间设为一个较大的数值是为了切换动作后能马上更换图片
            }
        };
        
        this.Update=function(gameTime){
            if(action&&action!=""&&list[action]){
                timeStep+=gameTime;
                if(timeStep>=list[action].Interval){//是否达到每帧的切换时间
                    timeStep=0;
                    this.DrawPoint=j2d.Point(list[action].Frames[index]*this.Width,this.Direction*this.Height);
                    if(list[action].Callback){
                        list[action].Callback(index,action);
                    }
                    if(index>=list[action].Frames.length-1){
                        index=0;
                    }else{
                        index++;
                    }
                }
            }
            
            for(var n=0;n<this.Containet.length;n++){
                if(this.Containet[n]){
                    if(this.Containet[n].Update){
                        this.Containet[n].Update(gameTime);
                    }
                }
            }
        };
    }


  实现了封装后,下面的开发中我们将大大简化相关代码,在下面,我将再次实现一个会行走的精灵,这次我不再打算用上次的小龙了,必竟这是老外们的龙,咱 们中国人,当然要用中国元素,所以我决定用一个小美女,嗯,现在美女大家都喜欢,不是吗,特别对于代码老是报“找不到对象”的错误的同学们来说!

  先展示一下我们要用到的精灵素材(部分)

 

j2d.Context=document.getElementById("scene").getContext("2d");

//为Sprite添加一个RunTo方法,用来控制行走
j2d.Sprite.prototype.RunTo=function(point,Callback){
    clearInterval(this.runTimer);
    var speed=parseFloat(this.Speed/1000)*(1000/j2d.FPS);
    //point = arguments[0];
    var me = this;
    
    var moveStep = j2d.GetMoveStep(this.Coordinate,point,this.Speed);

    me.runTimer=setInterval(function(){
        var x = parseInt(me.Coordinate.X+moveStep.X),
        y=parseInt(me.Coordinate.Y+moveStep.Y);
        if(x<0||x>(j2d.Context.canvas.width-me.Offset.X)||y<0||y>(j2d.Context.canvas.height - me.Offset.Y)
||j2d.RatherPoint(me.Direction,me.Coordinate,point)){//碰撞检测与判断是否到达目标点
            clearInterval(me.runTimer);
            if(Callback){
                Callback(me);
            };
        }else{
            me.Coordinate.X=x;
            me.Coordinate.Y=y;
        }
    },1000/j2d.FPS)
};

var map,hero;
var mapImg = new Image();
mapImg.onload=function(){
    map = new j2d.Map();//地图
    map.DrawObject=mapImg;
    map.Width = mapImg.width;
    map.Height=mapImg.height;
    var heroImg = new Image();
    heroImg.onload=function(){
        hero = new j2d.Sprite();//美女
        hero.DrawObject=heroImg;
        hero.Width = 70;
        hero.Height=200;
        hero.Speed=5;//移动速度
        hero.Offset = j2d.Point(35,160);
        hero.Coordinate = j2d.Point(400,350);//所处位置
        hero.AddAnimation("Stand",{//添加动作-站立
            Interval:10,
            Frames:[0,1,2,3,4,5,6,7,8,9]
        });
        hero.AddAnimation("Walk",{//添加动作 - 行走
            Interval:10,
            Frames:[10,11,12,13,14,15,16,17,18,19]
        });
        hero.SetAnimation("Stand");//设置当前动作为-站立
        map.Containet.push(hero);
        j2d.Containet.push(map);
        
        j2d.Context.canvas.onclick=function(){//点击事件
            var point = j2d.GetMousePoint();//获取点击坐标
            hero.Direction=j2d.GetDirection(hero.Coordinate,point);//设置朝向
            hero.SetAnimation("Walk");//设置行走动画
            hero.RunTo(point,function(){//开始行走
                hero.SetAnimation("Stand");//到达目标点的回调方法:此处为改变动作为站立
            });
        };
        
        j2d.Run();//开始执行
        
        
    };
    heroImg.src="Data/Sprite/1.png";
};
mapImg.src = "Data/Map/1.gif";

好,我们来运行一下结果吧:

 

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

相关源码,请到此下载:http://www.cnblogs.com/hnvvv/archive/2012/01/18/2326025.html


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

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