转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53939303

本文出自【DylanAndroid的博客】


【Android开发VR实战】三.开发一个寻宝类VR游戏TreasureHunt

VR即Virtual Reality虚拟现实。虚拟现实技术是一种能够创建和体验虚拟世界的计算机仿真系统它利用计算机生成一种模拟环境是一种多源信息融合的交互式的三维动态视景和实体行为的系统仿真使用户沉浸到该环境中。

那么,怎样在Android中去开发VR功能的APP呢?我们利用谷歌提供的开源SDK去实现一个360°全景游戏的功能。

接下来主要是针对谷歌提供的开发VR的SDK中的游戏样例进行翻译。

CardBoard:卡纸板,google早期推出的VR 开发集合,封装改动了Activity。GLSurfaceView 以及 Render等标准类的一层API,当中详细仔细的实现封在so库中。用户使用CardBoard提供的jar包以及so,依照API的规则使用OPENGL实现特定函数就可以开发VR程序

DayDream:白日梦,在CardBoard基础上的专业版,实现了很多其它的VR特性功能,如3D音效,全景视图,全景视频播放。控制器,封装的API和so也对应的增多。API更加有结构模块化。

TreasureHunt游戏场景包含一个平面接地网格和一个浮动 “宝藏”多维数据集。

当用户观看立方体时,立方体将变成金色。 用户能够直接激活Cardboard触发器在其Cardboard查看器上使用触摸触发器,或使用白日梦基于控制器的触发器仿真。 然后激活触发器,点击寻找宝藏,宝藏消失后随机又一次定位立方体。

一.在build.gradle中引入谷歌VR的SDK依赖

  1. compile 'com.google.vr:sdk-audio:1.10.0'
  2. compile 'com.google.vr:sdk-base:1.10.0'

二.注意支持的最小SDK

  1. minSdkVersion 19
  2. targetSdkVersion 25

三.界面布局文件

  1. <?
  2. xml version="1.0" encoding="utf-8"?>
  3. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:id="@+id/ui_layout"
  5. android:orientation="vertical"
  6. android:layout_width="fill_parent"
  7. android:layout_height="fill_parent" >
  8. <com.google.vr.sdk.base.GvrView
  9. android:id="@+id/gvr_view"
  10. android:layout_width="fill_parent"
  11. android:layout_height="fill_parent"
  12. android:layout_alignParentTop="true"
  13. android:layout_alignParentLeft="true" />
  14. </RelativeLayout>

