|
/*
DEVELOPING GAME IN JAVA
Caracteristiques
Editeur : NEW RIDERS
Auteur : BRACKEEN
Parution : 09 2003
Pages : 972
Isbn : 1-59273-005-1
Reliure : Paperback
Disponibilite : Disponible a la librairie
*/
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferUShort;
import java.awt.image.IndexColorModel;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class GameObjectTest extends GameCore3D {
public static void main(String[] args) {
new GameObjectTest().run();
}
private static final int NUM_BOTS = 5;
private static final int NUM_POWER_UPS = 7;
private static final int GAME_AREA_SIZE = 1500;
private static final float PLAYER_SPEED = .5f;
private static final float PLAYER_TURN_SPEED = 0.04f;
private static final float BULLET_HEIGHT = 75;
protected GameAction fire = new GameAction("fire",
GameAction.DETECT_INITAL_PRESS_ONLY);
private PolygonGroup robotModel;
private PolygonGroup powerUpModel;
private PolygonGroup blastModel;
private GameObjectManager gameObjectManager;
private TexturedPolygon3D floor;
public void init() {
init(LOW_RES_MODES);
inputManager.mapToKey(fire, KeyEvent.VK_SPACE);
inputManager.mapToMouse(fire, InputManager.MOUSE_BUTTON_1);
}
public void createPolygons() {
// create floor
Texture floorTexture = Texture.createTexture("../images/roof1.png",
true);
((ShadedTexture) floorTexture)
.setDefaultShadeLevel(ShadedTexture.MAX_LEVEL * 3 / 4);
Rectangle3D floorTextureBounds = new Rectangle3D(new Vector3D(0, 0, 0),
new Vector3D(1, 0, 0), new Vector3D(0, 0, 1), floorTexture
.getWidth(), floorTexture.getHeight());
float s = GAME_AREA_SIZE;
floor = new TexturedPolygon3D(new Vector3D[] { new Vector3D(-s, 0, s),
new Vector3D(s, 0, s), new Vector3D(s, 0, -s),
new Vector3D(-s, 0, -s) });
floor.setTexture(floorTexture, floorTextureBounds);
// set up the local lights for the model.
float ambientLightIntensity = .5f;
List lights = new LinkedList();
lights.add(new PointLight3D(-100, 100, 100, .5f, -1));
lights.add(new PointLight3D(100, 100, 0, .5f, -1));
// load the object models
ObjectLoader loader = new ObjectLoader();
loader.setLights(lights, ambientLightIntensity);
try {
robotModel = loader.loadObject("../images/robot.obj");
powerUpModel = loader.loadObject("../images/cube.obj");
blastModel = loader.loadObject("../images/blast.obj");
} catch (IOException ex) {
ex.printStackTrace();
}
// create game objects
gameObjectManager = new SimpleGameObjectManager();
gameObjectManager.addPlayer(new GameObject(new PolygonGroup("Player")));
gameObjectManager.getPlayer().getLocation().y = 5;
for (int i = 0; i < NUM_BOTS; i++) {
Bot object = new Bot((PolygonGroup) robotModel.clone());
placeObject(object);
}
for (int i = 0; i < NUM_POWER_UPS; i++) {
GameObject object = new GameObject((PolygonGroup) powerUpModel
.clone());
placeObject(object);
}
}
// randomly place objects in game area
public void placeObject(GameObject object) {
float size = GAME_AREA_SIZE;
object.getLocation().setTo((float) (Math.random() * size - size / 2),
0, (float) (Math.random() * size - size / 2));
gameObjectManager.add(object);
}
public void createPolygonRenderer() {
viewWindow = new ViewWindow(0, 0, screen.getWidth(),
screen.getHeight(), (float) Math.toRadians(75));
Transform3D camera = new Transform3D();
polygonRenderer = new ZBufferedRenderer(camera, viewWindow);
}
public void updateWorld(long elapsedTime) {
float angleVelocity;
// cap elapsedTime
elapsedTime = Math.min(elapsedTime, 100);
GameObject player = gameObjectManager.getPlayer();
MovingTransform3D playerTransform = player.getTransform();
Vector3D velocity = playerTransform.getVelocity();
playerTransform.stop();
float x = -playerTransform.getSinAngleY();
float z = -playerTransform.getCosAngleY();
if (goForward.isPressed()) {
velocity.add(x, 0, z);
}
if (goBackward.isPressed()) {
velocity.add(-x, 0, -z);
}
if (goLeft.isPressed()) {
velocity.add(z, 0, -x);
}
if (goRight.isPressed()) {
velocity.add(-z, 0, x);
}
if (fire.isPressed()) {
float cosX = playerTransform.getCosAngleX();
float sinX = playerTransform.getSinAngleX();
Blast blast = new Blast((PolygonGroup) blastModel.clone(),
new Vector3D(cosX * x, sinX, cosX * z));
// blast starting location needs work. looks like
// the blast is coming out of your forehead when
// you're shooting down.
blast.getLocation().setTo(player.getX(),
player.getY() + BULLET_HEIGHT, player.getZ());
gameObjectManager.add(blast);
}
velocity.multiply(PLAYER_SPEED);
playerTransform.setVelocity(velocity);
// look up/down (rotate around x)
angleVelocity = Math.min(tiltUp.getAmount(), 200);
angleVelocity += Math.max(-tiltDown.getAmount(), -200);
playerTransform.setAngleVelocityX(angleVelocity * PLAYER_TURN_SPEED
/ 200);
// turn (rotate around y)
angleVelocity = Math.min(turnLeft.getAmount(), 200);
angleVelocity += Math.max(-turnRight.getAmount(), -200);
playerTransform.setAngleVelocityY(angleVelocity * PLAYER_TURN_SPEED
/ 200);
// for now, mark the entire world as visible in this frame.
gameObjectManager.markAllVisible();
// update objects
gameObjectManager.update(elapsedTime);
// limit look up/down
float angleX = playerTransform.getAngleX();
float limit = (float) Math.PI / 2;
if (angleX < -limit) {
playerTransform.setAngleX(-limit);
} else if (angleX > limit) {
playerTransform.setAngleX(limit);
}
// set the camera to be 100 units above the player
Transform3D camera = polygonRenderer.getCamera();
camera.setTo(playerTransform);
camera.getLocation().add(0, 100, 0);
}
public void draw(Graphics2D g) {
polygonRenderer.startFrame(g);
// draw floor
polygonRenderer.draw(g, floor);
// draw objects
gameObjectManager.draw(g, (GameObjectRenderer) polygonRenderer);
polygonRenderer.endFrame(g);
super.drawText(g);
}
}
/**
* The Bot game object is a small static bot with a turret that turns to face
* the player.
*/
class Bot extends GameObject {
private static final float TURN_SPEED = .0005f;
private static final long DECISION_TIME = 2000;
protected MovingTransform3D mainTransform;
protected MovingTransform3D turretTransform;
protected long timeUntilDecision;
protected Vector3D lastPlayerLocation;
public Bot(PolygonGroup polygonGroup) {
super(polygonGroup);
mainTransform = polygonGroup.getTransform();
PolygonGroup turret = polygonGroup.getGroup("turret");
if (turret != null) {
turretTransform = turret.getTransform();
} else {
System.out.println("No turret defined!");
}
lastPlayerLocation = new Vector3D();
}
public void notifyVisible(boolean visible) {
if (!isDestroyed()) {
if (visible) {
setState(STATE_ACTIVE);
} else {
setState(STATE_IDLE);
}
}
}
public void update(GameObject player, long elapsedTime) {
if (turretTransform == null || isIdle()) {
return;
}
Vector3D playerLocation = player.getLocation();
if (playerLocation.equals(lastPlayerLocation)) {
timeUntilDecision = DECISION_TIME;
} else {
timeUntilDecision -= elapsedTime;
if (timeUntilDecision <= 0 || !turretTransform.isTurningY()) {
float x = player.getX() - getX();
float z = player.getZ() - getZ();
turretTransform.turnYTo(x, z, -mainTransform.getAngleY(),
TURN_SPEED);
lastPlayerLocation.setTo(playerLocation);
timeUntilDecision = DECISION_TIME;
}
}
super.update(player, elapsedTime);
}
}
/**
* The ZBuffer class implements a z-buffer, or depth-buffer, that records the
* depth of every pixel in a 3D view window. The value recorded for each pixel
* is the inverse of the depth (1/z), so there is higher precision for close
* objects and a lower precision for far-away objects (where high depth
* precision is not as visually important).
*/
class ZBuffer {
private short[] depthBuffer;
private int width;
private int height;
/**
* Creates a new z-buffer with the specified width and height.
*/
public ZBuffer(int width, int height) {
depthBuffer = new short[width * height];
this.width = width;
this.height = height;
clear();
}
/**
* Gets the width of this z-buffer.
*/
public int getWidth() {
return width;
}
/**
* Gets the height of this z-buffer.
*/
public int getHeight() {
return height;
}
/**
* Gets the array used for the depth buffer
*/
public short[] getArray() {
return depthBuffer;
}
/**
* Clears the z-buffer. All depth values are set to 0.
*/
public void clear() {
for (int i = 0; i < depthBuffer.length; i++) {
depthBuffer[i] = 0;
}
}
/**
* Sets the depth of the pixel at at specified offset, overwriting its
* current depth.
*/
public void setDepth(int offset, short depth) {
depthBuffer[offset] = depth;
}
/**
* Checks the depth at the specified offset, and if the specified depth is
* lower (is greater than or equal to the current depth at the specified
* offset), then the depth is set and this method returns true. Otherwise,
* no action occurs and this method returns false.
*/
public boolean checkDepth(int offset, short depth) {
if (depth >= depthBuffer[offset]) {
depthBuffer[offset] = depth;
return true;
} else {
return false;
}
}
}
/**
* The FastTexturedPolygonRenderer is a PolygonRenderer that efficiently renders
* Textures.
*/
class FastTexturedPolygonRenderer extends PolygonRenderer {
public static final int SCALE_BITS = 12;
public static final int SCALE = 1 << SCALE_BITS;
public static final int INTERP_SIZE_BITS = 4;
public static final int INTERP_SIZE = 1 << INTERP_SIZE_BITS;
protected Vector3D a = new Vector3D();
protected Vector3D b = new Vector3D();
protected Vector3D c = new Vector3D();
protected Vector3D viewPos = new Vector3D();
protected BufferedImage doubleBuffer;
protected short[] doubleBufferData;
protected HashMap scanRenderers;
public FastTexturedPolygonRenderer(Transform3D camera, ViewWindow viewWindow) {
this(camera, viewWindow, true);
}
public FastTexturedPolygonRenderer(Transform3D camera,
ViewWindow viewWindow, boolean clearViewEveryFrame) {
super(camera, viewWindow, clearViewEveryFrame);
}
protected void init() {
destPolygon = new TexturedPolygon3D();
scanConverter = new ScanConverter(viewWindow);
// create renders for each texture (HotSpot optimization)
scanRenderers = new HashMap();
scanRenderers.put(PowerOf2Texture.class, new PowerOf2TextureRenderer());
scanRenderers.put(ShadedTexture.class, new ShadedTextureRenderer());
scanRenderers.put(ShadedSurface.class, new ShadedSurfaceRenderer());
}
public void startFrame(Graphics2D g) {
// initialize buffer
if (doubleBuffer == null
|| doubleBuffer.getWidth() != viewWindow.getWidth()
|| doubleBuffer.getHeight() != viewWindow.getHeight()) {
doubleBuffer = new BufferedImage(viewWindow.getWidth(), viewWindow
.getHeight(), BufferedImage.TYPE_USHORT_565_RGB);
//doubleBuffer = g.getDeviceConfiguration().createCompatibleImage(
//viewWindow.getWidth(), viewWindow.getHeight());
DataBuffer dest = doubleBuffer.getRaster().getDataBuffer();
doubleBufferData = ((DataBufferUShort) dest).getData();
}
// clear view
if (clearViewEveryFrame) {
for (int i = 0; i < doubleBufferData.length; i++) {
doubleBufferData[i] = 0;
}
}
}
public void endFrame(Graphics2D g) {
// draw the double buffer onto the screen
g.drawImage(doubleBuffer, viewWindow.getLeftOffset(), viewWindow
.getTopOffset(), null);
}
protected void drawCurrentPolygon(Graphics2D g) {
if (!(sourcePolygon instanceof TexturedPolygon3D)) {
// not a textured polygon - return
return;
}
TexturedPolygon3D poly = (TexturedPolygon3D) destPolygon;
Texture texture = poly.getTexture();
ScanRenderer scanRenderer = (ScanRenderer) scanRenderers.get(texture
.getClass());
scanRenderer.setTexture(texture);
Rectangle3D textureBounds = poly.getTextureBounds();
a.setToCrossProduct(textureBounds.getDirectionV(), textureBounds
.getOrigin());
b.setToCrossProduct(textureBounds.getOrigin(), textureBounds
.getDirectionU());
c.setToCrossProduct(textureBounds.getDirectionU(), textureBounds
.getDirectionV());
int y = scanConverter.getTopBoundary();
viewPos.y = viewWindow.convertFromScreenYToViewY(y);
viewPos.z = -viewWindow.getDistance();
while (y <= scanConverter.getBottomBoundary()) {
ScanConverter.Scan scan = scanConverter.getScan(y);
if (scan.isValid()) {
viewPos.x = viewWindow.convertFromScreenXToViewX(scan.left);
int offset = (y - viewWindow.getTopOffset())
* viewWindow.getWidth()
+ (scan.left - viewWindow.getLeftOffset());
scanRenderer.render(offset, scan.left, scan.right);
}
y++;
viewPos.y--;
}
}
/**
* The ScanRenderer class is an abstract inner class of
* FastTexturedPolygonRenderer that provides an interface for rendering a
* horizontal scan line.
*/
public abstract class ScanRenderer {
protected Texture currentTexture;
public void setTexture(Texture texture) {
this.currentTexture = texture;
}
public abstract void render(int offset, int left, int right);
}
//================================================
// FASTEST METHOD: no texture (for comparison)
//================================================
public class Method0 extends ScanRenderer {
public void render(int offset, int left, int right) {
for (int x = left; x <= right; x++) {
doubleBufferData[offset++] = (short) 0x0007;
}
}
}
//================================================
// METHOD 1: access pixel buffers directly
// and use textures sizes that are a power of 2
//================================================
public class Method1 extends ScanRenderer {
public void render(int offset, int left, int right) {
for (int x = left; x <= right; x++) {
int tx = (int) (a.getDotProduct(viewPos) / c
.getDotProduct(viewPos));
int ty = (int) (b.getDotProduct(viewPos) / c
.getDotProduct(viewPos));
doubleBufferData[offset++] = currentTexture.getColor(tx, ty);
viewPos.x++;
}
}
}
//================================================
// METHOD 2: avoid redundant calculations
//================================================
public class Method2 extends ScanRenderer {
public void render(int offset, int left, int right) {
float u = a.getDotProduct(viewPos);
float v = b.getDotProduct(viewPos);
float z = c.getDotProduct(viewPos);
float du = a.x;
float dv = b.x;
float dz = c.x;
for (int x = left; x <= right; x++) {
doubleBufferData[offset++] = currentTexture.getColor(
(int) (u / z), (int) (v / z));
u += du;
v += dv;
z += dz;
}
}
}
//================================================
// METHOD 3: use ints instead of floats
//================================================
public class Method3 extends ScanRenderer {
public void render(int offset, int left, int right) {
int u = (int) (SCALE * a.getDotProduct(viewPos));
int v = (int) (SCALE * b.getDotProduct(viewPos));
int z = (int) (SCALE * c.getDotProduct(viewPos));
int du = (int) (SCALE * a.x);
int dv = (int) (SCALE * b.x);
int dz = (int) (SCALE * c.x);
for (int x = left; x <= right; x++) {
doubleBufferData[offset++] = currentTexture.getColor(u / z, v
/ z);
u += du;
v += dv;
z += dz;
}
}
}
//================================================
// METHOD 4: reduce the number of divides
// (interpolate every 16 pixels)
// Also, apply a VM optimization by referring to
// the texture's class rather than it's parent class.
//================================================
// the following three ScanRenderers are the same, but refer
// to textures explicitly as either a PowerOf2Texture, a
// ShadedTexture, or a ShadedSurface.
// This allows HotSpot to do some inlining of the textures'
// getColor() method, which significantly increases
// performance.
public class PowerOf2TextureRenderer extends ScanRenderer {
public void render(int offset, int left, int right) {
PowerOf2Texture texture = (PowerOf2Texture) currentTexture;
float u = SCALE * a.getDotProduct(viewPos);
float v = SCALE * b.getDotProduct(viewPos);
float z = c.getDotProduct(viewPos);
float du = INTERP_SIZE * SCALE * a.x;
float dv = INTERP_SIZE * SCALE * b.x;
float dz = INTERP_SIZE * c.x;
int nextTx = (int) (u / z);
int nextTy = (int) (v / z);
int x = left;
while (x <= right) {
int tx = nextTx;
int ty = nextTy;
int maxLength = right - x + 1;
if (maxLength > INTERP_SIZE) {
u += du;
v += dv;
z += dz;
nextTx = (int) (u / z);
nextTy = (int) (v / z);
int dtx = (nextTx - tx) >> INTERP_SIZE_BITS;
int dty = (nextTy - ty) >> INTERP_SIZE_BITS;
int endOffset = offset + INTERP_SIZE;
while (offset < endOffset) {
doubleBufferData[offset++] = texture.getColor(
tx >> SCALE_BITS, ty >> SCALE_BITS);
tx += dtx;
&nbs
|