GithubHelp home page GithubHelp logo

article's People

Contributors

06wj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

eric183

article's Issues

创建一个立方体

创建一个立方体

  • 设置摄像机
  • 创建舞台 Stage
  • 生成一个Ticker
  • 构建一个Mesh:创建几何体
  • 构建一个Mesh:创建材质

设置摄像机

在创建一个立方体之前,我们首先创建一个透视摄像机,Hilo3d.PerspectiveCamera

代码如下:

var camera = new Hilo3d.PerspectiveCamera({
	aspect: innerWidth / innerHeight,
	far: 20000,
	near:1,
	z:5
});
				
  • aspect: 指定了我们的摄像机需要捕捉的设备空间(默认情况下为手机屏幕的设备宽高比)
  • far: 指定了平截头体的最远平面z值
  • near: 指定了平截头体的最近平面z值
  • z: 指定了当前的相机所处位置

创建一个舞台Stage

完成相机以后我们需要一个舞台元素,和Hilo2D 舞台元素Stage 类似,我们在创建3D场景的时候 也需要一个舞台元素

var stage = new Hilo3d.Stage({
	container: $('container'),
	camera: camera,
	width: innerWidth,
	height: innerHeight
});
				
  • container: 制定舞台绑定的DOM元素,Hilo3D会创建Canvas元素填充,同时生成一个逻辑的舞台
  • camera: 摄像机元素,即我们上面创建的透视相机
  • width: 舞台的宽度
  • height: 舞台的高度

生成一个Ticker

通常情况下,我们不太需要单帧的渲染输出,而是需要在一个交互过程里完成对不同操作响应和元素的渲染。完成这项工作的就是计时器(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

在构建Mesh之前,我们先说说3维构造,从模型和计算渲染角度来讲,我们把模型分成两块,一种是几何形,另外一种是几何形上的附着(材质)。可以理解成人体的骨络和皮肉。

为了更加形象解释,我们使用2015年《双十一万店同庆》的天猫模型举例:

image.png

如上图所示,设计师在使用Maya 3DMax等软件,通过创建3维点构造出了猫模型的几何形。即Hilo3d中的gemometry的概念

设计好几何体以后设计师按照一定的UV展开,在这个几何形上添加纹理附着:

image.png

完成“骨络”和“皮肉”的一一对应的最关键的工作就是UV展开,以下就是猫模型的UV展开图:

image.png

构建Mesh,先创建一个几何体

介绍完几何形和附着,我们先创建一个简单的几何形——立方体 new Hilo3d.BoxGeometry()

V1.0 版本提供最基本的几何体构建,操作也非常简便。

构建Mesh,再创建一个材质

材质分类比较广,和被渲染物体本身材质属性有关,也和环境、光有关。在Hilo3d中以Material定义材质。

根据光和自身物理属性,我们把材质的属性分为以下几类:

  • 底色附着(BaseColor)
  • 反光度(Reflectivity)
  • 透明度(Transparency)
  • 金属色(Metallic)
  • 高光(Specular)
  • 粗糙度(Roughness)
  • 其他材质属性

所有的材质在渲染上的表现都是材质属性(上述几类)和光的叠加计算后的结果。如下图所示,如果我们把物体材质的各种属性抽象为一张张描述贴图,通过在着色器中加入光的计算,渲染后的结果就是我们在现实世界里的看到的样子。

着色器,指一组供计算机图形资源在执行渲染任务时使用的指令

e79935c45bbe00a3bd1bb1c73e1efe5c

有一类材质渲染只考虑贴图和颜色,这些贴图颜色在处理之前已经做过预渲染(Photoshop 或者 单反相机等),这一类的渲染方式称为Texture mapping ,或者叫diffuse mapping。

实现一个颜色纹理的材质可以这样:

var color = new Hilo3d.Color(0, 0, 1)

new Hilo3d.BasicMaterial({
	diffuse: color
})

  • diffuse参数:diffuse Mapping,又叫 Texture mapping,这里接受参数 Hilo3d.Color 或者 Hilo3d.Texture
  • BasicMaterial 使用的光照模型为 Phong shading,此处不做扩展解释

刚才提到,底色附着可以通过颜色实现,也可以通过纹理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;
    }
});
  • x:-200,Mesh均继承Node,位置属性等均由Node结点提供
  • onUpdate:继承自Node,会在每一帧轮询期间被调用

