Download Building a Java First-Person Shoote

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Indexed color wikipedia , lookup

Stereo display wikipedia , lookup

Image editing wikipedia , lookup

BSAVE (bitmap format) wikipedia , lookup

Double Dragon (video game) wikipedia , lookup

Apple II graphics wikipedia , lookup

2.5D wikipedia , lookup

Hold-And-Modify wikipedia , lookup

Spatial anti-aliasing wikipedia , lookup

Transcript
3D Java Game Programming – Episode 15
Building a Java First-Person Shoote
Episode 15– Textures + More!
In this episode CHERNO discusses how to display the FPS counter on the screen and the addition of a
texture to the scene.
The changes are rather minimal but the difference in how the application looks is rather impressive.
Figure 1 - Episode 15 version of Minefront
The code changes only took place in two older Java classes (Display.java and Render3D.java) and the
introduction of a new class to handle the reading in of the Texture.
Adding FPS to the Screen
The application made use of typical graphics methods to draw a string (g.drawString()) and set the color
and font type. The change required the introduction of a new variable fps that was constantly updated
in the run() method. Here is the entire Java class with new portions highlighted.
Table 1- Display.java for Episode 15
package com.mime.minefront;
import java.awt.Canvas;
1
3D Java Game Programming – Episode 15
import
import
import
import
import
import
import
import
import
import
java.awt.Color;
java.awt.Cursor;
java.awt.Font;
java.awt.Graphics;
java.awt.Insets;
java.awt.Point;
java.awt.Toolkit;
java.awt.image.BufferStrategy;
java.awt.image.BufferedImage;
java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import com.mime.minefront.graphics.Screen;
import com.mime.minefront.input.Controller;
import com.mime.minefront.input.InputHandler;
public class Display extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static final String TITLE = "Minefront Pre-Alpha 0.02";
public static final int FRAMES_PER_SECOND = 60;
private Thread thread;
private boolean running = false; // indicates if the game is running or not
private
private
private
private
Screen screen;
Game game;
BufferedImage img;
int[] pixels;
private InputHandler input;
private int newX = 0;
private int oldX = 0;
private int fps = 0;
public Display() {
screen = new Screen(WIDTH, HEIGHT);
img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
game = new Game();
input = new InputHandler();
addKeyListener(input);
addMouseListener(input);
addMouseMotionListener(input);
addFocusListener(input);
}
2
3D Java Game Programming – Episode 15
private void start() {
if (running) {
return;
}
running = true;
thread = new Thread(this);
thread.start();
}
private void stop() {
if (!running) {
return;
}
running = false;
try {
thread.join();
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
private void tick() {
game.tick(input.key);
}
private void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
screen.render(game);
for (int i = 0; i < WIDTH * HEIGHT; i++) {
pixels[i] = screen.pixels[i];
}
Graphics g = bs.getDrawGraphics();
g.drawImage(img, 0, 0, WIDTH, HEIGHT, null);
g.setFont(new Font("Verdana", Font.BOLD, 20));
g.setColor(Color.yellow);
g.drawString("FPS: " + fps, 20, 40);
g.dispose();
bs.show();
}
@Override
public void run() {
int frames = 0;
frames drawn in one second
// holds the number of
3
3D Java Game Programming – Episode 15
double unprocessedSeconds = 0;
// accumulates time
long previousTime = System.nanoTime(); // start counting
double secondsPerTick = 1 / (double)FRAMES_PER_SECOND;
defining 60 ticks in a second or in other words
// We are
// declaring
that we want 60 frames / second
int tickCount = 0;
assume 60 ticks signal that we
// If 60 ticks (or 1 second) we
// should render a
frame
boolean ticked = false;
frame should be rendered
// our signal indicator that a
while(running) {
long currentTime = System.nanoTime(); // holds the time now
long passedTime = currentTime - previousTime; // holds the time
between now and when stop
// watch started
previousTime = currentTime;
// remember
for the next time check
unprocessedSeconds = unprocessedSeconds + (passedTime /
1000000000.0); // time elapsed
while (unprocessedSeconds > secondsPerTick) { // Has 1/60 sec
elapsed, i.e. a tick
tick();
// do it!
unprocessedSeconds -= secondsPerTick; // remember the
time that went over a tick
ticked = true;
// signal to render a frame
tickCount++;
if (tickCount % FRAMES_PER_SECOND == 0) {
// approximately a second has passed, output
fps = frames;
//previousTime += 1000;
//
fudge factor ???
tickCount = 0;
frames = 0;
}
}
if (ticked) {
render();
// handles rendering things to the screen
frames++;
ticked = false;
}
newX = InputHandler.MouseX;
if (newX > oldX) {
Controller.turnRight = true;
} else if (newX < oldX) {
Controller.turnLeft = true;
} else {
Controller.turnLeft = false;
4
3D Java Game Programming – Episode 15
Controller.turnRight = false;
}
oldX = newX;
}
}
public void resizeToInternalSize(JFrame frame, int internalWidth, int
internalHeight) {
Insets insets = frame.getInsets();
final int newWidth = internalWidth + insets.left + insets.right;
final int newHeight = internalHeight + insets.top + insets.bottom;
Runnable resize = new Runnable() {
public void run() {
setSize(newWidth, newHeight);
}
};
if (SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(resize);
} catch (Exception e) {
// ignore ...but will be no no if using Sonar!
}
} else {
resize.run();
}
validate();
}
public static void main(String[] args) {
BufferedImage cursor = new BufferedImage(16,16,
BufferedImage.TYPE_INT_ARGB);
Cursor blank = Toolkit.getDefaultToolkit().createCustomCursor(cursor,
new Point(0,0), "blank");
Display game = new Display();
JFrame frame = new JFrame();
frame.add(game);
frame.getContentPane().setCursor(blank);
frame.setTitle(TITLE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(WIDTH, HEIGHT);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
// ensure the display canvas is the right size!
game.resizeToInternalSize(frame, WIDTH, HEIGHT);
game.start();
}
}
5
3D Java Game Programming – Episode 15
The changes above inserts at the top-left corner the number of frames per second the screen is
updating.
Using Paint.NET to create the texture
We will use the free program Paint.NET to create a texture or graphics image and to use as a texture.
What is a texture?
An image (an array of pixels) used to represent or wallpaper a 3D surface is a texture. The texture is
usually has two-dimensional qualities such as color and brightness. A texture is used to wrap around any
3-dimensional object (called texture mapping).
You can easily find and download Paint.NET at http://www.getpaint.net.
When you start it up you will see the UI.
Tool Bar
Image List
Main Editing Area
(canvas)
Figure 2 - Paint.NET UI
6
3D Java Game Programming – Episode 15
Paint.NET started out as a senior project to create a paint tool to replace Microsoft Windows Paint
program. In addition, it focused on using .NET technology using the C# programming language. The tool
was made freely available to the general public (donations are encouraged) and it seems to be quite
popular among the programming community that creates indie or game jam games.
The four floating child windows – Tools, History, Layers, and Colors are all movable. You can close them
or toggle them on and off by using the keys F5, F6, F7, and F8 respectively.
The manner in which the texture was created was rather simple.
1.
2.
3.
4.
Open Paint.NET
Select the Image menu item and click on Resize to display the Resize dialog box
Turn off “Maintain aspect ratio” if the checkbox is clicked
Enter 8 for the Width and the Height
Figure 3 - Resizing our texture
5. The canvas will be rather too small to work with so click on the Zoom In icon. I changed it to
2400%.
7
3D Java Game Programming – Episode 15
Zoom into the
canvas
Gradient tool
Select gray color
Figure 4 - Our small texture
6.
7.
8.
9.
Select View menu and select Pixel Grid.
Click on the gradient tool from the tool bar
Select dark gray as the Primary color by clicking on it from the Color dialog
Move the mouse to the bottom-right corner of the canvas (your 8x8 grid) and click the leftmouse button and drag it diagonally to the top-left corner
8
3D Java Game Programming – Episode 15
Pencil tool
Exchange color
Figure 5 - Creating your texture
10. Save the above image as floor.png. click “OK” in “Save Configuration” dialog window.
11. Now click on the double arrow under the dialog box that has “Primary” as its selection. This will
swap the white with the gray.
12. Select the “pencil” tool from the Tools dialog and color the texture image as shown below.
9
3D Java Game Programming – Episode 15
Figure 6 - Update floor texture
13. Select “Save As” from the file menu and save the image as floor2.png.
Updating the Code to use the new Texture
Create a new class under com.mime.minefront.graphics named Texture.java.
Table 2 - Texture.java
package com.mime.minefront.graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
public class Texture {
public static Render floor = loadBitmap("/textures/floor2.png");
public static Render loadBitmap(String fileName) {
try {
10
3D Java Game Programming – Episode 15
BufferedImage image =
ImageIO.read(Texture.class.getResource(fileName));
int width = image.getWidth();
int height = image.getHeight();
Render result = new Render(width, height);
image.getRGB(0, 0, width, height, result.pixels, 0, width);
return result;
} catch (Exception e) {
System.out.println("CRASH!");
throw new RuntimeException(e);
}
}
}
The main purpose of the class is to read in the image that corresponds to the texture into a Render
object (which essentially saves it into a pixels array).
The image is extracted and saved into the array of pixels in the Render object. The key BufferedImage
method is:
public int[] getRGB(int startX,
int startY,
int w,
int h,
int[] rgbArray,
int offset,
int scansize)
The startX and startY is the top-left location in the image to start moving into the pixel array. The next
two parameters the width and height (w and h) or the number of pixels across and the number of pixels
down in the image are set to the actual width and height of the image. The rgbArray holds the translated
pixels from the image (with the correct display colors). The offset specifies the offset into the array
where the pixels from the source image shall be moved into. The resulting Render object is returned.
Another thing to point out about the Texture object is that the creation of the class shall automatically
read in the texture image and get it ready for processing. This means that the Texture class only
processes one and only one image at a Time. A more general class to handle more than one texture for
a game would have been a better design since it would allow us to easily re-use in future game
development projects.
Accessing the texture resource
You will need to create a new directory for the game textures. The convention is to create a directory
named resources or res to hold all game resources such as images, sounds and data files. We will place
11
3D Java Game Programming – Episode 15
our two images (floor.png and floor2.png) into a project directory named resources/textures as shown
below.
Figure 7 - Package Explorer in Episode 15
You will need to perform the following steps in order to include the resources directory in the classpath
so that the line:
BufferedImage image = ImageIO.read(Texture.class.getResource(fileName));
Will work.
1. Highlight the Project name “Minefront”
2. Right-click and select “Build Path” and from the context sub-menu select “Configure Build Path”
3. Click on the “Add Class Folder” button
12
3D Java Game Programming – Episode 15
Figure 8 - Adding a folder to the classpath
4. Click on the checkbox next to the /resources directory.
Using the New Texture
The only other change left is to replace the line that sets the pixel contents, note the highlighted change
below:
Table 3 - Render3D.java for Episode 15
package com.mime.minefront.graphics;
import com.mime.minefront.Game;
public class Render3D extends Render {
public double[] zBuffer;
private double renderDistance = 5000;
public Render3D(int width, int height) {
super(width, height);
zBuffer = new double[width* height];
}
public void floor(Game game) {
double floorPosition = 8.0;
double ceilingPosition = 8.0;
double forward = game.controls.z;
13
3D Java Game Programming – Episode 15
double right = game.controls.x;
double rotation = game.controls.rotation;
double cosine = Math.cos(rotation);
double sine = Math.sin(rotation);
for (int y=0; y < height; y++) {
double ceiling = (y + - height / 2.0) / height;
double z = (ceiling != 0 ? (floorPosition / ceiling) : 0.0);
if (ceiling < 0 ) {
z = ceilingPosition / -ceiling;
}
for (int x = 0; x < width; x++) {
double depth = (x - width / 2.0) / height;
depth *= z;
double xx = depth * cosine + z * sine; // + right;
double yy = z * cosine - depth * sine; // + forward;
int xPix = (int)(xx + right);
int yPix = (int)(yy + forward);
int pixelPosition = x + y * width;
zBuffer[pixelPosition] = z;
//pixels[pixelPosition] = ( ((xPix & 15) << 4) | ((yPix &
15)) << 12)
& 0x00ffffff;
pixels[pixelPosition] = Texture.floor.pixels[(xPix & 7)
(yPix & 7 ) * 8];
if (z > 500 || y == (height / 2)) {
pixels[pixelPosition] = 0;
}
}
}
}
public void renderDistanceLimiter() {
for (int i=0; i < width * height; i++) {
int color = pixels[i];
int brightness = (int) (renderDistance / zBuffer[i]);
if (brightness < 0) brightness = 0;
if (brightness > 255) brightness = 255;
int r = (color >> 16) & 0xff;
int g = (color >> 8) & 0xff;
int b = color & 0xff;
r = r * brightness / 255;
g = g * brightness / 255;
b = b * brightness / 255;
14
+
3D Java Game Programming – Episode 15
pixels[i] = r << 16 | g << 8 | b;
}
}
}
The algorithm used to tile the floor and ceiling is very smart. The 3 rightmost bits of the xPix value (a
value from 000-111 or 0-7) and the 3 rightmost bits of the yPix as well are used to access the pixel
position to draw to the screen in our Texture.floor.pixels array!
That’s it….try replacing the texture used with other 8x8 images to see different effects and rooms.
15