起源

在Qt的演示样例中看到了一个有趣的demo。截图例如以下:

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

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

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

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

http://yuehaowang.github.io/demo/drag_and_drop_robot/

截图例如以下:

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

以下是实现过程。

准备工作

先来看看文件结构:



当中,lufylegend-1.9.9.simple.min.js是html5引擎lufylegend里的文件,因为该引擎带有缓动类,所以实现本次效果会easy一些。

引擎官方地址: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)];
}

代码非常easy,如有不懂之处能够先參考给出的文档地址,或者在文章下方留言。

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);
};

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

源码

Github地址:https://github.com/yuehaowang/drag_and_drop_robot

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

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


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

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

http://blog.csdn.net/yorhomwang

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

  1. 『HTML5梦幻之旅』-缤纷多姿的烟花效果

    天花无数月中开,五采祥云绕绛台.堕地忽惊星彩散,飞空旋作雨声来.怒撞玉斗翻晴雪,勇踏金轮起疾雷.更漏已深人渐散,闹竿挑得彩灯回. ——明·瞿佑·<烟火戏> 记得每年过春节的那段时间,除了欣 ...

  2. 01_MUI之Boilerplate中:HTML5演示样例,动态组件,自己定义字体演示样例,自己定义字体演示样例,图标字体演示样例

     1安装HBuilder5.0.0,安装后的界面截图例如以下: 2 依照https://www.muicss.com/docs/v1/css-js/boilerplate-html中的说明,创建上 ...

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

    本篇为<『HTML5挑战经典』是英雄就下100层-开源讲座>第二篇,需要用到开源引擎lufylegend,可以到这里下载: 下载地址:http://lufylegend.googlecod ...

  4. 关于『HTML5』:第二弹

    关于『HTML5』:第二弹 建议缩放90%食用 咕咕咕咕咕咕咕!!1 (蒟蒻大鸽子终于更新啦) 自开学以来,经过了「一脸蒙圈的 半期考试」.「二脸蒙圈的 体测」的双重洗礼,我终于有空肝 HTML5 辣 ...

  5. 关于『HTML5』第一弹

    关于『HTML5』:第一弹 建议缩放90%食用 祝各位国庆节快乐!!1 经过了「过时的 HTML」.「正当时的 Markdown」的双重洗礼后,我下定决心,好好学习HTML5  这回不过时了吧(其实和 ...

  6. MuPlayer『百度音乐播放内核』

    MuPlayer『百度音乐播放内核』 —— 跨平台.轻量级的音频播放解决方案. 多端(PC & WebApp)通用,统一的API调用方式 HTML5 Audio与Flash内核的平滑切换(支持 ...

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

    原文://http://blog.csdn.net/wb09100310/article/details/47271555 1. 概述 搭建了Spring.NET+NHibernate的一个数据查询系 ...

  8. 【阿里云产品公测】以开发者角度看ACE服务『ACE应用构建指南』

    作者:阿里云用户mr_wid ,z)NKt#   @I6A9do   如果感觉该评测对您有所帮助, 欢迎投票给本文: UO<claV   RsfTUb)<   投票标题:  28.[阿里云 ...

  9. html5的audio实现高仿微信语音播放效果

    效果图 前台大体呈现效果图如下: 点击就可以播放mp3格式的录音.点击另外一个录音,当前录音停止! 思路 关于播放动画,这个很简单,我们可以用css3的逐帧动画来实现.关于逐帧动画,我之前的文章也写过 ...

随机推荐

  1. DOM中的节点属性

    摘抄自:http://www.imooc.com/code/1589 nodeName 属性: 节点的名称,是只读的. 1. 元素节点的 nodeName 与标签名相同 2. 属性节点的 nodeNa ...

  2. [luogu1707] 刷题比赛 [矩阵快速幂]

    题面: 传送门 思路: 一眼看上去是三个递推......好像还挺麻烦的 仔细观察一下,发现也就是一个线性递推,但是其中后面的常数项比较麻烦 观察一下,这里面有以下三个递推是比较麻烦的 第一个是$k^2 ...

  3. Java接口对Hadoop集群的操作

    Java接口对Hadoop集群的操作 首先要有一个配置好的Hadoop集群 这里是我在SSM框架搭建的项目的测试类中实现的 一.windows下配置环境变量 下载文件并解压到C盘或者其他目录. 链接: ...

  4. Prime Gift(prime)

    Prime Gift(prime) 题目描述 Jyt有nn个质数,分别为p1,p2,p3-,pnp1,p2,p3-,pn. 她认为一个数xx是优秀的,当且仅当xx的所有质因子都在这nn个质数中. 她想 ...

  5. Java面试题之在多线程情况下,单例模式中懒汉和饿汉会有什么问题呢?

    懒汉模式和饿汉模式: public class Demo { //private static Single single = new Single();//饿汉模式 private static S ...

  6. jquery 跳转页面传值的问题

    关于 跳转页面传值的问题 1. 目前最多的是使用 ajax 方法 //举例 ajax 传值,举例: $.ajax({ type : "post", url : "save ...

  7. 勒索病毒 -- “永恒之蓝”NSA 武器免疫工具

    “永恒之蓝”NSA 武器免疫工具 针对 445 端口:445端口是一个毁誉参半的端口,他和139端口一起是IPC$入侵的主要通道.有了它我们可以在局域网中轻松访问各种共享文件夹或共享打印机,但也正是因 ...

  8. HTML 上标和下标

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  9. mysql source、mysqldump 导入导出数据(转)

    解决了mysql gbk编码的导入导出问题,感谢作者. 一.导入数据 1.确定 数据库默认编码,比如编码 为gbk,将读入途径编码同样设为gbk,命令为:           set names gb ...

  10. Java学习路线-基础篇!

    下面以黑马程序员Java学院的学习路线为例,进行一次史无前例的剖析,我会采取连载的形式,细致的讲解零基础的人怎么学习Java.先看下Java在基础阶段的知识点路线图. 内容多吗?不要被吓到了,知识点剖 ...