龙之谷手游WebVR技术分享

主要面向Web前端工程师,需要一定Javascript及three.js基础;
本文主要分享内容为基于three.js开发WebVR思路及碰到的问题;
有兴趣的同学,欢迎跟帖讨论。

目录:
一、项目体验
1.1、项目简介
1.2、功能介绍
1.3、游戏体验
二、技术方案
2.1、为什么使用WebVR
2.2、常用的WebVR解决方案
2.2.1、Mozilla的A-Frame方案
2.2.2、three.js及webvr-polyfill方案
三、技术实现
3.1、知识储备
3.2、实现步骤
3.3、工作原理
四、技术难点
4.1、程序与用户共同控制摄像头
4.2、多重蒙板贴图
4.3、镜头移动
4.4、3d自适应长度文字提示
4.5、unity3d地形导出
4.6、3dmax动画导出问题
五、完整的源代码及相应组件

一、项目体验
1.1、项目简介:
1.1.1、名称:
“重历阿尔特里亚”——龙之谷手游手首发ChinaJoy2016预热VR小游戏

1.1.2、开发背景:
基于龙之谷手游具备的3D属性,全景视角体验,以及ChinaJoy首发的线下场景,我们和品牌讨论除了基于VR的线下体验项目。由于基于Web技术较好的兼容性、开发的高效性,我们采用了WebVR技术来实现整个体验。

1.1.3、使用WebVR优势:
1.1.3.1、普通web前端工程师可以参与VR应用开发,降低了开发门槛;
1.1.3.2、跨设备终端、跨操作系统、跨APP载体;
1.1.3.3、开发快速、维护方便、随时调整、传播便捷;
1.1.3.4、浏览器即可体验,无需安装。

1.2、功能介绍
基于游戏内3D场景、人物和道具模型,通过WebGL框架three.js开发的VR小游戏,在ChinaJoy龙之谷手游展台给玩家提供线下VR互动体验,并在后续应用于线上营销传播。不具备VR眼镜设备的用户可选择普通模式进行互动体验。

1.3、游戏体验
如果你身边正好有VR眼镜,请选择VR模式体验;如果没有,请选择普通模式。
需要说明的是,由于本次应用针对线下场景,而合作方三星提供了最新的S7手机和GearVR设备,所以项目只针对S7做了体验优化,所以可能部分手机会有卡顿或者3D模型错乱的情况。

你可以扫描如下二维码或打开http://dn.qq.com/act/vr/进行体验:

二、技术方案
2.1、为什么是时候尝试WebVR了?
2.1.1、时机慢慢成熟,我们通过几件事件即可感知:
2015年初,Mozilla在firefox nightly增加了对WebVR的支持;
2015年底,MozVR团队推出开源框架A-Frame,能过HTML标签,即可创建VR网页;
2015年底,Egret3D发布,开发团队称将在以后版本中实现WebVR的支持;
2016年初,Google与Mozilla联合创建WebVR标准;
2016年6月,Google计划将整个Chrome浏览器搬进VR世界中。
2.1.2、WebVR开发成本更低。
2015年VR硬件迅速发展,但时至今日,VR内容还是稍显单薄。原因在于,VR开发成本过高,而WebVR依托于WebGL及类似threeJS等框架,大大降低开发者进入VR领域的门槛。
2.1.3、Web自身的优势
上文中已有提及,依托也Web,具有不需安装、便于传播、便于快速迭代等特点。

2.2、目前阶段,常用的WebVR解决方案:
2.2.1、A-frame
介绍:Mozilla的开源框架,通过定制HTML元素即可构建WebVR方案的框架,适用于没有webGL与threeJS基础的初学者。
优点:基于threeJS的封装,通过特定的标签就能够快速创建VR网页;
缺点:所提供的组件有限,难以完成较复杂的项目。
实例:
2.2.1.1、创建一个简单的场景。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Composite — A-Frame">
<script src="../aframe.js"></script>
</head>
<body>
	<a-scene>
		<!-- 环境光. -->
		<a-entity light="type: ambient; color: #888"></a-entity>
		<a-entity position="0 2.2 4">
		<!-- 添加相机 -->
		<a-entity camera look-controls wasd-controls>
			<!-- 添加圆环 -->
			<a-entity cursor
			geometry="primitive: ring; radiusOuter: 0.015; radiusInner: 0.01; segmentsTheta: 32" material="color: #283644; shader: flat" raycaster="far: 30" position="0 0 -0.75"></a-entity>
		</a-entity>
		</a-entity>
	</a-scene>