最后将网格结点添加到舞台

stage.addChild(blueBox);

渲染一个立方体的工作就完成了。

示例demo地址

Reference

实现38女王节3D打斗场面

使用Hilo3d V1.0 Alpha版本渲染38女王节AR 互动打斗场景截图:

55b34711f9982f1ef17fd02639cf600d

好,我们使用Hilo 3d实现以上效果:

创建一个透视相机

var camera = new Hilo3d.PerspectiveCamera({
    aspect: innerWidth / innerHeight,
    far: 1000,
    near:0.1,
    fov:75,
    y:4,
    z:3
});

  • aspect: 选取设备的像素宽高比
  • far: 平截头体的远视口,z轴方向
  • near: 平截头体的近视口,z轴方向
  • fov,透视开角大小,关于fov移步这里
  • y,z:摄像机的y z位置,x y z默认为0

创建一个舞台stage

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";
}
  • container: 指定绑定的Dom结点,Hilo3d会创建一个Canvas对象
  • camera: 即上面创建的摄像机
  • width: 设备像素宽度,本例取的window.innerWidth * window.devicePixelRatio
  • height: 设备像素高度,本例取的window.innerHeight * window.devicePixelRatio

为了避免锯齿现象,我们把Canvas画布的对象真实宽高置为window.devicePixelRatio倍,同时将画布的css的宽高置为当前窗口大小。

避免锯齿的方法除了上面的方式,修改着色器也是方法之一,以后再做阐述。

设置计时器

var ticker = new Hilo3d.Ticker(60);
ticker.addTick(stage);

ticker.addTick(Hilo3d.Tween);
ticker.addTick(Hilo3d.Animation);

ticker.start();
  • Hilo3d.Ticker(60): 指定FPS参数数值大小
  • addTick(Hilo3d.Tween): 开场需要对一些模型做缓动,需要将缓动Hilo3d.Tween手动加入到Ticker循环中
  • addTick(stage): stage中所有的子对象都会被逐结点查找,并在逐时间片内完成update和render过程
  • addTick(Hilo3d.Animation): 如果glTF模型中包含动画,需要添加Hilo3d.Animation对象,Hilo3d会协助完成动画相关的解析和渲染
  • ticker.start(): start一旦调用,渲染和3维元素数据更新便开启了

准备3D素材资源

借助海螺码头素材转化能力,我们将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"
    }
];
  • 海螺码头获取3D素材资源列表,3个角色,一个地面场景

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)
  • node.getChildByName('RootNode'): Node提供查找子节点相关方法,以结点名称查找

另外,还可以按照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);

  • 从loadQueue获取的资源列表选取我们要设置的角色对象
  • 将角色对象添加到根节点中
player0.anim.addClip('run', 0, 20/30);
player0.anim.addClip('attack', 20/30, 45/30);
player0.anim.stop();
  • 本例中设计师将一套动画资源放在一起,我们需要根据时间片选取对应动画数据
  • player0.anim 为Hilo3d.Animation 对象,包含和动画相关的API,如果模型文件中不包含动画,此属性为null
  • player0.anim.addClip('run', 0, 20/30): 为player0指定一段自定义为“run”的动画片段,动画的时间片为[0, 20/30],时长为2/3秒
  • player0.anim.addClip('attack', 20/30, 45/30):为player0指定一段自定义为“attack”的动画片段,动画的时间片为[20/30, 45/30],时长为0.83秒
  • player0.anim.stop(): Hilo3d默认启动动画播放,不需要自动执行动画时需要暂停

缓动角色对象

soldier.setPosition(pos[0], 5, pos[2]);
soldier.rotationY = index > 3?-90:90;
soldier.setScale(soldierScale);
soldier.visible = false;
  • 设置角色对象的soldier 位置,大小和朝向
