/* * Copyright 2011 Rod Hyde (rod@badlydrawngames.com) * * 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.todoroo.zxzx; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Pool; import com.todoroo.zxzx.entity.AlienShip; import com.todoroo.zxzx.entity.BaseShot; import com.todoroo.zxzx.entity.Player; import com.todoroo.zxzx.entity.PlayerShot; import com.todoroo.zxzx.general.Colliders; import com.todoroo.zxzx.general.Colliders.ColliderHandler; import com.todoroo.zxzx.general.Colliders.RemovalHandler; import com.todoroo.zxzx.general.Config; import com.todoroo.zxzx.general.GameObject; import com.todoroo.zxzx.general.Pools; /** The <code>World</code> is the representation of the game world of <b>Very Angry Robots</b>. It knows nothing about how it will * be displayed, neither does it know about how the player is controlled, particle effects, sounds, nor anything else. It purely * knows about the {@link Player}, {@link AlienShip}s and the walls of the room that the player is in. * * @author Rod */ public class World { /** The <code>FireCommand</code> interface is how the {@link World} is told that a {@link GameObject} wants to fire. */ public static interface FireCommand { /** Tells the {@link World} that a {@link GameObject} wants to fire. Note that <code>dx</code> and <code>dy</code> must be * normalised. The World does not have to fire just because it is asked to. * * @param firer the {@link GameObject} that wants to fire. * @param dx the horizontal component of the bullet's direction. * @param dy the vertical component of the bullet's direction. */ void fire (GameObject firer, float dx, float dy); } // Wall sizes. public static final float WALL_HEIGHT = 0.25f; public static final float WALL_WIDTH = 6.0f; public static final float OUTER_WALL_ADJUST = WALL_HEIGHT; private static final int MAX_PLAYER_SHOTS = Config.asInt("Player.maxShots", 10); private static final float FIRING_INTERVAL = Config.asFloat("Player.firingInterval", 0.2f); private static final float SHOT_SPEED = Config.asFloat("Player.shotSpeed", 20f); // Game states. public static final int RESETTING = 1; public static final int PLAYING = 2; public static final int PLAYER_DEAD = 3; public static final int ALIEN_DEAD = 4; public static final int VICTORY = 5; private final Pool<PlayerShot> shotPool; private final Rectangle roomBounds; private float nextFireTime; private float now, restartLevelTime = 0; private Player player; private AlienShip alienShip; private Array<PlayerShot> playerShots; private int state; private int level; private float stateTime; private float levelTime; private float gameTime; private boolean isPaused; private float pausedTime; private LevelManager levelManager; private WorldNotifier notifier; /** Constructs a new {@link World}. */ public World() { notifier = new WorldNotifier(); roomBounds = new Rectangle(0, 0, 800, 1280); player = new Player(); level = 0; gameTime = 0; shotPool = new Pool<PlayerShot>(MAX_PLAYER_SHOTS, MAX_PLAYER_SHOTS) { @Override protected PlayerShot newObject () { return new PlayerShot(); } }; levelManager = new LevelManager(); } /** Resets the {@link World} to its starting state. */ public void reset () { setState(RESETTING); } public void restartGame() { level = 0; gameTime = 0; reset(); } // -------- updating /** Called when the {@link World} is to be updated. * * @param delta the time in seconds since the last render. */ public void update (float delta) { if (!isPaused) { now += delta; stateTime += delta; switch (state) { case RESETTING: updateResetting(); break; case PLAYING: levelTime += delta; gameTime += delta; updatePlaying(delta); break; case PLAYER_DEAD: updatePlayerDead(delta); break; case ALIEN_DEAD: updateAlienDead(delta); break; case VICTORY: break; } } else { pausedTime += delta; } } private void updateMobiles (float delta) { update(playerShots, delta); if(alienShip.updateBulletManagers()) notifier.onAlienFired(); } private void updatePlaying (float delta) { player.update(delta); alienShip.update(delta); updateMobiles(delta); if (now >= nextFireTime) addPlayerShot(0, SHOT_SPEED); checkForCollisions(); clipBounds(); } private void updatePlayerDead (float delta) { player.update(delta); updateMobiles(delta); checkForCollisions(); if (now >= restartLevelTime) { reset(); } } private void updateAlienDead (float delta) { player.update(delta); alienShip.update(delta); updateMobiles(delta); clipBounds(); if (now >= restartLevelTime) { level++; if(levelManager.wonTheGame(level)) setState(VICTORY); else { notifier.onLevelChange(level); reset(); } } } private void updateResetting () { levelTime = 0; notifier.onWorldReset(); populateLevel(); } private void update (Array<? extends GameObject> gos, float delta) { for (GameObject go : gos) { go.update(delta); } } // ------- position initialization private void populateLevel () { placePlayer(); placeBoss(); createPlayerShots(); setState(PLAYING); } private void placeBoss() { alienShip = levelManager.initAlienShip(level); alienShip.initBulletManagers(this, roomBounds); alienShip.x = roomBounds.width / 2 - alienShip.width / 2; alienShip.y = roomBounds.height - alienShip.height; } private void placePlayer () { player.x = roomBounds.width / 2 - player.width / 2; player.y = player.height; player.inCollision = false; player.setState(Player.FLYING); notifier.onPlayerSpawned(); } private void createPlayerShots () { playerShots = Pools.makeArrayFromPool(playerShots, shotPool, MAX_PLAYER_SHOTS); } // ------- shot mechanics private void addPlayerShot (float dx, float dy) { if (state == PLAYING && playerShots.size < MAX_PLAYER_SHOTS) { PlayerShot shot = shotPool.obtain(); shot.inCollision = false; float x = player.x + player.width / 2 - shot.width / 2; float y = player.y + player.height / 2 - shot.height / 2; shot.fire(x, y, dx, dy); playerShots.add(shot); nextFireTime = now + FIRING_INTERVAL; notifier.onPlayerFired(); } } private final RemovalHandler<BaseShot> shotRemovalHandler = new RemovalHandler<BaseShot>() { public void onRemove (BaseShot shot) { // } }; private void doPlayerHit() { setState(PLAYER_DEAD); restartLevelTime = now + 5; notifier.onPlayerHit(); } private void doAlienDied() { setState(ALIEN_DEAD); restartLevelTime = now + 10; notifier.onAlienDestroyed(); } // -------- collisions private void checkForCollisions () { checkMobileMobileCollisions(); removeMarkedMobiles(); if (state == PLAYING) { if(player.inCollision) doPlayerHit(); if(alienShip.getAlienHealthPercentage() <= 0) doAlienDied(); } } private void checkMobileMobileCollisions () { alienShip.checkBulletCollision(player); Colliders.collide(alienShip, playerShots, playerShotCollisionHandler); Colliders.collide(player, alienShip, playerAlienCollisionHandler); } private void removeMarkedMobiles () { Colliders.removeOutOfBounds(shotPool, playerShots, roomBounds); Colliders.removeMarkedCollisions(shotPool, playerShots, shotRemovalHandler); } private void clipBounds () { player.x = Math.max(0, Math.min(roomBounds.width - player.width, player.x)); player.y = Math.max(0, Math.min(roomBounds.height - player.height, player.y)); alienShip.x = Math.max(0, Math.min(roomBounds.width - alienShip.width, alienShip.x)); alienShip.y = Math.max(roomBounds.height - 2 * alienShip.height, Math.min(roomBounds.height - 2 * alienShip.height / 3, alienShip.y)); } private ColliderHandler<Player, AlienShip> playerAlienCollisionHandler = new ColliderHandler<Player, AlienShip>() { public void onCollision(Player t, AlienShip u) { if(!t.inCollision) { notifier.onAlienHit(); u.hit(5); } t.inCollision = true; } }; private ColliderHandler<AlienShip, PlayerShot> playerShotCollisionHandler = new ColliderHandler<AlienShip, PlayerShot>() { public void onCollision(AlienShip t, PlayerShot u) { t.hit(1); u.inCollision = true; notifier.onAlienHit(); } }; // -------- getters / setters /** Adds another listener to the {@link World}. * * @param listener the listener. */ public void addWorldListener (WorldListener listener) { notifier.addListener(listener); } public BulletManager[] getBulletManagers() { return alienShip.getBulletManagers(); } public void pause () { isPaused = true; pausedTime = 0.0f; } public void resume () { isPaused = false; } public boolean isPaused () { return isPaused; } public float getPausedTime () { return pausedTime; } public int getState () { return state; } private void setState (int newState) { state = newState; stateTime = 0.0f; } public float getStateTime () { return stateTime; } public float getLevelTime() { return levelTime; } public float getGameTime() { return gameTime; } public Player getPlayer () { return player; } public Array<PlayerShot> getPlayerShots () { return playerShots; } public AlienShip getAlienShip() { return alienShip; } }