I was asked by uni to create a 2d racing game in java.
we were told to use one jcomponent in a jframe.
we told to update the game by the swing timer and call repaint() somewhere appropriate in their.
I have now finished but all the way through my use of Graphics g was annoying me. for example below is my gameobject class that players and stuff derive from.
import java.awt.Component;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
/*
* Provides The basic functionality of Displaying a game object on the screen
* and methods able to change the image
*/
public class GameObject {
public Vector position = new Vector();
//image set up variables
protected BufferedImage[] buffImage;
protected ImageIcon icon = new ImageIcon();
protected int numberOfImages;
protected String filePrefix;
protected String fileExt;
//////////////interactive variables
//image
protected int imageNumber = 0;
public GameObject(int numOfImages, String filePre, String fileE)
{
//if this gives grief about constructors implement a custom initObject() method that can be overridden with super.
this.numberOfImages = numOfImages;
buffImage = new BufferedImage[numOfImages];
filePrefix = filePre;
fileExt = fileE;
}
//Adds images to array
protected void initImageArray()
{
for(int i = 0; i <numberOfImages; i++)
{
try{
buffImage[i] = (BufferedImage) ImageIO.read(new File(filePrefix + "/" + Integer.toString(i) + fileExt));
}
catch(Exception e)
{
System.out.println(e);
}
}
}
//Draws object to screen
public void draw(Graphics g, Component c){
icon.setImage(getNextImage());
icon.paintIcon(c, g, position.x, position.y);
}
//returns current image
protected BufferedImage getNextImage(){
return buffImage[imageNumber];
}
//
protected void increaseImageNumber(){
if(imageNumber < numberOfImages-1){
imageNumber++;
}
else{
imageNumber = 0;
}
}
protected void decreaseimageNumber(){
if(imageNumber > 0){
imageNumber--;
}else{
imageNumber = numberOfImages - 1;
}
}
}
The bit specifically i don't think im doing right is passing the same "Graphics g" parameter to every objects draw method. it works but when i tried to place 15 stars in the back ground and change their imageIcon image from one buffered image array to the next everything became very jarred. I put this down to sharing this one graphic class. It could also just be that we were told not to use a threaded solution but i think any new comp should be able to handle 17 images updating and changing. the update timer is at every 33 milliseconds.
Thank you to anyone who can help explain to me the best way to achieve what i was after or the proper use/sharing of the graphics object.
Related
I have this code and its supposed to have the text "Simple Animation" scroll across the screen in swirling colors. Right now, it does that, but even after the text moves along, the color still stays. I was wondering if there was a way to have text in the background. For example, I was thinking I could just print out the exact same "Simple Animation" but in the same color as the background and about 10 pixels behind the actual text. However, when I tried this, the white text (that's the background color) just covered the swirling colors. I tried googling if I could have background text, but from I read, the only thing that a background can do is set the color. So, is there a way to have text in the background in a Java Graphics file?
Here is my Code:
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import javax.swing.*;
public class Scrolling_Sign extends JApplet implements Runnable {
String mesag = "Simple Animation";
Font f = new Font("Bauhaus 93",Font.BOLD,72);
Color colors[] = new Color[100000];
Thread runThread;
int Xposition = 600;
public void init() {
setBackground(Color.white);
}
public void start() {
if (runThread == null) {
runThread = new Thread(this);
runThread.start();
}
}
public void stop() {
if (runThread != null) {
runThread.stop();
runThread = null;
}
}
public void run() {
float c = 0;
for (int i = 0; i < colors.length; i++) {
colors[i] = Color.getHSBColor(c, (float)1.0,(float)1.0);
c += .02;
}
int i = 0;
while (true) {
setForeground(colors[i]);
repaint();
i++;
try { Thread.sleep(100); }
catch (InterruptedException e) { }
if (i == colors.length ) i = 0;
}
}
public void paint(Graphics g) {
g.setFont(f);
g.drawString(mesag,Xposition,100);
Xposition--;
if (Xposition < -290) {
Xposition = 600;
}
}
}
Thank you!
Suggestions:
Never draw directly within a JApplet or other top-level window.
Instead draw in the paintComponent of a JPanel that is displayed within the applet. The Swing tutorials will show you how.
Be sure to call the super.paintComponent(g) method within your override, and again read the Swing tutorials to see why. For more tutorials see: Swing Info
This is Swing -- use a Swing Timer to drive your animation, not threads.
If you ever do use Threads, never call Thread#stop() or use any other deprecated methods. Please read Why is Thread.stop deprecated?.
Please look at this answer for an example of Swing animation using a Swing Timer.
Unless this is for a class assignment, don't create JApplets as this is a dead technology, something even Oracle will tell you.
To display text in the background use the java.awt.Graphics method for writing text: drawString(...). Either that or place a JLabel over your background image.
I'm sure I'm going about this all wrong but...
I have a 2D-Array of custom Tile objects that extend JComponent. They contain model information, as well as an override of the paintComponent() method. The tiles really only contain an Image, a String representing their type, and two boolean values indicating whether they contain a player and whether they are able to be walked upon. This Tile class is a superclass for all my Tile objects, which include NormalTile, ActionTile, and EmptyTile currently.
The 2D array itself is contained within a TilePanel class that extends JPanel, and this class has several methods for modifying this array of tiles.
At the top of this chain is the MapManager, which doesn't explicitly extend anything, and instead contains both an instance of TilePanel and JScrollPane, and the TilePanel is added to the JScrollPane.
When my TilePanel is first created, it initializes the 2D array with a series of NormalTiles, which easily display themselves with the test image they use. This all displays just perfectly.
However, immediately after the creation of the TilePanel, I call its addTileBlock(int width, int height, int x, int y, String type) method to change the tiles at a particular point in the 2D array. This is where the problem in updating the display arises.
The Tiles in the specified location do appear to change, according to the data I gather by printing them out, and their behavior when the player steps on them does work according to how their changed type would indicate, yet the change in display is never reflected. The new Tiles (in this case, I have used EmptyTiles) instead simply display the same image the previous NormalTiles used, despite their underlying objects having changed.
As it stands now, "updating" the tiles actually makes the old Tile references point to a new Tile instance, since my Tile objects themselves are immutable. Whenever the update is being done, the TilePanel class simply class an abstract class TileFactory to use its generateTile(String type) method to create the correct Tile instance. It simply uses a switch/case block to return the tiles.
It was suggested to me to attempt using the revalidate() method on the tile objects to fix the problem, which you can see where I've implemented it in the Tile class's constructor. I also previous attempted using it in its paintComponent method, but that did nothing as well.
Here is the addTileBlock method and Tile classes in full:
addTileBlock:
public void addTileBlock(int width, int height, int x, int y, String type)
{
for(int i = 0; i < width; i++)
for(int j = 0; j < height; j++)
{
tiles[i][j] = null;
tiles[i][j] = TileFactory.generateTile(type);
tiles[i][j].repaint();
}
}
Tile Class:
package games.tile.tiles;
import games.tile.Player;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JComponent;
abstract public class Tile extends JComponent implements TileConstants
{
private String type;
private Image tileImage;
private boolean containsPlayer = false;
private boolean walkable;
public Tile(String type, int size, boolean walkable)
{
this.setSize(size, size);
this.type = type;
this.walkable = walkable;
this.setPreferredSize(new Dimension(this.getHeight(), this.getWidth()));
tileImage = null;
revalidate();
}
public Tile(String type, int size, boolean walkable, Image image)
{
this.setSize(size, size);
this.type = type;
this.walkable = walkable;
tileImage = image;
this.setPreferredSize(new Dimension(this.getHeight(), this.getWidth()));
this.revalidate();
}
public boolean getWalkable() { return walkable; }
public void setWalkable(boolean val) { walkable = val; }
public boolean containsPlayer() { return containsPlayer; }
public void setContainsPlayer(boolean val) { containsPlayer = val;}
public Image getImage() { return tileImage; }
public void setImage(Image image) { tileImage = image; }
public String getType() { return type; }
abstract public void applyEffect(Player player);
#Override
public void paintComponent(Graphics g)
{
if(type.equals(EMPTY) || tileImage == null)
{
g.setColor(Color.black);
g.fillRect(0, 0, 32, 32);
}
else
{
g.drawImage(tileImage, 0, 0, null);
}
if(containsPlayer)
{
g.drawImage(Player.PLAYER_IMAGE, 0, 0, null);
}
}
}
Can anyone inform me as to what I've probably done wrong?
It was suggested to me to attempt using the revalidate() method on the tile objects to fix the problem,
revalidate() is done when you add/remove a component from a panel. So the basic code is:
panel.add(...);
panel.revalidate();
panel.repaint();
This assumes you are using a proper layout manager. In your case I would guess a GridLayout.
As it stands now, "updating" the tiles actually makes the old Tile references point to a new Tile instance,
You can't just change the reference of a variable to point to a new Swing component. You need to actually add the component to the panel. So in your case because your are using a grid, I would guess you need to remove the component at the current point on the grid and then add another component at that point.
That is a lot of work. Since you say your components basically just contain an image, an easier approach is to probably create a changeImage(...) image and then invoke repaint() from within that method and the component will repaint itself and you don't need to worry about creating new components and adding them to the panel.
I'm attempting to partially fill a JTextArea based on an object's member field that is between 0 and 1. If I hard code the percentage in the paintComponent function, it works great. But when I try to use the member field as the percentage value, it's always 0.0 in the debugger and no rectangle is painted behind the text.
Why are the member fields seemingly uninitialized within paintComponent()? After calling setPercent(), percentFilled is correct. (I do invalidate the container of the BarGraphText objects after their setPercent() are called.)
EDIT: setPercent() is called after an ActionListener is triggered by a button. Would the separate gui thread have something to do with this failing? It works when the class below is in a JFrame by itself. Update: Having a button change the percent and redraw the component makes no difference when I have this in a separate project.
Solved: I was clearing the values at the wrong spot in my program. I'll leave this question published.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JTextArea;
public class BarGraphText extends JTextArea {
double percentFilled;
Color fillColor;
BarGraphText( String s )
{
super(s);
setOpaque(false);
percentFilled = 0.0;
}
#Override
public void paintComponent( Graphics g )
{
int width, height;
Rectangle bounds = g.getClipBounds();
if( bounds != null )
{
height = bounds.height;
width = bounds.width;
}
else
{
System.err.println("Error [BarGraphText]: Clipping bounds unknown.");
height = width = 0;
}
g.setColor(fillColor);
g.fillRect(0, 0, (int) (width*percentFilled), height);
super.paintComponent(g);
}
public void setPercent( int myResp, int totResp )
{
percentFilled = (float)myResp / totResp;
}
public void setColor( Color c )
{
fillColor = c;
}
}
I was clearing the values at the wrong spot in my program. This is not in the code published above.
I tried making a program that flips a coin(shows image of heads first and later shows image of tails) and I encountered problems trying to have the image of the coin viewed when I ran the problem; only a blank screen would show. I don't know whether this is from an improper saving method of the jpg images or from an error in the code. I also came across an error before again coding the program where I had the heads image show and tails image not show.
CoinTest.java runs coin runner and Coin.java is the class for the program.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CoinTest extends JPanel
implements ActionListener
{
private Coin coin;
public CoinTest ()
{
Image heads = (new ImageIcon("quarter-coin-head.jpg")).getImage();
Image tails = (new ImageIcon("Indiana-quarter.jpg")).getImage();
coin = new Coin(heads, tails);
Timer clock = new Timer(2000, this);
clock.start();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int x = getWidth() / 2;
int y = getHeight() / 2;
coin.draw(g, x, y);
}
public void actionPerformed(ActionEvent e)
{
coin.flip();
repaint();
}
public static void main(String[] args)
{
JFrame w = new JFrame("Flipping coin");
w.setSize(300, 300);
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CoinTest panel = new CoinTest();
panel.setBackground(Color.WHITE);
Container c = w.getContentPane();
c.add(panel);
w.setVisible(true);
}
}
Now the actual Coin class.
import java.awt.Image;
import java.awt.Graphics;
public class Coin
{
private Image heads;
private Image tails;
private int side = 1;
public Coin(Image h, Image t)
{
heads = h;
tails = t;
}
//flips the coin
public void flip()
{
if (side == 1)
side = 0;
else
side = 1;
}
//draws the appropriate side of the coin - centered in the JFrame
public void draw(Graphics g, int x, int y)
{
if (side == 1)
g.drawImage(heads, heads.getWidth(null)/3, heads.getHeight(null)/3, null);
else
g.drawImage(heads, tails.getWidth(null)/3, tails.getHeight(null)/3, null);
}
}
Firstly, ensure that both images are in the correct location to load.
Secondly, you have a typo here:
if (side == 1)
g.drawImage(heads, heads.getWidth(null)/3, heads.getHeight(null)/3, null);
else
g.drawImage(heads, tails.getWidth(null)/3, tails.getHeight(null)/3, null);
^^^^
should be tails...
The width and height of the applet are coded in the tag. The code that draws the applet uses the two methods to get these values at run time. So now, different tags can ask for the same applet to paint different sized rectangles. The source code does not need to be recompiled for different sizes.
Having problems loading/showing an image in a java applet. Not sure if I'm loading the image incorrectly or if I'm accessing it incorrectly. Here's the code that draws the ship and the background(it's an asteroid-like game). The background draws correctly but the ship doesn't. This is the main class method that I'm dealing with:
public void paintFrame(Graphics g) {
Dimension d = size();
g.fillRect(0, 0, d.width, d.height);
g.drawImage(ship.getImage(), d.width/2, d.height/2, null);
}
I create an instance of the ship class at the beginning of the class. However, if I try to instantiate the ship class in a method (such as "Ship ship = new Ship();), it says the variable "ship" is never used.
Here's the entire ship class:
public class Ship {
private int dx;
private int dy;
private int x;
private int y;
private Image image;
public Ship() {
ImageIcon ii = new ImageIcon("ship1.png");
image = ii.getImage();
}
public Image getImage() {
return image;
}
}
If I run it as is, it runs without errors, but it doesn't display the ship. If I try to create the instance of the ship anywhere else except at the top, it gives me a NullPointerException.
Update
Here's my entire main class:
import java.applet.Applet;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
public class RunGame extends Applet implements Runnable {
int frame;
int delay;
Thread animator;
Ship ship = new Ship();
Level level;
Dimension offDimension;
Image offImage;
Graphics offGraphics;
/**
* Initialize the applet and compute the delay between frames.
*/
public void init() {
String str = getParameter("fps");
int fps = (str != null) ? Integer.parseInt(str) : 10;
delay = (fps > 0) ? (1000 / fps) : 100;
}
/**
* Method is called when the applet becomes visible on
* the screen.
*/
public void start() {
animator = new Thread(this);
animator.start();
}
/**
* This method is called by the thread that was created in
* the start method. It does the main animation.
*/
public void run() {
// Remember the starting time
long tm = System.currentTimeMillis();
while (Thread.currentThread() == animator) {
// Display the next frame of animation.
repaint();
// Delay depending on how far we are behind.
try {
tm += delay;
Thread.sleep(Math.max(0, tm - System.currentTimeMillis()));
} catch (InterruptedException e) {
break;
}
// Advance the frame
frame++;
}
}
/**
* This method is called when the applet is no longer
* visible. Set the animator variable to null so that the
* thread will exit before displaying the next frame.
*/
public void stop() {
animator = null;
offImage = null;
offGraphics = null;
}
/**
* Update a frame of animation.
*/
public void update(Graphics g) {
Dimension d = size();
// Create the offscreen graphics context
if ((offGraphics == null) || (d.width != offDimension.width) || (d.height != offDimension.height)) {
offDimension = d;
offImage = createImage(d.width, d.height);
offGraphics = offImage.getGraphics();
}
// Erase the previous image
offGraphics.setColor(getBackground());
offGraphics.fillRect(0, 0, d.width, d.height);
offGraphics.setColor(Color.black);
// Paint the frame into the image
paintFrame(offGraphics);
// Paint the image onto the screen
g.drawImage(offImage, 0, 0, null);
}
/**
* Paint the previous frame (if any).
*/
public void paint(Graphics g) {
if (offImage != null) {
g.drawImage(offImage, 0, 0, null);
}
}
/**
* Paint a frame of animation.
*/
public void paintFrame(Graphics g) {
Dimension d = size();
g.fillRect(0, 0, d.width, d.height);
//g.drawImage(level.getImage(), 0, 0, null);
g.drawImage(ship.getImage(), 400, 300, null);
}
}
ImageIcon ii = new ImageIcon("ship1.png");
The ImageIcon constructor that accepts a String presumes the string represents ..
..a file name or a file path.
'Applets and files do not mix.' Only a trusted applet can load a File object, and even then, only from the file system of the end user. The File objects cannot point back to the server.
Applets would more typically work with paths formed from an URL. The Applet class provides a number of methods to help form that URL, and the ImageIcon constructor is overloaded to accept an URL.
..Not sure if I'm loading the image incorrectly or if I'm accessing it incorrectly.
Debugging 101 is to display the image immediately after loading it. Drop the image icon into a label and use a JOptionPane to show the label.
Without seeing your full code we can't really tell what's going on with the exception, but in your call to drawImage() specify 'this' as the last parameter instead of null. Images are loaded asynchronously and so they need something that implements ImageObserver (such as your frame) to tell when they have managed to load a bit more (or all of it) - so that they can repaint.