Hilo3d.Tween.to(soldier, {
    y:0
}, {
    onStart:function(){
        soldier.visible = true;
    },
    delay:250,
    duration:500
});

  • Hilo3d.Tween: 沿用Hilo2d的缓动Tween
  • 将soldier的y方向位置置为0,耗时0.5秒,延迟 0.25秒执行
soldiers.push(soldier);
_root.addChild(soldier);
soldier.anim.addClip('run', 0, 20/30);
soldier.anim.addClip('attack', 20/30, 45/30);
soldier.anim.stop();
  • 设置soldier的角色动画,禁止自动播放

设置相机朝向

camera.lookAt(ground);

  • camera.lookAt: lookAt继承自 Hilo3d.Node , 确定摄像机的朝向为某个场景内物体,另外对场景内的物体使用lookAt 可以实现Billboard的效果

843f628f94f9b14690b37f558bddfc3d

输入控制OrbitControls

var orbitControls = new OrbitControls(stage);
  • 将手机屏幕等虚拟成一个近似球形平面,支持双手指缩放
  • 接受参数 Hilo3d.Stage

渲染Gizmos

渲染期间想了解模型的fps,面数,drawcall次数,系统内存占用等性能信息(PC Only),可直接创建Stats对象

var stats = new Stats(ticker, stage.renderer.renderInfo);
  • Stats以插件形式提供,对应目录 example/
  • renderer.renderInfo 可以提取可渲染性能相关信息

演示示例

传送门

Hilo技术全景分析

Hilo,一套HTML5跨终端的互动游戏解决方案。Hilo支持了多届淘宝&天猫狂欢城等双十一大型和日常营销活动。内核极简,提供包括DOM,Canvas,Flash,WebGL等多种渲染方案,满足全终端和性能要求。支持多种模块范式的包装版本以及开放的扩展方式,方便接入和扩展。提供对2D物理,骨骼动画的内建和扩展支持。另外,Hilo提供丰富的周边工具及开发案例。

目前,Hilo已经开源,并入到Hilo Team中。开源地址 https://github.com/hiloteam/Hilo (欢迎Star)

Hilo的特点:

  • 极简内核: Hilo核心模块极精简,保留了2D游戏引擎最必要的模块,同时采用模块化管理。
  • 完善接入&扩展: Hilo 支持多种模块范式的包装版本,包括AMD,CMD,Standalone多种方式接入。另外,你可以新增和扩展需要的模块和类型。
  • 多种渲染方式:提供DOM,Canvas,Flash,WebGL等多种渲染方案,可以做到跨全端,高性能的要求。
  • 完善的周边工具:提供动画编辑器 ,Yeoman脚手架及典型案例产出的辅助开发工具。
  • 案例丰富: 支持天猫,手淘多次大型和日常活动,如双十一,年中大促等。代表产品如狂欢城。

极简内核,完善的接入和扩展

Hilo采用极简的内核。核心模块包括基础类工具(Class),事件系统(EventMixin),渲染(Render)和可视对象(View),如下图所示。

_2016_03_02_12_05_10

首先,我们来看看如何接入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在表现上就是一个个矩形,无论图片还是文字都可以使用一个最小的矩形包裹。在这些可视对象上做平移,旋转,缩放,透明处理等操作就可以实现普通动画的绝大部分。

_2016_03_01_19_33_40

如上图所示,View解决了可视对象展示的基本问题。

利用Hilo提供创建类和扩展类的方法,我们可以扩展出可视对象所属管理的Container类:

  • 添加和删除可视对象
  • 可视对象排序
  • 位置对换,可视对象的所属包含判断
  • 由坐标系的位置获取可视对象
  • 根据位置、索引、Id来添加和删除可视对象

_2016_03_02_12_06_12

类似地,根据不同View的其他展示特性,Hilo扩展出舞台Stage,位图Bitmap,画图Graphic,精灵动画Sprite等类型。

多种渲染方式

我们知道一个游戏运行的核心流程——在一个Loop循环内,接受输入并完成对所有可视对象的游戏属性更新,然后渲染。下面是单个可视对象的一个循环过程:

_2016_03_02_10_37_18

作为一个可视对象,包含了位置,大小,缩放,旋转等自然信息,如下图所示:

