『HTML5梦幻之旅』 - 仿Qt示例Drag and Drop Robot(换装机器人)

起源

在Qt的示例中看到了一个有趣的demo,截图如下:

这个demo的名字叫Drag and Drop Robot,简单概括而言,在这个demo中,可以把机器人四周的颜色拖动到机器人的各个部位,比如说头,臂,身躯等,然后这个部位就会变成相应的颜色,类似于换装小游戏。

上图就是经过愚下的拖动颜色使其简略换装后的样子。

当然,拖动颜色使部件变色的功能并不难实现,关键在于这个机器人是动态的,我们要研究的就恰恰是这个机器人动画是怎么做出来的。

光凭两张图片我们无法知道这个动画到底是什么样子的,大家可以参考本次用html5移植到浏览器平台的demo:

http://wyh.wjjsoft.com/demo/drag_and_drop_robot/

截图如下:

由于愚下对人体运动了解不深,所以demo里的机器人运动得不是很和谐。各位看官可以在文末下载源代码,通过本次讲解,拿回去自己改改,让这个机器人动得更带感一点。

以下是实现过程。

准备工作

先来看看文件结构:

其中,lufylegend-1.9.9.simple.min.js是html5引擎lufylegend里的文件,由于该引擎带有缓动类,所以实现本次效果会容易一些。

引擎官方地址:http://lufylegend.com

中文文档地址:http://lufylegend.com/api/zh_CN/out/index.html

由于下文的代码中会多次出现一些引擎里的类和方法,所以我把这些类和方法在文档里的地址放在下面,供大家参考:

  1. LExtends:http://lufylegend.com/api/zh_CN/out/classes/%E5%85%A8%E5%B1%80%E5%87%BD%E6%95%B0.html#method_LExtends
  2. LLoadManage:http://lufylegend.com/api/zh_CN/out/classes/LLoadManage.html
  3. LInit:http://lufylegend.com/api/zh_CN/out/classes/%E5%85%A8%E5%B1%80%E5%87%BD%E6%95%B0.html#method_LInit
  4. LSprite:http://lufylegend.com/api/zh_CN/out/classes/LSprite.html
  5. LTextField:http://lufylegend.com/api/zh_CN/out/classes/LTextField.html
  6. LDropShadowFilter:http://lufylegend.com/api/zh_CN/out/classes/LDropShadowFilter.html
  7. LTweenLite:http://lufylegend.com/api/zh_CN/out/classes/LTweenLite.html
  8. LGraphics:http://lufylegend.com/api/zh_CN/out/classes/LGraphics.html

实现过程

Main.js

完整代码:

LInit(50, "mydemo", 800, 600, loadRes);

var stageLayer, selectedColorBox = null, partList = null;

function loadRes () {
    var loadList = [
        {path : "./Robot.js"},
        {path : "./Part.js"},
        {path : "./Body.js"},
        {path : "./Head.js"},
        {path : "./Limb.js"},
        {path : "./ColorBox.js"}
    ];

    var loadingTxt = new LTextField();
    loadingTxt.text = "Loading...";
    addChild(loadingTxt);

    LLoadManage.load(loadList, null, function () {
        loadingTxt.remove();

        initStageLayer();
        addRobot();
        addColors();
    });
}

function initStageLayer () {
    stageLayer = new LSprite();
    stageLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height]);
    addChild(stageLayer);

    stageLayer.addEventListener(LMouseEvent.MOUSE_MOVE, function () {
        if (selectedColorBox) {
            selectedColorBox.x = mouseX;
            selectedColorBox.y = mouseY;
        }
    });

    stageLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
        if (selectedColorBox) {
            if (partList) {
                for (var i = 0, l = partList.length; i < l; i++) {
                    var o = partList[i], p = o.part, e = o.exec;

                    if (isPartHitObject(p, selectedColorBox)) {
                        setPartAlpha(p, 1);
                        e.fillColor = selectedColorBox.color;
                        e.draw();
                    }
                }
            }

            selectedColorBox.remove();

            selectedColorBox = null;
        }
    });

    stageLayer.addEventListener(LEvent.ENTER_FRAME, loop);
}

