起源

在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. NOIP 2016 滚粗记

    Day -∞ 听说要去晋城一中去考试. MMP,我在省会城市,为什么要去一个偏远的小城市去考NOIP 就是因为几年前它们那里出了一个金牌吗?都怪我们太菜了. Day 0 坐着长途大巴车去考试,其他人都 ...

  2. qw

    // 主页 @RequestMapping(value = "/home") public ModelAndView home() { ModelAndView MV = new ...

  3. 【CCF】JSON查询

    #include<iostream> #include<cstdio> #include<string> #include<cstring> #incl ...

  4. svg动画 之 我的自制太阳系

    SVG意为可缩放矢量图形,svg的图片与普通的jpg,png等图片相比,其优势在于不失真.一般普通的图片放大后,会呈现出锯齿的形状,但是svg图片则不会这样,它可以被高质量地打印. 现在就用dream ...

  5. 双倍回文(bzoj 2342)

    Description Input 输入分为两行,第一行为一个整数,表示字符串的长度,第二行有个连续的小写的英文字符,表示字符串的内容. Output 输出文件只有一行,即:输入数据中字符串的最长双倍 ...

  6. pat 甲级 1098. Insertion or Heap Sort (25)

    1098. Insertion or Heap Sort (25) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yu ...

  7. Codeforces 297C. Splitting the Uniqueness

    C. Splitting the Uniqueness time limit per test:1 second memory limit per test:256 megabytes input:s ...

  8. The type or namespace name 'Html' does not exist in the namespace 'System.Web.Mvc' (are you missing an assembly reference?)

    The type or namespace name 'Html' does not exist in the namespace 'System.Web.Mvc' (are you missing ...

  9. Codeforces Round #449 Div. 2 A B C (暂时)

    A. Scarborough Fair 题意 对给定的长度为\(n\)的字符串进行\(m\)次操作,每次将一段区间内的某一个字符替换成另一个字符. 思路 直接模拟 Code #include < ...

  10. MMU介绍【转】

    转自:http://blog.csdn.net/martree/article/details/3321578 虚拟存储器的基本思想是程序,数据,堆栈的总的大小可以超过物理存储器的大小,操作系统把当前 ...