四.绘制TreasureHunt的VR游戏界面代码


  1. /**
  2. * 将视图设置为我们的GvrView并初始化我们将用于渲染我们的场景的转换矩阵。
  3. */
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. initializeGvrView();
  8. modelCube = new float[16];
  9. camera = new float[16];
  10. view = new float[16];
  11. modelViewProjection = new float[16];
  12. modelView = new float[16];
  13. modelFloor = new float[16];
  14. tempPosition = new float[4];
  15. // Model first appears directly in front of user.
  16. modelPosition = new float[]{0.0f, 0.0f, -MAX_MODEL_DISTANCE / 2.0f};
  17. headRotation = new float[4];
  18. headView = new float[16];
  19. vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
  20. // Initialize 3D audio engine.
  21. gvrAudioEngine = new GvrAudioEngine(this, GvrAudioEngine.RenderingMode.BINAURAL_HIGH_QUALITY);
  22. }
  23. /**
  24. * 初始化VR显示界面
  25. */
  26. public void initializeGvrView() {
  27. setContentView(R.layout.common_ui);
  28. GvrView gvrView = (GvrView) findViewById(R.id.gvr_view);
  29. gvrView.setEGLConfigChooser(8, 8, 8, 8, 16, 8);
  30. /**设置渲染器**/
  31. gvrView.setRenderer(this);
  32. gvrView.setTransitionViewEnabled(true);
  33. /**使用Daydream耳机启用Cardboard触发反馈。
  34. * 这是一种使用现有Cardboard触发器API支持Daydream控制器输入进行基本交互的简单方法。**/
  35. gvrView.enableCardboardTriggerEmulation();
  36. if (gvrView.setAsyncReprojectionEnabled(true)) {
  37. /**异步投影,沉浸式,性能模式**/
  38. AndroidCompat.setSustainedPerformanceMode(this, true);
  39. }
  40. setGvrView(gvrView);
  41. }
  42. @Override
  43. public void onPause() {
  44. gvrAudioEngine.pause();
  45. super.onPause();
  46. }
  47. @Override
  48. public void onResume() {
  49. super.onResume();
  50. gvrAudioEngine.resume();
  51. }
  52. @Override
  53. public void onRendererShutdown() {
  54. Log.i(TAG, "onRendererShutdown");
  55. }
  56. @Override
  57. public void onSurfaceChanged(int width, int height) {
  58. Log.i(TAG, "onSurfaceChanged");
  59. }
  60. /**
  61. * 创建用于存储有关3D界面的信息的缓冲区。
  62. * <p>
  63. * <p>OpenGL不使用Java数组。而是须要能够理解的格式的数据。
  64. 因此我们使用ByteBuffers。
  65. *
  66. * @param config The EGL configuration used when creating the surface.
  67. */
  68. @Override
  69. public void onSurfaceCreated(EGLConfig config) {
  70. Log.i(TAG, "onSurfaceCreated");
  71. GLES20.glClearColor(0.1f, 0.1f, 0.1f, 0.5f); // Dark background so text shows up well.
  72. ByteBuffer bbVertices = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_COORDS.length * 4);
  73. bbVertices.order(ByteOrder.nativeOrder());
  74. cubeVertices = bbVertices.asFloatBuffer();
  75. cubeVertices.put(WorldLayoutData.CUBE_COORDS);
  76. cubeVertices.position(0);
  77. ByteBuffer bbColors = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_COLORS.length * 4);
  78. bbColors.order(ByteOrder.nativeOrder());
  79. cubeColors = bbColors.asFloatBuffer();
  80. cubeColors.put(WorldLayoutData.CUBE_COLORS);
  81. cubeColors.position(0);
  82. ByteBuffer bbFoundColors =
  83. ByteBuffer.allocateDirect(WorldLayoutData.CUBE_FOUND_COLORS.length * 4);
  84. bbFoundColors.order(ByteOrder.nativeOrder());
  85. cubeFoundColors = bbFoundColors.asFloatBuffer();
  86. cubeFoundColors.put(WorldLayoutData.CUBE_FOUND_COLORS);
  87. cubeFoundColors.position(0);
  88. ByteBuffer bbNormals = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_NORMALS.length * 4);
  89. bbNormals.order(ByteOrder.nativeOrder());
  90. cubeNormals = bbNormals.asFloatBuffer();
  91. cubeNormals.put(WorldLayoutData.CUBE_NORMALS);
  92. cubeNormals.position(0);
  93. // make a floor
  94. ByteBuffer bbFloorVertices = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_COORDS.length * 4);
  95. bbFloorVertices.order(ByteOrder.nativeOrder());
  96. floorVertices = bbFloorVertices.asFloatBuffer();
  97. floorVertices.put(WorldLayoutData.FLOOR_COORDS);
  98. floorVertices.position(0);
  99. ByteBuffer bbFloorNormals = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_NORMALS.length * 4);
  100. bbFloorNormals.order(ByteOrder.nativeOrder());
  101. floorNormals = bbFloorNormals.asFloatBuffer();
  102. floorNormals.put(WorldLayoutData.FLOOR_NORMALS);
  103. floorNormals.position(0);
  104. ByteBuffer bbFloorColors = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_COLORS.length * 4);
  105. bbFloorColors.order(ByteOrder.nativeOrder());
  106. floorColors = bbFloorColors.asFloatBuffer();
  107. floorColors.put(WorldLayoutData.FLOOR_COLORS);
  108. floorColors.position(0);
  109. int vertexShader = loadGLShader(GLES20.GL_VERTEX_SHADER, R.raw.light_vertex);
  110. int gridShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, R.raw.grid_fragment);
  111. int passthroughShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, R.raw.passthrough_fragment);
  112. cubeProgram = GLES20.glCreateProgram();
  113. GLES20.glAttachShader(cubeProgram, vertexShader);
  114. GLES20.glAttachShader(cubeProgram, passthroughShader);
  115. GLES20.glLinkProgram(cubeProgram);
  116. GLES20.glUseProgram(cubeProgram);
  117. checkGLError("Cube program");
  118. cubePositionParam = GLES20.glGetAttribLocation(cubeProgram, "a_Position");
  119. cubeNormalParam = GLES20.glGetAttribLocation(cubeProgram, "a_Normal");
  120. cubeColorParam = GLES20.glGetAttribLocation(cubeProgram, "a_Color");
  121. cubeModelParam = GLES20.glGetUniformLocation(cubeProgram, "u_Model");
  122. cubeModelViewParam = GLES20.glGetUniformLocation(cubeProgram, "u_MVMatrix");
  123. cubeModelViewProjectionParam = GLES20.glGetUniformLocation(cubeProgram, "u_MVP");
  124. cubeLightPosParam = GLES20.glGetUniformLocation(cubeProgram, "u_LightPos");
  125. checkGLError("Cube program params");
  126. floorProgram = GLES20.glCreateProgram();
  127. GLES20.glAttachShader(floorProgram, vertexShader);
  128. GLES20.glAttachShader(floorProgram, gridShader);
  129. GLES20.glLinkProgram(floorProgram);
  130. GLES20.glUseProgram(floorProgram);
  131. checkGLError("Floor program");
  132. floorModelParam = GLES20.glGetUniformLocation(floorProgram, "u_Model");
  133. floorModelViewParam = GLES20.glGetUniformLocation(floorProgram, "u_MVMatrix");
  134. floorModelViewProjectionParam = GLES20.glGetUniformLocation(floorProgram, "u_MVP");
  135. floorLightPosParam = GLES20.glGetUniformLocation(floorProgram, "u_LightPos");
  136. floorPositionParam = GLES20.glGetAttribLocation(floorProgram, "a_Position");
  137. floorNormalParam = GLES20.glGetAttribLocation(floorProgram, "a_Normal");
  138. floorColorParam = GLES20.glGetAttribLocation(floorProgram, "a_Color");
  139. checkGLError("Floor program params");
  140. Matrix.setIdentityM(modelFloor, 0);
  141. Matrix.translateM(modelFloor, 0, 0, -floorDepth, 0); // Floor appears below user.
  142. // Avoid any delays during start-up due to decoding of sound files.
  143. new Thread(
  144. new Runnable() {
  145. @Override
  146. public void run() {
  147. // Start spatial audio playback of OBJECT_SOUND_FILE at the model position. The
  148. // returned sourceId handle is stored and allows for repositioning the sound object
  149. // whenever the cube position changes.
  150. gvrAudioEngine.preloadSoundFile(OBJECT_SOUND_FILE);
  151. sourceId = gvrAudioEngine.createSoundObject(OBJECT_SOUND_FILE);
  152. gvrAudioEngine.setSoundObjectPosition(
  153. sourceId, modelPosition[0], modelPosition[1], modelPosition[2]);
  154. gvrAudioEngine.playSound(sourceId, true /* looped playback */);
  155. // Preload an unspatialized sound to be played on a successful trigger on the cube.
  156. gvrAudioEngine.preloadSoundFile(SUCCESS_SOUND_FILE);
  157. }
  158. })
  159. .start();
  160. updateModelPosition();
  161. checkGLError("onSurfaceCreated");
  162. }
  163. /**
  164. * 将保存为资源的原始文本文件转换为OpenGL ES着色器。
  165. *
  166. * @param type The type of shader we will be creating.
  167. * @param resId The resource ID of the raw text file about to be turned into a shader.
  168. * @return The shader object handler.
  169. */
  170. private int loadGLShader(int type, int resId) {
  171. String code = readRawTextFile(resId);
  172. int shader = GLES20.glCreateShader(type);
  173. GLES20.glShaderSource(shader, code);
  174. GLES20.glCompileShader(shader);
  175. // Get the compilation status.
  176. final int[] compileStatus = new int[1];
  177. GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
  178. // If the compilation failed, delete the shader.
  179. if (compileStatus[0] == 0) {
  180. Log.e(TAG, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader));
  181. GLES20.glDeleteShader(shader);
  182. shader = 0;
  183. }
  184. if (shader == 0) {
  185. throw new RuntimeException("Error creating shader.");
  186. }
  187. return shader;
  188. }
  189. /**
  190. * 检查我们在OpenGL ES中是否有错误,假设有错误查看错误。
  191. *
  192. * @param label Label to report in case of error.
  193. */
  194. private static void checkGLError(String label) {
  195. int error;
  196. while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
  197. Log.e(TAG, label + ": glError " + error);
  198. throw new RuntimeException(label + ": glError " + error);
  199. }
  200. }
  201. /**
  202. * 更新立方体的位置。
  203. */
  204. protected void updateModelPosition() {
  205. Matrix.setIdentityM(modelCube, 0);
  206. Matrix.translateM(modelCube, 0, modelPosition[0], modelPosition[1], modelPosition[2]);
  207. // Update the sound location to match it with the new cube position.
  208. if (sourceId != GvrAudioEngine.INVALID_ID) {
  209. gvrAudioEngine.setSoundObjectPosition(
  210. sourceId, modelPosition[0], modelPosition[1], modelPosition[2]);
  211. }
  212. checkGLError("updateCubePosition");
  213. }
  214. /**
  215. * 将原始文本文件转换为字符串。
  216. *
  217. * @param resId 要转换为着色器的原始文本文件的资源ID。
  218. * @return 文本文件的上下文。或者在出现错误的情况下为null。
  219. */
  220. private String readRawTextFile(int resId) {
  221. InputStream inputStream = getResources().openRawResource(resId);
  222. try {
  223. BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  224. StringBuilder sb = new StringBuilder();
  225. String line;
  226. while ((line = reader.readLine()) != null) {
  227. sb.append(line).append("\n");
  228. }
  229. reader.close();
  230. return sb.toString();
  231. } catch (IOException e) {
  232. e.printStackTrace();
  233. }
  234. return null;
  235. }
  236. /**
  237. * 在绘制视图之前准备OpenGL ES。
  238. *
  239. * @param headTransform 新帧中的头变换。
  240. */
  241. @Override
  242. public void onNewFrame(HeadTransform headTransform) {
  243. setCubeRotation();
  244. // Build the camera matrix and apply it to the ModelView.
  245. Matrix.setLookAtM(camera, 0, 0.0f, 0.0f, CAMERA_Z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
  246. headTransform.getHeadView(headView, 0);
  247. // Update the 3d audio engine with the most recent head rotation.
  248. headTransform.getQuaternion(headRotation, 0);
  249. gvrAudioEngine.setHeadRotation(
  250. headRotation[0], headRotation[1], headRotation[2], headRotation[3]);
  251. // Regular update call to GVR audio engine.
  252. gvrAudioEngine.update();
  253. checkGLError("onReadyToDraw");
  254. }
  255. /**
  256. * 设置立方体旋转矩阵
  257. */
  258. protected void setCubeRotation() {
  259. Matrix.rotateM(modelCube, 0, TIME_DELTA, 0.5f, 0.5f, 1.0f);
  260. }
  261. /**
  262. * 为我们的视野画每一帧图。
  263. *
  264. * @param eye 视图呈现。 包含全部必需的转换。
  265. */
  266. @Override
  267. public void onDrawEye(Eye eye) {
  268. GLES20.glEnable(GLES20.GL_DEPTH_TEST);
  269. GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
  270. checkGLError("colorParam");
  271. // Apply the eye transformation to the camera.
  272. Matrix.multiplyMM(view, 0, eye.getEyeView(), 0, camera, 0);
  273. // Set the position of the light
  274. Matrix.multiplyMV(lightPosInEyeSpace, 0, view, 0, LIGHT_POS_IN_WORLD_SPACE, 0);
  275. // Build the ModelView and ModelViewProjection matrices
  276. // for calculating cube position and light.
  277. float[] perspective = eye.getPerspective(Z_NEAR, Z_FAR);
  278. Matrix.multiplyMM(modelView, 0, view, 0, modelCube, 0);
  279. Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);
  280. drawCube();
  281. // Set modelView for the floor, so we draw floor in the correct location
  282. Matrix.multiplyMM(modelView, 0, view, 0, modelFloor, 0);
  283. Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);
  284. drawFloor();
  285. }
  286. @Override
  287. public void onFinishFrame(Viewport viewport) {
  288. }
  289. /**
  290. * 绘制立方体。
  291. * <p>
  292. * <p>设置了全部的转换矩阵。
  293. 简单地将它们传递给着色器。
  294. */
  295. public void drawCube() {
  296. GLES20.glUseProgram(cubeProgram);
  297. GLES20.glUniform3fv(cubeLightPosParam, 1, lightPosInEyeSpace, 0);
  298. // Set the Model in the shader, used to calculate lighting
  299. GLES20.glUniformMatrix4fv(cubeModelParam, 1, false, modelCube, 0);
  300. // Set the ModelView in the shader, used to calculate lighting
  301. GLES20.glUniformMatrix4fv(cubeModelViewParam, 1, false, modelView, 0);
  302. // Set the position of the cube
  303. GLES20.glVertexAttribPointer(
  304. cubePositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, cubeVertices);
  305. // Set the ModelViewProjection matrix in the shader.
  306. GLES20.glUniformMatrix4fv(cubeModelViewProjectionParam, 1, false, modelViewProjection, 0);
  307. // Set the normal positions of the cube, again for shading
  308. GLES20.glVertexAttribPointer(cubeNormalParam, 3, GLES20.GL_FLOAT, false, 0, cubeNormals);
  309. GLES20.glVertexAttribPointer(cubeColorParam, 4, GLES20.GL_FLOAT, false, 0,
  310. isLookingAtObject() ? cubeFoundColors : cubeColors);
  311. // Enable vertex arrays
  312. GLES20.glEnableVertexAttribArray(cubePositionParam);
  313. GLES20.glEnableVertexAttribArray(cubeNormalParam);
  314. GLES20.glEnableVertexAttribArray(cubeColorParam);
  315. GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36);
  316. // Disable vertex arrays
  317. GLES20.glDisableVertexAttribArray(cubePositionParam);
  318. GLES20.glDisableVertexAttribArray(cubeNormalParam);
  319. GLES20.glDisableVertexAttribArray(cubeColorParam);
  320. checkGLError("Drawing cube");
  321. }
  322. /**
  323. * 画地板。
  324. * <p>
  325. * <p>这将底层的数据馈入着色器。
  326. 注意。这不会输入关于灯的位置的数据,因此。假设我们重写我们的代码来绘制地板。照明可能
  327. * 看起来非常奇怪。
  328. */
  329. public void drawFloor() {
  330. GLES20.glUseProgram(floorProgram);
  331. // Set ModelView, MVP, position, normals, and color.
  332. GLES20.glUniform3fv(floorLightPosParam, 1, lightPosInEyeSpace, 0);
  333. GLES20.glUniformMatrix4fv(floorModelParam, 1, false, modelFloor, 0);
  334. GLES20.glUniformMatrix4fv(floorModelViewParam, 1, false, modelView, 0);
  335. GLES20.glUniformMatrix4fv(floorModelViewProjectionParam, 1, false, modelViewProjection, 0);
  336. GLES20.glVertexAttribPointer(
  337. floorPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, floorVertices);
  338. GLES20.glVertexAttribPointer(floorNormalParam, 3, GLES20.GL_FLOAT, false, 0, floorNormals);
  339. GLES20.glVertexAttribPointer(floorColorParam, 4, GLES20.GL_FLOAT, false, 0, floorColors);
  340. GLES20.glEnableVertexAttribArray(floorPositionParam);
  341. GLES20.glEnableVertexAttribArray(floorNormalParam);
  342. GLES20.glEnableVertexAttribArray(floorColorParam);
  343. GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 24);
  344. GLES20.glDisableVertexAttribArray(floorPositionParam);
  345. GLES20.glDisableVertexAttribArray(floorNormalParam);
  346. GLES20.glDisableVertexAttribArray(floorColorParam);
  347. checkGLError("drawing floor");
  348. }
  349. /**
  350. * 当点击或拉动Cardboard触发器时调用。
  351. */
  352. @Override
  353. public void onCardboardTrigger() {
  354. Log.i(TAG, "onCardboardTrigger");
  355. if (isLookingAtObject()) {
  356. successSourceId = gvrAudioEngine.createStereoSound(SUCCESS_SOUND_FILE);
  357. gvrAudioEngine.playSound(successSourceId, false /* looping disabled */);
  358. hideObject();
  359. }
  360. // Always give user feedback.
  361. vibrator.vibrate(50);
  362. }
  363. /**
  364. * 方法作用:隐藏物体即为对象找到一个新的随机位置。
  365. * <p>
  366. * 方法说明:我们将环绕Y轴旋转它,使它看不见,然后向上或向下一点点。
  367. */
  368. protected void hideObject() {
  369. float[] rotationMatrix = new float[16];
  370. float[] posVec = new float[4];
  371. // First rotate in XZ plane, between 90 and 270 deg away, and scale so that we vary
  372. // the object's distance from the user.
  373. float angleXZ = (float) Math.random() * 180 + 90;
  374. Matrix.setRotateM(rotationMatrix, 0, angleXZ, 0f, 1f, 0f);
  375. float oldObjectDistance = objectDistance;
  376. objectDistance =
  377. (float) Math.random() * (MAX_MODEL_DISTANCE - MIN_MODEL_DISTANCE) + MIN_MODEL_DISTANCE;
  378. float objectScalingFactor = objectDistance / oldObjectDistance;
  379. Matrix.scaleM(rotationMatrix, 0, objectScalingFactor, objectScalingFactor, objectScalingFactor);
  380. Matrix.multiplyMV(posVec, 0, rotationMatrix, 0, modelCube, 12);
  381. float angleY = (float) Math.random() * 80 - 40; // Angle in Y plane, between -40 and 40.
  382. angleY = (float) Math.toRadians(angleY);
  383. float newY = (float) Math.tan(angleY) * objectDistance;
  384. modelPosition[0] = posVec[0];
  385. modelPosition[1] = newY;
  386. modelPosition[2] = posVec[2];
  387. updateModelPosition();
  388. }
  389. /**
  390. * 通过计算对象在眼睛空间中的位置来检查用户是否正在查看对象。
  391. *
  392. * @return 假设用户正在查看对象。则为true。
  393. */
  394. private boolean isLookingAtObject() {
  395. // Convert object space to camera space. Use the headView from onNewFrame.
  396. Matrix.multiplyMM(modelView, 0, headView, 0, modelCube, 0);
  397. Matrix.multiplyMV(tempPosition, 0, modelView, 0, POS_MATRIX_MULTIPLY_VEC, 0);
  398. float pitch = (float) Math.atan2(tempPosition[1], -tempPosition[2]);
  399. float yaw = (float) Math.atan2(tempPosition[0], -tempPosition[2]);
  400. return Math.abs(pitch) < PITCH_LIMIT && Math.abs(yaw) < YAW_LIMIT;
  401. }

