/* * Copyright © 2012 Nokia Corporation. All rights reserved. * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. * Oracle and Java are trademarks or registered trademarks of Oracle and/or its * affiliates. Other product and company names mentioned herein may be trademarks * or trade names of their respective owners. * See LICENSE.TXT for license information. */ package com.nokia.example.racer.views; import com.nokia.example.racer.Main; import com.nokia.example.racer.helpers.ImageLoader; import com.nokia.example.racer.helpers.Engine; import com.nokia.example.racer.helpers.KeyCodes; import com.nokia.example.racer.sensor.AccelerationProvider; import com.nokia.example.racer.views.components.BrakeButton; import com.nokia.example.racer.views.components.Item; import com.nokia.example.racer.views.components.Track; import com.nokia.example.racer.views.tracks.EightLoop; import com.nokia.example.racer.views.tracks.LapListener; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; import javax.microedition.lcdui.Font; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.game.Sprite; public class GameView extends View implements LapListener { private static final int MAX_SPEED = 8; // car max speed private Timer gameTimer; private Timer raceTimer; private TimerTask clock; private long currentLapTime = 0; private long bestLapTime = 0; private long latestLapTime = 0; /* * camera coordinates */ private int camX = 0; private int camY = 0; /* * car coordinates */ private int carX = 0; private int carY = 0; private int speed = 2; private int surfaceMultiplier = 0; private int friction = 2; private int xIncrement = 0; private int yIncrement = 0; private int currentFrame = 0; private int lastFrame = 0; /* * precalculated angles */ private double[] sins; private double[] coss; /* * image items */ private Item topBar; private Item speedMeter; private Item startInfo; /* * brake button */ private BrakeButton brakeButton; /* * current track */ private Track track; /* * sprites */ private Sprite carSprite; private Sprite countDown; private Sprite speedNeedle; /* * when stopped, car does not move */ private boolean stopped = true; protected boolean applyGas = false; /* * true when turn button clicked */ private boolean turnLeft = false; private boolean turnRight = false; protected Engine engine; /* * thread that shows 3 - 2 - 1 - GO! */ private Thread countdownThread; private Font smallFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_ITALIC, Font.SIZE_SMALL); private Font bigFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_ITALIC, Font.SIZE_LARGE); private int tilt = 0; private int cumulativeTilt; private boolean countdownDone = false; /* * Object providing sensor data */ private AccelerationProvider accelerationProvider; public GameView() { super(); enableSensors(); loadGraphics(); preCalculate(); engine = new Engine(); // init engine } /** * Called when showing racing view */ public void showNotify() { enableSensors(); /* * EightLoop as the default track */ if (track == null) { setTrack(new EightLoop(this)); } gameTimer = new Timer(); g = getGraphics(); TimerTask coordAndUi = new TimerTask() { public void run() { updateCoordinates(); track.checkGatePass(carX, carY); render(g); flushGraphics(); } }; TimerTask turning = new TimerTask() { public void run() { if (!stopped) { turnByRotate(); turnByKeyPress(); } } }; TimerTask physics = new TimerTask() { public void run() { int frame = lastFrame; updateForce(frame); lastFrame = currentFrame; updateFriction(); } }; gameTimer.schedule(coordAndUi, 0, 40); gameTimer.schedule(turning, 0, 75); gameTimer.schedule(physics, 0, 200); raceTimer = new Timer(); clock = new TimerTask() { public void run() { currentLapTime += 10; } }; countdownThread = new Thread() { public void run() { try { if (!countdownDone) { countDown.setFrame(0); Thread.sleep(1000); countDown.nextFrame(); Thread.sleep(1000); countDown.nextFrame(); Thread.sleep(1000); countDown.nextFrame(); Thread.sleep(700); stopped = false; if (!brakeButton.isPressed()) { accelerate(); } countdownDone = true; } else { stopped = false; } raceTimer.scheduleAtFixedRate(clock, 10, 10); } catch (InterruptedException ie) { } } }; countdownThread.start(); engine.on(); } /** * Resets all current driving info for new race to be started. */ public void reset() { this.carX = track.getCarStartX(); this.carY = track.getCarStartY(); this.camX = carX; this.camY = carY; currentFrame = track.getCarStartFrame(); carSprite.setFrame(currentFrame); speed = 2; xIncrement = 0; yIncrement = 0; currentLapTime = 0; bestLapTime = 0; latestLapTime = 0; stopped = true; decelerate(); track.resetLaps(); track.resetGates(); countDown.setFrame(0); countdownDone = false; } /** * Accelerates the car */ public void accelerate() { applyGas = true; engine.accelerate(); } /** * Decelerates the car */ public void decelerate() { applyGas = false; engine.decelerate(); } /** * Called when a lap is driven. Resets lap timer. * * @param laps Total laps driven. */ public void lapDriven(int laps) { latestLapTime = currentLapTime; currentLapTime = 0; synchronized (this) { raceTimer.cancel(); raceTimer = new Timer(); clock = new TimerTask() { public void run() { currentLapTime += 10; } }; raceTimer.scheduleAtFixedRate(clock, 10, 10); } if (latestLapTime < bestLapTime || bestLapTime == 0) { bestLapTime = latestLapTime; } } /** * Changes the current track * * @param track The new track */ public void setTrack(Track track) { this.track = null; this.track = track; this.carX = track.getCarStartX(); this.carY = track.getCarStartY(); currentFrame = track.getCarStartFrame(); carSprite.setFrame(currentFrame); } /** * Garbage collects track resources. */ public void cleanTrackResources() { track = null; System.gc(); } /** * Handles drags the gesture provider sends. * * @param startX X coordinate of the start point of the drag * @param endX X coordinate of the end point of the drag */ public void handleDrag(int startX, int endX) { if (speed > friction) { /* * check if drag was from left to right */ if (endX > startX) { turnRight(); } else { turnLeft(); } } } /** * Called when leaving racing view */ protected void hideNotify() { countdownThread.interrupt(); gameTimer.cancel(); raceTimer.cancel(); engine.off(); stopped = true; if (accelerationProvider != null) { accelerationProvider.close(); } } protected void sizeChanged(int w, int h) { super.sizeChanged(w, h); setPositions(); } /* * Turns the car one frame left */ protected void turnLeft() { lastFrame = currentFrame; if (currentFrame == 0) { currentFrame = carSprite.getFrameSequenceLength() - 1; } else { currentFrame--; } carSprite.setFrame(currentFrame); } /* * Turns the car one frame right */ protected void turnRight() { lastFrame = currentFrame; if (currentFrame == carSprite.getFrameSequenceLength() - 1) { currentFrame = 0; } else { currentFrame++; } carSprite.setFrame(currentFrame); } protected void pointerPressed(int x, int y) { if (isSensorTurning()) { brakeButton.pointerPressed(x, y); } } protected void pointerReleased(int x, int y) { if (isSensorTurning()) { brakeButton.pointerReleased(x, y); accelerate(); // for cases when the release is not happened on the button } /* * menu button */ if ((Main.landscape && x >= 70 && x < 150 && y <= 40) || (!Main.landscape && x <= 60 && y <= 40)) { showMenu(); } } protected void keyPressed(int keyCode) { switch (keyCode) { case KEY_NUM8: // brake Main.getInstance().getGameView().accelerate(); break; } int gameAction = getGameAction(keyCode); switch (gameAction) { case DOWN: // brake Main.getInstance().getGameView().decelerate(); break; case KEY_NUM4: // turn left turnLeft = true; break; case KEY_NUM6: // turn right turnRight = true; break; case LEFT: // turn left turnLeft = true; break; case RIGHT: // turn right turnRight = true; break; } } protected void keyReleased(int keyCode) { switch (keyCode) { case KeyCodes.RIGHT_SOFTKEY: showMenu(); break; case KEY_NUM8: Main.getInstance().getGameView().accelerate(); break; } int gameAction = getGameAction(keyCode); switch (gameAction) { case DOWN: Main.getInstance().getGameView().accelerate(); break; case KEY_NUM4: turnLeft = false; break; case KEY_NUM6: turnRight = false; break; case LEFT: turnLeft = false; break; case RIGHT: turnRight = false; break; case KEY_NUM0: showMenu(); break; } } /** * Tells whether car turning is done by tilting the phone. * * @return true if car turning is done by tilting the phone */ private boolean isSensorTurning() { return Main.sensorsSupported && Main.landscape; } private void render(Graphics g) { int anchor = g.TOP | g.LEFT; track.render(g); carSprite.paint(g); speedMeter.render(g); topBar.render(g); if (isSensorTurning()) { brakeButton.render(g); } speedNeedle.paint(g); if (stopped && !countdownDone) { if (Main.gesturesSupported || Main.sensorsSupported) { startInfo.render(g); } countDown.paint(g); } g.setColor(0xFFFFFF); g.setFont(bigFont); g.drawString(track.getLaps() + "", View.screenWidth / 2 - 22, 5, anchor); g.setFont(smallFont); g.drawString((currentLapTime / 1000) + ":" + (currentLapTime % 1000 / 10), View.screenWidth / 2 + 57, 0, anchor); g.drawString((bestLapTime / 1000) + ":" + (bestLapTime % 1000 / 10), View.screenWidth / 2 + 57, 12, anchor); } private void loadGraphics() { try { brakeButton = new BrakeButton(); ImageLoader loader = ImageLoader.getInstance(); speedMeter = new Item("/speedo.png"); topBar = new Item("/top_bar.png"); startInfo = new Item(isSensorTurning() ? "/help_sensors.png" : "/help.png"); carSprite = new Sprite(loader.loadImage("/car_sprite.png"), 64, 64); carSprite.setRefPixelPosition(32, 32); carSprite.setFrame(currentFrame); countDown = new Sprite(loader.loadImage("/countdown.png"), 240, 121); countDown.setFrame(0); speedNeedle = new Sprite(loader.loadImage("/speed_sprite.png"), 46, 46); speedNeedle.setFrame(0); setPositions(); } catch (IOException e) { } } private void setPositions() { int w = View.screenWidth; int h = View.screenHeight; int speedMeterWidth = speedMeter.getImage().getWidth(); int speedMeterHeight = speedMeter.getImage().getHeight(); brakeButton.setPosition(5, h - brakeButton.getImage().getHeight() - 5); topBar.setPosition((w - topBar.getImage().getWidth()) / 2, 0); speedMeter.setPosition(w - speedMeterWidth - 5, h - speedMeterHeight - 5); countDown.setPosition(w / 2 - countDown.getWidth() / 2, h / 2 - countDown.getHeight() / 2); speedNeedle.setPosition(w - speedMeterWidth / 2 - speedNeedle.getWidth() / 2 - 5, h - speedMeterHeight / 2 - speedNeedle.getHeight() / 2 - 7); if (Main.landscape) { startInfo.setPositionRel(0.5f, 0.85f); } else { startInfo.setPositionRel(0.5f, 0.75f); } } private void preCalculate() { int frames = carSprite.getFrameSequenceLength(); /* * precalculate angles */ sins = new double[frames]; coss = new double[frames]; for (int i = 0; i < frames; i++) { int angle = 360 / frames * i; sins[i] = Math.sin(Math.toRadians(angle)); coss[i] = Math.cos(Math.toRadians(angle)); } } private void updateForce(int frame) { if (frame == -1) { frame = currentFrame; } if (applyGas) { if (speed < MAX_SPEED) { speed++; } } else if (!applyGas && speed > friction) { speed -= 2; } int speedCalc = ((speed + surfaceMultiplier) - friction); if (speedCalc < 0) { speedCalc = 0; } /* * calculate speed components */ yIncrement = (int) (speedCalc * coss[frame]); xIncrement = (int) (speedCalc * sins[frame]); if (speed >= 0 && speed <= 12) { int s = speed * 11 / MAX_SPEED; if (s < 0) { s = 0; } if (!applyGas && speed == friction) { s = 0; } speedNeedle.setFrame(s); } } /* * Updates car coordinates and keeps them inside track */ private void updateCoordinates() { int carRadius = carSprite.getWidth() / 2; int trackWidth = track.getWidth(); int trackHeight = track.getHeight(); int halfWidth = View.screenWidth / 2; int halfHeight = View.screenHeight / 2; /* * Car */ /* * x component */ carX += xIncrement; if (carX < carRadius) { carX = carRadius; } if (carX > trackWidth - carRadius) { carX = trackWidth - carRadius; } /* * y component */ carY -= yIncrement; if (carY < carRadius) { carY = carRadius; } if (carY > trackHeight - carRadius) { carY = trackHeight - carRadius; } // Camera /* * x component */ camX = carX; if (camX < halfWidth) { camX = halfWidth; } if (camX > trackWidth - halfWidth) { camX = trackWidth - halfWidth; } /* * y component */ camY = carY; if (camY < halfHeight) { camY = halfHeight; } if (camY > trackHeight - halfHeight) { camY = trackHeight - halfHeight; } carSprite.setPosition(halfWidth + carX - camX - 32, halfHeight + carY - camY - 32); track.setPosition(halfWidth - camX, halfHeight - camY); } /* * Changes friction according to ground type */ private void updateFriction() { /* * get the color of the ground under center of the car */ int[] rgb = new int[1]; track.getTrackImage().getRGB(rgb, 0, 1, carX, carY, 1, 1); int currentRGB = rgb[0]; /* * rgb is in format 0xAARRGGBB where AA is alpha channel values */ int red = (currentRGB & 0x00FF0000) >> 16; int green = (currentRGB & 0x0000FF00) >> 8; int blue = (currentRGB & 0x000000FF); /* * if the color values are less than 70, there is probably asphalt */ if (red < 70 && green < 70 && blue < 70) { surfaceMultiplier = 0; } else { surfaceMultiplier = -2; } } private void enableSensors() { if (accelerationProvider != null) { accelerationProvider.close(); } /* * add acceleration listener */ accelerationProvider = AccelerationProvider.getProvider( new AccelerationProvider.Listener() { public void dataReceived(double ax, double ay, double az) { if (isSensorTurning()) { tilt = (int) ay; } } }); if (accelerationProvider != null) { Main.sensorsSupported = true; } else { Main.sensorsSupported = false; } } private void turnByRotate() { cumulativeTilt += tilt; /* * When cumulative tilt reaches +-4, rotate happens. The more device is rotated, the faster tilt increases/decreases. */ if (isSensorTurning()) { if (cumulativeTilt > 4) { turnRight(); cumulativeTilt = 0; } if (cumulativeTilt < -4) { turnLeft(); cumulativeTilt = 0; } } } private void turnByKeyPress() { if (turnRight) { turnRight(); } else if (turnLeft) { turnLeft(); } } private void showMenu() { reset(); Main.getInstance().showMenu(); } }