</body>
</html>

源码讲解:
如上简单的几个标签,即可构建一个包含灯光、相机、跟随相机的物体的场景,其余事情,都将由A-frame进行解析,具体标签与属性不多作讲解,可以参考 A-frame DOC

2.2.1.1、加载一个由软件(比如3dmax)导出的模型。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Composite — A-Frame">
<script src="../aframe.js"></script>
<script>
	AFRAME.registerComponent(‘json-model‘, {
		schema: {
			type: ‘src‘
		},
		init: function () {
			this.loader = new THREE.JSONLoader();
		},
		update: function () {
			var mesh = this.el.getOrCreateObject3D(‘mesh‘, THREE.Mesh);
			this.loader.load(this.data, function (geometry) {
			mesh.geometry = geometry;
			});
		}
	});
</script>
</head>
<body>
<a-scene>
	<a-assets>
		<a-asset-item id="sculpture" src="data/building-ground.js"></a-asset-item>
	</a-assets>
	<a-entity id="car" json-model="#sculpture"  position="0 0 0" scale="5 5 5" rotation="0 45 0" material="src: url(cross-domain/skin/xianxiasq_zhujianqiangmian_001.png)"></a-entity>
</a-scene>
</body>
</html>

源码讲解:
这个例子主要演示,A-Frame如何添加组件,对,因为A-Frame现阶段组件太少,加载自定义模式需要自己扩展组件。而组件添加需要three.js基础。
so,A-Frame出发点是非常美好的,学习几个简单的标签及属性,即可以搭建3d/webvr场景,但是现实却是目前它还并不成熟,并且伴随着A-Frame主设计师跳槽到Google,所以我很早就放弃这个方案了。

2、基于threeJS与webVR组件,事实上,A-frame就是基于这两者的封装。
优点:可以完成复杂项目,可以结合原生的webGL;
缺点:需要掌握threeJS,需要了解webGL,学习成本较高。

在本项目中,选用的就是这个方案,在下章节中,将会进行详细介绍。

三、技术实现
3.1、知识储备:
three.js(掌握)、webGL(了解)、javascript
对three.js没有基础的同学,可以移步至 Three.js实例教程

3.2、实现步骤:
简单来说,完成一个WebVR应用,需要以下三个步骤:
3.2.1、搭建场景

如上图与示:
首先我们需要载入我们的资源,这些资源包括地形、角色、动画、及辅助元素;
然后创建我们需要的元素,比如灯光、相机、天空等;
然后完成主业务逻辑。

3.2.2、交互
即用户的动作输入,这些动作包括:
位置移动、旋转、视线焦点、声音、甚至全身所有关节动作。
当然,当前我们可利用的硬件设备有限,手机自身可利用的如陀螺仪、罗盘、听筒。其余辅助设备常用如Leap Motion、Kinect等。
更多的额外设备意识着更高的使用成本,在本案例中使用的到的动作输入信息:
用户当前方向,由VRControls.js与webvr-polyfill.js实现完成;
用户视角焦点,完成按钮点击、攻击等动作,通过跟随相机的物体检测碰撞来完成。

3.2.3、分屏

如上图所示,为让用户更具沉侵感,通常会根据用户瞳距将屏幕分割成具有一定视差的两部分,勿需担心,这部分工作由VREffect.js来完成。

3.3、工作原理
上节中提到了webvr相关组件,本来我们可以简单利用它提供的接口就可以完成,但肯定还是有同学会好奇,它的工作原理是怎样的呢。

这得从Mozilla与Google 2016年初联手推出的WebVR API提案开始,WebVR Specification,该提案给VR硬件定义了专门定制的接口,让开发者能够构建出沉浸感强,舒适度高的VR体验。但由于该标准还处于草案阶段,所以我们开发需要WebVR Polyfill,这个组件不需要特定浏览器,就可以使用WebVR API中的接口。
所以我们只需要在项目中,引入webvr-polyfill.js及VRControls、VREffect两个类,并调用即可。

vrEffect = new THREE.VREffect(renderer);
vrControls = new THREE.VRControls(camera);

webvr-polyfill基于普通浏览器实现了WebVR API 1.0功能;
VRControls更新摄像头信息,让用户以第一人称置于场景中;
VREffect负责分屏。

四、技术难点