五.GitHub

【Android开发VR实战】三.开发一个寻宝类VR游戏TreasureHunt的更多相关文章

  1. Cocos2dx游戏开发系列笔记13:一个横版拳击游戏Demo完结篇

    懒骨头(http://blog.csdn.net/iamlazybone QQ:124774397 ) 写下这些东西的同时 旁边放了两部电影 周星驰的<还魂夜> 甄子丹的<特殊身份& ...

  2. 《ASP.NET Core应用开发入门教程》与《ASP.NET Core 应用开发项目实战》正式出版

    “全书之写印,实系初稿.有时公私琐务猬集,每写一句,三搁其笔:有时兴会淋漓,走笔疾书,絮絮不休:有时意趣萧索,执笔木坐,草草而止.每写一段,自助覆阅,辄摇其首,觉有大不妥者,即贴补重书,故剪刀浆糊乃不 ...

  3. Unity 游戏开发技巧集锦之制作一个望远镜与查看器摄像机

    Unity 游戏开发技巧集锦之制作一个望远镜与查看器摄像机 Unity中制作一个望远镜 本节制作的望远镜,在鼠标左键按下时,看到的视图会变大:当不再按下的时候,会慢慢缩小成原来的视图.游戏中时常出现的 ...

  4. 【C语言探索之旅】 第一部分第八课:第一个C语言小游戏

    ​ 内容简介 1.课程大纲 2.第一部分第八课:第一个C语言小游戏 3.第一部分第九课预告: 函数 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写 ...

  5. 【Android开发VR实战】二.播放360&#176;全景视频

    转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53924006 本文出自[DylanAndroid的博客] [Android开发 ...

  6. 浅析Android Camera开发中的三个尺寸和三种变形 (贡献一个自适配Picturesize和Previewsize的工具类)

    转至 (http://blog.csdn.net/yanzi1225627/article/details/17652643) 经常听人问Camera开发中,各种变形问题,今天有空就在此梳理总结下. ...

  7. Android快乐贪吃蛇游戏实战项目开发教程-01项目概述与目录

    一.项目简介 贪吃蛇是一个很经典的游戏,也很适合用来学习.本教程将和大家一起做一个Android版的贪吃蛇游戏. 我已经将做好的案例上传到了应用宝,无病毒.无广告,大家可以放心下载下来把玩一下.应用宝 ...

  8. android游戏物理引擎开发——粒子系统(三)

    生病了,医院躺了几天,动了个小手术,动手术之后的几天在医院看了几本<大众软件>,又想到自己必须得买台台式机了,这破笔记本实在用不下去了,然后开始喜欢看些硬件的东西,等我熟悉了以后,写几个硬 ...

  9. 玩转Android之MVVM开发模式实战,炫酷的DataBinding!

    C# 很早就有了MVVM的开发模式,Android手机中的MVVM一直到去年Google的I\O大会上才推出,姗姗来迟.MVVM这中开发模式的优点自不必多说,可以实现视图和逻辑代码的解耦,而且,按照G ...

随机推荐

  1. [laravel]用户异地登录后踢掉之前的登录

    不同用户和服务器之间由一个唯一的session来区分,但是一般情况下不同的session对应的用户model可以是同一个. 为了实现只能同时在一个地方登陆,可以在用户的字段里增加一个last_sess ...

  2. (一)Python 学习第一天--基础知识,列表

    1. .pyc文件 .pyc文件:在python3中,当模块运行时会自动生成在_pycache_文件夹中,其中c为compiled的缩写. Python是一门现编译后解释的语言,在运行时首先寻找.py ...

  3. C# textbox 获得焦点

    this.ActiveControl = txt_core;

  4. 基于python xlsxwriter、xlrd 生成测试报告

    import xlsxwriter,xlrd ''' 思路: 1.获取数据 2.整合数据 3.写入文件 ''' #筛选 def filt(category,table,filt_name=None,r ...

  5. 取三级分销上下级用户id

    //取上三级的用户idpublic function _get_up_third_id($member_id){ $up_id=array(); $invite_id=dbselect('invite ...

  6. S-HR之OSF

    1):getWorkDayCount ->ArrayList data = (ArrayList) com.kingdee.shr.rpts.ctrlreport.osf.OSFExecutor ...

  7. 奇怪的print progname ":\n"日志

    [root@xxxxxxxx /home/ahao.mah] #tail /var/log/messages -f Feb 10 10:01:01 csaccurate-49-5011 } Feb 1 ...

  8. json 添加 和删除两种方法

    <script> var test = { name: "name", age: "12" }; var countrys = { "ne ...

  9. 使用官方组件下载图片,保存到MySQL数据库,保存到MongoDB数据库

    需要学习的地方,使用官方组件下载图片的用法,保存item到MySQL数据库 需要提前创建好MySQL数据库,根据item.py文件中的字段信息创建相应的数据表 1.items.py文件 from sc ...

  10. 《零压力学Python》 之 第二章知识点归纳

    第二章(数字)知识点归纳 要生成非常大的数字,最简单的办法是使用幂运算符,它由两个星号( ** )组成. 如: 在Python中,整数是绝对精确的,这意味着不管它多大,加上1后都将得到一个新的值.你将 ...