function loop () {
    if (partList && selectedColorBox) {
        for (var i = 0, l = partList.length; i < l; i++) {
            var p = partList[i].part;

            if (isPartHitObject(p, selectedColorBox)) {
                setPartAlpha(p, 0.5);
            } else {
                setPartAlpha(p, 1);
            }
        }
    }
}

function isPartHitObject (list, obj) {
    for (var i = 0, l = list.length; i < l; i++) {
        if (list[i].hitTestObject(obj)) {
            return true;
        }
    }

    return false;
}

function setPartAlpha (list, a) {
    for (var i = 0, l = list.length; i < l; i++) {
        list[i].alpha = a;
    }
}

function addRobot () {
    var robot = new Robot();
    robot.x = (LGlobal.width - robot.getWidth()) / 2;
    robot.y = 220;
    stageLayer.addChild(robot);
}

function addColors () {
    var colorList = [
        "orange",
        "red",
        "yellow",
        "green",
        "blue",
        "lightblue",
        "purple",
        "brown",
        "lightgreen",
        "orangered"
    ];
    var r = (LGlobal.height - 80) / 2;

    var layer = new LSprite();
    layer.x = LGlobal.width / 2;
    layer.y = LGlobal.height / 2;
    stageLayer.addChild(layer);

    for (var i = 0, l = colorList.length; i < l; i++) {
        var angle = 2 * i * Math.PI / l;

        var colorBox = new ColorBox(colorList[i]);
        colorBox.x = r * Math.cos(angle);
        colorBox.y = r * Math.sin(angle);
        layer.addChild(colorBox);

        colorBox.addEventListener(LMouseEvent.MOUSE_DOWN, function (e) {
            selectedColorBox = e.currentTarget.clone();
            selectedColorBox.x = e.offsetX;
            selectedColorBox.y = e.offsetY;
            stageLayer.addChild(selectedColorBox);
        });
    }
}

变量介绍:

  • stageLayer:舞台层
  • selectedColorBox:正在拖动的颜色
  • partList:机器人部件列表,下文会有详细介绍

函数介绍:

  • loadRes:用于加载文件
  • initStageLayer:初始化舞台层。包括舞台层添加事件,以实现拖动颜色以及拖动的颜色与机器人碰撞检测(其中出现变量partList的地方可暂时忽略,读到后文,看官再回头来看,自会明白代码的意思)
  • loop:循环事件监听器
  • isPartHitObject:判断机器人的部件是否与某对象碰撞(判断拖动的颜色是否与机器人部件相碰撞)
  • setPartAlpha:设置机器人部件的透明度(拖动的颜色碰到机器人部件上后,需改变部件透明度以提示碰撞)
  • addRobot:加入机器人
  • addColors:加入四周的颜色

这里主要讲一下如何实现拖动颜色,并如何给部件上色。

首先我们需要的是几个事件:鼠标移动,鼠标按下,鼠标松开,循环事件。鼠标按下是加在ColorBox对象上的(此类于后文讲解),鼠标移动、和松开事件以及循环事件是加载舞台层stageLayer的。当我们在ColorBox对象上按下鼠标,首先要克隆该对象,并将克隆产物赋值给selectedColorBox。这时再移动鼠标,触发鼠标移动事件监听器,并判断到了存在selectedColorBox,即鼠标在某ColorBox上按下,这时就执行ColorBox跟随鼠标操作。当鼠标松开后,首先判断克隆产物selectedColorBox是否正在与机器人部件产生碰撞,如果是则为该部件上色,随后将克隆产物销毁,这时如果再移动鼠标,则检测到克隆产物不存在,则跟随鼠标的操作不会执行。循环事件用于执行如果克隆产物碰到机器人部件则将部件变为半透明的操作。

ColorBox.js

上面的代码中有这个类的出现,这里把这个类的代码展示了:

function  ColorBox (color) {
    var s = this;
    LExtends(s, LSprite, []);

    s.color = color;

    s.graphics.drawArc(0, "", [0, 0, 25, 25, 0, Math.PI * 2], true, color);

    s.filters = [new LDropShadowFilter(null, null, color)];
}

