Java tutorial
/* * Copyright 2012 Antti Kolehmainen * * 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. */ package com.sturdyhelmetgames.dodgethecars.screen; import java.util.Iterator; import java.util.Random; import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Touchpad; import com.badlogic.gdx.scenes.scene2d.ui.Touchpad.TouchpadStyle; import com.badlogic.gdx.utils.Array; import com.sturdyhelmetgames.dodgethecars.DodgeTheCarsGame; import com.sturdyhelmetgames.dodgethecars.RandomUtil; import com.sturdyhelmetgames.dodgethecars.assets.Art; import com.sturdyhelmetgames.dodgethecars.assets.Sound; import com.sturdyhelmetgames.dodgethecars.entity.BasicEntity; import com.sturdyhelmetgames.dodgethecars.entity.BasicEntity.Direction; import com.sturdyhelmetgames.dodgethecars.entity.Car; import com.sturdyhelmetgames.dodgethecars.entity.GameOverIndicator; import com.sturdyhelmetgames.dodgethecars.entity.Heart; import com.sturdyhelmetgames.dodgethecars.entity.PauseIndicator; import com.sturdyhelmetgames.dodgethecars.entity.PineCone; import com.sturdyhelmetgames.dodgethecars.entity.Point; import com.sturdyhelmetgames.dodgethecars.entity.Point.PointType; import com.sturdyhelmetgames.dodgethecars.entity.Squirrel; import com.sturdyhelmetgames.dodgethecars.entity.TrafficLight; import com.sturdyhelmetgames.dodgethecars.events.EventCache; /** * Main game screen. * * @author Antti 19.6.2012 * */ public class GameScreen extends TransitionScreen { private static final float TIME_PER_ROUND = 5f; /** * Number of car "waves" */ private long wave; /** * How long the game session has been running. */ private float gameTime; /** * First time attribute that is used to control the speed of {@link Car} * spawns. */ private float carSpeedUpTotalTime; /** * Second time attribute that is used to control the speed of {@link Car} * spawns. */ private float carSpeedUpTime; /** * Time that has passed since last {@link Car} spawn. */ private float carTime; /** * Thime that has passed since last {@link PineCone} spawn. */ private float pineConeTime; /** * Tells if the game is paused or not. */ private boolean paused = false; /** * Tells if the game is over or not. */ private boolean gameOver = false; /** * Tells how long game over lasts. */ private float gameOverTime = 0f; /** * List for game objects. */ private final Array<BasicEntity> gameEntities = new Array<BasicEntity>(false, 30); /** * Pool for cars. */ private final Array<Car> carPool = new Array<Car>(false, 20); /** * Pool for pinecones. */ private final Array<PineCone> pineConePool = new Array<PineCone>(false, 20); public static final float LEVEL_BOUNDARY_X_RIGHT = 21f; public static final float LEVEL_BOUNDARY_X_LEFT = -23f; public static final float LEVEL_BOUNDARY_Y_TOP = 14f; public static final float LEVEL_BOUNDARY_Y_BOTTOM = -15f; /** * The player character. */ private Squirrel player; /** * Traffic light. */ private TrafficLight trafficLight; /** * Game over indicator. */ private final GameOverIndicator gameOverIndicator = new GameOverIndicator(); /** * Pause indicator. */ private final PauseIndicator pauseIndicator = new PauseIndicator(); /** * Holder for score text position. */ private final Vector3 scoreTextPosition = new Vector3(LEVEL_BOUNDARY_X_LEFT, LEVEL_BOUNDARY_Y_TOP - 1f, 0f); /** * Stage for {@link Touchpad}. */ private final Stage stage; /** * {@link Touchpad} for touch events. */ private final Touchpad touchpad; /** * Indicates if touch controls are active or not. */ private boolean isTouchActive = false; public GameScreen(final DodgeTheCarsGame game) { super(game); Gdx.input.setCatchBackKey(true); player = new Squirrel(); gameEntities.add(player); trafficLight = new TrafficLight(); camera.project(scoreTextPosition); stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true) { @Override public boolean keyDown(int keyCode) { if (gameTime > 3.5f) { if (!paused && !gameOver) { if (keyCode == Keys.P || keyCode == Keys.ESCAPE || keyCode == Keys.BACK || keyCode == Keys.HOME) { pause(); return true; } } else if (paused) { if (keyCode == Keys.P || keyCode == Keys.ESCAPE || keyCode == Keys.BACK || keyCode == Keys.C) { resumeGame(); return true; } else if (keyCode == Keys.Q) { updateLeaderboard(); game.setScreen(new TitleScreen(game)); return true; } } if (gameOver) { if (keyCode == Keys.R) { game.setScreen(new GameScreen(game)); return true; } else if (keyCode == Keys.Q) { game.setScreen(new TitleScreen(game)); return true; } } } return super.keyDown(keyCode); } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { final Vector3 position = new Vector3(screenX, screenY, 0f); camera.unproject(position); if (gameTime > 3.5f) { if (paused) { // very dirty code, sorry :) if (position.x > -7f && position.x < 7f && position.y > 3f && position.y < 6f) { resumeGame(); return true; } else if (position.x > -7f && position.x < 7f && position.y < -4f && position.y > -7f) { updateLeaderboard(); game.setScreen(new TitleScreen(game)); return true; } } if (gameOver) { // very dirty code, sorry :) if (position.x > -7f && position.x < 7f && position.y > 4f && position.y < 7f) { game.setScreen(new GameScreen(game)); return true; } else if (position.x > -7f && position.x < 7f && position.y < -4f && position.y > -7f) { game.setScreen(new TitleScreen(game)); return true; } } } return super.touchUp(screenX, screenY, pointer, button); } }; Gdx.input.setInputProcessor(stage); touchpad = new Touchpad(2.0f, Art.skin.get("touchpad", TouchpadStyle.class)); touchpad.setBounds(15, 15, 200, 200); stage.addActor(touchpad); // if touch-based device, activate touch controls if (ApplicationType.Android.equals(Gdx.app.getType()) || ApplicationType.iOS.equals(Gdx.app.getType())) { isTouchActive = true; } } /** * Activates game over screen. */ private void activateGameOver() { gameOver = true; if (Sound.musicHappyGoLucky.isPlaying()) { Sound.musicHappyGoLucky.stop(); } Sound.soundSquirrelDie.play(); updateLeaderboard(); } /** * Update leaderboard with player score. */ private void updateLeaderboard() { EventCache.updateLeaderboard.eventValue = player.score; game.fireEvent(EventCache.updateLeaderboard); } @Override protected void updateScreen(float fixedStep) { super.updateScreen(fixedStep); // check input and after that update entities checkInput(fixedStep); if (paused) { pauseIndicator.update(fixedStep); } else { // touch controls if on android if (isTouchActive && player.isAlive()) { stage.act(fixedStep); final float knobX = touchpad.getKnobX() - 100; final float knobY = touchpad.getKnobY() - 100; player.acceleration.x = knobX; player.acceleration.y = knobY; } // cumulate timers gameTime += fixedStep; carSpeedUpTotalTime += fixedStep; trafficLight.update(fixedStep); if (gameOver) { gameOverTime += fixedStep; if (gameOverTime > 2f) { gameOverIndicator.update(fixedStep); } } if (gameTime > 3f) { if (!Sound.musicHappyGoLucky.isPlaying() && player.isAlive()) { Sound.musicHappyGoLucky.play(); } carSpeedUpTime += fixedStep; carTime += fixedStep; pineConeTime += fixedStep; // add some score when player is alive if (player.isAlive()) { player.score += (fixedStep * 90); } wave = Math.round(gameTime / TIME_PER_ROUND) + 1; carSpeedUpTime = wave % 3 == 0 ? (carSpeedUpTotalTime * wave) / 5000 + 3.5f : 2f; if (carSpeedUpTime > 4.2f) { carSpeedUpTotalTime = 2f; } // randomly spawn cars if (carTime > 4f + MathUtils.random(5f) - carSpeedUpTime) { carTime = 0f; // from which side the car drives final boolean side = getRandom().nextBoolean(); final float carX = side ? 35f : -35f; final float carY = getRandom().nextInt(32) - 16; final Direction carDirection = side ? Direction.LEFT : Direction.RIGHT; final int carColor = getRandom().nextInt(3); final float maxVelocity = RandomUtil.oneOfFive() ? 20f : BasicEntity.VELOCITY_MAX; Car car = null; if (carPool.size < 1) { // pool didn't have spare cars, create a new one car = new Car(carX, carY, carDirection, carColor, maxVelocity); } else { // pool had cars, fetch one and reset car = carPool.get(0); car.reset(carX, carY, carDirection, carColor, maxVelocity); carPool.removeIndex(0); } gameEntities.add(car); } // randomly spawn pinecones and hearts if (pineConeTime > 2f + MathUtils.random(5f)) { pineConeTime = 0f; // randomize new powerup position final float powerupX = getRandom().nextInt(44) - 22; final float powerupY = getRandom().nextInt(28) - 14; if (RandomUtil.oneOfSeven()) { // TODO: pool hearts for less GC Heart heart = new Heart(powerupX, powerupY); gameEntities.add(heart); } else { // randomize new pinecone color int color = PineCone.PINECONE_COLOR_BRONZE; if (RandomUtil.oneOfThree()) { color = PineCone.PINECONE_COLOR_SILVER; } else if (RandomUtil.oneOfFive()) { color = PineCone.PINECONE_COLOR_GOLD; } PineCone pineCone = null; if (pineConePool.size < 1) { // pool didn't have spare pinecones, create a new // one pineCone = new PineCone(powerupX, powerupY, color); } else { // pineConePool.sort(); // pool had pinecones, fetch one and reset pineCone = pineConePool.get(0); pineCone.reset(powerupX, powerupY, color); pineConePool.removeIndex(0); } gameEntities.add(pineCone); } } // update entities for (Iterator<BasicEntity> i = gameEntities.iterator(); i.hasNext();) { BasicEntity entity = i.next(); entity.update(fixedStep); if (entity instanceof Car) { Car car = (Car) entity; // if car hits player, squirrel takes damage if (car.hit(player)) { if (!player.isDamaged && player.health > 1) { Sound.soundSquirrelHit.play(); } if (player.takeDamage()) { activateGameOver(); } } // if car is out of the limits send it to pool if (car.x < -36f || car.x > 36f) { carPool.add(car); i.remove(); } } if (entity instanceof PineCone) { PineCone pineCone = (PineCone) entity; // if pine cone hits player, "collect" it if (player.isAlive() && pineCone.hit(player)) { pineCone.collected = true; player.score += (500 * (pineCone.color + 1)); // TODO: pool point objects if (pineCone.color == PineCone.PINECONE_COLOR_BRONZE) { gameEntities.add(new Point(pineCone.x, pineCone.y, PointType.POINT_500)); } else if (pineCone.color == PineCone.PINECONE_COLOR_SILVER) { gameEntities.add(new Point(pineCone.x, pineCone.y, PointType.POINT_1000)); } else if (pineCone.color == PineCone.PINECONE_COLOR_GOLD) { gameEntities.add(new Point(pineCone.x, pineCone.y, PointType.POINT_1500)); } } // if pine cone is collected send it to pool if (!pineCone.isAlive() || pineCone.collected) { if (pineCone.collected) { Sound.soundCollect.play(); } pineCone.collected = false; pineConePool.add(pineCone); i.remove(); } } if (entity instanceof Heart) { Heart heart = (Heart) entity; // if heart hits player, "collect" it and get score and // health if (player.isAlive() && heart.hit(player)) { heart.collected = true; player.score += 500; player.replenishHealth(); // TODO: pool points gameEntities.add(new Point(heart.x, heart.y, PointType.POINT_500)); } if (!heart.isAlive() || heart.collected) { if (heart.collected) { Sound.soundCollect.play(); } i.remove(); } } if (entity instanceof Point) { Point point = (Point) entity; if (!point.isAlive()) { i.remove(); } } } } // sort order of entities for drawing, pretty lame solution gameEntities.sort(); } } @Override public void renderScreen(float delta) { spriteBatch.getProjectionMatrix().set(camera.combined); spriteBatch.begin(); // render the background spriteBatch.draw(Art.backgroundTexture, -24, -46.5f, 48, 64); // if player is dead, render it underneath everything if (!player.isAlive()) { player.render(spriteBatch, delta); } // render the entities except squirrel when it's dead for (BasicEntity entity : gameEntities) { if (!(entity instanceof Squirrel) || ((Squirrel) entity).isAlive()) { entity.render(spriteBatch, delta); } } // render hud for (int i = 0; i < Squirrel.HEALTH_MAX; i++) { if (i < player.health) { spriteBatch.draw(Art.heartTex, LEVEL_BOUNDARY_X_LEFT + i * 2f, LEVEL_BOUNDARY_Y_TOP - 1f, 2f, 2f); } else { spriteBatch.draw(Art.heartBlackTex, LEVEL_BOUNDARY_X_LEFT + i * 2f, LEVEL_BOUNDARY_Y_TOP - 1f, 2f, 2f); } } trafficLight.render(spriteBatch, delta); if (gameOver) { gameOverIndicator.render(spriteBatch, delta); } if (paused) { pauseIndicator.render(spriteBatch, delta); } spriteBatch.end(); if (isTouchActive) { stage.draw(); } // render fps and additional stuff for debugging spriteBatch.getProjectionMatrix().set(normalProjection); spriteBatch.begin(); Art.scoreFont.draw(spriteBatch, String.valueOf(player.score), scoreTextPosition.x, scoreTextPosition.y); if (isDebug()) { Art.debugFont.draw(spriteBatch, "Score: " + player.score, 0, 40); int numOfCarsInPlay = 0; int numOfCarsInPool = 0; for (BasicEntity entity : gameEntities) { if (entity instanceof Car) { numOfCarsInPlay++; } } for (Car car : carPool) { if (car != null) { numOfCarsInPool++; } } Art.debugFont.draw(spriteBatch, "Number of cars in play: " + numOfCarsInPlay, 0, 60); Art.debugFont.draw(spriteBatch, "Number of cars in pool: " + numOfCarsInPool, 0, 80); int numOfPineConesInPlay = 0; int numOfPineConesInPool = 0; for (BasicEntity entity : gameEntities) { if (entity instanceof PineCone) { numOfPineConesInPlay++; } } for (PineCone pineCone : pineConePool) { if (pineCone != null) { numOfPineConesInPool++; } } Art.debugFont.draw(spriteBatch, "Number of pine cones in play: " + numOfPineConesInPlay, 0, 100); Art.debugFont.draw(spriteBatch, "Number of pine cones in pool: " + numOfPineConesInPool, 0, 120); Art.debugFont.draw(spriteBatch, "Game speed up time: " + carSpeedUpTime, 0, 140); Art.debugFont.draw(spriteBatch, "Round: " + wave, 0, 160); } spriteBatch.end(); renderFadeIn(delta); } /** * Processes player movement keys. * * @param fixedStep */ private void checkInput(float fixedStep) { if (!paused) { if (player.isAlive()) { // process player movement keys if (Gdx.input.isKeyPressed(Keys.UP)) { player.direction = Direction.UP; player.acceleration.y = BasicEntity.ACCELERATION_MAX; } if (Gdx.input.isKeyPressed(Keys.DOWN)) { player.direction = Direction.DOWN; player.acceleration.y = -BasicEntity.ACCELERATION_MAX; } if (Gdx.input.isKeyPressed(Keys.RIGHT)) { player.direction = Direction.RIGHT; player.acceleration.x = BasicEntity.ACCELERATION_MAX; } if (Gdx.input.isKeyPressed(Keys.LEFT)) { player.direction = Direction.LEFT; player.acceleration.x = -BasicEntity.ACCELERATION_MAX; } } } } @Override public void pause() { super.pause(); paused = true; pauseIndicator.reset(); } public void resumeGame() { paused = false; } /** * Return a {@link Random} instance. * * @return Random instance. */ private Random getRandom() { return RandomUtil.getRandom(); } @Override public void dispose() { super.dispose(); stage.dispose(); } }