设计师怎么玩转Html5 3D游戏,一款让开发都折服的H5

栏目: Html5 · 发布时间: 8年前

内容简介:设计师怎么玩转Html5 3D游戏,一款让开发都折服的H5

基于webgl的html5 3D实战,两位设计师两周内完成设计和编程

第一次接触webgl还是去年的天猫活动,记得当时网上好多人疯狂转载,因为是国内第一次出现,所以大家觉得挺新鲜的,后来我了解到原来使用的是threejs的第三方库。去官网看了现有的示例,当时深深地被其3D效果所震惊,原来web还能做到主机和客户端的3D效果。作为一名交互设计师,除了日常产品对接体验优化工作外,自己业余时间也在学习threejs。不得不吐槽,threejs文档真心难用,学习成本还是比较高,但是多看看官网示例,多看看源码还是有不少进步的,顺便让我的Javascript的基础知识更牢固了。废话不多说了,进入今天的主题吧。先放几张截图和体验地址

设计师怎么玩转Html5 3D游戏,一款让开发都折服的H5

设计师怎么玩转Html5 3D游戏,一款让开发都折服的H5

设计师怎么玩转Html5 3D游戏,一款让开发都折服的H5

首先说一说需求来源,同程旅游市场中心品牌组要搞一个616运营活动,主要是以游乐场为场景,突出616活动气氛,以H5的形式出现。产品找到我们UED,本来是让我们这边出交互和设计稿,我们UED leader峰哥说可以尝试不一样的,可以试试我们最近研究的Webgl,产品看了两个demo后,欣然同意我们的想法(Demo更厉害,留着以后放大招)。期限为两周,包括搭场景、开发和测试,说真的,第一次做压力不是一般大,时间比较紧,难度有点高,而且要做那种漫游的感觉。

做的时候我和峰哥两人分工的,我负责coding,峰哥负责建模搭场景。这时不得不佩服我们leader的综合能力,场景是3dmax做的,包括建模,贴图,烘焙,AO等。这东西我只是了解,这部分交给峰哥了。Webgl基本环境和场景搭建我就不介绍了,这个太简单了,主要包括相机、灯光、场景等,大家可以去Github上看我源码

我首先要解决的问题是路径问题,飞机要沿着一定的路径飞行,因为场景不能太大,如果单纯绕圈或者沿着某一方向飞行一会就结束了,没有趣味性,飞机要飞得忽上忽下,有种漫游的感觉(PM原话)。要沿着某一固定曲线路径,首先要生成曲线即SplineCurve,查看document文档,发现只能生成二维的,找到几个示例才发现CatmullRomCurve3才是生成三维曲线(写在了对象里,为了“再玩一次”重新初始化)。

game={

status: "playing",

t:0,

score:0,

k:0,

spline: new THREE.CatmullRomCurve3([

new THREE.Vector3(-1095, 1285, 71),

new THREE.Vector3(134, 920, 230),

new THREE.Vector3(1170, 762, 615),

new THREE.Vector3(2234, 628, 537),

new THREE.Vector3(2618, 737, -701),

new THREE.Vector3(1802, 488, -1260),

new THREE.Vector3(823, 462, -1236),

new THREE.Vector3(203, 307, -357),

new THREE.Vector3(207, 275, 745),

new THREE.Vector3(1031, 468, 1247),

new THREE.Vector3(1280, 550, 256),

new THREE.Vector3(1280, 448, -550)

])

};

生成曲线后飞机怎么才能沿着曲线飞行呢?对了,飞行的时候相机是跟随的,这时候要用到Object3D,类似于父类DIV容器一样,代码如下:

var body=new THREE.Object3D();

body.add(camera); //添加相机

body.add(plane); //添加飞机机身

body.add(scoller); //添加螺旋桨

调好两者的位置,这样飞机和相机都绑定在一块了,只要控制body的运动,飞机和相机就会做相同动作。沿着曲线运动用到了getPoint()和getTangent(),但是遇到了问题,曲线的法线方向是一直变化的,而飞机的朝向一直是固定的,移动时一直朝向前方,这就尴尬了,应该是朝向沿着曲线切线方向,这样才有飞行的感觉。去Stack Overflow搜了一下,其实很简单,移动时一直计算切线方向,利用反余弦获取角度,然后使飞机朝向和切线方向一致就可以了。代码如下:

var pt = game.spline.getPoint( game.t );

body.position.set( pt.x, pt.y, pt.z );

var tangent = game.spline.getTangent( game.t ).normalize();

axis.crossVectors( up, tangent ).normalize();

var radians = Math.acos( up.dot( tangent ) );

body.quaternion.setFromAxisAngle( axis, radians );

第一个问题花了一天左右解决后,其他问题就简单了,主要就是模型的导出和导入,以及一些细节的处理。模型基本上都是导出json文件,然后调用ObjectLoader或JsonLoader导入。导出Json有好几种方法,例如网上用Blender导出Json,这种方法自己试过跑通的但是不方便,得要一个一个导出,还有就是Blender界面超难用,果断放弃。后来自己和峰哥探索出了Maya,3dmax导出Json的方法,但是稳定性有待提高,等我学好了 python 再优化。我们现在使用最稳妥的就是3dmax导出obj文件,导入到threejs官网的editor里编辑后导出Json,但是只能导入静态模型,动画模型还是要用到3dmax或者Maya导出,但对于这个项目是足够的。方法如下:

模型导入后就是大的背景搭建了,比如天空盒和海水,天空盒就是在建个Box,六张贴图贴在六个面(前后上下左右),代码如下:

function createSkybox(){

var path = "images/sky_";

var format = '.jpg';

var urls = [

path + 'px' + format, path + 'nx' + format,

path + 'py' + format, path + 'ny' + format,

path + 'pz' + format, path + 'nz' + format

];

var materials = [];

for (var i = 0; i<urls.length; ++i) {

var loader = new THREE.TextureLoader();

loader.setCrossOrigin( this.crossOrigin );

var texture = loader.load( urls[i], function(){}, undefined, function(){} );

materials.push(new THREE.MeshBasicMaterial({

map: texture

})

);

}  //导入六张贴图(前后左右上下)

var skyBox = new THREE.Mesh( new THREE.CubeGeometry( 100000, 100000, 100000 ), new THREE.MeshFaceMaterial( materials ) );//建个正方形盒子,尽量超大

skyBox.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, -1 ) );

scene.add( skyBox );

}

海水就比较麻烦了,用的是threejs示例里的Mirror.js和WaterShader.js,可以参考下官网的例子,调下参数就OK了,海水代码如下:

function createWater(){

waterNormals=new THREE.TextureLoader().load( 'models/texture_scene/waternormals.jpg' );

waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;

water = new THREE.Water( renderer, camera, scene, {

textureWidth: 512,

textureHeight: 512,

waterNormals: waterNormals,

alpha:   1.0,

sunDirection: light.position.clone().normalize(),

sunColor: 0xffffff,

waterColor: 0x0090c5,

distortionScale: 50.0,

});

mirrorMesh = new THREE.Mesh(

new THREE.PlaneBufferGeometry( 100000, 100000),

water.material

);

mirrorMesh.add( water );

mirrorMesh.rotation.x = - Math.PI * 0.5;

scene.add( mirrorMesh );

}

第三步就是加入金币和红包了,金币和红包要分布在固定曲线路线的两侧,而且朝向得跟曲线方向一致,不然就吃不到了,所以得要再次用到上续的代码,核心代码如下:

for (var i=1; i

Pn[i]=game.spline.getPoint(i/nBlocs);

Tn[i]=game.spline.getTangent(i/nBlocs);

var m=new THREE.Mesh(geometry, mat);

var s=0.2;

m.scale.set(s,s,s);

m.position.set(Pn[i].x+randomRange(-12,12),Pn[i].y,Pn[i].z);//分布在曲线两侧

var tt= game.spline.getTangent(i/nBlocs ).normalize();

Tn.crossVectors( up, tt).normalize();

var radians11 = Math.acos( up.dot( tt ) );

m.quaternion.setFromAxisAngle( Tn, radians11 );

coinsets.add(m);

if(i%11==0){i=i+2;}

}