代码很简单,如有不懂之处可以先参考给出的文档地址,或者在文章下方留言。

Robot.js

前面我们看到的机器人就是通过这个类来实现的。但是正如学过生物必修一的同学都知道,动物生命层次是这样的:个体->系统->器官->组织->细胞,我们的机器人就是个体,那么四肢构成运动系统,以此类推。所以我们的这个Robot类就只是个装载头部,身躯,四肢的容器。在上面给出的文件结构中可以看到,还有Head.js和Body.js这些类,他们的实例化对象就是放在Robot这个个体里的部件了。

因此先来看Robot.js:

function Robot () {
    var s = this;
    LExtends(s, LSprite, []);

    s.body = null;
    s.head = null;
    s.leftArm = null;
    s.rightArm = null;
    s.leftLeg = null;
    s.rightLeg = null;

    s.addBody();
    s.addHead();
    s.addArms();
    s.addLegs();

    partList = [
        {
            exec : s.body,
            part : [s.body.bodyLayer]
        },
        {
            exec : s.head,
            part : [s.head.faceLayer]
        },
        {
            exec : s.leftArm,
            part : [s.leftArm.part1, s.leftArm.part2]
        },
        {
            exec : s.rightArm,
            part : [s.rightArm.part1, s.rightArm.part2]
        },
        {
            exec : s.leftLeg,
            part : [s.leftLeg.part1, s.leftLeg.part2]
        },
        {
            exec : s.rightLeg,
            part : [s.rightLeg.part1, s.rightLeg.part2]
        }
    ];
}

Robot.prototype.addBody = function () {
    var s = this;

    s.body = new Body(80, 100, 15);
    s.addChild(s.body);
};

Robot.prototype.addHead = function () {
    var s = this;

    s.head = new Head(40, 50);
    s.head.x = s.body.getWidth() / 2;
    s.body.addChild(s.head);
};

Robot.prototype.addArms = function () {
    var s = this, l = 60, r = 7.5;

    s.leftArm = new Limb(l, r, 90, 90, 60, 5);
    s.leftArm.x = r + 4;
    s.leftArm.y = r + 4;
    s.body.addChild(s.leftArm);

    s.rightArm = new Limb(l, r, -140, -140, -30, -5);
    s.rightArm.x = 76 - r;
    s.rightArm.y = r + 4;
    s.body.addChild(s.rightArm);
};

Robot.prototype.addLegs = function () {
    var s = this, l = 70, r = 7.5;

    s.leftLeg = new Limb(l, r, 70, -40, 80, 0);
    s.leftLeg.x = r + 3;
    s.leftLeg.y = 96 -r;
    s.body.addChild(s.leftLeg);

    s.rightLeg = new Limb(l, r, -60, 30, 10, 60);
    s.rightLeg.x = 76 - r;
    s.rightLeg.y = 96 -r;
    s.body.addChild(s.rightLeg);
};

属性介绍:

  • body:机器人身躯对象
  • head:机器人头部对象
  • leftArm & rightArm:机器人手臂对象
  • leftLeg & rightLeg:机器人腿部对象

函数介绍:

  • 构造器:调用其他各个函数并为partList赋值
  • addBody & addHead & addArms & addLegs:加入各个部件

partList数据结构介绍:

先前我们在Main.js中看到过这个变量。这个变量是个数组,里面存放了多个Object。这些Object中有part和exec两个属性。part对应的值是部件中参与碰撞检测的对象(LSprite对象),比如说头部里的faceLayer,手臂中的两个部分part1和part2。exec主要是在刷新部件时用到,毕竟改变了颜色后,机器人身上的部件要重画一遍,那么就需要调用exec对应的对象中的重画函数。

画出各种部件及其缓动动画的实现

※ 提示:下面的代码,会用到很多LGraphics,LTweenLite,不熟悉的同学,建议先阅读上文给出的文档

Part.js

所有部件的父类——Part类:

