zoukankan      html  css  js  c++  java
  • 『HTML5挑战经典』是英雄就下100层-开源讲座(一)从天而降的英雄

    是英雄就下100层是一款经典的手机小游戏,以前是在诺基亚手机上十分有名。今天我们就用HTML5和lufylegend一步步地实现它。

    一,准备工作

    首先,你需要下载lufylegend,下载地址如下:

    http://lufylegend.googlecode.com/files/lufylegend-1.7.0.rar

    接着你需要了解lufylegend,API介绍如下:

    http://lufylegend.com/lufylegend/api

    接下来我们准备几张图片:

    人物图片

    游戏背景

    游戏旁边的墙

    顶部的钉子

    有朋友也许看了就喷血了,内心坚强的估计都想打我了。因为我的人物图片是三国志曹操传里的。其实在下也是没办法呀,找不到图了,只有用这一张稍微长得帅一点的士兵哥哥了。

    接着让我们来做初始化,首先我们需要几个层用来放人物,背景,建筑物,障碍等,所以首先定义了几个层变量。

    var backLayer,loadingLayer,mapLayer,stageLayer,buildingLayer,charaLayer,overLayer;

    另外一些杂七杂八的变量,都添了注释,应该能看懂的:

    //加载图片用的数组
    var imglist = {};
    var imgData = [
        {name:"player",path:"./images/player.png"},
        {name:"back",path:"./images/gameback.png"},
        {name:"apron",path:"./images/apron.png"},
        {name:"nail",path:"./images/nail.png"}
    ];
    //人物变量
    var hero;
    var charaIniX = 100;
    var charaIniY = 100;
    var isMirror = false;

    然后进行游戏初始化:

    init(30,"mylegend",stageWidth,stageHeight,main);

    init是lufylegend中初始化函数,用法可以到api里面查,这里不多啰嗦了,接下来看看main函数里的东西:

    function main(){
        //初始化加载层
        loadingLayer = new LoadingSample1();
        addChild(loadingLayer);
        //载入图片,并显示进度条
        LLoadManage.load(
            imgData,
            function(progress){
                loadingLayer.setProgress(progress);
            },
            function(result){
                imglist = result;
                removeChild(loadingLayer);
                loadingLayer = null;
                //开始游戏初始化
                gameInit();
            }
        );
    }

    这个main函数是在做游戏中的图片加载,加载完后调用gameInit。可以在上面定义的变量中找到imgData,不难看出它是装有图片路径的数组,加载时就加载就能将游戏中的图片一张张地加入到了游戏中。至于LLoadManage的方法可以在api里看看,因为即使我来解释,我也只能抄抄api里的东西。

    接下来看看gameInit:

    function gameInit(){
        //初始化层
        initLayer();
        //加入游戏人物
        addChara();
        //加入游戏事件
        addEvent();
        //加入游戏背景
        addGameBack();
        //加入游戏挡板
        addApron();
        //加入顶部钉子
        addNail();
    }

    上面的代码都加了注释,应该不难看懂。(其实也应该看懂,因为全是调用函数)

    到此,游戏初始化搞定。

    二,游戏背景的实现

    刚才我们看了gameInit,里面的动西我们还讲完,我们挨着挨着看里面调用的函数。

    首先是initLayer,看英语就能知道是初始层,具体代码如下:

    function initLayer(){
        //加入底层
        backLayer = new LSprite();
        addChild(backLayer);
        //加入地图层
        mapLayer = new LSprite();
        backLayer.addChild(mapLayer);
        //加入障碍层
        stageLayer = new LSprite();
        backLayer.addChild(stageLayer);
        //加入建筑层
        buildingLayer = new LSprite();
        backLayer.addChild(buildingLayer);
        //加入人物层
        charaLayer = new LSprite();
        backLayer.addChild(charaLayer);
        //加入其他层
        overLayer = new LSprite();
        backLayer.addChild(overLayer);
    }

    首先我将刚刚定义的层变量通通实例化为LSprite,可能懂点ActionScript的朋友们知道这个东西(虽然我不懂ActionScript),它就相当于一个容器,里面可以放图片,文字,按钮,绘制的图……凡所能放的,无所不能放,这就是LSprite。LSprite可以理解成一个层,因为用它可以轻易地实现层次化效果。

    由于引擎是仿照Actionscript语法开发的,所以用法和ActionScript中的一样。当然不懂这玩儿意的朋友可以再去翻翻api,里面的介绍很详细。

    添加好层了之后,我们开始加入人物。

    看看addChara里的代码:

    function addChara(){
        //创建一个人物
        hero = new Charactor();
        //确定人物位置
        hero.x = charaIniX;
        hero.y = charaIniY;
        //加入到人物层
        charaLayer.addChild(hero);
    }

    这里的代码一会儿单独讲,这里不作细讲。

    接着是加入事件,大家可以看看代码:

    function addEvent(){
        //加入鼠标事件
        backLayer.addEventListener(LMouseEvent.MOUSE_DOWN,onDown);
        backLayer.addEventListener(LMouseEvent.MOUSE_UP,onUp);
        //加入onframe调用
        backLayer.addEventListener(LEvent.ENTER_FRAME,onframe);
    }
    function onDown(event){
        //取出鼠标坐标
        var mouseX = event.offsetX;
        //判断人物移动方向【点击人物右边,让人物向右移动;点击人物左边,让人物向左移动】
        if(mouseX > hero.x+20){
            //角色向右移动
            charaMove("right");
        }else if(mouseX < hero.x+20){
            //角色向左移动
            charaMove("left");
        }else{
            hero.mode = "stand";
        }
    }
    function onUp(){
        //松开鼠标后改变造型
        hero.mode = "stand";
        //改变站立时的方向和样子
        hero.anime.setAction(0,0,1,isMirror);
    }
    function onframe(){
        //使用Charactor中run函数,让人物动起来
        hero.run();
    }

    至于addEventListener的使用方法可以看看api,它和js原来的addEventListener的用法不一样。另外提一下,LMouseEvent.MOUSE_DOWN是鼠标点击事件,LMouseEvent.MOUSE_UP是鼠标弹起事件。LEvent.ENTER_FRAME是时间轴事件,由于我们的界面在不断的刷新,那么每刷新一次我们就调用这个事件所对应的函数。

    接下来3个函数全是绘画背景,addNail是加入顶部的钉子,addApron是加入游戏两边的墙,addGameBack是贴背景图。代码如下:

    function addGameBack(){
        //循环显示背景块,以便用来平铺背景
        for(var i=0;i<4;i++){
            for(var j=0;j<4;j++){
                var backBitmapdata = new LBitmapData(imglist["back"],0,0,130,130);  
                var backBitmap = new LBitmap(backBitmapdata);
                //每画一块后移背景块
                backBitmap.x = j*130;
                backBitmap.y = i*130;
                mapLayer.addChild(backBitmap);
            }
        }
    }
    function addApron(){
        //循环显示挡板块,以便用来铺出两条挡板
        for(var i=0;i<15;i++){
            for(var j=0;j<2;j++){
                var apronBitmapdata = new LBitmapData(imglist["apron"],0,0,17,34);  
                var apronBitmap = new LBitmap(apronBitmapdata);
                //每画一块后移挡板块
                apronBitmap.x = j*383;
                apronBitmap.y = i*34 + 20;
                buildingLayer.addChild(apronBitmap);
            }
        }
    }
    function addNail(){
        //循环显示钉子,以便用来铺出钉条
        for(var i=0;i<30;i++){
            var nailBitmapdata = new LBitmapData(imglist["nail"],0,0,14,14);  
            var nailBitmap = new LBitmap(nailBitmapdata);
            //每画一块后移钉子
            nailBitmap.x = i*14 + 5;
            nailBitmap.y = 0;
            buildingLayer.addChild(nailBitmap);
        }
    }

    addGameBack中可以看见LBitmap和LBitmapData两个类,它们分别是用来贴图的,LBitmapData里是装图片数据,LBitmap是将LBitmapData里的数据画出。用法可以看看api。由于我们的背景图很小,所以我们为了添满画布,我不得不采用平铺和拉伸两种方法。在考虑采用平铺还是拉伸时,我认为平铺更能将效果处理好一些,拉伸后图片就不好看了。因此我尝试平铺,没想到lufylegend中没有平铺,没办法只有我自己写了。

    我计算出有要铺满画布,我们需要横着画四次,竖着画四次。

    看代码:

    for(var i=0;i<4;i++){
        for(var j=0;j<4;j++){
            var backBitmapdata = new LBitmapData(imglist["back"],0,0,130,130);  
            var backBitmap = new LBitmap(backBitmapdata);
            //每画一块后移背景块
            backBitmap.x = j*130;
            backBitmap.y = i*130;
            mapLayer.addChild(backBitmap);
        }
    }

    我弄一个循环套循环的方法,让它每铺一块就移动到下一块的位置再继续铺,这样一来就可以铺出背景了。

    添加钉子和围墙的方法与其类似,不一一说了。

    运行代码,效果很理想:



    三,英雄降临(Hero Fall)

    这一小节的名字有点奇怪。当然所谓的奇怪当然是后面的那一串英文Hero Fall。其实这个Hero Fall来自最新上映的电影007天幕杀机,它的英文是Sky fall,所以我也借此多打两三个字符。当然,我可不是来和大家交谈电影的,也不是来交谈如何多打写字符。主要目的是开发游戏。哈!

    刚刚废话了一下,现在回到游戏中。

    首先我们建立一个人物类Charactor,代码如下:

    /**
    *Charactor人物类
    */
    function Charactor(){
        base(this,LSprite,[]);
        var self = this;
        //初始化人物模式为"stand"
        self.mode = "stand";
        //将图片分解为装满坐标的二维数组
        var list = LGlobal.divideCoordinate(192,256,4,4);
        var data = new LBitmapData(imglist["player"],0,0,48,64);
        //添加动画类
        self.anime = new LAnimation(self,data,list);
        //调整动画
        self.anime.setAction(1,0,1,false);
        //调整动画频率的相关变量
        self.step = 2;
        self.stepindex = 0;
    }

    首先一来就可能有朋友不懂了,base是神马?告诉你吧,是lufylegend中的继承函数,参数分别是:需要继承的类,基类(被继承的类),传给被继承类的参数,当然,api里也有介绍,想了解更多的朋友可以看看。接着我们将目光跳到self.mode这一行,这个mode在游戏中的作用很大,它包括判断是否碰到障碍物,以及任务的方向,这些都归它管。接下来来我们再看看LGlobal.divideCoordinate这个东西,它是专门用来切坐标的,用它可以把一张图片分解成一个二维数组,这样可以用来方便动画处理,可以看看api了解更多。

    构造好了就可以在addChara函数中用变量hero实例化。并加到charaLayer中。

    再说说LAnimation类,它是一个播放动画的类,它和LGlobal.divideCoordinate用法可以举例说明:

    init(100,"legend",800,450,main); 
    var imgData = [ {name:"player",path:"player.png"} ]; 
    var imglist; 
    var backLayer,hero; 
    function main(){ 
        LLoadManage.load(imgData,null,gameInit); 
    } 
    function gameInit(result){
        imglist = result; 
        backLayer = new LSprite(); 
        addChild(backLayer); 
        var list = LGlobal.divideCoordinate(256,256,4,4); 
        var data = new LBitmapData(imglist["player"],0,0,64,64); 
        hero = new LAnimation(backLayer,data,list); 
        backLayer.addEventListener(LEvent.ENTER_FRAME,onframe); 
    } 
    function onframe(){ 
        hero.onframe(); 
    }

    LAnimation类官方说明:

    LAnimation类 LAnimation(layer,data,list)
    ■作用:
    实现简单动画的播放,原理是将一张大的图片,按照保存有坐标的二维数组保存的坐标来逐个显示。
    ■参数:
    layer:LSprite显示层
    data:LBitmapData对象
    list:装有坐标的二维数组
     


    上面的三个参数中,layer是一个LSprite对象,data是一个LBitmapData对象,这些都比较好理解,第三个参数list是一个二维数组,它的格式如下:

    [  
    [{x:0,y:0},{x:0,y:0},{x:0,y:0}],  
    [{x:0,y:0},{x:0,y:0},{x:0,y:0}],  
    [{x:0,y:0},{x:0,y:0},{x:0,y:0}]  
    ]  

    LAnimation对象的setAction函数,有四个参数,分别为:

    LAnimation.setAction(rowIndex,colIndex,mode,isMirror)  
    参数:  
    rowIndex:播放动画的行号  
    colIndex:播放动画的列号  
    mode:(1,0,-1)分别代表(正序播放,静止,倒序播放)  
    isMirror:Boolean型,当设定为true的时候,图片显示为水平翻转后的镜像  

    详细的使用方法还可以看看api。

    接着添加Charactor中的run,再在run里加入动画,这样的话,人物就可以动起来了。

    看看run方法:

    Charactor.prototype.run = function (){
        var self = this;
        //将人物不断下落
        self.y += fallSpeed;
        //减少动画切换的频率
        if(self.stepindex++ > self.step){
            self.stepindex = 0;
            self.anime.onframe();
        }
        //判断人物模式,以便用来移动人物
        if(self.mode == "left"){ //向左移动时的处理
            //判断人物是否到了最左画布边缘
            if(self.x > 10){
                self.x -= heroSpeed;
            }
        }else if(self.mode == "right"){ //向右移动时的处理
            //判断人物是否到了最右画布边缘
            if(self.x < LStage.width - self.getWidth()-20){
                self.x += heroSpeed;
            }
        }else if(self.mode == "stand"){
            return;
        }
    }

    再定义控制下降速度和行走速度变量:

    var heroSpeed = 10;
    var fallSpeed = 10;

    结合注释看看,其实也不难理解。首先,我们的英雄要不断下降,所以将他的y坐标不断地加。接下来播放人物动画,播放人物动画的代码还是值得我们看看:

    if(self.stepindex++ > self.step){
        self.stepindex = 0;
        self.anime.onframe();
    }

    是什么意思呢?由于这个run方法是在界面每刷新一次时调用的,刷新频率由于太高了,而我们的人物动画速度是根据这个频率决定的,所以,频率有多快这个人物动画就动得这么快。因此我们假如想每100ms播放一下人物动画,就必须设定两个变量,一个是刷新次数变量另一个是限定变量。限定变量 = 要求频率÷刷新频率,刷新次数变量初始值 为0。让刷新次数变量在每次刷新后加一,然后判断是否大于那个限定变量,如果是就将刷新次数变量设为0,并播放一帧。这样就可以限定动画频率了。

    接下来我们根据mode来判断人物行走方向,以及是否是行走。再看一遍代码;

    if(self.mode == "left"){ //向左移动时的处理
        //判断人物是否到了最左画布边缘
        if(self.x > 10){
            self.x -= heroSpeed;
        }
    }else if(self.mode == "right"){ //向右移动时的处理
        //判断人物是否到了最右画布边缘
        if(self.x < LStage.width - self.getWidth()-20){
            self.x += heroSpeed;
        }
    }else if(self.mode == "stand"){
        return;
    }

    当mode为left时,人物就往左移,减x坐标就可以;当mode为right时,人物就往右移,加x坐标就可以;当mode为right时,人物就不动。

    不过mode在哪里去改呢?这还要追溯到addEvent中调用的函数。

    再看一下addEvent中调用的函数:

    function onDown(event){
        //取出鼠标坐标
        var mouseX = event.offsetX;
        //判断人物移动方向【点击人物右边,让人物向右移动;点击人物左边,让人物向左移动】
        if(mouseX > hero.x+20){
            //角色向右移动
            charaMove("right");
        }else if(mouseX < hero.x+20){
            //角色向左移动
            charaMove("left");
        }else{
            hero.mode = "stand";
        }
    }
    function onUp(){
        //松开鼠标后改变造型
        hero.mode = "stand";
        //改变站立时的方向和样子
        hero.anime.setAction(0,0,1,isMirror);
    }
    function onframe(){
        //使用Charactor中run函数,让人物动起来
        hero.run();
    }

    当按下鼠标时,我们在addEvent中调用的是onDown函数。接着看看onDown函数:

    function onDown(event){
        //取出鼠标坐标
        var mouseX = event.offsetX;
        //判断人物移动方向【点击人物右边,让人物向右移动;点击人物左边,让人物向左移动】
        if(mouseX > hero.x+20){
            //角色向右移动
            charaMove("right");
        }else if(mouseX < hero.x+20){
            //角色向左移动
            charaMove("left");
        }else{
            hero.mode = "stand";
        }
    }

    首先我们取出鼠标坐标,然后判断这个位置是在人物的哪个方向,也就是判断x坐标哪个大。如果鼠标x坐标大,说明在右边,接着调用charaMove并给参数赋值为right;如果人物x坐标大,说明在左边,接着调用charaMove并给参数赋值为left;如果不大不小就就站在原地。

    看看charaMove里的代码:

    function charaMove(direction){
        switch(direction){
            case "right":
                //改变人物模式"right",使其转换方向
                hero.mode = "right"
                hero.anime.setAction(1,0,1,true);
                //改变站立时的方向变量
                isMirror = true;
                break;
            case "left":
                //改变人物模式为"left",使其转换方向
                hero.mode = "left"
                hero.anime.setAction(1,0,1,false);
                //改变站立时的方向变量
                isMirror = false;
                break;
        }
    }

    这段代码进入后就判断参数是什么,当为right就将人物的mode设定right,并改变人物样式。当为left就将人物的mode设定left,并改变人物样式。

    当松开鼠标时就调用onUp,如下:

    function onUp(){
        //松开鼠标后改变造型
        hero.mode = "stand";
        //改变站立时的方向和样子
        hero.anime.setAction(0,0,1,isMirror);
    }

    代码很简单,也就是当松开鼠标后便改变人物的样子和方向。

    接下来看看onframe:

    function onframe(){
        //使用Charactor中run函数,让人物动起来
        hero.run();
    }

    由于onframe是界面每刷新一次就调用一次,所以如果人物的mode改了,就会马上做出反应。

    好了,运行一下看看:

    按下鼠标:

    测试地址:http://www.cnblogs.com/yorhom/archive/2013/04/06/3002850.html

    还不错吧,英雄果然是不断下降,并且如果按下鼠标英雄会移动。

    现在这个英雄是一个拿给我们玩弄的傀儡,因为这里没有他想看到的跳板。

    下一次就来完成跳板和减血这一方面的内容。希望大家多支持。

    本次源码下载:http://files.cnblogs.com/yorhom/jump(1).rar

  • 相关阅读:
    MVC session过期如何处理跳转
    MVC+EF更新数据库
    python 判断 windows 隐藏文件/系统文件
    字符编码简介
    Essential C++ 学习笔记02--Array/Vector 与指针
    Essential C++ 学习笔记01--基本语法
    mongodb 入门笔记
    何谓可分页和非分页内存
    Git使用
    _stdcall 函数 debug/release汇编代码区别
  • 原文地址:https://www.cnblogs.com/jiangxiaobo/p/6004771.html
Copyright © 2011-2022 走看看