_2016_03_01_19_36_07

Update是个计算过程,后面赛车案例会讲通过Update我们可以做一些特殊的效果出来。在这之前,我们先看看渲染,即如何根据可视对象的自然信息来把他们 “画”出来。那么如何实现View的render函数呢?如上图所示,在render函数中主要解决两个问题:

  • 位置,尺寸,旋转等变换(transform)
  • 渲染(背景样式,图片,颜色,透明度等)
    render: function(renderer, delta){
        //不同的View renderer方式可以多样,相同的View也可以根据renderer的类型不同而呈现差异化
        ...
    }

Hilo首次提出了特别的渲染方案——即提供DOM 、Canvas、 Flash或者WebGL 四种渲染的方式来实现render,目前该方案已经申请专利。这四种渲染方式是和View独立分开的,View在做自身属性更新时完全不需要考虑怎么被“画”出来,同样,拿到View后我们可以使用不同的“画笔”把它描绘出来。如果你有更好的绘制方式,也可以扩展出更多的渲染方案。如下图所示:

_2016_03_02_11_01_21

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工程实现绘制的方法,如下图所示:

_2016_03_02_11_06_41

由于Flash在PC浏览器上的广泛支持,特别是IE的支持,使用Flash渲染额外好处就是跨终端,这个终端包括所有主流PC浏览器(包括IE 6,7,8)在内。

另外,在一些低端的手机浏览器上,可以选择DOM渲染模式来代替其他的渲染方式。在Canvas支持不好的机器上或者互动游戏场景本身比较耗性能情况下,DOM渲染模式可以很好地胜任渲染的工作。2015年天猫年中大促的赛车互动,我们就在Android机器上使用DOM渲染的方案。

衍生能力

除了多种渲染模式,Hilo还提供给一些其他衍生能力。这些衍生能力或者来自每次项目的技术改进,或者来自对其他优秀引擎的能力的吸收。例如Hilo支持主流骨骼动画和自建骨骼动画系统(Tahiti),狂欢城多图片下高性能优化,主流物理引擎无缝支持和一些特殊物理效果实现。

骨骼动画

相比较精灵动画(Sprite Animation),骨骼动画(Skeletal animation)使用一套资源就可以完成千万种动作变化。

目前做骨骼动画比较成熟的产品有SpineDragonBones 。两者在功能上已经接近,考虑DragonBones免费,可自由使用,Hilo实现了对DragonBones的支持。

Hilo也实现了自己的动画编辑器(目前仅内部使用)——Tahiti。Tahiti通过Flash插件的方式实现,目前可以支持CSS3 animation,DOM,Canvas,Hilo动画导出。

骨骼动画将可视对象进行分解,得到一个个可视组件。很显然,这些一个个可视组件本身就是一个个View,只要调整相应的时间片内调整这些View的transform属性,把他们组合起来就是一套完整的动作。

_2016_03_01_11_42_17

_2016_03_01_11_23_11

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)的渲染。

1

Hilo 骨骼动画 Demo 点这里

性能

一年一度的双十一狂欢城是检验性能的大考。以2015年双十一狂欢城为例,在性能方面主要面临的挑战:

  • 多图:100多商家,每个商家都有品牌Logo
  • 动画:大部分商家需要动态展示Logo,至少3帧精灵动画
  • 单屏:所有的动画需要在一个页面里分屏展示(IPhone 6 plus 下为8屏)

狂欢城预估图片总数为200张 ( 252*296),为了优化性能,我们首先对整体画面做了三个分层:

  • 地图部分:此层的资源绝大数为静止画面,内容更新概率低。
  • Logo展示部分:商家品牌的动态展示区域,一般为3帧的精灵动画。
  • UI 层:导航,操作UI等。

下面主要看看地图部分的优化。

地图部分多为静止画面,为TiledMap的拼块。由于Canvas大小有限制,同时为了性能考虑,我们把8屏狂欢城界面按512*512分解成多个块,分解方式如下图:

_2016_03_02_12_07_27

把这些分块独立成一个个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计算方式。具体实现在后续介绍文章中推出。

Alt text