function Part () {
    var s = this;
    LExtends(s, LSprite, []);

    s.fillColor = "lightgray";
}

只有一个属性fillColor:部件填充的颜色

Body.js

身躯部件——Body类:

function Body (w, h, r) {
    var s = this;
    LExtends(s, Part, []);

    s.w = w;
    s.h = h;
    s.r = r;

    s.bodyLayer = new LSprite();
    s.addChild(s.bodyLayer);

    s.bodyLayer.addShape(LShape.RECT, [0, 0, w, h]);

    s.draw();

    LTweenLite.to(s, 1, {
        rotate : 5,
        loop : true,
        ease : Cubic.easeInOut
    }).to(s, 1, {
        rotate : -10,
        ease : Cubic.easeInOut
    });
}

Body.prototype.draw = function () {
    var s = this,
    w = s.w,
    h = s.h,
    r = s.r,
    c = s.fillColor,
    lx = r - 3,
    rx = w - r + 3,
    uy = r - 3,
    dy = h - r + 3,
    pi = Math.PI * 2;

    s.bodyLayer.graphics.clear();

    s.bodyLayer.graphics.drawRoundRect(1, "black", [0, 0, w, h, 10], true, c);

    s.bodyLayer.graphics.drawArc(1, "black", [lx, uy, r, 0, pi], true, c);
    s.bodyLayer.graphics.drawArc(1, "black", [rx, uy, r, 0, pi], true, c);

    s.bodyLayer.graphics.drawArc(1, "black", [lx, dy, r, 0, pi], true, c);
    s.bodyLayer.graphics.drawArc(1, "black", [rx, dy, r, 0, pi], true, c);
};

参数介绍:

  • w:身躯的宽度
  • h:身躯的高度
  • r :身躯上用于装饰的圆的半径

在该类中,draw函数就是用来绘制部件的,如果重复调用draw,则可达到刷新的目的。除此之外,我们使用了LTweenLite来实现缓动动画。以下其他的类和此类原理相同。

Head.js

头部部件——Head类:

function Head (w, h) {
    var s = this;
    LExtends(s, Part, []);

    s.w = w;
    s.h = h;

    s.faceLayer = new LSprite();
    s.faceLayer.x = -w / 2;
    s.faceLayer.y = -h * 0.9;
    s.addChild(s.faceLayer);

    s.faceLayer.addShape(LShape.RECT, [0, 0, w, h]);

    s.draw();

    LTweenLite.to(s, 0.8, {
        rotate : -20,
        loop : true,
        ease : Sine.easeInOut
    }).to(s, 0.8, {
        rotate : 20,
        ease : Sine.easeInOut
    });
}

Head.prototype.draw = function () {
    var s = this, w = s.w, h = s.h;

    s.faceLayer.graphics.clear();

    s.faceLayer.graphics.drawRoundRect(1, "black", [0, 0, w, h, 10], true, s.fillColor);

    s.faceLayer.graphics.drawArc(1, "black", [12, 15, 6, 0, Math.PI * 2], true, "white");
    s.faceLayer.graphics.drawArc(1, "black", [11, 15, 2, 0, Math.PI * 2], true, "black");

    s.faceLayer.graphics.drawArc(1, "black", [w - 12, 15, 6, 0, Math.PI * 2], true, "white");
    s.faceLayer.graphics.drawArc(1, "black", [w - 11, 15, 1, 0, Math.PI * 2], true, "black");

    s.faceLayer.graphics.add(function () {
        var c = LGlobal.canvas;

        c.lineWidth = 3;
        c.strokeStyle = "black";
        c.lineCap = "round";
        c.moveTo(10, 30);
        c.quadraticCurveTo(20, 50, w - 10, 30);
        c.stroke();
    });
};

Limb.js

肢干部件——Limb类:

