package com.ptrprograms.asteroidbelttv; import android.content.Context; import android.hardware.input.InputManager; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.hardware.input.InputManager.InputDeviceListener; import android.opengl.Matrix; import android.view.KeyEvent; import android.view.MotionEvent; import com.ptrprograms.asteroidbelttv.Objects.Asteroid; import com.ptrprograms.asteroidbelttv.Objects.Bullet; import com.ptrprograms.asteroidbelttv.Objects.Ship; import com.ptrprograms.asteroidbelttv.Utils.Constants; import com.ptrprograms.asteroidbelttv.Utils.ShapeBuffer; import com.ptrprograms.asteroidbelttv.Utils.Utils; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** * Created by PaulTR on 7/20/14. */ public class GameView extends GLSurfaceView implements GLSurfaceView.Renderer, InputDeviceListener { private static GameView mInstance; private ShapeBuffer mShapeBuffer; private Ship mShip; private long mLastUpdateTimeMillis; private int mWindowWidth; private int mWindowHeight; private List<Asteroid> mAsteroids; public List<Bullet> mBullets; private int mLevel = 1; private final float[] mMVPMatrix = new float[16]; public GameView(Context context) { super( context ); setEGLContextClientVersion( 2 ); this.setRenderer( this ); this.requestFocus(); mInstance = this; mLastUpdateTimeMillis = System.currentTimeMillis(); mShip = new Ship(); InputManager inputManager = (InputManager) context.getSystemService( Context.INPUT_SERVICE ); inputManager.registerInputDeviceListener( this, null ); mAsteroids = new ArrayList<Asteroid>(); mBullets = new ArrayList<Bullet>(); initLevel(); } public static GameView getInstance() { return mInstance; } private void initLevel() { mShip.reset(); if( mBullets != null ) { ListIterator<Bullet> iter = mBullets.listIterator(); while( iter.hasNext() ) { iter.next(); iter.remove(); } } for( int i = 0; i < mLevel + 2; i++ ) { mAsteroids.add( new Asteroid(Utils.Color.RED, Constants.ASTEROID_SIZE_LARGE)); } } private void update( float delta ) { if( mAsteroids.isEmpty() ) { mLevel++; initLevel(); return; } mShip.update( delta ); for( Asteroid asteroid : mAsteroids ) { asteroid.update( delta ); } ListIterator<Bullet> bulletIter = mBullets.listIterator(); ListIterator<Asteroid> asteroidIter = mAsteroids.listIterator(); Bullet bullet; Asteroid asteroid; while( bulletIter.hasNext() ) { bullet = bulletIter.next(); bullet.update( delta ); if( bullet.mLifeTimer == 0 ) { bulletIter.remove(); } } while( asteroidIter.hasNext() ) { asteroid = asteroidIter.next(); bulletIter = mBullets.listIterator(); while( bulletIter.hasNext() ) { bullet = bulletIter.next(); if ((bullet.mPositionX > asteroid.mPositionX && bullet.mPositionX < (asteroid.mPositionX + (2 * asteroid.mAsteroidSize)) && (bullet.mPositionY > asteroid.mPositionY && bullet.mPositionY < (asteroid.mPositionY + (2 * asteroid.mAsteroidSize))))) { bulletIter.remove(); asteroidIter.remove(); if (asteroid.mAsteroidSize == Constants.ASTEROID_SIZE_LARGE) { asteroidIter.add(new Asteroid(Utils.Color.RED, Constants.ASTEROID_SIZE_MEDIUM, asteroid.mPositionX, asteroid.mPositionY)); asteroidIter.add(new Asteroid(Utils.Color.RED, Constants.ASTEROID_SIZE_MEDIUM, asteroid.mPositionX, asteroid.mPositionY)); } else if (asteroid.mAsteroidSize == Constants.ASTEROID_SIZE_MEDIUM) { asteroidIter.add(new Asteroid(Utils.Color.RED, Constants.ASTEROID_SIZE_SMALL, asteroid.mPositionX, asteroid.mPositionY)); asteroidIter.add(new Asteroid(Utils.Color.RED, Constants.ASTEROID_SIZE_SMALL, asteroid.mPositionX, asteroid.mPositionY)); } break; } } } } @Override public void onInputDeviceAdded(int deviceId) { } @Override public void onInputDeviceRemoved(int deviceId) { } @Override public void onInputDeviceChanged(int deviceId) { } @Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { // The ShapeBuffer creates OpenGl resources, so don't create it until after the // primary rendering surface has been created. mShapeBuffer = new ShapeBuffer(); mShapeBuffer.loadResources(); } /** * Here we do our drawing */ @Override public void onDrawFrame(GL10 unused) { // Clear the screen to black. GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); // Don't try to draw if the shape buffer failed to initialize. if (!mShapeBuffer.isInitialized()) { return; } long currentTimeMillis = System.currentTimeMillis(); // Compute frame delta. frameDelta = # of "ideal" frames that have occurred since the // last update. "ideal" assumes a constant frame-rate (60 FPS or 16.7 milliseconds per // frame). Since the delta doesn't depend on the "real" frame-rate, the animations always // run at the same wall clock speed, regardless of what the real refresh rate is. // // frameDelta was used instead of a time delta in order to make the values passed // to update easier to understand when debugging the code. For example, a frameDelta // of "1.5" means that one and a half hypothetical frames have passed since the last // update. In wall time this would be 25 milliseconds or 0.025 seconds. float frameDelta = Utils.millisToFrameDelta(currentTimeMillis - mLastUpdateTimeMillis); update(frameDelta); draw(); mLastUpdateTimeMillis = currentTimeMillis; } /** * If the surface changes, reset the view size. */ @Override public void onSurfaceChanged(GL10 unused, int width, int height) { // Make sure the window dimensions are never 0. mWindowWidth = Math.max(width, 1); mWindowHeight = Math.max(height, 1); } public void draw() { mShapeBuffer.clear(); mShip.draw(mShapeBuffer); for( Asteroid asteroid : mAsteroids ) { asteroid.draw( mShapeBuffer ); } for( Bullet bullet : mBullets ) { bullet.draw( mShapeBuffer ); } updateViewportAndProjection(); mShapeBuffer.draw( mMVPMatrix ); } private void updateViewportAndProjection() { float viewportAspectRatio = 1.0f; if ((mWindowWidth > 0) && (mWindowHeight > 0)) { viewportAspectRatio = (float) mWindowWidth / (float) mWindowHeight; } float viewportWidth = (float) mWindowWidth; float viewportHeight = (float) mWindowHeight; float viewportOffsetX = 0.0f; float viewportOffsetY = 0.0f; if ( Constants.WORLD_ASPECT_RATIO > viewportAspectRatio ) { // Our window is taller than the ideal aspect ratio needed to accommodate the world // without stretching. // Reduce the viewport height to match the aspect ratio of the world. The world // will fill the whole width of the screen, but have some empty space on the top and // bottom of the screen. viewportHeight = viewportWidth / Constants.WORLD_ASPECT_RATIO; // Center the viewport on the screen. viewportOffsetY = ((float) mWindowHeight - viewportHeight) / 2.0f; } else if (viewportAspectRatio > Constants.WORLD_ASPECT_RATIO) { // Our window is wider than the ideal aspect ratio needed to accommodate the world // without stretching. // Reduce the viewport width to match the aspect ratio of the world. The world // will fill the whole height of the screen, but have some empty space on the // left and right of the screen. viewportWidth = viewportHeight * Constants.WORLD_ASPECT_RATIO; // Center the viewport on the screen. viewportOffsetX = ((float) mWindowWidth - viewportWidth) / 2.0f; } Matrix.orthoM( mMVPMatrix, 0, Constants.WORLD_LEFT_COORDINATE, Constants.WORLD_RIGHT_COORDINATE, Constants.WORLD_BOTTOM_COORDINATE, Constants.WORLD_TOP_COORDINATE, Constants.WORLD_NEAR_PLANE, Constants.WORLD_FAR_PLANE ); GLES20.glViewport((int) viewportOffsetX, (int) viewportOffsetY, (int) viewportWidth, (int) viewportHeight); } public boolean handleMotionEvent(MotionEvent motionEvent) { if ( mShip != null ) { mShip.getController().setDeviceId( motionEvent.getDeviceId() ); mShip.getController().handleMotionEvent(motionEvent); return true; } return false; } public boolean handleKeyEvent(KeyEvent keyEvent) { if ( mShip != null) { mShip.getController().setDeviceId( keyEvent.getDeviceId() ); mShip.getController().handleKeyEvent(keyEvent); return true; } return false; } }