hiloteam / article Goto Github PK
View Code? Open in Web Editor NEWHilo article
Hilo article
在创建一个立方体之前,我们首先创建一个透视摄像机,Hilo3d.PerspectiveCamera
代码如下:
var camera = new Hilo3d.PerspectiveCamera({
aspect: innerWidth / innerHeight,
far: 20000,
near:1,
z:5
});
完成相机以后我们需要一个舞台元素,和Hilo2D 舞台元素Stage 类似,我们在创建3D场景的时候 也需要一个舞台元素
var stage = new Hilo3d.Stage({
container: $('container'),
camera: camera,
width: innerWidth,
height: innerHeight
});
通常情况下,我们不太需要单帧的渲染输出,而是需要在一个交互过程里完成对不同操作响应和元素的渲染。完成这项工作的就是计时器(Ticker)。Hilo3d沿用了Hilo2d的Ticker Hilo3d.Ticker
var ticker = new Hilo3d.Ticker(60);
ticker.addTick(stage);
ticker.addTick(Hilo3d.Tween);
ticker.start();
Hilo3d.Ticker(60)
的构造函数指定了FPS,你可以修改这个值来指定高低频的渲染addTick
,任何一个需要同步渲染和计数的对象都需要被添加,以在每个时间片来临后完成相应操作start
, Ticker需要主动唤起在构建Mesh之前,我们先说说3维构造,从模型和计算渲染角度来讲,我们把模型分成两块,一种是几何形,另外一种是几何形上的附着(材质)。可以理解成人体的骨络和皮肉。
为了更加形象解释,我们使用2015年《双十一万店同庆》的天猫模型举例:
如上图所示,设计师在使用Maya 3DMax等软件,通过创建3维点构造出了猫模型的几何形。即Hilo3d中的gemometry
的概念
设计好几何体以后设计师按照一定的UV展开,在这个几何形上添加纹理附着:
完成“骨络”和“皮肉”的一一对应的最关键的工作就是UV展开,以下就是猫模型的UV展开图:
介绍完几何形和附着,我们先创建一个简单的几何形——立方体 new Hilo3d.BoxGeometry()
V1.0 版本提供最基本的几何体构建,操作也非常简便。
材质分类比较广,和被渲染物体本身材质属性有关,也和环境、光有关。在Hilo3d中以Material
定义材质。
根据光和自身物理属性,我们把材质的属性分为以下几类:
所有的材质在渲染上的表现都是材质属性(上述几类)和光的叠加计算后的结果。如下图所示,如果我们把物体材质的各种属性抽象为一张张描述贴图,通过在着色器中加入光的计算,渲染后的结果就是我们在现实世界里的看到的样子。
着色器,指一组供计算机图形资源在执行渲染任务时使用的指令
有一类材质渲染只考虑贴图和颜色,这些贴图颜色在处理之前已经做过预渲染(Photoshop 或者 单反相机等),这一类的渲染方式称为Texture mapping ,或者叫diffuse mapping。
实现一个颜色纹理的材质可以这样:
var color = new Hilo3d.Color(0, 0, 1)
new Hilo3d.BasicMaterial({
diffuse: color
})
Hilo3d.Color
或者 Hilo3d.Texture
刚才提到,底色附着可以通过颜色实现,也可以通过纹理Hilo3d.Texture
来实现:
var texture = new Hilo3d.Texture({ image });
new Hilo3d.BasicMaterial({
diffuse: texture
})
构建完材质以后,利用上面的几何形,我们就可以创建一个网格(Mesh)
PS: 网格是几何形与材质的结合
var blueBox = new Hilo3d.Mesh({
geometry: new Hilo3d.BoxGeometry(),
material: new Hilo3d.BasicMaterial({
diffuse: new Hilo3d.Color(0, 0, 1)
}),
x: -200,
onUpdate: function() {
this.rotationX += .3;
this.rotationY += .3;
}
});
最后将网格结点添加到舞台
stage.addChild(blueBox);
渲染一个立方体的工作就完成了。
使用Hilo3d V1.0 Alpha版本渲染38女王节AR 互动打斗场景截图:
好,我们使用Hilo 3d实现以上效果:
var camera = new Hilo3d.PerspectiveCamera({
aspect: innerWidth / innerHeight,
far: 1000,
near:0.1,
fov:75,
y:4,
z:3
});
var dpr = window.devicePixelRatio;
var stage = new Hilo3d.Stage({
container: document.getElementById('container'),
camera: camera,
width: innerWidth * dpr,
height: innerHeight * dpr
});
if(dpr > 1){
stage.canvas.style.width = innerWidth + "px";
stage.canvas.style.height = innerHeight + "px";
}
为了避免锯齿现象,我们把Canvas画布的对象真实宽高置为window.devicePixelRatio
倍,同时将画布的css的宽高置为当前窗口大小。
避免锯齿的方法除了上面的方式,修改着色器也是方法之一,以后再做阐述。
var ticker = new Hilo3d.Ticker(60);
ticker.addTick(stage);
ticker.addTick(Hilo3d.Tween);
ticker.addTick(Hilo3d.Animation);
ticker.start();
Hilo3d.Tween
手动加入到Ticker循环中Hilo3d.Animation
对象,Hilo3d会协助完成动画相关的解析和渲染借助海螺码头素材转化能力,我们将3D设计师提供给我们的3d素材转换成Hilo3d指定渲染格式(glTF)
var res = [{
"id": "soldier",
"src": "//cx.alicdn.com/tmx/1cf44ad84cc97b8a24e411d7caed0719.gltf"
},{
"id": "ground",
"src": "//cx.alicdn.com/tmx/429cd105280881c86312f1f7aabd908e.gltf"
}, {
"id": "player0",
"src": "//cx.alicdn.com/tmx/076728f9a1d45e926fe70f7efe625bf5.gltf"
},{
"id": "player1",
"src": "//cx.alicdn.com/tmx/0551344875278d5619b175f02d3075d1.gltf"
}
];
var loadQueue = new Hilo3d.LoadQueue();
loadQueue.add(res).on('complete', function(){
res.forEach(function(r){
var node = loadQueue.getContent(r.id).node;
node.getChildByName('RootNode').setScale(0.01);
res[r.id] = node;
});
init();
}).start();
Hilo3d.LoadQueue: Hilo3d下载队列,Hilo3d.LoadQueue
采用链式调用,开放load
,error
,complete
事件监听
loadQueue.getContent:获取对应Id资源
loadQueue.getContent(r.id).node: Hilo3d.LoadQueue
会自动根据资源类型路由到Hilo3d.GLTFLoader
, GLTFLoader会调用Hilo3d.GLTFParser
解析glTF模型并返回以下属性字段:
.node(Hilo3d.Node)
.meshes(Hilod.Mesh 或者 Hilo3d.SkinedMesh)
.cameras(Hilo3d.PerspectiveCamera)
.textures(Hilo3d.Texture)
.materials(Hilo3d.BasicMaterial)
另外,还可以按照nodeId 方式查找节点node.getChildById("XX")
或是按照className 查找 node.getChildrenByClassName("XX"),className为Hilo3d的实体类型分类,如Node的className为"Node", Mesh的className为"Mesh", Geometry的className为"Geometry"
_root = new Hilo3d.Node({
y:0.1,
onUpdate:function(){
this.rotationY += 0.3;
}
}).addTo(stage);
player0 = res.player0;
player0.x = -3;
player0.rotationY = 90;;
player0.setScale(playerScale);
_root.addChild(player0);
player0.anim.addClip('run', 0, 20/30);
player0.anim.addClip('attack', 20/30, 45/30);
player0.anim.stop();
Hilo3d.Animation
对象,包含和动画相关的API,如果模型文件中不包含动画,此属性为nullsoldier.setPosition(pos[0], 5, pos[2]);
soldier.rotationY = index > 3?-90:90;
soldier.setScale(soldierScale);
soldier.visible = false;
Hilo3d.Tween.to(soldier, {
y:0
}, {
onStart:function(){
soldier.visible = true;
},
delay:250,
duration:500
});
soldiers.push(soldier);
_root.addChild(soldier);
soldier.anim.addClip('run', 0, 20/30);
soldier.anim.addClip('attack', 20/30, 45/30);
soldier.anim.stop();
camera.lookAt(ground);
Hilo3d.Node
, 确定摄像机的朝向为某个场景内物体,另外对场景内的物体使用lookAt 可以实现Billboard的效果var orbitControls = new OrbitControls(stage);
Hilo3d.Stage
渲染期间想了解模型的fps,面数,drawcall次数,系统内存占用等性能信息(PC Only),可直接创建Stats
对象
var stats = new Stats(ticker, stage.renderer.renderInfo);
renderer.renderInfo
可以提取可渲染性能相关信息Hilo,一套HTML5跨终端的互动游戏解决方案。Hilo支持了多届淘宝&天猫狂欢城等双十一大型和日常营销活动。内核极简,提供包括DOM,Canvas,Flash,WebGL等多种渲染方案,满足全终端和性能要求。支持多种模块范式的包装版本以及开放的扩展方式,方便接入和扩展。提供对2D物理,骨骼动画的内建和扩展支持。另外,Hilo提供丰富的周边工具及开发案例。
目前,Hilo已经开源,并入到Hilo Team中。开源地址 https://github.com/hiloteam/Hilo (欢迎Star)
Hilo的特点:
Hilo采用极简的内核。核心模块包括基础类工具(Class),事件系统(EventMixin),渲染(Render)和可视对象(View),如下图所示。
首先,我们来看看如何接入Hilo。
Hilo是模块化的架构,且每个模块尽量保持无依赖或最小依赖。在Hilo的源码中,你看不到一般的模块定义的范式:
define(function(require, exports, module){
var a = require('a'),
b = require('b');
//something code here
return someModule;
}
);
取而代之的是,Hilo的每个模块都会有这样的注释定义:
/**
* @module hilo/view/Sprite
* @requires hilo/core/Hilo
* @requires hilo/core/Class
* @requires hilo/view/View
* @requires hilo/view/Drawable
*/
我们使用注释标签@module
来标记模块名称,用@requires
标记模块的依赖。
在编译阶段,我们会根据这些标记获取模块的相关信息,然后编译生成符合不同的模块范式定义的代码。比如:
define(function(require, exports, module){
var Hilo = require('hilo/core/Hilo');
var Class = require('hilo/core/Class');
var View = require('hilo/view/View');
var Drawable = require('hilo/view/Drawable');
//some code here
return Sprite;
};
我们除开提供一个独立无依赖的版本外,还提供AMD、CommonJS、CMD、CommonJS、Kissy等多种模块范式的版本。开发者可以根据自己的习惯,下载Hilo的不同范式版本使用。
hilo/
└── build/
├── standalone/
├── amd/
├── commonjs/
├── kissy/
└── cmd/
接下来,我们来看看Hilo如何做扩展。
Class.create
是Hilo里创建类的主要方法,如下:
var SomeClass = Class.create({
Extends: ParentClass,
Mixes: SomeMixin,
Statics: SomeStatics,
constructor: Constructor,
propertyName: propertyValue,
methodName: methodValue
});
其中:
Extends
- 指定一个父类。Mixes
- 指定混入对象。可以是一个Object或Array。Statics
- 指定静态属性。constructor
- 创建类的构造函数。此外 Hilo 使用 Class.mix(target, [mixinObject])
,可以为target混入属性和方法。
代码示例:
var EventMixin = {
on: function(type, handler){ },
off: function(type, handler){ },
fire: function(type, detail){ }
}
Class.mix(object, EventMixin);
再以扩展Hilo的可视对象的基础类View为例。View在表现上就是一个个矩形,无论图片还是文字都可以使用一个最小的矩形包裹。在这些可视对象上做平移,旋转,缩放,透明处理等操作就可以实现普通动画的绝大部分。
如上图所示,View解决了可视对象展示的基本问题。
利用Hilo提供创建类和扩展类的方法,我们可以扩展出可视对象所属管理的Container类:
类似地,根据不同View的其他展示特性,Hilo扩展出舞台Stage,位图Bitmap,画图Graphic,精灵动画Sprite等类型。
我们知道一个游戏运行的核心流程——在一个Loop循环内,接受输入并完成对所有可视对象的游戏属性更新,然后渲染。下面是单个可视对象的一个循环过程:
作为一个可视对象,包含了位置,大小,缩放,旋转等自然信息,如下图所示:
Update是个计算过程,后面赛车案例会讲通过Update我们可以做一些特殊的效果出来。在这之前,我们先看看渲染,即如何根据可视对象的自然信息来把他们 “画”出来。那么如何实现View的render函数呢?如上图所示,在render函数中主要解决两个问题:
render: function(renderer, delta){
//不同的View renderer方式可以多样,相同的View也可以根据renderer的类型不同而呈现差异化
...
}
Hilo首次提出了特别的渲染方案——即提供DOM 、Canvas、 Flash或者WebGL 四种渲染的方式来实现render,目前该方案已经申请专利。这四种渲染方式是和View独立分开的,View在做自身属性更新时完全不需要考虑怎么被“画”出来,同样,拿到View后我们可以使用不同的“画笔”把它描绘出来。如果你有更好的绘制方式,也可以扩展出更多的渲染方案。如下图所示:
View可以粗略分为普通类的View,文字类View(Text)和画图类View(Graphic)几种类型。不同类型的View“长相”不尽相同,在做Update和Render时也要针对处理。
普通类的View,如Bitmap,Container,Button,Sprite,在渲染层面主要处理图片的展示问题。单从图片展示的技术实现上讲,DOM渲染可以通过设置元素的background样式实现,Canvas也有绘图方法drawImage,WebGL则可以通过shader做纹理绑定。
特别地,在Flash的渲染模式下,Hilo首先将View所有和绘图相关的方法通过JSBridge的方式交由适配器FlashAdapter,FlashAdapter来翻译成对应Flash工程实现绘制的方法,如下图所示:
由于Flash在PC浏览器上的广泛支持,特别是IE的支持,使用Flash渲染额外好处就是跨终端,这个终端包括所有主流PC浏览器(包括IE 6,7,8)在内。
另外,在一些低端的手机浏览器上,可以选择DOM渲染模式来代替其他的渲染方式。在Canvas支持不好的机器上或者互动游戏场景本身比较耗性能情况下,DOM渲染模式可以很好地胜任渲染的工作。2015年天猫年中大促的赛车互动,我们就在Android机器上使用DOM渲染的方案。
除了多种渲染模式,Hilo还提供给一些其他衍生能力。这些衍生能力或者来自每次项目的技术改进,或者来自对其他优秀引擎的能力的吸收。例如Hilo支持主流骨骼动画和自建骨骼动画系统(Tahiti),狂欢城多图片下高性能优化,主流物理引擎无缝支持和一些特殊物理效果实现。
相比较精灵动画(Sprite Animation),骨骼动画(Skeletal animation)使用一套资源就可以完成千万种动作变化。
目前做骨骼动画比较成熟的产品有Spine和DragonBones 。两者在功能上已经接近,考虑DragonBones免费,可自由使用,Hilo实现了对DragonBones的支持。
Hilo也实现了自己的动画编辑器(目前仅内部使用)——Tahiti。Tahiti通过Flash插件的方式实现,目前可以支持CSS3 animation,DOM,Canvas,Hilo动画导出。
骨骼动画将可视对象进行分解,得到一个个可视组件。很显然,这些一个个可视组件本身就是一个个View,只要调整相应的时间片内调整这些View的transform属性,把他们组合起来就是一套完整的动作。
Tahiti将分离出的可视对象扁平化管理,各个部件处于同一层级。借助我们自己实现Flash 插件就可以导出如下的动画数据格式:
{
//图层数据,按层级从上到下排列
"layers":[
{
"name":"head",
//关键帧数据
"frames":[
{
"tween": true, //是否缓动
"duration": 10, //持续帧数
"image":"img1",//对应texture中的键值
"elem": {
"scaleX": 1,
"scaleY": 1,
"rotation": 30,
"originX": 46.5,
"originY": 76.5,
"x": 108.5,
"y": 507.5,
"alpha": 100 //透明度,范围0~100,0完全透明,100完全不透明
}
}
]
}
],
//素材数据
"texture":{
"img1":{
"x":20, //在大图中的位置x
"y":50, //在大图中的位置y
"w":100,
"h":200
}
},
//舞台数据
"stage":{
"width":550, //动画容器宽
"height":400, //动画容器高
"fps":24 //帧频
},
//动作数据
"actions":{
"anim_die":12 //{动作名:帧数}
}
}
Tahiti实现了对导出数据的解析,再借助Hilo的渲染,就可以把设定的动画运行起来了。特别地,Tahiti不仅对接了Hilo的渲染,还可以对接了CSS3 animation,独立JS模式(Canvas)的渲染。
一年一度的双十一狂欢城是检验性能的大考。以2015年双十一狂欢城为例,在性能方面主要面临的挑战:
狂欢城预估图片总数为200张 ( 252*296),为了优化性能,我们首先对整体画面做了三个分层:
下面主要看看地图部分的优化。
地图部分多为静止画面,为TiledMap的拼块。由于Canvas大小有限制,同时为了性能考虑,我们把8屏狂欢城界面按512*512分解成多个块,分解方式如下图:
把这些分块独立成一个个Canvas,他们只在首次加载时和内容更改时(比较低的频率)进行渲染,渲染结果保存在这些Cache List中。这个List同时被图示地图Container管理,每次更新时只需要对这些缓存在内存中的List做一次整体渲染即可,这样就避免了成百个view单独渲染的问题,大大的降低了draw的次数。
此外,这些分块在有内容更新时,如上图的脏矩形更新所示,View 2在某一帧有内容更新,那么先找出和View 2 所有相交的view,然后把这些View按照Z轴次序从远及近更新,且只更新其相交部分。这样,便可以最大限度的只渲染需要渲染的部分。
概括地说,我们首先从结构上保证性能,大的方面上做了分层,抽出那些“动”和“不太动”的。其次,我们把view做了分块,把多个的view 更新渲染变成了一个512*512 大小的view 渲染,组成Cache List,然后在交互范围内把Cache List里需要输出的对象渲染到手机真实物理屏。与此同时,在更新Cache List时,为了避免Cache List 内 view对象的全部更新,我们尝试了更新引起变化的最小矩形块。
考虑到性能和库的精简,Hilo选择Chipmunk 作为自己的默认2D物理引擎。
我们知道物理世界非常丰富,不可能通过一个物理引擎就把物理世界全部展现的。不同材质,刚体或非刚体,流体或者布料这些在物理表现上差别巨大。物理引擎帮助我们解决一些基本问题,就像Hilo里的基础类View解决可视对象的基础问题。
面向业务,我们可以扩展出更多的物理效果。如2015年天猫年中大促的赛车项目,我们自己实现了一套赛车漂移的效果,改写的就是View Update计算方式。具体实现在后续介绍文章中推出。
以上,从动画,性能,物理三个方面案例阐述了Hilo可以做更多的事情,相信在业务支持和技术推动下,Hilo可以更加完善,非常希望对互动感兴趣的同学加入到Hilo开源队伍中,完善Hilo能力,提高Hilo开发效率,同时产出更多令人欣喜的作品。
Hilo 从阿里前端委员会建议立项到开发完成,得到许多关注和帮助,也同时支持到共享、阿里通信、手淘、天猫、城市生活、国际UED、阿里妈妈多个BU。因技术推广这里就不一一感谢。 留下两位主要作者的花名 @正霖 @墨川
Macaca 是一套完整的自动化测试解决方案。
特性:
Hilo 是阿里巴巴开源的一款HTML5跨终端游戏解决方案,ta可以帮助开发者快速创建HTML5游戏。
ta有以下几个特性:
Hilo作为作为一款功能丰富的游戏框架,对框架自身的质量要求也比较高。对核心的类和方法覆盖单元测试,而对渲染相关的方法最好的测试覆盖方式就是截图对比。
通过Macaca提供的uitest模块,就可以轻松完成测试覆盖,uitest是基于macaca-electron
的轻量封装,配合mocha
测试框架和断言库,完成用例。
如下是对精灵帧动画的功能测试
describe('view', function() {
var stage, ticker;
var stageElem = document.getElementById('stage');
beforeEach('init stage', function() {
stage = new Hilo.Stage({
container:stageElem,
renderType:'canvas',
width:550,
height:400
});
ticker = new Hilo.Ticker(60);
ticker.addTick(stage);
ticker.start();
});
afterEach('destroy stage', function() {
ticker.removeTick(stage);
ticker.stop();
if(stage.drawable && stage.drawable.domElement && stage.drawable.domElement.parentNode){
stage.drawable.domElement.parentNode.removeChild(stage.drawable.domElement);
}
else if(stage.canvas && stage.canvas.parentNode){
stage.canvas.parentNode.removeChild(stage.canvas);
}
stage = null;
ticker = null;
});
describe('Sprite', function() {
var atlas, sprite;
beforeEach('init atlas', function(done){
utils.loadImage('images/fish.png', function(img){
atlas = new Hilo.TextureAtlas({
image: img,
width: 174,
height: 1512,
frames: {
frameWidth: 174,
frameHeight: 126,
numFrames: 12
},
sprites: {
fish: {from:0, to:7}
}
});
sprite = new Hilo.Sprite({
frames: atlas.getSprite('fish')
});
stage.addChild(sprite);
done();
});
});
it('goto frame 1 should work', function(done){
sprite.goto(1, true);
utils.diffWithScreenshot('Sprite-goto-frame1', done);
});
it('goto frame 7 should work', function(done){
sprite.goto(7, true);
utils.diffWithScreenshot('Sprite-goto-frame7', done);
});
});
...
});
还需要配合截图并与提交到仓库的预期图片相对比(imagediff)
所有测试用例可查看这里
使用Macaca进行覆盖还有个优势,可以在没有屏幕的服务器端运行,也兼容正常浏览器环境的测试。服务端与浏览器端输出完全一致。
在电影里或者荧屏上,我们经常看到下面的特写镜头。
这种特写镜头(除了最后的效果部分)和我们的今天要说的补间动画有些相似。在实际操作时,有些场景下我们没有必要刻画游戏物体每一帧的位置颜色等其他信息。 而是使用一种计算方法,来替我们告诉这些游戏对象在每一帧里应该做怎样的变换。这一类动画我们可以宽泛的称之为补间动画。技术描述上可能很宽泛,比如叫transition,缓动,过渡等等。
CSS3提供transition的方式来对物体做缓动,例如我们想改变一个标签的宽度值:
div
{
width:100px;
transition: width linear 2s;
}
假设一个div元素的当前宽度为300px,我们想让这个div在2秒内做从300px到100px的线性渐变。我们就可以指定一个div的元素的transition
属性值为width,即对物体的宽度做过渡(transition)。
transition 属性是一个简写属性,用于设置四个过渡属性:
transition-property
transition-duration
transition-timing-function
transition-delay
transition-property
属性指定了需要对哪些属性做过渡(transition),后文中会提到。transition-duration指定了过渡效果的时间,而 transition-timing-function
则指定过渡的计算方式,CSS3可以使用预设的几种过渡函数,也可以直接使用贝塞尔曲线函数。下面是可选的几种枚举值:
值 | 描述 |
---|---|
linear | 规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1))。 |
ease-in | 规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))。 |
ease-out | 规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))。 |
ease-in-out | 规定以慢速开始和结束的过渡效果(等于 cubic-bezier(0.42,0,0.58,1))。 |
cubic-bezier(n,n,n,n) | 在 cubic-bezier 函数中定义自己的值。可能的值是 0 至 1 之间的数值。 |
那么触发过渡(transition)的条件是什么呢?可以是css触发:
div:hover
{
width:300px;
}
也可以使用js来触发:
document.querySelector("div#id").style.width = "300px";
通过任意可能的方式改变div元素的宽度值即可触发这种过渡效果。
另外,如果想延缓动画触发的时间,可以设置transition-delay的值,单位为秒(s)。
CSS3的过渡(transition)接口简单,在做一些简单的动画时比较实用。但是在需要一些逻辑控制的场景下,脚本控制更适合游戏动画的逻辑设计。
function easeInCubic(t) {
return Math.pow(t, 3);
}
var el = document.querySelector('#easeOutCubic');
var t = 0;
var step = 0.01;
var startValue = 0;
var endValue = 100;
function easeIn(){
t += step;
var blue = startValue + easeInCubic(t) * (endValue - startValue);
el.style.backgroundColor = 'rgb(100,100,' + blue + ')';
requestAnimationFrame(easeIn);
};
easeIn();
上面的代码描述了缓入(ease-in)的动画效果,实际上是利用了Y= X^3(Cubic) 的部分弧线效果,如下图红线标注部分。
示例代码作用对象为UI元素的背景颜色值(Blue), 我们可以控制步长step
来控制颜色变化的速度。
easeInCubic
通过控制输入当前(迭代)步长或时间来确定输出值,如下图 :
除了Y = X^ 3这条曲线可以模拟出ease-out的效果,诸如 Y = X ^ k 类型的曲线,如Y = X ^ 4(Quart) ,Y = X ^ 5 (Quint),另外,三角函数和指数函数也可以做到类似的效果。过渡函数详细的分类
实际上上文提到的CSS3的过渡函数接口cubic-bezier(n,n,n,n)
也是一种过渡函数的表达方式,如下图,P1,P2是2个控制点,两个点的坐标对应cubic-bezier(n,n,n,n)
四个参数:
cubic-bezier(n,n,n,n)
只能设置两个控制点的位置,显然,它能所表达的过渡动画种类不如脚本表达的丰富。
看完CSS3的过渡(transition)接口和原生脚本实现,我们再来看看Hilo里提供的补间动画接口——Tween (点这里查看详细Api文档):
ticker.addTick(Hilo.Tween);//需要把Tween加到ticker里才能使用
var obj = {x:5, y:10, alpha: 0};
Hilo.Tween.to(obj, {
x:100,
y:20,
alpha:0
}, {
duration:1000,
delay:500,
ease:Hilo.Ease.Quad.EaseIn,
onUpdate: function(ratio,tween){
console.log(tween.target.x,tween.target.y,tween.target.alpha);
},
onComplete:function(){
console.log("complete");
}
});
在做过渡动画时,Hilo需找指定一个补间对象,这个对象可以是Hilo的游戏对象,如View类型对象。也可以是普通对象,通过修改普通对象的属性值在onUpdate
中间接修改游戏本体对象,如示例代码。
onUpdate
会根据ticker的指定的帧率重复执行。在动画完成后onComplete
会被调用。
另外,Hilo的过渡动画可以进行过渡动画组合调用, 使用link
就能将一组过渡动画关联起来。
var box1 = boxes[0], box2 = boxes[1];
Tween.to(box1, {y:100}, {
duration: 1000
}).link(Tween.to(box2, {y:100}, {
duration: 1000
}));
使用这种关联,我们可以对多个对象做连续的过渡动画。Demo1地址,Demo2地址,Demo3地址,效果如下图所示:
开题的两组慢镜头,除了镜头后半部分物体发生的形变,前半部分镜头都对运动物体的位置做了缓动。除了位置,我们还会经常对哪些属性做缓动处理呢?
实际上,和尺寸、位置及颜色(color)相关的数值属性都是可以做缓动的。但是CSS3在做过渡动画时会有一些属性限制,而Hilo是可以对所有的属性做过渡动画的。
CSS3 可以做过渡效果的属性:
属性名称 | 类型 |
---|---|
background-color | color |
background-image | only gradients |
background-position | percentage, length |
border-bottom-color | color |
border-bottom-width | length |
border-color | color |
border-left-color | color |
border-left-width | length |
border-right-color | color |
border-right-width | length |
border-spacing | length |
border-top-color | color |
border-top-width | length |
border-width | length |
bottom | length, percentage |
color | color |
crop | rectangle |
font-size | length, percentage |
font-weight | number |
grid-* | various |
height | length, percentage |
left | length, percentage |
letter-spacing | length |
line-height | number, length, percentage |
margin-bottom | length |
margin-left | length |
margin-right | length |
margin-top | length |
max-height | length, percentage |
max-width | length, percentage |
min-height | length, percentage |
min-width | length, percentage |
opacity | number |
outline-color | color |
outline-offset | integer |
outline-width | length |
padding-bottom | length |
padding-left | length |
padding-right | length |
padding-top | length |
right | length, percentage |
text-indent | length, percentage |
text-shadow | shadow |
top | length, percentage |
vertical-align | keywords, length, percentage |
visibility | visibility |
width | length, percentage |
word-spacing | length, percentage |
z-index | integer |
zoom | number |
在上文中看Hilo如何描绘HTML5互动世界——补间动画中我们描述了特效的前半部分——缓动,后半段的撞击破碎效果可以使用今天提到的粒子效果来实现。
粒子系统可以看成是多个粒子渲染单元的规则或不规则运动的集合,因此在组成上也就分为两个部分——粒子单元和运动集合。
单个粒子而言,和普通的游戏对象没有太大区别。但是当多个粒子组合在一起以后就会形成各种形状图案,运动后形成各种运动轨迹。粒子特效在需要氛围场景的情况下应用广泛。
Hilo 提供了ParticleSystem 能够实现规则粒子一般场景下的粒子特效,如火焰,烟花,尾气,云雾,爆炸等效果。
粒子的外观来源可以多样,可以是Graphic对象,可以是拼合图(Sprite)的某一区域,也可以是一张单独的位图(Bitmap)。
ParticleSystem 支持通过一张拼合图(Sprite)的形式来描述粒子形状,只需要指定拼合图的 image
字段(拼合图),和指定帧的区域信息。
var particleSystem = new Hilo.ParticleSystem({
x:0,
y:0,
emitNum:20,
emitTime:1,
particle:{
frame:[
[75, 236, 7, 11], //指定拼合图单图有效信息
[119, 223, 7, 17],
[90, 223, 22, 17]
],
image:img, //指定拼合图对象
life:22,
alphaV:-.01,
vxVar:300,
vyVar:300,
axVar:200,
ayVar:200,
scale:.5,
rotationVar:360,
rotationVVar:4,
pivotX:.5,
pivotY:.5
}
});
ParticleSystem 定义了两类概念——发射器(Emitter)和粒子(Particle)
gx
, gy
两个参数指定所有粒子的重力大小。另外,利用发射器可以将粒子效果——如尾气效果,布置到汽车上(通过指定坐标),这样就可以做到汽车喷气的效果。*Var
类型的变量,如vxVar
, vyVar
,这类在已知变量a(对应的 vx
, vy
)的后缀加上 Var 是为了限定a 变化区间。 例如,vx = 100; vxVar = 80
那么vx 的区间为[100 - 80, 100 +80 ]实际上,Hilo提供的ParticleSystem解决了一类规则运动问题。这些粒子的特点是:
使用ParticleSystem可以快速创建出火焰,烟花,尾气,云雾,爆竹,雨滴,降雪等效果,如下图:
但是在还有一类不规则效果,粒子之间的运动有一定的差异性,需要对粒子的运动设计单独的计算方式。
在编写特殊运动效果之前,我们先构建一个粒子单元,即一个可视对象View。我们选择一个Bitmap来做这件事情。
粒子位图即一个位图对象,和ParticleSystem相比,可被渲染的对象更丰富一些——可以是单张Bitmap,纯色背景,或者是一个Graphic 对象。
var particle = new Hilo.Bitmap({
image:texture,
rect: [0,0,w,h], //指定位图选取图片的位置区域
x:x,
y:y,
onUpdate:function(){
//更新位置,旋转,透明度信息
}
});
下面是根据星星图案创建的单个位图对象:
创建好粒子位图后,我们就需要对这个粒子位图做运动计算。
以@墨川 实现的猫头散开运动为例,单个粒子——每个三角形面片p(Bitmap)在执行动画时有速度,旋转和alpha 值变化,粒子的update函数如下:
onUpdate = function(){
if(this.isStart){
this.x += this.vx;
this.y += this.vy;
this.rotation += Math.random() * 10;
this.alpha -= Math.random() * 0.1;
this.scaleX -= Math.random() * 0.01;
this.scaleY -= Math.random() * 0.01;
...
}
}
单个粒子的运动考虑清楚以后,我们再借助粒子编辑器(下文会提到)获得所有粒子的集合particles = []
。
然后,我们确定猫头的中心位置,根据每个粒子距离中心的位置来确定每个粒子延迟的缓动值:
var distance = Math.sqrt((p.x - centerX)*(p.x - centerX) + (p.y - centerY)*(p.y - centerY))
var delay = ( distance)* 5; //延迟时间和距离有关
这样同心圆上相同半径圆弧上的粒子就会有有相似的运动轨迹。
顺便,记录下每个粒子最初状态,在做猫头合拢动效的时候使用:
p.target = {
x:p.x,
y:p.y,
rotation:p.rotation,
delay:delay
};
猫头合拢的动画就是缓动回之前的状态:
p.close = function(){
p.isStart = false;
Hilo.Tween.to(p, {
x:p.target.x, //缓动到开合前的状态
y:p.target.y,
rotation:p.target.rotation,
alpha:1,
scaleX:1,
scaleY:1
},{
ease:Hilo.Ease.Bounce.EaseInOut,
duration:1000,
delay:p.target.delay
})
};
还有一类粒子系统实现了比较好的生物群落仿真行为,如 Boids,Boids粒子运动算法是克雷格·雷诺兹于1986年提出的。它们用于模拟各种畜群,虫群,鱼群,鸟群(Flocking)等生物群落行为,Boids内的粒子可以对其它物体的存在和自身起反应,具体算法描述参见 Boids 算法实现,如果有兴趣改成Hilo的版本,欢迎到 Hilo Github 提PR。
上图是模拟Boids的一种实现。
编辑器是一个非常重要的辅助互动&游戏开发的思路。整个互动&游戏开发开发流程中核心部分就是数据、驱动和渲染,其中,数据和渲染又是核心中的核心。数据,往小的方面来说就是在一段简单代码前需要构思怎样的数据结构;往大的方面来说,就是构建所有游戏对象的基础信息(怎样的位置、形状、运动等等)如关卡数据,地图数据,骨骼数据,帧动画数据,UI数据,粒子数据等。 拿到这些数据我们可以使用Hilo来渲染,可以使用3D来渲染,只要数据格式匹配,是可以使用任何引擎来做渲染的。通过编写编辑器我们可以快速拿到需要的数据格式。
Hilo会以工具的形式提供一些通用的编辑器。
按照上文提到的ParticleSystem 的粒子特点,这类粒子有相似的初始位置,XY方向上的速度和加速度,根据这些特点我们可以针对性的写一个运动编辑器,Hilo提供了这样的粒子运动编辑器:点击这里使用编辑器
编辑器效果UI如下:
规则粒子的运动参数基本是固定的,因此其对应的运动编辑器会比较容易写。
通常不规则粒子运动受算法制约,适用性没有规则运动广泛,因此除非有特殊需要,才会编写编辑器。但是,一些场景下,不规则运动的粒子最初会有一些图案和特殊的粒子形状,例如天猫年会需要展示的猫头。这种场景下需要我们把组成猫头形状的粒子特征提取出来。
拿到视觉稿分析一下,基础图形的形状只有四个(a,b,c,d):
那么我们需要编辑器给我们产出两类信息——位置(相对矩形区域)和形状:
{
"colIndex": 1,
"rowIndex": 0,
"color": "rgb(240, 131, 39)",
"alpha": "1",
"type": "d"
}
拿到这些基本信息我们就可以构建单元粒子的外观、形状和位置。无独有偶,在之前的文章《天猫抢豪车竞速互动技术回放》讲到竞速赛道的地图编辑器也是相似的道理。
总结起来,如下图所示:
在需要提供渲染数据支持时,我们编写对应的编辑器;在需要提供运动数据支持时,我们编写对应的编辑器;在需要动作数据时,我们编写对应的编辑器。
由于粒子系统要操作大量的元素,因此在性能上会遇到一些问题。不过粒子效果多数为氛围效果,交互性不是非常强,所以只要保证粒子在渲染的时候符合预期就好了。
粒子非常多的情况下,如果逐个粒子渲染是非常耗性能的。下面的这个动画是我们使用dom实现的一个方案——改变dom的transform值来实现的效果。
PS:以上是在相同的帧率下 进行的录屏操作
可以看出,相比较猫头散开的动画帧率下降很多。
所以, 尽可能把所有渲染进行batch,Hilo提供Webgl的渲染模式,可以直接把这样的渲染效果集约到一次Draw call中进行。
使用上,只需要在创建stage
的时候将渲染类型指定为"webgl"
:
var stage = new Hilo.Stage({
width:canvasWidth,
height:canvasHeight,
container:containerID,
renderType:"webgl"
});
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.