package org.erikaredmark.monkeyshines.screendraw;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.image.VolatileImage;
import org.erikaredmark.monkeyshines.GameConstants;
import org.erikaredmark.monkeyshines.GameWorldLogic;
import org.erikaredmark.monkeyshines.Powerup;
import org.erikaredmark.monkeyshines.resource.CoreResource;
import org.erikaredmark.monkeyshines.resource.WorldResource;
/**
*
* Assumes a drawing size of 640x480 and draws the universe represented by the {@code GameWorldLogic} into that
* size constraint. This only handles rendering the entire scene to an image, not how that image is then actually
* painted to the screen.
* <p/>
* This surface has a 1:1 analog to the logical world. This means that a 20x20 unit tile is drawn at 20x20 pixels, no
* graphics substitutions. Screen drawers that then draw the final graphics context on the screen may at their discretion
* stretch or blow up the final result, however.
* <p/>
* The object itself is not drawn to anything, it merely provides the ability to render new images
* that will be drawn to something.
*
* TODO eventual goal is to have a HiDefSurface as well that uses larger graphics for a nicer looking game at a larger
* size without compromising the gameplay (since the game logic assumes a 640x480 world).
*
* @author Erika Redmark
*
*/
public final class StandardSurface {
private static final int SURFACE_SIZE_X = 640;
private static final int SURFACE_SIZE_Y = 480;
// Drawing location to start drawing the health bar.
private static final int HEALTH_DRAW_X = 241;
private static final int HEALTH_DRAW_Y = 50;
private static final int HEALTH_DRAW_WIDTH = 151;
private static final int HEALTH_DRAW_HEIGHT = 14;
private static final int HEALTH_DRAW_Y2 = HEALTH_DRAW_Y + HEALTH_DRAW_HEIGHT;
// Used to map the 'logical' health to the 'width' of the health bar.
// Bonzos health will be converted to double and extended/contracted by this multplier to get draw width.
private static final double HEALTH_MULTIPLIER = (double)HEALTH_DRAW_WIDTH / (double)GameConstants.HEALTH_MAX;
// Score draw x/y is the top left location of the FIRST, leftmost digit.
private static final int SCORE_DRAW_X = 13;
private static final int SCORE_DRAW_Y = 32;
private static final int SCORE_WIDTH = 16;
private static final int SCORE_HEIGHT = 30;
// Precomputation of effectively a constant
private static final int SCORE_DRAW_Y2 = SCORE_DRAW_Y + SCORE_HEIGHT;
private static final int INFINITY_DRAW_X = 582;
private static final int INFINITY_DRAW_Y = 29;
private static final int INFINITY_DRAW_X2 = 626;
private static final int INFINITY_DRAW_Y2 = 65;
private static final int INFINITY_WIDTH = 44;
private static final int INFINITY_HEIGHT = 36;
private static final int LIFE_DRAW_X = 595;
private static final int LIFE_DRAW_Y = 33;
// Width and height are same as score width/height, as numerals are same
// size.
private static final int LIFE_DRAW_X2 = LIFE_DRAW_X + SCORE_WIDTH;
private static final int LIFE_DRAW_Y2 = LIFE_DRAW_Y + SCORE_HEIGHT;
private static int BONUS_DRAW_X = 152;
// Bonus draw Y is same as score; same y level
// widths and height same as score
// POWERUPS
private static final int POWERUP_DRAW_X = 418;
private static final int POWERUP_DRAW_Y = 37;
private static final int POWERUP_DRAW_X2 = POWERUP_DRAW_X + GameConstants.GOODIE_SIZE_X;
private static final int POWERUP_DRAW_Y2 = POWERUP_DRAW_Y + GameConstants.GOODIE_SIZE_Y;
private final GameWorldLogic universe;
/**
*
* Creates a new instance of a standard surface for the given world.
*
* @param universe
*
*/
public StandardSurface(final GameWorldLogic universe) {
this.universe = universe;
}
/**
*
* Draws the entire world at the current origin of the graphics context, by an area of 640x480, including
* UI.
*
* @param g2d
* graphics configuration to create a compatible buffered image.
*
* @param showUI
* {@code true} to draw the UI at 0,0 and game at 0,80, {@code false} to draw game at 0,0.
* this is typically {@code false} for when the splash screen is visible and {@code true} otherwise.
*
*/
public void renderDirect(Graphics2D g2d, boolean showUI) {
renderToGraphics(g2d, showUI);
}
/**
*
* Draws the entire world onto a 640x480 image that will be stored in video memory.
* Because of this, the image returned may suddenly cease to exist at any point. If
* it does, this method must be called again before repainting. It is important that
* clients check for lost contents of the image returned before using it.
*
* @param configuration
* graphics configuration for the current hardware
*
* @param showUI
* {@code true} to draw the UI at 0,0 and game at 0,80, {@code false} to draw game at 0,0.
* this is typically {@code false} for when the splash screen is visible and {@code true} otherwise
*
*/
public VolatileImage renderVolatile(GraphicsConfiguration configuration, boolean showUI) {
VolatileImage page = configuration.createCompatibleVolatileImage(SURFACE_SIZE_X, SURFACE_SIZE_Y);
do {
Graphics2D g2d = page.createGraphics();
try {
renderToGraphics(g2d, showUI);
} finally {
g2d.dispose();
}
} while (page.contentsLost() );
// Successful draw; return the image.
return page;
}
/**
*
* Renders the world to the graphics context.
*
* @param showUI
* {@code true} to draw the UI at 0,0 and game at 0,80, {@code false} to draw game at 0,0.
* this is typically {@code false} for when the splash screen is visible and {@code true} otherwise.
*
* @param g2d
*/
private void renderToGraphics(Graphics2D g2d, boolean showUI) {
// Clear
g2d.clearRect(0, 0, GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT + GameConstants.UI_HEIGHT);
if (showUI) {
/* --------------------- Initial Banner ---------------------- */
WorldResource rsrc = universe.getResource();
g2d.drawImage(rsrc.getBanner(),
0, 0,
GameConstants.SCREEN_WIDTH, GameConstants.UI_HEIGHT,
0, 0,
GameConstants.SCREEN_WIDTH, GameConstants.UI_HEIGHT,
null);
/* ------------------------- Health -------------------------- */
// Normalise bonzo's current health with drawing.
double healthWidth = ((double)universe.getBonzoHealth()) * HEALTH_MULTIPLIER;
g2d.drawImage(rsrc.getEnergyBar(),
HEALTH_DRAW_X, HEALTH_DRAW_Y,
HEALTH_DRAW_X + (int)healthWidth, HEALTH_DRAW_Y2,
0, 0,
(int)healthWidth, 10,
null);
/* -------------------------- Score -------------------------- */
for (int i = 0; i < GameWorldLogic.SCORE_NUM_DIGITS; i++) {
int drawToX = SCORE_DRAW_X + (SCORE_WIDTH * i);
// draw to Y is always the same
int drawFromX = SCORE_WIDTH * universe.getScoreDigits()[i];
// draw from Y is always the same, 0
g2d.drawImage(rsrc.getScoreNumbersSheet(),
drawToX, SCORE_DRAW_Y,
drawToX + SCORE_WIDTH, SCORE_DRAW_Y2,
drawFromX, 0,
drawFromX + SCORE_WIDTH, SCORE_HEIGHT,
null);
}
/* -------------------- Bonus Countdown ---------------------- */
for (int i = 0; i < GameWorldLogic.BONUS_NUM_DIGITS; i++) {
int drawToX = BONUS_DRAW_X + (SCORE_WIDTH * i);
// draw to Y is always the same
int drawFromX = SCORE_WIDTH * universe.getBonusDigits()[i];
// draw from Y is always the same, 0
g2d.drawImage(rsrc.getBonusNumbersSheet(),
drawToX, SCORE_DRAW_Y,
drawToX + SCORE_WIDTH, SCORE_DRAW_Y2,
drawFromX, 0,
drawFromX + SCORE_WIDTH, SCORE_HEIGHT,
null);
}
/* ------------------------- Lives --------------------------- */
{
int lifeDigit = universe.getLifeDigit();
if (lifeDigit >= 0) {
assert lifeDigit < 10;
int drawFromX = SCORE_WIDTH * lifeDigit;
g2d.drawImage(rsrc.getScoreNumbersSheet(),
LIFE_DRAW_X, LIFE_DRAW_Y,
LIFE_DRAW_X2, LIFE_DRAW_Y2,
drawFromX, 0,
drawFromX + SCORE_WIDTH, SCORE_HEIGHT,
null);
} else {
g2d.drawImage(CoreResource.INSTANCE.getInfinity(),
INFINITY_DRAW_X, INFINITY_DRAW_Y,
INFINITY_DRAW_X2, INFINITY_DRAW_Y2,
0, 0,
INFINITY_WIDTH, INFINITY_HEIGHT,
null);
}
}
/* ------------------------ Powerup --------------------------- */
{
if (universe.isPowerupVisible() ) {
Powerup powerup = universe.getCurrentPowerup();
assert powerup != null : "Powerup should be invisible if null";
g2d.drawImage(rsrc.getGoodieSheet(),
POWERUP_DRAW_X, POWERUP_DRAW_Y,
POWERUP_DRAW_X2, POWERUP_DRAW_Y2,
powerup.drawFromX(), Powerup.POWERUP_DRAW_FROM_Y,
powerup.drawFromX2(), Powerup.POWERUP_DRAW_FROM_Y2,
null);
}
}
}
/* ----------------------- Actual Game -------------------------- */
// game is drawn at 80 pixels down if UI was drawn
if (showUI) {
g2d.translate(0, 80);
universe.paintTo(g2d);
g2d.translate(0, -80);
} else {
universe.paintTo(g2d);
}
}
}