first of all thanks for taking the time to read my question!
I'm developing a 2D java game, with a tile based map. When my character moves around,
everthing is fine, although when he moves left, vertical white lines, also know as artifacting/tearing, appear on the screen. Same thing happens when he moves up, though the lines are horizontal and much smaller in width. Oddly enough, this doesn't happen when I move right or down. I have searched around on internet to find a solution, though I haven't encountered anything that fit my problem.
Here's the code, although I've heavily downsized and simplified it for the sake of testing. It can therefore be run without any images. Thank you for any answer you provide!
package adriana;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
/**
*
* #author Christophe
*/
public class Main extends JFrame implements Runnable{
public Image dbImage;
public Graphics dbGraphics;
//Image + Array size
final static int listWidth = 500, listHeight = 500;
//Move Variables
int playerX = 320, playerY = 240, xDirection, yDirection;
//Sprites
BufferedImage spriteSheet;
//Lists for sprite sheet: 1 = STILL; 2 = MOVING_1; 3 = MOVING_2
BufferedImage[] ARCHER_NORTH = new BufferedImage[4];
BufferedImage[] ARCHER_SOUTH = new BufferedImage[4];
BufferedImage[] ARCHER_EAST = new BufferedImage[4];
BufferedImage[] ARCHER_WEST = new BufferedImage[4];
Image[] TILE = new Image[12];
//Animation Variables
int currentFrame = 0, framePeriod = 150;
long frameTicker = 0l;
Boolean still = true;
Boolean MOVING_NORTH = false, MOVING_SOUTH = false, MOVING_EAST = false, MOVING_WEST = false;
BufferedImage player;
//World Tile Variables
//20 X 15 = 300 tiles
Rectangle[][] blocks = new Rectangle[listWidth][listHeight];
int tileX = 250, tileY = 250;
Rectangle playerRect = new Rectangle(playerX + 4,playerY+20,32,20);
//Map Navigation
static final byte PAN_UP = 0, PAN_DOWN = 1, PAN_LEFT = 2, PAN_RIGHT = 3;
final int speed = 8;
public Main(){
this.setTitle("JAVA4K");
this.setSize(640,505);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
addKeyListener(new AL());
for(int y = 0; y < listHeight; y++){
for(int x = 0; x < listWidth; x++){
blocks[x][y] = new Rectangle(x*32-8000, y*32-8000, 32, 32);
}
}
}
//Key Listener
public class AL extends KeyAdapter{
public void keyPressed(KeyEvent e){
int keyInput = e.getKeyCode();
still = false;
if(keyInput == e.VK_LEFT){
navigateMap(PAN_RIGHT);
}if(keyInput == e.VK_RIGHT){
navigateMap(PAN_LEFT);
}if(keyInput == e.VK_UP){
navigateMap(PAN_DOWN);
}if(keyInput == e.VK_DOWN){
navigateMap(PAN_UP);
}
}
public void keyReleased(KeyEvent e){
int keyInput = e.getKeyCode();
setYDirection(0);
setXDirection(0);
if(keyInput == e.VK_LEFT){
}if(keyInput == e.VK_RIGHT){
}if(keyInput == e.VK_UP){
}if(keyInput == e.VK_DOWN){
}
}
}
public void moveMap(){
for(int a = 0; a < 500; a++){
for(int b = 0; b < 500; b++){
if(blocks[a][b] != null){
blocks[a][b].x += xDirection;
blocks[a][b].y += yDirection;
}
}
}
}
public void navigateMap(byte pan){
switch(pan){
default:
System.out.println("Unrecognized pan!");
break;
case PAN_UP:
setYDirection(-1 * speed);
break;
case PAN_DOWN:
setYDirection(+1 * speed);
break;
case PAN_LEFT:
setXDirection(-1 * speed);
break;
case PAN_RIGHT:
setXDirection(+1 * speed);
break;
}
}
public void setXDirection(int xdir){
xDirection = xdir;
if(blocks[0][0] != null) tileX = ((playerRect.x - blocks[0][0].x) / 32)-1;
}
public void setYDirection(int ydir){
yDirection = ydir;
if(blocks[0][0] != null) tileY = ((playerRect.y - blocks[0][0].y) / 32)-1;
}
public void paint(Graphics g){
dbImage = createImage(getWidth(), getHeight());
dbGraphics = dbImage.getGraphics();
paintComponent(dbGraphics);
g.drawImage(dbImage, 0, 25, this);
}
public void paintComponent(Graphics g){
requestFocus();
//Draws tiles and rectangular boundaries for debugging
for(int a = tileX - 18; a < tileX + 20; a++){
for(int b = tileY - 15; b < tileY + 17; b++){
g.setColor(Color.RED);
g.fillRect(blocks[a][b].x, blocks[a][b].y, 32, 32);
g.setColor(Color.BLACK);
g.drawRect(blocks[a][b].x, blocks[a][b].y, 32, 32);
}
}
//Draw player
g.drawRect(playerX, playerY, 40, 40);
repaint();
}
public void run(){
try{
System.out.println("Running");
while(true){
moveMap();
Thread.sleep(13);
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Main main = new Main();
//Threads
Thread thread1 = new Thread(main);
thread1.start();
}
}
You have no synchronization between the thread updating the map and the drawing. So the map can be in inconsistent state during drawing.
A quick fix would be wrapping the map updating and drawing in a synchronized block:
synchronized(blocks) {
// drawing or modification here
}
(or just making the whole methods synchronized)
Also, the other fields (like those modified in the key listener) are also susceptible being in inconsistent state.
There are other problems too:
Don't override paint() of a frame. Instead override paintComponent() of a JPanel. There's no need to create an image at every redraw, the swing painting mechanism is by default double buffered. See custom painting in swing.
Use KeyBindings instead of a KeyListener
Swing components must be accessed (and created) only in the event dispatch thread.
Related
The essential question of what I am trying to ask is: How would I make a graph in which the line at y=0 is actually at y=*some number* and that as my Y increases, Java's y decreases?
I am attempting to make a line graph which documents change of populations of animals. I need to make the graph's Y=0 around the y=980 line. I also need to make something that would notice an increase in population and graph it as a decrease in y (to make the line go UP). What I'm trying to say is that I need to create a line graph that looks like a line graph.
I have attempted multiple different things, which each give me a different result based on different inputs. I have successfully created the graph that starts around the y=980 line and goes up, as shown by the image below. The method I used for this was to draw the lines (for the graph itself) and then to take the absolute value of the difference of 10 times the value minus 90, as shown by
g.drawLine((125), (y+1)*93, width, (y+1)*93);
g.drawString(Math.abs(y*10-90) + " alive", (80), (y+1)*93);
This completely works for the graph, but when trying to implement it into the graphing of the lines, I receive mixed results.
For this project, I have 3 populations: predators of my animal, prey of my animal, and my animal. I want to graph the population size of all of these. When using the previously shown method, I successfully graph the first value of the population of the predators. The other two, however, are on the opposite side of the graph from where they must be. (e.g. my animal's population size is set to start at 90, but on the graph it is around the mid-20s, as also shown by the graph below). The code I used for each of these is:
// predator animal line
g.setColor(Color.red);
g.drawLine(i*93+125, (Math.abs((predatorAnimalAmt[i]*10)-90)), (i+1)*93+125, (Math.abs((predatorAnimalAmt[i+1]*10)-90)));
g.drawLine(100, height-45, 120, height-45);
g.drawString("PREDATOR", 30, height-40);
// prey animal line
g.setColor(Color.green);
g.drawLine(i*93+125, (Math.abs((preyAnimalAmt[i]*10)-90)), (i+1)*93+125, (Math.abs((preyAnimalAmt[i+1]*10)-90)));
g.drawLine(100, height-60, 120, height-60);
g.drawString("PREY", 30, height-55);
// our animal's line
g.setColor(Color.blue);
g.drawLine(i*93+125, (Math.abs((ourAnimalAmt[i]*10)-90)), (i+1)*93+125, (Math.abs((ourAnimalAmt[i+1]*10)-90)));
g.drawLine(100, height-75, 120, height-75);
g.drawString("OUR ANIMAL", 30, height-70);
Here is my code in this class (there are other classes accessible in my github page)
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.util.ArrayList;
import java.util.Random;
// ALL OF MY CLASSES
import natural.selection.main.animalia.Animal;
import natural.selection.main.animalia.OurAnimal;
import natural.selection.main.animalia.Predator;
import natural.selection.main.animalia.Prey;
public class MainApp implements Runnable {
private Display display;
private int width, height;
public String title;
private boolean running = false;
private Thread thread;
private Random random = new Random();
private BufferStrategy bs;
private Graphics g;
private ArrayList<Animal> allAnimals = new ArrayList<>();
private int[] animalAmt = new int[20];
private ArrayList<Prey> allPreyAnimals = new ArrayList<>();
private int[] preyAnimalAmt = new int[20];
private ArrayList<OurAnimal> allOurAnimals = new ArrayList<>();
private int[] ourAnimalAmt = new int[20];
private ArrayList<Predator> allPredatorAnimals = new ArrayList<>();
private int[] predatorAnimalAmt = new int[20];
public MainApp(String title, int width, int height){
this.width = width;
this.height = height;
this.title = title;
}
private void init(){
display = new Display(title, width, height);
for(int i=0; i<90; i++) {
OurAnimal animal = new OurAnimal(random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10));
allOurAnimals.add(animal);
allAnimals.add(animal);
}
for(int i=0; i<(80+random.nextInt(10)); i++) {
Prey animal = new Prey(random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10));
allPreyAnimals.add(animal);
allAnimals.add(animal);
}
for(int i=0; i<(50+random.nextInt(10)); i++) {
Predator animal = new Predator(random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10), random.nextInt(10));
allPredatorAnimals.add(animal);
allAnimals.add(animal);
}
animalAmt[0] = allAnimals.size();
preyAnimalAmt[0] = allPreyAnimals.size();
ourAnimalAmt[0] = allOurAnimals.size();
predatorAnimalAmt[0] = allPredatorAnimals.size();
}
private void tick() {
}
// Amount of weeks to simulate
private int weeksToSim = 20;
private void render(){
bs = display.getCanvas().getBufferStrategy();
if(bs == null){
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
//Clear Screen
g.clearRect(0, 0, width, height);
//Draw Here!
g.setColor(Color.black);
// LINE GRAPH OF SPECIES COUNT
// Draw the graph
for(int x=0; x<weeksToSim; x++) {
g.drawLine(x*93+125, height-100, x*93+125, 0);
g.drawString("Week " + (x+1), x*93+125-20, height-80);
}
for(int y=0; y<10; y++) {
g.drawLine((125), (y+1)*93, width, (y+1)*93);
g.drawString(Math.abs(y*10-90) + " alive", (80), (y+1)*93);
}
// Draw the line
for(int i=0; i<(animalAmt.length-1); i++) {
// predator animal line
g.setColor(Color.red);
g.drawLine(i*93+125, (Math.abs((predatorAnimalAmt[i]*10)-90)), (i+1)*93+125, (Math.abs((predatorAnimalAmt[i+1]*10)-90)));
g.drawLine(100, height-45, 120, height-45);
g.drawString("PREDATOR", 30, height-40);
// prey animal line
g.setColor(Color.green);
g.drawLine(i*93+125, (Math.abs((preyAnimalAmt[i]*10)-90)), (i+1)*93+125, (Math.abs((preyAnimalAmt[i+1]*10)-90)));
g.drawLine(100, height-60, 120, height-60);
g.drawString("PREY", 30, height-55);
// our animal's line
g.setColor(Color.blue);
g.drawLine(i*93+125, (Math.abs((ourAnimalAmt[i]*10)-90)), (i+1)*93+125, (Math.abs((ourAnimalAmt[i+1]*10)-90)));
g.drawLine(100, height-75, 120, height-75);
g.drawString("OUR ANIMAL", 30, height-70);
}
//End Drawing!
bs.show();
g.dispose();
}
public void run(){
init();
int fps = 60;
double timePerTick = 1000000000 / fps;
double delta = 0;
long now;
long lastTime = System.nanoTime();
long timer = 0;
int ticks = 0;
Toolkit.getDefaultToolkit().sync();
while(running){
now = System.nanoTime();
delta += (now - lastTime) / timePerTick;
timer += now - lastTime;
lastTime = now;
if(delta >= 1){
tick();
render();
ticks++;
delta--;
}
if(timer >= 1000000000){
System.out.println("Ticks and Frames: " + ticks);
ticks = 0;
timer = 0;
}
}
stop();
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
public synchronized void start(){
if(running)
return;
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop(){
if(!running)
return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
How would I go about making all of the points correct with just one formula without completely disrupting all of my code?
Here is what I get as a result of my code:
Let's extract this functionality into two functions that calculate the correct x and y position for a given week and count. This way, you can change the chart layout by just changing a single function instead of multiple lines scattered throughout the code:
private int getChartX(double week) {
return (int)Math.round(week * 93 + 32);
}
private int getChartY(double count) {
return (int)Math.round(930 - count * 9.3);
}
Using these functions, you can re-structure your drawing code like this:
// Draw the graph
for(int week = 1; week <= weeksToSim; week++) {
int x = getChartX(week);
g.drawLine(x, getChartY(0), x, 0);
g.drawString("Week " + week, x-20, getChartY(0) + 20);
}
for(int y=0; y<10; y++) {
int count = 10 * y;
int y = getChartY(count);
g.drawLine(getChartX(1), y, width, y);
g.drawString(count + " alive", getChartX(1) - 45, y);
}
// Draw the line
for(int i=0; i<(animalAmt.length-1); i++) {
int week = i + 1;
// predator animal line
g.setColor(Color.red);
g.drawLine(getChartX(week), getChartY(predatorAnimalAmt[i]), getChartX(week + 1), getChartY(predatorAnimalAmt[i + 1]));
...
}
So I am working on a game where an actor is moved by the user in a grid, and if the space the user is trying to move the actor to of a certain type, then it can be moved to. The grid is made up of a class that I know works called PGrid, so the grid is made of PGrids. The problem is the keyListener does not function at all, not even print out 'hi.' Below is the code for PGame and moveGrid, where PGame handles moveGrid stuff but moveGrid draws out the grid (as it is the JPanel). I tried moving the keylistener from PGame to moveGrid, but it did not work.
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
//where all the different classes are put together
public class PGame implements KeyListener{ //may want to move the listener to moveGrid
private static moveGrid panel = new moveGrid(8,8); //taking something from moveGrid
private static PActor pguy = new PActor("Bill", 2, 2);
private boolean shallmove = false;
private int newx, newy;
public static void main(String[] args){
//create a new frame
JFrame frame = new JFrame("PGame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//moveGrid panel = new moveGrid(8,8); //taking something from moveGrid
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
panel.requestFocus();
//creating a "path" of places able to move to
moveGrid.pgrids.get(0).changeType(1);
moveGrid.pgrids.get(1).changeType(1);
moveGrid.pgrids.get(2).changeType(1);
moveGrid.pgrids.get(3).changeType(1);
moveGrid.pgrids.get(10).changeType(1);
moveGrid.pgrids.get(11).changeType(1);
moveGrid.pgrids.get(19).changeType(1);
moveGrid.pgrids.get(27).changeType(1);
//moveGrid.pgrids.get(4).changeType(2);
//start our pguy out in a position
PGrid pguystart = new PGrid(2,0,0);
moveGrid.pgrids.set(0,pguystart);
panel.repaint();
}
public void keyPressed(KeyEvent e) {
//here test if the grid can be updated
//Test:
pguy.canMove(3,3);
pguy.Move(4,3,3);
if(e.getKeyCode() == KeyEvent.VK_UP){
newx = pguy.getx();
newy = pguy.gety() - 1;
shallmove = pguy.canMove(newx,newy);
System.out.println("Hi");
}else if(e.getKeyCode() == KeyEvent.VK_DOWN){
newx = pguy.getx();
newy = pguy.gety() + 1;
shallmove = pguy.canMove(newx,newy);
} else if(e.getKeyCode() == KeyEvent.VK_LEFT){
newx = pguy.getx() - 1;
newy = pguy.gety();
shallmove = pguy.canMove(newx,newy);
}else if(e.getKeyCode() == KeyEvent.VK_RIGHT){
newx = pguy.getx() + 1;
newy = pguy.gety();
shallmove = pguy.canMove(newx,newy);
}
}
public void keyReleased(KeyEvent e) {
System.out.println("Hi");
//update the grid if it can here
//somewhere in here add this:
//moveGrid.repaint(); //tell movegrid to repaint
if(shallmove = true){
//change a certain spot to the actor
PGrid temp = new PGrid(2,newx,newy);
moveGrid.pgrids.set(pguy.getplace(),temp);
//need to also change to old space to be back to what it was....
//*here*
pguy.Move(pguy.newPos,newx, newy);
panel.repaint();
}
}
public void keyTyped(KeyEvent e) { }
}
moveGrid:
//a grid in which stuff can move
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;
public class moveGrid extends JPanel {
private int height;
private int width;
private int newx, newy;
private static PActor pguy = new PActor("Bill", 2, 2);
private boolean shallmove = false;
public static ArrayList<PGrid> pgrids = new ArrayList<PGrid>(); //an array full of grid boxes with type PGrid
public moveGrid(int height, int width){
this.height = height;
this.width = width;
setPreferredSize(new Dimension(800, 800));
//make all the values in pgrids equal to "Water" and give them locations
int i = 0;
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
PGrid pnull = new PGrid(0, x, y);
pgrids.add(i, pnull);
i++;
}
}
//drawGrid();
}
/*public void drawGrid(Graphics g){
g.drawRect(x,y,20,20);
} */
public void addNotify() {
super.addNotify();
requestFocus();
}
public void paintComponent(Graphics g){
//PGrid curLoc = new PGrid(height, height, height);
//go through and draw out the grid
int q = 0;
int midx = 0; //need to make these somehow so the squares get drawn at the center
int midy = 0;
for(int qh = 0; qh < height; qh++){
for(int qw = 0; qw < width; qw++){
PGrid pcur = pgrids.get(q); //
int p = pcur.getType();
if(p == 0){
//may want to import a water looking image
g.setColor(Color.BLUE);
g.fillRect((40*qw)+midx,(40*qh)+midy,40,40);
g.setColor(Color.BLACK);
g.drawRect((40*qw)+midx,(40*qh)+midy,40,40);
}else if(p == 1){
//may want to import a better image
g.setColor(Color.GREEN);
g.fillRect((40*qw)+midx,(40*qh)+midy,40,40);
}else if(p == 2){
//draws the "character"
g.setColor(Color.ORANGE);
g.fillRect((40*qw)+midx,(40*qh)+midy,40,40);
}
q++;
}
}
//here draw the character in the proper position
//so like multiply the x and y by 40
}
}
I also may have an error in the PActor class, which is supposedly the Actor that moves.
public class PActor {
private String name;
private int curx, cury;
int newPos;
public PActor(String name, int curx, int cury){
this.name = name;
this.curx = curx;
this.cury = cury;
}
public boolean canMove(int x, int y){
boolean abletomove = false;
//test if the space that the user is trying to moveto can be moved to
//use indexOf(a temp variable with values x and y also with type 1) to test
PGrid togo = new PGrid(1,x,y);
//now scan through pgrids in moveGrid to see the desired spot can be moved to
for(int s = 0; s <= moveGrid.pgrids.size(); s++){
PGrid temp = moveGrid.pgrids.get(s);
//test if the temp space is equal
if((togo.getType() == temp.getType()) && (togo.getx() == temp.getx()) && (togo.gety() == temp.gety())){
abletomove = true;
newPos = s;
break; //stop scanning, as it is now unnecessary
}
else{ //do nothing
}
}
//now test pgrids to see if there is a spot like such that is moveable
return abletomove;
}
public int getplace(){
return newPos;
}
public int getx(){
return curx;
}
public int gety(){
return cury;
}
public void Move(int pos, int x, int y){
PGrid temp = new PGrid(2,x,y);
moveGrid.pgrids.set(pos,temp);
}
public String toString(){
return name + " ";
}
}
Suggestions:
Again, for any Java Swing listener to work, the listener must be added to a component. For instance for a KeyListener to work, you first need to add it to the component that you wish to have listened to by calling addKeyListener(myKeyListener) on that component.
For a KeyListener to work, the component being listened to must have the keyboard focus. This means that if your'e listening to a JPanel, you first have to make it focusable by calling setFocusable(true) on the JPanel and then you need to request focus, such as by calling requestFocusInWindow() on it.
If something later steals the focus, such as a JButton that has been pushed or a text component, then the KeyListener will no longer work.
To get around this, we usually recommend that you use Key Bindings in place of KeyListeners, but note that the set up of these is completely different from that of KeyListeners, and you would need to throw all assumptions to the side and study the tutorial on use of these first.
If still stuck, then you will want to create and post a minimal example program, a small program that has a lot less code than the program you've posted, yet that compiles for us, runs, and that shows us directly your problem.
Links:
KeyListener Tutorial
Key Bindings tutorial
I have been looking at this one for a while and it doesn't make sense. When I start my program, I set the background color of a grid of rectangles to a specific color using the SetupLawn call from my main function in a different file. That part works fine. Then when I try to change the color later by calling the SetupLawn function again with a new color it doesn't change. I put prints in the SetupLawn function and the color I am passing is getting there correctly but as soon as paintComponent gets called the value of currentColor is back to being what it was originally. I verified this with a debug print as well.
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class Lawn
{
public static class SetupLawn extends JPanel
{
private List<Point> lawn;
Color currentColor;
public SetupLawn(Color color)
{
lawn = new ArrayList<>(25);
currentColor = color;
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Point cell : lawn)
{
int cellX = cell.x;
int cellY = cell.y;
g.setColor(currentColor);
g.fillRect(cellX, cellY, 50, 50);
}
//Set grid color and draw border
g.setColor(Color.BLACK);
g.drawRect(10, 10, 800, 500);
for (int i = 10; i <= 800; i += 50)
{
g.drawLine(i, 10, i, 510);
}
for (int i = 10; i <= 500; i += 50)
{
g.drawLine(10, i, 810, i);
}
for (int i = 60; i <= 750; i += 50)
{
for (int j =60; j <= 450; j += 50)
{
lawn.add(new Point(i, j));
}
}
}
}
}
Here is the way I was calling it.
Lawn.SetupLawn lawn = new Lawn.SetupLawn(spring);
I can see now why this method was not a good idea. It was really late at night when I was working on this and Java is not a strong language for me.
There is something in the following code that I am unable to understand. After digging through google for a while, I decided it would be better to ask someone.
I am following a game programming tutorial on youtube, and I feel I understand (to some degree) everything I have written, except for some lines which concern the rendering part of the program.
package com.thomas.game;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
import com.thomas.game.graphics.Screen;
import com.thomas.game.input.Keyboard;
public class Game extends Canvas implements Runnable {
private static final int WIDTH = 300;
private static final int HEIGHT = (WIDTH / 16) * 9;
private static final int SCALE = 3;
private static final String TITLE = "Game";
private JFrame frame;
private Thread thread;
private Screen screen;
private BufferedImage image;
private Keyboard key;
private int[] pixels;
private boolean running = false;
private int x = 0, y = 0;
public Game() {
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
screen = new Screen(WIDTH, HEIGHT);
frame = new JFrame();
initializeFrame();
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
this is what I don't understand. I get the image raster, from which I get the databuffer. I typecast that databuffer into a (DatabufferInt), which allows me to retrieve an int[] through the getData() method. After this is done, pixel.length has a value of 48600, and every index contains the int value 0. Operating with this int[] makes the program render like it is supposed to. However, if I don't typecast and retrieve the int[] in the above manner, and instead say pixels = new int[48600], i end up with a black screen.
I guess what I want to know is: what is the difference between these two int[], or rather, what makes the first one work? How does it work?
key = new Keyboard();
addKeyListener(key);
setFocusable(true);
}
public void run() {
long lastTime = System.nanoTime();
double nsPerTick = 1E9/60;
double delta = 0;
long now;
int ticks = 0;
int frames = 0;
long timer = System.currentTimeMillis();
while(running) {
now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
while(delta >= 1) {
tick();
ticks++;
delta--;
}
render();
frames++;
if(System.currentTimeMillis() - timer >= 1000) {
timer += 1000;
frame.setTitle(TITLE + " | ups: " + ticks + " fps: " + frames);
ticks = 0;
frames = 0;
}
}
}
private void render() {
BufferStrategy bs = getBufferStrategy(); // retrieves the bufferstrategy from the current component (the instance of Game that calls this method)
if(bs == null) {
createBufferStrategy(3);
return;
}
screen.clear();
screen.render(x, y);
getPixels();
Graphics g = bs.getDrawGraphics(); // retrieves a graphics object from the next in line buffer in the bufferstrategy, this graphics object draws to that buffer
g.drawImage(image, 0, 0, getWidth(), getHeight(), null); // draws the bufferedimage to the available buffer
g.dispose();
bs.show(); // orders the next in line buffer (which the graphics object g is tied to) to show its contents on the canvas
}
private void tick() {
key.update();
if(key.up)
y--;
if(key.down)
y++;
if(key.left)
x--;
if(key.right)
x++;
}
public void initializeFrame() {
frame.setTitle(TITLE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(this);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public synchronized void start() {
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop() {
running = false;
try {
thread.join();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Game game = new Game();
game.start();
}
public void getPixels() {
for(int i = 0; i < pixels.length; i++)
pixels[i] = screen.pixels[i];
}
}
It seems like the bufferedimage gets values from the pixels array. But I don't understand how these two communicate, or how they are connected. I haven't explicitly told the bufferedimage to get its pixels from the pixels array, so how does it know?
I will also attach the Screen class, which is responsible for updating the pixels array.
package com.thomas.game.graphics;
import java.util.Random;
public class Screen {
private int width, height;
public int[] pixels;
private final int MAP_SIZE = 64;
private final int MAP_SIZE_MASK = MAP_SIZE - 1;
private int[] tiles;
private int tileIndex;
private int xx, yy;
private Random r;
public Screen(int w, int h) {
width = w;
height = h;
pixels = new int[width * height];
tiles = new int[MAP_SIZE * MAP_SIZE];
r = new Random(0xffffff);
for(int i = 0; i < tiles.length; i++) {
tiles[i] = r.nextInt();
}
tiles[0] = 0;
}
public void clear() {
for(int i = 0; i < pixels.length; i++)
pixels[i] = 0;
}
public void render(int xOffset, int yOffset) {
for(int y = 0; y < height; y++) {
yy = y + yOffset;
for(int x = 0; x < width; x++) {
xx = x + xOffset;
tileIndex = (yy >> 4 & MAP_SIZE_MASK) * MAP_SIZE + (xx >> 4 & MAP_SIZE_MASK);
pixels[y * width + x] = tiles[tileIndex];
}
}
}
}
I really hope someone can explain this to me, it would be greatly appreciated. The program is working like it is supposed to, but I don't feel comfortable continuing on the tutorial until I grasp this.
Basic types like short, int, long etc are not Objects.
However, int[] is an array. Arrays are objects in java. Java manipulates objects by reference, not value.
In this line you are not creating a new object. You are storing a reference to the object int[] in your variable pixels. Anything you change in pixels, gets changed inside of the int[] object in image:
pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
I've created an example, try running this code:
public class Data {
private int[] values = {25,14};
public int[] getValues() {
return values;
}
public static void main(String[] args) {
Data d = new Data();
System.out.println(d.getValues()[0]);
int[] values = d.getValues();
values[0] = 15;
System.out.println(d.getValues()[0]);
}
}
Output:
25
15
Note that you have this code...
pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
while it should be like this...
pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
Change image to img.
Hope it works!
I'm begining a little project to create a simple checkers game. However it's been a long time since I've used the java GUI tools. The goal of the code at this point is to draw the initial board (red pieces at top, black at bottom). However all I get when I run the code is a blank frame. I'm also a little uncertain if my circle drawing code will do what I want (ie create solid red or black circles inside certain squares) Here is the code. Thanks in advance for any help/suggestions
EDIT: I should probably alternate drawing blue and gray squares or else the thing will probably just be a giant blue blob, however I'll settle for a giant blue blob at this point :p
import javax.swing.*;
import java.awt.*;
public class CheckersServer
{
public static class Board
{
private JFrame frame = new JFrame();
private JPanel backBoard = new JPanel();
Board()
{
frame.setSize(905,905);
backBoard.setSize(900,900);
frame.setTitle("Checkers");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
backBoard.setVisible(true);
boardSquare bs;
String type = null;
//Filling in Red Side
for (int i = 0; i <=1; i++)
{
for(int j = 0; j < 9; j++)
{
if(j % 2 == 0)
{
type = "Red";
}
else
{
type = "Blank";
}
bs = new boardSquare(100*j,100*i,type);
backBoard.add(bs);
}
}
//Filling in empty middle
type = "Blank";
for (int i = 2; i < 7; i++)
{
for(int j = 0; j < 9; j++)
{
bs = new boardSquare(100*j,100*i,type);
backBoard.add(bs);
}
}
//Filling in Black side
for (int i = 7; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
if(j % 2 != 0)
{
type = "Black";
}
else
{
type = "Blank";
}
bs = new boardSquare(100*j,100*i,type);
backBoard.add(bs);
}
}
backBoard.repaint();
frame.add(backBoard);
frame.repaint();
}
private class boardSquare extends JComponent
{
private int x; //x position of the rectangle measured from top left corner
private int y; //y position of the rectangle measured from top left corner
private boolean isBlack = false;
private boolean isRed = false;
public boardSquare(int p, int q, String type)
{
x = p;
y = q;
if (type.equals("Black"))
{
isBlack = true;
isRed = false;
}
else if (type.equals("Red"))
{
isRed = true;
isBlack = false;
}
else if (type.equals("Blank"))
{
isBlack = false;
isRed = false;
}
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
Rectangle box = new Rectangle(x,y,100,100);
g2.draw(box);
g2.setPaint(Color.BLUE);
g2.fill(box);
if(isBlack)
{
g2.fillOval(x, y,100 ,100 );
g2.setColor(Color.black);
g2.drawOval(x, y, 100, 100);
}
else if(isRed)
{
g2.fillOval(x, y,100 ,100 );
g2.setColor(Color.red);
g2.drawOval(x, y, 100, 100);
}
}
}
}
public static void main(String[] args)
{
Board game = new Board();
}
}
You have several issues.
Java UI is layout-based, which means that when you add a component to a parent, the parent's layout determines where the child component will be placed. You don't have any code to set up the layout, and so your application is using the defaults (FlowLayout is the default, and this may work in your case, as long as your JFrame and children are the appropriate size).
The bigger problems are in your boardSquare class. By default, JPanels have a dimension of 10x10. You aren't specifying the size, and so all your squares are 10x10. You need to tell the squares how big they are. You can do this in the boardSquare constructor:
setPreferredSize(new Dimension(100, 100));
Finally, in your drawing code, you are doing an offset of x,y when drawing the squares and circles. This is an offset from the top-left corner of the component. Your components (after setting the size) will be 100x100 pixels. But if your x,y are greater than these values, you will be drawing outside of the bounds of the component. Instead, these values should be set to 0,0 because that is the top-left corner of the component you are drawing in.
By just setting the preferred size of the squares and setting x,y to 0, I was able to get the squares drawing in the frame, though it wasn't pretty. You will need to work on setting the correct layout before it will be laid out correctly.
Here are some hints:
Your BoardSquares have dimension 0x0. Not a good size for something you want to be visible to the user.
To help visualize what's going on, cause each BoardSquare to be 100x100 pixels in size, and give them a border. Now you can see where they are showing up in your GUI. Your GUI code still needs significant changes, but this will at least let you start seeing what you're dealing with.
public BoardSquare(int p, int q, String type)
{
this.setBorder(new LineBorder(Color.CYAN, 2));
this.setPreferredSize(new Dimension(100, 100));
// ... etc ...
BoardSquare seems to be coded to draw its contents based on coordinates from the absolute topmost leftmost point in the window, but they should be coded to draw themselves from the topmost leftmost point of the BoardSquare itself. That is, components should only draw within their own boundaries, and they should use coordinates that assume 0,0 designates the top,left of the component, not of the window.
If you want to use BoardSquares (JComponents) and add them to the frame, you probably should use a different layout manager, like GridLayout. FlowLayout won't give you the kind of precise positioning you want.
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
public class CheckersServer2
{
public static String type_BLANK = "BLANK";
public static String type_RED = "RED";
public static String type_BLACK = "BLACK";
public static int width = 100;
public static int height = 100;
public static class Board
{
private JFrame frame = new JFrame();
private JPanel backBoard = new JPanel();
Board()
{
int numRows = 8;
int numCols = 8;
frame.setSize(905,905);
backBoard.setSize(900,900);
frame.setTitle("Checkers");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
backBoard.setVisible(true);
String type;
for(int r=0; r<numRows; r++){
for(int c=0; c<numCols; c++){
//
type = type_BLANK;
if(c%2==0){
if(r==0 || r==2) {
type = type_RED;
}else if(r==6){
type = type_BLACK;
}
}else{
if(r==1){
type = type_RED;
} else if(r==5 || r==7) {
type = type_BLACK;
}
}
backBoard.add(new BoardSquare(r,c,type));
}
}
backBoard.repaint();
frame.add(backBoard);
frame.repaint();
}
private class BoardSquare extends JComponent
{
/**
*
*/
private static final long serialVersionUID = 1L;
private int x; //x position of the rectangle measured from top left corner
private int y; //y position of the rectangle measured from top left corner
private boolean isBlack = false;
private boolean isRed = false;
public BoardSquare(int p, int q, String type)
{
//this.setBorder(new LineBorder(Color.CYAN, 2));
this.setPreferredSize(new Dimension(width, height));
x = p;
y = q;
if (type.equals(type_BLACK))
{
isBlack = true;
isRed = false;
}
else if (type.equals(type_RED))
{
isRed = true;
isBlack = false;
}
else if (type.equals(type_BLANK))
{
isBlack = false;
isRed = false;
}
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
Rectangle box = new Rectangle(x,y,width,height);
g2.draw(box);
g2.setPaint(Color.BLUE);
g2.fill(box);
int ovalWidth = width - 15;
int ovalHeight = ovalWidth;
if(isBlack)
{
g2.setColor(Color.black);
g2.fillOval(x, y, ovalWidth, ovalHeight);
g2.drawOval(x, y, ovalWidth, ovalHeight);
}
else if(isRed)
{
g2.setColor(Color.red);
g2.fillOval(x, y, ovalWidth, ovalHeight);
g2.drawOval(x, y, ovalWidth, ovalHeight);
}
}
}
}
public static void main(String[] args)
{
Board game = new Board();
}
}