4.1、程序与用户共同控制摄像头
当程序在自动移动镜头的过程中,允许用户四处观察,这时候需要一个辅助容器共同控制镜头旋转与移动。

// 添加摄像机
camera = new THREE.PerspectiveCamera(60, size.w / size.h, 1, 10000);
camera.position.set(0, 0, 0);
camera.lookAt(new THREE.Vector3(0,0,0));

// 辅助镜头移动
dolly  = dolly = new THREE.Group();
dolly.position.set(10, 40, 40);
dolly.rotation.y = Math.PI/10;
dolly.add(camera);
scene.add(dolly);

4.2、多重蒙板贴图

如上图所示,该地形由三种贴图通过蒙板共同合成,这时候我们需要使用自定义Shader来实现,由rbg三个通道控制显示。
核心代码(片元着色器):

fragmentShader: [
	‘uniform sampler2D texture1;‘,
	‘uniform sampler2D texture2;‘,
	‘uniform sampler2D texture3;‘,
	‘uniform sampler2D mask;‘,
	‘void main() {‘,
		‘vec4 colorTexture1 = texture2D(texture1, vUv* 40.0);‘,
		‘vec4 colorTexture2 = texture2D(texture2, vUv* 60.0);‘,
		‘vec4 colorTexture3 = texture2D(texture3, vUv* 20.0);‘,
		‘vec4 colorMask = texture2D(mask, vUv);‘,
		‘vec3  outgoingLight = vec3( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b ) * 0.6;‘,
		‘gl_FragColor =  vec4(outgoingLight, 1.0);‘,
	‘}‘
].join("\n")

完整代码(添加three.js灯光,雾化):

// 合成材质
var map1 = texLoader.load(‘cross-domain/skins/foor_stone02.png‘ );
var map2 = texLoader.load(‘cross-domain/skins/green_wet09.png‘);
var map3 = texLoader.load(‘cross-domain/skins/stone_dry02.png‘);

// 自定义复合蒙板shader
THREE.FogShader = {
	uniforms: lib.extend( [

		THREE.UniformsLib[ "fog" ],
		THREE.UniformsLib[ "lights" ],
		THREE.UniformsLib[ "shadowmap" ],
		{
			‘texture1‘: { type: "t", value: map1},
			‘texture2‘: { type: "t", value: map2},
			‘texture3‘: { type: "t", value: map3},
			‘mask‘: { type: "t", value: texLoader.load(‘cross-domain/skins/mask.png‘)}
		}
	] ),
	vertexShader: [
		"varying vec2 vUv;",
		"varying vec3 vNormal;",
		"varying vec3 vViewPosition;",

		THREE.ShaderChunk[ "skinning_pars_vertex" ],
		THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
		THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ],

		"void main() {",

			THREE.ShaderChunk[ "skinbase_vertex" ],
			THREE.ShaderChunk[ "skinnormal_vertex" ],

			"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",

			"vUv = uv;",
			"vNormal = normalize( normalMatrix * normal );",
			"vViewPosition = -mvPosition.xyz;",

			"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
			THREE.ShaderChunk[ "logdepthbuf_vertex" ],
		"}"
	].join(‘\n‘),

	fragmentShader: [
		‘uniform sampler2D texture1;‘,
		‘uniform sampler2D texture2;‘,
		‘uniform sampler2D texture3;‘,
		‘uniform sampler2D mask;‘,

		‘varying vec2 vUv;‘,
		‘varying vec3 vNormal;‘,
		‘varying vec3 vViewPosition;‘,
		// "vec3 outgoingLight = vec3( 0.0 );",
		THREE.ShaderChunk[ "common" ],
		THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
		THREE.ShaderChunk[ "fog_pars_fragment" ],
		THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ],

		‘void main() {‘,
			THREE.ShaderChunk[ "logdepthbuf_fragment" ],
			THREE.ShaderChunk[ "alphatest_fragment" ],

			‘vec4 colorTexture1 = texture2D(texture1, vUv* 40.0);‘,
			‘vec4 colorTexture2 = texture2D(texture2, vUv* 60.0);‘,
			‘vec4 colorTexture3 = texture2D(texture3, vUv* 20.0);‘,
			‘vec4 colorMask = texture2D(mask, vUv);‘,

			‘vec3 normal = normalize( vNormal );‘,
			‘vec3 lightDir = normalize( vViewPosition );‘,

			‘float dotProduct = max( dot( normal, lightDir ), 0.0 ) + 0.2;‘,

			‘vec3  outgoingLight = vec3( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b ) * 0.6;‘,

			THREE.ShaderChunk[ "shadowmap_fragment" ],
			THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
			THREE.ShaderChunk[ "fog_fragment" ],

			// ‘gl_FragColor = vec4( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b, 1.0 )  + vec4(outgoingLight, 1.0);‘,
			// ‘gl_FragColor = outgoingLight;‘,
			‘gl_FragColor =  vec4(outgoingLight, 1.0);‘,
		‘}‘
	].join("\n")

};
THREE.FogShader.uniforms.texture1.value.wrapS = THREE.FogShader.uniforms.texture1.value.wrapT = THREE.RepeatWrapping;
THREE.FogShader.uniforms.texture2.value.wrapS = THREE.FogShader.uniforms.texture2.value.wrapT = THREE.RepeatWrapping;
THREE.FogShader.uniforms.texture3.value.wrapS = THREE.FogShader.uniforms.texture3.value.wrapT = THREE.RepeatWrapping;