以上,从动画,性能,物理三个方面案例阐述了Hilo可以做更多的事情,相信在业务支持和技术推动下,Hilo可以更加完善,非常希望对互动感兴趣的同学加入到Hilo开源队伍中,完善Hilo能力,提高Hilo开发效率,同时产出更多令人欣喜的作品。

Hilo 从阿里前端委员会建议立项到开发完成,得到许多关注和帮助,也同时支持到共享、阿里通信、手淘、天猫、城市生活、国际UED、阿里妈妈多个BU。因技术推广这里就不一一感谢。 留下两位主要作者的花名 @正霖 @墨川

Reference

使用Macaca进行单元测试

Macaca

Macaca 是一套完整的自动化测试解决方案。
特性:

  • 支持移动端和PC端
  • 支持Native, Hybrid, H5 等多种应用类型
  • 提供客户端工具和持续集成服务
  • 对于游戏和图表类的富渲染产品也有很好的支持

游戏类产品

Hilo 是阿里巴巴开源的一款HTML5跨终端游戏解决方案,ta可以帮助开发者快速创建HTML5游戏。

ta有以下几个特性:

  • 多种渲染方式, 提供DOM,Canvas,Flash,WebGL等多种渲染方案
  • 集成了物理引擎,骨骼动画模块
  • 案例丰富,经受过双11,双12等大型互动活动考验

覆盖原则

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进行覆盖还有个优势,可以在没有屏幕的服务器端运行,也兼容正常浏览器环境的测试。服务端与浏览器端输出完全一致。

相关链接

看Hilo如何描绘HTML5互动世界——补间动画

在电影里或者荧屏上,我们经常看到下面的特写镜头。

_
_

这种特写镜头(除了最后的效果部分)和我们的今天要说的补间动画有些相似。在实际操作时,有些场景下我们没有必要刻画游戏物体每一帧的位置颜色等其他信息。 而是使用一种计算方法,来替我们告诉这些游戏对象在每一帧里应该做怎样的变换。这一类动画我们可以宽泛的称之为补间动画。技术描述上可能很宽泛,比如叫transition,缓动,过渡等等。

有哪些工具和方法

CSS3 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)。

利用脚本实现一个补间动画

tween

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) 的部分弧线效果,如下图红线标注部分。

_2016_04_05_16_45_32

示例代码作用对象为UI元素的背景颜色值(Blue), 我们可以控制步长step来控制颜色变化的速度。

easeInCubic 通过控制输入当前(迭代)步长或时间来确定输出值,如下图 :

_2016_04_05_16_46_51

除了Y = X^ 3这条曲线可以模拟出ease-out的效果,诸如 Y = X ^ k 类型的曲线,如Y = X ^ 4(Quart) ,Y = X ^ 5 (Quint),另外,三角函数和指数函数也可以做到类似的效果。过渡函数详细的分类

Alt text

实际上上文提到的CSS3的过渡函数接口cubic-bezier(n,n,n,n) 也是一种过渡函数的表达方式,如下图,P1,P2是2个控制点,两个点的坐标对应cubic-bezier(n,n,n,n)四个参数:

_2016_04_05_17_03_29

cubic-bezier(n,n,n,n)只能设置两个控制点的位置,显然,它能所表达的过渡动画种类不如脚本表达的丰富。

Hilo提供的过渡动画接口

看完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地址,效果如下图所示:

tween

tween

tween3 (大图,建议打开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

Reference

看Hilo如何描绘HTML5互动世界——粒子特效

粒子

在上文中看Hilo如何描绘HTML5互动世界——补间动画中我们描述了特效的前半部分——缓动,后半段的撞击破碎效果可以使用今天提到的粒子效果来实现。

粒子系统可以看成是多个粒子渲染单元的规则或不规则运动的集合,因此在组成上也就分为两个部分——粒子单元和运动集合。

单个粒子而言,和普通的游戏对象没有太大区别。但是当多个粒子组合在一起以后就会形成各种形状图案,运动后形成各种运动轨迹。粒子特效在需要氛围场景的情况下应用广泛。

ParticleSystem

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 ]

拼合图demo点这里