function Limb (l, r, rotate1, rotate2, rotate3, rotate4) {
    var s = this;
    LExtends(s, Part, []);

    s.l = l;
    s.r = r;

    s.part1 = new LSprite();
    s.addChild(s.part1);

    s.part2 = new LSprite();
    s.part2.y = l - r;
    s.part1.addChild(s.part2);

    s.draw();

    s.part1.addShape(LShape.RECT, [-r, -r, r * 2, l]);
    s.part2.addShape(LShape.RECT, [-r, -r * 1.5, r * 2, l]);

    LTweenLite.to(s.part1, 1, {
        rotate : rotate1,
        loop : true
    }).to(s.part1, 0.8, {
        rotate : rotate2
    });

    LTweenLite.to(s.part2, 1, {
        rotate : rotate3,
        loop : true
    }).to(s.part2, 0.8, {
        rotate : rotate4
    });
}

Limb.prototype.draw = function () {
    var s = this,
    l = s.l,
    r = s.r,
    w = r * 2,
    c = s.fillColor;

    s.part1.graphics.clear();
    s.part2.graphics.clear();

    s.part1.graphics.drawRoundRect(1, "black", [-r, -r, w, l, r], true, c);
    s.part1.graphics.drawArc(1, "black", [0, 0, r * 1.4, 0, Math.PI * 2], true, c);

    s.part2.graphics.drawRoundRect(1, "black", [-r, -r * 1.5, w, l, r], true, c);
    s.part2.graphics.drawArc(1, "black", [0, 0, r * 1.4, 0, Math.PI * 2], true, c);
};

以上的代码应该注意的是每种部件的画法。当然艺术这种东西怎么能靠言传呢?所以我就不打算深究代码的含义了。至于各种部件的画法,欢迎各位借鉴。如有不通之处,敬请留言。

源代码下载

下载地址:http://wyh.wjjsoft.com/downloads/drag_and_drop_robot.zip

本次梦幻之旅就到此为止,喜欢该系列的看官可以来此专栏阅读该系列其他文章:

http://blog.csdn.net/column/details/dreamy-travel-in-h5.html


欢迎大家继续关注我的博客

转载请注明出处:Yorhom’s Game Box

http://blog.csdn.net/yorhomwang

时间: 2024-10-10 06:50:08

『HTML5梦幻之旅』 - 仿Qt示例Drag and Drop Robot(换装机器人)的相关文章

『HTML5梦幻之旅』 - 仿Qt演示样例Drag and Drop Robot(换装机器人)

起源 在Qt的演示样例中看到了一个有趣的demo.截图例如以下: 这个demo的名字叫Drag and Drop Robot,简单概括而言,在这个demo中,能够把机器人四周的颜色拖动到机器人的各个部位,比方说头.臂,身躯等.然后这个部位就会变成相应的颜色.相似于换装小游戏. 上图就是经过愚下的拖动颜色使其简略换装后的样子. 当然,拖动颜色使部件变色的功能并不难实现,关键在于这个机器人是动态的,我们要研究的就恰恰是这个机器人动画是怎么做出来的. 光凭两张图片我们无法知道这个动画究竟是什么样子的,

『HTML5梦幻之旅』 - 炫酷的节日贺卡

刚过完春节,想必大家收到了各种祝福和贺卡吧-Y某我今年也为同学和家人准备了贺卡.不一样的是,我的贺卡可不是made from树,而是一行行代码凝聚而来的. 考虑到本次开发需要的功能不多,所以就没有用库件了,利用纯Html5 Canvas API来完成本次梦幻之旅:节日贺卡.虽然用到的Canvas API不多,但是效果还是蛮理想的- 首先上截图吧: 哎呀,看到了截图,各位是不是领悟了传说中的炫酷华丽(luàn qī bā zāo)? 测试地址:http://wyh.wjjsoft.com/demo

『HTML5梦幻之旅』 - 跟随歌曲显示当前歌词

好像哪位老师曾说过,音乐是世界通用语言.是的,听不懂英文,但总能欣赏英文歌曲吧. 很早以前就想做个音乐播放器,但是由于跟随歌曲显示当前歌词的效果一直实现不了,所以我的想法一直无法实现.不过,最近创意不佳,没心情开发游戏了,于是闲下来搞点小发明.这次就先模仿一下手机QQ音乐中歌词显示效果. 恰巧,年末新歌蛮多的,于是我就选了一首比较好听的歌曲--手写的从前. 先看本次演示截图: 演示地址:http://wyh.wjjsoft.com/demo/lyrics/ 上面的演示地址可能有一半以上的人都打不

