/* * SpriteGameGLField.java * * Copyright � 1998-2011 Research In Motion Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Note: For the sake of simplicity, this sample application may not leverage * resource bundles and resource strings. However, it is STRONGLY recommended * that application developers make use of the localization features available * within the BlackBerry development platform to ensure a seamless application * experience across a variety of languages and geographies. For more information * on localizing your application, please refer to the BlackBerry Java Development * Environment Development Guide associated with this release. */ package com.rim.samples.device.openglspritegamedemo; import javax.microedition.khronos.opengles.GL; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; import net.rim.device.api.animation.AbstractAnimation; import net.rim.device.api.animation.Animation; import net.rim.device.api.animation.AnimationListener; import net.rim.device.api.animation.Animator; import net.rim.device.api.math.Matrix4f; import net.rim.device.api.math.Transform3D; import net.rim.device.api.math.Vector3f; import net.rim.device.api.opengles.GL20; import net.rim.device.api.opengles.GLField; import net.rim.device.api.system.Characters; import net.rim.device.api.system.Display; import net.rim.device.api.ui.Keypad; import net.rim.device.api.ui.TouchEvent; import net.rim.device.api.ui.TouchGesture; import net.rim.device.api.util.TimeSource; /** * The GLField which renders the current level and handles the animations and * any keyboard or touch events. */ public class SpriteGameGLField extends GLField implements AnimationListener { /** Holds the game's current level */ private SpriteGameLevel _level; /** Holds the win sprite */ private final Sprite _win; /** Holds the win animation */ private final Animation _winAnimation; /** Holds whether or not the win sprite should be rendered */ private boolean _renderWin = false; /** Holds the lose sprite */ private final Sprite _lose; /** Holds the lose animation */ private final Animation _loseAnimation; /** Holds whether or not the lose sprite should be rendered */ private boolean _renderLose = false; /** Holds the modelview matrix. Specific to GL20 version */ private Matrix4f _modelview; /** Holds the projection matrix. Specific to GL20 version */ private Matrix4f _projection; /** Holds the current level */ private int _currentLevel = 0; /** Holds the version of GL being used */ private final int _glVersion; /** A translation vector */ Vector3f _t = new Vector3f(); /** Holds the animator used to update the obstacle animations */ private final Animator _animator; /** Holds whether the sprite should be moving left */ private boolean _moveLeft; /** Holds whether the sprite should be moving right */ private boolean _moveRight; /** Holds the list of levels */ private final String[] _levelNames = { "/levelone.xml", "/leveltwo.xml" }; /** Time source used for animator and to calculate elapsed time */ private final TimeSource _timeSource; /** Time value used in calculating elapsed time between updates */ private long _prevTime; /** * Constructor for the SpriteGameGLField * * @param glVersion * The version of OpenGL to use */ SpriteGameGLField(final int glVersion) { super(glVersion); _glVersion = glVersion; final Vector3f translation = new Vector3f(-40.0f, -4.0f, 0.0f); // Set up the win sprite _win = new Sprite("win.png"); _win._transform.setScale(new Vector3f(5.0f, 5.0f, 5.0f)); _win._transform.setTranslation(translation); // Set up the lose sprite _lose = new Sprite("lose.png"); _lose._transform.setScale(new Vector3f(5.0f, 5.0f, 5.0f)); _lose._transform.setTranslation(translation); _timeSource = new TimeSource(); _timeSource.start(); _animator = new Animator(0, _timeSource); // Set up the win animation _winAnimation = _animator.addAnimation(_win._transform, Transform3D.ANIMATION_PROPERTY_TRANSLATE, 4, new float[] { 0.0f, 0.2f, 0.8f, 1.0f }, 0, new float[] { -40.0f, -4.0f, 0.0f, 0.0f, -4.0f, 0.0f, 0.0f, -4.0f, 0.0f, 20.0f, -4.0f, 0.0f }, 0, Animation.EASINGCURVE_LINEAR, 1750L); _winAnimation.setRepeatCount(1); _winAnimation.setListener(this); // set up the lose animation _loseAnimation = _animator.addAnimation(_lose._transform, Transform3D.ANIMATION_PROPERTY_TRANSLATE, 4, new float[] { 0.0f, 0.2f, 0.8f, 1.0f }, 0, new float[] { -40.0f, -4.0f, 0.0f, 0.0f, -4.0f, 0.0f, 0.0f, -4.0f, 0.0f, 20.0f, -4.0f, 0.0f }, 0, Animation.EASINGCURVE_LINEAR, 1750L); _loseAnimation.setRepeatCount(1); _loseAnimation.setListener(this); loadLevel(); } /** * @see net.rim.device.api.ui.Field#layout(int, int) */ protected void layout(final int width, final int height) { setExtent(width, height); } /** * @see GLField#initialize(GL) */ protected void initialize(final GL gl) { // Determine what version of OpenGL we are using // and call the corresponding initialize() method. if (_glVersion == GLField.VERSION_1_1) { initialize((GL11) gl); } else { initialize((GL20) gl); } setTargetFrameRate(70); } /** * Initialize method for OpenGL v1.1 * * @param gl * The OpenGL 1.1 object that will be initialized */ private void initialize(final GL11 gl) { // Set up the GL11 object gl.glEnable(GL10.GL_TEXTURE_2D); gl.glClearColor(0.35f, 0.67f, 1.0f, 1.0f); gl.glEnable(GL10.GL_NORMALIZE); gl.glEnable(GL10.GL_CULL_FACE); gl.glEnable(GL10.GL_COLOR_MATERIAL); gl.glShadeModel(GL10.GL_SMOOTH); gl.glDisable(GL10.GL_DITHER); gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); gl.glEnable(GL10.GL_BLEND); gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); // Set up the orthographic (2D) projection matrix gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); gl.glOrthof(-12.0f, 12.0f, -10.0f, 20.0f * Display.getHeight() / Display.getWidth() - 10.0f, -1.0f, 1.0f); _win.initialize(gl); _lose.initialize(gl); } /** * Initialize method for OpenGL v2.0 * * @param gl * The OpenGL 2.0 object that will be initialized */ private void initialize(final GL20 gl) { _modelview = new Matrix4f(); _projection = new Matrix4f(); // Set up the GL20 object gl.glClearColor(0.35f, 0.67f, 1.0f, 1.0f); gl.glEnable(GL20.GL_CULL_FACE); gl.glDisable(GL20.GL_DITHER); gl.glEnable(GL20.GL_BLEND); gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); // Load the shader used for rendering Sprite.loadShader(gl); // Set up the orthographic (2D) projection matrix Matrix4f.createOrthographic(-12.0f, 12.0f, -10.0f, 20.0f * Display.getHeight() / Display.getWidth() - 10.0f, -1.0f, 10.0f, _projection); _win.initialize(gl); _lose.initialize(gl); } /** * @see GLField#update() */ protected void update() { _animator.update(); // Get amount of time last frame took, in seconds final float elapsedTime = (_timeSource.getTime() - _prevTime) / 1000.0f; _prevTime = _timeSource.getTime(); // Process input (Note: touch events are also set up to add // these keys to the array). if (_moveLeft) { _level.getCharacter().move(-elapsedTime * 60); } if (_moveRight) { _level.getCharacter().move(elapsedTime * 60); } int result = 0; // Update the level if (!_renderWin || !_renderLose) { result = _level.update(); } // The level has been won, so begin the win animation if (result == SpriteGameLevel.LEVEL_WIN && !_renderWin) { _winAnimation.begin(0L); } // The level has been lost, so begin the lose animation else if (result == SpriteGameLevel.LEVEL_LOSE) { _level.getCharacter().reset(); _loseAnimation.begin(0L); } } /** * @see GLField#render(GL) */ protected void render(final GL gl) { // Determine what OpenGL version to use // and call the corresponding render() method. if (gl instanceof GL20) { render((GL20) gl); } else { render((GL11) gl); } } /** * Render method for OpenGL v1.1 * * @param gl * The OpenGL v1.1 object that will be used to render */ private void render(final GL11 gl) { if (!_level.isLevelLoaded()) { return; } if (!_level.isLevelInitialized()) { _level.initialize(gl); } // Clear the color and depth buffers gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // Clear the modelview matrix gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); // Translate to the correct location gl.glTranslatef(0.0f, 0.0f, -1.0f); // Render the level. _level.render(gl); // If the win or lose animations are running, render the corresponding // sprite if (_renderWin) { _win.render(gl); } if (_renderLose) { _lose.render(gl); } } /** * Render method for OpenGL v2.0 * * @param gl * The OpenGL 2.0 object that will be used to render */ private void render(final GL20 gl) { if (!_level.isLevelLoaded()) { return; } if (!_level.isLevelInitialized()) { _level.initialize(gl); } // Clear the color and depth buffers gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); // Load the shader program gl.glUseProgram(Sprite.getProgram()); // Load the projection matrix gl.glUniformMatrix4fv(Sprite.getProjectionMatrixLocation(), 1, false, _projection.getArray(), 0); // Render the level _level.render(gl, _modelview); // If the win or lose animations are running, render the corresponding // sprite if (_renderWin) { _win.render(gl, _modelview); } if (_renderLose) { _lose.render(gl, _modelview); } } /** * Loads the current level */ private void loadLevel() { _level = new SpriteGameLevel(_levelNames[_currentLevel], _animator); _currentLevel++; if (_currentLevel >= _levelNames.length) { _currentLevel = 0; } } /** * @see net.rim.device.api.animation.AnimationListener#animationBegin(AbstractAnimation) */ public void animationBegin(final AbstractAnimation animation) { if (animation == _winAnimation) { _renderWin = true; } else if (animation == _loseAnimation) { _renderLose = true; } } /** * @see net.rim.device.api.animation.AnimationListener#animationEnd(AbstractAnimation) */ public void animationEnd(final AbstractAnimation animation) { if (animation == _winAnimation) { _renderWin = false; loadLevel(); } else if (animation == _loseAnimation) { _renderLose = false; } } /** * @see net.rim.device.api.ui.Field#keyDown(int, int) */ protected boolean keyDown(final int keycode, final int time) { final StringBuffer sb = new StringBuffer(); // Retrieve the characters mapped to the keycode for the current // keyboard layout Keypad.getKeyChars(keycode, sb); if (sb.toString().indexOf('q') != -1) { _moveLeft = true; } if (sb.toString().indexOf('w') != -1) { _moveRight = true; } return false; } /** * @see net.rim.device.api.ui.Field#keyUp(int, int) */ protected boolean keyUp(final int keycode, final int time) { final StringBuffer sb = new StringBuffer(); // Retrieve the characters mapped to the keycode for the current // keyboard layout Keypad.getKeyChars(keycode, sb); if (sb.toString().indexOf('q') != -1) { _moveLeft = false; } if (sb.toString().indexOf('w') != -1) { _moveRight = false; } return false; } /** * @see net.rim.device.api.ui.Field#keyChar(char, int, int) */ protected boolean keyChar(final char c, final int status, final int time) { switch (c) { case Characters.SPACE: _level.getCharacter().jump(); return true; case Characters.LATIN_SMALL_LETTER_S: _level.getCharacter().shrink(); return true; case Characters.ESCAPE: default: return false; } } /** * @see net.rim.device.api.ui.Field#navigationClick(int, int) */ protected boolean navigationClick(final int status, final int time) { _level.getCharacter().jump(); return true; } /** * @see net.rim.device.api.ui.Field#navigationMovement(int, int, int, int) */ protected boolean navigationMovement(final int dx, final int dy, final int status, final int time) { _level.getCharacter().move(dx * 4); return true; } /** * @see net.rim.device.api.ui.Field#touchEvent(TouchEvent) */ protected boolean touchEvent(final TouchEvent message) { if (message == null) { return false; } boolean tappedCenter = false; switch (message.getEvent()) { case TouchEvent.GESTURE: { final TouchGesture gesture = message.getGesture(); if (gesture.getEvent() == TouchGesture.SWIPE) { final int direction = gesture.getSwipeDirection(); if (direction == TouchGesture.SWIPE_NORTH || direction == TouchGesture.SWIPE_SOUTH) { // Swipe up or down to shrink or grow _level.getCharacter().shrink(); } } break; } case TouchEvent.DOWN: { final int x = message.getX(1); final int x2 = message.getX(2); // Touch with two fingers to jump. Note: can tap the screen // while moving in a direction to jump while moving. if (x2 != -1) { _level.getCharacter().jump(); } else { final int displayWidth = Display.getWidth(); // Touch the left or right quarter of the // screen to move in that direction. if (x < displayWidth / 4) { _moveLeft = true; } else if (x > displayWidth - displayWidth / 4) { _moveRight = true; } else { tappedCenter = true; } } break; } case TouchEvent.UP: { final int x = message.getX(1); // Release the first finger that went down to stop moving if (x != -1) { _moveLeft = false; _moveRight = false; } // Jump if the middle of the screen was tapped if (tappedCenter) { _level.getCharacter().jump(); } break; } } return true; } }