var material = new THREE.ShaderMaterial({
	uniforms        : THREE.FogShader.uniforms,
	vertexShader    : THREE.FogShader.vertexShader,
	fragmentShader  : THREE.FogShader.fragmentShader,
	fog: true
});

3、 镜头移动(依赖Tween类)
功能函数:

cameraTracker: function(paths){
	var tweens = [];
	for(var i = 0; i < paths.length; i++) {
		(function(i){
			var tween = new TWEEN.Tween({pos: 0}).to({pos: 1}, paths[i].duration || 5000);
			tween.easing(paths[i].easing || TWEEN.Easing.Linear.None);
			tween.onStart(function(){
				var oriPos =  dolly.position;
				var oriRotation = dolly.rotation;
				this.oriPos = {x: oriPos.x, y: oriPos.y, z: oriPos.z};
				this.oriRotation = {x: oriRotation.x, y: oriRotation.y, z: oriRotation.z};
			});
			tween.onUpdate(paths[i].onupdate || function(){
				if(paths[i].pos) {
				  	dolly.position.x = this.oriPos.x + this.pos * (paths[i].pos.x -  this.oriPos.x);
				  	dolly.position.y = this.oriPos.y + this.pos * (paths[i].pos.y -  this.oriPos.y);
				  	dolly.position.z = this.oriPos.z + this.pos * (paths[i].pos.z -  this.oriPos.z);
				}
				if(paths[i].rotation) {
				  	dolly.rotation.x = this.oriRotation.x + this.pos * (paths[i].rotation.x -  this.oriRotation.x);
				  	dolly.rotation.y = this.oriRotation.y + this.pos * (paths[i].rotation.y -  this.oriRotation.y);
				  	dolly.rotation.z = this.oriRotation.z + this.pos * (paths[i].rotation.z -  this.oriRotation.z);
				}
			});
			tween.onComplete(function(){
				paths[i].fn && paths[i].fn();
				var fn = tweens.shift();
				fn && fn.start();
			});
			tweens.push(tween);
		})(i);
	}
	tweens.shift().start();
}

调用:

lib.cameraTracker([
	{‘pos‘: { x: -45,y: 5, z: -38},‘rotation‘: {x: 0, y: -1.8, z: 0},  ‘easing‘: TWEEN.Easing.Cubic.Out,‘duration‘:4000}
]);

4、自适应长度文字提示
根据文字长度生成canvas作为贴图到Sprite对象。