规则粒子物体运动

实际上,Hilo提供的ParticleSystem解决了一类规则运动问题。这些粒子的特点是:

  • 在某方向上有速度和加速度
  • 粒子和粒子之间的运动非常相近
  • 有一定的生命周期
  • 粒子在运动过程中除了速度和方向上改变,还有旋转、缩放或透明度变化
  • 相似的初始位置
  • 以上特征在一定范围内随机

使用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

上图是模拟Boids的一种实现。

粒子编辑器

编辑器是一个非常重要的辅助互动&游戏开发的思路。整个互动&游戏开发开发流程中核心部分就是数据、驱动和渲染,其中,数据和渲染又是核心中的核心。数据,往小的方面来说就是在一段简单代码前需要构思怎样的数据结构;往大的方面来说,就是构建所有游戏对象的基础信息(怎样的位置、形状、运动等等)如关卡数据,地图数据,骨骼数据,帧动画数据,UI数据,粒子数据等。 拿到这些数据我们可以使用Hilo来渲染,可以使用3D来渲染,只要数据格式匹配,是可以使用任何引擎来做渲染的。通过编写编辑器我们可以快速拿到需要的数据格式。

Hilo会以工具的形式提供一些通用的编辑器。

规则粒子运动编辑器

按照上文提到的ParticleSystem 的粒子特点,这类粒子有相似的初始位置,XY方向上的速度和加速度,根据这些特点我们可以针对性的写一个运动编辑器,Hilo提供了这样的粒子运动编辑器:点击这里使用编辑器

编辑器效果UI如下:

编辑器效果图

  • 区域1:粒子效果展示
  • 区域2:粒子参数,对任何一个参数值都有对应的调整按钮
  • 区域3:导出的粒子数据格式,这些数据可直接运用到 ParticleSystem 的构造函数中
  • 区域4:预置的一些粒子效果

规则粒子的运动参数基本是固定的,因此其对应的运动编辑器会比较容易写。

不规则粒子图形编辑器

通常不规则粒子运动受算法制约,适用性没有规则运动广泛,因此除非有特殊需要,才会编写编辑器。但是,一些场景下,不规则运动的粒子最初会有一些图案和特殊的粒子形状,例如天猫年会需要展示的猫头。这种场景下需要我们把组成猫头形状的粒子特征提取出来。

矩形基础

拿到视觉稿分析一下,基础图形的形状只有四个(a,b,c,d):

基础形状

那么我们需要编辑器给我们产出两类信息——位置(相对矩形区域)和形状:

    {
          "colIndex": 1, 
          "rowIndex": 0, 
          "color": "rgb(240, 131, 39)", 
          "alpha": "1", 
          "type": "d"
    }

拿到这些基本信息我们就可以构建单元粒子的外观、形状和位置。无独有偶,在之前的文章《天猫抢豪车竞速互动技术回放》讲到竞速赛道的地图编辑器也是相似的道理。

总结起来,如下图所示:

粒子效果

在需要提供渲染数据支持时,我们编写对应的编辑器;在需要提供运动数据支持时,我们编写对应的编辑器;在需要动作数据时,我们编写对应的编辑器。

性能

由于粒子系统要操作大量的元素,因此在性能上会遇到一些问题。不过粒子效果多数为氛围效果,交互性不是非常强,所以只要保证粒子在渲染的时候符合预期就好了。

粒子非常多的情况下,如果逐个粒子渲染是非常耗性能的。下面的这个动画是我们使用dom实现的一个方案——改变dom的transform值来实现的效果。

使用dom 完成的效果

PS:以上是在相同的帧率下 进行的录屏操作

可以看出,相比较猫头散开的动画帧率下降很多。

所以, 尽可能把所有渲染进行batch,Hilo提供Webgl的渲染模式,可以直接把这样的渲染效果集约到一次Draw call中进行。
使用上,只需要在创建stage 的时候将渲染类型指定为"webgl":


var stage = new Hilo.Stage({
    width:canvasWidth,
    height:canvasHeight,
    container:containerID,
    renderType:"webgl"
});

关于batch,可以参考这里这里,还有这里

参考

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.