【Android开发VR实战】三.开发一个寻宝类VR游戏TreasureHunt
转载请注明出处: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依赖
compile 'com.google.vr:sdk-audio:1.10.0'compile 'com.google.vr:sdk-base:1.10.0'
- 1
- 2
- 1
- 2
二.注意支持的最小SDK
minSdkVersion 19targetSdkVersion 25
- 1
- 2
- 1
- 2
三.界面布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/ui_layout"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent" ><com.google.vr.sdk.base.GvrView
android:id="@+id/gvr_view"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layout_alignParentTop="true"android:layout_alignParentLeft="true" /></RelativeLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
四.绘制TreasureHunt的VR游戏界面代码
/*** 将视图设置为我们的GvrView并初始化我们将用于渲染我们的场景的转换矩阵。*/@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);initializeGvrView();modelCube = new float[16];camera = new float[16];view = new float[16];modelViewProjection = new float[16];modelView = new float[16];modelFloor = new float[16];tempPosition = new float[4];// Model first appears directly in front of user.modelPosition = new float[]{0.0f, 0.0f, -MAX_MODEL_DISTANCE / 2.0f};headRotation = new float[4];headView = new float[16];vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);// Initialize 3D audio engine.gvrAudioEngine = new GvrAudioEngine(this, GvrAudioEngine.RenderingMode.BINAURAL_HIGH_QUALITY);}/*** 初始化VR显示界面*/public void initializeGvrView() {setContentView(R.layout.common_ui);GvrView gvrView = (GvrView) findViewById(R.id.gvr_view);gvrView.setEGLConfigChooser(8, 8, 8, 8, 16, 8);/**设置渲染器**/gvrView.setRenderer(this);gvrView.setTransitionViewEnabled(true);/**使用Daydream耳机启用Cardboard触发反馈。* 这是一种使用现有Cardboard触发器API支持Daydream控制器输入进行基本交互的简单方法。**/gvrView.enableCardboardTriggerEmulation();if (gvrView.setAsyncReprojectionEnabled(true)) {/**异步投影,沉浸式,性能模式**/AndroidCompat.setSustainedPerformanceMode(this, true);}setGvrView(gvrView);}@Overridepublic void onPause() {gvrAudioEngine.pause();super.onPause();}@Overridepublic void onResume() {super.onResume();gvrAudioEngine.resume();}@Overridepublic void onRendererShutdown() {Log.i(TAG, "onRendererShutdown");}@Overridepublic void onSurfaceChanged(int width, int height) {Log.i(TAG, "onSurfaceChanged");}/*** 创建用于存储有关3D界面的信息的缓冲区。*
* OpenGL不使用Java数组,而是需要可以理解的格式的数据。 因此我们使用ByteBuffers。** @param config The EGL configuration used when creating the surface.*/@Overridepublic void onSurfaceCreated(EGLConfig config) {Log.i(TAG, "onSurfaceCreated");GLES20.glClearColor(0.1f, 0.1f, 0.1f, 0.5f); // Dark background so text shows up well.ByteBuffer bbVertices = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_COORDS.length * 4);bbVertices.order(ByteOrder.nativeOrder());cubeVertices = bbVertices.asFloatBuffer();cubeVertices.put(WorldLayoutData.CUBE_COORDS);cubeVertices.position(0);ByteBuffer bbColors = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_COLORS.length * 4);bbColors.order(ByteOrder.nativeOrder());cubeColors = bbColors.asFloatBuffer();cubeColors.put(WorldLayoutData.CUBE_COLORS);cubeColors.position(0);ByteBuffer bbFoundColors =ByteBuffer.allocateDirect(WorldLayoutData.CUBE_FOUND_COLORS.length * 4);bbFoundColors.order(ByteOrder.nativeOrder());cubeFoundColors = bbFoundColors.asFloatBuffer();cubeFoundColors.put(WorldLayoutData.CUBE_FOUND_COLORS);cubeFoundColors.position(0);ByteBuffer bbNormals = ByteBuffer.allocateDirect(WorldLayoutData.CUBE_NORMALS.length * 4);bbNormals.order(ByteOrder.nativeOrder());cubeNormals = bbNormals.asFloatBuffer();cubeNormals.put(WorldLayoutData.CUBE_NORMALS);cubeNormals.position(0);// make a floorByteBuffer bbFloorVertices = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_COORDS.length * 4);bbFloorVertices.order(ByteOrder.nativeOrder());floorVertices = bbFloorVertices.asFloatBuffer();floorVertices.put(WorldLayoutData.FLOOR_COORDS);floorVertices.position(0);ByteBuffer bbFloorNormals = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_NORMALS.length * 4);bbFloorNormals.order(ByteOrder.nativeOrder());floorNormals = bbFloorNormals.asFloatBuffer();floorNormals.put(WorldLayoutData.FLOOR_NORMALS);floorNormals.position(0);ByteBuffer bbFloorColors = ByteBuffer.allocateDirect(WorldLayoutData.FLOOR_COLORS.length * 4);bbFloorColors.order(ByteOrder.nativeOrder());floorColors = bbFloorColors.asFloatBuffer();floorColors.put(WorldLayoutData.FLOOR_COLORS);floorColors.position(0);int vertexShader = loadGLShader(GLES20.GL_VERTEX_SHADER, R.raw.light_vertex);int gridShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, R.raw.grid_fragment);int passthroughShader = loadGLShader(GLES20.GL_FRAGMENT_SHADER, R.raw.passthrough_fragment);cubeProgram = GLES20.glCreateProgram();GLES20.glAttachShader(cubeProgram, vertexShader);GLES20.glAttachShader(cubeProgram, passthroughShader);GLES20.glLinkProgram(cubeProgram);GLES20.glUseProgram(cubeProgram);checkGLError("Cube program");cubePositionParam = GLES20.glGetAttribLocation(cubeProgram, "a_Position");cubeNormalParam = GLES20.glGetAttribLocation(cubeProgram, "a_Normal");cubeColorParam = GLES20.glGetAttribLocation(cubeProgram, "a_Color");cubeModelParam = GLES20.glGetUniformLocation(cubeProgram, "u_Model");cubeModelViewParam = GLES20.glGetUniformLocation(cubeProgram, "u_MVMatrix");cubeModelViewProjectionParam = GLES20.glGetUniformLocation(cubeProgram, "u_MVP");cubeLightPosParam = GLES20.glGetUniformLocation(cubeProgram, "u_LightPos");checkGLError("Cube program params");floorProgram = GLES20.glCreateProgram();GLES20.glAttachShader(floorProgram, vertexShader);GLES20.glAttachShader(floorProgram, gridShader);GLES20.glLinkProgram(floorProgram);GLES20.glUseProgram(floorProgram);checkGLError("Floor program");floorModelParam = GLES20.glGetUniformLocation(floorProgram, "u_Model");floorModelViewParam = GLES20.glGetUniformLocation(floorProgram, "u_MVMatrix");floorModelViewProjectionParam = GLES20.glGetUniformLocation(floorProgram, "u_MVP");floorLightPosParam = GLES20.glGetUniformLocation(floorProgram, "u_LightPos");floorPositionParam = GLES20.glGetAttribLocation(floorProgram, "a_Position");floorNormalParam = GLES20.glGetAttribLocation(floorProgram, "a_Normal");floorColorParam = GLES20.glGetAttribLocation(floorProgram, "a_Color");checkGLError("Floor program params");Matrix.setIdentityM(modelFloor, 0);Matrix.translateM(modelFloor, 0, 0, -floorDepth, 0); // Floor appears below user.// Avoid any delays during start-up due to decoding of sound files.new Thread(new Runnable() {@Overridepublic void run() {// Start spatial audio playback of OBJECT_SOUND_FILE at the model position. The// returned sourceId handle is stored and allows for repositioning the sound object// whenever the cube position changes.gvrAudioEngine.preloadSoundFile(OBJECT_SOUND_FILE);sourceId = gvrAudioEngine.createSoundObject(OBJECT_SOUND_FILE);gvrAudioEngine.setSoundObjectPosition(sourceId, modelPosition[0], modelPosition[1], modelPosition[2]);gvrAudioEngine.playSound(sourceId, true /* looped playback */);// Preload an unspatialized sound to be played on a successful trigger on the cube.gvrAudioEngine.preloadSoundFile(SUCCESS_SOUND_FILE);}}).start();updateModelPosition();checkGLError("onSurfaceCreated");}/*** 将保存为资源的原始文本文件转换为OpenGL ES着色器。** @param type The type of shader we will be creating.* @param resId The resource ID of the raw text file about to be turned into a shader.* @return The shader object handler.*/private int loadGLShader(int type, int resId) {String code = readRawTextFile(resId);int shader = GLES20.glCreateShader(type);GLES20.glShaderSource(shader, code);GLES20.glCompileShader(shader);// Get the compilation status.final int[] compileStatus = new int[1];GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);// If the compilation failed, delete the shader.if (compileStatus[0] == 0) {Log.e(TAG, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader));GLES20.glDeleteShader(shader);shader = 0;}if (shader == 0) {throw new RuntimeException("Error creating shader.");}return shader;}/*** 检查我们在OpenGL ES中是否有错误,如果有错误查看错误。** @param label Label to report in case of error.*/private static void checkGLError(String label) {int error;while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {Log.e(TAG, label + ": glError " + error);throw new RuntimeException(label + ": glError " + error);}}/*** 更新立方体的位置。*/protected void updateModelPosition() {Matrix.setIdentityM(modelCube, 0);Matrix.translateM(modelCube, 0, modelPosition[0], modelPosition[1], modelPosition[2]);// Update the sound location to match it with the new cube position.if (sourceId != GvrAudioEngine.INVALID_ID) {gvrAudioEngine.setSoundObjectPosition(sourceId, modelPosition[0], modelPosition[1], modelPosition[2]);}checkGLError("updateCubePosition");}/*** 将原始文本文件转换为字符串。** @param resId 要转换为着色器的原始文本文件的资源ID。* @return 文本文件的上下文,或者在出现错误的情况下为null。*/private String readRawTextFile(int resId) {InputStream inputStream = getResources().openRawResource(resId);try {BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));StringBuilder sb = new StringBuilder();String line;while ((line = reader.readLine()) != null) {sb.append(line).append("\n");}reader.close();return sb.toString();} catch (IOException e) {e.printStackTrace();}return null;}/*** 在绘制视图之前准备OpenGL ES。** @param headTransform 新帧中的头变换。*/@Overridepublic void onNewFrame(HeadTransform headTransform) {setCubeRotation();// Build the camera matrix and apply it to the ModelView.Matrix.setLookAtM(camera, 0, 0.0f, 0.0f, CAMERA_Z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);headTransform.getHeadView(headView, 0);// Update the 3d audio engine with the most recent head rotation.headTransform.getQuaternion(headRotation, 0);gvrAudioEngine.setHeadRotation(headRotation[0], headRotation[1], headRotation[2], headRotation[3]);// Regular update call to GVR audio engine.gvrAudioEngine.update();checkGLError("onReadyToDraw");}/*** 设置立方体旋转矩阵*/protected void setCubeRotation() {Matrix.rotateM(modelCube, 0, TIME_DELTA, 0.5f, 0.5f, 1.0f);}/*** 为我们的视野画每一帧图。** @param eye 视图呈现。 包括所有必需的转换。*/@Overridepublic void onDrawEye(Eye eye) {GLES20.glEnable(GLES20.GL_DEPTH_TEST);GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);checkGLError("colorParam");// Apply the eye transformation to the camera.Matrix.multiplyMM(view, 0, eye.getEyeView(), 0, camera, 0);// Set the position of the lightMatrix.multiplyMV(lightPosInEyeSpace, 0, view, 0, LIGHT_POS_IN_WORLD_SPACE, 0);// Build the ModelView and ModelViewProjection matrices// for calculating cube position and light.float[] perspective = eye.getPerspective(Z_NEAR, Z_FAR);Matrix.multiplyMM(modelView, 0, view, 0, modelCube, 0);Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);drawCube();// Set modelView for the floor, so we draw floor in the correct locationMatrix.multiplyMM(modelView, 0, view, 0, modelFloor, 0);Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);drawFloor();}@Overridepublic void onFinishFrame(Viewport viewport) {}/*** 绘制立方体。* * 设置了所有的转换矩阵。简单地将它们传递给着色器。*/public void drawCube() {GLES20.glUseProgram(cubeProgram);GLES20.glUniform3fv(cubeLightPosParam, 1, lightPosInEyeSpace, 0);// Set the Model in the shader, used to calculate lightingGLES20.glUniformMatrix4fv(cubeModelParam, 1, false, modelCube, 0);// Set the ModelView in the shader, used to calculate lightingGLES20.glUniformMatrix4fv(cubeModelViewParam, 1, false, modelView, 0);// Set the position of the cubeGLES20.glVertexAttribPointer(cubePositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, cubeVertices);// Set the ModelViewProjection matrix in the shader.GLES20.glUniformMatrix4fv(cubeModelViewProjectionParam, 1, false, modelViewProjection, 0);// Set the normal positions of the cube, again for shadingGLES20.glVertexAttribPointer(cubeNormalParam, 3, GLES20.GL_FLOAT, false, 0, cubeNormals);GLES20.glVertexAttribPointer(cubeColorParam, 4, GLES20.GL_FLOAT, false, 0,isLookingAtObject() ? cubeFoundColors : cubeColors);// Enable vertex arraysGLES20.glEnableVertexAttribArray(cubePositionParam);GLES20.glEnableVertexAttribArray(cubeNormalParam);GLES20.glEnableVertexAttribArray(cubeColorParam);GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36);// Disable vertex arraysGLES20.glDisableVertexAttribArray(cubePositionParam);GLES20.glDisableVertexAttribArray(cubeNormalParam);GLES20.glDisableVertexAttribArray(cubeColorParam);checkGLError("Drawing cube");}/*** 画地板。* * 这将底层的数据馈入着色器。 注意,这不会输入关于灯的位置的数据,因此,如果我们重写我们的代码来绘制地板,照明可能* 看起来很奇怪。*/public void drawFloor() {GLES20.glUseProgram(floorProgram);// Set ModelView, MVP, position, normals, and color.GLES20.glUniform3fv(floorLightPosParam, 1, lightPosInEyeSpace, 0);GLES20.glUniformMatrix4fv(floorModelParam, 1, false, modelFloor, 0);GLES20.glUniformMatrix4fv(floorModelViewParam, 1, false, modelView, 0);GLES20.glUniformMatrix4fv(floorModelViewProjectionParam, 1, false, modelViewProjection, 0);GLES20.glVertexAttribPointer(floorPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, floorVertices);GLES20.glVertexAttribPointer(floorNormalParam, 3, GLES20.GL_FLOAT, false, 0, floorNormals);GLES20.glVertexAttribPointer(floorColorParam, 4, GLES20.GL_FLOAT, false, 0, floorColors);GLES20.glEnableVertexAttribArray(floorPositionParam);GLES20.glEnableVertexAttribArray(floorNormalParam);GLES20.glEnableVertexAttribArray(floorColorParam);GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 24);GLES20.glDisableVertexAttribArray(floorPositionParam);GLES20.glDisableVertexAttribArray(floorNormalParam);GLES20.glDisableVertexAttribArray(floorColorParam);checkGLError("drawing floor");}/*** 当点击或拉动Cardboard触发器时调用。*/@Overridepublic void onCardboardTrigger() {Log.i(TAG, "onCardboardTrigger");if (isLookingAtObject()) {successSourceId = gvrAudioEngine.createStereoSound(SUCCESS_SOUND_FILE);gvrAudioEngine.playSound(successSourceId, false /* looping disabled */);hideObject();}// Always give user feedback.vibrator.vibrate(50);}/*** 方法作用:隐藏物体即为对象找到一个新的随机位置。* * 方法说明:我们将围绕Y轴旋转它,使它看不见,然后向上或向下一点点。*/protected void hideObject() {float[] rotationMatrix = new float[16];float[] posVec = new float[4];// First rotate in XZ plane, between 90 and 270 deg away, and scale so that we vary// the object's distance from the user.float angleXZ = (float) Math.random() * 180 + 90;Matrix.setRotateM(rotationMatrix, 0, angleXZ, 0f, 1f, 0f);float oldObjectDistance = objectDistance;objectDistance =(float) Math.random() * (MAX_MODEL_DISTANCE - MIN_MODEL_DISTANCE) + MIN_MODEL_DISTANCE;float objectScalingFactor = objectDistance / oldObjectDistance;Matrix.scaleM(rotationMatrix, 0, objectScalingFactor, objectScalingFactor, objectScalingFactor);Matrix.multiplyMV(posVec, 0, rotationMatrix, 0, modelCube, 12);float angleY = (float) Math.random() * 80 - 40; // Angle in Y plane, between -40 and 40.angleY = (float) Math.toRadians(angleY);float newY = (float) Math.tan(angleY) * objectDistance;modelPosition[0] = posVec[0];modelPosition[1] = newY;modelPosition[2] = posVec[2];updateModelPosition();}/*** 通过计算对象在眼睛空间中的位置来检查用户是否正在查看对象。** @return 如果用户正在查看对象,则为true。*/private boolean isLookingAtObject() {// Convert object space to camera space. Use the headView from onNewFrame.Matrix.multiplyMM(modelView, 0, headView, 0, modelCube, 0);Matrix.multiplyMV(tempPosition, 0, modelView, 0, POS_MATRIX_MULTIPLY_VEC, 0);float pitch = (float) Math.atan2(tempPosition[1], -tempPosition[2]);float yaw = (float) Math.atan2(tempPosition[0], -tempPosition[2]);return Math.abs(pitch) < PITCH_LIMIT && Math.abs(yaw) < YAW_LIMIT;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
五.GitHub
【Android开发VR实战】三.开发一个寻宝类VR游戏TreasureHunt相关推荐
- Cocos2dx游戏开发系列笔记7:一个简单的跑酷游戏《萝莉快跑》的消化(附下载)
懒骨头(http://blog.csdn.net/iamlazybone QQ124774397 青岛 ) 或许有天 我们羡慕和崇拜的人 因为我们的努力 也会来了解我们 说不定 还会成为好友 骨头喜 ...
- Cocos2dx游戏开发系列笔记7:一个简单的跑酷游戏《萝莉快跑》的消化(附下载)...
2019独角兽企业重金招聘Python工程师标准>>> 或许有天 我们羡慕和崇拜的人 因为我们的努力 也会来了解我们 说不定 还会成为好友 骨头喜欢这样与哲哲共勉 多少个夜晚 一张长 ...
- android studio的GearVR应用开发(二)、一个简单的VR app(Oculus官方GearVR开发教程,翻译转载)
声明:本文是Oculus官方的GearVR开发教程,为本人翻译转载,供广大VR开发爱好者一同学习进步使用. 原文章 一个简单的VR app 概观 在搭建好GearVR框架后,让我们一起来创建第一个VR ...
- NB-IOT开发|nbiot开发教程《三》AT指令类模组驱动-STM32实现AT指令状态机
嵌入式开发中我们要时刻保持代码的高效与整洁看之前,先点赞 好习惯,要养成 一.前言 嵌入式开发中我们要时刻保持代码的高效与整洁.在第一节中"NB-IOT开发|nbiot开发教程<一&g ...
- 先进技术android,React Native实战(JavaScript开发iOS和Android应用)/计算机科学先进技术译丛...
导语 内容提要 本书作者Nader Dabit是AWS Mobile开发人员.React Native Training创始人和React Native Radio播客主持人.本书旨在帮助iOS.An ...
- Android开发笔记(三十七)按钮类控件
Button与ImageButton Button是文本按钮(继承自TextView),而ImageButton是图像按钮(继承自ImageView).两者之间的区别在于: 1.Button即可显示文 ...
- 【cocos2d游戏开发实战】一款射击类小游戏《Zombie Age》的开发(一)
前言 因为学习的原因需要完成一款射击类的小游戏,之前做的飞机大战没有达到要求,在网上找了几款射击类游戏后因为素材和难度的关系最后决定山寨一下<僵尸时代2>这款小游戏,开发到游戏主体基本完整 ...
- android 弹出菜单环形,『Android自定义View实战』实现一个小清新的弹出式圆环菜单...
前言 Android表现快捷菜单的形式有很多种,比如使用PopupWindow弹出来的小弹窗,类似QQ的侧拉功能菜单,以及之前讲过的弧形菜单( Android 自定义弧形旋转菜单栏--卫星菜单),这次 ...
- 【python实战】使用 pygame 写一个 flappy-bird 类小游戏 | 涉及思路+项目结构+代码详解 | 新手向
基于 pygame 的 Amazing-brick 实现 本文涉及三个 .py 文件: amazing_brick / amazing_brick_utils.py/ wrapped_amazing_ ...
最新文章
- asyncdata 获取参数_载入页面初始数据(asyncData)《 Nuxt.js:异步数据 》
- 浅析Java中的final关键字
- DOM0,DOM2,DOM3事件,事件基础知识入门
- 如何删除Struts2动作的后缀扩展名
- 【原】相煎何太急——input的blur事件与button的click事件
- C语言模拟实现标准库函数之qsort()
- 来了!云栖大会都能看到什么?
- leetcode python3 简单题169. Majority Element
- python 输出文件中返回码为200的接口的平均响应时间_python-处理日志文件,找出各个接口状态码为 200时的平均响应时间...
- 博微JAVA面试_博微Java笔试题
- 注册表禁用计算机管理,解除封锁注册表被禁用的几种解决办法
- 大厂Android相关岗位校招职位要求汇总
- Python3 etree, requests库抓取bt
- 模拟电路4(三极管的特性曲线和重要参数)
- instant java,关于java:Format Instant to String
- android anr 分析方法,Android ANR分析
- 大神F1 Plus和中兴V5s哪个好
- java 基础知识(不定期更新)
- 汇承金融科技:开创多渠道资产与资金撮合融资服务模式
- 儿童使用显微镜有好处吗?
热门文章
- 西米支付报告:2023年五大支付趋势,重点关注支付失败率
- C语言输出ABBBCCCCCDDDDDDDCCCCCBBBA
- 半年翻倍!内存条掀起涨价潮
- Guide to Data Mining 读书笔记 相似度距离算法的取舍
- 《Splunk智能运维实战》——2.2 使原始事件数据具备可读性
- 上交所技术 基于FPGA技术的FAST行情解码研究
- 硬件电路设计--运算放大器(一)参数和分类
- 率土之滨鸿蒙之初,率土之滨黑科技第5期:上阵姐妹花,S1赛季大小乔吊打大汉弓...
- 网游如何防外挂篡改数据——客户端内存加密
- iphone 异步加载图片