hint = function(text, type, posY, fadeTime){
	var chinense = text.replace(/[u4E00-u9FA5]/g, ‘‘);
	var dbc = chinense.length;
	var sbc = text.length - dbc;
	var length = dbc * 2 + sbc;
	var fontsize = 40;
	var textWidth = fontsize* length / 2;
	posY = posY || 0.3;
	type = type || 1;
	fadeTime = fadeTime === window.undefined ? 500 : fadeTime;

	if(text == ‘sucess‘ || text == ‘fail‘) {
		text = ‘ ‘;
	}

	var canvas = document.createElement("canvas");
	var width = 1024, height = 512;
	canvas.width = width;
	canvas.height = height;
	var context = canvas.getContext(‘2d‘);

	var imageObj = document.querySelector(‘#img-hint-‘ + type);

	context.drawImage(imageObj, width/2 - imageObj.width/2, height/2 - imageObj.height/2);
	context.font = ‘Bold ‘+ fontsize +‘px simhei‘;
	context.fillStyle = "rgba(255,255,255,1)";
	context.fillText(text, width/2-textWidth/2, height/2+15);			

	var texture = new THREE.Texture(canvas);
	texture.needsUpdate = true;

	var mesh;
	var material = new THREE.SpriteMaterial({
		map: texture,
		transparent: true,
		opacity: 0
	});
	mesh = new THREE.Sprite(material);
	mesh.scale.set(width/400, height/400, 1);
	mesh.position.set(0, posY, -3);
	camera.add(mesh);	

	var tweenIn = new TWEEN.Tween({pos: 0}).to({pos: 1}, fadeTime);
	tweenIn.onUpdate(function(){
		material.opacity = this.pos;
	});
	if(fadeTime === 0) {
		material.opacity = 1;
	} else {
		tweenIn.start();
	}

	var tweenOut = new TWEEN.Tween({pos: 1}).to({pos: 0}, fadeTime);
	tweenOut.onUpdate(function(){
		material.opacity = this.pos;
	});
	tweenOut.onComplete(function(){
		camera.remove(mesh);
	});
	tweenOut.fadeOut = tweenOut.start;
	tweenOut.remove = function(){
		camera.remove(mesh);
	}

	return tweenOut;
};

5、unity地形导出
5.1、首先将unity地形导出为obj

5.2、然后导入3dmax,使用ThreeJSExporter.ms导出为js格式。

6、3dmax动画导出问题
6.1、动画导出错误
通常是对象为可编辑多边形,需要转换成网格对象。

操作步骤:
6.1.1、选择对象,右键转换为可编辑网络;
6.1.2、选择蒙皮修改器,重新蒙皮;
6.1.3、点击蒙皮修改器下的骨骼 > 添加,添加原有的骨骼。
6.2、动画导出错乱
很容易让人以为是权重出问题了,但就我自己多个项目动画导出的经验来看,大部分出现在骨骼添加上。在3dmax及unity中,不添加根节点往往不影响动画执行,但导出到three.js,需要添加根节点。如果问题还存在,则仔细观察是哪个骨骼引起的,多余骨骼或缺少骨骼都可能引起动画错乱。

五、完整的源代码及相应组件
点击下载
main.js - 完整的源代码
tween.min.js - 动画类
OrbitControls.js - 视图控制器,旋转、移动、缩放场景,方便调试
audio.min.js - motion音频组件,解决自动播放音频问题
其余vr相关组件上文已有介绍

时间: 2024-10-04 05:10:51

龙之谷手游WebVR技术分享的相关文章

谈谈龙之谷手游兼容测试的一百个坑

一.项目背景 1. 高价值IP 龙之谷 ,一款优秀的端游移植到手游平台,凭借的丰富的游戏内容和优秀的游戏品质,公测首日便在畅销榜登顶,取得了巨大的成功.  游戏内容不仅继承了端游的内容,还根据手游操作方式以及平台特性进行了改进,使之更适合移动用户操作,界面分部也更加合理.  2.初期兼容性问题较多 龙之谷与其他游戏产品一样,版本初期暴露的兼容性问题很多,类似无法安装以及必现的CRASH等致命问题多次出现外,还存在着大量UI错位.资源加载异常.屏幕分辨率适应差等严重级别的兼容性问题.  二.定制测

药王谷手游理财模式系统源码开发搭建

药王谷理财游戏系统原件开发(杨小姐:136-027-9-9492 微电).药王谷游戏系统开发.药王谷理财拆分盘操作系统开发.药王谷理财APP药王谷游戏APP软件开发.药王谷理财项目APP专业定制开发 药王谷这是一款手游,画面还挺精巧的.炼丹师游戏330项目定制,炼丹师界面设计,药王谷理财游戏网页版开发. 1.简单注册之后,能够看到自己每天炼丹的状况以及收益,以及收成好友的金丹明细,还有就是整个系统的买卖的记载明细. 2.加金丹则把库房中剩余的金丹放到丹炉中去炼丹,能够进步自己的基数,也就是第二天

手游录像分享传播实战攻略——《钢琴大师》

2015年手游行业进入盛期,各种类型的手游争相涌现,竞争相当激烈,游戏的推广和运营方式变得尤其重要.对于手游来说,视频录制是一个崭新的推广方式,手游内置的视频社交更是未来的趋势. 钢琴大师是一款音乐节奏类游戏,内置了Mob旗下的ShareREC手游录制分享功能,近期全面更新了UI体验,无缝融合了视频分享功能,玩家可通过演奏完美钢琴曲,实现边弹边唱或你弹我唱的应用场景!目前,钢琴大师的玩家已经上传了7000多个视频,视频分享率超过30%,回流率更是达到了600%,用户活跃度和粘度明显得到了提升,也

ShareREC手游录制分享,正式开放下载!!!

ShareREC是Mob(原ShareSDK)推出的最完美手游录像解决方案,开发者只需3分钟快速集成,即可让游戏拥有强大的录制分享功能,同时后台还能提供完善的数据统计,可供实时了解播放量.分享量.下载量等数据. ShareREC是涵盖手游录制.分享.视频社区.推广.统计五大功能,旨在帮助开发者通过游戏视频录制传播,对用户手机上的社会关系链进行整合.交叉推广,帮助开发者解决增强玩家活跃度和获取新用户两大痛点,从而以更快速度.更低成本实现更高质量的手游应用推广. 轻轻松松4步骤,初始化 -> 开始录

手游开发者交流会议暨OGEngine新版发布

由OGEngine举办的第二次手游开发者交流会议以圆桌会议方式在深圳高新园举行.会议不仅吸引了手游开发者,也有海外支付商,国内外的手游发行商参加. 会议环绕三大主题展开讨论和交流.主题分别是:手游开发技术交流暨OGEngine新版本发布:国内手游支付和发行交流以及手游开发发行交流. 活动的开始是手游开发技术相关交流.在座的有来自游戏公司的技术人员,也有开发者群里的一些热心开发者.大家分享了下各自使用OGEngine开发手游的经历和经验:宜搜游戏部门的祝总分享了他们团队如何在短短的一个月内完成从选

手游云测试工具TestBird登陆韩国

欢迎来到unity学习.unity培训.unity企业培训教育专区,这里有很多U3D资源.U3D培训视频.U3D教程.U3D常见问题.U3D项目源码,[狗刨学习网]unity极致学院,致力于打造业内unity3d培训.学习第一品牌. [狗刨学习网]报道 / 中国移动游戏本土市场在经过2013-2014两年的爆发期之后逐渐饱和,在精品化日益明显的今天,已有许多游戏商开始向国际移动游戏市场进军,从海外引入变为本土精品的海外输出.然而,在诸多CP出海之时,时常会遇到一些"水土不服"的问题,除

日新进用户200W+,解密《龙之谷》手游背后的压测故事

2017年3月,腾讯正式于全平台上线了<龙之谷>手游,次日冲到了App Store畅销排行第二的位置,并维持到了现在.上线当日百度指数超过40万,微信游戏平台数据显示预约数780多万,而据内部人员透露当日新进用户200W+,这就是<龙之谷>手游在安卓平台上所取得的成绩. 较高的市场期待让腾讯测试团队对<龙之谷>手游的测试倾尽全力,面对"经典IP"和盛大游戏一贯口碑,腾讯测试团队对游戏服务器进行了严格的压力测试,上线后服务器稳定的表现也证明了测试团队的

游戏设备技术提升分析小7手游稳定性是否靠谱

经过了这么多年的发展,游戏行业依旧是很大的热门.因为游戏设备技术的不断提升,让人们对游戏有了更多需求.比如现在用户追求的游戏画面和操作方式,确实让很多游戏公司明确新的方向.而整个产业也是如此,会朝着这个大致的方向发力,让用户得到满足.所以在做游戏行业分析的时候,依旧觉得游戏行业还有很大的空缺.因为技术是推进游戏行业发展的关键因素,并且不会达到一个饱和的状态.虽然可能会出现技术维持的阶段,但是最终还是提升的一个过程.只有技术水平得到了提升,游戏行业就得继续努力.这就是市场附庸的一种关系,只要用户提

手游服务器开发技术详解

从事游戏服务器开发差不多两年时间,两年间参与了不少项目,学到了很多游戏服务器开发技术,参与过几个不同架构的服务器开发,就随便聊聊游戏服务器开发需要的技术.(以下所指游戏服务器更偏向于手游,因为我对端游和页游开发接触并不多) 一.聊聊服务器开发有哪些东西要考虑. 1.开发语言的选择: 工欲善其事,必先利其器,选择一门适合的开发语法对后期开发有着事半功倍的作用. 业界主要的是c/c++ + Python/lua模式做游戏服务器.c/c++做网络通讯数据传输,python/lua做业务逻辑.这样既保持