第四步就是导入飞机和控制飞机,飞机拆成了螺旋桨和机身是为了螺旋桨的动画,通过var delta = clock.getDelta(); rotatescoller.rotation.z+=delta*50;两行代码让螺旋桨旋转起来。最难的就是控制飞行了,可能你会说不就是利用陀螺仪控制左右移动嘛,刚开始就是这样的,半小时搞定了;但后来发现存在一个问题,飞行时左右移动是漂移的,就是太tm假了,没有物理飞行的效果。好吧,在leader的重压之下搬出了高中的物理知识,利用JS的if…else写出了物理运动效果,当时也想过调用第三方库,但是提醒的大家是要想做出好的Webgl作品,尽量要克制使用第三方库,不然渲染时肯定会卡,就用原生的JS写,这样你才能有所提高。代码如下,有点傻瓜但是实用。

function controlUpdate(tt){

var actualV=0.5;

var angleLR=0.02;

group.position.x+=flymove;

group.rotation.z=flyangle;

if( moveLeft&&group.position.x>-10){

if(flyangle<0.2){

flyangle+=angleLR;

}

flymove-=actualV*tt;

}else if(moveRight&&group.position.x<10){

if(flyangle>-0.2){

flyangle-=angleLR;

}

flymove+=actualV*tt;

}

else {

flymove=0;

if(flyangle<-0.04){

flyangle+=0.02;

}

else if(flyangle>0.04)

{

flyangle-=0.02;

}

else{

flyangle=0;

}

}

}

飞机弄完后就是检测碰撞了,离成功只有一步之遥了,能否吃到金币和红包得要利用threejs里的Raycaster,Raycaster就是射出一道射线,类似于雷达波一样,检测到物体则返回值,从而判断是否碰撞到物体。检测代码如下:

function detectcollision(){

var collisions;

var rays = [

new THREE.Vector3(0, 0, 1),

new THREE.Vector3(1, 0, 1),

new THREE.Vector3(1, 0, 0),

new THREE.Vector3(1, 0, -1),

new THREE.Vector3(0, 0, -1),

new THREE.Vector3(-1, 0, -1),

new THREE.Vector3(-1, 0, 0),

new THREE.Vector3(-1, 0, 1),

];//射线方向

for (var i = 0; i<rays.length; i++) {

var caster = new THREE.Raycaster(body.position,rays[i],0,6);

// We reset the raycaster to this direction

collisions = caster.intersectObjects(coinsets.children);

//判断是否碰撞到

if (collisions.length > 0){

game.score+=2;

document.getElementById("track").innerHTML ='score: '+game.score;

var collectcoin=collisions[0].object;

coinsets.remove(collectcoin);

var Particleset=new ParticlesHolder();

Particleset.spawnParticles(collectcoin.position,6,1);

scene.add(Particleset.mesh);

}

}

}

大家看到代码里有一个对象ParticlesHolder,这个就是碰撞后的粒子,就是碰撞的一个反馈,也是纯JS手写,高中物理没白学…..

最后一步就是动画了,场景要实时渲染的,包括螺旋桨,飞机轨迹,场景、海水都是要一直渲染的,加上function animate(){requestAnimationFrame( animate );render();}就可以了。

除了上续的threejs核心代码外,还有一些流程上其他的工作,比如预加载,再玩一次,分享等。说一下预加载,因为是我第一次写这个预加载,所以去国内网站上看了一些例子,只能说很坑,new image()预加载图片还行,Json文件根本没反应,后来用了XMLHttpRequest()就可以了。

总的来说,这次收获还是蛮大的。期间遇到各种各样的问题,坑肯定不止以上这些问题,但是我们没有放弃,坚持在两周内解决了。但是整个游戏流程还是有点问题,比如吃完金币和红包没有任何奖励,这点体验很不好,但快上线了已经来不及了,这次也算是一个教训,遗憾总是有的。这算是我们市场中心UED的第一次尝试,由于本人只是个业余开发,学代码也才一两年,未来还有很长的路要走,感谢峰哥和小伙伴的帮助和鼓励,希望以后会带给大家更多更好的作品,也希望大家多给些建议,大家互相学习。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Pro Django

Pro Django

Marty Alchin / Apress / 2008-11-24 / USD 49.99

Django is the leading Python web application development framework. Learn how to leverage the Django web framework to its full potential in this advanced tutorial and reference. Endorsed by Django, Pr......一起来看看 《Pro Django》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具