『HTML5梦幻之旅』 - 舞动色彩,Canvas下实现颜色动画

注:为了方便起见,本次开发用到了开源引擎lufylegend,官方地址如下:http://lufylegend.com/lufylegend 今天来学习下HTML5 Canvas颜色动画.什么是颜色动画呢?以我的理解就是以某种颜色过渡到另一种颜色.和这个效果有点类似:http://w3school.com.cn/tiy/t.asp?f=css3_animation1 上面的demo是用css3实现,而我们今天要用的是Canvas.Canvas并没有相关的API,所以要想实现这种效果,只有靠自己了

『HTML5挑战经典』是英雄就下100层-开源讲座(二)危险!英雄

本篇为<『HTML5挑战经典』是英雄就下100层-开源讲座>第二篇,需要用到开源引擎lufylegend,可以到这里下载: 下载地址:http://lufylegend.googlecode.com/files/lufylegend-1.7.1.rar API文档:http://lufylegend.com/lufylegend/api 却说我们的英雄能顺利地从天而降了,不过丝毫没有悬念,他一定会被摔死的,因为还没有跳板出现.我每次路过时都看到我们的英雄是边下降边大叫:help! help!然

NHibernate框架与BLL+DAL+Model+Controller+UI 多层架构十分相似--『Spring.NET+NHibernate+泛型』概述、知识准备及介绍(一)

原文://http://blog.csdn.net/wb09100310/article/details/47271555 1. 概述 搭建了Spring.NET+NHibernate的一个数据查询系统.之前没用过这两个框架,也算是先学现买,在做完设计之 后花了一周搭建成功了.其中,还加上了我的一些改进思想,把DAO和BLL之中相似且常用的增删改查通过泛型T抽象到了DAO和BLL的父类中,其DAO 和BLL子类只需继承父类就拥有了这些方法.和之前的一个数据库表(视图)对应一个实体,一个实体对应一

老秦『十里桃花招商』Q849852

老秦『十里桃花招商』Q849852 十里桃花招商老秦是一个信誉至上的上级,对下级更是无私的教导 十里桃花招商老秦秉持著赚钱一起赚的心态! 十里桃花招商老秦把下级当做是兄弟.是亲人.更是最重要的工作伙伴! 十里桃花招商老秦在市场已经混了六年,信誉更是无庸置疑! 十里桃花招商老秦团队目前已有二十馀人,团队还在持续扩大中! 十里桃花招商老秦诚邀您一起加入我们! 跟著十里桃花招商老秦,即时知道市场动态!

WV梦幻之旅是什么

一.公司 1.世界环旅集团专门就是为了爱旅行.需要出行的朋友省钱的集团公司,05年成立之后收购了大型的预订平台rovia(已经有31年历史)没有谷歌就没有百度,同样中国的携程也是效仿美国的预购平台,今年又和agoda达成战略合作.目前在国内有两万多家的酒店合作. rovia的客户包括美国航空公司american airlines. rovia也是iata(国际航空协会),asta(美国旅行社协会)和clia(国际旅游游轮协会)认证公司,现在是asta(美国旅行社协会)会长. rovia在2011

99美元澳大利亚深度游 3天2夜wv梦幻之旅

               99美元澳大利亚深度游 3天2夜wv梦幻之旅                              时间:可加长游 2015-1-16~2015-2-16  1.深度游,慢生活,放松,交友;2.精心打造,超五星的服务,二星的收费:3.慈善之旅,修心之旅,探险之旅,边旅行边获益.... 1.你喜欢旅游吗?2. 你喜欢免费旅游吗?3. 你想一边旅游,一边赚钱吗 ?喜欢边旅游边工作的朋友,可以加入我们的wv梦幻之旅俱乐部关于wv梦幻之旅有任何不明白地方,敬请咨询,有问