内容简介:设计师怎么玩转Html5 3D游戏,一款让开发都折服的H5
基于webgl的html5 3D实战,两位设计师两周内完成设计和编程
第一次接触webgl还是去年的天猫活动,记得当时网上好多人疯狂转载,因为是国内第一次出现,所以大家觉得挺新鲜的,后来我了解到原来使用的是threejs的第三方库。去官网看了现有的示例,当时深深地被其3D效果所震惊,原来web还能做到主机和客户端的3D效果。作为一名交互设计师,除了日常产品对接体验优化工作外,自己业余时间也在学习threejs。不得不吐槽,threejs文档真心难用,学习成本还是比较高,但是多看看官网示例,多看看源码还是有不少进步的,顺便让我的Javascript的基础知识更牢固了。废话不多说了,进入今天的主题吧。先放几张截图和体验地址
首先说一说需求来源,同程旅游市场中心品牌组要搞一个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的第一次尝试,由于本人只是个业余开发,学代码也才一两年,未来还有很长的路要走,感谢峰哥和小伙伴的帮助和鼓励,希望以后会带给大家更多更好的作品,也希望大家多给些建议,大家互相学习。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。